slope calc fix
This commit is contained in:
parent
18585e42cd
commit
01acdce045
3 changed files with 57 additions and 5 deletions
|
|
@ -1224,8 +1224,13 @@ Layer toggle **SPY open/close**, on by default.
|
|||
### 2026-08-18 — Trendline slope in the tooltip
|
||||
|
||||
Hovering a sloped line now shows signed points per hour (`+1.25 /h`,
|
||||
`-0.40 /h`). Degrees follow the zoom; points per bar follow the timeframe.
|
||||
The stored slope is still price per second. Flat levels omit the line.
|
||||
`-0.40 /h`). The first implementation multiplied persisted wall-clock slope by
|
||||
3,600. That disagreed between parallel duplicates when one endpoint span crossed
|
||||
a settlement/weekend gap: duplication preserves source-bar geometry, not elapsed
|
||||
wall time. The tooltip now derives price change per canonical source bar and
|
||||
converts by that source bucket's active duration. Parallel duplicates therefore
|
||||
show the same rate. The stored slope remains price per second so persisted
|
||||
endpoints are unchanged. Flat levels omit the line.
|
||||
|
||||
### 2026-08-18 — Events is the sent-alert log
|
||||
|
||||
|
|
|
|||
|
|
@ -2235,7 +2235,7 @@ class ConfluenceChart {
|
|||
const color = this.describeDrawingColor?.(line.color);
|
||||
this.tooltip.textContent = [
|
||||
`#${line.number} ${line.label}`,
|
||||
ConfluenceChart.slopeLabel(line),
|
||||
this.slopeLabel(line),
|
||||
color?.name,
|
||||
color?.annotation,
|
||||
].filter(Boolean).join('\n');
|
||||
|
|
@ -3016,9 +3016,24 @@ class ConfluenceChart {
|
|||
return marks.filter(mark => mark.t >= fromT - 3600 && mark.t <= toT + 3600);
|
||||
}
|
||||
|
||||
static slopeLabel(line) {
|
||||
slopePerHour(line) {
|
||||
const source = this.sourceSeries(line);
|
||||
if (source) {
|
||||
const start = ConfluenceChart.timeframeIndexAt(source, line.anchor_t);
|
||||
const end = ConfluenceChart.timeframeIndexAt(source, line.last_t);
|
||||
const duration = start == null || end == null
|
||||
? null : ConfluenceChart.sourceDuration(source, Math.floor((start + end) / 2));
|
||||
if (duration > 0 && end !== start) {
|
||||
const endPrice = line.anchor_p + line.slope * (line.last_t - line.anchor_t);
|
||||
return (endPrice - line.anchor_p) / (end - start) * 3600 / duration;
|
||||
}
|
||||
}
|
||||
return line.slope * 3600;
|
||||
}
|
||||
|
||||
slopeLabel(line) {
|
||||
if (ConfluenceChart.isFlat(line) || !line.slope) return '';
|
||||
const perHour = line.slope * 3600;
|
||||
const perHour = this.slopePerHour(line);
|
||||
const decimals = Math.abs(perHour) >= 10 ? 1 : 2;
|
||||
return `${perHour > 0 ? '+' : ''}${perHour.toFixed(decimals)} /h`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,38 @@ test('a 30m line keeps source geometry when the 1m window starts after its ancho
|
|||
});
|
||||
});
|
||||
|
||||
test('parallel duplicates keep the same source-space hourly slope across a gap',
|
||||
{ timeout: 180000 }, async () => {
|
||||
await withChart(async page => {
|
||||
const result = await page.evaluate(() => {
|
||||
const c = window.__chart;
|
||||
const halfHour = 30 * 60;
|
||||
const weekend = 49 * 3600;
|
||||
const times = [
|
||||
0, halfHour, halfHour + weekend, 2 * halfHour + weekend,
|
||||
3 * halfHour + weekend, 4 * halfHour + weekend,
|
||||
];
|
||||
c.setTrendlineGeometry({ mode: 'source_tf', series: { '30m': { times, duration: halfHour } } });
|
||||
const line = (anchor_t, last_t) => ({
|
||||
kind: 'manual', tf: '30m', anchor_t, anchor_p: 100, last_t,
|
||||
slope: 2 / (last_t - anchor_t),
|
||||
});
|
||||
const acrossGap = line(times[0], times[2]);
|
||||
const contiguous = line(times[2], times[4]);
|
||||
return {
|
||||
raw: [acrossGap.slope * 3600, contiguous.slope * 3600],
|
||||
canonical: [c.slopePerHour(acrossGap), c.slopePerHour(contiguous)],
|
||||
labels: [c.slopeLabel(acrossGap), c.slopeLabel(contiguous)],
|
||||
};
|
||||
});
|
||||
|
||||
assert.notEqual(result.raw[0], result.raw[1], 'fixture did not cross a wall-clock gap');
|
||||
assert.deepEqual(result.canonical, [2, 2]);
|
||||
assert.deepEqual(result.labels, ['+2.00 /h', '+2.00 /h']);
|
||||
assertNoPageErrors(page, assert);
|
||||
});
|
||||
});
|
||||
|
||||
test('future 5m projections keep their slope after the settlement break',
|
||||
{ timeout: 180000 }, async () => {
|
||||
await withChart(async page => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue