diff --git a/manual_trading_hub/hub.py b/manual_trading_hub/hub.py index 1d5f3c4..d694bf2 100644 --- a/manual_trading_hub/hub.py +++ b/manual_trading_hub/hub.py @@ -62,7 +62,7 @@ _allow_pub_raw = (os.getenv("HUB_ALLOW_PUBLIC") or "").strip().lower() # 云服务器 + 域名反代时设为 true:不做 IP 限制,仅靠 HUB_PASSWORD / 登录页保护 HUB_ALLOW_PUBLIC = _allow_pub_raw in ("1", "true", "yes", "on") DIR = Path(__file__).resolve().parent -HUB_BUILD = "20260525-hub-sso" +HUB_BUILD = "20260525-ui-dir" HUB_AGENT_TIMEOUT = float(os.getenv("HUB_AGENT_TIMEOUT", "8")) HUB_FLASK_TIMEOUT = float(os.getenv("HUB_FLASK_TIMEOUT", "10")) _board_key_prices_raw = (os.getenv("HUB_BOARD_KEY_PRICES", "true") or "").strip().lower() diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index ed77791..da4c12d 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -710,14 +710,35 @@ body.hub-fullscreen-open { font-weight: 500; } -.hub-pos-card .pos-side-long { - background: rgba(37, 58, 110, 0.9); - color: #6eb5ff; +.hub-pos-card .pos-side-long, +.hub-pos-card .pos-side-badge.side-long { + background: rgba(0, 255, 157, 0.12); + color: var(--green); + border: 1px solid rgba(0, 255, 157, 0.35); } -.hub-pos-card .pos-side-short { - background: rgba(74, 34, 48, 0.9); - color: #ff8a8a; +.hub-pos-card .pos-side-short, +.hub-pos-card .pos-side-badge.side-short { + background: rgba(255, 77, 109, 0.12); + color: var(--red); + border: 1px solid rgba(255, 77, 109, 0.35); +} + +.side-long { + color: var(--green); + font-weight: 600; + text-shadow: 0 0 10px rgba(0, 255, 157, 0.25); +} + +.side-short { + color: var(--red); + font-weight: 600; + text-shadow: 0 0 10px rgba(255, 77, 109, 0.25); +} + +.data-table td.side-long, +.data-table td.side-short { + font-weight: 600; } .hub-pos-card .pos-head-actions { @@ -890,6 +911,31 @@ body.hub-fullscreen-open { border-radius: 8px; } +.hub-mini-card.hub-key-pending, +.list-line.hub-key-pending { + border-color: rgba(0, 212, 255, 0.55); + background: rgba(0, 212, 255, 0.08); + box-shadow: 0 0 16px rgba(0, 212, 255, 0.12); +} + +.hub-key-pending-tag { + display: inline-block; + margin-left: 6px; + padding: 1px 7px; + font-size: 10px; + font-weight: 600; + color: var(--accent); + background: rgba(0, 212, 255, 0.15); + border: 1px solid rgba(0, 212, 255, 0.45); + border-radius: 4px; + vertical-align: middle; +} + +.hub-key-pending .hub-key-status-line, +.list-line.hub-key-pending { + color: var(--text); +} + .hub-mini-title { font-size: 12px; font-weight: 600; diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 7d495b8..2f0fe0e 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -77,6 +77,45 @@ return n > 0 ? "pnl-pos" : "pnl-neg"; } + function normSide(side) { + const s = (side || "").toLowerCase(); + if (s === "buy") return "long"; + if (s === "sell") return "short"; + return s; + } + + function sideDirCls(side) { + const s = normSide(side); + if (s === "long") return "side-long"; + if (s === "short") return "side-short"; + return ""; + } + + function sideDirLabel(side) { + const s = normSide(side); + if (s === "long") return "做多"; + if (s === "short") return "做空"; + return side || "—"; + } + + function renderDirectionHtml(side) { + const cls = sideDirCls(side); + const label = sideDirLabel(side); + if (!cls) return esc(String(label)); + return `${esc(label)}`; + } + + function keyHasPendingOrder(keyRow, keyPrice) { + const kp = keyPrice || {}; + const oid = keyRow.fib_limit_order_id; + if (oid != null && String(oid).trim() !== "") return true; + const gm = String(kp.gate_metrics || ""); + if (gm.includes("限价单") || gm.includes("挂单")) return true; + const gs = String(kp.gate_summary || ""); + if (/挂|限价|等待成交/.test(gs)) return true; + return false; + } + /** 全屏持仓区:按仓位数量附加布局 class(1~6 固定列数,7+ 自动填充) */ function hubPosListCountClass(n) { const c = Math.max(0, parseInt(n, 10) || 0); @@ -499,8 +538,8 @@ function renderLivePositionCard(exchangeId, pos, monitorOrder) { const symbol = pos.symbol || ""; const side = (pos.side || "long").toLowerCase(); - const sideCn = side === "long" ? "做多" : "做空"; - const sideCls = side === "long" ? "pos-side-long" : "pos-side-short"; + const sideCn = sideDirLabel(side); + const sideCls = sideDirCls(side) || "side-long"; const mo = monitorOrder || {}; const cond = condOrdersFromPosition(pos); const reg = Array.isArray(pos.regular_orders) ? pos.regular_orders : []; @@ -584,10 +623,16 @@ .map((k) => { const kp = kmap[k.id] || kmap[String(k.id)] || {}; const mt = k.monitor_type || k.type || ""; - return `
-
${esc(k.symbol)} · ${esc(mt)}
+ const pending = keyHasPendingOrder(k, kp); + const cardCls = pending ? "hub-mini-card hub-key-pending" : "hub-mini-card"; + const dir = k.direction ? ` · ${renderDirectionHtml(k.direction)}` : ""; + const pendingTag = pending + ? `挂单中` + : ""; + return `
+
${esc(k.symbol)} · ${esc(mt)}${dir} ${pendingTag}
上沿 ${esc(k.upper)} / 下沿 ${esc(k.lower)}
-
${esc(kp.gate_summary || kp.price_display || kp.price || "—")}
+
${esc(kp.gate_summary || kp.price_display || kp.price || "—")}${kp.gate_metrics ? ` · ${esc(kp.gate_metrics)}` : ""}
`; }) .join(""); @@ -598,7 +643,7 @@ return orders .map( (o) => `
-
#${esc(o.id)} · ${esc(o.symbol || o.exchange_symbol)} · ${esc(o.direction)}
+
#${esc(o.id)} · ${esc(o.symbol || o.exchange_symbol)} · ${renderDirectionHtml(o.direction)}
触发 ${fmt(o.trigger_price, 4)} · SL ${fmt(o.stop_loss, 4)} · TP ${fmt(o.take_profit, 4)} · ${esc(o.trade_style || o.monitor_type || "下单监控")}
` ) @@ -610,7 +655,7 @@ return trends .map( (t) => `
-
#${esc(t.id)} · ${esc(t.symbol)} · ${esc(t.direction)}
+
#${esc(t.id)} · ${esc(t.symbol)} · ${renderDirectionHtml(t.direction)}
SL ${fmt(t.stop_loss, 4)} · TP ${fmt(t.take_profit, 4)} · 状态 ${esc(t.status || "active")}
` ) @@ -643,7 +688,7 @@ - +
合约方向张数浮盈操作
${esc(x.symbol)}${esc(x.side)}${renderDirectionHtml(x.side)} ${fmt(x.contracts, 4)} ${fmt(x.unrealized_pnl, 4)} @@ -673,7 +718,7 @@ if (orders.length) { inner += `
下单监控 · ${orders.length}
`; orders.forEach((o) => { - inner += `
${esc(o.symbol || o.exchange_symbol)} · ${esc(o.direction)} · 触发 ${fmt(o.trigger_price, 4)}
`; + inner += `
${esc(o.symbol || o.exchange_symbol)} · ${renderDirectionHtml(o.direction)} · 触发 ${fmt(o.trigger_price, 4)}
`; }); } if ((row.capabilities || []).includes("key")) { @@ -687,19 +732,24 @@ keys.forEach((k) => { const kp = kmap[k.id] || kmap[String(k.id)] || {}; const mt = k.monitor_type || k.type || ""; - let line = `${esc(k.symbol)} · ${esc(mt)} · ${k.upper} / ${k.lower}`; + const pending = keyHasPendingOrder(k, kp); + const lineCls = pending ? "list-line hub-key-pending" : "list-line"; + let line = `${esc(k.symbol)} · ${esc(mt)}`; + if (k.direction) line += ` · ${renderDirectionHtml(k.direction)}`; + if (pending) line += ` · 挂单`; + line += ` · ${esc(k.upper)} / ${esc(k.lower)}`; if (kp.price_display != null || kp.price != null) { line += ` · ${esc(kp.price_display != null ? kp.price_display : kp.price)}`; } line += ` · ${esc(kp.gate_summary || "-")}`; - inner += `
${line}
`; + inner += `
${line}
`; }); } } if ((row.capabilities || []).includes("trend") && trends.length) { inner += `
趋势回调 · ${trends.length}
`; trends.forEach((t) => { - inner += `
#${t.id} ${esc(t.symbol)} ${t.direction} · SL ${t.stop_loss} · TP ${t.take_profit}
`; + inner += `
#${t.id} ${esc(t.symbol)} ${renderDirectionHtml(t.direction)} · SL ${t.stop_loss} · TP ${t.take_profit}
`; }); } if (rolls.length) { diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 575f39e..d315fc6 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -8,7 +8,7 @@ - + @@ -109,6 +109,6 @@
- + diff --git a/manual_trading_hub/static/login.html b/manual_trading_hub/static/login.html index a65ac17..3b1d58a 100644 --- a/manual_trading_hub/static/login.html +++ b/manual_trading_hub/static/login.html @@ -8,7 +8,7 @@ - +