lower time frame trendlines blocked from dma chart

This commit is contained in:
Chris Amow 2026-08-23 21:10:09 -05:00
parent 7c5f0a7627
commit 913eec34ea
5 changed files with 56 additions and 5 deletions

View file

@ -1094,6 +1094,12 @@ same effective style is applied to rare SVG endpoint bridges so an off-grid
anchor cannot introduce a visible style seam. Browser coverage checks 30m-on-5m anchor cannot introduce a visible style seam. Browser coverage checks 30m-on-5m
and native 30m rendering separately. and native 30m rendering separately.
Sloped manual lines also stop propagating upward: a 30m line is available on
30m and lower charts but hidden on 1h/1d. This is a display filter only; the
drawing remains loaded and flat price levels remain visible everywhere. The
daily-chart regression creates a real 30m drawing, confirms it is rendered on
the lower chart, switches to 1d, and requires only its series to disappear.
### 2026-08-15 — 1m zoom-out died at ~1am because the socket sent 1,000 bars ### 2026-08-15 — 1m zoom-out died at ~1am because the socket sent 1,000 bars
Compressing the 1m time scale stopped around 01:00, then looked empty. Compressing the 1m time scale stopped around 01:00, then looked empty.

View file

@ -122,8 +122,10 @@ if your parser doesn't filter those, that fixture will catch it.
1d base chart + THE DAILY MAs weight 16 ← a switchable base timeframe 1d base chart + THE DAILY MAs weight 16 ← a switchable base timeframe
``` ```
Base timeframe controls the candles only. **Every level stays visible on every base Derived levels and flat price levels stay visible on every base timeframe — the
timeframe** — the 200DMA on a 1-minute chart is the point, not a side effect. 200DMA on a 1-minute chart is the point, not a side effect. Sloped manual
trendlines are visible on their attributed timeframe and lower charts, but not
on higher charts: 30m structure belongs on 30m/15m/5m/1m, not on 1h or 1d.
--- ---
@ -917,8 +919,9 @@ LAYERS
☐ Hidden levels still count toward confluence ☐ Hidden levels still count toward confluence
``` ```
- **The base timeframe switcher changes only the candles.** Every level stays on screen - **Derived and flat levels stay across base timeframes.** A 200DMA is equally
— a 200DMA is equally valid on a 1m chart. That is the entire premise of the product. valid on a 1m chart. Sloped manual trendlines are the exception: hide them
above their attributed timeframe while retaining them on native/lower charts.
- **The group checkbox is a master toggle** — unchecking "Daily MAs" hides all five at - **The group checkbox is a master toggle** — unchecking "Daily MAs" hides all five at
once; individual periods nest under it. once; individual periods nest under it.
- The colour swatch beside each timeframe is that timeframe's hue, used identically on - The colour swatch beside each timeframe is that timeframe's hue, used identically on

View file

@ -1347,7 +1347,8 @@ createApp({
function enabled(level) { function enabled(level) {
if (level.kind === 'ma') return (prefs.value.enabled.ma[level.tf] || []).includes(level.period); if (level.kind === 'ma') return (prefs.value.enabled.ma[level.tf] || []).includes(level.period);
if (level.kind === 'manual') { if (level.kind === 'manual') {
return prefs.value.enabled.drawings && prefs.value.enabled.manual; return prefs.value.enabled.drawings && prefs.value.enabled.manual
&& ConfluenceChart.manualLevelVisibleOn(level, timeframe.value);
} }
if (level.kind === 'horizontal') return prefs.value.enabled.horizontal; if (level.kind === 'horizontal') return prefs.value.enabled.horizontal;
if (level.kind === 'vwap') return prefs.value.enabled.vwap; if (level.kind === 'vwap') return prefs.value.enabled.vwap;

View file

@ -1250,6 +1250,11 @@ class ConfluenceChart {
return level.kind === 'ma' || level.kind === 'vwap' || level.kind === 'horizontal'; return level.kind === 'ma' || level.kind === 'vwap' || level.kind === 'horizontal';
} }
static manualLevelVisibleOn(level, displayedTf) {
if (level.kind !== 'manual' || ConfluenceChart.isFlat(level)) return true;
return ConfluenceChart.tfRank[level.tf] >= ConfluenceChart.tfRank[displayedTf];
}
manualLineDisplayStyle(level) { manualLineDisplayStyle(level) {
const baseWidth = level.line_width || 1; const baseWidth = level.line_width || 1;
const displayedTf = this.bars[0]?.tf; const displayedTf = this.bars[0]?.tf;

View file

@ -358,6 +358,42 @@ test('higher-timeframe trendlines are dashed and twice their stored width',
}); });
}); });
test('a lower-timeframe trendline is hidden from the daily chart',
{ timeout: 180000 }, async () => {
await withChart(async page => {
const created = await page.evaluate(async () => {
const bars = (await (await fetch('/api/bars?tf=30m&limit=2')).json()).bars;
const first = bars[0];
const second = bars[1];
const response = await fetch('/api/lines', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tf: '30m', side: 'support', anchor_t: first.t, anchor_p: first.l,
end_t: second.t, end_p: first.l + 1, note: `daily filter ${Date.now()}`,
}),
});
return response.json();
});
await page.waitForFunction(id => window.__chart.levelSeries.has(id), created.id);
await page.click('.timeframes button:text-is("1d")');
await page.waitForFunction(() => window.__chart.bars[0]?.tf === '1d');
await page.waitForFunction(id => !window.__chart.levelSeries.has(id), created.id);
const state = await page.evaluate(async id => ({
drawingStillStored: (await (await fetch('/api/drawings')).json()).drawings
.some(drawing => drawing.id === id),
flatLevelVisible: ConfluenceChart.manualLevelVisibleOn(
{ kind: 'manual', slope: 0, tf: '30m' }, '1d',
),
}), created.id);
assert.equal(state.drawingStillStored, true, 'daily filtering deleted the drawing state');
assert.equal(state.flatLevelVisible, true, 'daily filtering hid a flat price level');
assertNoPageErrors(page, assert);
});
});
test('a click, a move, then a click starts at the first click', { timeout: 180000 }, async () => { test('a click, a move, then a click starts at the first click', { timeout: 180000 }, async () => {
await withChart(async page => { await withChart(async page => {
const box = await chartBox(page); const box = await chartBox(page);