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:

AxisMeaningWhere it lives
TriggerThe mechanical condition that fires it, with exact thresholds.compute.js detectors / poller.js fire sites
MeasurementHorizon, sign convention, unit, how the claim resolves, what the study measured.EV_META / EV_META_MAIN, resolver in poller.js
DisplayWhat the card / board / drawer renders, and from which payload field.app.js (never re-derived client-side)
AlertWhich 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)

RuleDetail
Expectancy-centeredavg ≤ 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 sourceOwn 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 blendOnce 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 capliveNoEdge: ≥10 resolved, hit < 50%, med ≤ 0 → evidence hard-capped at 8, no live edge badge. Sits on top of the blend.
Earnings guardReport 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

ModifierGateEffect
★ primehit ≥ 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
Decayclaim age > horizonscore × 0.6, amber state, prime off; age > 2× horizon → dropped
Confluencemultiple same-side conditions on one namedefault +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
⇄ Conflictlong and short events both live on one nameflagged, no bonus for anyone; each claim resolves on its own record
Universe lanescapPerUniverse(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

MechanismHow it works
Frozen geometryAt 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-armIf 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.
ResolutionresolveLedger 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.
Unitsprem → 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 gatesstopGeometryOk / 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 stampEvery 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 dedupResolved 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 cryptoReason
gap · gapfade · ondrift · pead · sweepStructurally impossible on a 24/7 tape — no session boundary, no earnings, no "prior session" object.
premNo analogue — main-dex oracle premium is arbitraged tight.
squeeze · unwindGeometry not yet trustworthy: composite fires away from the range edge its levels assume. Stays out until the equity record validates the trigger.
Crypto overridesDetail
Compressed horizonsEV_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 geometryAll σ / range constructions via logLevel / logExtend (logGeo) — no negative prices. Equity keeps additive math byte-for-byte.
Epoch purgeStored 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.
PoolingCrypto 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.

EventParamValuesIncumbent (index 1 at boot)
bigmoveσ≥1.5 · 2 · 2.52
gapgate σ≥0.5 · 0.75 · 10.75
squeezescore≥50 · 60 · 7060
fundfliprun≥2 · 3 · 53
unwindscore≥50 · 60 · 7060
oiflushσ≤−1.5 · 2 · 2.52

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)

PanelWhat it shows
Record stripPer-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 bucketsResolved claims bucketed by fire-time score — if 55+ scores don't hit more than <35, the scorer is broken, not the events.
SlicesBy side (longs vs shorts hit), form (last-N vs all-time), confluence (with-company vs solo hit + the earned bonus value).
Equity curveCumulative R, equal-weight, fired order; solid = at-horizon, dashed = stop-aware; hover shows the claim behind each step.
TuningVariant chips per event: ★ incumbent, each challenger's n/hit/avg, last promotion line.
Strategy shadowsSTRAT_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 historyUniverse-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.

GateValueWhy
Horizon floorACT_MIN_HZ = 3d equity · ACT_MIN_HZ_MAIN = 2d cryptoSwing board — 1d scalps belong to the Signals tab. Gate reads EV_META horizons, so future events enroll automatically.
FreshnessACT_MAX_BARS = 10 bars in triggerThen the setup is gone stale.
Record (CONFIRMED)n ≥ 8 AND avg > 0 AND evR > 0 AND !noedgeavg 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.
Geometrynet 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.

PropertyMechanism
Cursor semanticsGET /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 onceKeys 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 grace5-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 deviceBadge 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.
Ownershiprule events are owner-scoped — your rules never appear in someone else's bell log. Market/server events are shared.

Alert classes

ClassFires whenScan / cadenceAnti-noise discipline
setupA claim newly reaches the Actionable CONFIRMED board.trigScan on every board buildPer-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.
ledgerAn announced claim's void taken, target reached, or horizon resolved.levelScan on LVL_SCAN_MS off the live markOne 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".
ruleA user-authored metric rule crosses its threshold.ruleScan every 15s vs the snapshot payloadEdge-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.
trendTrend-board state changes (new stacks / retests).trendScan every 5m; primed at 6m post-bootOne full pass seeds state before anything fires — no announcing every 4/4 stack on deploy.
filingMaterial 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.
earningsA name with an OPEN, ANNOUNCED claim reports today or tomorrow.earnScan hourlyScoped HARD — 84 names in season is a calendar, not an alert. One per ticker per report date.
aiA regenerated report's action stance flips vs the cached one.emitted from the generation pathStructurally rare: reports regenerate on cooldown, usually by hand, and only a changed mind is worth an interruption.
regimeNet crowding |≥35| across percentile-carrying names, or aggregate OI stretch ≥2σ of its own 60d.regimeScan every 15mPersistent-condition trap: ledger-style re-arm — one episode, one alert, re-arms only when the condition genuinely lapses.
coverageA data source goes stale / degraded.coverageScan every 10mSame re-arm discipline.
opsServer telling on itself: deploys, poller stall, degraded feeds.pushHealthTick every 60s + emit sitesAdmin-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:

EventConditionStamps
Void takenlevelHit(side, "stop", e.stp, px, bar) and not already announcedals=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 reachedlevelHit(side, "target", e.tgt, …), claim not stoppedalt=1; target is the frozen tgt, never reconstructed from mv
Horizon resolvedresolverlog entry; only the void hit gets a browser toast — target/horizon are for the log

User metric rules

ConstantValueMeaning
RULE_MAX60Bounded shared list — past this it's a screener, not an alert
RULE_DEFAULT_COOLDOWN30 minPer rule×coin
RULE_STATE_MAX4000Bounded edge state (armed / lastFire / prev-value maps)
Cadence15sChained 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.

MechanismDetail
LinkingPanel 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.
OutboxBounded (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 capPUSH_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 handling429 → 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 prefsClass 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

PieceBehavior
PullChained 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.
ToastsBottom-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 panelA window onto the server's persisted ring — survives refreshes and closed laptops. One shared formatter for toast + panel; unread = persisted watermark.
ConfigDefaults 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)

FieldMeaning
evevent type
vishadow-variant index (absent = the real, visible claim; 0 = strategy shadow / airead)
t0 / tRfire time / resolve time (ms)
mark0mark at fire
dirEVENT direction sign (a gap-fader's dir is the gap, not the trade)
psdpublished play side — outcome sign follows this when present
sd030d daily σ at fire (R normalization); an R-united event without sd0 is a legacy %-outcome entry
stp / mv / tgtvoid level frozen at fire · playbook-target distance % · absolute target level frozen at fire
alo / als / altopening announced · void announced as taken · target announced as reached
score0 / pr / confscore at fire · prime flag at fire · confluence flag at fire
bt / eg / talopened on first post-boot build · episode-gap flag · daily ribbon aligned with the side at fire
realized / realizedS / stoppedat-horizon outcome (play-signed, event unit) · stop-aware outcome · void touched before horizon
fnd / fndPfunding rate at fire · percentile vs own 31d hourly (≥96 samples)
oi5 / rngP / mktR5d OI change % · position in 30d range (0=low, 1=high) · benchmark 24h move % (BTC / SPX proxy)
ses / hod / dowsession bucket (xyz: rth/on/wknd) · UTC hour-of-day bucket (crypto: asia/eu/us/late) · UTC day-of-week (0=Sun)
gw / emv / ew / rmgapfade 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

KnobValueFileWhat moving it does
Variant thresholdssee tablepoller.js VARIANTSFire rates of the 6 tuned events. Prefer letting promotion move the incumbent — edits reset the apples-to-apples record.
fpdiv gate~0.8σ z7poller.js fire siteDivergence sensitivity (embedded in the intensity formula)
prem gate2σ of 7d baselinepoller.js fire siteDislocation sensitivity
volume gate2.5× 30d normpoller.js fire siteContext-flag frequency
coil gatebottom decile of 120compute.jsCompression flag frequency
ondrift gates|z|≥2 vs universe, |drift|≥1%, ≥25 mktspoller.jsDrift-claim frequency
fundextpctile 90/10 · 24h persistence · min samplespoller.js FUNDEXT_*Crowding fade frequency + episode definition
casc3σ liq spike · age 1–24h · flush window 3hcompute.js detectCascExhaust / cascadeFlagsCascade claim frequency + staleness window
Shadow detectorsWICK_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-2compute.jsShadow fire rates — cheap to tune since nothing visible changes
Evidenceown 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 / evidenceHow fast trust migrates study→live and how hard losers sink
Primehit≥60% · avg>0 · R/R≥1.2 · +6poller.js★ frequency
Confluencedefault +8 · earned at 15/15 per sidepoller.jsStacking bonus
Decay×0.6 past horizon · drop at 2×poller.jsCard staleness
Universe lanes40 / 40compute.js capPerUniversePayload split
Promotionn≥30 both sides + expectancy beatcompute.js shouldPromoteSelf-tuning strictness
Actionablehz 3d/2d · bars 10 · RR 2.0–20 · void .05% · rec n 8poller.js ACT_*Board strictness / kill switch
Rules60 max · 30m cooldown · 4000 state · 15spoller.js RULE_*Rule lane bounds
Push wirelink 10m · gap 3s · cap 20/h · queue 50 · grace 5m · poll 20s · drain 1s · tries 5 · log 40 · abort 15spoller.js PUSH_*Delivery pacing / bounds
Scan cadenceshealth 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 600poller.js boot blockAlert latency per class
Regime gatescrowd ±35 · OI z 2poller.js REGIME_*Regime-alert rarity
Ledger epochsCRYPTO_EPOCH 2026-07-26Z · chain gap ≤2dpoller.jsWhich 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.

↑ top