Commit graph

8 commits

Author SHA1 Message Date
2efcec6a76 Price trendlines across bars, add one-shot alerts, collapse layers, drop 1h MAs
The trendline bug: a line continued past its second anchor at a different
slope. Two conventions were fighting, and both were wrong.

Lightweight Charts spaces bars evenly however much time separates them — a
weekend is forty-nine hours and one bar wide. The renderer extended the line by
interpolating between bar indices, which looked straight but disagreed with the
server, since price_at() advances per second. Measured on real bars that reached
147 points: the chart drew a level the alerts did not believe in. Making the
renderer match price_at() fixed the disagreement and made the visible kick worse,
because now the line really did climb an hour's worth of slope across a one-bar
maintenance break.

Neither convention is what a person means by drawing a line. A trendline advances
per bar, so both sides now evaluate in bar space: a new bar_space module the
runtime uses to position sloped levels, mirrored by indexAt() in the chart. The
line is straight on screen and the alert fires where it is drawn.

Also, from testing against the live chart:

- A plain click with the trendline tool armed did nothing and left the tool
  armed, so the next click began a new line — which is how the slope change was
  first noticed. Click-click and press-drag-release are both supported now, with
  the rubber band following the cursor between clicks.
- Hand-placed levels are armed, fire once, then disarm themselves, and can be
  re-armed from the sidebar. Verified end to end: created armed, tripped within
  thirty seconds, disarmed, re-armed.
- Layers is collapsible.
- The 1h moving averages are gone; only the daily set remains.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 04:15:05 -05:00
e9c22f6bbd Move alert evaluation server-side so push works without a browser open
Alerts were evaluated inside the WebSocket handler, with a separate AlertEngine
per connection. Three consequences, all of which defeated the point of phone
push:

- No browser connected meant no alert at all. The notification only existed if
  a tab was open to receive it, which is precisely when you least need it.
- Two tabs meant two notifications, since each connection evaluated
  independently.
- Cooldowns lived and died with the connection, so reloading the page cleared
  them and a zone that had just alerted alerted again at once.

The third also meant the calibration in the README described a system nobody was
running: it models a single engine, which is what this now is.

Evaluation moves into Runtime, once per closed 1m bar, over every level. Layer
preferences are deliberately not consulted — they are a display choice made in
one browser, and a push notification should not depend on which checkboxes that
browser has ticked. Sockets now only relay what the runtime produced.

ntfy dispatch is a detached task with its own error handling. It previously ran
inline in the socket loop and called raise_for_status(), where the only except
clause caught disconnects — so a transient ntfy outage dropped the client's
connection.

Delivery verified end to end against ntfy.sh: title, priority and the multi-line
body all arrive as intended. NTFY_TOPIC still has to be set for anything to send.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 00:48:03 -05:00
8ca624d435 Add prior-day levels and session VWAP; fix alert repetition they exposed
The confluence engine had nothing to work with. Daily moving averages were the
only level source, and they sat 163 to 697 points from price, so every cluster
had exactly one member and no alert could ever fire.

Two new sources, chosen for having a real following — the engine is a bet that
many participants watch the same price, which is what makes a level hold:

- Prior day high/low/close, from the last *closed* daily bar so mid-session the
  levels do not silently switch to today's own developing range. Full daily
  weight rather than the 0.75 average discount: a traded high is structure, not
  a derived average.
- Session VWAP, anchored to the 18:00 ET open like the daily bars. Institutional
  execution is benchmarked against it, and zero-volume overnight minutes are
  skipped rather than dividing by zero.

Both are stamped 1d, so they get their own colours to stay distinguishable from
the daily averages. Prior-day levels draw as price lines, which span the chart
and label the axis instead of relying on bar-index interpolation.

VWAP re-prices every minute while a daily average carries hundreds of points and
changes once a session, so broadcasting the whole level set on the VWAP cadence
would have pushed the entire history every minute. Levels now go out as a delta
that clients merge by id.

Adding the levels then exposed two defects that had been invisible while nothing
could cluster:

- Cluster identity was sha1(side + round(center / tolerance)), and tolerance
  derives from ATR, so it changed every bar. The same zone was continually
  issued a new id, never matched the cooldown table, and the cooldown did
  nothing. Identity is now the set of converging levels.
- Alert suppression keyed on that identity, so a level drifting in or out of a
  group read as a new zone. It now suppresses by proximity: two zones within an
  ATR are the same zone, and the strongest is the one reported.

Over six replayed sessions at threshold 28 that is 247 alerts, then 54, then 40;
raising the cooldown to 4h — which only affects repeats of the same area, never
a genuinely new zone — gives 17 total with a worst session of 9.

calibrate_alerts.py now sweeps threshold and cooldown together in one pass,
since the threshold turns out to be quantised and nearly useless as a control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 00:36:21 -05:00
49cd06089f Implement M5 manual trendlines 2026-08-09 20:55:05 -05:00
e3ebe2914d Implement M4 confluence alerts 2026-08-09 20:51:32 -05:00
7f2fcc2020 Implement M3 projected daily moving averages 2026-08-09 20:44:11 -05:00
f87ca0a153 Implement M2 session-aware aggregation 2026-08-09 20:41:36 -05:00
acc59817c4 Implement M1 live one-minute chart 2026-08-09 20:38:50 -05:00