Align options review PnL with OKX positions-history realizedPnl.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -135,6 +135,98 @@ def _created_at_ms(created_at: Any) -> int | None:
|
||||
return None
|
||||
|
||||
|
||||
def _group_key_for_closed_trade(row: Any) -> str:
|
||||
inst = str(row["inst_id"] or "").strip()
|
||||
ord_id = str(row["close_ord_id"] or "").strip() if "close_ord_id" in row.keys() else ""
|
||||
if ord_id:
|
||||
return f"{inst}|ord:{ord_id}"
|
||||
closed = str(row["closed_at"] or "").strip()
|
||||
return f"{inst}|close:{(closed[:16] if closed else '')}"
|
||||
|
||||
|
||||
def backfill_closed_options_realized_pnl_from_history(
|
||||
conn: sqlite3.Connection,
|
||||
hist_rows: list[dict[str, Any]],
|
||||
*,
|
||||
trade_limit: int = 200,
|
||||
) -> int:
|
||||
"""
|
||||
用 OKX positions-history 的 realizedPnl 覆盖本地已平记录.
|
||||
同一次平仓多笔本地 open(加仓)按权利金占比分摊交易所总盈亏.
|
||||
"""
|
||||
by_inst: dict[str, list[dict[str, Any]]] = {}
|
||||
for raw in hist_rows or []:
|
||||
if not isinstance(raw, dict):
|
||||
continue
|
||||
inst = str(raw.get("instId") or "").strip()
|
||||
if not inst:
|
||||
continue
|
||||
by_inst.setdefault(inst, []).append(raw)
|
||||
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT id, inst_id, sheets, premium_paid, realized_pnl, created_at, closed_at, close_ord_id
|
||||
FROM options_trades
|
||||
WHERE status = 'closed'
|
||||
ORDER BY id DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
(int(trade_limit),),
|
||||
).fetchall()
|
||||
if not rows:
|
||||
return 0
|
||||
|
||||
groups: dict[str, list[Any]] = {}
|
||||
for row in rows:
|
||||
inst = str(row["inst_id"] or "").strip()
|
||||
if not inst or inst not in by_inst:
|
||||
continue
|
||||
groups.setdefault(_group_key_for_closed_trade(row), []).append(row)
|
||||
|
||||
updated = 0
|
||||
for group in groups.values():
|
||||
inst = str(group[0]["inst_id"] or "").strip()
|
||||
open_candidates = [_created_at_ms(r["created_at"]) for r in group]
|
||||
open_ms = min((x for x in open_candidates if x is not None), default=None)
|
||||
close_info = resolve_option_close_from_history(by_inst.get(inst) or [], open_ms=open_ms)
|
||||
if not close_info:
|
||||
continue
|
||||
ex_pnl = _safe_float(close_info.get("realized_pnl"))
|
||||
if ex_pnl is None:
|
||||
continue
|
||||
close_quote = _safe_float(close_info.get("close_quote"))
|
||||
total_paid = 0.0
|
||||
for r in group:
|
||||
total_paid += float(_safe_float(r["premium_paid"]) or 0.0)
|
||||
allocated = 0.0
|
||||
for i, r in enumerate(group):
|
||||
paid = float(_safe_float(r["premium_paid"]) or 0.0)
|
||||
if i == len(group) - 1:
|
||||
share = round(float(ex_pnl) - allocated, 4)
|
||||
elif total_paid > 0:
|
||||
share = round(float(ex_pnl) * (paid / total_paid), 4)
|
||||
allocated += share
|
||||
else:
|
||||
share = round(float(ex_pnl) / len(group), 4)
|
||||
allocated += share
|
||||
local = _safe_float(r["realized_pnl"])
|
||||
if local is not None and abs(local - share) < 1e-6:
|
||||
continue
|
||||
prem_recv = round(paid + share, 4)
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE options_trades
|
||||
SET realized_pnl = ?,
|
||||
premium_received = ?,
|
||||
close_quote = COALESCE(?, close_quote)
|
||||
WHERE id = ?
|
||||
""",
|
||||
(share, prem_recv, close_quote, int(r["id"])),
|
||||
)
|
||||
updated += 1
|
||||
return updated
|
||||
|
||||
|
||||
def sync_open_options_trades(
|
||||
conn: sqlite3.Connection,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user