Fix options PnL backfill matching when the same contract is traded twice.
Match exchange history by sheets and open time so an earlier close is not overwritten with the later trade's PnL. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -45,7 +45,7 @@ def test_format_option_history_row():
|
||||
assert row["status_label"] == "已平"
|
||||
assert row["open_avg_px_fmt"] == "380"
|
||||
assert row["premium_paid_fmt"] == "3.80"
|
||||
assert row["history_key"] == "ex:pos-btc"
|
||||
assert row["history_key"] == "ex:pos-btc:1784088035000"
|
||||
|
||||
|
||||
def test_resolve_option_close_from_history_picks_latest():
|
||||
@@ -59,6 +59,40 @@ def test_resolve_option_close_from_history_picks_latest():
|
||||
assert got["pos_id"] == "9"
|
||||
|
||||
|
||||
def test_resolve_option_close_from_history_matches_open_and_sheets():
|
||||
rows = [
|
||||
{
|
||||
"instId": "ETH-USD_UM-260806-1875-C",
|
||||
"cTime": "1785932775047",
|
||||
"uTime": "1785933936733",
|
||||
"realizedPnl": "-3.036",
|
||||
"closeAvgPx": "12.4",
|
||||
"closeTotalPos": "57",
|
||||
"posId": "same-pos",
|
||||
},
|
||||
{
|
||||
"instId": "ETH-USD_UM-260806-1875-C",
|
||||
"cTime": "1785938392445",
|
||||
"uTime": "1785957764279",
|
||||
"realizedPnl": "16.937",
|
||||
"closeAvgPx": "41.0",
|
||||
"closeTotalPos": "66",
|
||||
"posId": "same-pos",
|
||||
},
|
||||
]
|
||||
# 本地时间相对交易所偏 8h 时,仍应按 cTime/张数对齐到正确一笔
|
||||
early = resolve_option_close_from_history(
|
||||
rows, open_ms=1785902775000, close_ms=1785903937000, sheets=57
|
||||
)
|
||||
late = resolve_option_close_from_history(
|
||||
rows, open_ms=1785908392000, close_ms=1785927764000, sheets=66
|
||||
)
|
||||
assert early is not None and early["realized_pnl"] == -3.036
|
||||
assert early["close_quote"] == 12.4
|
||||
assert late is not None and late["realized_pnl"] == 16.937
|
||||
assert late["close_quote"] == 41.0
|
||||
|
||||
|
||||
def test_sync_open_options_trades_marks_expired_closed():
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.row_factory = sqlite3.Row
|
||||
@@ -96,9 +130,9 @@ def test_sync_open_options_trades_skips_without_close_evidence():
|
||||
INSERT INTO options_trades
|
||||
(inst_id, underlying, opt_type, strike, exp_time, sheets, eth_amount,
|
||||
open_quote, premium_paid, status, created_at)
|
||||
VALUES (?, 'BTC', 'P', 62000, '', 1, 0.01, 380.0, 3.8, 'open', '2026-07-09 08:00:00')
|
||||
VALUES (?, 'BTC', 'P', 62000, '', 1, 0.01, 380.0, 3.8, 'open', '2026-08-05 08:00:00')
|
||||
""",
|
||||
("BTC-USD_UM-260710-62000-P",),
|
||||
("BTC-USD_UM-261231-62000-P",),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
@@ -213,3 +247,64 @@ def test_backfill_closed_options_realized_pnl_from_history():
|
||||
assert float(row["close_quote"]) == 48.5
|
||||
# idempotent
|
||||
assert backfill_closed_options_realized_pnl_from_history(conn, hist) == 0
|
||||
|
||||
|
||||
def test_backfill_does_not_overwrite_earlier_close_with_later_pnl():
|
||||
from lib.options.options_monitor_lib import backfill_closed_options_realized_pnl_from_history
|
||||
|
||||
conn = sqlite3.connect(":memory:")
|
||||
conn.row_factory = sqlite3.Row
|
||||
init_options_tables(conn)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO options_trades
|
||||
(inst_id, underlying, opt_type, strike, exp_time, sheets, eth_amount,
|
||||
open_quote, close_quote, premium_paid, realized_pnl, status, created_at, closed_at)
|
||||
VALUES (?, 'ETH', 'C', 1875, '', 57, 0.57, 16.6, 41.0, 9.462, 16.9368,
|
||||
'closed', '2026-08-05 12:26:15', '2026-08-05 12:45:37')
|
||||
""",
|
||||
("ETH-USD_UM-260806-1875-C",),
|
||||
)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO options_trades
|
||||
(inst_id, underlying, opt_type, strike, exp_time, sheets, eth_amount,
|
||||
open_quote, close_quote, premium_paid, realized_pnl, status, created_at, closed_at)
|
||||
VALUES (?, 'ETH', 'C', 1875, '', 66, 0.66, 14.2, 41.0, 9.372, 16.9368,
|
||||
'closed', '2026-08-05 13:59:52', '2026-08-05 19:22:45')
|
||||
""",
|
||||
("ETH-USD_UM-260806-1875-C",),
|
||||
)
|
||||
conn.commit()
|
||||
hist = [
|
||||
{
|
||||
"instId": "ETH-USD_UM-260806-1875-C",
|
||||
"uTime": "1785933936733",
|
||||
"cTime": "1785932775047",
|
||||
"realizedPnl": "-3.03616314",
|
||||
"closeAvgPx": "12.4",
|
||||
"closeTotalPos": "57",
|
||||
"posId": "3806091806281486337",
|
||||
},
|
||||
{
|
||||
"instId": "ETH-USD_UM-260806-1875-C",
|
||||
"uTime": "1785957764279",
|
||||
"cTime": "1785938392445",
|
||||
"realizedPnl": "16.9368375",
|
||||
"closeAvgPx": "41.0",
|
||||
"closeTotalPos": "66",
|
||||
"posId": "3806091806281486337",
|
||||
},
|
||||
]
|
||||
n = backfill_closed_options_realized_pnl_from_history(conn, hist)
|
||||
assert n >= 1
|
||||
rows = {
|
||||
int(r["id"]): r
|
||||
for r in conn.execute(
|
||||
"SELECT id, realized_pnl, close_quote FROM options_trades ORDER BY id"
|
||||
).fetchall()
|
||||
}
|
||||
assert abs(float(rows[1]["realized_pnl"]) - (-3.0362)) < 1e-3
|
||||
assert float(rows[1]["close_quote"]) == 12.4
|
||||
assert abs(float(rows[2]["realized_pnl"]) - 16.9368) < 1e-3
|
||||
assert float(rows[2]["close_quote"]) == 41.0
|
||||
|
||||
Reference in New Issue
Block a user