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
|
### 2026-08-18 — Trendline slope in the tooltip
|
||||||
|
|
||||||
Hovering a sloped line now shows signed points per hour (`+1.25 /h`,
|
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.
|
`-0.40 /h`). The first implementation multiplied persisted wall-clock slope by
|
||||||
The stored slope is still price per second. Flat levels omit the line.
|
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
|
### 2026-08-18 — Events is the sent-alert log
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2235,7 +2235,7 @@ class ConfluenceChart {
|
||||||
const color = this.describeDrawingColor?.(line.color);
|
const color = this.describeDrawingColor?.(line.color);
|
||||||
this.tooltip.textContent = [
|
this.tooltip.textContent = [
|
||||||
`#${line.number} ${line.label}`,
|
`#${line.number} ${line.label}`,
|
||||||
ConfluenceChart.slopeLabel(line),
|
this.slopeLabel(line),
|
||||||
color?.name,
|
color?.name,
|
||||||
color?.annotation,
|
color?.annotation,
|
||||||
].filter(Boolean).join('\n');
|
].filter(Boolean).join('\n');
|
||||||
|
|
@ -3016,9 +3016,24 @@ class ConfluenceChart {
|
||||||
return marks.filter(mark => mark.t >= fromT - 3600 && mark.t <= toT + 3600);
|
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 '';
|
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;
|
const decimals = Math.abs(perHour) >= 10 ? 1 : 2;
|
||||||
return `${perHour > 0 ? '+' : ''}${perHour.toFixed(decimals)} /h`;
|
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',
|
test('future 5m projections keep their slope after the settlement break',
|
||||||
{ timeout: 180000 }, async () => {
|
{ timeout: 180000 }, async () => {
|
||||||
await withChart(async page => {
|
await withChart(async page => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue