From d942e5b08897a75e1af745279fabe7904e5ed9a5 Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 2 Jun 2026 11:19:20 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=8C=E6=83=85=E5=8C=BA=EF=BC=9A60s=20?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E3=80=81=E6=A8=AA=E5=90=91=20OHLCV=E3=80=81?= =?UTF-8?q?=E4=BA=A4=E6=98=93=E6=89=80=E6=A0=87=E8=AF=86=E3=80=81=E4=BB=B7?= =?UTF-8?q?=E6=A0=BC=E8=87=AA=E5=8A=A8=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- manual_trading_hub/static/app.css | 97 ++++++++++++++++++++++------ manual_trading_hub/static/app.js | 2 + manual_trading_hub/static/chart.js | 95 +++++++++++++++++++++------ manual_trading_hub/static/index.html | 23 ++++--- 4 files changed, 166 insertions(+), 51 deletions(-) diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index 6de8715..4db75c5 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -1976,47 +1976,104 @@ body.login-page { height: 100%; } +.market-exchange-badge { + position: absolute; + top: 50%; + right: 52px; + z-index: 3; + transform: translateY(-50%) rotate(-90deg); + transform-origin: center center; + font-family: var(--font-display, var(--font)); + font-size: 0.95rem; + font-weight: 600; + letter-spacing: 0.12em; + color: rgba(184, 212, 232, 0.22); + pointer-events: none; + white-space: nowrap; + user-select: none; +} + +.market-exchange-badge:empty { + display: none; +} + .market-ohlcv-overlay { position: absolute; top: 10px; left: 10px; z-index: 4; pointer-events: none; - padding: 10px 12px; + padding: 8px 12px; border-radius: 8px; background: rgba(8, 14, 24, 0.88); border: 1px solid var(--border-soft); font-size: 0.78rem; - min-width: 200px; - opacity: 0; - visibility: hidden; - transition: opacity 0.12s ease; -} - -.market-ohlcv-overlay.is-active { - opacity: 1; - visibility: visible; + max-width: calc(100% - 80px); } .market-ohlcv-title { font-weight: 600; color: var(--accent); - margin-bottom: 6px; + margin-bottom: 4px; display: flex; - gap: 8px; + flex-wrap: wrap; + align-items: center; + gap: 6px 10px; } -.market-ohlcv-grid { - display: grid; - grid-template-columns: 1fr 1fr; +.mkt-exchange-tag { + padding: 1px 8px; + border-radius: 4px; + background: rgba(0, 255, 157, 0.12); + border: 1px solid rgba(0, 255, 157, 0.35); + color: #00ff9d; + font-size: 0.72rem; + font-weight: 600; +} + +.mkt-exchange-tag:empty { + display: none; +} + +.market-ohlcv-row { + display: flex; + flex-wrap: wrap; + align-items: center; gap: 4px 14px; } -.market-ohlcv-grid .k { - color: var(--muted); - margin-right: 6px; +.market-ohlcv-row .ohlcv-item { + white-space: nowrap; } -.market-ohlcv-grid .market-vol { - grid-column: 1 / -1; +.market-ohlcv-row .k { + color: var(--muted); + margin-right: 4px; +} + +.market-price-auto { + position: absolute; + right: 6px; + top: 42%; + z-index: 5; + padding: 4px 8px; + font-size: 0.68rem; + font-family: var(--font); + border-radius: 6px; + border: 1px solid var(--border-soft); + background: rgba(8, 14, 24, 0.92); + color: var(--muted); + cursor: pointer; + line-height: 1.2; +} + +.market-price-auto:hover { + border-color: var(--accent); + color: var(--text); +} + +.market-price-auto.is-on { + color: #00ff9d; + border-color: rgba(0, 255, 157, 0.45); + background: rgba(0, 255, 157, 0.1); } diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 047e5f2..c52d112 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -262,6 +262,8 @@ if (page === "settings") loadSettingsUI(); if (page === "market" && window.hubMarketChart) { window.hubMarketChart.init(); + } else if (window.hubMarketChart && window.hubMarketChart.stopAutoRefresh) { + window.hubMarketChart.stopAutoRefresh(); } } diff --git a/manual_trading_hub/static/chart.js b/manual_trading_hub/static/chart.js index dcbe92d..b079506 100644 --- a/manual_trading_hub/static/chart.js +++ b/manual_trading_hub/static/chart.js @@ -1,7 +1,8 @@ /** - * 中控行情区:K 线 + 底部成交量;十字线时显示 OHLCV;可视区间高低点。 + * 中控行情区:K 线 + 成交量;默认最新 OHLCV;60s 自动刷新;价格轴「自动」。 */ (function () { + const AUTO_REFRESH_MS = 60000; const chartHost = document.getElementById("market-chart"); if (!chartHost) return; @@ -11,25 +12,29 @@ const elRefresh = document.getElementById("market-refresh"); const elStatus = document.getElementById("market-status"); const elUpdated = document.getElementById("market-updated"); - const elOverlay = document.querySelector(".market-ohlcv-overlay"); const elO = document.getElementById("mkt-o"); const elH = document.getElementById("mkt-h"); const elL = document.getElementById("mkt-l"); const elC = document.getElementById("mkt-c"); const elV = document.getElementById("mkt-v"); + const elExLabel = document.getElementById("mkt-exchange-label"); + const elExBadge = document.getElementById("market-exchange-badge"); const elSymLabel = document.getElementById("mkt-symbol-label"); const elTfLabel = document.getElementById("mkt-tf-label"); + const elPriceAuto = document.getElementById("market-price-auto"); let chart = null; let candleSeries = null; let volumeSeries = null; let priceTick = null; + let priceAutoScale = true; let rangeMarkers = []; let lastCandles = []; let candleByTime = {}; let chartMeta = null; let loadToken = 0; let marketInited = false; + let refreshTimer = null; function fmtVol(v) { if (v == null || Number.isNaN(Number(v))) return "-"; @@ -62,9 +67,26 @@ return text; } - function setOverlayVisible(on) { - if (!elOverlay) return; - elOverlay.classList.toggle("is-active", !!on); + function exchangeLabel() { + if (!elExchange) return ""; + const opt = elExchange.options[elExchange.selectedIndex]; + if (opt && opt.textContent) return opt.textContent.trim(); + return (elExchange.value || "").trim().toUpperCase(); + } + + function updateExchangeDisplay() { + const label = exchangeLabel(); + if (elExLabel) elExLabel.textContent = label; + if (elExBadge) { + elExBadge.textContent = label; + elExBadge.setAttribute("aria-hidden", label ? "false" : "true"); + } + } + + function updateHeaderLabels(sym, tf) { + if (elSymLabel) elSymLabel.textContent = sym || "—"; + if (elTfLabel) elTfLabel.textContent = tf || "—"; + updateExchangeDisplay(); } function paintOhlcv(bar) { @@ -82,9 +104,18 @@ if (elV) elV.textContent = fmtVol(bar.volume); } - function hideOhlcvOverlay() { - setOverlayVisible(false); - paintOhlcv(null); + function latestCandle() { + return lastCandles.length ? lastCandles[lastCandles.length - 1] : null; + } + + function showLatestOhlcv() { + paintOhlcv(latestCandle()); + } + + function applyPriceAutoScale() { + if (!chart) return; + chart.priceScale("right").applyOptions({ autoScale: priceAutoScale }); + if (elPriceAuto) elPriceAuto.classList.toggle("is-on", priceAutoScale); } function indexCandles(candles) { @@ -125,7 +156,7 @@ vertLines: { visible: false }, horzLines: { visible: false }, }, - rightPriceScale: { borderColor: "#2a4058" }, + rightPriceScale: { borderColor: "#2a4058", autoScale: true }, timeScale: { borderColor: "#2a4058", timeVisible: true, secondsVisible: false }, crosshair: { mode: LightweightCharts.CrosshairMode @@ -175,18 +206,18 @@ chart.priceScale("volume").applyOptions({ scaleMargins: { top: 0.78, bottom: 0 }, }); + applyPriceAutoScale(); chart.subscribeCrosshairMove(function (param) { if (!param || param.time == null) { - hideOhlcvOverlay(); + showLatestOhlcv(); return; } const bar = candleAtTime(param.time); if (!bar) { - hideOhlcvOverlay(); + showLatestOhlcv(); return; } - setOverlayVisible(true); paintOhlcv(bar); }); @@ -199,7 +230,6 @@ chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight }); }); chart.applyOptions({ width: chartHost.clientWidth, height: chartHost.clientHeight }); - hideOhlcvOverlay(); return true; } @@ -240,7 +270,7 @@ lineWidth: 1, lineStyle: 2, axisLabelVisible: true, - title: "可视高", + title: "高点", }) ); rangeMarkers.push( @@ -250,7 +280,7 @@ lineWidth: 1, lineStyle: 2, axisLabelVisible: true, - title: "可视低", + title: "低点", }) ); } @@ -270,6 +300,20 @@ if (elTf && !elTf.value) elTf.value = "1d"; } + function startAutoRefresh() { + stopAutoRefresh(); + refreshTimer = setInterval(function () { + const page = document.getElementById("page-market"); + if (!page || page.classList.contains("hidden")) return; + loadChart(false); + }, AUTO_REFRESH_MS); + } + + function stopAutoRefresh() { + if (refreshTimer) clearInterval(refreshTimer); + refreshTimer = null; + } + async function loadMeta() { const r = await fetch("/api/chart/meta", { credentials: "same-origin" }); chartMeta = await r.json(); @@ -283,6 +327,7 @@ }); readQuery(); applyDefaults(); + updateExchangeDisplay(); } async function loadChart(force) { @@ -302,9 +347,7 @@ elStatus.className = "market-status"; elStatus.textContent = "加载中…"; } - hideOhlcvOverlay(); - if (elSymLabel) elSymLabel.textContent = sym; - if (elTfLabel) elTfLabel.textContent = tf; + updateHeaderLabels(sym, tf); const qs = new URLSearchParams({ exchange_key: exKey, @@ -330,7 +373,9 @@ candleSeries.setData(lastCandles); volumeSeries.setData(buildVolumeData(lastCandles)); chart.timeScale().fitContent(); + applyPriceAutoScale(); updateVisibleRangeMarkers(); + showLatestOhlcv(); const limit = data.limit || lastCandles.length; let hint = @@ -342,9 +387,7 @@ (data.from_cache || 0) + " / 新拉 " + (data.fetched || 0) + - ")· 保留 " + - (data.retention_days || 15) + - " 天"; + ")· 每 60s 刷新"; if (data.stale && data.stale_message) { hint += " · 缓存:" + data.stale_message; } @@ -375,6 +418,7 @@ } if (elExchange) { elExchange.addEventListener("change", function () { + updateExchangeDisplay(); loadChart(false); }); } @@ -389,6 +433,12 @@ loadChart(false); }); } + if (elPriceAuto) { + elPriceAuto.addEventListener("click", function () { + priceAutoScale = !priceAutoScale; + applyPriceAutoScale(); + }); + } } window.hubMarketChart = { @@ -398,11 +448,14 @@ await loadMeta(); bind(); } + startAutoRefresh(); await loadChart(false); }, reload: function (force) { loadChart(!!force); }, + startAutoRefresh: startAutoRefresh, + stopAutoRefresh: stopAutoRefresh, }; if ( diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 23bb882..9437213 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -8,7 +8,7 @@ - + @@ -66,7 +66,7 @@ 数据说明
优先读中控 data/hub_kline.db,不足时向所选交易所实例请求并写入库。
- 日内周期最多 1000 根,日线/周线最多 500 根。仅在本页操作或点「刷新」时拉取交易所。 + 日内周期最多 1000 根,日线/周线最多 500 根。打开本页时每 60 秒自动刷新一次 K 线。
@@ -97,19 +97,22 @@

+
+ 1d
-
-
-
-
-
-
+
+ + + + +
+
@@ -179,7 +182,7 @@
- - + +