xyz-monitor // signal · study · shadow reference
Every event the engine can fire, how it triggers, how it's measured, what renders, and what reaches a phone. Written against the codebase as of 2026.07.27. File anchors: detection math in src/compute.js, assembly & fire sites in src/poller.js, rendering in public/app.js, routes in server.js, pins in test/test.js.
Every entry below is described on the same four axes, because those are the four things you'd tune independently:
| Axis | Meaning | Where it lives |
| Trigger | The mechanical condition that fires it, with exact thresholds. | compute.js detectors / poller.js fire sites |
| Measurement | Horizon, sign convention, unit, how the claim resolves, what the study measured. | EV_META / EV_META_MAIN, resolver in poller.js |
| Display | What the card / board / drawer renders, and from which payload field. | app.js (never re-derived client-side) |
| Alert | Which alert class it can emit and under what gate. | trigger stream + transports in poller.js/compute.js |
Pipeline in one pass
data (hourly spine · daily · funding · OI · Coinalyze deriv · Finnhub earnings · EDGAR)
↓ studies (in-sample base rates, per market per event, memoized to data version)
↓ buildSignals — live surveillance: condition fires → mkSignal(intensity 0–50 + evidence 0–50)
↓ openLedger — claim frozen at fire: mark0 · psd · stp · mv · tgt · sd0 · context stamp
↓ episode gate (rearm) · universe gate (evAllowed) · geometry gate (stop/claimGeometryOk)
↓ resolveLedger — at horizon: realized (play-signed, event unit) + realizedS (stop-aware)
↓ record sets → Bayesian blend back into evidence · no-edge cap · confluence lift · variant promotion
↓ surfaces: Signals cards · Actionable board · drawer record · ticker history · AI report
↓ trigger stream (one canonical sequenced log) → transports: browser toast/bell · Telegram push
One-code-path contract. Every level on every surface (entry, void, target, R/R, lateness) ships on the server payload, frozen by the ledger at fire time. The client sorts and formats; it never recomputes a level. The moment two surfaces derive the same number separately they can disagree — that rule is pinned all over the test suite.
Scoring model
Score = intensity (0–50, per-event formula: how unusual the condition is right now) + evidence (0–50, expectancy-centered). Displayed on every card; drives ranking and the calibration audit.
Evidence (evPts / evidence in poller.js)
| Rule | Detail |
| Expectancy-centered | avg ≤ 0 → 0 points + neg exp flag, regardless of how loud the condition is. Else min(1, avg/scale)·30 + max(0, hit−0.5)·2·20. Scale: 0.5R for R-united events, 0.8% for %-united. |
| Evidence source | Own study if n ≥ 8 (full weight) → asset-class pooled if n ≥ 12 (0.7 discount, unproven flag) → token 6–8 pts (unproven). Crypto pools in its own bucket, never mixed with equity classes. |
| Live blend | Once an event type has ≥ 5 live resolutions, stats become a blend of study and live record: w = resolved/(resolved+25) (0.17 at n=5, 0.67 at n=50). Shown as liveW %. Trust migrates both directions. |
| No-live-edge cap | liveNoEdge: ≥10 resolved, hit < 50%, med ≤ 0 → evidence hard-capped at 8, no live edge badge. Sits on top of the blend. |
| Earnings guard | Report today/tomorrow (ET) inside the horizon → evidence capped at the same 8, prime disabled, earnings flag. Intensity untouched — the condition is real, only borrowed confidence is trimmed. |
Modifiers
| Modifier | Gate | Effect |
| ★ prime | hit ≥ 60% · avg > 0 · not noedge/negexp/earnings · R/R ≥ 1.2 where levels exist | +6 score, emphasis styling; pr stamped once on the claim at fire |
| Decay | claim age > horizon | score × 0.6, amber state, prime off; age > 2× horizon → dropped |
| Confluence | multiple same-side conditions on one name | default +8/condition; once ≥15 resolutions on each side (with-company vs solo), bonus scales to the measured lift — and goes to 0 if agreement doesn't prove out |
| ⇄ Conflict | long and short events both live on one name | flagged, no bonus for anyone; each claim resolves on its own record |
| Universe lanes | capPerUniverse(kept, 40, 40) | 40 signals per universe — crypto σ is 5–20× equity σ, so a single global sort would evict the equity board on any volatile day |
Ledger mechanics
| Mechanism | How it works |
| Frozen geometry | At fire: mark0 (entry mark), psd (play side), stp (void level), mv (target distance %, rounded), tgt (absolute target price — stamped because mv cannot be turned back into a price), sd0 (30d daily σ for R normalization), score0, pr. Never re-derived, never moved — a bias flip later leaves the frozen claim to resolve untouched. |
| Episode re-arm | If a claim resolves while its condition still fires, the key parks in rearm and cannot re-open until the condition lapses for ≥1 full build. One episode = one claim; kills serial pseudo-replication on persistent conditions (weekend premium, all-day big move). Applies to shadows too. |
| Resolution | resolveLedger revisits each claim at resolveAt: realized = at-horizon outcome, signed by the play side, in the event's unit. realizedS = stop-aware parallel track (outcome capped as if the void had been a hard stop; stopped flags a touched void). Session-calendar events (gap, gapfade) resolve at the next cash close instead of a wall-clock horizon. |
| Units | prem → bp; R-united set (bigmove breakout breakdown fundflip volshift oiflush fpdiv reclaim mapull failbrk pead sweep airead casc fundext) → R via sd0; everything else %. |
| Geometry gates | stopGeometryOk / claimGeometryOk refuse inverted or degenerate levels at fire. Crypto levels are built in log space (logGeo) so σ constructions can't produce negative prices; equity keeps the exact additive arithmetic so its record stays byte-comparable. |
| Context stamp | Every claim (shadows included) carries fire-time features for slicing: fnd/fndP (funding + 31d percentile), oi5, rngP (position in 30d range), mktR (benchmark 24h move — BTC for crypto, SPX proxy for xyz), ses (xyz: rth/on/wknd) or hod (crypto: asia/eu/us/late), dow. Absent means honestly unknown, never null-padded. |
| Chain dedup | Resolved re-fires ≤2d apart chain into one displayed episode on charts — recorded, but not re-marked. |
| Persistence | /data/ledger.json + append-only ledger-archive.jsonl on trim; optional off-site GitHub backup behind LEDGER_BACKUP_REPO + LEDGER_BACKUP_TOKEN (blob-sha skip, bytes verbatim). Crypto claims from before CRYPTO_EPOCH (2026-07-26 UTC) stay purged — old additive geometry produced fabricated targets. |
Equity ledger roster — 13 events (xyz universe)
The scoped roster the record strip renders (pinned client & server against each other): bigmove · breakout · breakdown · gap · fundflip · squeeze · unwind · oiflush · fpdiv · prem · ondrift · tretest · tretestdn. Zero-claim roster members render as explicit "awaiting first claim" rows — never fired ≠ not wired.
Big move bigmove DIRECTIONALXYZCRYPTO
TriggerToday's move ≥ incumbent σ of the market's own trailing 30d daily returns. Variant thresholds σ≥ 1.5 / 2 / 2.5, incumbent 2σ.
MeasureStudy d1: did past moves continue (+) or fade (−) the next day, signed with the move. Horizon 1d (crypto: 12h). Unit R.
PlaybookContinuation side; target = ±|study med|% from px; stop = 1σ against the move. Watch: volume staying elevated — thrust on fading volume is the fade setup instead.
DisplayCard with reading like "+3.1σ daily move", study line, playbook row with live level distances.
Alertsetup class if it clears the Actionable CONFIRMED gate; ledger class on void/target/horizon.
30d-high breakout breakout LONGXYZCRYPTO
TriggerFirst close/mark above the prior 30-day high.
MeasureStudy d5: forward 5d return after past first-crosses. Horizon 5d (crypto 2d). Unit R.
PlaybookLong; target = +|med|% from px; stop = the breakout level itself — a close back below the prior 30d high voids it.
NotesLive record finding F2: breakout continuation ran ~−0.73R / 39% hit on this tape — the failbrk shadow was built to test the fade directly.
30d-low breakdown breakdown SHORTXYZCRYPTO
TriggerClose crossed below the prior 30d low. Bearish mirror of the breakout.
MeasureForward 5d, signed with the breakdown. Horizon 5d (crypto 2d). Unit R.
Outsized gap gap ADAPTIVEXYZ ONLY
TriggerLive move since the last cash close is outsized vs this market's own gap distribution. Gate variants σ≥ 0.5 / 0.75 / 1, incumbent 0.75σ. Fires only while the cash market is closed / pre-session (session calendar aware).
MeasureStudy session: did the next cash session continue (+) or fade (−) the gap. Resolves at the next session close (horizonMs: null — calendar walk, not wall clock). Unit %.
Fade flipIf the name's own record is proven (n ≥ 8) and med < 0, the market historically FADES its gaps: the card, the study numbers and the ledgered claim are flipped into fade-play units (positive = the fade paid), and the playbook shorts an up-gap with target = prior close. Unproven names never pick a side (WATCH).
DisplayCard states the flip explicitly; drawer gap chip; liveClose shipped in dailyCache for the in-progress gap.
Funding flip fundflip DIRECTIONALXYZCRYPTO
TriggerDay-summed funding changed sign after ≥ incumbent run days pinned the other way. Variants run≥ 2 / 3 / 5, incumbent 3.
Measure3d move toward the new crowd (crypto 2d). Unit R.
PlaybookSide of the new crowd; no target; stop = 1σ against the flip (added in the ops-findings pass — before it, fundflip had no stop-aware track). Watch: funding flipping straight back voids it; staying flipped 2+ days confirms.
Squeeze setup squeeze LONGXYZ ONLY
TriggerComposite: crowded shorts (negative 7d funding) × OI building × price pressing the range highs. Fires at composite score ≥ incumbent. Variants 50 / 60 / 70, incumbent 60.
MeasureHorizon 3d. No in-sample study (needs longer OI history) — record earned live. Unit %.
PlaybookLong; target = hi30 + 0.382 × range (measured-move extension — squeezes resolve through the range, not to it; targeting hi30 itself produced inverted R/R exactly when the signal fired); stop = lo30 + 0.25 × range. Watch: ΔOI(7d) turning negative = shorts covering, spring released.
CaveatExcluded from crypto: the composite routinely fires with price far from the range edge its levels assume — an inverted-at-fire level is still inverted even in log space. It stays xyz-only until the equity record proves the trigger.
Long unwind unwind SHORTXYZ ONLY
TriggerMirror composite: crowded longs paying funding × OI building × price near range lows. Same score variants 50 / 60 / 70, incumbent 60.
MeasureHorizon 3d, no in-sample study, unit %.
CaveatSource of the degenerate-void guard: its fixed-fraction-of-range void once landed 0.035% from entry (gross R/R 390+). The claim still ledgers; the Actionable board refuses it under degenerate (void ≥ 5bp absolute + σ-relative floor).
OI flush oiflush LONGXYZCRYPTO
Trigger7d ΔOI collapsed below −incumbent σ of the market's own distribution while price fell — forced deleveraging exhausting itself. Variants σ≤− 1.5 / 2 / 2.5, incumbent 2.
Measure5d bottoming thesis (crypto 2d), study d5. Unit R. Flushes measure positions destroyed, not price traveled.
PlaybookLong; target = +|med|%; stop = 1σ below. Watch: ΔOI stabilizing/turning up = flush complete; continued bleed = knife still falling.
Funding–price divergence fpdiv DIRECTIONALXYZCRYPTO
Trigger7d price z-score diverging from funding trajectory, gate |z7| ≥ ~0.8σ (intensity formula (|z7|−0.8)·20+16). Strength while funding falls = shorts pressing (long, squeeze-adjacent); weakness while funding rises = longs averaging down (short, fragile).
Measure3d with the divergence (crypto 2d), study d3, unit R.
PlaybookDivergence side; target = ±|med|; stop 1σ against. Watch: funding re-converging with price = setup spent.
Premium dislocation prem MEAN-REVERTXYZ ONLY
TriggerPerp price dislocated from oracle, |z| ≥ 2σ of its own 7d premium baseline (intensity (|z|−2)·12+18). During closed cash sessions this IS live price discovery for the synthetic — the card says so.
MeasureHorizon 12h, reversion toward oracle. Unit bp (the one non-R/non-% event).
PlaybookPerp rich → short the perp / cheap → long; target = the oracle itself; no stop. Watch: closed-session — does the cash open confirm the level or snap it back; open — persistence (a dislocation that survives arb for hours is information).
CaveatNo crypto analogue — main-dex oracle premium is arbitraged tight and carries nothing.
Overnight drift ondrift DIRECTIONALXYZ ONLY
TriggerEach market's summed off-hours drift over its last ~21 closed windows, z-scored across the universe (needs ≥25 markets with the stat). Fires at |z| ≥ 2 AND |drift| ≥ 1% absolute.
MeasureClaim covers ONLY the next 5 overnight windows, held close→open — a sum of windows, resolved by its own dedicated branch, not one span. Cash-session performance is irrelevant to the claim. Unit %. Ships with no backtest study by design; record earned purely out of sample.
PlaybookDrift side; no target, no stop. Watch: the drift sign flipping in the live windows = regime gone.
Trend retest tretest / tretestdn LONGSHORTXYZCRYPTO
TriggerThe Trend board's RETEST badge, promoted verbatim: a board-visible row (trend score ≥ 3/4 by trendRead's own gate, top-10 by rank) whose retesting rung probed the 13/21 EMA zone while the close held EMA21 (tretestdn: rallied into the zone, close held below EMA21). Read from the SAME buildTrend output the tab renders — signal and board cannot disagree.
MeasureHorizon 5d (crypto 3d). Frozen at fire: entry = mark, void = the rung's own EMA21 (ladder value), target = that rung's prior swing (null-tolerant: no valid swing → no target, claim still ledgers on its stop-aware leg). Unit %. No in-sample study — record earned out of sample.
FeaturesBoard score / rung tf / rrv / age recorded on the claim (tsc tf rrv tage) — recorded, NOT gated; the ledger decides which slices earn trust.
NotesCleanest crypto transfer in the roster — geometry was never a σ construction, so enrollment needed nothing but the whitelist entry.
Context flags — render, never ledger
Vol expansion volshift SIGNAL, NO ROSTER ROW
Trigger10d realized vol crossed above the 90th percentile of its own trailing ~6 months.
MeasureStudy d5, 5d horizon (crypto 2d), unit R — it carries full study evidence and EV_META, but is not in the scoped ledger roster strips (no "awaiting" row in either universe).
Range compression coil CONTEXT ONLY
Trigger10d realized vol in the bottom decile of its own trailing 120 observations — the spring is loaded, direction unknown.
MeasureNo ledger claim, no direction, unproven by construction. Exists to feed direction-aware confluence: a breakout/breakdown firing OUT of compression earns extra score.
Volume surge volume CONTEXT ONLY
Trigger24h volume ≥ 2.5× the market's own 30d norm.
MeasureNo claim; amplifies whatever else is firing. Intensity (ratio−2.5)·10+12, token 6 pts, unproven.
Crypto universe rules
Crypto roster (record strip): bigmove · breakout · breakdown · fundflip · oiflush · fpdiv · tretest · tretestdn · casc · fundext. Enrollment is a whitelist (MAIN_EVS), consulted by one gate — evAllowed(uni, ev) — at every fire site AND in openLedger, so a card can never render a play whose claim the gate refused.
| Excluded on crypto | Reason |
| gap · gapfade · ondrift · pead · sweep | Structurally impossible on a 24/7 tape — no session boundary, no earnings, no "prior session" object. |
| prem | No analogue — main-dex oracle premium is arbitraged tight. |
| squeeze · unwind | Geometry not yet trustworthy: composite fires away from the range edge its levels assume. Stays out until the equity record validates the trigger. |
| Crypto overrides | Detail |
| Compressed horizons | EV_META_MAIN: bigmove 12h · breakout/breakdown/volshift/fundflip/oiflush/fpdiv/reclaim/failbrk 2d · tretest(dn) 3d · mapull 4d · wickfill 1d. A 5d horizon on a name printing 12%/day resolves on tape noise, not the setup. Records never pool across universes. |
| Log-space geometry | All σ / range constructions via logLevel / logExtend (logGeo) — no negative prices. Equity keeps additive math byte-for-byte. |
| Epoch purge | Stored crypto claims older than CRYPTO_EPOCH (2026-07-26T00:00Z) purged on hydrate — pre-rewrite additive geometry fabricated targets; they feed no aggregate, in any record set. |
| Pooling | Crypto has its own asset-class bucket ("Crypto") for the small-n rescue — never pooled with equities. |
Cascade exhaustion casc DIRECTIONALCRYPTO ONLY
TriggerTwo-stage. (1) cascadeFlags: a 15-minute bucket where one side's forced-liquidation notional spiked ≥3σ above its own trailing 24h WHILE open interest fell in the same bucket — flow that actually cleared positioning. Data: aggregated CEX liqs via Coinalyze. (2) detectCascExhaust: cascade age between 1h and 24h; flush window = the cascade hour + the 2 following; void = the flush extreme (low for a long-side cascade, high for short-side), target = the pre-cascade close; mark must sit strictly between flush extreme and pre-cascade level (beyond the extreme = thesis dead, past the level = move made).
MeasureHorizon 12h. Unit R. Longs carried out → long the exhaustion; shorts → short it. Geometry is entirely observed prices — nothing for claimGeometryOk to clamp — which is why it's the flagship crypto event.
DisplayCard: "long-side cascade 4h ago — 12.3M force-liquidated in one 15m bucket, OI −2.1% with it; the flush low has held since". Intensity decays with age: max(10, 34 − ageH). Venue mismatch (CEX trigger, Hyperliquid resolution) disclosed on the card (cexsrc).
Stampscliq (liq notional), cdoi (bucket ΔOI%), cage (hours).
IsolationFire site wrapped in its own try — the deriv lane is an optional external feed and must never take the board down.
Funding extreme fundext FADECRYPTO ONLY
TriggerFunding at ≥90th or ≤10th percentile (FUNDEXT_HI / FUNDEXT_LO) of the name's own 31-day hourly distribution (≥96 samples via fundPctileNow — the same helper the AI crypto read uses). Persistence floor: the same sign must hold across the trailing FUNDEXT_HOURS = 24 with ≥ FUNDEXT_MIN_SAMPLES — the episode definition the retired version lacked (without it one extreme reported n=40 for a single observation).
MeasureHorizon 2d, unit R. Fade the crowd: crowded longs → short, crowded shorts → long.
PlaybookTarget = geometric mid of the 30d range (√(hi·lo) — scale-free, an observed structure; only shipped if it's actually on the fade side of px); void = 1.5σ beyond the extreme the crowd is defending. Watch: funding decaying back to median = crowd unwinding on its own; a percentile extreme that persists for days is the setup, one print is not.
DisplayCard: "funding +212% APR — 97th percentile of its own 31d, crowded long".
Shadow layer 1 — threshold variants (self-tuning)
Each gated event runs 2–3 candidate thresholds. Only the incumbent emits visible signals; every variant (incumbent included) silently ledgers shadow claims (vi-stamped) on identical bookkeeping, so incumbent-vs-challenger is apples-to-apples out-of-sample.
| Event | Param | Values | Incumbent (index 1 at boot) |
| bigmove | σ≥ | 1.5 · 2 · 2.5 | 2 |
| gap | gate σ≥ | 0.5 · 0.75 · 1 | 0.75 |
| squeeze | score≥ | 50 · 60 · 70 | 60 |
| fundflip | run≥ | 2 · 3 · 5 | 3 |
| unwind | score≥ | 50 · 60 · 70 | 60 |
| oiflush | σ≤− | 1.5 · 2 · 2.5 | 2 |
Promotion (shouldPromote, checked every build before signals): challenger promotes only on ≥30 out-of-sample resolutions on both sides with a real expectancy beat. Logged, reversible, persisted with the ledger; the Signals tab's tuning panel shows every variant's n / hit / avg and the last promotion. Bounded self-improvement — never free re-fitting.
Shadow layer 2 — whole-strategy shadows
Whole candidate strategies (vs. threshold tweaks) earning a record before any promotion. All open as vi=0 shadow claims: invisible to the visible record sets, drawer payloads and confluence; resolved on identical bookkeeping including the stop-aware track. Isolated per-row try/catch (the −79 outage: one market's string-typed closes threw inside shadow bookkeeping and blanked the whole board — three catch blocks now count into swingFails, logged once per build, never fatal). Detectors coerce string closes and fail closed on garbage. The ones in STRAT_DEFS render in the "strategy shadows (earning their record)" panel; wickfill and roundfr have no client surface at all until a deliberate promotion build adds one.
Universal gap fade gapfade SHADOWXYZ ONLY
TriggerEvery ≥1σ gap, faded toward the prior close regardless of the per-name fade/continue record — the roster-wide test of gap mean reversion.
MeasureResolves at the next session close, same calendar as gap. Unit %. Two void widths run side by side on identical entries: 1.0σ and 1.5σ of the market's own gap σ (both ledger via [1, 1.5].forEach; gw stamp records which).
DisplayShadows panel, split rows "void 1.0σ" / "void 1.5σ".
Breakdown reclaim reclaim LONGSHADOWXYZCRYPTO
TriggerPrior 30d closing low (window excludes the last 3 sessions) broken by at least one of the last 3 closes; the break is fresh (one of the last two closes still below); the mark has reclaimed above the level.
MeasureHorizon 5d (crypto 2d), unit R, stop-aware. Long the sprung trap: stop = the flush low; target = level + (level − flush), the measured move above.
Failed-breakout fade failbrk SHORTSHADOWXYZCRYPTO
TriggerExact mirror: prior 30d closing high broken by one of the last 3 closes, fresh, and the mark is back below the level.
Measure5d (crypto 2d), R. Stop = the flush high; target = level − (flush − level). Built to directly test finding F2 (breakout continuation ran negative expectancy on this tape).
MA50 pullback mapull LONGSHADOWXYZCRYPTO
TriggerRising 50d MA; price pulled back from ≥4% above it to a touch.
MeasureHorizon 10d (crypto 4d), R. Long at the MA; stop 1σ below it; target = the prior 30d closing high.
Post-earnings drift pead DIRECTIONALSHADOWXYZ ONLY
TriggerAn earnings reaction ≥1.5σ of the name's own daily vol; entered only after the reaction session's cash close, within 3 sessions of it, drifting WITH the move. The reaction is the study's own earnReactWindow (since build -122): the last cash close before the print → the first cash close after it — BMO/DMH the prior close → the print day's, AMC the print day's close → the next session's (Friday → Monday; holidays and 13:00 half days via usDayStatus). Exact 16:00/13:00 closes off the 5m archive and the hourly spine, counted only once a later row proves the bell bar closed; without them, session bars for BMO/DMH only (a daily-tier AMC window spans two sessions and never fires). TBD prints never fire. Fires before -122 used the print day's own UTC bar (or hourly +24h); ew marks the new trigger.
MeasureHorizon 10d, R. Stop = 1σ back through the reaction close against the drift; target = half the reaction magnitude further from the mark — drift scales with the surprise, mechanically. emv stamps the frozen reaction move %. Accrues at earnings-season pace.
Liquidity sweep (5m) sweep DIRECTIONALSHADOWXYZ ONLY
TriggerThe failed-break reclaim one timeframe down: a 5m wick pierces the prior COMPLETED session's high or low (dailyRaw[-2], never the in-progress bar), is rejected inside the bar, the reclaim holds, and the mark is back on the origin side — a stop-run that trapped the break and reversed. Reads the tail of the optional 5m sqlite archive (node:sqlite, range-queried, never resident); gated behind store.candlesEnabled.
MeasureHorizon 1d, R, stop-aware. Void = the sweep extreme; target = level + 1× the trap depth. Builds forward only — no lookback backfill.
Outsized-wick fill wickfill DIRECTIONALSHADOW · NO UIXYZCRYPTO
TriggerClosed daily spine bar (via bucketCandles, never the forming day) with a dominant wick ≥55% of range (WICK_FRAC) on a bar ≥1.1× normal size (WICK_SIZE_MULT). Refuses two-sided bars (no dominant wick = no unambiguous direction), short history, mark already past target or through void.
MeasureHorizon 3d (crypto 1d), R. Target = the wick midpoint (bodyHi + high)/2 (mirrored short); void = the bar's opposite extreme. wickPct measured.
Round-figure front-run roundfr FADESHADOW · NO UIXYZ ONLY
TriggerroundStep picks the dominant round-figure grid deterministically (step never >12% of price: 87→10, 432→50, 6.4→0.5 …). Fires when price has been advancing into a figure and sits inside the approach band 0.05–0.6σ from it (RNDF_LO_BAND / RNDF_HI_BAND), and the level is fresh — no close beyond the figure in the last 20 bars. Both sides (advance into 90 from below → short; decline into 80 from above → long). Declining toward a round above = no advance = no claim.
MeasureHorizon 2d, R. Fade into the figure: void = figure × (1 ± 0.25σ) — just through the round; target = 0.75σ retrace of the approach.
Analyst read airead DIRECTIONALACCOUNTABILITY BUCKET
TriggerEvery validated directional AI report (bias long/short) freezes a claim at generation: entry = mark at generation, void = the report's validator-corrected voidLevel, target from the report's target level, rm = the model that authored it. Neutral reads never ledger — "stand aside" contains nothing falsifiable. Gated by stopGeometryOk.
MeasureHorizon 5d, R, stop-aware. One open read per name (|airead#0): a same-bias regen never pseudo-replicates; a bias flip leaves the frozen claim to resolve untouched — moving a claim because the analyst changed its mind is what frozen geometry forbids.
Displayvi=0 keeps it out of the visible record sets and drawer; absent from STRAT_DEFS keeps it off the shadows panel. Surfaces only inside the Report tab itself: "analyst reads: X% across n" strip + this-name record. Report footer states "synthesis layer — not a ledger signal".
Alertai class — fires only when a regenerated report's action stance flips vs the previously cached one (aiFlipCheck). Structurally rare by design.
Record & audit displays (Signals tab)
| Panel | What it shows |
| Record strip | Per-event chips, record-first ordering (resolved desc → open → awaiting). cl = distinct tape-day count behind n, shown only when it diverges (claims clustered). Record sets keyed by move-threshold × prime × universe suffix (0x stocks, 0m crypto); the evidence blend and no-edge cap always read the FULL "0" set. |
| By event (honesty gap) | Claimed-vs-live per event type: live hit / med vs the study's claim, pf profit factor (catches the 55%-hit event that still loses money), stop-aware parallel column, confirmed / no live edge verdicts. |
| Calibration buckets | Resolved claims bucketed by fire-time score — if 55+ scores don't hit more than <35, the scorer is broken, not the events. |
| Slices | By side (longs vs shorts hit), form (last-N vs all-time), confluence (with-company vs solo hit + the earned bonus value). |
| Equity curve | Cumulative R, equal-weight, fired order; solid = at-horizon, dashed = stop-aware; hover shows the claim behind each step. |
| Tuning | Variant chips per event: ★ incumbent, each challenger's n/hit/avg, last promotion line. |
| Strategy shadows | STRAT_DEFS panel per universe (uni field decides listing — gapfade/pead/sweep never appear under crypto). n / hit / avg / avgS / open per strategy (and per void-width split for gapfade). |
| Ticker history | Universe-validated search → full claim-by-claim audit: what fired, at what score and mark, what it claimed, what happened, open claims counting down. Drawer "Signal record" is the compact version. |
Actionable board
One cross-scope list of names currently AT a swing trigger. The candidate set is the open ledger itself — the board re-derives no geometry; it reads, carry-nets, and merges.
| Gate | Value | Why |
| Horizon floor | ACT_MIN_HZ = 3d equity · ACT_MIN_HZ_MAIN = 2d crypto | Swing board — 1d scalps belong to the Signals tab. Gate reads EV_META horizons, so future events enroll automatically. |
| Freshness | ACT_MAX_BARS = 10 bars in trigger | Then the setup is gone stale. |
| Record (CONFIRMED) | n ≥ 8 AND avg > 0 AND evR > 0 AND !noedge | avg is retrospective (did the family pay); evR is this entry, now, net of carry and lateness — both required, not the same test. Failures are DROPPED, not demoted: no list of things-not-to-do presented as things-to-do. |
| Geometry | net R/R ≥ ACT_MIN_RR = 2.0 · ≤ ACT_MAX_RR = 20 · void ≥ ACT_MIN_VOID_PCT = 0.05% (plus σ-relative check when sd0 exists) | R/R is repriced from the live mark every build — this floor doubles as the board's kill switch: a chased setup dies here on its own, no second lateness rule. >20 means the void is a rounding error; degenerate voids counted under coverage.degenerate. |
Columns render from payload only: Fired (mark frozen at claim open — what the record was scored on) · Now (live) · Late (gap in the setup's own risk unit) · void/target frozen · R/R net of expected funding over the horizon · rec (n, hit, avgR) · Ago. Coverage block explains every rejection reason so an empty board can always say why. trigScan runs at the end of every board build — detection on the poller's clock, whether or not a tab is open.
Trigger stream — the canonical event log
The poller owns one sequenced, persisted event stream (emitTrig → trigSeq, trigSeen, ring). Every transport (browser toast, bell panel, Telegram) is a thin consumer with its own eligibility filter — eligibility never gates the stream itself, so a channel's thresholds can't censor the record of what fired.
| Property | Mechanism |
| Cursor semantics | GET /api/triggers?since=seq → events = everything above your cursor (what to interrupt for); recent = cursor-independent display list (the bell panel is a window onto the server ring, not a second copy). no-store. |
| Announce once | Keys are per-claim AND per-fire (coin|side|ev|t0) with the frozen t0 — rebuilds never re-announce, and a restart with the persisted announced-set + ledger re-fires nothing. This property is what lets a push channel survive a deploy. |
| Boot grace | 5-min startup grace (PUSH_GRACE_MS) — the ring may hold events nobody was listening for; the first post-boot build's board is seeded, not blasted. |
| First-run device | Badge starts clean at the current seq — forty retained events are history, not forty unread items. Read state is a persisted watermark, never a hand count. |
| Ownership | rule events are owner-scoped — your rules never appear in someone else's bell log. Market/server events are shared. |
Alert classes
| Class | Fires when | Scan / cadence | Anti-noise discipline |
| setup | A claim newly reaches the Actionable CONFIRMED board. | trigScan on every board build | Per-channel eligibility via trigEligible: minEV · minRR (reads gross) · maxLate (unknown lateness ≠ late) · R/R-class filter (rr level-triggered vs ev σ-built — different trades, not different quality) · per-name mute · sides · noEarnings. Event carries both marks + geometry so a transport composes without re-reading the board. |
| ledger | An announced claim's void taken, target reached, or horizon resolved. | levelScan on LVL_SCAN_MS off the live mark | One class, not three — the message says which. Stop retires target in the same breath (als=1 → alt=1): a stopped claim can never later announce "target reached". |
| rule | A user-authored metric rule crosses its threshold. | ruleScan every 15s vs the snapshot payload | Edge-triggered with an arm band + per-rule cooldown (default 30m). Evaluated against the exact bytes the client renders — an alert can never disagree with the number on the board. Owner-scoped. |
| trend | Trend-board state changes (new stacks / retests). | trendScan every 5m; primed at 6m post-boot | One full pass seeds state before anything fires — no announcing every 4/4 stack on deploy. |
| filing | Material EDGAR filing on a roster name. | rides the EDGAR rotation (~2 names/min, full pass ~40m) | Push forms only: 8-K · 8-K/A · 10-K · 10-Q · SC 13D(/A) · 6-K · 425 (DEF 14A / S-3 stay in the news tab; ownership forms 3/4/5/144/13G never alert). Must be ≤6h fresh; 45-min priming seeds the 7-day backlog silently; seen-set capped at 600, persisted. |
| earnings | A name with an OPEN, ANNOUNCED claim reports today or tomorrow. | earnScan hourly | Scoped HARD — 84 names in season is a calendar, not an alert. One per ticker per report date. |
| ai | A regenerated report's action stance flips vs the cached one. | emitted from the generation path | Structurally rare: reports regenerate on cooldown, usually by hand, and only a changed mind is worth an interruption. |
| regime | Net crowding |≥35| across percentile-carrying names, or aggregate OI stretch ≥2σ of its own 60d. | regimeScan every 15m | Persistent-condition trap: ledger-style re-arm — one episode, one alert, re-arms only when the condition genuinely lapses. |
| coverage | A data source goes stale / degraded. | coverageScan every 10m | Same re-arm discipline. |
| ops | Server telling on itself: deploys, poller stall, degraded feeds. | pushHealthTick every 60s + emit sites | Admin-only, enforced in delivery AND the in-app feed. Deploy notice is quiet by construction (enters the ring, never reaches a phone — Railway redeploys per pushed file); the wire proves itself via the stall watchdog + test-fire instead. |
Defaults: recipients with no selection get a frozen set of the four classes whose rates are known; every class added later is opt-in, shown in the panel with its measured fires/day. Clearing a selection resets to defaults — never to "everything", never to silence.
Level scan — claim life-cycle alerts
Runs unconditionally (armed outside the Telegram token gate — detection is never a transport's hostage). Walks open, announced claims against the live mark + latest bar:
| Event | Condition | Stamps |
| Void taken | levelHit(side, "stop", e.stp, px, bar) and not already announced | als=1 AND alt=1 — target retired with the stop, so a later run to the (now dead) target can never announce the most misleading message this class could send |
| Target reached | levelHit(side, "target", e.tgt, …), claim not stopped | alt=1; target is the frozen tgt, never reconstructed from mv |
| Horizon resolved | resolver | log entry; only the void hit gets a browser toast — target/horizon are for the log |
User metric rules
| Constant | Value | Meaning |
RULE_MAX | 60 | Bounded shared list — past this it's a screener, not an alert |
RULE_DEFAULT_COOLDOWN | 30 min | Per rule×coin |
RULE_STATE_MAX | 4000 | Bounded edge state (armed / lastFire / prev-value maps) |
| Cadence | 15s | Chained to the snapshot cadence — rules judge the number the board is showing, latency traded for consistency (level alerts run off the live mark instead) |
Edge state persists — a redeploy re-announces nothing already in breach. Ownerless legacy rules are admin-managed, never silently adopted.
Telegram delivery (push transport)
Second consumer of the same stream. Everything deciding what a message says lives as pure functions in compute.js (pushEligible/pushFmt, delegating to trigEligible — Telegram and the browser can never announce different things); poller.js holds only the wire. Fully dormant without TG_BOT_TOKEN: no timers, no writes, no noise — unset the var and the transport doesn't exist.
| Mechanism | Detail |
| Linking | Panel mints a single-use code (PUSH_LINK_TTL 10 min) carrying the minting browser's owner handle → human sends /start CODE to the bot → chat bound to that browser in one unforgeable step. /stop always works (control of the Telegram account outranks any cookie); /status//help report state. Inbound via getUpdates poll every 20s — no webhook, no public URL needed. |
| Outbox | Bounded (PUSH_QUEUE_MAX 50 — overflow drops oldest, counts, and the next delivered message discloses it), drained every 1s, paced 1 msg / 3s (PUSH_SEND_GAP). First eligible item, not first item — a digest scheduled for the end of a quiet window can't head-of-line-block an urgent alert. 15s abort on every request (one hung connection would otherwise stall the whole sequential lane). |
| Per-recipient cap | PUSH_CAP_HOUR 20/h — held, not dropped; disclosure protects you from believing silence means nothing fired. force bypasses for command replies (a /stop confirmation is not an alert). |
| Failure handling | 429 → backoff for their retry_after; 403 (blocked/deactivated) handled; up to PUSH_MAX_TRIES 5; last 40 outcomes in the panel log (PUSH_LOG_RING); Telegram's error description surfaced verbatim — the difference between "alerts don't work" and "the token is wrong". |
| Per-recipient prefs | Class selection (empty = default set), per-name/side/threshold trig filters, mute, quiet hours (from/to + tz — scheduling, not subscription), daily digest hour (digestTick every 5m). Chat ids masked in the panel (…1234). Bell panel shows YOUR recipients only; admin manages everyone in the Admin tab. |
Browser transport
| Piece | Behavior |
| Pull | Chained to the snapshot cadence + a 5-min safety net (the standalone 60s timer is gone). The trig.on toggle gates firing, never the feed pull — history keeps flowing for people who turned toasts off. |
| Toasts | Bottom-left (terminal owns bottom-right, drawer owns the right edge). Setup toast carries fire mark, geometry, per-name mute and a Report deep-link. Ledger class: only the void hit toasts (+ desktop Notification if granted); targets/horizons go to the log. |
| Bell panel | A window onto the server's persisted ring — survives refreshes and closed laptops. One shared formatter for toast + panel; unread = persisted watermark. |
| Config | Defaults OFF. Free numeric inputs for minEV / minRR / maxLate (the fixed three-option selects are gone); no "proven-only" toggle — the stream is confirmed-only upstream. |
Claim-field glossary (ledger export)
| Field | Meaning |
| ev | event type |
| vi | shadow-variant index (absent = the real, visible claim; 0 = strategy shadow / airead) |
| t0 / tR | fire time / resolve time (ms) |
| mark0 | mark at fire |
| dir | EVENT direction sign (a gap-fader's dir is the gap, not the trade) |
| psd | published play side — outcome sign follows this when present |
| sd0 | 30d daily σ at fire (R normalization); an R-united event without sd0 is a legacy %-outcome entry |
| stp / mv / tgt | void level frozen at fire · playbook-target distance % · absolute target level frozen at fire |
| alo / als / alt | opening announced · void announced as taken · target announced as reached |
| score0 / pr / conf | score at fire · prime flag at fire · confluence flag at fire |
| bt / eg / tal | opened on first post-boot build · episode-gap flag · daily ribbon aligned with the side at fire |
| realized / realizedS / stopped | at-horizon outcome (play-signed, event unit) · stop-aware outcome · void touched before horizon |
| fnd / fndP | funding rate at fire · percentile vs own 31d hourly (≥96 samples) |
| oi5 / rngP / mktR | 5d OI change % · position in 30d range (0=low, 1=high) · benchmark 24h move % (BTC / SPX proxy) |
| ses / hod / dow | session bucket (xyz: rth/on/wknd) · UTC hour-of-day bucket (crypto: asia/eu/us/late) · UTC day-of-week (0=Sun) |
| gw / emv / ew / rm | gapfade void width (1.0/1.5σ) · pead frozen reaction move % · pead reaction price tier (cash/daily; absent = pre-122 trigger) · airead authoring model |
Tuning index — every knob in one table
| Knob | Value | File | What moving it does |
| Variant thresholds | see table | poller.js VARIANTS | Fire rates of the 6 tuned events. Prefer letting promotion move the incumbent — edits reset the apples-to-apples record. |
| fpdiv gate | ~0.8σ z7 | poller.js fire site | Divergence sensitivity (embedded in the intensity formula) |
| prem gate | 2σ of 7d baseline | poller.js fire site | Dislocation sensitivity |
| volume gate | 2.5× 30d norm | poller.js fire site | Context-flag frequency |
| coil gate | bottom decile of 120 | compute.js | Compression flag frequency |
| ondrift gates | |z|≥2 vs universe, |drift|≥1%, ≥25 mkts | poller.js | Drift-claim frequency |
| fundext | pctile 90/10 · 24h persistence · min samples | poller.js FUNDEXT_* | Crowding fade frequency + episode definition |
| casc | 3σ liq spike · age 1–24h · flush window 3h | compute.js detectCascExhaust / cascadeFlags | Cascade claim frequency + staleness window |
| Shadow detectors | WICK_FRAC .55 · WICK_SIZE_MULT 1.1 · RNDF_LO/HI_BAND .05/.6 · pead 1.5σ/3 sessions · mapull 4% · reclaim/failbrk 30d-window-minus-3, fresh-within-2 | compute.js | Shadow fire rates — cheap to tune since nothing visible changes |
| Evidence | own n≥8 · pooled n≥12 @0.7 · blend prior 25 · blend floor 5 resolved · no-edge (10, 50%, 0) cap 8 · scales 0.5R / 0.8% | poller.js evPts / evidence | How fast trust migrates study→live and how hard losers sink |
| Prime | hit≥60% · avg>0 · R/R≥1.2 · +6 | poller.js | ★ frequency |
| Confluence | default +8 · earned at 15/15 per side | poller.js | Stacking bonus |
| Decay | ×0.6 past horizon · drop at 2× | poller.js | Card staleness |
| Universe lanes | 40 / 40 | compute.js capPerUniverse | Payload split |
| Promotion | n≥30 both sides + expectancy beat | compute.js shouldPromote | Self-tuning strictness |
| Actionable | hz 3d/2d · bars 10 · RR 2.0–20 · void .05% · rec n 8 | poller.js ACT_* | Board strictness / kill switch |
| Rules | 60 max · 30m cooldown · 4000 state · 15s | poller.js RULE_* | Rule lane bounds |
| Push wire | link 10m · gap 3s · cap 20/h · queue 50 · grace 5m · poll 20s · drain 1s · tries 5 · log 40 · abort 15s | poller.js PUSH_* | Delivery pacing / bounds |
| Scan cadences | health 60s · levels LVL_SCAN_MS · rules 15s · earnings 1h · regime 15m · trend 5m (prime 6m) · coverage 10m · digest 5m · filings prime 45m, ≤6h fresh, seen 600 | poller.js boot block | Alert latency per class |
| Regime gates | crowd ±35 · OI z 2 | poller.js REGIME_* | Regime-alert rarity |
| Ledger epochs | CRYPTO_EPOCH 2026-07-26Z · chain gap ≤2d | poller.js | Which history counts |
Reference document only — describes the shipped behavior; the code and its test pins remain the source of truth. Change a threshold → the manifest tests in test/test.js that pin it must move with it, or the suite fails by design.