/** * 中控资金概况:总资金曲线、分户资金与回撤(资金户+交易户,不含浮盈)。 */ (function () { const page = document.getElementById("page-funds"); if (!page) return; const elStatus = document.getElementById("funds-status"); const elTotal = document.getElementById("funds-total-usdt"); const elDdU = document.getElementById("funds-total-dd-u"); const elDdPct = document.getElementById("funds-total-dd-pct"); const elDelta = document.getElementById("funds-total-delta"); const elMeta = document.getElementById("funds-meta"); const elChartHost = document.getElementById("funds-chart-total"); const elAccounts = document.getElementById("funds-accounts"); const elBtnRefresh = document.getElementById("funds-btn-refresh"); let chart = null; let lineSeries = null; let inited = false; let loading = false; function fmt(n, d) { if (n == null || n === "" || !Number.isFinite(Number(n))) return "—"; return Number(n).toFixed(d == null ? 2 : d); } function fmtDelta(n) { if (n == null || !Number.isFinite(Number(n))) return "—"; const v = Number(n); const sign = v > 0 ? "+" : ""; return sign + v.toFixed(2) + " U"; } function deltaClass(n) { if (!Number.isFinite(Number(n))) return ""; if (Number(n) > 0) return "pos"; if (Number(n) < 0) return "neg"; return ""; } function setStatus(msg, isErr) { if (!elStatus) return; elStatus.textContent = msg || ""; elStatus.className = "funds-status" + (isErr ? " err" : ""); } function seriesToChartData(series) { return (series || []) .filter(function (p) { return p && p.day && Number.isFinite(Number(p.total_usdt)); }) .map(function (p) { return { time: String(p.day), value: Number(p.total_usdt) }; }); } function destroyChart() { if (chart) { chart.remove(); chart = null; lineSeries = null; } if (elChartHost) elChartHost.innerHTML = ""; } function chartPalette() { const light = document.documentElement.getAttribute("data-theme") === "light"; return light ? { bg: "#f0f4f9", text: "#4a6078", border: "#b8c8d8", line: "#006e9a" } : { bg: "#0b0e18", text: "#9aa4b8", border: "#2a3348", line: "#3b82f6" }; } function ensureChart() { if (!elChartHost || !window.LightweightCharts) return; if (chart) return; const p = chartPalette(); chart = LightweightCharts.createChart(elChartHost, { layout: { background: { color: p.bg }, textColor: p.text }, grid: { vertLines: { color: p.border, visible: true }, horzLines: { color: p.border, visible: true }, }, rightPriceScale: { borderColor: p.border }, timeScale: { borderColor: p.border, timeVisible: true }, crosshair: { mode: LightweightCharts.CrosshairMode.Normal }, handleScroll: { mouseWheel: true, pressedMouseMove: true }, handleScale: { axisPressedMouseMove: true, mouseWheel: true, pinch: true }, }); lineSeries = chart.addAreaSeries({ lineColor: p.line, topColor: p.line + "44", bottomColor: p.line + "08", lineWidth: 2, priceFormat: { type: "price", precision: 2, minMove: 0.01 }, }); new ResizeObserver(function () { if (chart && elChartHost) { chart.applyOptions({ width: elChartHost.clientWidth, height: elChartHost.clientHeight }); } }).observe(elChartHost); chart.applyOptions({ width: elChartHost.clientWidth, height: elChartHost.clientHeight }); } function renderMiniChart(host, series) { if (!host || !window.LightweightCharts) return; host.innerHTML = ""; const data = seriesToChartData(series); if (data.length < 2) { host.textContent = "历史不足"; return; } const p = chartPalette(); const mini = LightweightCharts.createChart(host, { layout: { background: { color: "transparent" }, textColor: p.text }, grid: { vertLines: { visible: false }, horzLines: { visible: false } }, rightPriceScale: { visible: false }, timeScale: { visible: false }, crosshair: { mode: LightweightCharts.CrosshairMode.Hidden }, handleScroll: false, handleScale: false, }); const s = mini.addLineSeries({ color: p.line, lineWidth: 1.5 }); s.setData(data); mini.timeScale().fitContent(); const w = host.clientWidth; const h = host.clientHeight; if (w > 0 && h > 0) mini.applyOptions({ width: w, height: h }); } function renderAccounts(accounts) { if (!elAccounts) return; if (!accounts || !accounts.length) { elAccounts.innerHTML = '

暂无账户配置

'; return; } elAccounts.innerHTML = accounts .map(function (ac) { const monitored = !!ac.monitored; const cls = monitored ? "" : " is-off"; const total = monitored && ac.data_ok ? fmt(ac.total_usdt, 2) + " U" : "—"; const funding = monitored && ac.funding_usdt != null ? fmt(ac.funding_usdt, 2) : "—"; const trading = monitored && ac.trading_usdt != null ? fmt(ac.trading_usdt, 2) : "—"; const dd = ac.drawdown || {}; const ddU = dd.max_drawdown_u != null ? fmt(dd.max_drawdown_u, 2) + " U" : "—"; const ddPct = dd.max_drawdown_pct != null ? fmt(dd.max_drawdown_pct, 2) + "%" : "—"; const status = monitored ? (ac.data_ok ? "已监控" : "余额未齐") : "未监控"; return ( '
' + '
' + '

' + (ac.name || ac.key || "—") + "

" + '' + status + "" + "
" + '
' + '
总资金' + total + "
" + '
资金户' + funding + "
" + '
交易户' + trading + "
" + '
最大回撤' + ddU + " / " + ddPct + "
" + "
" + '' + "
" ); }) .join(""); elAccounts.querySelectorAll(".funds-ac-card").forEach(function (card, idx) { const ac = accounts[idx]; const host = card.querySelector(".funds-ac-chart"); if (ac && ac.monitored && host) { renderMiniChart(host, ac.series || []); } else if (host) { host.textContent = monitoredLabel(ac); } }); } function monitoredLabel(ac) { return ac && ac.monitored ? "暂无曲线" : "未参与合计"; } function renderOverview(data) { const totals = data.totals || {}; const dd = totals.drawdown || {}; if (elTotal) { elTotal.textContent = totals.total_usdt != null ? fmt(totals.total_usdt, 2) + " U" : "—"; } if (elDdU) elDdU.textContent = dd.max_drawdown_u != null ? fmt(dd.max_drawdown_u, 2) + " U" : "—"; if (elDdPct) { elDdPct.textContent = dd.max_drawdown_pct != null ? fmt(dd.max_drawdown_pct, 2) + "%" : "—"; } if (elDelta) { elDelta.textContent = fmtDelta(totals.day_delta_usdt); elDelta.className = "funds-stat-val " + deltaClass(totals.day_delta_usdt); } if (elMeta) { const parts = [ "交易日 " + (data.trading_day || "—"), "切日 " + (data.reset_hour != null ? data.reset_hour : 8) + ":00 北京", "自 " + (data.history_start_day || "2026-06-09") + " 起", "最多 " + (data.keep_days || 180) + " 交易日", ]; if (data.updated_at) parts.push("刷新 " + data.updated_at); if (totals.live_known_count != null) { parts.push("合计含 " + totals.live_known_count + " 户"); } elMeta.textContent = parts.join(" · "); } ensureChart(); if (lineSeries) { const pts = seriesToChartData(totals.series || []); if (pts.length) { lineSeries.setData(pts); chart.timeScale().fitContent(); } else { lineSeries.setData([]); } } renderAccounts(data.accounts || []); } async function load() { if (loading) return; loading = true; setStatus("加载中…"); try { const r = await fetch("/api/hub/fund-overview", { credentials: "same-origin" }); const j = await r.json(); if (!r.ok) { setStatus(j.detail || j.msg || "加载失败", true); return; } renderOverview(j); setStatus(""); } catch (e) { setStatus(String(e.message || e), true); } finally { loading = false; } } function bind() { if (elBtnRefresh) elBtnRefresh.addEventListener("click", load); document.addEventListener("hub-theme-change", function () { destroyChart(); load(); }); } function init() { if (!page || page.classList.contains("hidden")) return; if (!inited) { bind(); inited = true; } load(); } function destroy() { destroyChart(); } window.hubFundsPage = { init: init, destroy: destroy, reload: load }; })();