diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index 144923f..fbd80f1 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -1280,6 +1280,39 @@ body.market-chart-fs-open { border-bottom: 1px dashed var(--border-soft); } +.card-strategy-stats { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 10px 0 4px; + padding-top: 8px; + border-top: 1px dashed var(--border-soft); +} + +.card-stat-chip { + display: inline-flex; + align-items: center; + padding: 3px 8px; + border-radius: 6px; + font-size: 11px; + line-height: 1.3; + color: var(--accent); + background: var(--accent-dim); + border: 1px solid var(--border-soft); +} + +.hub-tile .card-strategy-stats { + margin: 4px 0 0; + padding-top: 6px; + border-top: none; + gap: 4px; +} + +.hub-tile .card-stat-chip { + font-size: 10px; + padding: 2px 6px; +} + .pos-action-group { display: inline-flex; flex-direction: row; diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 105684e..5062498 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -1606,6 +1606,68 @@ `; } + const KEY_BUCKET_FIB_TYPES = new Set([ + "斐波回调0.618", + "斐波回调0.786", + "关键位斐波0.618", + "关键位斐波0.786", + ]); + const KEY_BUCKET_BREAKOUT_TYPES = new Set([ + "箱体突破", + "收敛突破", + "关键位箱体突破", + "关键位收敛突破", + "关键位收敛结构", + ]); + const KEY_BUCKET_WATCH_TYPES = new Set([ + "关键阻力位", + "关键支撑位", + "关键位监控", + ]); + + function classifyKeyMonitorBucket(monitorType) { + const t = String(monitorType || "").trim(); + if (!t) return "watch"; + if (KEY_BUCKET_FIB_TYPES.has(t) || /斐波/.test(t)) return "fib"; + if (KEY_BUCKET_BREAKOUT_TYPES.has(t) || /突破/.test(t)) return "breakout"; + if (KEY_BUCKET_WATCH_TYPES.has(t) || /阻力|支撑/.test(t)) return "watch"; + return "watch"; + } + + function countKeyMonitorsByBucket(keys) { + const counts = { breakout: 0, fib: 0, watch: 0 }; + (keys || []).forEach((k) => { + if (!k || typeof k !== "object") return; + const bucket = classifyKeyMonitorBucket(k.monitor_type || k.type); + if (bucket === "breakout") counts.breakout += 1; + else if (bucket === "fib") counts.fib += 1; + else counts.watch += 1; + }); + return counts; + } + + function renderCardStrategyStats(row, hm, flaskOk) { + if (!flaskOk || !hm || typeof hm !== "object") return ""; + const caps = row.capabilities || []; + const chips = []; + if (caps.includes("key")) { + const kc = countKeyMonitorsByBucket(hm.keys || []); + if (kc.breakout > 0) chips.push(`突破 ${kc.breakout}`); + if (kc.fib > 0) chips.push(`斐波 ${kc.fib}`); + if (kc.watch > 0) chips.push(`监控 ${kc.watch}`); + } + if (caps.includes("trend")) { + const trendN = Array.isArray(hm.trends) ? hm.trends.length : 0; + if (trendN > 0) chips.push(`趋势回调 ${trendN}`); + } + const rollN = Array.isArray(hm.rolls) ? hm.rolls.length : 0; + if (rollN > 0) chips.push(`顺势加仓 ${rollN}`); + if (!chips.length) return ""; + return `