Files
crypto_monitor/tests/test_options_sync.py
dekun f19500bcd9 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>
2026-08-06 09:04:30 +08:00

311 lines
11 KiB
Python

"""期权平仓/到期状态同步单测."""
import sqlite3
from lib.exchange.okx_options_lib import (
format_option_history_row,
format_usdc_amount,
is_option_full_close_history,
resolve_option_close_from_history,
)
from lib.options.options_db import init_options_tables
from lib.options.options_monitor_lib import sync_open_options_trades
def test_format_usdc_amount():
assert format_usdc_amount(4.896) == "4.90"
assert format_usdc_amount(4.9) == "4.90"
assert format_usdc_amount(4.0) == "4.00"
def test_is_option_full_close_history():
assert is_option_full_close_history({"type": "2"})
assert is_option_full_close_history({"type": "3"})
assert not is_option_full_close_history({"type": "1"})
assert not is_option_full_close_history({"type": "5"})
def test_format_option_history_row():
raw = {
"instId": "BTC-USD_UM-260710-62000-P",
"openAvgPx": "380",
"closeAvgPx": "0",
"closeTotalPos": "1",
"openMaxPos": "1",
"realizedPnl": "-3.99",
"pnlRatio": "-1.049",
"type": "2",
"cTime": "1784000000000",
"uTime": "1784088035000",
"posId": "pos-btc",
}
row = format_option_history_row(raw, tick_sz="0.1", ct_mult=0.01)
assert row["inst_id"] == "BTC-USD_UM-260710-62000-P"
assert row["sheets"] == 1
assert row["realized_pnl"] == -3.99
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:1784088035000"
def test_resolve_option_close_from_history_picks_latest():
rows = [
{"instId": "ETH-USD_UM-260709-1700-P", "uTime": "1000", "realizedPnl": "-1.0", "closeAvgPx": "0"},
{"instId": "ETH-USD_UM-260709-1700-P", "uTime": "2000", "realizedPnl": "-1.24", "closeAvgPx": "0", "posId": "9"},
]
got = resolve_option_close_from_history(rows, open_ms=500)
assert got is not None
assert got["realized_pnl"] == -1.24
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
init_options_tables(conn)
conn.execute(
"""
INSERT INTO options_trades
(inst_id, underlying, opt_type, strike, exp_time, sheets, eth_amount,
open_quote, premium_paid, status)
VALUES (?, 'ETH', 'P', 1700, '', 20, 0.2, 6.2, 1.24, 'open')
""",
("ETH-USD_UM-260709-1700-P",),
)
conn.commit()
n = sync_open_options_trades(
conn,
live_inst_ids=set(),
fetch_history_fn=lambda _inst: [],
)
assert n == 1
row = conn.execute("SELECT status, premium_received, realized_pnl, signal_note FROM options_trades").fetchone()
assert row["status"] == "closed"
assert row["premium_received"] == 0.0
assert row["realized_pnl"] == -1.24
assert "到期结算" in (row["signal_note"] or "")
def test_sync_open_options_trades_skips_without_close_evidence():
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, premium_paid, status, created_at)
VALUES (?, 'BTC', 'P', 62000, '', 1, 0.01, 380.0, 3.8, 'open', '2026-08-05 08:00:00')
""",
("BTC-USD_UM-261231-62000-P",),
)
conn.commit()
n = sync_open_options_trades(
conn,
live_inst_ids=set(),
fetch_history_fn=lambda _inst: [],
)
assert n == 0
row = conn.execute("SELECT status FROM options_trades").fetchone()
assert row["status"] == "open"
def test_reconcile_live_open_trades_reopens_sync_artifact():
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, premium_paid, status, closed_at)
VALUES (?, 'BTC', 'P', 62000, '', 1, 0.01, 380.0, 3.8, 'closed', '2026-07-09 09:10:34')
""",
("BTC-USD_UM-260710-62000-P",),
)
conn.commit()
from lib.options.options_monitor_lib import reconcile_live_open_trades
n = reconcile_live_open_trades(conn, live_inst_ids={"BTC-USD_UM-260710-62000-P"})
assert n == 1
row = conn.execute("SELECT status, closed_at FROM options_trades").fetchone()
assert row["status"] == "open"
assert row["closed_at"] is None
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, premium_paid, status, created_at)
VALUES (?, 'ETH', 'P', 1700, '', 20, 0.2, 6.2, 1.24, 'open', '2026-07-08 02:32:44')
""",
("ETH-USD_UM-260709-1700-P",),
)
conn.commit()
def _hist(_inst):
return [
{
"instId": "ETH-USD_UM-260709-1700-P",
"uTime": "1784000000000",
"realizedPnl": "-0.5",
"closeAvgPx": "0.1",
"posId": "pos-1",
}
]
n = sync_open_options_trades(
conn,
live_inst_ids=set(),
fetch_history_fn=_hist,
)
assert n == 1
row = conn.execute(
"SELECT status, premium_received, realized_pnl, close_ord_id FROM options_trades"
).fetchone()
assert row["status"] == "closed"
assert row["realized_pnl"] == -0.5
assert row["premium_received"] == 0.74
assert row["close_ord_id"] == "pos-1"
def test_backfill_closed_options_realized_pnl_from_history():
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, premium_received, realized_pnl,
status, created_at, closed_at, close_ord_id)
VALUES (?, 'ETH', 'C', 1860, '', 43, 0.43, 22.4, 50.6, 9.632, 21.758, 12.126,
'closed', '2026-07-20 08:02:14', '2026-07-21 01:38:43', 'ord-1')
""",
("ETH-USD_UM-260721-1860-C",),
)
conn.commit()
hist = [
{
"instId": "ETH-USD_UM-260721-1860-C",
"uTime": "1784564323000",
"realizedPnl": "11.64",
"closeAvgPx": "48.5",
"closeTotalPos": "43",
"posId": "pos-x",
}
]
n = backfill_closed_options_realized_pnl_from_history(conn, hist)
assert n == 1
row = conn.execute(
"SELECT realized_pnl, premium_received, close_quote FROM options_trades WHERE id=1"
).fetchone()
assert row["realized_pnl"] == 11.64
assert abs(float(row["premium_received"]) - (9.632 + 11.64)) < 1e-6
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