Review charts: M1/M5/M15/H1 bar counts 500/200/100/50; simplify markers.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-21 17:33:18 +08:00
parent 11c14fbb21
commit e2847c90df
6 changed files with 87 additions and 125 deletions
+61 -106
View File
@@ -1,6 +1,6 @@
/** 复盘四周期蜡烛图:围绕成交裁剪窗口,入场/出场轻量标注 */
/** 复盘四周期:固定根数裁剪;进出场只用圆点,价签放在图外图例 */
(function () {
var TF_MAX = { M1: 90, M5: 48, M15: 36, H1: 24 };
var TF_MAX = { M1: 500, M5: 200, M15: 100, H1: 50 };
function num(v) {
if (v == null || v === "") return null;
@@ -22,10 +22,9 @@
return best;
}
/** 只保留成交前后一段,避免 M1 挤满、H1 太空 */
function cropBars(bars, markers, tf) {
if (!bars.length) return bars;
var maxN = TF_MAX[tf] || 60;
var maxN = TF_MAX[tf] || 100;
if (bars.length <= maxN) return bars;
var a = nearestBarIndex(bars, markers.openTs);
@@ -35,7 +34,7 @@
var lo = Math.min(a, b);
var hi = Math.max(a, b);
var span = hi - lo + 1;
var pad = Math.max(8, Math.floor((maxN - span) / 2));
var pad = Math.max(4, Math.floor((maxN - span) / 2));
var start = Math.max(0, lo - pad);
var end = Math.min(bars.length, start + maxN);
start = Math.max(0, end - maxN);
@@ -49,16 +48,16 @@
var dpr = window.devicePixelRatio || 1;
var cssW = canvas.clientWidth || 400;
var cssH = canvas.clientHeight || 200;
var cssH = canvas.clientHeight || 220;
canvas.width = cssW * dpr;
canvas.height = cssH * dpr;
var ctx = canvas.getContext("2d");
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
var padL = 6;
var padR = 56; // 右侧留给价签
var padT = 10;
var padB = 6;
var padL = 4;
var padR = 4;
var padT = 4;
var padB = 4;
var min = Infinity;
var max = -Infinity;
@@ -75,7 +74,7 @@
max = Math.max(max, markers.closePrice);
}
if (!(max > min)) max = min + 1;
var padPrice = (max - min) * 0.08 || 0.8;
var padPrice = (max - min) * 0.06 || 0.5;
min -= padPrice;
max += padPrice;
@@ -95,8 +94,7 @@
return padL + i * slot + slot * 0.5;
};
// K 线
var bodyW = Math.max(1.5, Math.min(8, slot * 0.55));
var bodyW = Math.max(1, Math.min(6, slot * 0.6));
bars.forEach(function (b, i) {
var x = xOf(i);
var up = b.close >= b.open;
@@ -114,6 +112,7 @@
ctx.fillRect(x - bodyW / 2, top, bodyW, bh);
});
// 仅圆点,不画横线/竖线/价签(价签在图例外)
var entryColor = "#4cc2ff";
var exitColor = "#f0b429";
var entryIdx = nearestBarIndex(bars, markers.openTs);
@@ -124,109 +123,65 @@
markers.closePrice != null &&
exitIdx >= 0;
var openP = markers.openPrice;
var closeP = markers.closePrice;
var samePrice =
openP != null &&
closeP != null &&
Math.abs(openP - closeP) < 0.08;
// 价格横线:价差很小时只画一条,避免两行字叠在一起
function drawHLine(price, color) {
var y = yOf(price);
ctx.save();
ctx.strokeStyle = color;
ctx.globalAlpha = 0.7;
ctx.lineWidth = 1;
ctx.setLineDash([4, 3]);
ctx.beginPath();
ctx.moveTo(padL, y);
ctx.lineTo(padL + w, y);
ctx.stroke();
ctx.restore();
return y;
}
function drawRightTag(y, text, color, dy) {
dy = dy || 0;
var ty = Math.max(padT + 10, Math.min(cssH - padB - 4, y + dy));
ctx.save();
ctx.font = "11px Microsoft YaHei UI, sans-serif";
ctx.fillStyle = color;
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillText(text, padL + w + 4, ty);
ctx.restore();
}
if (samePrice && openP != null) {
var ySame = drawHLine(openP, entryColor);
drawRightTag(ySame, "入/出 " + openP.toFixed(2), entryColor, 0);
} else {
if (openP != null) {
var yIn = drawHLine(openP, entryColor);
drawRightTag(yIn, "入 " + openP.toFixed(2), entryColor, 0);
}
if (hasExit && closeP != null) {
var yOut = drawHLine(closeP, exitColor);
var dy = 0;
if (openP != null && Math.abs(yOf(openP) - yOut) < 14) {
dy = yOut < yOf(openP) ? -12 : 12;
}
drawRightTag(yOut, "出 " + closeP.toFixed(2), exitColor, dy);
}
}
// 点:无竖线、无三角、无紫连线 —— 只留圆点 + 短字
function drawDot(i, price, color, label, labelSide) {
if (i < 0 || price == null) return;
function drawDot(i, price, color) {
if (i < 0 || price == null) return { x: 0, y: 0 };
var x = xOf(i);
var y = yOf(price);
ctx.save();
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.arc(x, y, 4.5, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.lineWidth = 1.5;
ctx.strokeStyle = "#0e0e18";
ctx.strokeStyle = "#ffffff";
ctx.stroke();
ctx.font = "bold 10px Microsoft YaHei UI, sans-serif";
ctx.fillStyle = color;
ctx.textBaseline = "bottom";
if (labelSide === "left") {
ctx.textAlign = "right";
ctx.fillText(label, x - 7, y - 4);
} else if (labelSide === "right") {
ctx.textAlign = "left";
ctx.fillText(label, x + 7, y - 4);
} else {
ctx.textAlign = "center";
ctx.fillText(label, x, y - 7);
}
ctx.restore();
return { x: x, y: y };
}
var closeTogether =
hasExit && entryIdx >= 0 && Math.abs(exitIdx - entryIdx) <= 2;
if (entryIdx >= 0 && openP != null) {
drawDot(
entryIdx,
openP,
entryColor,
"入",
closeTogether ? "left" : "center"
);
var pe = null;
var px = null;
if (entryIdx >= 0 && markers.openPrice != null) {
pe = drawDot(entryIdx, markers.openPrice, entryColor);
}
if (hasExit) {
drawDot(
exitIdx,
closeP,
exitColor,
"出",
closeTogether ? "right" : "center"
);
// 同一根或相邻:出场点略向右错开,避免叠成一团
if (Math.abs(exitIdx - entryIdx) <= 1) {
var x = xOf(exitIdx) + Math.max(6, slot * 0.8);
var y = yOf(markers.closePrice);
if (pe && Math.abs(y - pe.y) < 6) {
y = pe.y - 8;
}
ctx.beginPath();
ctx.arc(x, y, 4.5, 0, Math.PI * 2);
ctx.fillStyle = exitColor;
ctx.fill();
ctx.lineWidth = 1.5;
ctx.strokeStyle = "#ffffff";
ctx.stroke();
px = { x: x, y: y };
} else {
px = drawDot(exitIdx, markers.closePrice, exitColor);
}
}
// 极短引线:点旁只写「入」「出」一字,不写价格
ctx.font = "bold 10px Microsoft YaHei UI, sans-serif";
if (pe) {
ctx.fillStyle = entryColor;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillText("入", pe.x, pe.y - 6);
}
if (px) {
ctx.fillStyle = exitColor;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillText("出", px.x, px.y - 6);
}
// 更新图外图例里的实际显示根数
var meta = canvas.parentElement && canvas.parentElement.querySelector(".chart-meta");
if (meta) {
meta.textContent = bars.length + " 根";
}
}
@@ -244,7 +199,7 @@
document.querySelectorAll("canvas.chart-canvas").forEach(function (el) {
try {
var bars = JSON.parse(el.dataset.bars || "[]");
var tf = (el.dataset.tf || el.id || "").replace(/^chart-/, "") || "M1";
var tf = el.dataset.tf || "M1";
drawCandleChart(el, bars, readMarkers(el), tf);
} catch (e) {
console.warn(e);