Hub funds and dashboard: include OKX options balances in USDT totals.

Merge options USDC/USDT at 1:1 into fund overview snapshots, dashboard aggregation, and monitor board totals while keeping a separate options breakdown in the UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-07 13:06:01 +08:00
parent 6cc3382526
commit 3163912266
9 changed files with 362 additions and 21 deletions
+80 -2
View File
@@ -21,6 +21,11 @@ from hub_ai.config import (
trading_day_reset_hour,
)
from hub_ai.fund_history import format_fund_history_text, get_fund_history, record_fund_snapshot
from lib.hub.hub_options_funds_lib import (
merge_perp_options_balances,
options_float_pnl_usdt,
options_open_position_count,
)
from lib.hub.hub_trades_lib import current_trading_day, summarize_trades
_CHAT_CONTEXT_CACHE: dict[str, dict[str, Any]] = {}
@@ -413,6 +418,13 @@ def _fetch_account_bundle(
"funding_usdt": None,
"trading_usdt": None,
"available_trading_usdt": None,
"perpetual_funding_usdt": None,
"perpetual_trading_usdt": None,
"options_funding_usdt": None,
"options_trading_usdt": None,
"options_float_pnl_u": None,
"options_open_position_count": 0,
"options_snapshot": None,
"trades_yesterday": [],
"trade_stats_yesterday": summarize_trades([]),
"monitor_lines": {"trends": [], "orders": [], "keys": [], "rolls": []},
@@ -465,8 +477,10 @@ def _fetch_account_bundle(
if r.status_code == 200:
acct_body = r.json()
if isinstance(acct_body, dict) and acct_body.get("ok"):
base["funding_usdt"] = _safe_float(acct_body.get("funding_usdt"))
base["trading_usdt"] = _safe_float(acct_body.get("trading_usdt"))
base["perpetual_funding_usdt"] = _safe_float(acct_body.get("funding_usdt"))
base["perpetual_trading_usdt"] = _safe_float(acct_body.get("trading_usdt"))
base["funding_usdt"] = base["perpetual_funding_usdt"]
base["trading_usdt"] = base["perpetual_trading_usdt"]
base["available_trading_usdt"] = _safe_float(acct_body.get("available_trading_usdt"))
base["flask_ok"] = True
except Exception as exc:
@@ -542,6 +556,41 @@ def _fetch_account_bundle(
if base["positions"]:
_enrich_positions_exchange_tpsl(base["positions"], price_snap, hub_mon)
caps = ex.get("capabilities") or []
if "options" in caps:
try:
r = client.get(
f"{flask_url}/api/hub/options/snapshot",
headers=_hub_headers(),
timeout=hub_flask_timeout(),
)
if r.status_code == 200:
opt_body = r.json()
if isinstance(opt_body, dict):
base["options_snapshot"] = opt_body
if opt_body.get("ok") is not False and opt_body.get("enabled") is not False:
base["flask_ok"] = True
merged = merge_perp_options_balances(
base.get("perpetual_funding_usdt"),
base.get("perpetual_trading_usdt"),
opt_body,
)
base["options_funding_usdt"] = merged.get("options_funding_usdt")
base["options_trading_usdt"] = merged.get("options_trading_usdt")
if merged.get("funding_usdt") is not None:
base["funding_usdt"] = merged.get("funding_usdt")
if merged.get("trading_usdt") is not None:
base["trading_usdt"] = merged.get("trading_usdt")
opt_count = options_open_position_count(opt_body)
base["options_open_position_count"] = opt_count
base["open_position_count"] += opt_count
opt_upl = options_float_pnl_usdt(opt_body)
if opt_upl is not None:
base["options_float_pnl_u"] = opt_upl
base["float_pnl_u"] = round(float(base["float_pnl_u"]) + opt_upl, 4)
except Exception as exc:
base["issues"].append(f"期权接口: {exc}")
if monitored and not base["agent_ok"] and not base["flask_ok"]:
base["status"] = "连接异常"
elif base["issues"]:
@@ -598,6 +647,9 @@ def build_daily_context(
total_funding = 0.0
total_trading = 0.0
total_open_positions = 0
total_options_open_positions = 0
total_options_float = 0.0
options_float_known = 0
funding_known = trading_known = 0
for ac in accounts:
if ac.get("status") == "未监控":
@@ -609,6 +661,11 @@ def build_daily_context(
total_loss += int(st.get("loss_count") or 0)
total_float += float(ac.get("float_pnl_u") or 0)
total_open_positions += int(ac.get("open_position_count") or _account_open_position_count(ac))
total_options_open_positions += int(ac.get("options_open_position_count") or 0)
opt_float = _safe_float(ac.get("options_float_pnl_u"))
if opt_float is not None:
total_options_float += opt_float
options_float_known += 1
fu = _safe_float(ac.get("funding_usdt"))
tu = _safe_float(ac.get("trading_usdt"))
if fu is not None:
@@ -631,6 +688,8 @@ def build_daily_context(
"loss_count": total_loss,
"float_pnl_u": round(total_float, 4),
"open_position_count": total_open_positions,
"options_open_position_count": total_options_open_positions,
"options_float_pnl_u": round(total_options_float, 4) if options_float_known else None,
"total_funding_usdt": round(total_funding, 4) if total_funding is not None else None,
"total_trading_usdt": round(total_trading, 4) if total_trading is not None else None,
}
@@ -929,6 +988,25 @@ def format_dashboard_account_detail(ac: dict) -> dict[str, Any]:
"pnl": round(upnl, 4),
}
)
opt_snap = ac.get("options_snapshot") if isinstance(ac.get("options_snapshot"), dict) else {}
if opt_snap.get("ok") is not False and opt_snap.get("enabled") is not False:
for p in opt_snap.get("positions") or []:
if not isinstance(p, dict):
continue
inst = p.get("inst_id") or "?"
opt_type = (p.get("opt_type") or "").upper()
label = "Call" if opt_type == "C" else "Put" if opt_type == "P" else opt_type or "OPT"
upl = p.get("upl")
line: dict[str, Any] = {
"kind": "options",
"text": f"期权 {inst} {label}",
}
if upl is not None:
try:
line["pnl"] = round(float(upl), 4)
except (TypeError, ValueError):
pass
position_lines.append(line)
issues = [str(x) for x in (ac.get("issues") or [])[:3]]
return {
"monitor_counts": {
+6
View File
@@ -55,6 +55,12 @@ def _enrich_account_row(ac: dict) -> dict:
"monitored": ac.get("status") != "未监控",
"funding_usdt": ac.get("funding_usdt"),
"trading_usdt": ac.get("trading_usdt"),
"perpetual_funding_usdt": ac.get("perpetual_funding_usdt"),
"perpetual_trading_usdt": ac.get("perpetual_trading_usdt"),
"options_funding_usdt": ac.get("options_funding_usdt"),
"options_trading_usdt": ac.get("options_trading_usdt"),
"options_float_pnl_u": ac.get("options_float_pnl_u"),
"options_open_position_count": ac.get("options_open_position_count"),
"capital_total_usdt": round(capital, 4) if capital is not None else None,
"available_trading_usdt": ac.get("available_trading_usdt"),
"pnl_u": st.get("total_pnl_u"),
+34 -3
View File
@@ -60,6 +60,8 @@
const floating = Number(totals.float_pnl_u);
const funding = totals.total_funding_usdt;
const trading = totals.total_trading_usdt;
const optionsFloat = Number(totals.options_float_pnl_u);
const optionsPos = totals.options_open_position_count;
elKpi.innerHTML = [
kpiCard("交易日", esc(totals.trading_day || "—"), ""),
kpiCard("平仓盈亏", pnlSigned(closed, 2), pnlClass(closed)),
@@ -76,10 +78,20 @@
? `${fmt(Number(funding) + Number(trading), 2)}U`
: "—",
"",
`资金 ${fmt(funding, 2)} + 交易 ${fmt(trading, 2)}`
`永续+期权 USDT 等价 · 资金 ${fmt(funding, 2)} + 交易 ${fmt(trading, 2)}`
),
kpiCard("实盘持仓", `${totals.open_position_count || 0}`, ""),
].join("");
kpiCard(
"实盘持仓",
`${totals.open_position_count || 0}`,
"",
Number.isFinite(optionsPos) && optionsPos > 0 ? `含期权 ${optionsPos}` : ""
),
Number.isFinite(optionsFloat) && Math.abs(optionsFloat) > 1e-9
? kpiCard("期权浮盈", pnlSigned(optionsFloat, 2), pnlClass(optionsFloat))
: "",
]
.filter(Boolean)
.join("");
}
function kpiCard(label, value, valCls, sub) {
@@ -183,6 +195,24 @@
alert && barW > 0
? `<div class="dash-loss-bar" title="占资金合计 ${fmt(lossPct, 2)}%"><i style="width:${barW}%"></i></div>`
: "";
const optFunding = ac.options_funding_usdt;
const optTrading = ac.options_trading_usdt;
const optFloat = Number(ac.options_float_pnl_u);
const optPos = Number(ac.options_open_position_count) || 0;
const optMetrics =
optFunding != null || optTrading != null || optPos > 0
? `<div class="dash-ac-metric"><span>期权资金</span><strong>${fmt(
optFunding != null && optTrading != null
? Number(optFunding) + Number(optTrading)
: null,
2
)}U</strong></div>
<div class="dash-ac-metric"><span>期权持仓</span><strong>${optPos} 仓</strong></div>
<div class="dash-ac-metric"><span>期权浮盈</span><strong class="${pnlClass(optFloat)}">${pnlSigned(
optFloat,
2
)}</strong></div>`
: "";
return `<article class="dash-ac-card${alert ? " is-alert" : ""}${unmon ? " is-unmon" : ""}">
<div class="dash-ac-top">
<div class="dash-ac-name">${esc(ac.name || "—")}</div>
@@ -195,6 +225,7 @@
<div class="dash-ac-metric"><span>今日盈亏</span><strong class="${pnlClass(pnl)}">${pnlSigned(pnl, 2)}</strong></div>
<div class="dash-ac-metric"><span>平仓笔数</span><strong>${Number(ac.closed_count) || 0}</strong></div>
<div class="dash-ac-metric"><span>浮盈亏</span><strong class="${pnlClass(floatPnl)}">${pnlSigned(floatPnl, 2)}</strong></div>
${optMetrics}
</div>
${lossBar}
${renderAccountDetail(ac)}
+15 -2
View File
@@ -176,6 +176,18 @@
monitored && ac.funding_usdt != null ? fmt(ac.funding_usdt, 2) + " U" : "—";
const trading =
monitored && ac.trading_usdt != null ? fmt(ac.trading_usdt, 2) + " U" : "—";
const optFunding =
monitored && ac.options_funding_usdt != null ? fmt(ac.options_funding_usdt, 2) + " U" : "";
const optTrading =
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">' +
(optFunding || "—") +
" / " +
(optTrading || "—") +
"</span></div>"
: "";
const dd = ac.drawdown || {};
const ddU = dd.max_drawdown_u != null ? fmt(dd.max_drawdown_u, 2) + " U" : "—";
const ddPct = dd.max_drawdown_pct != null ? fmt(dd.max_drawdown_pct, 2) + "%" : "—";
@@ -214,6 +226,7 @@
'<div><span class="k">交易户</span><span class="v">' +
trading +
"</span></div>" +
optLine +
'<div><span class="k">较昨日</span><span class="v ' +
deltaCls +
'">' +
@@ -266,7 +279,7 @@
if (elFsTitle) elFsTitle.textContent = ac.name || ac.key || "—";
if (elFsSub) {
const parts = [
"资金户 + 交易户不含浮盈)",
"资金户 + 交易户 + 期权户(USDC≈USDT不含浮盈)",
"交易日 " + (meta.trading_day || "—"),
"自 " + (meta.history_start_day || "—") + " 起",
];
@@ -327,7 +340,7 @@
const hour = data && data.reset_hour != null ? data.reset_hour : 8;
if (elDescBody) {
elDescBody.textContent =
"总资金 = 各监控户(资金账户 + 交易账户);自 " +
"总资金 = 各监控户(永续资金账户 + 交易账户 + 期权账户,USDC 按 1:1 计入 USDT);自 " +
start +
" 起按北京时间 " +
hour +
+2 -2
View File
@@ -1145,8 +1145,8 @@
<script src="/assets/calculator.js?v=3"></script>
<script src="/assets/trade_stats_calendar.js?v=3"></script>
<script src="/assets/archive.js?v=20260626-archive-layout"></script>
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
<script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script>
<script src="/assets/funds.js?v=20260707-hub-options-funds"></script>
<script src="/assets/dashboard.js?v=20260707-hub-options-funds"></script>
<script src="/assets/strategy.js?v=3"></script>
<script src="/assets/ai_review_render.js?v=3"></script>
<script src="/assets/time_close_ui.js?v=3"></script>