diff --git a/lib/hub/hub_fund_history_lib.py b/lib/hub/hub_fund_history_lib.py index f38a3af..1f5888b 100644 --- a/lib/hub/hub_fund_history_lib.py +++ b/lib/hub/hub_fund_history_lib.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import Any, Optional from lib.hub.hub_trades_lib import current_trading_day -from lib.hub.hub_options_funds_lib import merge_board_row_balances +from lib.hub.hub_options_funds_lib import merge_board_row_balances, repair_double_counted_fund_entry from lib.paths import manual_trading_hub_dir @@ -275,7 +275,7 @@ def _series_from_history( total = 0.0 n = 0 for key in account_keys: - ac = ac_map.get(key) or {} + ac = repair_double_counted_fund_entry(ac_map.get(key) or {}) t = account_total_usdt(ac.get("funding_usdt"), ac.get("trading_usdt")) if t is None: t = _safe_float(ac.get("total_usdt")) @@ -291,7 +291,8 @@ def _series_from_history( def _account_series(history: dict[str, dict], key: str) -> list[dict[str, Any]]: out: list[dict[str, Any]] = [] for day in sorted(history.keys()): - ac = (history.get(day) or {}).get("accounts", {}).get(key) or {} + raw = (history.get(day) or {}).get("accounts", {}).get(key) or {} + ac = repair_double_counted_fund_entry(raw) t = account_total_usdt(ac.get("funding_usdt"), ac.get("trading_usdt")) if t is None: t = _safe_float(ac.get("total_usdt")) diff --git a/lib/hub/hub_options_funds_lib.py b/lib/hub/hub_options_funds_lib.py index 602d4ef..e466747 100644 --- a/lib/hub/hub_options_funds_lib.py +++ b/lib/hub/hub_options_funds_lib.py @@ -36,17 +36,41 @@ def _sum_optional(*values: Any) -> Optional[float]: def options_balances_usdt_equiv(options_snap: dict[str, Any] | None) -> dict[str, Any]: - """从期权 snapshot 提取资金户/交易户 USDT 等价余额.""" + """从期权 snapshot 提取资金户/交易户 USDT 等价余额. + + - funding_usdt / trading_usdt: USDT+USDC(+USDG) 全账户(勿与永续 USDT 再加总) + - funding_usdc_equiv / trading_usdc_equiv: 仅非 USDT 稳定币,可安全加到永续 USDT 上 + """ snap = options_snap if isinstance(options_snap, dict) else {} if snap.get("enabled") is False: - return {"ok": False, "funding_usdt": None, "trading_usdt": None} + return { + "ok": False, + "funding_usdt": None, + "trading_usdt": None, + "funding_usdc_equiv": None, + "trading_usdc_equiv": None, + } if snap.get("ok") is False: - return {"ok": False, "funding_usdt": None, "trading_usdt": None} + return { + "ok": False, + "funding_usdt": None, + "trading_usdt": None, + "funding_usdc_equiv": None, + "trading_usdc_equiv": None, + } bal = snap.get("balances") if isinstance(snap.get("balances"), dict) else snap - funding = _sum_optional(bal.get("funding_usdt"), bal.get("funding_usdc")) - trading = _sum_optional(bal.get("trading_usdt"), bal.get("trading_usdc")) + funding_usdc_equiv = _sum_optional(bal.get("funding_usdc"), bal.get("funding_usdg")) + trading_usdc_equiv = _sum_optional(bal.get("trading_usdc"), bal.get("trading_usdg")) + funding = _sum_optional(bal.get("funding_usdt"), funding_usdc_equiv) + trading = _sum_optional(bal.get("trading_usdt"), trading_usdc_equiv) ok = funding is not None and trading is not None - return {"ok": ok, "funding_usdt": funding, "trading_usdt": trading} + return { + "ok": ok, + "funding_usdt": funding, + "trading_usdt": trading, + "funding_usdc_equiv": funding_usdc_equiv, + "trading_usdc_equiv": trading_usdc_equiv, + } def options_float_pnl_usdt(options_snap: dict[str, Any] | None) -> Optional[float]: @@ -96,19 +120,45 @@ def merge_perp_options_balances( perpetual_trading_usdt: Any, options_snap: dict[str, Any] | None, ) -> dict[str, Any]: - """永续 + 期权余额合并为中控 USDT 统计口径.""" + """永续 USDT + 期权非 USDT 稳定币合并为中控总资金(避免 OKX 同账户 USDT 双计). + + 与实例顶栏 total_funds_usdt(..., options_usdc, None, None) 口径一致: + 期权 snapshot 里的 USDT 与永续资金/交易户是同一钱包,只把 USDC/USDG 加上. + """ opt = options_balances_usdt_equiv(options_snap) - funding = _sum_optional(perpetual_funding_usdt, opt.get("funding_usdt")) - trading = _sum_optional(perpetual_trading_usdt, opt.get("trading_usdt")) + # 展示用期权户:优先非 USDT 稳定币;若仅有 USDT 则仍给出全量以便辨识 + opt_fund_disp = opt.get("funding_usdc_equiv") + opt_trade_disp = opt.get("trading_usdc_equiv") + if opt_fund_disp is None and opt_trade_disp is None and opt.get("ok"): + opt_fund_disp = opt.get("funding_usdt") + opt_trade_disp = opt.get("trading_usdt") + + if opt.get("ok"): + if perpetual_funding_usdt is None and perpetual_trading_usdt is None: + # 永续账户未取到时,期权 snapshot 已含同账户 USDT+USDC,直接用全量 + funding = opt.get("funding_usdt") + trading = opt.get("trading_usdt") + else: + funding = _sum_optional(perpetual_funding_usdt, opt.get("funding_usdc_equiv")) + trading = _sum_optional(perpetual_trading_usdt, opt.get("trading_usdc_equiv")) + else: + funding = _safe_float(perpetual_funding_usdt) + trading = _safe_float(perpetual_trading_usdt) + total = _account_total_usdt(funding, trading) + # 任一侧齐全即可展示;永续缺一侧但有期权 USDC 时仍尽量给出合计 + if total is None: + total = _sum_optional(funding, trading) perp_total = _account_total_usdt(perpetual_funding_usdt, perpetual_trading_usdt) opt_total = _account_total_usdt(opt.get("funding_usdt"), opt.get("trading_usdt")) data_ok = total is not None return { "perpetual_funding_usdt": _safe_float(perpetual_funding_usdt), "perpetual_trading_usdt": _safe_float(perpetual_trading_usdt), - "options_funding_usdt": opt.get("funding_usdt"), - "options_trading_usdt": opt.get("trading_usdt"), + "options_funding_usdt": opt_fund_disp, + "options_trading_usdt": opt_trade_disp, + "options_funding_full_usdt": opt.get("funding_usdt"), + "options_trading_full_usdt": opt.get("trading_usdt"), "options_ok": bool(opt.get("ok")), "funding_usdt": funding, "trading_usdt": trading, @@ -119,6 +169,33 @@ def merge_perp_options_balances( } +def repair_double_counted_fund_entry(ac: dict[str, Any]) -> dict[str, Any]: + """识别并修复历史快照中「永续 USDT + 期权(USDT+USDC)」的双计. + + 旧口径 options_* 存的是 USDT+USDC 全量,且 funding≈2×期权资金户 USDT 部分. + 新口径 options_* 多为纯 USDC,不会误伤. + """ + if not isinstance(ac, dict): + return {} + out = dict(ac) + ofu = _safe_float(ac.get("options_funding_usdt")) + otu = _safe_float(ac.get("options_trading_usdt")) + fu = _safe_float(ac.get("funding_usdt")) + tu = _safe_float(ac.get("trading_usdt")) + if ofu is None or otu is None or fu is None or tu is None: + return out + if ofu < 1.0: + return out + ratio = fu / ofu if ofu > 0 else 0.0 + # 经典双计:合并资金户 ≈ 2 × 期权资金户(同钱包 USDT 加了两遍) + if 1.8 <= ratio <= 2.25: + out["funding_usdt"] = ofu + out["trading_usdt"] = otu + out["total_usdt"] = round(ofu + otu, 4) + out["repaired_double_count"] = True + return out + + def merge_board_row_balances(row: dict[str, Any]) -> dict[str, Any]: """监控板行 → 含期权的资金统计.""" caps = row.get("capabilities") or [] diff --git a/manual_trading_hub/static/funds.js b/manual_trading_hub/static/funds.js index 8030121..29c77d9 100644 --- a/manual_trading_hub/static/funds.js +++ b/manual_trading_hub/static/funds.js @@ -211,7 +211,7 @@ monitored && ac.options_trading_usdt != null ? fmt(ac.options_trading_usdt, 2) + " U" : ""; const optLine = optFunding || optTrading - ? '