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
+
+
`;
}
+ 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 += `
- ${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) + "%" : "—"} |
-
`;
- });
- html += "
";
- } 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 += `
+ ${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) + "%" : "—"} |
+
`;
+ });
+ 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 = 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 @@
-
+