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 — 市场状态
-
-
+
-
+
-
-
-
当前制度
-
—
-
-
—
-
置信度 —
-
成熟度 —
+
+
-
-
-
+
+
+
价格结构 PRICE STRUCTURE
+
—
+
+
-
-
+
-
-
+
-
-
-
-
-
+
+
+
制度历史 REGIME HISTORY
+
市场广度 BREADTH
-
-
信号期望查询
-
-
-
-
-
-
-
-
- —
-
+
+
信号期望 SIGNAL EXPECTANCY
+
+
+
+ —
-
-
- | 层级 | 样本 | 有效样本 | 原始胜率 | 后验胜率 | 平均收益 |
-
-
-
-
+
+ | 层级 | 样本 | 有效样本 | 原始胜率 | 后验胜率 | 平均收益 |
+
+
+