potential 1m fix
This commit is contained in:
parent
eb18de9bb9
commit
c28b0ce535
2 changed files with 70 additions and 5 deletions
|
|
@ -2514,10 +2514,18 @@ class ConfluenceChart {
|
||||||
if (bar.t >= level.anchor_t && bar.t <= cutoff) stamps.add(bar.t);
|
if (bar.t >= level.anchor_t && bar.t <= cutoff) stamps.add(bar.t);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const displayed = this.bars.filter(bar => bar.t >= level.anchor_t && bar.t <= cutoff);
|
||||||
for (const time of ConfluenceChart.sourceTimes(source) || []) {
|
for (const time of ConfluenceChart.sourceTimes(source) || []) {
|
||||||
if (time >= first && time <= lastBar && time >= level.anchor_t && time <= cutoff) {
|
if (time < first || time > lastBar || time < level.anchor_t || time > cutoff) continue;
|
||||||
stamps.add(time);
|
let lo = 0;
|
||||||
|
let hi = displayed.length;
|
||||||
|
while (lo < hi) {
|
||||||
|
const mid = (lo + hi) >> 1;
|
||||||
|
if (displayed[mid].t < time) lo = mid + 1;
|
||||||
|
else hi = mid;
|
||||||
}
|
}
|
||||||
|
const bar = displayed[lo] || displayed[displayed.length - 1];
|
||||||
|
if (bar) stamps.add(bar.t);
|
||||||
}
|
}
|
||||||
if (lastBar >= level.anchor_t && lastBar <= cutoff) stamps.add(lastBar);
|
if (lastBar >= level.anchor_t && lastBar <= cutoff) stamps.add(lastBar);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,9 +94,10 @@ test('a 30m line keeps source geometry when the 1m window starts after its ancho
|
||||||
'the rollback path no longer restores displayed-grid geometry');
|
'the rollback path no longer restores displayed-grid geometry');
|
||||||
assert.equal(result.shifted, result.sourceSecondBar,
|
assert.equal(result.shifted, result.sourceSecondBar,
|
||||||
'a source-timeframe nudge did not move by one 30m bar');
|
'a source-timeframe nudge did not move by one 30m bar');
|
||||||
assert.deepEqual(result.sampledTimes.slice(0, result.displayedTimes.length), result.displayedTimes,
|
const historical = result.sampledTimes.filter(time => time <= result.displayedTimes.at(-1));
|
||||||
'manual line samples no longer match the displayed candle grid');
|
assert.deepEqual(historical, [result.displayedTimes[0], result.displayedTimes.at(-1)],
|
||||||
assert.ok(result.sampledTimes.slice(result.displayedTimes.length)
|
'a 30m line on 1m should sample source-bar times, not every 1m candle');
|
||||||
|
assert.ok(result.sampledTimes.slice(historical.length)
|
||||||
.every(time => time > result.displayedTimes.at(-1)),
|
.every(time => time > result.displayedTimes.at(-1)),
|
||||||
'manual-line extension mixed future points into candle history');
|
'manual-line extension mixed future points into candle history');
|
||||||
assert.deepEqual(result.trimmedTimes, result.expectedTrimmedTimes,
|
assert.deepEqual(result.trimmedTimes, result.expectedTrimmedTimes,
|
||||||
|
|
@ -353,6 +354,62 @@ test('a short final data gap does not kink a future trendline',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a 30m line stays straight on 1m when the bucket is missing its first minutes',
|
||||||
|
{ timeout: 180000 }, async () => {
|
||||||
|
await withChart(async page => {
|
||||||
|
const result = await page.evaluate(() => {
|
||||||
|
const c = window.__chart;
|
||||||
|
const minute = 60;
|
||||||
|
const half = 30 * minute;
|
||||||
|
const open = 400000;
|
||||||
|
const bars = [];
|
||||||
|
for (let t = open - 2 * half; t < open; t += minute) {
|
||||||
|
bars.push({ tf: '1m', t, o: 100, h: 101, l: 99, c: 100, v: 1, closed: true });
|
||||||
|
}
|
||||||
|
for (let t = open + 9 * minute; t < open + 2 * half; t += minute) {
|
||||||
|
bars.push({ tf: '1m', t, o: 100, h: 101, l: 99, c: 100, v: 1, closed: true });
|
||||||
|
}
|
||||||
|
const line = {
|
||||||
|
id: 'late-open', kind: 'manual', tf: '30m', side: 'support', label: 'late',
|
||||||
|
anchor_t: open - 2 * half, anchor_p: 100, last_t: open,
|
||||||
|
slope: 3 / half, cutoff_t: null, hidden: false, color: '#65b7cf',
|
||||||
|
line_width: 2, provisional: false,
|
||||||
|
};
|
||||||
|
c.setDisplayFutureTimes(Array.from(
|
||||||
|
{ length: ConfluenceChart.FUTURE_SLOTS }, (_, index) => bars.at(-1).t + (index + 1) * minute,
|
||||||
|
));
|
||||||
|
c.setBars(bars);
|
||||||
|
c.setTrendlineGeometry({
|
||||||
|
mode: 'source_tf',
|
||||||
|
series: { '30m': { times: [open - 2 * half, open - half, open, open + half], duration: half } },
|
||||||
|
});
|
||||||
|
c.syncLevels([line]);
|
||||||
|
const data = c.levelSeries.get(line.id).series.data();
|
||||||
|
const point = time => {
|
||||||
|
const row = data.find(value => Number(value.time) === time);
|
||||||
|
return {
|
||||||
|
x: c.chart.timeScale().timeToCoordinate(time),
|
||||||
|
y: row ? c.candles.priceToCoordinate(row.value) : null,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const a = point(open - half);
|
||||||
|
const b = point(open + 9 * minute);
|
||||||
|
const d = point(open + half);
|
||||||
|
return {
|
||||||
|
snappedOpen: data.some(value => Number(value.time) === open + 9 * minute),
|
||||||
|
injectedOpen: data.some(value => Number(value.time) === open),
|
||||||
|
lateSlope: (b.y - a.y) / (b.x - a.x),
|
||||||
|
fullSlope: (d.y - b.y) / (d.x - b.x),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
assert.equal(result.injectedOpen, false, '30m open that is not a 1m bar was placed on the scale');
|
||||||
|
assert.equal(result.snappedOpen, true, 'missing 30m open was not snapped to the first 1m bar');
|
||||||
|
assert.ok(Math.abs(result.lateSlope - result.fullSlope) < 1e-6,
|
||||||
|
`late-open 30m segment kinked from ${result.lateSlope} to ${result.fullSlope}`);
|
||||||
|
assertNoPageErrors(page, assert);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('painted canvas pixels keep the same slope through the live edge',
|
test('painted canvas pixels keep the same slope through the live edge',
|
||||||
{ timeout: 180000 }, async () => {
|
{ timeout: 180000 }, async () => {
|
||||||
await withChart(async page => {
|
await withChart(async page => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue