dashboard: mark neutral votes and no-pattern rows on the indicator chart
A NEUTRAL vote (value 0) and an absent divergence/candlestick entry both drew no bar, so RSI, Div and Cndl looked missing on the live page. The engine computes them on every scan; divergence and candlestick only exist when a pattern is found (by design, "no opinion"). The chart now draws a grey dot on the zero line for a neutral vote and a faint "no pattern" tag for an absent pattern indicator; rows hover as a whole. Display-only, signal code and books untouched. Suite 554/554. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+45
-5
@@ -28,6 +28,10 @@ const IND_NAME = {
|
||||
adx: "ADX", roc: "Rate of change", keltner: "Keltner channel",
|
||||
divergence: "Divergence", candlestick: "Candlestick pattern"
|
||||
};
|
||||
// Divergence and candlestick only exist on a scan that found a pattern
|
||||
// (engine.py); for them an absent entry means "no pattern", not a failure.
|
||||
const PATTERN_IND = new Set(["divergence", "candlestick"]);
|
||||
const absentWord = name => PATTERN_IND.has(name) ? "no pattern" : "no reading";
|
||||
const VOTE = { STRONG_BUY: 2, BUY: 1, NEUTRAL: 0, SELL: -1, STRONG_SELL: -2 };
|
||||
const SIG_ICON = {
|
||||
STRONG_BUY: "fa-angles-up", BUY: "fa-arrow-trend-up", NEUTRAL: "fa-arrows-left-right",
|
||||
@@ -275,11 +279,41 @@ function indChart(tf) {
|
||||
barThickness: 7
|
||||
}]
|
||||
},
|
||||
// A neutral vote and a missing reading both have no bar; this marks
|
||||
// them on the zero line (grey dot = neutral, faint tag = nothing found).
|
||||
plugins: [{
|
||||
id: "indMarkers",
|
||||
afterDatasetsDraw(chart) {
|
||||
const cells = lastCells[tf];
|
||||
if (!cells) return;
|
||||
const { ctx, scales } = chart;
|
||||
const x0 = scales.x.getPixelForValue(0);
|
||||
ctx.save();
|
||||
cells.forEach((cell, i) => {
|
||||
const y = scales.y.getPixelForValue(i);
|
||||
if (cell.present && cell.v === 0) {
|
||||
ctx.fillStyle = C.flat;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x0, y, 3, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
} else if (!cell.present) {
|
||||
ctx.fillStyle = "rgba(255,255,255,.32)";
|
||||
ctx.font = '9px "IBM Plex Mono", monospace';
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(absentWord(IND_ORDER[i]), x0 + 6, y);
|
||||
}
|
||||
});
|
||||
ctx.restore();
|
||||
}
|
||||
}],
|
||||
options: {
|
||||
indexAxis: "y",
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
layout: { padding: { right: 4 } },
|
||||
// Hover the whole row: a zero-length bar cannot be intersected.
|
||||
interaction: { mode: "index", axis: "y", intersect: false },
|
||||
scales: {
|
||||
x: {
|
||||
min: -2, max: 2,
|
||||
@@ -291,8 +325,8 @@ function indChart(tf) {
|
||||
grid: { display: false },
|
||||
border: { display: false },
|
||||
ticks: {
|
||||
// An indicator with no reading is not a neutral vote: both draw
|
||||
// no bar, so the dimmed label is what tells them apart.
|
||||
// An indicator with no reading is not a neutral vote: the label
|
||||
// dims and the indMarkers plugin tags the row instead of a dot.
|
||||
color: ctx => (lastCells[tf] && lastCells[tf][ctx.index] && !lastCells[tf][ctx.index].present)
|
||||
? "rgba(255,255,255,.32)" : C.axis,
|
||||
font: { family: '"IBM Plex Mono", monospace', size: 10 },
|
||||
@@ -307,7 +341,10 @@ function indChart(tf) {
|
||||
title: items => IND_NAME[IND_ORDER[items[0].dataIndex]],
|
||||
label: item => {
|
||||
const cell = (lastCells[tf] || indCells(currentReport(tf)))[item.dataIndex];
|
||||
if (!cell || !cell.present) return "No reading on this scan";
|
||||
if (!cell || !cell.present) {
|
||||
const w = absentWord(IND_ORDER[item.dataIndex]);
|
||||
return `${w[0].toUpperCase()}${w.slice(1)} on this scan`;
|
||||
}
|
||||
return `${sigWord(cell.sig)}, confidence ${Math.round(cell.conf)}%`;
|
||||
}
|
||||
}
|
||||
@@ -687,9 +724,12 @@ function indTable(src) {
|
||||
const i = src && src[name];
|
||||
const k = `<td class="ind-k">${IND_SHORT[name]}</td>`;
|
||||
if (!i) {
|
||||
return `<tr>${k}<td class="flat">no reading</td><td class="num text-end">—</td>
|
||||
const why = PATTERN_IND.has(name)
|
||||
? "no pattern was found on this scan"
|
||||
: "this indicator had nothing to say on this scan";
|
||||
return `<tr>${k}<td class="flat">${absentWord(name)}</td><td class="num text-end">—</td>
|
||||
<td class="num text-end">—</td><td class="num text-end">—</td>
|
||||
<td class="flat">this indicator had nothing to say on this scan</td></tr>`;
|
||||
<td class="flat">${why}</td></tr>`;
|
||||
}
|
||||
const w = i.weight == null ? null : +i.weight;
|
||||
return `<tr>${k}
|
||||
|
||||
Reference in New Issue
Block a user