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
+23 -6
View File
@@ -146,6 +146,8 @@ def upsert_option_history_row(conn: sqlite3.Connection, row: dict[str, Any]) ->
"close_avg": _safe_float(row.get("close_avg_px") if row.get("close_avg_px") is not None else row.get("close_avg")),
"premium_paid": _safe_float(row.get("premium_paid")),
"realized_pnl": pnl,
"initial_premium_usdt": _safe_float(row.get("initial_premium_usdt")),
"profit_rate": _safe_float(row.get("profit_rate")),
}
cols = list(fields.keys())
if existing:
@@ -317,7 +319,7 @@ def sync_options_from_local_trades(
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,
trading_usdt_before, pnl_settled
trading_usdt_before, pnl_settled, initial_premium_usdt
FROM options_trades
WHERE status = 'closed'
ORDER BY id DESC
@@ -439,12 +441,19 @@ def sync_options_from_local_trades(
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 pnl_u is None:
# 初始权利金(U)=开仓指数×权利金币量;不含多买
stored_initial = _safe_float(r["initial_premium_usdt"])
open_idx_for_prem = open_idx if (open_idx is not None and open_idx > 0) else idx
prem_u = stored_initial
if prem_u is None:
prem_u = _to_usdt(prem, ccy=ccy, idx=open_idx_for_prem)
if ccy in ("ETH", "BTC") and pnl_u is None and not acct_pending:
pnl_u = pnl
if ccy in ("ETH", "BTC") and prem_u is None:
prem_u = prem
profit_rate = None
if pnl_u is not None and prem_u is not None and abs(float(prem_u)) > 1e-12:
profit_rate = round(float(pnl_u) / float(prem_u), 6)
opened_at = r["created_at"]
closed_at = r["closed_at"]
action = upsert_option_history_row(
@@ -461,10 +470,12 @@ def sync_options_from_local_trades(
"open_avg_px": r["open_quote"],
"close_avg_px": r["close_quote"],
"premium_paid": prem_u if prem_u is not None else prem,
"realized_pnl": pnl_u if pnl_u is not None else pnl,
"realized_pnl": pnl_u if pnl_u is not None else (None if acct_pending else pnl),
"initial_premium_usdt": prem_u,
"profit_rate": profit_rate,
"created_at": opened_at,
"closed_at": closed_at,
"status_label": "已平",
"status_label": "待结算" if acct_pending else "已平",
},
)
if action == "inserted":
@@ -759,6 +770,12 @@ def _row_to_dict(row: Any) -> dict[str, Any]:
def enrich_trade_row(row: dict[str, Any], entry: dict[str, Any] | None = None) -> dict[str, Any]:
out = dict(row)
# 盈利率缺失时按 复盘盈亏/初始权利金 补算
if _safe_float(out.get("profit_rate")) is None:
pnl = _safe_float(out.get("realized_pnl_total"))
prem = _safe_float(out.get("initial_premium_usdt"))
if pnl is not None and prem is not None and abs(prem) > 1e-12:
out["profit_rate"] = round(pnl / prem, 6)
out["source_label"] = SOURCE_LABELS.get(str(out.get("source_type") or ""), out.get("source_type"))
out["is_hedge"] = str(out.get("source_type") or "") in (SOURCE_PERP_OPTIONS, SOURCE_OPTIONS_OPTIONS)
legs = []