Fix intermittent loss of options positions and realtime PnL on refresh.
Use stale-while-revalidate for positions API and UI, throttle sync calls, and avoid overwriting displayed PnL with null on transient failures. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from flask import Flask, jsonify, redirect, request, url_for
|
||||
@@ -142,14 +143,34 @@ def _budget_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
|
||||
return float(raw), ""
|
||||
|
||||
|
||||
def _sync_options_trades(cfg: dict[str, Any]) -> None:
|
||||
_OPTIONS_SYNC_LOCK = threading.Lock()
|
||||
_OPTIONS_SYNC_LAST_AT = 0.0
|
||||
_OPTIONS_SYNC_INTERVAL_SEC = 15.0
|
||||
|
||||
|
||||
def _sync_options_trades(
|
||||
cfg: dict[str, Any],
|
||||
*,
|
||||
raw_positions: list[dict[str, Any]] | None = None,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
ex = cfg.get("exchange_options")
|
||||
if ex is None:
|
||||
return
|
||||
now = time.time()
|
||||
with _OPTIONS_SYNC_LOCK:
|
||||
if not force and now - _OPTIONS_SYNC_LAST_AT < _OPTIONS_SYNC_INTERVAL_SEC:
|
||||
return
|
||||
_OPTIONS_SYNC_LAST_AT = now
|
||||
from lib.exchange.okx_options_lib import fetch_option_position_history
|
||||
from lib.options.options_monitor_lib import reconcile_live_open_trades, sync_open_options_trades
|
||||
|
||||
raw = cfg["fetch_option_positions"](ex)
|
||||
if raw_positions is None:
|
||||
raw = cfg["fetch_option_positions"](ex)
|
||||
if raw is None:
|
||||
return
|
||||
else:
|
||||
raw = raw_positions
|
||||
live_ids = {str(p.get("instId") or "") for p in raw if str(p.get("instId") or "")}
|
||||
|
||||
def _hist(inst_id: str):
|
||||
@@ -348,6 +369,10 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
from lib.exchange.okx_options_lib import invalidate_option_positions_cache
|
||||
|
||||
invalidate_option_positions_cache()
|
||||
_sync_options_trades(cfg, force=True)
|
||||
return jsonify({"ok": True, "order": order, "sizing": sizing})
|
||||
|
||||
@app.route("/api/options/positions")
|
||||
@@ -356,8 +381,10 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
ex, err = _require_options_ex(cfg)
|
||||
if ex is None:
|
||||
return jsonify({"ok": False, "msg": err})
|
||||
_sync_options_trades(cfg)
|
||||
raw = cfg["fetch_option_positions"](ex)
|
||||
if raw is None:
|
||||
return jsonify({"ok": False, "msg": "获取期权持仓失败"})
|
||||
_sync_options_trades(cfg, raw_positions=raw)
|
||||
rows = [cfg["format_position_row"](p) for p in raw]
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
@@ -396,6 +423,8 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
if not use_market and (bid is None or bid <= 0):
|
||||
return jsonify({"ok": False, "msg": "暂无买一价,无法限价平仓"})
|
||||
raw_positions = cfg["fetch_option_positions"](ex)
|
||||
if raw_positions is None:
|
||||
return jsonify({"ok": False, "msg": "获取期权持仓失败"})
|
||||
pos = next((p for p in raw_positions if str(p.get("instId")) == inst_id), None)
|
||||
if not pos:
|
||||
return jsonify({"ok": False, "msg": "未找到持仓"})
|
||||
@@ -465,6 +494,10 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
from lib.exchange.okx_options_lib import invalidate_option_positions_cache
|
||||
|
||||
invalidate_option_positions_cache()
|
||||
_sync_options_trades(cfg, force=True)
|
||||
return jsonify({"ok": True, "order": order, "bid": bid, "sheets": close_sheets})
|
||||
|
||||
@app.route("/api/options/convert/quote", methods=["POST"])
|
||||
@@ -712,6 +745,8 @@ def _start_monitor_thread(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
if ex is None:
|
||||
return []
|
||||
raw = cfg["fetch_option_positions"](ex)
|
||||
if raw is None:
|
||||
return []
|
||||
return [cfg["format_position_row"](p) for p in raw]
|
||||
|
||||
def _sync(conn):
|
||||
@@ -722,6 +757,8 @@ def _start_monitor_thread(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
if ex is None:
|
||||
return 0
|
||||
raw = cfg["fetch_option_positions"](ex)
|
||||
if raw is None:
|
||||
return 0
|
||||
live_ids = {str(p.get("instId") or "") for p in raw if str(p.get("instId") or "")}
|
||||
reconcile_live_open_trades(conn, live_inst_ids=live_ids)
|
||||
return sync_open_options_trades(
|
||||
|
||||
Reference in New Issue
Block a user