From 7813e319b4314bfe6fc00f56950b87f42c45f43a Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Wed, 24 Jun 2026 18:28:40 +0800 Subject: [PATCH] =?UTF-8?q?web:=20professional=20trading-terminal=20redesi?= =?UTF-8?q?gn=20=E2=80=94=20dark=20theme,=20chart=20grid,=20progress=20bar?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ChanMacro/web/static/js/dashboard.js | 175 ++++++++++---------- ChanMacro/web/templates/index.html | 233 +++++++++++++++------------ 2 files changed, 208 insertions(+), 200 deletions(-) diff --git a/ChanMacro/web/static/js/dashboard.js b/ChanMacro/web/static/js/dashboard.js index 781d698..6d6fb53 100644 --- a/ChanMacro/web/static/js/dashboard.js +++ b/ChanMacro/web/static/js/dashboard.js @@ -1,60 +1,75 @@ -// dashboard.js — ChanMacro dashboard +// dashboard.js — ChanMacro +const C = { TREND: "#3fb950", RANGE: "#d29922", PANIC: "#f85149" }; let regimeChart = null, breadthChart = null; -const REGIME_COLORS = { TREND: "#3fb950", RANGE: "#d29922", PANIC: "#f85149" }; -const BUCKET_CLASS = { EXTREME: "bucket-EXTREME", STRONG: "bucket-STRONG", - NORMAL: "bucket-NORMAL", WEAK: "bucket-WEAK", PANIC: "bucket-PANIC" }; - async function loadState() { try { const r = await fetch("/api/state"); const d = await r.json(); - if (d.error) { document.getElementById("db-status").textContent = d.error; return; } + if (d.error) { document.getElementById("update-time").textContent = d.error; return; } - document.getElementById("db-status").textContent = "✓ " + d.date; - document.getElementById("update-time").textContent = "更新于 " + new Date().toLocaleTimeString(); + document.getElementById("update-time").textContent = d.date; // Hero const regime = d.regime; - document.getElementById("hero-regime").textContent = regime === "TREND" ? "趋势" : regime === "RANGE" ? "震荡" : "恐慌"; - document.getElementById("hero-regime").className = "hero-regime regime-" + regime; + const names = { TREND: "TREND", RANGE: "RANGE", PANIC: "PANIC" }; + document.getElementById("hero-regime").textContent = names[regime] || regime; + document.getElementById("hero-regime").className = "regime-name " + regime.toLowerCase(); document.getElementById("hero-badge").textContent = regime; - document.getElementById("hero-badge").className = "badge-regime badge-" + regime; + document.getElementById("hero-badge").className = "regime-badge " + regime.toLowerCase(); document.getElementById("hero-conf").textContent = (d.regime_confidence * 100).toFixed(0) + "%"; - document.getElementById("hero-maturity").textContent = d.regime_maturity.toFixed(0) + "/100"; - - // Factors - document.getElementById("f-price").textContent = d.price_structure.score.toFixed(0); - document.getElementById("f-price").style.color = + document.getElementById("hero-maturity").textContent = d.regime_maturity.toFixed(0); + document.getElementById("hero-ps").textContent = d.price_structure.score.toFixed(0); + document.getElementById("hero-ps").style.color = d.price_structure.score >= 60 ? "#3fb950" : d.price_structure.score >= 40 ? "#d29922" : "#f85149"; - document.getElementById("f-price-sub").textContent = d.price_structure.label; - document.getElementById("f-price-narr").textContent = d.price_structure.narrative; + document.getElementById("hero-br").textContent = d.breadth.score.toFixed(0); + document.getElementById("hero-br").style.color = + d.breadth.bucket === "EXTREME" || d.breadth.bucket === "STRONG" ? "#3fb950" : + d.breadth.bucket === "WEAK" || d.breadth.bucket === "PANIC" ? "#f85149" : "#d29922"; - const b = d.breadth; - document.getElementById("f-breadth").textContent = b.score.toFixed(0); - document.getElementById("f-breadth").setAttribute("style", - "color: " + (b.bucket === "EXTREME" || b.bucket === "STRONG" ? "#3fb950" : - b.bucket === "WEAK" || b.bucket === "PANIC" ? "#f85149" : - b.bucket === "NORMAL" ? "#d29922" : "#e6edf3")); + // Factor cards + const ps = d.price_structure; + document.getElementById("f-price").textContent = ps.score.toFixed(0); + document.getElementById("f-price").style.color = + ps.score >= 60 ? "#3fb950" : ps.score >= 40 ? "#d29922" : "#f85149"; + document.getElementById("f-price-sub").textContent = + `趋势 ${ps.trend.toFixed(0)} · 波动 ${ps.vol_comp.toFixed(0)} · 动量 ${ps.momentum.toFixed(0)}`; + document.getElementById("bar-price").style.width = ps.score + "%"; + document.getElementById("bar-price").style.background = + ps.score >= 60 ? "#3fb950" : ps.score >= 40 ? "#d29922" : "#f85149"; + + const br = d.breadth; + document.getElementById("f-breadth").textContent = br.score.toFixed(0); + document.getElementById("f-breadth").style.color = + br.bucket === "EXTREME" || br.bucket === "STRONG" ? "#3fb950" : + br.bucket === "WEAK" || br.bucket === "PANIC" ? "#f85149" : "#d29922"; document.getElementById("f-breadth-sub").textContent = - `${b.bucket} · T20=${b.top20.toFixed(0)} T50=${b.top50.toFixed(0)} div=${b.divergence > 0 ? "+" : ""}${b.divergence.toFixed(0)}`; - document.getElementById("f-breadth-narr").textContent = b.narrative; + `${br.bucket} · T20=${br.top20.toFixed(0)} T50=${br.top50.toFixed(0)}`; + document.getElementById("bar-breadth").style.width = br.score + "%"; + document.getElementById("bar-breadth").style.background = + br.bucket === "EXTREME" || br.bucket === "STRONG" ? "#3fb950" : + br.bucket === "WEAK" || br.bucket === "PANIC" ? "#f85149" : "#d29922"; - document.getElementById("f-oi").textContent = d.oi_state; + document.getElementById("f-oi").textContent = d.oi_state.toUpperCase().replace(" ", "\n"); document.getElementById("f-oi").style.color = - d.oi_state === "New Longs" ? "#3fb950" : d.oi_state === "New Shorts" ? "#f85149" : - d.oi_state === "Short Covering" ? "#d29922" : d.oi_state === "Long Exit" ? "#f85149" : "#e6edf3"; - document.getElementById("f-oi-sub").textContent = `分数: ${d.oi_score.toFixed(0)}`; - document.getElementById("f-oi-narr").textContent = d.oi_narrative; + d.oi_state === "New Longs" ? "#3fb950" : d.oi_state.includes("Short") || d.oi_state === "Long Exit" ? "#f85149" : "#8b949e"; + document.getElementById("f-oi-sub").textContent = d.oi_narrative; - document.getElementById("f-vol").textContent = d.volatility; + const vm = { LOW_VOL: "低波动", NORMAL_VOL: "正常", HIGH_VOL: "高波动", EXPLOSIVE_VOL: "极端" }; + document.getElementById("f-vol").textContent = vm[d.volatility] || d.volatility; document.getElementById("f-vol").style.color = - d.volatility === "LOW_VOL" ? "#58a6ff" : d.volatility === "NORMAL_VOL" ? "#e6edf3" : + d.volatility === "LOW_VOL" ? "#58a6ff" : d.volatility === "NORMAL_VOL" ? "#8b949e" : d.volatility === "HIGH_VOL" ? "#d29922" : "#f85149"; - document.getElementById("f-vol-sub").textContent = `分数: ${d.price_structure.score.toFixed(0)}`; + document.getElementById("f-vol-sub").textContent = d.volatility; + document.getElementById("bar-vol").style.width = + (d.volatility === "EXPLOSIVE_VOL" ? 95 : d.volatility === "HIGH_VOL" ? 70 : + d.volatility === "NORMAL_VOL" ? 40 : 20) + "%"; + document.getElementById("bar-vol").style.background = + d.volatility === "EXPLOSIVE_VOL" ? "#f85149" : d.volatility === "HIGH_VOL" ? "#d29922" : + d.volatility === "NORMAL_VOL" ? "#8b949e" : "#58a6ff"; } catch (e) { - document.getElementById("db-status").textContent = "连接失败"; + document.getElementById("update-time").textContent = "连接失败"; } } @@ -63,72 +78,50 @@ async function loadHistory() { const r = await fetch("/api/history?days=60"); const d = await r.json(); - // Regime chart const dates = d.regimes.map(x => x.date); - const regimes = d.regimes.map(x => x.regime); - const colors = regimes.map(r => REGIME_COLORS[r] || "#8b949e"); + const colors = d.regimes.map(x => C[x.regime] || "#5c6675"); if (regimeChart) regimeChart.destroy(); - const ctx1 = document.getElementById("chart-regime").getContext("2d"); - regimeChart = new Chart(ctx1, { + regimeChart = new Chart(document.getElementById("chart-regime").getContext("2d"), { type: "bar", - data: { - labels: dates, - datasets: [{ - label: "置信度", - data: d.regimes.map(x => x.confidence * 100), - backgroundColor: colors, - borderWidth: 0, - borderRadius: 2, - }] - }, + data: { labels: dates, datasets: [{ data: d.regimes.map(x => x.confidence * 100), + backgroundColor: colors, borderWidth: 0, borderRadius: 2 }] }, options: { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { display: false }, - tooltip: { - callbacks: { - label: ctx => `${d.regimes[ctx.dataIndex].regime} · ${ctx.raw.toFixed(0)}%` - } - } - }, + responsive: true, maintainAspectRatio: false, + plugins: { legend: { display: false } }, scales: { - x: { ticks: { color: "#8b949e", maxTicksLimit: 15, maxRotation: 45 } }, - y: { max: 100, ticks: { color: "#8b949e" } } + x: { ticks: { color: "#5c6675", maxTicksLimit: 15, maxRotation: 45, font: { size: 10 } }, + grid: { color: "#151a23" } }, + y: { max: 100, ticks: { color: "#5c6675", font: { size: 10 } }, grid: { color: "#151a23" } } } } }); - // Breadth chart if (breadthChart) breadthChart.destroy(); - const ctx2 = document.getElementById("chart-breadth").getContext("2d"); - breadthChart = new Chart(ctx2, { + breadthChart = new Chart(document.getElementById("chart-breadth").getContext("2d"), { type: "line", data: { labels: d.breadth.map(x => x.date), datasets: [ { label: "上涨", data: d.breadth.map(x => x.advance), borderColor: "#3fb950", - backgroundColor: "rgba(63,185,80,0.1)", fill: true, tension: 0.3, pointRadius: 0 }, + backgroundColor: "rgba(63,185,80,0.08)", fill: true, tension: 0.3, pointRadius: 0 }, { label: "下跌", data: d.breadth.map(x => x.decline), borderColor: "#f85149", - backgroundColor: "rgba(248,81,73,0.1)", fill: true, tension: 0.3, pointRadius: 0 }, + backgroundColor: "rgba(248,81,73,0.06)", fill: true, tension: 0.3, pointRadius: 0 }, { label: ">EMA20", data: d.breadth.map(x => x.above_ema20), borderColor: "#58a6ff", - borderDash: [4, 2], tension: 0.3, pointRadius: 0 }, + borderDash: [3, 3], tension: 0.3, pointRadius: 0 }, ] }, options: { - responsive: true, - maintainAspectRatio: false, - plugins: { legend: { labels: { color: "#8b949e", usePointStyle: true, boxWidth: 8 } } }, + responsive: true, maintainAspectRatio: false, + plugins: { legend: { labels: { color: "#5c6675", usePointStyle: true, boxWidth: 6, font: { size: 10 } } } }, scales: { - x: { ticks: { color: "#8b949e", maxTicksLimit: 15, maxRotation: 45 } }, - y: { max: 50, ticks: { color: "#8b949e" } } + x: { ticks: { color: "#5c6675", maxTicksLimit: 15, maxRotation: 45, font: { size: 10 } }, + grid: { color: "#151a23" } }, + y: { ticks: { color: "#5c6675", font: { size: 10 } }, grid: { color: "#151a23" } } } } }); - } catch (e) { - console.error("History load failed:", e); - } + } catch (e) { console.error(e); } } async function loadExpectancy() { @@ -136,36 +129,32 @@ async function loadExpectancy() { try { const r = await fetch(`/api/expectancy?signal=${signal}`); const d = await r.json(); - if (d.error) { document.getElementById("exp-layers").innerHTML = `${d.error}`; return; } + if (d.error) { document.getElementById("exp-layers").innerHTML = + `${d.error}`; return; } - document.getElementById("exp-sufficiency").textContent = d.sufficiency; - document.getElementById("exp-sufficiency").className = - "badge " + (d.sufficiency === "HIGH" ? "bg-success" : d.sufficiency === "MEDIUM" ? "bg-warning" : - d.sufficiency === "LOW" ? "bg-danger" : "bg-secondary"); + const el = document.getElementById("exp-sufficiency"); + el.textContent = d.sufficiency; + el.className = "suff suff-" + d.sufficiency; let html = ""; for (const l of d.layers) { html += ` - ${l.name} - ${l.samples} - ${l.effective_samples.toFixed(0)} + ${l.name}${l.samples}${l.effective_samples.toFixed(0)} ${l.raw_winrate ? (l.raw_winrate * 100).toFixed(1) + "%" : "—"} ${(l.posterior_winrate * 100).toFixed(1)}% - ${l.avg_return ? (l.avg_return > 0 ? "+" : "") + l.avg_return.toFixed(1) + "%" : "—"} + ${l.avg_return ? (l.avg_return > 0 ? "+" : "") + l.avg_return.toFixed(2) + "%" : "—"} `; } document.getElementById("exp-layers").innerHTML = html; - let summary = `最终估计: ${(d.final_estimate * 100).toFixed(1)}%`; - if (d.avg_return_7d) summary += ` · 平均收益: ${d.avg_return_7d > 0 ? "+" : ""}${d.avg_return_7d.toFixed(1)}%`; - if (d.profit_factor) summary += ` · 盈亏比: ${d.profit_factor}`; - document.getElementById("exp-summary").innerHTML = summary; - } catch (e) { - console.error("Expectancy load failed:", e); - } + let s = `后验胜率 ${(d.final_estimate * 100).toFixed(1)}%`; + if (d.avg_return_7d) s += ` · 平均收益 ${d.avg_return_7d > 0 ? "+" : ""}${d.avg_return_7d.toFixed(2)}%`; + if (d.profit_factor) s += ` · 盈亏比 ${d.profit_factor}`; + if (d.max_adverse) s += ` · MAE ${d.max_adverse.toFixed(1)}%`; + document.getElementById("exp-summary").innerHTML = s; + } catch (e) { console.error(e); } } -// Init loadState(); loadHistory(); loadExpectancy(); diff --git a/ChanMacro/web/templates/index.html b/ChanMacro/web/templates/index.html index 1aa3b36..9433e79 100644 --- a/ChanMacro/web/templates/index.html +++ b/ChanMacro/web/templates/index.html @@ -4,138 +4,157 @@ ChanMacro — 市场状态 - -
+
-
+
-

ChanMacro 市场状态

- -
-
- 加载中... +

ChanMacro

+
加载中...
- -
-
当前制度
-
-
- - 置信度 - 成熟度 + +
+
+
+
+
+
+
+
置信度
+
成熟度
+
+
+
+
价格结构
+
+
+
市场广度
-
-
-
-
价格结构
-
-
-
-
+
+
+
价格结构 PRICE STRUCTURE
+
+
+
-
-
-
市场广度
-
-
-
-
+
+
市场广度 BREADTH
+
+
+
-
-
-
OI 状态
-
-
-
-
+
+
持仓状态 OI MATRIX
+
+
-
-
-
波动率
-
-
-
+
+
波动率 VOLATILITY
+
+
+
- -
-
-
-
制度历史
- -
-
-
-
-
市场广度
- -
-
+ +
+

制度历史 REGIME HISTORY

+

市场广度 BREADTH

-
-
信号期望查询
-
-
- -
-
- -
-
- -
+
+

信号期望 SIGNAL EXPECTANCY

+
+ + +
-
- - - -
层级样本有效样本原始胜率后验胜率平均收益
-
-
+ + + +
层级样本有效样本原始胜率后验胜率平均收益
+