From 429010da6bf183dd01cb942f6e1fc66b30701ffa Mon Sep 17 00:00:00 2001 From: dekun Date: Wed, 21 Oct 2026 20:24:28 +0800 Subject: [PATCH] Fix review chart markers to use containing bar time, not nearest open. Co-authored-by: Cursor --- app/static/js/charts.js | 69 +++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/app/static/js/charts.js b/app/static/js/charts.js index 1ea4473..d92fb4d 100644 --- a/app/static/js/charts.js +++ b/app/static/js/charts.js @@ -1,6 +1,7 @@ -/** 复盘四周期:固定根数裁剪;进出场只用圆点,价签放在图外图例 */ +/** 复盘四周期:按「包含成交时刻」的 K 线落点,避免偏到下一根 */ (function () { var TF_MAX = { M1: 500, M5: 200, M15: 100, H1: 50 }; + var TF_SEC = { M1: 60, M5: 300, M15: 900, H1: 3600 }; function num(v) { if (v == null || v === "") return null; @@ -8,18 +9,23 @@ return Number.isFinite(n) ? n : null; } - function nearestBarIndex(bars, ts) { + /** 找到覆盖 ts 的那根 K 线:barOpen <= ts < barOpen+period(最后一根含右边界) */ + function barIndexAt(bars, ts, tf) { if (ts == null || !bars.length) return -1; - var best = 0; - var bestDiff = Math.abs(Number(bars[0].ts) - ts); - for (var i = 1; i < bars.length; i++) { - var d = Math.abs(Number(bars[i].ts) - ts); - if (d < bestDiff) { - bestDiff = d; - best = i; + var period = TF_SEC[tf] || 60; + var t = Number(ts); + var i; + for (i = 0; i < bars.length; i++) { + var open = Number(bars[i].ts); + var end = open + period; + var last = i === bars.length - 1; + if (t >= open && (t < end || (last && t <= end))) { + return i; } } - return best; + // 落在首根之前 / 末根之后:夹到边界 + if (t < Number(bars[0].ts)) return 0; + return bars.length - 1; } function cropBars(bars, markers, tf) { @@ -27,8 +33,9 @@ var maxN = TF_MAX[tf] || 100; if (bars.length <= maxN) return bars; - var a = nearestBarIndex(bars, markers.openTs); - var b = markers.closeTs != null ? nearestBarIndex(bars, markers.closeTs) : a; + var a = barIndexAt(bars, markers.openTs, tf); + var b = + markers.closeTs != null ? barIndexAt(bars, markers.closeTs, tf) : a; if (a < 0) a = Math.floor(bars.length / 2); if (b < 0) b = a; var lo = Math.min(a, b); @@ -41,6 +48,17 @@ return bars.slice(start, end); } + function fmtBjt(ts) { + if (ts == null) return ""; + var d = new Date(Number(ts) * 1000); + // 用 UTC+8 显示,与复盘页北京时间一致 + var utc = d.getTime() + d.getTimezoneOffset() * 60000; + var bjt = new Date(utc + 8 * 3600000); + var hh = String(bjt.getHours()).padStart(2, "0"); + var mm = String(bjt.getMinutes()).padStart(2, "0"); + return hh + ":" + mm; + } + function drawCandleChart(canvas, bars, markers, tf) { if (!canvas || !bars || !bars.length) return; bars = cropBars(bars, markers || {}, tf); @@ -57,7 +75,7 @@ var padL = 4; var padR = 4; var padT = 4; - var padB = 4; + var padB = 16; // 底部时间轴 var min = Infinity; var max = -Infinity; @@ -112,12 +130,20 @@ ctx.fillRect(x - bodyW / 2, top, bodyW, bh); }); - // 仅圆点,不画横线/竖线/价签(价签在图例外) + // 底部左右时间(北京时间),方便对照成交时刻 + ctx.fillStyle = "#888899"; + ctx.font = "10px Microsoft YaHei UI, sans-serif"; + ctx.textBaseline = "top"; + ctx.textAlign = "left"; + ctx.fillText(fmtBjt(bars[0].ts), padL, cssH - padB + 2); + ctx.textAlign = "right"; + ctx.fillText(fmtBjt(bars[n - 1].ts), cssW - padR, cssH - padB + 2); + var entryColor = "#4cc2ff"; var exitColor = "#f0b429"; - var entryIdx = nearestBarIndex(bars, markers.openTs); + var entryIdx = barIndexAt(bars, markers.openTs, tf); var exitIdx = - markers.closeTs != null ? nearestBarIndex(bars, markers.closeTs) : -1; + markers.closeTs != null ? barIndexAt(bars, markers.closeTs, tf) : -1; var hasExit = markers.closeTs != null && markers.closePrice != null && @@ -143,7 +169,6 @@ pe = drawDot(entryIdx, markers.openPrice, entryColor); } if (hasExit) { - // 同一根或相邻:出场点略向右错开,避免叠成一团 if (Math.abs(exitIdx - entryIdx) <= 1) { var x = xOf(exitIdx) + Math.max(6, slot * 0.8); var y = yOf(markers.closePrice); @@ -163,7 +188,6 @@ } } - // 极短引线:点旁只写「入」「出」一字,不写价格 ctx.font = "bold 10px Microsoft YaHei UI, sans-serif"; if (pe) { ctx.fillStyle = entryColor; @@ -178,10 +202,15 @@ ctx.fillText("出", px.x, px.y - 6); } - // 更新图外图例里的实际显示根数 var meta = canvas.parentElement && canvas.parentElement.querySelector(".chart-meta"); if (meta) { - meta.textContent = bars.length + " 根"; + meta.textContent = + bars.length + + " 根 · " + + fmtBjt(bars[0].ts) + + "–" + + fmtBjt(bars[n - 1].ts) + + " 北京时间"; } }