This commit is contained in:
Chris Amow 2026-08-15 03:50:38 -05:00
parent 73570d2904
commit 0c7e81d222
3 changed files with 13 additions and 3 deletions

View file

@ -1069,3 +1069,8 @@ so nine elapsed minutes occupy nine logical slots and the line remains straight.
Gaps over 30 minutes stay compressed, preserving the established treatment of the Gaps over 30 minutes stay compressed, preserving the established treatment of the
65-minute settlement break and weekends. A browser regression pins both the 65-minute settlement break and weekends. A browser regression pins both the
nine-slot spacing and equal historical/future screen slope. nine-slot spacing and equal historical/future screen slope.
Production then showed that whitespace-only points could still disappear from the
shared scale. The time-scale owner now uses transparent zero-valued points with an
`autoscaleInfoProvider` that returns null. They remain invisible and cannot affect
price range, but Lightweight Charts retains their timestamps as real series data.

View file

@ -233,6 +233,7 @@ class ConfluenceChart {
// something to label and so timeToCoordinate answers out there. // something to label and so timeToCoordinate answers out there.
this.futureSpace = this.chart.addSeries(LightweightCharts.LineSeries, { this.futureSpace = this.chart.addSeries(LightweightCharts.LineSeries, {
priceScaleId: 'right', priceScaleId: 'right',
color: 'rgba(0,0,0,0)',
lastValueVisible: false, lastValueVisible: false,
priceLineVisible: false, priceLineVisible: false,
crosshairMarkerVisible: false, crosshairMarkerVisible: false,
@ -531,7 +532,7 @@ class ConfluenceChart {
if (step <= 0) return []; if (step <= 0) return [];
const slots = []; const slots = [];
for (let index = 1; index <= ConfluenceChart.FUTURE_SLOTS; index += 1) { for (let index = 1; index <= ConfluenceChart.FUTURE_SLOTS; index += 1) {
slots.push({ time: last.t + index * step }); slots.push({ time: last.t + index * step, value: 0 });
} }
return slots; return slots;
} }
@ -544,7 +545,7 @@ class ConfluenceChart {
const gap = bars[index].t - bars[index - 1].t; const gap = bars[index].t - bars[index - 1].t;
if (gap <= step || gap > ConfluenceChart.MAX_INTRADAY_GAP_SECONDS) continue; if (gap <= step || gap > ConfluenceChart.MAX_INTRADAY_GAP_SECONDS) continue;
for (let time = bars[index - 1].t + step; time < bars[index].t; time += step) { for (let time = bars[index - 1].t + step; time < bars[index].t; time += step) {
slots.push({ time }); slots.push({ time, value: 0 });
} }
} }
return [...slots, ...this.futureTimes(bars)]; return [...slots, ...this.futureTimes(bars)];
@ -585,7 +586,7 @@ class ConfluenceChart {
return; return;
} }
const futureTime = bar.t + ConfluenceChart.FUTURE_SLOTS * step; const futureTime = bar.t + ConfluenceChart.FUTURE_SLOTS * step;
this.futureSpace.update({ time: futureTime }); this.futureSpace.update({ time: futureTime, value: 0 });
for (const level of this.levels) { for (const level of this.levels) {
if (level.kind !== 'manual' || level.hidden || ConfluenceChart.isFlat(level) if (level.kind !== 'manual' || level.hidden || ConfluenceChart.isFlat(level)
|| futureTime < level.anchor_t || futureTime > (level.cutoff_t ?? Infinity)) continue; || futureTime < level.anchor_t || futureTime > (level.cutoff_t ?? Infinity)) continue;

View file

@ -201,11 +201,15 @@ test('a short final data gap does not kink a future trendline',
historicalSlope: (current.y - previous.y) / (current.x - previous.x), historicalSlope: (current.y - previous.y) / (current.x - previous.x),
futureSlope: (future.y - current.y) / (future.x - current.x), futureSlope: (future.y - current.y) / (future.x - current.x),
historicalSlots: (current.x - previous.x) / (future.x - current.x), historicalSlots: (current.x - previous.x) / (future.x - current.x),
missingSlotOwned: c.futureSpace.data()
.some(point => Number(point.time) === previousTime + minute),
}; };
}); });
assert.ok(Math.abs(result.historicalSlots - 9) < 0.01, assert.ok(Math.abs(result.historicalSlots - 9) < 0.01,
`a nine-minute data gap occupied ${result.historicalSlots} slots`); `a nine-minute data gap occupied ${result.historicalSlots} slots`);
assert.equal(result.missingSlotOwned, true,
'the invisible scale series discarded the missing intraday timestamp');
assert.ok(Math.abs(result.historicalSlope - result.futureSlope) < 1e-9, assert.ok(Math.abs(result.historicalSlope - result.futureSlope) < 1e-9,
`line kinked from ${result.historicalSlope} to ${result.futureSlope}`); `line kinked from ${result.historicalSlope} to ${result.futureSlope}`);
assertNoPageErrors(page, assert); assertNoPageErrors(page, assert);