lower time frame trendlines blocked from dma chart
This commit is contained in:
parent
7c5f0a7627
commit
913eec34ea
5 changed files with 56 additions and 5 deletions
|
|
@ -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.
|
||||||
|
|
|
||||||
11
docs/plan.md
11
docs/plan.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue