notbebooks initial
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
import sqlite3
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from scripts.spbt_day import (
|
||||
calculate_pair_theo_ret,
|
||||
calculate_ranked_pairs_theo_ret,
|
||||
load_selector_pair_rankings,
|
||||
load_trading_instructions,
|
||||
pair_assets_and_quote,
|
||||
parse_mr_score_final,
|
||||
rank_selector_pairs,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("raw_score", "expected_score", "expected_status"),
|
||||
[
|
||||
('{"final":"0.75"}', 0.75, "ok"),
|
||||
('{"final":0.5}', 0.5, "ok"),
|
||||
(None, None, "missing_mr_score"),
|
||||
("not-json", None, "malformed_json"),
|
||||
("[]", None, "unexpected_json_type"),
|
||||
('{"other": "0.1"}', None, "missing_final"),
|
||||
('{"final": ""}', None, "missing_final"),
|
||||
('{"final": true}', None, "non_numeric_final"),
|
||||
('{"final": "abc"}', None, "non_numeric_final"),
|
||||
('{"final": "NaN"}', None, "non_finite_final"),
|
||||
],
|
||||
)
|
||||
def test_parse_mr_score_final(raw_score, expected_score, expected_status):
|
||||
assert parse_mr_score_final(raw_score) == (expected_score, expected_status)
|
||||
|
||||
|
||||
def test_rank_selector_pairs_uses_dense_descending_rank_and_preserves_bad_rows():
|
||||
selector_pairs = pd.DataFrame(
|
||||
{
|
||||
"pair_name": ["PAIR_C", "PAIR_A", "PAIR_B", "PAIR_BAD"],
|
||||
"mr_score": [
|
||||
'{"final":"0.7"}',
|
||||
'{"final":"0.9"}',
|
||||
'{"final":"0.7"}',
|
||||
'{"final":"bad"}',
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
ranked = rank_selector_pairs(selector_pairs)
|
||||
|
||||
assert ranked["pair_name"].tolist() == ["PAIR_A", "PAIR_B", "PAIR_C", "PAIR_BAD"]
|
||||
assert ranked["pair_rank"].iloc[:3].tolist() == [1, 2, 2]
|
||||
assert pd.isna(ranked["pair_rank"].iloc[3])
|
||||
assert ranked["mr_score_parse_status"].tolist() == [
|
||||
"ok",
|
||||
"ok",
|
||||
"ok",
|
||||
"non_numeric_final",
|
||||
]
|
||||
|
||||
|
||||
def test_load_selector_pair_rankings_validates_required_table():
|
||||
conn = sqlite3.connect(":memory:")
|
||||
|
||||
with pytest.raises(ValueError, match="missing required table: selector_pairs"):
|
||||
load_selector_pair_rankings(conn)
|
||||
|
||||
|
||||
def test_load_selector_pair_rankings_reads_sqlite_table():
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.execute("CREATE TABLE selector_pairs (pair_name TEXT, mr_score TEXT)")
|
||||
conn.executemany(
|
||||
"INSERT INTO selector_pairs (pair_name, mr_score) VALUES (?, ?)",
|
||||
[
|
||||
("PAIR_A", '{"final":"0.1"}'),
|
||||
("PAIR_B", '{"final":"0.2"}'),
|
||||
],
|
||||
)
|
||||
|
||||
ranked = load_selector_pair_rankings(conn)
|
||||
|
||||
assert ranked[["pair_rank", "pair_name", "mr_score_final"]].to_dict("records") == [
|
||||
{"pair_rank": 1, "pair_name": "PAIR_B", "mr_score_final": 0.2},
|
||||
{"pair_rank": 2, "pair_name": "PAIR_A", "mr_score_final": 0.1},
|
||||
]
|
||||
|
||||
|
||||
def test_pair_assets_and_quote_parses_two_leg_pair():
|
||||
assert pair_assets_and_quote("ADA:USD-BTC:USD") == (("ADA", "BTC"), "USD")
|
||||
|
||||
|
||||
def test_calculate_pair_theo_ret_replaces_targets_and_closes_open_position():
|
||||
trd_inst_df = pd.DataFrame(
|
||||
{
|
||||
"time_ns": [1, 2, 3],
|
||||
"tstamp": ["t1", "t2", "t3"],
|
||||
"data": [
|
||||
(
|
||||
'{"action":"TARGET","quote_asset":"USD","assets":'
|
||||
'{"AAA":{"reference_price":"100","strength":"0.01"},'
|
||||
'"BBB":{"reference_price":"50","strength":"-0.02"}}}'
|
||||
),
|
||||
(
|
||||
'{"action":"TARGET","quote_asset":"USD","assets":'
|
||||
'{"AAA":{"reference_price":"120","strength":"0.01"},'
|
||||
'"BBB":{"reference_price":"60","strength":"-0.02"}}}'
|
||||
),
|
||||
(
|
||||
'{"action":"CLOSE","quote_asset":"USD","assets":'
|
||||
'{"AAA":{"reference_price":"132"},'
|
||||
'"BBB":{"reference_price":"54"}}}'
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
theo_ret = calculate_pair_theo_ret("AAA:USD-BBB:USD", trd_inst_df)
|
||||
|
||||
assert theo_ret == {
|
||||
"pair_name": "AAA:USD-BBB:USD",
|
||||
"realized_pnl": pytest.approx(24.0),
|
||||
"unrealized_pnl": 0.0,
|
||||
}
|
||||
|
||||
|
||||
def test_calculate_pair_theo_ret_ignores_unmatched_quote_and_close_without_target():
|
||||
trd_inst_df = pd.DataFrame(
|
||||
{
|
||||
"time_ns": [1, 2],
|
||||
"data": [
|
||||
(
|
||||
'{"action":"TARGET","quote_asset":"EUR","assets":'
|
||||
'{"AAA":{"reference_price":"100","strength":"0.01"},'
|
||||
'"BBB":{"reference_price":"50","strength":"-0.02"}}}'
|
||||
),
|
||||
(
|
||||
'{"action":"CLOSE","quote_asset":"USD","assets":'
|
||||
'{"AAA":{"reference_price":"110"},'
|
||||
'"BBB":{"reference_price":"45"}}}'
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
assert calculate_pair_theo_ret("AAA:USD-BBB:USD", trd_inst_df) == {
|
||||
"pair_name": "AAA:USD-BBB:USD",
|
||||
"realized_pnl": 0.0,
|
||||
"unrealized_pnl": 0.0,
|
||||
}
|
||||
|
||||
|
||||
def test_calculate_ranked_pairs_theo_ret_preserves_pairs_without_instructions():
|
||||
rankings = pd.DataFrame(
|
||||
{
|
||||
"pair_name": ["AAA:USD-BBB:USD", "CCC:USD-DDD:USD"],
|
||||
"pair_rank": pd.Series([1, 2], dtype="Int64"),
|
||||
}
|
||||
)
|
||||
trd_inst_df = pd.DataFrame(
|
||||
{
|
||||
"time_ns": [1, 2],
|
||||
"data": [
|
||||
(
|
||||
'{"action":"TARGET","quote_asset":"USD","assets":'
|
||||
'{"AAA":{"reference_price":"100","strength":"0.01"},'
|
||||
'"BBB":{"reference_price":"50","strength":"-0.02"}}}'
|
||||
),
|
||||
(
|
||||
'{"action":"CLOSE","quote_asset":"USD","assets":'
|
||||
'{"AAA":{"reference_price":"110"},'
|
||||
'"BBB":{"reference_price":"45"}}}'
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
result = calculate_ranked_pairs_theo_ret(rankings, trd_inst_df)
|
||||
|
||||
assert result.to_dict("records") == [
|
||||
{
|
||||
"pair_name": "AAA:USD-BBB:USD",
|
||||
"mr_ranking": 1,
|
||||
"realized_pnl": 20.0,
|
||||
"unrealized_pnl": 0.0,
|
||||
},
|
||||
{
|
||||
"pair_name": "CCC:USD-DDD:USD",
|
||||
"mr_ranking": 2,
|
||||
"realized_pnl": 0.0,
|
||||
"unrealized_pnl": 0.0,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_load_trading_instructions_validates_required_table():
|
||||
conn = sqlite3.connect(":memory:")
|
||||
|
||||
with pytest.raises(ValueError, match="missing required table: trading_instructions"):
|
||||
load_trading_instructions(conn)
|
||||
Reference in New Issue
Block a user