fix模拟盘复盘盈亏跟盘跳动:优先用冻结现金 U,不再每次用实时指数重算。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-08 23:43:46 +08:00
parent 3f621835a9
commit a16dd4f9cc
2 changed files with 169 additions and 8 deletions
+61 -8
View File
@@ -284,17 +284,38 @@ def sync_options_from_local_trades(
) -> dict[str, Any]:
"""从本地 options_trades 已平仓记录导入复盘快照(不访问交易所).
币本位权利金/盈亏按指数折算成 USDT(U) 写入,复盘页统一按 U 展示.
盈亏优先用已冻结 realized_pnl_usdt(模拟盘现金口径 / 平仓时指数),
禁止每次同步用实时指数重算,否则复盘数字会跟盘跳动.
"""
init_options_review_tables(conn)
from lib.options.options_db import init_options_tables
from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode
init_options_tables(conn)
# 模拟盘先把现金盈亏写进 realized_pnl_usdt,复盘与持仓统计同一口径.
# 不可 is_sim_mode(lambda: conn): with_db 会 close 掉调用方连接.
try:
from lib.instance.runtime_settings_lib import ensure_runtime_settings_table, runtime_get
from lib.sim.mode_lib import MODE_SIM, default_trading_mode
ensure_runtime_settings_table(conn)
mode = runtime_get(conn, "trading.mode")
mode_s = str(mode or "").strip().lower() or default_trading_mode()
if mode_s == MODE_SIM:
from lib.options.options_stats_lib import sync_sim_cash_realized_pnl_usdt
sync_sim_cash_realized_pnl_usdt(conn, ex)
try:
conn.commit()
except Exception:
pass
except Exception:
pass
rows = conn.execute(
"""
SELECT id, inst_id, underlying, opt_type, strike, exp_time, sheets,
open_quote, close_quote, premium_paid, premium_received, realized_pnl,
realized_pnl_usdt, close_index_px, open_index_px, budget_usdt,
premium_ccy, created_at, closed_at, signal_note, status
FROM options_trades
WHERE status = 'closed'
@@ -325,9 +346,9 @@ def sync_options_from_local_trades(
for pub in pubs:
try:
if hasattr(pub, "public_get_market_ticker"):
rows = (pub.public_get_market_ticker({"instId": inst}) or {}).get("data") or []
if rows:
last = rows[0].get("last") or rows[0].get("lastPx")
tick_rows = (pub.public_get_market_ticker({"instId": inst}) or {}).get("data") or []
if tick_rows:
last = tick_rows[0].get("last") or tick_rows[0].get("lastPx")
if last is not None and float(last) > 0:
return float(last)
except Exception:
@@ -363,6 +384,12 @@ def sync_options_from_local_trades(
idx_cache: dict[str, float | None] = {}
need_idx: set[str] = set()
for r in rows:
# 已有冻结 U 或平仓指数时不再打实时行情
if _safe_float(r["realized_pnl_usdt"]) is not None:
continue
frozen_idx = _safe_float(r["close_index_px"]) or _safe_float(r["open_index_px"])
if frozen_idx is not None and frozen_idx > 0:
continue
inst0 = str(r["inst_id"] or "")
underly0 = str(r["underlying"] or (inst0.split("-")[0] if inst0 else "ETH") or "ETH")
ccy0 = str(r["premium_ccy"] or "").strip().upper()
@@ -381,7 +408,17 @@ def sync_options_from_local_trades(
ccy = str(r["premium_ccy"] or "").strip().upper()
if not ccy:
ccy = premium_ccy_for_mode(margin_mode_from_inst_id(inst), underly)
idx = idx_cache.get(underly)
frozen_usdt = _safe_float(r["realized_pnl_usdt"])
close_idx = _safe_float(r["close_index_px"])
open_idx = _safe_float(r["open_index_px"])
# 折算指数:平仓冻结 > 开仓 > 实时(仅无冻结时兜底)
idx = None
if close_idx is not None and close_idx > 0:
idx = close_idx
elif open_idx is not None and open_idx > 0:
idx = open_idx
else:
idx = idx_cache.get(underly)
pnl = _safe_float(r["realized_pnl"])
if pnl is None:
paid0 = _safe_float(r["premium_paid"])
@@ -389,10 +426,15 @@ def sync_options_from_local_trades(
if paid0 is not None and recv0 is not None:
pnl = recv0 - paid0
prem = _safe_float(r["premium_paid"])
pnl_u = _to_usdt(pnl, ccy=ccy, idx=idx)
if frozen_usdt is not None:
pnl_u = round(float(frozen_usdt), 4)
else:
pnl_u = _to_usdt(pnl, ccy=ccy, idx=idx)
# 权利金用平仓/开仓冻结指数折算,不用实时价
prem_u = _to_usdt(prem, ccy=ccy, idx=idx)
if ccy in ("ETH", "BTC") and idx is None:
if ccy in ("ETH", "BTC") and pnl_u is None:
pnl_u = pnl
if ccy in ("ETH", "BTC") and prem_u is None:
prem_u = prem
opened_at = r["created_at"]
closed_at = r["closed_at"]
@@ -664,7 +706,18 @@ def ensure_local_review_synced(
conn.commit()
except Exception:
pass
if backfill_exchange_pnl and ex is not None:
# 模拟盘用现金冻结,勿再用交易所历史+实时指数覆盖
sim_mode = False
try:
from lib.instance.runtime_settings_lib import ensure_runtime_settings_table, runtime_get
from lib.sim.mode_lib import MODE_SIM, default_trading_mode
ensure_runtime_settings_table(conn)
mode = runtime_get(conn, "trading.mode")
sim_mode = (str(mode or "").strip().lower() or default_trading_mode()) == MODE_SIM
except Exception:
sim_mode = False
if backfill_exchange_pnl and ex is not None and not sim_mode:
try:
from lib.exchange.okx_options_lib import fetch_all_option_positions_history
from lib.options.options_monitor_lib import (