diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index 4f86142..c32bcdd 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -3972,6 +3972,9 @@ body.hub-page-ai #page-ai { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); } +.archive-chart-wrap { + position: relative; +} .archive-chart-host { height: 360px; min-height: 280px; @@ -3980,6 +3983,30 @@ body.hub-page-ai #page-ai { background: var(--panel); overflow: hidden; } +.archive-mark-auto { + position: absolute; + right: 8px; + bottom: 10px; + z-index: 5; + padding: 4px 10px; + font-size: 0.72rem; + font-family: var(--font); + border-radius: 6px; + border: 1px solid var(--border-soft); + background: var(--chart-bar-bg, var(--inset-surface)); + color: var(--muted); + cursor: pointer; + line-height: 1.2; +} +.archive-mark-auto:hover { + border-color: var(--accent); + color: var(--text); +} +.archive-mark-auto.is-on { + color: #22c55e; + border-color: rgba(34, 197, 94, 0.45); + background: rgba(34, 197, 94, 0.1); +} .archive-trades { overflow: auto; max-height: 280px; diff --git a/manual_trading_hub/static/archive.js b/manual_trading_hub/static/archive.js index 00d76e6..6e2c7f7 100644 --- a/manual_trading_hub/static/archive.js +++ b/manual_trading_hub/static/archive.js @@ -23,7 +23,9 @@ const elBtnJump = document.getElementById("archive-btn-jump"); const elBtnReloadChart = document.getElementById("archive-btn-reload-chart"); const elChartHost = document.getElementById("archive-chart"); + const elMarkAuto = document.getElementById("archive-mark-auto"); const elTrades = document.getElementById("archive-trades"); + const ARCHIVE_MARK_AUTO_KEY = "hubArchiveMarkAuto"; const TF_MS = { "5m": 5 * 60_000, @@ -42,6 +44,41 @@ let candleSeries = null; let volumeSeries = null; let inited = false; + let markAuto = true; + let lastCandles = []; + + function loadMarkAutoPref() { + try { + const raw = localStorage.getItem(ARCHIVE_MARK_AUTO_KEY); + if (raw === "0" || raw === "false") markAuto = false; + else if (raw === "1" || raw === "true") markAuto = true; + } catch (_) {} + syncMarkAutoBtn(); + } + + function syncMarkAutoBtn() { + if (!elMarkAuto) return; + elMarkAuto.classList.toggle("is-on", markAuto); + elMarkAuto.setAttribute("aria-pressed", markAuto ? "true" : "false"); + } + + function saveMarkAutoPref() { + try { + localStorage.setItem(ARCHIVE_MARK_AUTO_KEY, markAuto ? "1" : "0"); + } catch (_) {} + } + + function tradeHistoryBounds(tradeList) { + let minOpen = null; + let maxClose = null; + (tradeList || []).forEach(function (tr) { + const o = tradeOpenMs(tr); + const c = tradeCloseMs(tr); + if (o != null) minOpen = minOpen == null ? o : Math.min(minOpen, o); + if (c != null) maxClose = maxClose == null ? c : Math.max(maxClose, c); + }); + return { minOpen: minOpen, maxClose: maxClose }; + } function fmt(n, d) { if (n == null || n === "" || !Number.isFinite(Number(n))) return "—"; @@ -223,35 +260,71 @@ return d === "long" || d === "多" || d === "buy"; } - function buildTradeMarkers(tr, candles, tf) { + function buildTradeMarkers(tr, candles, tf, opts) { if (!tr || !candles.length) return []; + const options = opts || {}; + const suffix = options.labelSuffix ? String(options.labelSuffix) : ""; + const highlight = !!options.highlight; const long = isLongDirection(tr.direction); const openMs = tradeOpenMs(tr); const closeMs = tradeCloseMs(tr); + const openColor = highlight ? (long ? "#4ade80" : "#f87171") : long ? "#22c55e" : "#ef4444"; + let closeColor = highlight ? "#fbbf24" : "#f59e0b"; + const pnl = Number(tr.pnl_amount); + if (!highlight && Number.isFinite(pnl) && pnl < -0.0001) { + closeColor = "#a855f7"; + } const markers = []; if (openMs) { markers.push({ time: snapToCandleTime(msToBarTime(openMs, tf), candles), position: long ? "belowBar" : "aboveBar", - color: long ? "#22c55e" : "#ef4444", + color: openColor, shape: long ? "arrowUp" : "arrowDown", - text: "开", + text: "开" + suffix, }); } if (closeMs) { markers.push({ time: snapToCandleTime(msToBarTime(closeMs, tf), candles), position: long ? "aboveBar" : "belowBar", - color: "#f59e0b", + color: closeColor, shape: long ? "arrowDown" : "arrowUp", - text: "平", + text: "平" + suffix, }); } - return markers.sort(function (a, b) { + return markers; + } + + function buildChartMarkers(candles, tf) { + if (!candles.length) return []; + const tr = pickAnchorTrade(); + if (!markAuto || !trades.length) { + return buildTradeMarkers(tr, candles, tf, { highlight: true }); + } + const sorted = trades.slice().sort(function (a, b) { + return (tradeOpenMs(a) || 0) - (tradeOpenMs(b) || 0); + }); + const multi = sorted.length > 1; + const out = []; + sorted.forEach(function (row, idx) { + const tid = String(row.trade_id || row.id); + const parts = buildTradeMarkers(row, candles, tf, { + labelSuffix: multi ? String(idx + 1) : "", + highlight: tid === String(selectedTradeId), + }); + out.push.apply(out, parts); + }); + return out.sort(function (a, b) { return a.time > b.time ? 1 : a.time < b.time ? -1 : 0; }); } + function applyChartMarkers() { + if (!candleSeries || !candleSeries.setMarkers || !lastCandles.length) return; + candleSeries.setMarkers(buildChartMarkers(lastCandles, timeframe)); + } + /** 初始只聚焦持仓段;完整历史已加载,可向左拖动/滚轮缩小查看建仓前全局。 */ function focusInitialTradeView(candles, tr, tf) { if (!chart || !candles.length || !tr) return; @@ -362,8 +435,16 @@ const tr = pickAnchorTrade(); const anchor = anchorMsForTrade(tr); const jump = (elJumpAt && elJumpAt.value || "").trim(); - const openMs = tradeOpenMs(tr); - const closeMs = tradeCloseMs(tr); + let openMs = null; + let closeMs = null; + if (markAuto && trades.length) { + const bounds = tradeHistoryBounds(trades); + openMs = bounds.minOpen; + closeMs = bounds.maxClose; + } else if (tr) { + openMs = tradeOpenMs(tr); + closeMs = tradeCloseMs(tr); + } const params = new URLSearchParams({ exchange_key: selected.exchange_key, symbol: selected.symbol, @@ -388,6 +469,7 @@ } ensureChart(); const candles = j.candles || []; + lastCandles = candles; candleSeries.setData( candles.map(function (c) { return { time: c.time, open: c.open, high: c.high, low: c.low, close: c.close }; @@ -402,16 +484,15 @@ }; }) ); - if (candleSeries.setMarkers) { - candleSeries.setMarkers(buildTradeMarkers(tr, candles, timeframe)); - } + applyChartMarkers(); if (tr && tradeOpenMs(tr) && tradeCloseMs(tr)) { focusInitialTradeView(candles, tr, timeframe); } else if (candles.length > 10) { chart.timeScale().setVisibleLogicalRange({ from: candles.length - 120, to: candles.length + 5 }); } + const markHint = markAuto && trades.length > 1 ? " · 自动标注 " + trades.length + " 笔" : tr ? " · 已标注开/平" : ""; const histHint = openMs && closeMs ? " · 可拖动/滚轮缩放查看建仓前走势" : ""; - setStatus("K 线 " + candles.length + " 根 · " + timeframe + (tr ? " · 已标注开/平" : "") + histHint); + setStatus("K 线 " + candles.length + " 根 · " + timeframe + markHint + histHint); } function renderTrades() { @@ -478,7 +559,11 @@ if (ev.target.closest("select") || ev.target.closest("input")) return; selectedTradeId = row.getAttribute("data-id"); renderTrades(); - loadChart(); + applyChartMarkers(); + const trSel = pickAnchorTrade(); + if (trSel && tradeOpenMs(trSel) && tradeCloseMs(trSel)) { + focusInitialTradeView(lastCandles, trSel, timeframe); + } }); }); elTrades.querySelectorAll(".archive-tag-select").forEach(function (sel) { @@ -629,6 +714,14 @@ } if (elViewMode) elViewMode.addEventListener("change", loadChart); if (elBtnReloadChart) elBtnReloadChart.addEventListener("click", loadChart); + if (elMarkAuto) { + elMarkAuto.addEventListener("click", function () { + markAuto = !markAuto; + syncMarkAutoBtn(); + saveMarkAutoPref(); + loadChart(); + }); + } if (elBtnJump) { elBtnJump.addEventListener("click", function () { loadChart(); @@ -641,6 +734,7 @@ return; } if (!inited) { + loadMarkAutoPref(); bindEvents(); inited = true; } diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 532288e..0d58bb8 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -15,7 +15,7 @@ - + @@ -234,7 +234,10 @@ -
+
+
+ +
@@ -349,7 +352,7 @@
- +