修复顶栏盈亏比:币本位已平盈亏先折 U 再统计。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-08 22:31:36 +08:00
parent a45b5d1b0b
commit e8f8a76753
3 changed files with 131 additions and 65 deletions
+56 -63
View File
@@ -260,13 +260,41 @@ def compute_options_stats_for_window(
return stats
def compute_options_stats(get_db) -> dict[str, Any]:
def _row_get(row: Any, key: str, default: Any = None) -> Any:
try:
if hasattr(row, "keys") and key in row.keys():
return row[key]
except Exception:
pass
if isinstance(row, dict):
return row.get(key, default)
return default
def _closed_trade_dicts(closed_rows) -> list[dict[str, Any]]:
out: list[dict[str, Any]] = []
for row in closed_rows or []:
out.append(
{
"status": "closed",
"realized_pnl": _row_get(row, "realized_pnl"),
"created_at": _row_get(row, "created_at"),
"closed_at": _row_get(row, "closed_at"),
"inst_id": _row_get(row, "inst_id") or "",
"underlying": _row_get(row, "underlying") or "",
"premium_ccy": _row_get(row, "premium_ccy") or "",
}
)
return out
def compute_options_stats(get_db, ex: Any = None) -> dict[str, Any]:
conn = get_db()
try:
init_options_tables(conn)
closed_rows = conn.execute(
"""
SELECT realized_pnl, created_at, closed_at
SELECT realized_pnl, created_at, closed_at, inst_id, underlying, premium_ccy
FROM options_trades
WHERE status = 'closed' AND realized_pnl IS NOT NULL
"""
@@ -276,71 +304,33 @@ def compute_options_stats(get_db) -> dict[str, Any]:
SELECT created_at FROM options_trades WHERE status = 'open'
"""
).fetchall()
return _stats_from_option_trade_rows(closed_rows, open_rows)
history = _closed_trade_dicts(closed_rows)
for row in open_rows:
history.append({"status": "open", "created_at": _row_get(row, "created_at")})
# 币本位盈亏按指数折 U,再算胜率/盈亏比(与 /api/options/stats 一致)
return compute_options_stats_from_history(history_pnl_to_usdt(history, ex))
finally:
conn.close()
def _stats_from_option_trade_rows(closed_rows, open_rows) -> dict[str, Any]:
wins: list[float] = []
losses: list[float] = []
win_holds: list[float] = []
loss_holds: list[float] = []
all_holds: list[float] = []
now = datetime.now()
for row in closed_rows:
pnl = float(row["realized_pnl"])
hold = _hold_seconds(row["created_at"], row["closed_at"])
if hold is not None:
all_holds.append(hold)
if pnl > 0:
wins.append(pnl)
if hold is not None:
win_holds.append(hold)
elif pnl < 0:
losses.append(pnl)
if hold is not None:
loss_holds.append(hold)
open_holds: list[float] = []
for row in open_rows:
start = _parse_ts(row["created_at"])
if start is None:
continue
sec = (now - start).total_seconds()
if sec >= 0:
open_holds.append(sec)
total_closed = len(wins) + len(losses)
win_rate = round(len(wins) / total_closed * 100, 2) if total_closed else 0
avg_win = sum(wins) / len(wins) if wins else None
avg_loss = sum(losses) / len(losses) if losses else None
total_profit = round(sum(wins), 4) if wins else 0.0
total_loss = round(abs(sum(losses)), 4) if losses else 0.0
net_realized = round(sum(wins) + sum(losses), 4)
return {
"total_closed": total_closed,
"win_count": len(wins),
"loss_count": len(losses),
"win_rate": win_rate,
"profit_loss_ratio": profit_loss_ratio_from_averages(avg_win, avg_loss),
"avg_win": round(avg_win, 4) if avg_win is not None else None,
"avg_loss": round(abs(avg_loss), 4) if avg_loss is not None else None,
"total_profit": total_profit,
"total_loss": total_loss,
"net_realized_pnl": net_realized,
"avg_hold_sec": _avg_seconds(all_holds),
"avg_win_hold_sec": _avg_seconds(win_holds),
"avg_loss_hold_sec": _avg_seconds(loss_holds),
"open_count": len(open_holds),
"avg_open_hold_sec": _avg_seconds(open_holds),
}
def _stats_from_option_trade_rows(closed_rows, open_rows, *, ex: Any = None) -> dict[str, Any]:
"""兼容旧调用;内部统一折 U 后走 compute_options_stats_from_history."""
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))
def header_options_stats_for_window(conn, list_window: dict[str, Any], app_tz) -> dict[str, Any]:
"""顶栏总交易/胜率/盈亏比:纯期权(options_trades),全站导航共用,按列表窗过滤."""
def header_options_stats_for_window(
conn,
list_window: dict[str, Any],
app_tz,
ex: Any = None,
) -> dict[str, Any]:
"""顶栏总交易/胜率/盈亏比:纯期权(options_trades),全站导航共用,按列表窗过滤.
币本位 realized_pnl 为币数量,须折 U 后再算盈亏比,否则 BTC/ETH 混算会严重失真.
"""
from lib.common.history_window_lib import utc_window_to_bj_sql_strings
init_options_tables(conn)
@@ -349,7 +339,7 @@ def header_options_stats_for_window(conn, list_window: dict[str, Any], app_tz) -
)
closed_rows = conn.execute(
"""
SELECT realized_pnl, created_at, closed_at
SELECT realized_pnl, created_at, closed_at, inst_id, underlying, premium_ccy
FROM options_trades
WHERE status = 'closed'
AND realized_pnl IS NOT NULL
@@ -361,7 +351,10 @@ def header_options_stats_for_window(conn, list_window: dict[str, Any], app_tz) -
open_rows = conn.execute(
"SELECT created_at FROM options_trades WHERE status = 'open'"
).fetchall()
stats = _stats_from_option_trade_rows(closed_rows, open_rows)
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))
return {
"total": int(stats.get("total_closed") or 0),
"rate": float(stats.get("win_rate") or 0),