diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index f4c541e..93045ed 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -7896,6 +7896,45 @@ body.funds-fullscreen-open { } } +/* ── 监控页:永续 / 期权分块 ── */ +.hub-monitor-block { + margin-bottom: 4px; +} + +.hub-monitor-block-label { + font-size: 11px; + font-weight: 700; + letter-spacing: 0.06em; + color: var(--text); + margin: 0 0 8px; +} + +.hub-monitor-perp .section-title:first-of-type { + margin-top: 0; +} + +.hub-monitor-options { + margin-top: 14px; + padding-top: 12px; + border-top: 1px dashed var(--border-soft); +} + +.hub-monitor-options .hub-monitor-block-label { + color: var(--accent); +} + +.hub-monitor-options .section-title.hub-options-title { + margin-top: 0; +} + +html[data-theme="light"] .hub-monitor-block-label { + color: #142232; +} + +html[data-theme="light"] .hub-monitor-options .hub-monitor-block-label { + color: #006e9a; +} + /* ── 监控页:OKX 期权只读聚合 ── */ .hub-options-title { margin-top: 14px; diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 114556f..ec785ce 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -3404,79 +3404,106 @@ `; } - function renderAccountStatRow(row, ag) { + function rowHasOptionsLayout(row) { + if (!(row.capabilities || []).includes("options")) return false; + const opt = row.options || {}; + return opt.enabled !== false; + } + + function sumUsdtEquiv(a, b) { + const vals = [a, b] + .map((v) => (v == null || v === "" ? null : Number(v))) + .filter((v) => v != null && Number.isFinite(v)); + if (!vals.length) return null; + return vals.reduce((s, v) => s + v, 0); + } + + function optionsBalanceFields(opt) { + const bal = (opt && opt.balances) || opt || {}; + return { + funding: sumUsdtEquiv(bal.funding_usdt, bal.funding_usdc), + trading: sumUsdtEquiv(bal.trading_usdt, bal.trading_usdc), + upl: opt && opt.upl_total_usdc != null && Number.isFinite(Number(opt.upl_total_usdc)) + ? Number(opt.upl_total_usdc) + : null, + }; + } + + function renderStatRow(funding, trading, upnl) { if (!showAccountPnlPref()) return ""; - const upnl = ag.total_unrealized_pnl; return `
-
资金账户
${fmt(row.funding_usdt, 2)} U
-
交易账户
${fmt(row.trading_usdt, 2)} U
+
资金账户
${fmt(funding, 2)} U
+
交易账户
${fmt(trading, 2)} U
浮盈合计
${fmt(upnl, 2)}
`; } + function renderAccountStatRow(row, ag) { + return renderStatRow(row.funding_usdt, row.trading_usdt, ag.total_unrealized_pnl); + } + function shortOptionsInst(instId) { const s = String(instId || ""); if (s.length <= 22) return s; return s.slice(0, 10) + "…" + s.slice(-8); } - function renderOptionsMonitorSection(row) { - const caps = row.capabilities || []; - if (!caps.includes("options")) return ""; - const opt = row.options || {}; - let html = ""; - if (opt.enabled === false) { - html += '
期权 · 主账户
'; - html += '
期权未启用(OKX_OPTIONS_ENABLED)
'; - return html; - } - if (opt.ok === false) { - html += '
期权 · 主账户
'; - html += `
${esc(opt.msg || "期权数据不可用")}
`; - return html; - } - const pos = Array.isArray(opt.positions) ? opt.positions : []; - const upl = opt.upl_total_usdc; - const tradingUsdc = opt.trading_usdc != null ? opt.trading_usdc : (opt.balances && opt.balances.trading_usdc); - const stats = opt.stats || {}; - html += `
期权 · 主账户 · ${pos.length} 仓
`; - html += `
- 交易户 ${fmt(tradingUsdc, 2)} USDC - 浮盈 ${fmt(upl, 4)} USDC - 已平 ${esc(stats.total_closed != null ? stats.total_closed : "—")} · 胜率 ${esc(stats.win_rate != null ? stats.win_rate + "%" : "—")} -
`; - if (pos.length) { - html += '
'; - html += ""; - html += ""; - pos.forEach((p) => { - const optType = (p.opt_type || "").toUpperCase() === "C" ? "Call" : (p.opt_type || "").toUpperCase() === "P" ? "Put" : (p.opt_type || "—"); - html += ` - - - - - - - `; - }); - html += "
合约类型张数标记浮盈浮盈%
${esc(shortOptionsInst(p.inst_id))}${esc(optType)}${esc(p.pos)}${fmt(p.mark_px, 4)}${fmt(p.upl, 4)}${p.upl_ratio_pct != null ? esc(p.upl_ratio_pct) + "%" : "—"}
"; - } else { - html += '
暂无期权持仓
'; - } - if (row.flask_url_browser || row.flask_url) { - html += `
打开期权页
`; - } + function renderOptionsPositionsTable(pos) { + if (!pos.length) return '
暂无期权持仓
'; + let html = '
'; + html += ""; + html += ""; + pos.forEach((p) => { + const optType = (p.opt_type || "").toUpperCase() === "C" ? "Call" : (p.opt_type || "").toUpperCase() === "P" ? "Put" : (p.opt_type || "—"); + html += ` + + + + + + + `; + }); + html += "
合约类型张数标记浮盈浮盈%
${esc(shortOptionsInst(p.inst_id))}${esc(optType)}${esc(p.pos)}${fmt(p.mark_px, 4)}${fmt(p.upl, 4)}${p.upl_ratio_pct != null ? esc(p.upl_ratio_pct) + "%" : "—"}
"; return html; } - function renderGridBody(row, ag, pos, hm, flaskOk, keys, orders, trends, rolls, kmap) { - const tickMap = buildPriceTickMap(row); - const intraday = isIntradayDisciplineRow(row); - let inner = renderAccountStatRow(row, ag); - inner += `
交易所持仓 · ${pos.length} 仓
`; + function renderOptionsMonitorSection(row) { + if (!rowHasOptionsLayout(row)) return ""; + const opt = row.options || {}; + let html = '
'; + html += '
期权账户
'; + if (opt.enabled === false) { + html += renderStatRow(null, null, null); + html += '
期权持仓
'; + html += '
期权未启用(OKX_OPTIONS_ENABLED)
'; + html += "
"; + return html; + } + if (opt.ok === false) { + html += renderStatRow(null, null, null); + html += '
期权持仓
'; + html += `
${esc(opt.msg || "期权数据不可用")}
`; + html += ""; + return html; + } + const pos = Array.isArray(opt.positions) ? opt.positions : []; + const bal = optionsBalanceFields(opt); + html += renderStatRow(bal.funding, bal.trading, bal.upl); + html += `
期权持仓 · ${pos.length} 仓
`; + html += renderOptionsPositionsTable(pos); + if (row.flask_url_browser || row.flask_url) { + html += `
打开期权页
`; + } + html += ""; + return html; + } + + function renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday) { + let html = ""; + html += `
交易所持仓 · ${pos.length} 仓
`; if (pos.length) { - inner += renderGridPositionsTable( + html += renderGridPositionsTable( row.id, row.key || row.id, pos, @@ -3486,9 +3513,26 @@ intraday ); } else { - inner += '
无持仓
'; + html += '
无持仓
'; + } + return html; + } + + function renderGridBody(row, ag, pos, hm, flaskOk, keys, orders, trends, rolls, kmap) { + const tickMap = buildPriceTickMap(row); + const intraday = isIntradayDisciplineRow(row); + let inner = ""; + if (rowHasOptionsLayout(row)) { + inner += '
'; + inner += '
永续账户
'; + inner += renderAccountStatRow(row, ag); + inner += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday); + inner += "
"; + inner += renderOptionsMonitorSection(row); + } else { + inner += renderAccountStatRow(row, ag); + inner += renderPerpetualPositionsSection(row, ag, pos, orders, trends, tickMap, intraday); } - inner += renderOptionsMonitorSection(row); inner += renderCardStrategyStats(row, hm, flaskOk); inner += intraday ? `
日内纪律:禁手动平仓/改委托 · 整点强制清仓${row.force_close && row.force_close.enabled ? " · " + esc(row.force_close.label || "强制清仓") : ""}
` @@ -3532,10 +3576,14 @@ html += `
${esc(row.error || ag.error || "子代理不可用")}
`; return html; } - html += renderAccountStatRow(row, ag); const posCount = pos.length; const posListCls = hubPosListCountClass(posCount); - html += `
持仓(${posCount} 仓 · 每币种一卡)
`; + if (rowHasOptionsLayout(row)) { + html += '
'; + html += '
永续账户
'; + } + html += renderAccountStatRow(row, ag); + html += `
交易所持仓(${posCount} 仓 · 每币种一卡)
`; html += `
`; if (posCount) { pos.forEach((p) => { @@ -3553,6 +3601,9 @@ html += '
暂无持仓
'; } html += "
"; + if (rowHasOptionsLayout(row)) { + html += "
"; + } html += renderOptionsMonitorSection(row); html += '
'; if ((row.capabilities || []).includes("key")) { diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 661ecbc..56307fa 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -15,7 +15,7 @@ - + @@ -1151,6 +1151,6 @@ - +