// dashboard.js — ChanMacro const C = { TREND: "#3fb950", RANGE: "#d29922", PANIC: "#f85149" }; let regimeChart = null, breadthChart = null; async function loadState() { try { const r = await fetch("/api/state"); const d = await r.json(); if (d.error) { document.getElementById("update-time").textContent = d.error; return; } document.getElementById("update-time").textContent = d.date; // Hero const regime = d.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 = "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); 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("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"; // 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 = `${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.toUpperCase().replace(" ", "\n"); document.getElementById("f-oi").style.color = 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; 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" ? "#8b949e" : d.volatility === "HIGH_VOL" ? "#d29922" : "#f85149"; 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("update-time").textContent = "连接失败"; } } async function loadHistory() { try { const r = await fetch("/api/history?days=60"); const d = await r.json(); const dates = d.regimes.map(x => x.date); const colors = d.regimes.map(x => C[x.regime] || "#5c6675"); if (regimeChart) regimeChart.destroy(); regimeChart = new Chart(document.getElementById("chart-regime").getContext("2d"), { type: "bar", 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 } }, scales: { 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" } } } } }); if (breadthChart) breadthChart.destroy(); 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.08)", fill: true, tension: 0.3, pointRadius: 0 }, { label: "下跌", data: d.breadth.map(x => x.decline), borderColor: "#f85149", 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: [3, 3], tension: 0.3, pointRadius: 0 }, ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: "#5c6675", usePointStyle: true, boxWidth: 6, font: { size: 10 } } } }, scales: { 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(e); } } async function loadExpectancy() { const signal = document.getElementById("exp-signal").value; try { const r = await fetch(`/api/expectancy?signal=${signal}`); const d = await r.json(); if (d.error) { document.getElementById("exp-layers").innerHTML = `