Fix hub funds overview double-counting OKX USDT with options.

Only add options USDC/USDG onto perpetual USDT totals, repair historical double-counted snapshots, and label the options line as USDC.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-07 10:30:34 +08:00
parent 4bc238b014
commit 0b8e5a0914
5 changed files with 134 additions and 19 deletions
+4 -3
View File
@@ -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"))
+88 -11
View File
@@ -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 []
+1 -1
View File
@@ -211,7 +211,7 @@
monitored && ac.options_trading_usdt != null ? fmt(ac.options_trading_usdt, 2) + " U" : "";
const optLine =
optFunding || optTrading
? '<div><span class="k">期权</span><span class="v">' +
? '<div><span class="k">期权USDC</span><span class="v">' +
(optFunding || "—") +
" / " +
(optTrading || "—") +
+1 -1
View File
@@ -1756,7 +1756,7 @@
<script src="/assets/trade_stats_calendar.js?v=3"></script>
<script src="/assets/archive.js?v=20260724-opt-archive"></script>
<script src="/assets/quotes.js?v=20260717-quotes-feed"></script>
<script src="/assets/funds.js?v=20260717-funds-scroll-fix"></script>
<script src="/assets/funds.js?v=20260807-opt-no-double"></script>
<script src="/assets/dashboard.js?v=20260807-opt-float"></script>
<script src="/assets/strategy.js?v=11"></script>
<script src="/assets/amp_stats.js?v=20260728-hedge"></script>
+40 -3
View File
@@ -4,6 +4,7 @@ from lib.hub.hub_options_funds_lib import (
merge_board_row_balances,
merge_perp_options_balances,
options_balances_usdt_equiv,
repair_double_counted_fund_entry,
)
@@ -18,20 +19,30 @@ class HubOptionsFundsLibTests(TestCase):
self.assertTrue(out["ok"])
self.assertEqual(out["funding_usdt"], 10.0)
self.assertEqual(out["trading_usdt"], 7.0)
self.assertEqual(out["funding_usdc_equiv"], 10.0)
self.assertEqual(out["trading_usdc_equiv"], 2.0)
def test_merge_perp_options_balances(self):
def test_merge_perp_options_balances_adds_usdc_only(self):
"""永续已含同账户 USDT 时,只叠加期权 USDC,避免双计."""
out = merge_perp_options_balances(
100,
50,
{
"ok": True,
"enabled": True,
"balances": {"funding_usdc": 8, "trading_usdc": 4},
"balances": {
"funding_usdt": 100,
"funding_usdc": 8,
"trading_usdt": 50,
"trading_usdc": 4,
},
},
)
self.assertEqual(out["funding_usdt"], 108.0)
self.assertEqual(out["trading_usdt"], 54.0)
self.assertEqual(out["total_usdt"], 162.0)
self.assertEqual(out["options_funding_usdt"], 8.0)
self.assertEqual(out["options_trading_usdt"], 4.0)
def test_merge_board_row_balances(self):
row = {
@@ -42,7 +53,7 @@ class HubOptionsFundsLibTests(TestCase):
"options": {
"ok": True,
"enabled": True,
"balances": {"funding_usdc": 1, "trading_usdc": 2},
"balances": {"funding_usdt": 20, "funding_usdc": 1, "trading_usdc": 2},
"positions": [{"inst_id": "X"}],
"upl_total_usdc": 0.5,
},
@@ -51,3 +62,29 @@ class HubOptionsFundsLibTests(TestCase):
self.assertEqual(out["total_usdt"], 53.0)
self.assertEqual(out["options_open_position_count"], 1)
self.assertEqual(out["options_float_pnl_u"], 0.5)
def test_repair_double_counted_fund_entry(self):
raw = {
"funding_usdt": 586.82,
"trading_usdt": 69.46,
"total_usdt": 656.28,
"options_funding_usdt": 293.26,
"options_trading_usdt": 69.44,
}
fixed = repair_double_counted_fund_entry(raw)
self.assertTrue(fixed.get("repaired_double_count"))
self.assertEqual(fixed["funding_usdt"], 293.26)
self.assertEqual(fixed["trading_usdt"], 69.44)
self.assertEqual(fixed["total_usdt"], round(293.26 + 69.44, 4))
def test_repair_skips_new_usdc_only_options_line(self):
raw = {
"funding_usdt": 293.26,
"trading_usdt": 69.44,
"total_usdt": 362.7,
"options_funding_usdt": 0.5,
"options_trading_usdt": 69.42,
}
fixed = repair_double_counted_fund_entry(raw)
self.assertFalse(fixed.get("repaired_double_count"))
self.assertEqual(fixed["total_usdt"], 362.7)