fix期权复盘与数据统计双口径盈亏,并修复卖回封顶导致盈利单冻成亏损。

平仓卖光可卖币后再按账户差分结算复盘;数据统计改为历史币盈亏×卖回价;复盘展示初始权利金与盈利率,并附历史修复脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-13 09:10:13 +08:00
parent d4de6a23ae
commit 9d77a430e5
16 changed files with 2858 additions and 2242 deletions
+32 -34
View File
@@ -59,11 +59,16 @@ def _underlying_index_usdt(ex: Any, underly: str) -> float | None:
return None
def history_pnl_to_usdt(history: list[dict[str, Any]], ex: Any = None) -> list[dict[str, Any]]:
"""
统计用:优先已冻结的 realized_pnl_usdt(平仓当时口径,含手续费后净额);
否则币本位用 close_index_px(再否则实时指数)折 U;USDC 原样.
折算失败的币仓剔除盈亏字段,避免把「币数量」当成 U.
def history_pnl_to_usdt(
history: list[dict[str, Any]],
ex: Any = None,
*,
prefer_frozen_usdt: bool = True,
) -> list[dict[str, Any]]:
"""折 U.
prefer_frozen_usdt=True:优先行内 realized_pnl_usdt(实盘 exchange freeze / 旧兼容).
prefer_frozen_usdt=False:口径 B — 币盈亏 × 平仓卖回价(close_index_px),忽略账户差分 A.
"""
from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode
@@ -75,7 +80,7 @@ def history_pnl_to_usdt(history: list[dict[str, Any]], ex: Any = None) -> list[d
out.append(r)
continue
frozen = _safe_float(r.get("realized_pnl_usdt"))
if frozen is not None:
if prefer_frozen_usdt and frozen is not None:
r["realized_pnl"] = round(float(frozen), 4)
out.append(r)
continue
@@ -104,6 +109,11 @@ def history_pnl_to_usdt(history: list[dict[str, Any]], ex: Any = None) -> list[d
return out
def history_pnl_to_usdt_stats(history: list[dict[str, Any]], ex: Any = None) -> list[dict[str, Any]]:
"""数据统计口径 B:期权历史币盈亏 × 平仓卖回价."""
return history_pnl_to_usdt(history, ex, prefer_frozen_usdt=False)
def _parse_ts(raw: Any) -> datetime | None:
if raw is None or raw == "":
return None
@@ -286,7 +296,8 @@ def _closed_trade_dicts(closed_rows) -> list[dict[str, Any]]:
{
"status": "closed",
"realized_pnl": _row_get(row, "realized_pnl"),
"realized_pnl_usdt": _row_get(row, "realized_pnl_usdt"),
# 口径 B 不读账户 realized_pnl_usdt;保留键位兼容旧调用
"realized_pnl_usdt": None,
"close_index_px": _row_get(row, "close_index_px"),
"created_at": _row_get(row, "created_at"),
"closed_at": _row_get(row, "closed_at"),
@@ -298,16 +309,13 @@ def _closed_trade_dicts(closed_rows) -> list[dict[str, Any]]:
return out
# 口径 B:凡有币盈亏的已平均计入;U 由 history_pnl_to_usdt_stats 按卖回价折算
_CLOSED_STATS_SELECT = """
SELECT realized_pnl, realized_pnl_usdt, close_index_px,
SELECT realized_pnl, close_index_px,
created_at, closed_at, inst_id, underlying, premium_ccy
FROM options_trades
WHERE status = 'closed'
AND (
(COALESCE(pnl_settled, 0) = 1 AND realized_pnl_usdt IS NOT NULL)
OR (trading_usdt_before IS NULL
AND (realized_pnl IS NOT NULL OR realized_pnl_usdt IS NOT NULL))
)
AND realized_pnl IS NOT NULL
"""
@@ -634,7 +642,7 @@ def apply_exchange_pnl_freeze(
def compute_options_stats(get_db, ex: Any = None) -> dict[str, Any]:
"""模拟盘:本地交易结算.实盘请用 compute_options_stats_live_exchange."""
"""模拟盘数据统计:口径 B(历史币盈亏×卖回价)."""
conn = get_db()
try:
history = load_local_options_stats_history(conn, ex)
@@ -642,7 +650,7 @@ def compute_options_stats(get_db, ex: Any = None) -> dict[str, Any]:
conn.commit()
except Exception:
pass
return compute_options_stats_from_history(history_pnl_to_usdt(history, ex))
return compute_options_stats_from_history(history_pnl_to_usdt_stats(history, ex))
finally:
conn.close()
@@ -717,9 +725,11 @@ def load_live_exchange_stats_history(cfg: dict[str, Any], ex: Any) -> list[dict[
def compute_options_stats_live_exchange(cfg: dict[str, Any], ex: Any) -> dict[str, Any]:
"""实盘:与「期权历史」同一数据源(交易所 positions-history),盈亏以交易所为准."""
"""实盘:与「期权历史」同一数据源;口径 B=币盈亏×卖回价(freeze)."""
history = load_live_exchange_stats_history(cfg, ex)
return compute_options_stats_from_history(history_pnl_to_usdt(history, ex))
return compute_options_stats_from_history(
history_pnl_to_usdt(history, ex, prefer_frozen_usdt=True)
)
def _apply_exchange_pnl_freeze_with_hints(
@@ -812,7 +822,7 @@ def _stats_from_option_trade_rows(closed_rows, open_rows, *, ex: Any = None) ->
history = _closed_trade_dicts(closed_rows)
for row in open_rows or []:
history.append({"status": "open", "created_at": _row_get(row, "created_at")})
return compute_options_stats_from_history(history_pnl_to_usdt(history, ex))
return compute_options_stats_from_history(history_pnl_to_usdt_stats(history, ex))
def header_options_stats_for_window(
@@ -821,32 +831,20 @@ def header_options_stats_for_window(
app_tz,
ex: Any = None,
) -> dict[str, Any]:
"""顶栏总交易/胜率/盈亏比:与期权统计同一结算口径.
新单(有 trading_usdt_before):仅 pnl_settled=1 的账户差分计入;
旧单(无 before):仍按原 realized 兼容.币数量须折 U 后再算盈亏比.
"""
"""顶栏总交易/胜率/盈亏比:口径 B(历史币盈亏×卖回价)."""
from lib.common.history_window_lib import utc_window_to_bj_sql_strings
init_options_tables(conn)
start_bj, end_bj = utc_window_to_bj_sql_strings(
list_window["start_utc"], list_window["end_utc"], app_tz
)
try:
freeze_missing_realized_pnl_usdt(conn, ex)
except Exception:
pass
closed_rows = conn.execute(
"""
SELECT realized_pnl, realized_pnl_usdt, close_index_px,
SELECT realized_pnl, close_index_px,
created_at, closed_at, inst_id, underlying, premium_ccy
FROM options_trades
WHERE status = 'closed'
AND (
(COALESCE(pnl_settled, 0) = 1 AND realized_pnl_usdt IS NOT NULL)
OR (trading_usdt_before IS NULL
AND (realized_pnl IS NOT NULL OR realized_pnl_usdt IS NOT NULL))
)
AND realized_pnl IS NOT NULL
AND COALESCE(closed_at, created_at) >= ?
AND COALESCE(closed_at, created_at) <= ?
""",
@@ -858,7 +856,7 @@ def header_options_stats_for_window(
history = _closed_trade_dicts(closed_rows)
for row in open_rows:
history.append({"status": "open", "created_at": _row_get(row, "created_at")})
stats = compute_options_stats_from_history(history_pnl_to_usdt(history, ex))
stats = compute_options_stats_from_history(history_pnl_to_usdt_stats(history, ex))
return {
"total": int(stats.get("total_closed") or 0),
"rate": float(stats.get("win_rate") or 0),