Milst Screener — the complete manual
Everything the terminal shows, how each number is computed, what every control does, how the alerts and the messaging reach you, and how the server behind it is run. Every number in the app also explains itself on hover; this page is the map. Nothing here is investment advice.
Sections about tabs you cannot see on this deployment are not on this page. Press / to search; the contents list follows you as you scroll.
New here? Start with the plain-language explainer — what every screen is for and what the numbers say — then how to use this site, which walks every feature: what it is worth, how to use it properly, and the habits that make it pay. Come back here for the exact definition.
1. What this is
Milst Screener is a monitor for the HIP-3 perpetual markets on Hyperliquid — the xyz dex, where synthetic perps track US and foreign equities, indices, FX, commodities and baskets — plus a second, walled-off universe of the main-dex crypto perps (BTC, ETH and the top names by open interest).
The design has one central idea: the heavy work happens once, on the server. A small Node process polls Hyperliquid, fetches and archives candles, samples open interest, computes every feature (momentum, volatility, reference prices, funding history, session studies, signals, sector aggregates) and caches the result. Browsers read a single pre-computed snapshot. Nobody's browser talks to Hyperliquid, so the per-IP rate limit is never a per-user problem and a cold start takes seconds rather than minutes.
How data reaches your screen
- Snapshot — the market table is rebuilt every ~15 seconds and served whole from
/api/snapshot. The status line's updated field is the server's last successful poll, not your last fetch. - Push stream — while the live stream (Server-Sent Events) is healthy the status line reads push live and the browser pulls the moment the server's data changes. The stream carries versions, never payloads: it tells the browser what changed, the browser fetches it.
- Fallback poll — if the stream drops, the browser polls at the cadence in the refresh selector (15s to 15m, default 30s). This setting is idle while the stream is up.
- WebSocket universe feed — server-side, the universe subscribes to Hyperliquid's real-time asset contexts for price / funding / OI at zero rate-limit cost; REST drops to a slow reconciliation poll while the socket is healthy.
- Persistence — open interest, funding history, the candle archive, the signal ledger, notes, messages and accounts live on a mounted volume and survive restarts and redeploys. A redeploy serves a warm table within a poll or two.
Two universes, one wall
The Stocks | Crypto switch in the nav is a hard wall: separate universes, separate benchmark (the S&P 500 perp for stocks, BTC for crypto), separate histories, never mixed in one list. Session concepts (gaps, overnight holds, cash sessions) do not exist on a 24/7 crypto tape and vanish in that scope. Crypto keeps 31 days of open-interest and funding history and a 90-day hourly spine; its daily bars match the equity side (build 2026.10.16-131): a year in memory and on the wire, read from the candle archive, which holds every closed day back to each coin's listing — so long moving averages, year-to-date, Drawdown, the daily correlation lookbacks and the backtest fill in for crypto too, and only a coin younger than the window shows a dash. The one deliberate exception: BTC may join a stock set in comp and ratio comparisons, aligned on the hours both legs traded.
Honesty rules that run through every tab
- A dash is a dash. Missing history, an unqualified baseline, a retired data series — all render as an explicit blank, never a guess, an interpolation or a zero.
- One code path. A level, a score or a record is computed once on the server and restated by every surface. The board, the drawer, the alerts and the Telegram wire cannot disagree because none of them recomputes.
- Frozen claims. Every signal is ledgered at the mark it fired at, with its void and target frozen, and scored out of sample. The record is the part to trust; the card is the part to read.
- Stamped prose. A note or a message that names a ticker carries the price it was written at, so every later read carries the move since.
2. Getting in — accounts, invites, sessions
The terminal has per-person accounts. An operator mints a single-use invite link from Admin › Access; opening it validates the code, moves it into a short-lived HttpOnly cookie and redirects to a bare /join, so the code leaves the address bar before anything renders and never lands in browser history or a Referer header. You pick a handle and a password; the invite is burned inside one database transaction, so two people opening the same link cannot both get in — the second sees "already used".
Sign in
/login — handle and password. Sessions are signed tokens carrying your account id and a per-user epoch; they last SESSION_DAYS (30 by default). A ⎋ button in the nav signs this browser out (/logout).
Forgot your password
/reset — enter your handle and a six-digit code is sent to your linked Telegram (the quiet window and the hourly cap are bypassed). The code lasts 10 minutes, works once, dies after five wrong guesses, and at most three are sent per hour per account. Every outcome — unknown handle, no Telegram, throttled — returns the same answer, so the page is not a directory of who has an account. No Telegram linked? Ask the operator for a reset link instead.
Sign out everywhere / disable
Resetting a password or an operator disabling an account bumps that account's epoch, which invalidates every session it holds without touching anybody else. Under the old shared password, removing one person meant rotating the password for everyone; that failure is what accounts replaced.
Passkeys
(Build 2026.10.16-131.) Sign in with your fingerprint, face or device PIN instead of a password: Sign in with a passkey on /login needs no handle (the passkey is discoverable, and the device must verify you). Manage yours from the ⚿ button beside sign-out — up to 5, each with a name, when it was added and last used. Adding or removing one, or changing the requirement below, first asks you to confirm it's you (your password, or one of your passkeys), good for 5 minutes on that browser, so a stolen session cannot plant its own passkey. Require a passkey on your own account and a correct password alone no longer signs in: the password is followed by a passkey prompt, and only then is a session issued. Removing a passkey, or turning the requirement on, signs you out everywhere else — like a password change, the browser you did it from stays signed in (and keeps its 5-minute check); every other session, including one somebody else might hold, ends. Operator-only while it soaks (the passkeys switch in Admin › Features); the sign-in door itself is always open.
Lost your passkey — the recovery path
A password reset (the Telegram code or an operator's reset link) changes the password only: on an account that requires a passkey it still ends at the passkey prompt, so a reset can never be used to slip past the requirement. If you have lost every passkey, an operator uses clear passkeys on your row in Admin › Access: all of them are removed, your own requirement is lifted, every device you are signed in on is signed out, and the act is written to the audit log with the operator's name. Then sign in with your password and register a new passkey. The operator's own way back in is the break-glass ADMIN_PASSWORD login, which is not an account and is never asked for a passkey — or another operator: nobody can clear their own passkeys. The operator powers that could hand an account to whoever holds a stolen session — clear passkeys, switching the operator passkey requirement off, and a reset link for yourself or for an account a passkey guards — ask the operator to confirm it's you first (password or passkey, inline in Admin › Access; 5 minutes on that browser).
Legacy shared password
A deployment that still has SITE_PASSWORD set accepts it as a shared door; a shared-password session that arrives after accounts exist lands on /claim to pick a handle. Your alerts, rules and notes carry over automatically — they were already keyed to your browser's signed handle. Once everyone has claimed, the operator sets LEGACY_SHARED_PASSWORD=0 and the shared door is closed for good — the login form's shared password and HTTP Basic alike.
Roles
- Member — sees the public feature set (whatever the operator has opened to the group) plus every personal surface: watchlist, layouts, alerts, notes you can read, messages, positions.
- Operator (admin) — everything, plus the Admin tab: accounts and invites, feature visibility, nav menus, alert recipients, the message read-through and its audit log, lane maintenance. The first account is bootstrapped with
ADMIN_PASSWORD, which stays as a break-glass login for when an operator locks themselves out. A member can be promoted with make operator in Admin › Access. Operators must use a passkey (Admin › Access › Passkeys, off by default) makes every operator account's password sign-in end at a passkey prompt; it is refused while any operator has no passkey, says so when you promote a member who has none, and never applies to the break-glass login.
Scripts and curl
Scripts sign in like a browser. POST /login with {"handle","password"} and keep the session cookie (curl -c jar -b jar …); that works on every deployment. HTTP Basic with the shared password (curl -u friend:PASSWORD …/api/snapshot) is part of the legacy shared door, and it gets a script in only while that door is open and no account exists yet: once any account is on file, Basic authenticates without identifying anyone, so every /api route answers it 401 {error:"claim-account"}; with LEGACY_SHARED_PASSWORD=0 (or no SITE_PASSWORD) the header is ignored and the caller is simply signed out. The boot log says which of these applies. On a deployment without SITE_PASSWORD the public read routes need no credential at all. /api/health stays open without any credential — it is the platform healthcheck. Eight wrong passwords from one IP lock that IP out for 15 minutes; Basic attempts count against the same lock.
USAGE_PUBLIC=0 forces it off); signed-in members are counted as described under Your usage instead. A one-line notice on the page says so and can be dismissed for the tab.3. The shell — one row of chrome, one line of meta
Header
One row: the mark, the navigation, the Stocks | Crypto scope pill and the session buttons (back, home, ?, sign out). The dot beside the mark is the connection state: green = live, amber = the server's data has gone stale (its poller stalled — the snapshot carries the last successful poll time), red = the browser cannot reach the server.
Meta line
Under the header, one mono line holds every fact about the state of the machine, left to right:
| Field | Meaning |
|---|---|
| markets · vol · OI | Totals for the active universe: the number of markets, 24h volume and open interest. |
| benchmark | The name every "vs" column is measured against in this scope: the S&P 500 perp for stocks, BTC for crypto. |
| next | Countdown to the next fallback pull. Reads push live while the stream is up. |
| sync | Server-side backfill progress: syncing X/Y while candle history is being fetched for new listings, then live. |
| updated | Age of the server's last successful poll — the number that goes amber when the poller stalls. |
| build | The server build stamp (also in /api/health). If it does not match the latest delivery, the deploy is stale. |
| freshness tray | One dot per external feed — Data (Hyperliquid poll), Earn (Finnhub calendar), News, Filings (SEC EDGAR) — colour-graded by age against each feed's own cadence; red also means the fetch is failing. The Loop dot is the server event-loop p99 (green <20ms, amber <50ms, red ≥50ms). Hover for the exact age. |
| ◎ focus chip | The focused ticker follows you across tabs: pick a name and every tab that has a per-ticker view opens on it. Click the chip to reopen the drawer, ✕ to clear. |
| 🔔 bell | Alerts — setups, ledger events, rules and ops notices, with an unread count. It lives on the meta line so it is reachable from every tab. See Alerts. |
Navigation ribbon
- Scope pill — Stocks | Crypto, at the right of the row. Remembered per browser. Tabs that have no meaning in a scope hide when you flip to it; Markets, Trend and Report follow the switch, Signals and Actionable have per-universe visibility the operator sets separately.
- Tabs and menus — related tabs are grouped into drop-down menus (by default Tape, Signals, Macro, Research; the operator can rename a menu or move a tab in Admin › Feature visibility without a deploy). Drag a tab to reorder it; the order is saved in this browser. Some tabs show a live count badge.
- ? — the per-tab explainer: how to read the tab you are on, plus the keyboard table. Each explainer links to the matching section of this manual.
- ⎋ — sign out.
Strips under the nav
- Macro banner — appears on every tab when the next universe-wide macro event (FOMC, CPI, payrolls, PPI, retail sales, GDP, PCE) is ≤2 ET days out, and flips to a blue result strip once the print is out.
- Regime strip — the first line of the Markets tab, not site chrome: the stocks board's regime read (breadth, dispersion, positioning, 30d correlation), or in crypto scope the tape strip: crowd pays (OI-weighted funding APR), breadth, total OI with its weighted delta, BTC's day and the alt-season gauge.
Always-on controls
- ▸_ Ask (bottom right, or ~) — the terminal: a query console over the board.
- Chat dock (bottom left) — unread messages, on every tab, once you are signed in.
- Toasts (bottom left) — new setups and void hits, if you have enabled trigger alerts in the bell.
5. Markets tab
One row per market, one column per lens. The table is the home screen; everything else in the app is a different cut of the same rows.
Controls
| Control | What it does |
|---|---|
| Filter ticker… | Narrows the table to matching tickers and names. Not part of a layout. |
| group | stocks · sectors · industries — the group lens (below). Industries are equities-only; crypto's curated sectors are already its fine grouping. |
| weight | How members average into a group cell: by volume (the tape's own weighting) or equal. Volume and OI are straight sums either way. |
| window | 1h · 4h · 1d · 7d · 30d — re-anchors every windowed column at once: vs S&P, vs tape, ΔOI, Squeeze, Carry, Avg Range, RVOL all answer "over this window". |
| refresh | Fallback-poll cadence, used only when the push stream is down. |
| Refresh | Pull now. |
| Filters | 24h volume and OI ranges (millions by default; suffixes 500k, 2.5b override), the Positions wallet link, and the toggles ⬡ held (names the linked wallet holds), ★ only (watchlist), ◢ noted (names you have written on), ☾ dim (fade foreign-home names while their exchange is closed). A dot on the button means a filter is active; the footer counts rows shown. |
| Layouts | Save and switch named views: column set and order, sort, window, vol/OI filters, ★-only. The active layout shows a • when the live view has unsaved changes. Synced to your account (see watchlist and layouts); the active layout is per browser on purpose, so a phone can run its own. |
| Columns | Show, hide and drag-reorder columns. Hidden-by-default columns (5m, 15m, H/4h/12h open, VWAP, DownCap, Hit%, MAs…) live here. |
| ↓ CSV | Download whichever lens is on screen, with the current filters and sort. |
Click any header to sort (a second click flips direction). Cell shading scales with the size of the move — a wall of deep red 7d cells is the market-breadth read. ★ beside a ticker pins it to the watchlist and to the top of the table.
Ticker cell badges
| Mark | Meaning |
|---|---|
| ★ / ☆ | Watchlist. Synced across your devices when signed in. |
| E solid / hollow | Reports earnings today / tomorrow (ET days, because BMO/AMC are ET concepts). Hover for the session and the EPS estimate. After the print it becomes a scoreboard: beat/miss and the live day move. |
| ◢ post-it | You have a note on this name: solid accent within 7 days of the newest note, dimmed to 30 days, a hollow outline after. Shows a count past one. Hover for the first line and the move since it was written; click to open the drawer. Age is calendar time only — the marker never moves because the market moved. |
| ⬡ | Your linked wallet holds a position here, in the side's colour. |
| Amber rail (left edge) · microline | Foreign-home name whose exchange is open right now; the microline counts down to the next open/close. |
| ◆ (Casc) | Crypto: a liquidation cascade in the last 24h; red = longs liquidated, green = shorts. |
Every column
Hover any cell for its exact inputs. "Window" means the column follows the window selector.
| Column | Definition |
|---|---|
| Ticker | Display ticker; hover for the market's full name and coin id. |
| Sess | Home market of the reference line under the perp — KR (KRX), JP (TSE), HK (HKEX), CN (SSE) for foreign listings with no US symbol; US for everything else. Lit dot = that exchange is open now, server-computed against its real holiday calendar. US·TW-style chips on ADRs mean a US-listed line (ET machinery applies) with the home line as context. Stocks scope only. |
| Price | Live mark, streamed. Flashes on change. |
| Funding (APR) | Hourly funding × 24 × 365. Positive = longs pay (red, crowded long); negative = longs receive (green). The ▴/▾ flag with a number = today's funding sits at the 90th+/10th− percentile of this market's own 31-day hourly distribution — the crowd is paying near its monthly max, the classic mean-reversion zone. |
| Fund next (est.) | (Build 2026.10.16-131; hidden by default; operator-only while it soaks — key fundnext.) The funding rate paid at the top of the hour, estimated, as APR, labelled est. in the cell. Hyperliquid averages the premium over the hour (sampled every 5 seconds) and pays m × (P̄ + clamp(I − P̄, ±c)) / 8, with each HIP-3 deployer setting the multiplier m, the interest rate I and the clamp c per asset (xyz today: m = 0.5 on almost every name, I = 0.01% per 8h except 0 on the FX names, c = 0.03% — the HIP-3 default, not the main dex's 0.05%). The Funding column is that formula on the hour so far — what Hyperliquid itself publishes as the predicted rate. This column completes the hour: the part already observed at its average (recovered exactly from the feed's own figure), the minutes still to come at the current premium (a 5-minute EWMA of the feed's samples). Early in the hour the two differ most; by :55 they converge. Validation before shipping (build 2026.10.16-131, one recorded hour, 115 xyz markets sampled every 30 s, 12,420 market-moments, scored against the realized rate): mean absolute error 0.143bp/h for this estimate, 0.149bp/h for the feed’s own figure, 0.437bp/h for “the last hour repeats”, against a mean |realized| of 0.558bp/h — informative against persistence (3× better), a modest gain on the feed overall (23% in the last 20 minutes of the hour, none around :20–:30). One hour is a thin sample; the live check below keeps scoring it. Hover a cell for the source, the minutes observed, the 7-day mean and the deployer's settings. A live check scores the estimate hourly against realized funding (see /api/fundnext); if it ever stops beating "the last hour repeats", the column goes blank rather than show a number that is not informative. |
| Prem | Perp vs oracle dislocation in basis points, (mark − oracle) / oracle. When the cash market is closed the oracle sits near the last print, so a persistent premium or discount is the live off-hours price discovery — the tradeable dislocation. |
| 5m · 15m | Live mark vs the mark ~5/15 minutes ago, sampled server-side every 15s from the same streaming mark the Price column shows. Memory-only: dashes for the first minutes after a deploy or across a feed gap. Hidden by default. |
| 1h · 4h | Rolling window changes. |
| 24h | Rolling 24-hour change vs Hyperliquid's price ~24h ago — at 3pm it measures against yesterday 3pm, not a day boundary. |
| D open · H open · 4h open · 12h open | Change since the current UTC day / hour / 4h bucket / 12h bucket opened — what the forming candle shows on a chart. Perps trade continuously, so a day's open is the prior day's close. Resets at the boundary by construction. Hover for the exact open. The three intraday ones are hidden by default. |
| 7d · 30d | Rolling window changes. |
| Gap | The last close→open move, measured on the name's home session: 16:00→09:30 ET for US names, the KRX/TSE/HKEX boundary for foreign-home names (SMSN gaps on Seoul's clock). Live while the market is closed. Hover for the cumulative ~30d off-hours drift — a persistent sign there is the overnight effect. |
| 30d trend | Sparkline of the 30-day price path; sorts by 30-day change. |
| vs S&P | Excess return over the benchmark perp across the window (this market % − benchmark %). BTC is the benchmark in crypto scope. |
| vs tape | This market's window return minus the universe median — relative strength against the whole tape rather than a benchmark, so a BTC-green-while-alts-bleed day is measured correctly. On a red tape, sort descending. |
| Δ vs ⬒ | The vs basket column: window return minus the return of a basket you pick in the header ▾. (Build 2026.10.16-131) a built-in or saved basket is priced by the server off the same snapshot (/api/baskets/live: the weighted mean of its members' own window returns, weights as the basket is defined) — the number a basket alert reads; a basket kept only in your browser uses the equal-weight mean of its members on the board. Relative strength against the basket you chose; β and every signal still measure against the S&P / BTC. Dash when fewer than 60% of members carry the field. |
| DownCap 31d | Down-capture vs the tape: on the last 31 days' 4h bars where the whole scope was red (≥70% of names down, negative median), how much of the tape's move this name ate. <100 dumps less than the typical name, negative = net green on red bars, >100 amplifies. Fixed 31d/4h, does not follow the window. Dash below 20 matched bars. |
| Hit% | On those same red-tape bars, the share where this name beat the median — the consistency check on DownCap. Good DownCap with low Hit% means a couple of lucky bars carried the average. |
| RVOL | Relative volume, clock-hour matched: notional over the window (1h/4h/1d) ÷ the median notional of the same clock hours across the prior month. 1.0× = normal for this time of day, ≥2× = genuinely elevated. Overnight hours are judged against prior overnights, so the session shape cannot masquerade as a volume signal. Dash on 7d/30d. |
| β | Beta to the benchmark perp on 90 days of daily returns. >1 amplifies it, 0–1 dampens, <0 moves inverse. |
| Momentum | Self-normalizing −100…+100: risk-adjusted 1h–30d returns × 7d trend quality, tilted by range position, modulated by OI conviction. Comparable across a quiet FX pair and a violent pre-IPO synthetic. ● = volume above the market's own norm. |
| MOM+ | The candidate momentum running head-to-head with the incumbent: same core, but the OI term is regime-qualified (OI building amplifies only when funding corroborates the side; falling OI is short covering and dampens at half strength) and a funding-crowding haircut (×0.8) applies at the crowd's monthly extreme. Adjudicated on daily forward rank IC in the Backtest tab's Score duel; promotion only on a locked verdict, and the loser is deleted. |
| Vol (ann) | Annualized realized volatility from ~30 days of hourly returns. |
| Avg Range | Mean of each completed day's (high − low) / close over the window (7d, or 30d on the 30d window). "On a typical day this moves X%." |
| vs 30d hi | Distance below the 30-day high; 0% = at the high now. |
| Swing R | Structural room for the next swing trade per unit of risk: distance to the nearest unified-level-map target in the trend direction ÷ distance to the D1 EMA21 void. The map merges detected structure, volume-profile HVN/LVN and the daily EMA50/200, confluence-weighted with disclosed hand-set weights. Needs a stacked trend side and a live EMA21; dash otherwise. |
| vs YTD hi | Distance below the year's highest daily high (the close on a bar without one). Names listed this year use their full-life high (crypto included since build 2026.10.16-131). While a name's daily history is still warming up from its hourly series (a window, not its life), this column and Y open show a dash rather than that window's high or first close. |
| Y open · M open | The level the year / month opened at (= the prior day's close on a continuous perp); green when price is above, red below; hover for the distance. |
| ΔOI | Open-interest change over the window with a price-vs-OI regime tag: longs+ price↑/OI↑ (new money confirming) · squeeze price↑/OI↓ (shorts covering, not fresh demand) · shorts+ price↓/OI↑ (new money pressing lower) · unwind price↓/OI↓ (longs deleveraging). Hover for conviction and whether funding corroborates. OI is symmetric: long/short attribution is always an inference. |
| Squeeze | 0–100, how loaded the short-squeeze spring is over the window: crowding (shorts paying via negative window-average funding) × fuel (OI building) × trigger (price pressing the 30d high). Zero whenever funding is positive. |
| Casc | Crypto only: ◆ with age when a 15-minute bucket in the last 24h printed one side's forced-liquidation notional ≥3σ above its trailing 24h baseline with OI dropping ≥1% in the same bucket. Aggregated CEX data via Coinalyze — context, not Hyperliquid-native. |
| 24h Liqs | Crypto only: total forced liquidations over 24h (Coinalyze). Hover for the long/short split. |
| Carry | Window-average funding APR ÷ annualized realized vol: how much you are paid per unit of risk for taking the unpopular side. Same sign convention as Funding. |
| 24h Vol · OI | Notional traded over 24h; open interest. Both in USD. |
| Position | Your open position from the linked wallet: side, notional at the live mark and the move since entry signed with the side. Hidden until a wallet is linked. |
| MA 20 · 50 · 100 · 200 | Simple moving averages of daily closes; green when price is above. Crypto fills all four since build 2026.10.16-131 (a year of daily bars); a dash means the name is younger than the average. |
| VWAP 30d · vs VWAP | Σ(typical price × volume) ÷ Σ(volume) over ~31 days of hourly candles, typical = (H+L+C)/3 — the average recent holder's entry, weighted by where volume printed. Positive vs VWAP = the month's buyers are in profit; deeply negative = trapped supply overhead. A candle-level approximation of tick VWAP. |
| OI/Vol | Open interest ÷ 24h volume. ≥2 = stale, crowded positioning that cannot exit quickly (fragile to squeezes and unwinds); <0.5 = fresh churn. |
Group lens — sectors and industries
The group toggle re-renders the same board with one row per sector (GICS plus asset classes) or industry (the finer curated split — Semis vs Software vs Memory, Banks vs Capital Markets). Each cell is the weighted average of exactly the rows the names view shows, computed only over members that have the value (a missing window is excluded and the weights renormalize, never zero-filled); hover discloses coverage. Best · Worst (pinned to 24h) is the aggregate-liar detector — whether the group number is everyone or one name dragging the rest — read against Cohesion (90-day average internal correlation: high = the label trades as one block, low = a stock-picker's bucket). Click a group row to drill in: the table flips back to its members with a chip to clear. Industry rows marked = sector have no curated split yet.
Action strip — heating · cooling · strongest bid
The collapsible strip under the table is the rate-of-change read next to the table's levels, and it always describes exactly the rows the table shows (scope, window, filters, drill and group lens all apply).
- accel = the window's natural fast leg (1h→15m, 4h→1h, 1d→4h, 7d→1d, 30d→7d) minus the window's own pace, both relative to the tape. heat = accel + 0.6·tanh(ΔOI/8) + 0.4·clamp(RVOL−1): flow can confirm an acceleration, never replace it; RVOL is dropped at 7d/30d, not faked.
- HEATING = positive accel with flow agreeing — what is starting. COOLING = names still ahead of the tape whose pace has died — with a high MOM this is the take-profit read. "Never went anywhere" makes no list at all.
- STRONGEST BID ranks the server's dip-reclaim claims off the 5-minute archive (equities, last 4h): the deepest peak→trough dip, the fraction reclaimed, minutes since the low, ranked dip × reclaimed ÷ √minutes. Demand response, not momentum.
- M on every chip is the momentum level: high/high = ride, high/stalling = exit, low/accelerating = new rotation. In a group lens the strip regroups (share-of-tape replaces RVOL as the volume confirm; the bid column stays name-level because dips don't aggregate).
The 1d movers row above the table lists the day's biggest moves as chips; click one to open the name.
Depth column and Rewind (build 2026.09.30-127)
Depth ±1% (hidden by default, in the column menu): resting size within 1% of mid on the thinner side of the live book — what a round trip can count on without walking the book. The server reads two books every 15 seconds, busiest markets first, so a value can be up to about half an hour old; hover for its age. ⏱ Rewind opens the board as it stood at any past moment the archive covers (captured every 15 minutes, about three weeks): price then and the move since, and the funding, open interest, volume and premium as they were. The operator can switch it off (rewind).
(Build 2026.10.15-130.) Typing a date in Rewind waits until you pause before it asks the archive, and [ ] / { } step the board 15 minutes / a day.
Since you left: type since 4h (or 1d, 30m, a date) — or ask "what did I miss" — in the terminal for your watchlist's moves, the tape's top movers, what the engine fired, calls that closed and earnings that reported meanwhile. It no longer opens a panel on its own when you come back.
Crypto scope
Adds the tape strip (crowd pays, breadth, total OI and its weighted delta, BTC's day, alt-season gauge), the Casc and 24h Liqs columns, and BTC as the benchmark. Session columns (Gap, Sess) vanish. Crypto claims and features run on compressed horizons throughout the app.
6. The ticker drawer
Click a ticker (or press Enter on a highlighted row, or pick one in the command palette) and the drawer slides in from the right. It sets the focused ticker, so Trend, Report, Charts and the rest open on the same name. Top to bottom:
- Header
- Name, price, day move, sector / industry, home session, the earnings badge, the AI report → deep link, and the Notes panel toggle.
- Notes
- Your written notes on this name, above the charts: everything else in the drawer is the server's read, the note is yours. Each note is stamped with the mark it was written at ("wrote it at 113.90 · +4.0% since").
#tagsin the body filter the Notes tab. Editing rewrites the body and nothing else; the original timestamp and price stand, and the rewrite is disclosed. Writing needs the operator's notes.write permission. - Position
- If your linked wallet holds this market: side, size, entry, leverage, liquidation price, margin, funding paid since open, and unrealized P&L / ROE derived off the live mark — beside the trend board's read of the name.
- Where the 30d return happened
- The 30-day return decomposed into what accrued off-hours (overnight + weekend close→open) vs during cash sessions. A persistent off-hours drift is the overnight-effect edge this panel exists to expose. Anchored to the home session for foreign-home names.
- Since your last look
- (Build 2026.10.15-130.) Under the header, when you last opened this name's drawer between 10 minutes and 30 days ago (remembered per browser, for the 200 names you opened most recently): its move since then, and how many trigger events fired on it and calls on it closed — click the chip for the list.
- Metrics
- The table's columns for this name, in one grid, including the hidden ones.
- Why it moved
- (Build 2026.09.30-127.) The move over 1h, 4h, 24h or 7d split three ways that always add up: market = the name's 90-day beta × its benchmark's move (the S&P perp, or BTC); sector = the average move of its sector peers beyond their own market share; its own = the rest. An accounting, not a model. Under it, the headlines, filing or earnings print that landed in the window. If a window cannot be read, the panel says so (with a Retry) instead of keeping the previous window's numbers.
- Cost of holding (est.)
- (Build 2026.10.16-131; where
fundnextis open to you.) Projected funding on a notional you choose (default $10,000, remembered per browser) over the next hour, day and week, long and short — pay in red, get in green. The next hour is the Fund next estimate; after it the hourly rate reverts to the name's 7-day mean at φ per hour, a pooled AR(1) coefficient the server fits on its own funding history (0.84 until the fit has enough consecutive hours: a half-life of about four hours). On 30 days of recorded xyz funding (24 markets) that reversion read the next day better than "today's rate held flat" (MAE 2.9bp of notional vs 3.7bp) and matched the 7-day mean at a week (18.6bp vs 18.9bp), where today's rate held for 168 hours did worse than assuming zero. - Depth & slippage
- (Build 2026.09.30-127.) The live Hyperliquid order book, read at most every 15 seconds: the spread, the resting size within ±0.5 / 1 / 2% of mid on each side, and what a market order of $10K, $50K, $250K (or your own size) would pay on average against mid. A value marked + means the published 20 levels ran out first.
- On other dexes
- (Build 2026.10.16-131; xyz names, where
crossdexis open to you.) The same underlying listed by another HIP-3 deployer — see Cross-dex: its mark, the mark and oracle spread against this market in bp, both legs' funding and the differential, its open interest, and the carry read after fees. Opening the drawer reads both order books (this one and up to four others): spread, depth within ±1%, and the average fill for $10K and $50K each side — cached 20 seconds server-side and drawn from the same on-demand book budget as the depth panel above, so a busy minute shows the last read, flagged. A same-name listing whose oracle is more than 10% away (a different contract size) is named and not compared. - Event risk
- The next scheduled earnings print and any macro event inside the coming days, plus the name's earnings reaction study: number of prints, average and median |cash-close → cash-close move| (with a bootstrap 90% interval from 4 prints), up/down split, cash-session gap behaviour (with its intraday coverage), and the move as a multiple of the name's usual daily range.
- 90-day price
- Sparkline; then Top co-movers and Top hedges — inverse over 90 days of daily returns.
- Hourly candles
- OHLCV chart with a timeframe switcher (1h–4h aggregations, 1–60 days), crosshair readout, and the levels the signal engine froze on any open claim. (Build -132) While the drawer is open and the page visible, the forming hour moves with the market: every 20 seconds it re-reads the hour, refined by the live 5-minute candle socket. That bar is provisional — drawn lighter with a dashed outline, marked live edge in the header, and its readout says live — unconfirmed; the exchange's record replaces it within minutes. Nothing that computes (signals, levels, alerts, the ledger) ever reads it.
- OI / funding history
- Open interest and funding APR sparklines over the retained history, with the current hourly funding stated.
- Fundamentals
- Finnhub basic financials and profile for US listings: point-in-time, quarterly-updated, refreshed daily server-side. Market cap, P/E and P/S are derived live off this board's mark.
- Derivs context
- Crypto: aggregated CEX liquidations and open interest for the coin (Coinalyze, 15-minute buckets aggregated hourly) — market context for the Hyperliquid name, not Hyperliquid-native, and labelled as such.
- Insiders · recent EMA touches
- (Build 2026.10.15-130.) Where the Insiders tab is on: the name's open-market buys and sales over 90 days and, when three or more different insiders bought within 10 days, a cluster chip with the dates. Where EMA Touch is on: the name's last few 50/200 EMA touch cards (4H and 1D) straight from the tab's feed — the feed keeps a card about a day on 4H and five sessions on 1D, so this is recent history, not all of it.
- News
- The last 72 hours of verified headlines tied to this name — the News tab's payload, filtered.
- Signal record
- The out-of-sample record of every claim the engine ever fired on this name: hit = share of resolved claims that went the way the signal implied. The claim-by-claim history lives behind the ticker search on the Signals tab. Signal accuracy — live track unfolds the per-event split.
7. The tabs
Each tab is one question asked of the same rows. Tabs marked operator-only by default ship closed to the group until the operator opens them in Admin › Feature visibility; a closed tab is absent from the ribbon and its routes refuse, not hidden. Where a tab has a per-universe split, the scope pills decide which universe you are reading.
Focus — today's six most tradeable equities
Six seats, stamped once at the 09:30 ET cash open, one late fill at +1h, then immutable for the day — a compressed morning scan, not a signal. It never reshuffles at 10:47 because a number ticked; yesterday's list stays viewable exactly as stamped.
- How seats are earned — a disclosed loudness ordering: |gap σ| + RVOL excess + |ΔOI| + a flat earnings boost + a small headline count, every unit in the name's own distribution. Max 2 seats per industry cluster, so six seats are six trades rather than one theme wearing six tickers. The cut line discloses what just missed and why. Foreign-home names are excluded (their gap anchors to the wrong exchange for an ET stamp).
- Liquidity floors — two walls the operator sets in Admin (a 24h notional minimum and an OI minimum), applied before loudness is compared: a seat you cannot exit at your size is not a seat. Names that fail a wall get their own BELOW FLOOR roster with the wall they failed and how far it would have to fall. The floors in force at 09:30 are frozen onto the record.
- The +1h columns — sVWAP, 1H HI, 1H LO fill once at 10:30 ET from the 5-minute archive and freeze; the 1H RANGE bar shows where the open sat in the hour's eventual range vs the 10:30 print — "who won the first hour".
- The chart (▦) — 5m/15m/1h/4h candles over 72h from the local 5m archive, off-session time shaded, the frozen levels drawn verbatim, the session VWAP labelled.
Funds — the 13F watchlist
Tracked institutional filers with filing badges, per-fund books (holdings, quarter-over-quarter delta, concentration) and the once-per-quarter season summary. 13F snapshots are quarter-end positions filed up to 45 days late: positioning history, never the current book. Long US-listed equity and listed options only. Curating the watchlist (whale add|rm|mute) is an operator verb; reading it is the tab. The terminal's fund, etf and holds verbs read the same lane.
(Build 2026.10.15-130.) The alert bell on each fund is the operator's mute switch and only the operator sees it. The list refreshes every minute while the tab is open and visible; the season summary is re-read only when the current season changed (a new quarter, or a rebuild after a late filer or an amendment), so an older quarter you opened stays put.
Trend — the EMA 13/21 ladder
An EMA 13/21 ribbon evaluated on D1 · H12 · H4 · H1 for every market in the active scope, ranked into long and short leaderboards (top 10 per side, ≥2/4 alignment). Per rung: green ▲ = price > EMA13 > EMA21 (stacked), yellow ◆ = the middle state, red ▼ = stacked the other way — each state also has its own shape (build 2026.10.15-130), so the ladder reads without colour. The score counts aligned rungs — 4/4 is an established trend on every rung; 2–3/4 means higher timeframes lead and you wait for the lower ones to agree.
- RETEST fires when the last few bars probed into the 13/21 zone on a trending rung while the close held the EMA21 side — the continuation pullback (long) or rally-into-resistance (short). The badge carries a clock-matched volume multiple when the baseline qualifies: ~1× or less is a quiet, healthy pullback; ≥2× means the level is being fought. A retest is promoted verbatim into the signal ledger as
tretest/tretestdn, with entry = mark, void = the rung's EMA21 and target = that rung's prior swing. - Width — average EMA13–EMA21 spread across aligned rungs, in percent: two 4/4s are not the same trade when one holds a 0.08% ribbon and the other a 2% ribbon. Age counts consecutive D1 days stacked this side (N+ = extends past available history). Δ21 is the live distance from the H1 EMA21 — the proximity-to-entry number.
- Ranking — by score, then fresh-first: a day-3 trend is the entry, a day-40 trend is the chase. Ticker badges carry the funding-percentile flag (crypto) or the earnings badge (stocks).
- Chart button — a candlestick chart of any rung with the ribbon, the retest zone banded at the ladder's own levels and a crosshair OHLC + EMA readout. Every annotation is the board's own payload restated. Bars inside the EMA seed window render dimmed with no ribbon.
H1 is the hourly spine; H4/H12 are UTC-aligned aggregations of it; D1 is the daily series with the live mark driving the forming bar. EMAs need 26+ bars per rung; a market missing a rung is excluded and counted in the header.
Charts — multi-pane grid and MTF wall
Up to 8 names side by side (GRID), or one name across timeframes (MTF WALL: 4, 6 or 8 panes at 15M/1H/4H/1D up to 5M…1D). Timeframes 5M · 15M · 1H read the local 5-minute archive; 4H · 12H · 1D read the deep daily archive seeded back to each listing's birth — the two sources are disclosed per pane and never mixed in one. Link shares crosshair and zoom across panes (always on in the wall). Picks are per universe and remembered per browser.
The live edge (build -132). The 5M · 15M · 1H panes refresh their right edge every 20 seconds while the tab is visible (nothing is fetched while it is hidden; an unchanged edge costs a 304). The archive only holds a bar once Hyperliquid's REST record of it has been pulled, a few minutes after it closes; until then the forming bar, and any just-closed bar not pulled yet, come from the 5-minute candle socket and are provisional: drawn lighter with a dashed outline, the readout says live — unconfirmed; the exchange's record replaces it within minutes, and the coverage line says dashed = live, unconfirmed. A 15M or 1H bar that holds a provisional 5-minute bar is provisional too. When the REST bar lands it replaces the provisional one — about 1 bar in 100 differs slightly (Hyperliquid can skip pushing a bar's last trade at the rollover). These bars are display only: signals, levels, the sweep detector, the weekly challenge, Focus, alerts and the ledger read the archive alone. With WS_CANDLES=0 the panes show the archive only.
Treemap
The universe by sector: tile area = the size measure you pick (24h volume or open interest), colour = the move over the selected window. Click a tile to open the name. Installs itself into the Tape menu at boot.
Sectors — rotation, leadership, cohesion
- Flow map (default) — each bubble is a sector (or industry). Horizontal = capital direction, a blend of return and OI conviction (right = money flowing in with conviction). Vertical = activity heat from volume and volatility. Top-right = accumulation, top-left = distribution, bottom half = quiet. Bubble size = 24h volume. Click a bubble for its members.
- Leaders map — positioning vs the benchmark over the window (right = beating it) against whether the lead is growing (up) or shrinking. Four regimes: Leaders, Catching up (early rotation candidates), Cooling (where leadership goes to die), Laggards. Intraday windows floor to 7d.
- Rotation board — sectors ranked by capital direction, with Cohesion (average pairwise correlation inside the sector: high = own the theme, any name; low = a stock-picker's sector).
- Sector × sector correlation — 90-day average pairwise daily-return correlation between members of each pair. Deep co-movement means one risk factor wearing two names.
- Grouping — sector vs curated industry (equities only). Industries may deliberately cross GICS sectors when that is how the tape trades; thin marks groups under 5 members, = sector marks names with no split. Display grouping only — signal pooling and news badges stay on the 11 GICS sectors.
Window, weighting and CSV export follow the same conventions as Markets. A weekly classification audit (AI-assisted, operator-reviewed) proposes overlay changes to the curated tables; applied overlays are revertable from Admin.
Gaps — the opening gap the perp is pricing, and how much of it has usually held
- Implied — while the US cash market is shut the perp is the only live price for an xyz stock; its distance from the perp's own price at the last cash close is the gap the market is pricing. US-session names in the main table; foreign-home listings (KRX, TSE, HKEX, SSE) get their own table below it (build 2026.10.15-130), each measured on its exchange's clock — see Home-market listings.
- Capture — the server replays each name's last 180 nights (holidays and early closes respected): the perp at the close, at a checkpoint (20:00, 04:00 or 09:00 ET), and at 10:00 ET. Capture is the least-squares slope, through the origin, of the 10:00 move on the checkpoint's implied move — 1.00 means the pre-open perp has been an unbiased read, 0.60 that 40% of it has usually faded by 10:00. The column uses the latest checkpoint already passed tonight. Held is how often the 10:00 print kept the implied direction (implied ≥ 0.2%). Fewer than 20 usable nights is a dash. The small range beside the slope (build 2026.10.15-130) is its 90% confidence interval: a heteroskedasticity-robust (HC1) standard error of the through-origin slope, Student t on n−1 nights — a capture of 0.80 whose interval runs 0.45…1.15 is not yet telling you much.
- Expected by 10:00 — capture × implied, with a 90% band that carries both the fit's residuals and the slope's own uncertainty (build 2026.10.15-130): ± t · √(σ² + implied² · se(b)²), so a big gap on a thin fit gets the wide band it deserves. A base rate, not a forecast: the night a name has news is the night its history describes least.
- During the session — the board shows how this morning went: implied at 09:00, where the name stood at 10:00, and now; held, extended or reversed.
- Home-market listings (build 2026.10.15-130) — names whose stock trades in Seoul, Tokyo, Hong Kong or Shanghai, labelled with their exchange, on that exchange's calendar (its holidays; a year outside the curated holiday table is flagged ≈cal — weekends only). The anchor is the home close — the first hourly print at or after the bell, since KRX and TSE close at 15:30 and the spine prints on the hour — the checkpoints are close + 4h, 04:00 local and the last whole hour before the open, and the read is the first hour after the home open (10:00 local). While a home exchange is open its rows show how its morning went.
- Both ends are the perp's own prices (hourly spine); the stock's own opening auction is not in this data.
EMA Touch — the 50 and 200 EMA on 4H and 1D, touching now or about to
- The line — an SMA-seeded EMA over closed candles, carried to the live mark: what a chart draws while the candle forms. 1D is the session series on a US name (weekends and exchange holidays fold into the next session), calendar days on crypto. A line needs 66 closed candles for the 50 and 216 for the 200; below that the name is silent on that line.
- Near a line — every line within 1.5σ of the mark, nearest first. σ is that rung's own bar-return stdev over the last 90 candles (the same unit the EMA200 alerts use), so 0.5σ is equally close on every name; the % is beside it. stacked = the 50 and the 200 sit within half a sigma of each other. ▼ = the gap is closing. Click a row for the drawer.
- Touch cards — a touch (the forming candle's high–low range reaches the line) opens a card: Testing support / Testing resistance. At that candle's close it resolves in place: Support held / Resistance held (closed back on the side it came from) or Broke down / Reclaimed (closed through). Every card carries one action line — what it means and the close that would invalidate it.
- Decay — cards age in candles of their own timeframe. Fresh for 3 bars (4H) or 2 sessions (1D) after the touch: the actionable window. Fading (dimmed) after that, until 6 bars / 5 sessions, then gone. ✓ worked: price went at least 1σ from the line the way the card expects. ✕ failed: a later close went back through the line — the card fades at once. since is price's distance from the line now, in σ, in the expected direction.
- Reading the feed — the big number on the left of every card is how long ago it touched (12m, 3h 20m, 1d 6h), with the clock time under it; NEW marks touches since your last visit to the tab. (Kept per universe since build 2026.10.15-130: a stocks visit no longer marks the crypto cards seen.) Cards are grouped into Touching now (candle still open, soonest close first), Fresh and Fading, newest first inside each; each card's right edge says how much life it has left ("fresh for 2 bars more", "leaves in 1 session"). View: newest first drops the groups; Show: live + fresh hides the rest.
- Cooldown — at most one card per line per candle, and after a card the line must re-arm: a close at least 1σ clear of it. Until then later touches fold into the same card as retouches, counted once per candle.
- Alerts — four classes, each chosen separately in the bell's delivery panel:
ma200andma50(close-confirmed reclaim, breakdown, bullish and bearish retests) andtouch200/touch50(intrabar touches, opt-in). ma50 and the touch classes are operator-only while the tab soaks. Every line seeds silently for the first ~6 minutes after a deploy.
Scanned server-side once a minute; the tab refreshes every 30 seconds while open. Follows the Stocks|Crypto switcher.
Drawdown — return since a date against the drawdown it cost
- The chart — one dot per market. Vertical = the return since the anchor (the live mark over the first daily close on or after it; switch to best for the highest close it printed). Horizontal = the max drawdown in the same window (deepest close-to-close fall from any running peak), zero at the right edge. Up and to the right is better: more return for less pain.
- References — dashed horizontal lines at the benchmarks' own return: BTC (gold) and ETH (violet) in crypto scope, the S&P and the XYZ100 index in stocks scope. Their dots wear the colour; everything else is blue.
- Anchor — presets count back from today's UTC midnight (YTD is January 1); the date box takes any day in the last year. A name whose first close is well after the anchor is drawn with a dashed ring and marked late: it starts at its own first close.
- Hover a dot for the anchor close, now, best and its date, the drawdown with its peak → trough dates, and how much the name gave back; click it, or a table row, for the drawer.
- Show top N — the chart draws the top 10, 20, 30 or 50 names by the return axis, or everyone; the references always stay. The table is never cut.
- Table — the whole universe, 25 rows a page: now, best and its date, max drawdown with its dates, gave back (best minus now, in points) and Best / DD (above 1 the name made more than it ever gave back). Every column sorts; CSV exports the lot.
- Under water (build 2026.10.15-130) — for the max drawdown, the days from its peak until price was back at that peak, or until now (marked open) while it still is not. Sortable; in the CSV.
- Labels — auto names everyone up to 45 dots, then only the standouts on either axis, the best ratios and the references; all and none override.
Closes only — the daily feed carries no intraday low, so a wick below the close is not counted. Follows the Stocks|Crypto switcher; crypto ships about a year of daily bars like the equity side (build 2026.10.16-131 — it was about 90 days), so only a coin listed after the anchor starts late, and the header says how many.
Correlation — matrix, pairs, COMP/G, ratios, baskets
- Matrix — pairwise correlation of daily log returns over the chosen lookback (30d–1y for stocks; for crypto the 4h/1d/7d intraday grids and, since build 2026.10.16-131, 30d/90d/1y daily lookbacks on calendar days — the same daily path, overlap floor, hatching, pair view and COMP/G as the stocks side), cluster-ordered (UPGMA on correlation distance) so blocks along the diagonal are real co-movement families. The number of overlapping days is in every hover — a ±0.9 on 12 days is noise. Focus search isolates tickers; top by vol widens or tightens the universe (20/40/60).
- Colours and significance (build 2026.10.15-130) — blue = co-moving, orange = inverse (a colour-blind-safe pair); a cell whose 95% interval spans 0 is hatched, not just faded. Window returns everywhere on the tab (hover, pair lists, the pair view, COMP/G's default anchor) start from the close before the window, like the Drawdown tab, so the first day's move counts.
- Δ 30−90 (build 2026.10.15-130) — the toggle after the lookbacks swaps the matrix for the CHANGE in correlation, r(30d) − r(90d) per pair, on the same diverging scale centred on 0 (blank where either window lacks the overlap), ordered by the 90d clusters; the pairs whose correlation rose and fell most; and the average pairwise correlation over a trailing 30 sessions for the last 60 days — names moving together more (dispersion collapsing) or less. Stocks scope; a lookback click leaves it.
- Pair view (click a cell) — hedge β (units of B per unit of A), the beta-adjusted spread ln(A) − β·ln(B) with its mean ±1σ band and z-score (beyond ±1.5–2, A is rich or cheap vs B), and the rolling correlation that says whether the relationship is stable enough to trust.
- Co-movers & hedges (click a ticker label) — proxies and contagion map on one side, natural hedges on the other. Strongest pairs below surfaces the tightest relationships across the set.
- COMP/G — N names rebased to 100 at a chosen anchor (presets, date picker, or drag the amber line), or spread mode (each name minus the equal-weight basket of visible names, in percentage points). Runs on closes already loaded, so it never adds a fetch.
- Ratio — synthetic pair candles A/B at 1h · 4h · 12h · 1d with an honest EMA200 (drawn only where 200 bars exist). BTC may face a stock, aligned on the hours both traded.
- Baskets operator-only by default — custom baskets (
basket create MAG7 AAPL MSFT …; in the manager,NVDA:30 AMD:20 AVGO:50weights a saved basket — equal weight otherwise) usable as virtual tickers in COMP/G and ratios, and (build 2026.10.16-131) as a benchmark wherever you pick one: the Δ vs ⬒ column, ⬒ rows on the Markets board (Filters → ⬒ rows: each basket's level and window returns above the names, marked INDEX — not tradable, never in a screen, a count or breadth), a basket alert (a rule on its level or 1h / 4h / 1d / 7d / 30d return) and the Backtest's single-asset benchmark. The index is daily-rebalanced at the basket's weights; a member listed later joins on its first close, a delisted member leaves after its last, and the manager says who and when. A member missing a day's close is held at its last close for that day and its move counted once when it reports again, so the index's return over any span matches the members' own returns over it. Never signal math: the S&P / BTC stay the benchmark under every β, signal and the ladder.
Funding — the heatmap
A row per market, a column per time bucket, readable at 1h, 8h or 24h, in one of two units:
- Annualized (default) — a cell is the bucket's mean hourly rate × 24 × 365, the convention every other funding readout uses. The timeframe is a resolution: one market reads one number on every grid, and all three share one colour cap (the 8h grid's 98th percentile) so a zoom never repaints a cell.
- Per bucket — a cell is what a 1× long paid over that bucket (the hourly spine summed), so the timeframe changes the quantity: the same market reads ~8× larger per 8h than per 1h, and each timeframe has its own colour cap. Remembered per browser; every tooltip carries both.
Carry pairs (build 2026.09.30-127), under the heatmap: among the top 40 markets by open interest in the universe you are in, pairs that moved together (ρ ≥ 0.6 on 60 daily returns) whose funding pays a hedged holder — long the leg being paid, short h dollars of the other per dollar long (h the regression hedge ratio, so the pair is roughly direction-neutral). Carry is −funding(long) + h × funding(short) per year over both legs' notional, on the 7-day average and at this hour's rate; the spread z-score says where the hedged pair sits against its own 60 days (|z| ≥ 2 = already stretched). Since build 2026.10.15-130: stock pairs are measured on trading sessions (a weekend folds into Monday), not calendar days; beside the 7-day carry sits the same carry off 30-day average funding with a word for how they compare — stable (the month paid the same side, at least half the week's rate), fading (same side, less) or flipping (the month paid the other side: this week is a turn, not a regime); a dash when the funding history is under a month. The z carries ± its standard error from the spread's own mean, clustered by calendar week, because a spread is a level and its days are not independent draws.
Red = longs pay (crowded long, carry is a cost), green = longs receive. A bucket whose spine covers under half its hours is hatched rather than drawn as zero — a gap in the data and a market that went flat mean opposite things to anyone sizing a position. The newest column is always the last complete bucket. Beside each row's window mean sits its current funding (this hour's rate off the live snapshot), so carry building or fading reads as now vs mean; rows rank by open interest and can be sorted by carry side, |carry| or the now column; the client trims to top 25 / 50 / all.
Sessions — when the returns happen
- Session decomposition (the flagship) — what an overnight (close→open), weekend (Fri→Mon) and cash (open→close) hold actually pays, pooled one equal-weight bet per calendar boundary across the equity class, compounded into equity curves. The shaded band is the running funding drag: an edge that dies net of funding is a donation. A rising overnight curve against a flat cash curve is the classic overnight effect.
- The clocks — hour-of-day activity in each market's own volume norm; the funding clock; a day-of-week 7×24 heatmap and return seasonality by hour. Read with sample-size humility.
- Structure panels — cross-ticker clustering by the shape of the trading day; asset-class overlays (equities vs FX vs commodities). Panels unlock as the 180-day hourly spine accrues; an empty panel means insufficient coverage.
- Every chart hovers — date, value and breadth (how many names stood behind that point).
Calls — the public call record
(Build 2026.09.30-127.) The case against: before a call goes on the record, the confirmation lists the strongest evidence against it from numbers the app already shows — a crowded side, the carry it pays over the horizon, earnings inside it, a live signal the other way with its record, a stacked trend it fades, a range extreme, the desk's open calls the other way. It never blocks the call. Challenge: one pick a week per member, long or short, any market; picks are hidden until the week's first US cash open, everyone enters at that price and exits at the week's last close; the result posts to the 📣 Calls recap topic, and the season table counts weeks won, weeks green and the total (build 2026.10.15-130: a tie for a week's best splits that win — two tied members get ½ each, shown as 0.5). A pick is not a call and does not touch the leaderboard.
Duels. Calls ▸ Challenges ▸ Duels: challenge any member head to head. Three formats: pick vs pick (each picks any market, long or short; the better return wins), same ticker, opposite sides (you name a market and a side, they get the other side: exactly one of you wins) and return minus drawdown (each picks any market; the return minus the max drawdown on the way wins — the smoothest win). You choose the window (the next cash session, a week, or 2–10 sessions), the entry (everyone at the first cash open, or the live price once accepted — a live pick is final once priced), the markets (any, stocks, crypto), an optional stop (a pick that goes 5% or 10% against is taken out there) and an optional max drawdown: pick 5, 10 or 20%, or type any figure from 0.5 to 50%. Whoever’s pick goes that far against them at any moment — read on the 5-minute bars, the stop’s rule — is knocked out and loses on the spot; the duel is scored there and then, the survivor marked at that moment (both out in the same bar is a tie). The invite lands in your DM with them and as a push; they accept, decline (and can mute you), or answer with new rules. It lapses unanswered at the lock or after 24 hours. Picks stay hidden until the lock. Once accepted, nobody can cancel. The one way out is a draw by agreement: both of you ask, inside the first 10% of the match (acceptance to the close), and it ends with no result. A duellist with no pick at the lock forfeits. Duels are private by default (the two of you); make one public and the desk can watch it once accepted, and the result also posts to the 📣 Calls recap topic. Each duel opens as a match card: both players, their picks, the entry, the live % for each side, who leads and by how much, a countdown, and an optional trash talk thread (the two of you write; whoever can see the duel reads; 280 characters). Share the card posts it as a picture into any conversation; save as PNG keeps it for anywhere else (1200×630 while live, 1080×1080 when final). Results feed the duel ladder only: an Elo rating from 1200 (K = 32; a win 1, a loss 0, a tie ½), ranked from 3 duels, recomputed from the results every time so it cannot drift. Draws by agreement and voids move nothing. A duel never touches the call leaderboard or the weekly challenge.
Custom contests. Calls ▸ Challenges ▸ Contests: any member can run one (three open at a time). The rules come from a fixed menu, and you see them in words before you start: the window; the entry; the markets (any, stocks, crypto, or a list of up to 20 tickers); the side (free, long only, short only); 1–3 picks each, averaged; the scoring (return, return vs benchmark, return minus drawdown, least drawdown, or first to +X%: the earliest pick to reach the gain wins, otherwise the best return); an optional stop; an optional max drawdown (any figure from 0.5 to 50%: an entrant whose pick goes that far against at any moment is knocked out and ranks below everyone still standing, while the contest carries on to its close); and whether picks stay hidden until the lock. Anyone joins with picks until the lock (up to 100 entrants). Fewer than two entrants at the lock is a void. The creator can call it off before the lock — except a live-entry contest once any pick is in, since a live pick is final. Under vs benchmark a pick with no benchmark mark counts its raw return, and every pick counts. A stop tighter than the max drawdown takes a pick out first. It is announced and settled in the 📣 Calls recap topic. Drawdown, stops and race targets are read from the 5-minute bars the call resolver uses, under the same session rule: a wick counts during the US cash session, only a 5-minute close off-hours for US session names.
Every member’s price calls in one place (Signals ▸ Calls). A $TICKER in a message stamps a call at the mark it was sent at; calls are public: every member sees the same calls and the same leaderboard. A call made in a channel shows whole; one made in a DM shows without its words — the call and its thesis are public, the message is not. DM calls from before calls went public (build 2026.09.28-124) stay with the two people in them and are not scored.
- Leaderboard. One row per caller over 30d / 90d / all, scored on closed calls, direction-adjusted, one unit per call, ranked on the 95% lower bound of the win rate (Wilson); fewer than 8 closed calls is provisional. Beside it: average result, max dd, vs bench (the S&P 500 proxy, or BTC for crypto, over the same hold), payoff, targets, streak and a curve. A caller’s profile adds the curve against the benchmark, the distribution, where the edge is and the track record.
- Max drawdown. How far a call went against you before it closed. A long at 80 that sinks to 50 and then hits 100 is a target hit that sat −37.5% first, and the result alone never says so. It is the worst price between the send and the close, measured on the 5-minute bars the target resolver reads, with the same rule: the low for a long and the high for a short, a wick during the US cash session but only a 5-minute close off-hours for US session names (crypto keeps any print). The exit counts too, and while a call is open the live mark does. It shows on the call’s message (max −37.5% against), in a drawn call’s header, in the track record and the recent feed, and on a hit or a miss’s post (went −37.5% against first). The leaderboard’s max dd is the mean over a caller’s closed calls, and the profile names the deepest. A market with no 5-minute bars over the call shows — (not measured), never 0. Calls made before this shipped are measured from the archive (370 days) in the background.
- A call on its candles. Click any call to draw it on 1D or 4H candles: the hold band, the entry at its exact minute and price, target and stop, the exit, and pins where the thesis (T), each update (U) and the post-mortem (PM) were written. The thesis timeline beside it replays the chart as of any entry — every candle after it greys out. The benchmark overlay rebases the call’s benchmark to the entry.
- Copy score (build 2026.09.28-125): the ring beside each caller, 0–100 — what a follower copying every public call would have made over 180 days, entered 15 minutes after the stamp (the first 5-minute close), paying 5 bps a side and the perp’s funding over the hold, 10% of the account per call (by conviction if you choose), total exposure capped at 100%. Scored 50% on risk-adjusted return (Sortino, ranked against the other callers), 20% on drawdown, 15% on consistency (30-day windows up) and 15% on followability (how much of the edge survives an hour’s delay), pulled toward the desk for small samples; under 8 public closed calls it is provisional. A caller’s profile shows the follower’s $10,000 against the caller’s own record, with delay, sizing and cost switches and the return at each delay.
- Follow a caller (☆ on the leaderboard): their next public call reaches your Telegram and browser push with its thesis and copy score. My copy portfolio copies everyone you follow, from the day you followed them, on one paper $10,000 account. Insights: markets with 2+ callers on them this week, how crowded / lone / split calls have done, win rate by conviction per caller, and a head-to-head between any two callers. When a call with a thesis closes, its author is nudged once to write the post-mortem.
- What a call counts (build 2026.09.28-126): by what its words committed to. A loose call — no date and no target in the words (
long $HOOD) — counts ½ toward the rank and the 8-call bar (tagged ½); a dated one ($HOOD 2w) counts 1; a target with a deadline ($HOOD to 130 by Dec 18) 1¼. Win rate, average and the rest stay plain counts. The same weights size the copy score’s sample. When you send a call the confirmation asks what the words left open: long or short (required — a call without a side is not a call) and 7 or 30 days; choosing a horizon there sets how long it runs but does not make it a dated call. - Closing by hand. Your own open call has close now (on its chart here, and close call on the message): it locks in at the live mark, is marked ✋ closed by author everywhere it shows, and cannot be reopened. The dialog asks why — optional, but asked — and the answer is posted as the call’s post-mortem, public like the thesis. The profile splits hand closes by outcome (winners 75% · losers 0%) and flags a caller who banks winners early and lets losers run to their date.
- Weekly recap: one week of the public record — calls made, closes and win rate, the best and worst closes, calls closed by hand and why, targets, callers of the week, what is still open. Step through weeks with ◀ ▶. Every Monday (13:00 UTC) the last week’s recap is also posted in the 📣 Calls recap topic in Messages, where anyone can join and talk it over. (Build 2026.10.15-130: the system posts into its own topic by id — another topic that happens to carry the same title never receives the recap or the challenge result.)
- Recent calls: every call, newest first, each drawn when clicked. By ticker: one market with every caller’s calls on it and a lane per call; click a name to isolate it.
- Share a call’s chart (build 2026.10.15-130): share ⤴ in a drawn call’s header posts the chart exactly as it is on screen (the replay step and the benchmark overlay included) into a conversation you pick, through the same share sheet as the Charts tab, with the call’s facts as its caption — caller, side, entry, now or the close, the result, the target or horizon. It is a picture of a call, never a call: sharing never stamps one under your name.
- Needs an account. The chart reads the candle archive; a market with nothing archived in the window says so.
Calendar — earnings and macro, in ET
- Upcoming earnings — the next 14 days for the equity universe, grouped by ET day with the session: BMO before the 09:30 open, AMC after the 16:00 close, DMH during hours, TBD date known, session not. Finnhub-fed in chunked date windows, refreshed every ~6h. Day move, 24h volume and ADR on each row come from the live snapshot; ★ names float to the top of each day.
- Reported — the two prior ET days stay on the tab with EPS actual vs estimate, the verdict (beat / miss / in line, EPS-only — the tape's verdict is the move beside it) and the print's own reaction candle (BMO/DMH scores its UTC daily candle, AMC the next one). Display precision expands until actual, estimate and the surprise % reconcile. Rows come from the persisted print history, so they survive redeploys; feed hygiene drops retracted and placeholder-date prints, and an operator can void a garbage print permanently with the × on the row.
- Reaction study — each name's own earnings base rate from the last cash close before each print to the first cash close after it (exact 16:00 ET anchors off intraday data, session daily closes as a counted fallback): n, average and median |move| with a bootstrap 90% interval, up/down split, cash-session gap behaviour (intraday-only, coverage shown), and the move as a multiple of the name's usual range. A one-time ~1-year backfill, self-accruing after.
- Pre-earnings setups (build 2026.09.24-100) — a collapsible strip at the top of the tab, one card per name reporting within the next 5 sessions, also shown as a section in the ticker drawer. Each card: the reaction study (typical move, up/down split, gap-and-hold vs gap-and-fade, n), positioning into the print (funding and its percentile vs the name's own 31d history, OI change over the last ~5 sessions, premium), the run-up so far vs the name's usual pre-print drift, and the typical move as a multiple of the current daily move next to the same ratio at past prints. One verdict line is composed from fixed rules — e.g. crowded long into print (funding p92, OI +18% in 5 sessions); typical move ±6.1%, gaps and fades 7/10 — with no AI involved. Anything missing reads n/a — why. A read of the setup, not a call.
- Coverage — indices, ETFs, FX, commodities, baskets and pre-IPO synthetics never report; foreign listings without a US symbol are real companies the feed does not carry. The coverage line states how many eligible equities exist so absence is auditable.
- Macro rows — FOMC decisions (statement 2:00 PM ET, presser 2:30, an SEP chip on dot-plot meetings) from the Fed's published schedule, and CPI · nonfarm payrolls · PPI · retail sales · GDP · PCE (8:30 ET) from FRED's release schedule. Upcoming rows show the prior print — not a consensus estimate, no street feed exists here — and flip to prior → actual once released (between the release clock and FRED's data landing they say "released — actual pending"). FOMC rows resolve to held / cut / hiked.
- Interaction with the rest — the E badge on Markets, the earnings and macro guards on Signals, the ⚠ / ◆ flags on Actionable, the flags and context handed to AI reports, and the global macro banner all read this calendar.
News — company headlines, macro tape, filings
Two feeds merged into one tape: per-name headlines (Finnhub) tied to names in the universe, and the general macro tape from configured Telegram channels — a 72-hour rolling window, per-name refresh every few minutes, served whole to the browser and sliced client-side (the drawer's per-name news is the same payload filtered). Badges cross-reference the app: amber = earnings within 7 days, red = a live signal is firing on that name. A headline is tied to a ticker only when the match verifies.
Filings overlay — SEC EDGAR filings for covered US names ride the same feed, flagged FL, with material forms marked; those also fire the filing alert class. Telegram channels are group configuration the operator edits (each shows a live status). A warm cache serves the last good pull across redeploys.
Housing — macro housing and MBS board
The backdrop behind the homebuilders, mortgage servicers, REITs and banks in the universe. Seven FRED series pulled in full history, refreshed every 6 hours, warm-cached: the Freddie Mac 30-year conforming mortgage rate, single-family vs multifamily starts (SAAR), months' supply of new homes (6+ historically reads as a buyer's market), new-home sales and the median price, and ICE BofA BBB corporate OAS standing in for non-QM spreads. Each card names its series; a Proxy chip says exactly what differs from the paid original. A series whose newest print is older than its own cadence can explain is dropped and named at the foot of the tab rather than charted as if current — FRED retires series without erroring. The 12m column compares the latest print with the observation closest to one year earlier; a missing month is a dash.
Liquidity — Fed net liquidity
Net liquidity = total assets − Treasury General Account − ON RRP, on the weekly H.4.1 dates (Thursdays ~4:30pm ET; the board refires after each release and every 6h otherwise). Shown in dollars and as % of nominal GDP, with year-to-date change by component signed by liquidity effect (holdings add when they grow; TGA and ON RRP add when they shrink — a blue TGA bar means Treasury ran its balance down), balance-sheet composition (Treasuries, agency debt, MBS), the TGA / ON RRP drains, and two plumbing-stress reads the usual pages leave out: bank reserves (what the Fed targets as "ample"; the 2019 repo spike came near $1.4T) and SOFR − IORB (persistently positive = collateral scarce, reserves tight). Ten FRED series, everything normalised to billions before any arithmetic. QT end is derived: the first week after the 13-week change in holdings stopped being negative.
Congress — STOCK Act disclosures
Periodic Transaction Reports filed with the House Clerk and the Senate, parsed into individual trades that name a ticker in the universe. Sorted by filing date, because that is when the market learned; the lag column is the gap back to the trade (a disclosure lands up to 45 days after it). Amounts are the disclosed band, never a point figure. The tab shows the parse rate and the ticker-resolution rate so the coverage is a real number; scanned House PDFs go through OCR (congress ocr) as an operator lane. (Build 2026.10.16-131.) The House's 2026 electronic filings draw their text inside page forms with two-byte subset fonts and a compressed stream an older reader could not open; they read now, and the filings the old reader had wrongly set aside as scans were put back on the parse queue once (newest first, at the normal daily pace). A typeset PDF the reader still cannot decode is retried rather than written off as a scan.
Senate (build 2026.10.16-131) — read from the Senate's eFD site (efdsearch.senate.gov) a few times a day: the lane accepts the site's usage agreement, searches Periodic Transaction Reports incrementally (from a week before the newest filing it already holds) and reads only the new ones, one request every few seconds. Electronic filings are HTML tables — transaction date, owner, ticker, asset (with a bond's coupon/maturity or an option's terms), asset type, purchase/sale, amount band and the filer's comment (the COMMENT column, hidden by default). Paper filings are scanned page images: they are listed under the table with a link and a paper flag, and are not parsed (nor counted as unreadable). The All · House · Senate chips filter the table, the counts and the search; every member carries an H or S marker. The congress alert class covers both chambers (a starred senator's new filing reads "· Senate"). Terminal: congress senate [n] runs a sync now.
Insiders — SEC Form 4
Section 16 transactions — officers, directors and 10% holders reporting their own trades within two business days — for the equity roster, rotated through EDGAR two names a minute. Exact share count, exact price, exact date: nothing here is a band or an estimate. Opens on open-market buys and sales (codes P and S); grants, option exercises and tax withholding are one chip away, as is the form's derivative half (options and strikes, kept in their own columns because an exercise price is not a transaction price). A sale under a Rule 10b5-1 plan was scheduled in advance and says less than a discretionary one.
Cluster buys (build 2026.10.15-130) — an open-market purchase is marked cluster when three or more different insiders bought the same ticker within 10 days, computed server-side over every stored Form 4 (a page alone never holds the other buyers). The clusters only chip keeps just those buys; the drawer shows the newest cluster inside 90 days. While a filter request is out the table dims and says updating….
Notes — every note, across every name
All your per-ticker notes, newest first, searchable over bodies and tickers and filterable by the #tags found in the bodies (derived at read time, never stored). Every note carries the mark it was written at and the move since. A name rotated out of the universe keeps its note, greyed and labelled; notes are keyed by coin and displayed by ticker, so a rename moves the note with the market. Stored server-side on the volume (not in the browser) because a note is prose somebody sat and typed. Writing is a separate permission (notes.write) from reading the tab, so opening the tab to the group never hands the group the pen. An operator can promote any price-stamped chat message into the Notes book, keeping the message's own timestamp and price.
AI Report — the analyst read
Everything the server holds on one name — price structure, positioning, funding, the signal ledger's own base rates, earnings and macro context, flags — compiled into one prompt and synthesized by an AI analyst (Claude or GPT, per server configuration) into a plain-language read. It is a reading of the board, not an oracle: every number it cites is a number another tab shows, and the machinery around the model exists to keep it honest.
- Sections — the picture in one paragraph; what the data says; scenarios with computed R/R and EV (colour follows the money — an adverse "target" the model mislabelled renders red); if you act on this, a mandatory action block whose EV-at-entry is computed server-side (a plan that prices negative is downgraded to "wait" before it reaches you); what would change the read; flags; the track record on this name; recent reports.
- Validation — a short read without a correctly-sided void and scenario is rejected; EMAs cannot be smuggled in as chart levels; an opposing-bias anchor cannot override the void. Every directional read is ledgered as an
aireadclaim and resolved out of sample, so the tab grades its own past reads. - Annotated chart — candles with a D1 · H12 · H4 switcher, the EMA 13/21 ribbon, the levels the read leans on as labelled lines, and proven-edge markers for past signal fires (only where the edge has earned it: ≥8 resolved roster-wide with positive average R, or ≥5 at ≥60% hit on this name).
- Caching and budget — reports are shared: the first generation is cached for everyone (
AI_REPORT_TTL_MIN, 30 minutes by default) and regenerate unlocks only on the cooldown or a material change (a moved mark, a fresh signal, an earnings print). Generation is capped per user (3/day, 20/month) and per group (5/day) unless you hold the admin unlock. Group reports on a sector or a custom basket run from the terminal (report sector Energy,report basket NVDA AMD …). - ai alert — a regenerated report whose action stance flips vs the cached one fires the ai alert class.
Messages — price-stamped conversations
Direct messages, groups and topic boards between account holders. The reason it exists rather than a Telegram group: a message carries the mark it was sent at. Type $TICKER and the server stamps the price straight off the snapshot it rebuilt seconds ago, so a call made at 113.90 and read at 118.50 says sent at 113.90 · +4.0% on its face. The stamp is written once; an edit never relocates it.
Conversations
A 1-to-1 per person, groups (creator-managed; the last owner leaving hands ownership to the longest-standing member), and topic boards anyone may join, whose first pin is the standing post. Adding, removing, renaming and leaving are recorded as system rows in the history. Joining marks the backscroll read. Close is per-viewer and reversible from the folded Closed section; clear forgets the backscroll for you alone; delete (owner or operator, groups only) shreds the thread for everyone and lands in the audit log.
Calls
Write short $HOOD (or $HOOD puts) and the call scores as a short; everything else is a long. calls ↗ in the rail opens the Calls tab (Signals ▸ Calls), where every call is in one place with the direction-adjusted move since. Calls are public: a call made in a DM shows there without its words — the preview under the box says 🌐 public, and pressing Send on a call asks once — Make the call (Enter), Send as a plain message (the same words, no stamp) or back (Esc). Delete-proof: deleting a call removes its words, never its stamp or score. A stamp card carries a one-tap ⚑ alert that arms a price alert at the called level.
Chat-style reading
Grouped runs with one name/time header, day dividers, per-member colours in groups, live read receipts (seen by under your last message), replies that quote one line and click back, @mentions, pins in a strip at the top, drafts that survive a reload, a JSON export per conversation, and tweet cards (x.com links unfurl via X's public oEmbed; only parsed fields are stored).
Attachments and reactions
Attachments are typed by the server's own magic-byte sniff, never the uploader's claim: png/jpeg/gif/webp render inline and .txt that validates as text is accepted; everything else is refused at upload. Reading a file is a membership check, so a forwarded link is not an access grant. Reactions are a fixed vocabulary of eight and name who reacted. Retention: 30 days in a 1-to-1, 7 in groups and topics, pinned messages exempt.
Escalation
An unread message reaches your linked Telegram after 5 minutes (DM_ESCALATE_MS) as one digest per sender, honouring your quiet hours and caps; being online cancels it; muted threads never escalate — except that a watched ticker (a per-person list in the rail, separate from the ★ watchlist) or an @mention escalates immediately and pierces a mute. Browser push (enable it in the rail) is the second leg, tab closed included. From Telegram, /r your message replies to your last notification and /r @handle message picks the person — command-only on purpose.
Search and keys
Search is scoped by membership (the scope is the authorization). / focuses the conversation search, j/k walk the rail, Esc backs out a level. Signed-out visitors get a rate-limited request an invite that pings the operator; members with no Telegram can adopt a chat the bot already serves with a 6-digit code.
Reading a call
A fixed vocabulary decides direction and horizon — short words before the ticker (short sell fade bearish bear dump puts lower downside, with -ing forms), a few after it (short puts lower down bearish dump); sell $X puts is a long, buy $X puts a short, $X calls a long unless sold. Horizons after the ticker: 30d · 2w · 1mo · next week · a month · eow · eom · year end · by Oct 15 · by 10/15. The composer preview names the word it read. When the words decide nothing, ask AI what I mean (gated by dm.ask) proposes a reading you apply or ignore — nothing posts on the model's say-so.
The call lifecycle
A call is open until its horizon — 7 days by default, or the days written right after the ticker ($HOOD 30d, 1–365). At the horizon the first daily close at or past it becomes the final result and the call stops moving. The author can close early at the live mark (with an optional reason that becomes the post-mortem, -126) or extend an open call by a week (hover their own stamp), never to a horizon that has already printed. The record is the closed calls: a call counts once, at its final; open calls are counted, not scored. The desk digest lists open calls with age and levels, the week's closes with their final (⊘ = closed early), and each person's record over the last 30 days of closed calls.
Call leaderboard
The Calls tab opens on the leaderboard: one row per caller over 30d, 90d or all time, scored on their closed calls — win rate, average result, vs bench (the result minus the S&P 500 proxy’s move over the same hold, or BTC’s for crypto, signed like the call), payoff, targets, streak and a curve. The default rank is the 95% lower bound of the win rate, so a long, good record outranks a short, lucky one; fewer than 8 closed calls in the window is provisional and unranked. Click a caller for their profile: the curve against the benchmark, the spread of results, where the edge is (long vs short, conviction, horizon, sector, ticker) and the full track record, each call opening to its thesis. Every member sees the same board. A call’s bubble carries its author’s record chip; click it for the profile.
Thesis
Once the preview under the box reads a call, + thesis opens a drawer: why, a catalyst, what would make it wrong, and your conviction (1–3). It is sealed with the price when you send — it can’t be edited, and it stays with the call if you delete the message. While the call is open you can + update it (dated, appended); once it closes, write post-mortem once. The profile shows how your high-conviction calls do against your low-conviction ones.
Call targets
Say where it is going and by when — $INTC to 32 by Oct 15, $HOOD 125 in 2w, short $TSLA to 300 by 10/31 unless 360 — and the call becomes a target: it resolves on whichever comes first. The mark touches the level on a 5-minute bar or the live mark (hit), the stop prints first (wrong; a bar that touches both counts as wrong), or the deadline’s daily close prints with neither touched (missed). The deadline is the call’s horizon, so +7d moves it and close call ends it as closed early, which is neither. The stamp grows a row: a bar for how far along it is, a triangle for time used (bar ahead of the triangle = ahead of its clock), a red tick at the stop. A resolution posts in the conversation under the author’s name. The calls board adds a target column and a second, binary record per person — hit / missed / wrong and the median days to a hit — beside the % record. The composer’s preview shows the target it read, or why it refused one; a refused target sends as a plain call.
Telegram sync
The ⇄ telegram box in a conversation's header mirrors that conversation to your linked Telegram both ways: every message lands on your phone as it happens, and plain text you send the bot posts here under your name, marked tg. One conversation per member at a time (a bot chat is one stream); sync starts from now, your own phone lines are never echoed back, the 5-minute digest stands down for it while a phone is reachable, and quiet hours hold the mirror then catch it up with the last ten lines. Bare text is accepted from a private chat with the bot only. Since build 2026.09.24-99 edits, deletions, reactions and files cross too: an edit or delete here changes the copy on your phone, editing a line you typed at the bot edits it here, a reaction on either side shows on the other (the bot can show one reaction per message: the most-used), and photos and files travel as files under the same type and size limits as an upload. Telegram tells bots nothing about deletions made there, so those stay on the phone.
Share to chat
Any cell, row or screen of the markets table into a conversation as a data card: hover a data cell for the ⤴ at its corner (that field), hover the ticker cell for the row, right-click for cell · row · this screen, or press s on a focused row. The sheet previews the exact card, picks the conversation (or a person), takes an optional note and one switch — quote as a call. A card keeps the table's own formatting, says when it was captured, shows the mark's drift since, and opens the drawer when clicked; re-capture posts a fresh one. /share HOOD funding, /share NVDA, /share screen do the same from the composer.
Share to chat everywhere
The same ⤴ leaves the screener (build 2026.09.24-98). Boards — Trend, Actionable, Sectors, Drawdown, and the Funding heatmap's rows — float it at the end of a hovered row; right-click offers the row or this board (first 25 rows). In the drawer it sits on every section header: metrics, earnings reaction, notes, OI and funding (their sparkline travels as numbers and is redrawn in the card), co-movers. Charts — each Charts pane's header, and the drawer's hourly candles — share as a picture, the way /ratio posts: rasterised to a PNG and attached, with the window's facts as the caption; a picture goes to an existing conversation. A screen can be shared live: its filters travel with it and re-run over the board whenever the card is viewed, with the captured table kept underneath as shared (names that dropped out struck through, newcomers marked). A screen that leans on your own ★ / noted / held list can't go live — it would be somebody else's list. Panels and charts are shared again from where they live; re-capture stays a screener verb.
Catching up
(Build 2026.10.15-130.) A conversation with unread messages has ↧ first unread on its rail row: it opens at the first message you have not read, walking back through older pages if it has to (up to 1,000 messages). Opening one with 15 or more unread shows a one-line While you were away strip counted from the messages in hand — who posted and how much, calls made and closed, the tickers named, the links shared — plain counting, no AI, the same messages always give the same strip; ✕ dismisses it for the session. Scrolled up while messages arrive, a ↓ N new chip lands you on the first of them. Rail rows and topics take Tab, Enter and Space; screen readers hear other people’s new messages as they arrive, not your own and not history being loaded.
Offline, sending and devices
A plain message that fails for want of a connection is queued, not lost: it shows at the bottom of its conversation as queued — will send when back online with a cancel that puts the words back in the box, and goes out by itself when the browser is back online (or the next time the server answers), oldest first, and not twice: each carries a one-time key the server remembers for 10 minutes (in memory, so a redeploy forgets it), and past that a retry is skipped when the conversation already shows a message of yours with the same words posted since it was queued. Kept in this browser for your account only, 20 per conversation and 50 in all. Calls, files, commands and a first message to somebody are never queued — a call is stamped at the price when it reaches the server, so it is not sent later at a price you did not see. A message still uploading or sending when you switch conversations shows sending… on its row. Under Browser notifications, all your devices… lists every browser that gets your notifications (marked this device where you are) with a remove. The watch list shows its count against its cap (12 / 200). Presence refreshes every 45 s from a light read; the full list reloads when you come back to the tab.
Commands in the composer are covered in Chat terminal. Delivery rides the same push stream as the tape (versions, never payloads), so no WebSocket is needed and a dropped frame costs nothing.
Watchlist, layouts and positions follow the account
- Watchlist and saved layouts sync to your account: one preference row per (member, key), last-writer-wins on the client's own timestamp, and a poke to your other tabs so a phone and a desktop converge on whichever change was made later. localStorage stays the working copy (signed out, nothing changes). The active layout is deliberately per browser.
- Positions overlay — link one Hyperliquid wallet under Filters › Positions (public address only; the app reads the public clearinghouse state and can never sign). The server reads the xyz book and, in crypto, the main-dex book every 30s — but only for wallets somebody is looking at. It ships the structure of each position (side, size, entry, leverage, liquidation, margin, funding paid); notional, unrealized P&L, ROE and the move since entry are derived client-side off the live mark, so they move with the tape. On the table: ⬡ beside the ticker, a sortable Position column (migrated in next to OI in saved layouts), the ⬡ held filter; in the drawer, a panel next to the trend read.
- Per browser, not synced — tab order, the active layout, phone or desktop layout and the phone's picked number, the funding unit, open admin folds, the mobile preset flag, the analysis window and refresh cadence, terminal history, chat drafts.
8. The terminal — ask the board
The ▸_ Ask button (or ~) opens a query console over the rows already in your browser. Every board column is a lens; the grammar below runs locally and instantly, and anything it cannot parse is handed to the AI fallback where the operator has enabled it. Tab completes verbs, fields and tickers; ↑ ↓ walk the history; a result names the rows it ran on.
| Verb | What it does |
|---|---|
<ticker> [field] | The card (price, day move, funding, OI, squeeze, momentum, vs tape…), or one column on one name: nvda funding, btc oi, tsla vsma200. |
top|bottom <field> [n] | Any column, plus gainers · losers · trending: top funding 5, bottom d7, top rvol 10. |
screen <expr> | Fields joined with &, compared with > / <: screen funding>20 & squeeze>50, screen vsma200>0, screen rvol>2. |
breadth · sectors [d7|d30] | Tape health; sector performance over a window. |
earnings [ticker|today|tomorrow|week|recent] | The calendar, or one name's next print. |
news [ticker] [n] | Verified headlines, 72h window. |
signals [ticker] · reports | Active signals; recent AI reports. |
vs <a> <b> · corr <a> <b> · diverge <ticker> | Side-by-side field compare; correlation and hedge β over 90 days; a name against its benchmark — is it decoupling. |
comp <a> <b> … | Overlay N names rebased to 100 (opens COMP/G on the Correlation tab). Baskets welcome; BTC may join a stock set. |
ratio <A>/<B> [1h|4h|12h|1d] | Synthetic pair candles with an honest EMA200: ratio MAG7/EWZ 4h, ratio NVDA/BTC. |
basket create|list|drop | Custom equal-weight baskets: basket create MAG7 AAPL MSFT …. Visual layer only. Operator-gated by default. |
report <ticker> · report sector <name> · report basket <t> <t> … | Open or generate the AI analyst report on a name, a GICS sector, or a custom basket (2–12 live equities). |
fund <ticker> · etf <symbol> · holds <ticker> | Latest SEC-filed balance sheet and income facts (XBRL, on demand); ETF composition from the latest N-PORT (30–60 day lag); who holds a name from the market-wide index. |
whale [KEY [full] | season] | Tracked 13F funds; one fund's book; the quarter summary. |
insiders · congress | Form 4 and PTR lane status. |
gaps [n|up|down] · gap <ticker> | (Build 2026.09.30-128.) The opening-gap board: while the cash market is shut, what the perp implies against the last cash close, the capture fit and the expected gap by 10:00 ET with its band; while it is open, this morning's 09:00 read against 10:00. gap <ticker>: one name, its capture at each checkpoint and its last nights. |
depth <ticker> [size] | The live order book: the spread, the resting size within ±0.5 / 1 / 2%, and the average fill against mid for $10K, $50K, $250K and your size (depth NVDA 250k). + marks a fill the published book ran out on. |
why <ticker> [1h|4h|24h|7d] | The move split into market, sector and its own, with the headlines in the window (24h by default). A question with more words ("why is NVDA down") still goes to the analyst. |
carry [n] | Carry pairs in the scope you are in: correlated long/short pairs that funding pays a hedged holder. |
xdex <ticker> | (Build 2026.10.16-131; where crossdex is open.) One xyz name against the same underlying on other HIP-3 dexes: mark and oracle spread, each leg's funding, OI, and the carry after the board's stated fee — the Cross-dex tab's row, plus any same-name listing refused as a different contract. |
rewind <when> [ticker] · since [when] | The board at a past moment against now (rewind 1d, rewind 2026-09-29 14:00 NVDA); what moved, fired, closed and reported since (since 4h, 30 days at most). |
case <ticker> long|short [days] | The case against a call, from numbers the app already shows (7 days by default). |
challenge · challenge long|short <ticker> · challenge drop | The weekly desk challenge: this week's board and the season; pick, change or withdraw your entry for next week. |
duels · duel <id> · duel @who long|short <ticker> [session|week|3s] · duel accept|decline <id> | Your duels and the duel ladder; one duel; challenge someone (pick vs pick, your pick hidden until the lock); answer an invite. In a chat duels shows only the ladder and public duels, and duel stays in the terminal. |
contests | Custom contests open for picks and in play. |
book · journal | Your linked wallet: risk in dollars (per 1% of the S&P and BTC, funding over the next 7 days, a one-day swing, clusters, stress); your closed trades grouped by signal, by whether you had called them, and by the carry at entry. |
alert <plain English> | A server-side rule in words — alert HOOD up 5% and crowded longs. It fires with no tab open; the bell lists it. |
stocks · crypto | Flip the scope. |
help · clear | The verb card; clear the scrollback. |
admin … | Operator verbs (unlock, lock, reset-reports, lane maintenance). Password echoes are redacted. |
Fields and aliases
Canonical fields: funding fundpct oi doi squeeze momentum vstape carry beta dd ddy vol vol30 d1 d7 d30 h1 h4 rvol gap prem adr vsvwap vsma200 vsyopen vsmopen yopen mopen turn dcap hitr rs sector price. Plain-English aliases resolve to them — apr, rate → funding; sqz → squeeze; mom → momentum; week, 7d → d7; month, 30d → d30; ytd → vsyopen; premium, basis → prem; relvol → rvol; volatility → vol30; downcap → dcap; hitrate → hitr; fromhigh → dd; spx, vssp → rs; oivol → turn.
Plain English
"who reports tomorrow", "best sector this week", "whats above the 200dma", "nvda vs amd", "most crowded shorts", "hows the tape" — a translation layer maps common questions onto the grammar first (the translation is echoed as → screen vsma200>0), and only what still cannot parse goes to the model. The AI leg is metered: the ask budget chip shows the group's remaining daily calls (ASK_MAX_PER_DAY, resets midnight UTC), each member has a daily cap, and the 🔒 chip means AI generation is locked until an operator runs admin unlock.
9. The chat terminal — commands in a conversation
Type / and a verb in any conversation's composer — /top funding 5, /nvda, /screen rvol>2, /earnings today — and the result posts into the thread under your name as a monospace block badged computed, so a group reads one table instead of six people opening the panel. One code path: the same handlers the terminal runs, against the same rows.
/helpis a private card only you see;/clearforgets your private lines;//textsends a message that really starts with a slash; the ? beside the composer opens the full guide; Tab completes./ratio A/B [tf]is the one verb that posts a picture: the Correlation tab's SVG rasterised to a PNG, riding the ordinary attachment path./alert NVDA > 200,/alert NVDA crosses down 180,/alert NVDA above 200ma,/alert HOOD d1 > 5 note,/alert any rvol > 3write a threshold rule bound to this conversation: when it fires, the fire posts here as a command result (one list per scan, quiet on the alert wire so nobody is told twice)./alert list,/alert off <id>,/alert help. The same line works at the Telegram bot. Neither/alertnor/shareis a terminal verb, so the dm.terminal switch does not gate them./share <ticker> [column]and/share screenpost a piece of the markets table as a data card — see Messages.- The edge verbs run here too (build 2026.09.30-128):
/gaps,/gap NVDA,/depth NVDA 250k,/why HOOD 4h,/carry,/rewind 1d,/since 4h,/case NVDA short,/challenge./sincein a conversation leaves your own watchlist out.bookandjournalread your own wallet, and a challenge pick changes state, so all three stay in the terminal. - Verbs that open a view or change state (
comp,report,basket,whale add,admin …) are refused with a pointer to the panel. A command result carries no price stamp and cannot be edited — delete it and run it again. - Two operator switches in Admin › Features: dm.terminal (the local grammar, public by default) and dm.ask (the AI fallback in chat, operator-only by default because it spends the shared budget and the answer lands where everyone reads it). The server enforces both on the post and on the ask.
10. Alerts — the bell, rules, Telegram, push, schedules
The poller owns one sequenced, persisted event stream. Every transport — the in-tab toasts, the bell panel, Telegram, webhooks, browser push — is a thin consumer with its own eligibility filter, so a channel's thresholds can never censor the record of what fired. Events are announced once per claim and per fire; a rebuild or a redeploy re-announces nothing.
The bell panel
| Section | What it holds |
|---|---|
| Trigger alerts | Alert on new triggers — a new CONFIRMED row on the Actionable board. Thresholds EV ≥ (expectancy in R), R:R ≥ (frozen at fire) and late ≤ (how far the setup already ran from its fire, in its own risk unit), the class filter (2:1+ setups vs positive-EV grinders), and per-name mutes. One control writes both the in-tab filter and your Telegram recipients' thresholds. Defaults off. |
| Rules | Metric rules, private to you, evaluated server-side against the exact snapshot the board renders, firing with no tab open. Ticker (blank = any market) · metric · > / < · threshold. Edge-triggered with an arm band and a 30-minute cooldown per rule×coin; 60 rules per person. A few metrics (squeeze, momentum, beta) are derived in the browser against your analysis window and so exist as this browser only rules that never reach Telegram. |
| Delivery | Your Telegram recipients (below), your webhooks (where the webhooks feature is open to you) and browser push. |
| Recent | The server-held ring — survives a refresh, a closed tab, a redeploy. Tags: SETUP, RES / VOID / TGT (ledger), rule, trend, level, filing, earnings, macro, ai, regime, coverage, ops. Consecutive identical rows collapse with a count. Clear hides what is listed for this browser only; Mark all read moves the unread watermark. A regime episode still in force sits above the log as an OPEN row: since when, and its value now. |
Server-side rule metrics
price · 1h % · 4h % · 1d % · 7d % · 30d % · funding APR % · funding percentile · premium (bp) · 24h volume · open interest · relative volume · 24h liquidations · ΔOI 1h/4h/1d/7d/30d % · funding 1d/7d avg APR %. Thresholds in the metric's unit (5 for 5%, 50 for 50M).
Net crowding (crowd) is scope-wide, not per market: the share of names whose funding sits at or above its own 90th percentile minus the share at or below its 10th, the Positioning regime's number. It takes a scope instead of a ticker and no AND extras: /alert stocks crowd > 30, /alert crypto crowd crosses 20, or /alert any crowd < -30 (stocks and crypto each evaluated on their own). Plain English works: stocks net crowding above 30. Same arm band, cooldown and crosses as every rule.
Basket alerts (build 2026.10.16-131, where baskets are open to you): a rule may name a built-in or saved basket instead of a market — /alert MAG7 d1 > 2, /alert TECH crosses 105 (its index level), or type the basket in the Rules form's ticker box. It reads the basket's level or its 1h / 4h / 1d / 7d / 30d return as /api/baskets/live prices it, with the same arm band, cooldown and per-owner cap as any rule, and fires as a rule event named ⬒MAG7. A basket kept only in your browser cannot be watched by the server.
Alert classes
| Class | Fires when | Default |
|---|---|---|
| setup | A claim newly reaches the Actionable CONFIRMED board, filtered by your EV / R:R / lateness / class / mute settings. | on |
| ledger | An announced claim's void is taken, its target reached, or its horizon resolves. One class; the message says which. Only the void hit toasts in the browser. | on |
| rule | One of your metric rules crosses its threshold. | on |
| trend | Trend-board state changes: new 4/4 stacks, new retests. Scanned every 5 minutes; a full pass seeds state before anything fires. | opt-in |
| ma200 | A daily-close cross of the 200-day moving average. | opt-in |
| levels | A newly confirmed structural level (a second pivot within the name's own volatility band) enters the name's 8 nearest levels on the level map, which re-keys hourly. A known level drifting into the 8 as price moves stays silent; one alert per level per 30 days; every name seeds silently on first sight after a deploy, and a backfill landing or a re-clustering re-seeds rather than announcing. Operator-only while it soaks. | opt-in (operators) |
| filing | A material EDGAR filing on a roster name (8-K, 10-K, 10-Q, SC 13D, 6-K, 425), ≤6h fresh. | opt-in |
| earnings | A name with an open, announced claim reports today or tomorrow. One per ticker per report date. | opt-in |
| macro | The universe-wide calendar: day-ahead, imminent and result for FOMC and the six FRED prints. No ticker, by design. | opt-in |
| ai | A regenerated report's action stance flips vs the cached one. | opt-in |
| regime | Net crowding beyond ±35 across percentile-carrying names, or aggregate OI stretch ≥2σ of its own 60 days. One episode, one alert; re-arms only when the condition lapses. While an episode is open, the bell's Recent fold and the morning brief's REGIME section show when it began and where it stands now. | opt-in |
| coverage | A data source goes stale or degraded. | opt-in |
| congress | A starred member's new congressional disclosure — House or Senate (build 2026.10.16-131), ≤10 days old. Each chamber stays silent until its own first full run has completed. | opt-in |
| ops | The server telling on itself: deploys, poller stalls, degraded feeds, and duplicate alert — the same setup, ledger, filing, earnings, macro or congress event firing twice inside 24h (one warning per pair a day, at most one per class an hour). Operator-only, enforced in delivery and in the feed. | on (operators) |
Recipients with no selection get the frozen default set; every class added later is opt-in, shown in the panel with its measured fires per day.
An alert follows its tab (build 2026.10.16-131). A class that comes from a tab reaches you — on Telegram, a webhook, the bell, the in-tab toasts and browser notifications — only while that tab is visible to you, checked at delivery against the live switchboard: congress ↔ Congress, filing ↔ Funds for a watched fund's 13F and News for a material EDGAR filing, trend ↔ Trend, ma200 / levels ↔ Sessions, ma50 / touch200 / touch50 ↔ EMA Touch, earnings / macro ↔ Earnings, ai ↔ AI Report. While Congress or Funds soaks operator-only, so do their alerts; opening the tab opens them; switching a tab off silences its alerts for everyone, operators included. setup / ledger / coverage follow the Signals and Actionable universe slices instead; rule, regime and ops have no tab of their own.
Telegram
- Open the bell → Delivery → link. The panel mints a single-use code (10 minutes) bound to your account.
- Send
/start CODEto the deployment's bot. Reply: Linked. - Pick classes, set quiet hours (a local range like
23-7; your browser supplies the offset), and the scheduled sends below. test sends a probe.
Bot commands: /start CODE link · /stop unlink (always works — control of the Telegram account outranks any cookie) · /status or /help · /r message and /r @handle message reply into Messages. Delivery is paced (1 message / 3s, 20 per recipient per hour — held, not dropped, and disclosed), with Telegram's own errors surfaced verbatim in the panel log. The whole transport is dormant without TG_BOT_TOKEN.
Webhooks
(Build 2026.10.16-131; the webhooks feature, operator-only while it soaks.) Up to 3 endpoints per member, added in the bell → Delivery → Webhooks with a label and a URL. The kind is detected from the URL:
| URL | Payload |
|---|---|
https://discord.com/api/webhooks/… (or discordapp.com) | Discord's {content, embeds}; mentions are disabled, so feed text can never ping a server. |
https://hooks.slack.com/services/… | Slack's {text}, with & < > escaped. |
| anything else | JSON {id, class, ts, title, body, ticker?, url?} (ts ISO 8601; id is stable across retries — dedupe on it), signed over the timestamp and the body: X-Xyz-Timestamp (send time, unix seconds) and X-Xyz-Signature: sha256=<hex HMAC-SHA256(secret, timestamp + "." + raw body)>. The per-endpoint secret is shown once, when the endpoint is created. |
Verifying a generic webhook (the signature covers the timestamp since build 2026.10.16-131, so a captured delivery cannot be replayed later under a new one): (1) read X-Xyz-Timestamp and the raw request body exactly as received, before any JSON parse; (2) compute "sha256=" + hex(HMAC-SHA256(secret, timestamp + "." + rawBody)); (3) compare it with X-Xyz-Signature in constant time (e.g. crypto.timingSafeEqual, hmac.compare_digest); (4) refuse a timestamp more than 5 minutes from your own clock; (5) dedupe on the body's id (a retry re-sends the same id with a new timestamp and signature). In Node: crypto.createHmac("sha256", secret).update(ts + "." + raw).digest("hex").
Each endpoint takes the same alert classes as Telegram (no selection = the default set; operator-only classes reach operators only), the same setup thresholds, and follows your Telegram quiet hours unless its own quiet hours switch is off (held, never dropped; ops and a void taken pierce). Send test posts one test message and shows what the endpoint answered.
Safety. A webhook URL must be https on port 443, a public DNS name (never an IP address, never user:pass@), at most 512 characters. Before every send the name is resolved and every address it returns must be public — loopback, private (RFC 1918), link-local, CGNAT, multicast, unspecified, reserved and documentation ranges, IPv6 unique-local / link-local / site-local and any IPv4-mapped, NAT64, 6to4 or Teredo form are refused — and the request goes to the address that passed (pinned; no second lookup). Redirects are never followed; each request has a 5-second limit; the response body is discarded (read at most 16 KB). A Discord or Slack URL is itself a credential: after creation the panel shows only the host and its last four characters, and no log line carries a URL or a secret.
Delivery. Through a persisted outbox (it survives a redeploy; an alert older than a day is dropped from it), at most 20 posts a minute per endpoint. The outbox holds at most 50 waiting alerts per endpoint, 100 per member and 300 in all; when one is full the oldest alert of whoever holds the most is dropped (and counted on that endpoint's row), so a member whose receivers are down or rate-limiting can never crowd out anyone else's alerts. A 5xx, a timeout or a network error is retried with backoff (up to 6 tries per alert); a 429 waits out the receiver's Retry-After; any other 4xx or a redirect drops that alert. After 10 consecutive failures the endpoint is disabled: its row says why, your Telegram (if linked) is told once, operators get an ops event, and Re-enable brings it back. While the feature is closed to you, or your account is disabled, nothing is delivered.
Scheduled sends
| Send | What it is | Default |
|---|---|---|
| Morning brief | Indices, movers, sectors, regime, positioning, earnings, macro and the day's headlines, with two written sections. AI-written, and its claims are validated against the server's own numbers. | 10:00 UTC daily (BRIEF_DEFAULT_HOUR), per recipient hour/days |
| The Landscape | Written market commentary built from the headline corpus — which stories are the same story, and what hasn't been repriced. Interpretation, not measurement; it cites the headlines it rests on. | 11:00 UTC Mon/Wed/Fri |
| Desk digest | Deterministic and per member (no model call): your own calls and how they are scoring, today's earnings for the universe, live signals and the tape's 24h extremes. | off until you pick an hour |
Each recipient sets the hour (local) and the days (all, weekdays, mon,wed,fri or MWF) per send.
Browser push
Browser notifications in the bell shows setups and void hits as system notifications while a tab is open. Enabling notifications in the Messages rail adds web-push for unread messages with the tab closed (same 5-minute grace, mute and mention-piercing rules as Telegram). VAPID keys are minted once on the volume.
11. Signals reference — the engine
How a condition becomes a card, a claim, a record and an alert. The full per-event reference with every threshold, stamp and knob is a separate page: Signal, study & shadow reference.
Pipeline in one pass
data (hourly spine · daily · funding · OI · Coinalyze · Finnhub earnings · EDGAR) ↓ studies in-sample base rates, per market per event, memoized to the data version ↓ signals live surveillance: condition fires → intensity 0–50 + evidence 0–50 ↓ ledger claim frozen at fire: mark · side · void · target · 30d σ · context stamp ↓ gates episode re-arm · universe whitelist · geometry (no inverted or degenerate levels) ↓ resolve at horizon: realized (play-signed) + stop-aware track + bracket track ↓ records blend back into evidence · no-edge cap · earned confluence · variant promotion ↓ surfaces Signals cards · Actionable board · drawer record · ticker history · AI report ↓ stream one sequenced event log → browser toast / bell · Telegram · push
Scoring
- Evidence is expectancy-centred: an event whose average outcome is ≤0 scores 0 evidence and wears neg exp, however loud the condition. Otherwise min(1, avg/scale)·30 + max(0, hit−0.5)·2·20, with scale = 0.5R for R-united events and 0.8% for %-united ones.
- Source: the market's own study if n ≥ 8; else the asset-class pool if n ≥ 12 at a 0.7 discount (unproven); else a token 6–8 points. Crypto pools in its own bucket.
- Live blend: once an event has ≥5 live resolutions, stats blend study and live record with weight resolved/(resolved+25). No-live-edge cap: ≥10 resolved, hit <50%, median ≤0 → evidence capped at 8.
- Universe lanes: 40 signals per universe, so a volatile crypto day cannot evict the equity board.
Ledger mechanics
- Frozen geometry — at fire: entry mark, play side, void level, target (absolute), 30-day daily σ for R normalization, score and prime flag. Never re-derived, never moved.
- Episode re-arm — if a claim resolves while its condition still fires, no new claim opens until the condition lapses for a full build. One episode, one claim.
- Units — premium in bp; the R-united set (big move, breakout, breakdown, funding flip, vol shift, OI flush, funding–price divergence, the shadows, cascade, funding extreme) in R via σ; everything else in %. Crypto levels are built in log space so σ constructions cannot produce negative prices.
- Context stamp — funding and its percentile, 5d OI change, position in the 30d range, the benchmark's 24h move, session or hour-of-day bucket, day of week — so records can be sliced later. Absent means unknown, never null-padded.
- Persistence —
ledger.jsonplus an append-only archive on trim; optional weekly off-site backup to a GitHub repo.
Event roster
| Event | Fires when | Play · horizon · unit | Universe |
|---|---|---|---|
Big move bigmove | Today's move ≥ 2σ of the market's own trailing 30d daily returns (variants 1.5 / 2 / 2.5). | Continuation; 1d (crypto 12h); R. Stop 1σ against the move. | both |
30d-high breakout breakout | First close/mark above the prior 30-day high. | Long; 5d (2d); R. Stop = the breakout level. | both |
30d-low breakdown breakdown | Close below the prior 30-day low. | Short; 5d (2d); R. | both |
Outsized gap gap | Live close→open move ≥0.75σ of the name's own gap distribution, while the cash market is closed. | Continue, or fade when the name's proven record fades its gaps; resolves at the next session close; %. | stocks |
Funding flip fundflip | Day-summed funding changed sign after ≥3 days pinned the other way. | Side of the new crowd; 3d (2d); R. Stop 1σ. | both |
Squeeze setup squeeze | Composite ≥60: crowded shorts × OI building × price pressing the range highs. | Long; 3d; %. Target = hi30 + 0.382 × range; stop = lo30 + 0.25 × range. | stocks |
Long unwind unwind | Mirror composite ≥60: crowded longs × OI building × price near range lows. | Short; 3d; %. | stocks |
OI flush oiflush | 7d ΔOI collapsed below −2σ of its own distribution while price fell — forced deleveraging exhausting itself. | Long; 5d (2d); R. | both |
Funding–price divergence fpdiv | 7d price z-score diverging from the funding trajectory (|z| ≥ ~0.8σ). | Divergence side; 3d (2d); R. | both |
Premium dislocation prem | Perp vs oracle |z| ≥ 2σ of its own 7d premium baseline. | Revert to the oracle; 12h; bp. No stop. | stocks |
Overnight drift ondrift | The name's summed off-hours drift over ~21 windows, z-scored across the universe, |z| ≥ 2 and |drift| ≥ 1%. | Drift side over the next 5 overnight windows; %. No target, no stop. | stocks |
Trend retest tretest / tretestdn | The Trend board's RETEST badge on a board-visible row (≥3/4, top 10). | Long / short; 5d (3d); %. Void = the rung's EMA21, target = prior swing. | both |
Cascade exhaustion casc | A ≥3σ liquidation spike with OI falling, aged 1–24h, mark between the flush extreme and the pre-cascade level. | Fade the flush; 12h; R. Void = flush extreme, target = pre-cascade close. | crypto |
Funding extreme fundext | Funding at the 90th+/10th− percentile of its own 31 days, same sign for 24h. | Fade the crowd; 2d; R. Target = geometric mid of the 30d range. | crypto |
| Context flags that render but never ledger: vol expansion (10d realized vol above its 90th percentile), range compression (bottom decile — feeds confluence on a breakout out of it), volume surge (24h ≥2.5× the 30d norm). | |||
Strategy shadows earning a record before any UI: universal gap fade, breakdown reclaim, failed-breakout fade, MA50 pullback, post-earnings drift, 5-minute liquidity sweep, outsized-wick fill, round-figure front-run. Plus airead, the analyst accountability bucket. | |||
Actionable gates
Horizon ≥ 3d equity / 2d crypto · ≤ 10 bars in trigger · CONFIRMED needs n ≥ 8, positive average and positive EV here, no no-edge cap · net R/R between 2.0 and 20 · void ≥ 0.05% (plus a σ-relative floor). R/R is repriced from the live mark every build, so a chased setup dies on its own.
12. Data sources, cadences, persistence
Sources
| Source | Feeds | Cadence | Needs |
|---|---|---|---|
| Hyperliquid REST + WebSocket | Universe, price, funding, OI, candles (1m for the Focus seats, 5m archive, hourly spine, daily), oracle, wallet positions. (Build 2026.10.16-131) every other HIP-3 dex's universe and per-asset funding settings (perpDexs, metaAndAssetCtxs), realized funding for the estimate's live check, and predictedFundings for the main dex. | Universe every 30s (slow reconciliation while the socket is healthy); snapshot rebuilt ~15s; new listings detected within 30s and backfilled first. Other dexes: hourly by REST, contexts from the socket. Funding check: six names an hour. (Build -132) A second socket streams each market's forming 5m bar; it is display only — the provisional live edge of the 5m charts and the drawer's forming hour — and never enters the archive, which REST alone fills on the same cadence with the socket on or off. | nothing |
| Finnhub | Earnings calendar and EPS actuals, company news, basic financials. | Calendar ~6h in chunked windows; news every few minutes per name. | FINNHUB_TOKEN |
| FRED | Macro release schedule and prints; the Housing and Liquidity boards. | ~6h; Liquidity also after the Thursday H.4.1 release. | FRED_KEY |
| SEC EDGAR | Filings feed, Form 4 insiders, 13F and N-PORT holdings, XBRL facts. | Rotation ~2 names/minute; full pass ~40 minutes. | SEC_CONTACT (their fair-access policy) |
| House Clerk | Congressional PTRs. | Ingest lane; OCR for scanned PDFs. | nothing |
| Senate eFD | Senate PTRs (build 2026.10.16-131). | Every 6 hours: agreement, incremental search, new filings only, a request every few seconds; paper filings listed, not parsed. | nothing |
| federalreserve.gov | FOMC meeting dates (build 2026.10.16-131). | Weekly conditional GET of the calendar page; merged over the built-in table (the page wins for future dates); kept on the volume in fomc.json. An ops warning when it cannot be read or no decision is scheduled within ~8 months. | nothing |
| Coinalyze | Aggregated CEX liquidations and OI for crypto names. | 15-minute buckets. | COINALYZE_API_KEY |
| Telegram | Alert delivery, DM escalation, reset codes, news channels. | Inbound poll every 20s; outbox drained every 1s. Queued messages (quiet-hours holds included) survive a redeploy for up to 24h; Telegram-sync edits do not. | TG_BOT_TOKEN |
| X (oEmbed) | Tweet cards in Messages. | On link, cached per id. | nothing |
| Anthropic / OpenAI | Reports, the ask fallback, the brief, the Landscape, the news/sector classifier. | On demand and scheduled. | ANTHROPIC_API_KEY or OPENAI_API_KEY |
Retention
- Open interest — append-only log, 365 days: full resolution for 31, thinned to hourly beyond (the crypto roster keeps a flat 31); pruned daily. OI accrues and cannot be re-fetched, so this is the one series a lost volume actually loses.
- Candles — a SQLite archive: 5-minute bars for ~20 days, the hourly spine for ~180 days, daily bars back to each listing's birth — crypto included: each coin is seeded once, in one request spread through the deep lane's own worker under the rate limiter, then captured a closed day at a time and never evicted (a coin that leaves the top 60 keeps its history and resumes from it). Crypto's daily refresh reads that archive and asks Hyperliquid only for the last few days (build 2026.10.16-131). Only spines that went stale while the server was down are re-fetched after a redeploy.
- Funding history — seeded from the OI samples and topped up by a best-effort backfill; the heatmap hatches what it cannot see.
- News 72h · messages 30d in a 1-to-1, 7d in groups (pins exempt) · ledger trimmed to an archive, never deleted.
What lives on the volume
DATA_DIR (mount it at /data). Every JSON file is written atomically (temp file, then rename) and warm-loaded on boot.
| File | Holds |
|---|---|
oi.log | The open-interest sample log. |
candles.db | The candle archive (5m, hourly, daily). |
accounts.db | Members, invites, sessions' epochs, messages, threads, attachments, reactions, preferences, wallets, audit log. Rotated backups in backups/ (or ACCOUNTS_BACKUP_DIR). |
hourly.ndjson · regime.json | The hourly candle spine and the regime state. |
ledger.json · ledger-archive.jsonl | The signal ledger and its trimmed archive. |
earnings.json · macro.json · fomc.json | Earnings prints and history; the macro calendar cache; the last good read of the Fed's FOMC calendar (build 2026.10.16-131). |
housing.json · liquidity.json | The FRED boards. |
news.json · tgchannels.json | The news cache; the Telegram channel configuration (its own file so a trimmed cache can never lose it). |
fundamentals.json | Finnhub basic financials cache. |
triggers.json · alertpush.json · alertrules.json · webhooks.json | The event ring and announced set; Telegram recipients and their prefs; server-side metric rules; members' webhook endpoints, their signing secrets and the webhook outbox (build 2026.10.16-131; written owner-only). |
features.json | The warm feature cache: per-market reference prices and features, the daily bars and the downsampled premium baseline, so a redeploy serves a warm table. |
navgroups.json · flags.json | Nav menu labels; feature visibility and runtime flags. |
baskets.json · notes.json · focus.json · whale.json | Custom baskets; notes; the frozen Focus stamps and floors; the 13F watchlist. |
whale13f.db · insiders.db · congress.db | 13F/N-PORT holdings; Form 4 transactions; PTR trades. |
derivs.log · derivmap.json | Coinalyze samples and the venue map. |
duel.json · sector-audit.json | The Score duel record; the classification audit overlays. |
ai-reports.json | Cached reports and the AI budget state. |
webpush-keys.json · loop-history.json · volume-heartbeat.json | VAPID keys; the event-loop p99 ring; a heartbeat that proves the volume is writable. |
precomp/ | Brotli and gzip copies of the client files, named by content hash, so a restart of the same build skips the compression. Swept to the running build's files on every boot; safe to delete. |
Write cadence and durability
- News cache —
news.jsonis rewritten only when a feed brought something new (or an item was purged or reclassified), at most once every 30 seconds, and once more on shutdown or crash. A hard kill can cost the last 30 seconds of headlines; the feeds refill them on the next rotation. - SQLite files — every database runs in WAL mode with
synchronous=NORMAL: a commit is not fsynced on its own, the log is fsynced at each checkpoint. A crash or kill of the process loses nothing; only an operating-system crash or power loss can roll back the last few commits (a message, a prefs save) since the previous checkpoint. The files are never left corrupt either way.accounts.dbtakes the same setting as the archives so a message or prefs save no longer waits on the disk. - Client files — the server starts listening before the compressed copies are ready (a second or two after a deploy); in that window the same files are sent gzipped per request.
13. HTTP API
Everything the site shows comes from these routes. All JSON, under /api, unless noted. The reference below lists every route the server registers; a test fails the build when a route is added without appearing here.
How a request is judged
- Site gate — with
SITE_PASSWORDset, every route needs a signed-in session (cookie), except the doors and the few routes marked Open. Signed out is401. Once accounts exist, a shared-password session — and HTTP Basic, which is the shared password — is sent to/claim(401 {error:"claim-account"}on/api); before then Basic passes while the legacy door is open, and never onceLEGACY_SHARED_PASSWORD=0. - Feature gate — a route owned by a tab or action in the feature manifest answers
403 {error:"feature-gated", feature}when that feature is closed to you. The column below names the key and its default audience; the operator can change it in Admin › Features. The gate judges the decoded path and the route that will actually run, so a percent-encoded or re-spelled path meets the same gate; an undecodable path is400. - Operator — routes marked Operator re-check the admin session inside the handler as well (
403), whatever the feature flag says. - Account — personal routes need a member account (not the shared password): messages, prefs, positions, usage, the adopt flow.
- Writes — every non-GET refuses a cross-site request (
Sec-Fetch-Site: cross-site→403); cookies areHttpOnly,SameSite=Lax,Secureover HTTPS. Bodies are size-capped per route (413past it). - Limits — a limited route answers
429, usually withRetry-After. Per-IP limits key on the lastX-Forwarded-Forelement only whenTRUST_PROXYis on (see Configuration). - Caching — cached payloads carry an ETag and revalidate (
304); personal and operator bodies areno-store.GET /api/events(Server-Sent Events) tells a client when to re-fetch.
Access column: Open = no credential · Site = behind the site gate · Account = a member account · Operator = the admin session, re-checked in the handler · key (default) = the feature key that gates it and who sees it by default.
Doors and open routes
| Route | Access | Returns · limits |
|---|---|---|
GET /api/health | Open | Liveness, build, staleness. Signed-in members also get poll/feed stats; the operator gets the rest (CSP report ledger, data dir, cache: 200 vs 304 counts per route group since boot — snapshot, daily, series, candles, other keyed, other cached — plus the keyed cache's fill) and lanes: every poll, feed, worker and scan with its last success, last error and in-flight stamp, graded hung / stale at 3× its interval (shown in the admin panel's Lane health fold). A lane held back on purpose — the daily workers and the deep-history seed waiting for the warm-up's hourly pass — reads waiting, not stale; a lane whose key is unset reads off (the macro calendar without FRED_KEY reads ok, labelled "FOMC table only"). |
POST /api/csp-report | Open | Browser CSP violation reports. 8 KB body; 10 a minute per IP and 120 site-wide; reports about another host are counted, never stored; control characters stripped. |
POST /api/dm/request-invite | Open | The signed-out "request an invite" — pings the operator. 1 per IP per hour. |
GET|POST /login · GET|POST /logout | Open | Sign in (account, break-glass ADMIN_PASSWORD, or the legacy shared password) and out. 8 wrong passwords per IP = 15-minute lock. |
POST /login/passkey/options · POST /login/passkey | Open | (Build 2026.10.16-131.) Sign in with a passkey: the options hand out a single-use challenge (5 minutes, bound to this browser by an HttpOnly cookie, to the origin and to the ceremony); the assertion must verify against a registered passkey — origin, RP ID, user presence, user verification (required here: the passkey is the only factor), signature and a counter that never goes backwards. The same endpoint finishes the passkey step that /login, a reset link (/join) and /reset/code answer with ({ok:false, step:"passkey", options}, no session) on an account that requires a passkey. A failed assertion counts against the login lock; options are capped at 30 per IP per 10 minutes. 400 when PUBLIC_ORIGIN does not cover the address. |
GET /join/:code · GET|POST /join | Open | Redeem an invite or a reset link. Same per-IP damper. |
GET|POST /reset · GET|POST /reset/code | Open | Forgot password: a 6-digit code over Telegram. 5 requests per IP per hour; 3 codes and 5 guesses per account per hour (a re-send does not refill the guesses). |
GET|POST /claim · GET|POST /bootstrap | Open (a legacy session / the admin session) | A legacy shared-password member makes their own account; the operator creates account #1 (needs the admin session, closes once any account exists). |
Markets and studies
| Route | Access | Returns · limits |
|---|---|---|
GET /api/snapshot | Site · markets (everyone) | The market table for both universes: price, funding, %-changes, reference prices, momentum, vol, OI deltas, earnings badges, and note digests (only for callers the Notes tab is open to). |
GET /api/snapshot?lean=1 · GET /api/snapshot/slow?since= | Site · markets | What the page itself polls (build 2026.10.16-131): the same table with each row's slow fields left out (reference prices, the 30-day features, sector, industry, name, news lane, home market, 200-day average) plus slowVer; /slow answers only the rows whose slow fields changed after since (every row when since is absent, malformed or from before a restart). The page merges the two back into exactly the /api/snapshot rows. |
GET /api/daily | Site · markets | Daily bars per market (correlation, beta, sparklines, backtest, drawdown). |
GET /api/daily/hist · GET /api/daily/tail | Site · markets | The same, cut by cadence (build 2026.10.16-131): hist holds the closed bars (before today, UTC), rebuilt about once a day under a content-hash version hv; tail holds everything that moves during the day — today's bars, today's funding, the close anchors, the session states — as per-name differences against the hv it names. The page fetches the tail whenever the daily data moves and the history only when hv changes, and merges them into exactly the /api/daily body. |
GET /api/series?coin= | Site · markets | Per-market OI and funding history. |
GET /api/candles?coin= | Site · markets | days=1..60 hourly OHLCV; tf=1h|4h|12h|1d the exact ladder series (with fast=&slow= for a custom MA pair); res=5m the 5-minute archive and res=4h|12h|1d the deep archive, with from=&to= (epoch ms) and max=200..6000 points. Ranges are clamped and snapped before caching. (Build -132) live=1 with res=5m or days=: the display-only live edge from the candle socket is folded on (bars newer than the archive's last; the body names the first provisional bar as live.from), and the ETag carries the socket's version so a new push is never answered with a 304. Without the socket (WS_CANDLES=0) the response is exactly the plain one. |
GET /api/events | Site | The Server-Sent Events stream: data versions (the board's and, since build 2026.10.16-131, the daily bars'), alert sequence, your own message cursor, prefs and position pokes. 200 streams site-wide, 4 per account or IP. |
GET /api/analytics?u= | Site · sessions (everyone) | The session studies for one universe. |
GET /api/funding?u= | Site · funding (everyone) | The funding heatmap. |
GET /api/crossdex · GET /api/crossdex?coin=&depth= | Site · crossdex (operator) | The same stock on other HIP-3 dexes (build 2026.10.16-131). The board: every matched pair — coin, other, dex, both legs (x, o: mark, oracle, hourly funding, OI $, 24h volume), sprMark / sprOra (bp, other vs xyz), fx / fo / dApr (funding APR %), carry (long coin, perDay $ per $10k a leg, feeRt, net1d, beDays, apr), stale, growth; refused (same name, oracles > 10% apart: why, ratio), ambiguous, dexes (each with its context age and source), takerBp (the assumed fee), refreshedAt, err. Rebuilt at most every 15 s; ETag on its content clock. ?coin= (no-store): one xyz name's pairs; depth=1 also reads both legs' order books (spread, depth ±1%, average fill for $10K / $50K), cached 20 s and drawn from the shared on-demand book budget (busy past it). depth is 0 or 1; a malformed coin is a 400. |
GET /api/fundnext | Site · fundnext (operator) | Next hour's funding, estimated (build 2026.10.16-131), per market: r (the hourly rate paid at nextAt), src (htd: the hour so far recovered from the feed's own funding figure; own: from this server's premium samples, inside the clamp's flat band; feed: no sample yet, the feed as published), w (share of the hour observed), m7 (7-day mean), p24 / p168 (projected sums over 24 h / 168 h, reverting to m7 at phi), prm (the deployer's multiplier / interest / clamp, when not the defaults). phi / phiN / phiSrc: the pooled AR(1) coefficient and its sample. skill: the live check — estimates frozen at :10, :30 and :50 scored against realized funding (six xyz names an hour), mean absolute error in bp per hour for the estimate, the feed's figure at the same moment and the last realized hour, per checkpoint and pooled; ok is true once the estimate beats the last hour on ≥ 30 scored hours, false if it does not (then show is false and the clients blank it), null before that. hlPred (crypto on): Hyperliquid's own predictedFundings against the feed's funding for the main dex. Rebuilt at most every 30 s; ETag on its content clock. |
GET /api/carry?scope= | Site · funding (everyone) | Carry pairs (build 2026.09.30-127): among the top 40 by OI in one universe (xyz or main), pairs with ρ ≥ 0.6 on 60 daily returns whose 7-day funding pays a hedged holder — the long leg, the short leg and its hedge ratio, ρ, carry per year on gross notional (7-day and now), the spread z-score with its week-clustered SE (zSe), and the 30-day carry with a stability word (apr30, stab: stable · fading · flipping; build 2026.10.15-130). Rebuilt at most every 5 minutes; scope-prefixed ETag. |
GET /api/gaps · GET /api/gaps?coin= | Site · gaps (operator) | The perp-implied opening gap board (build 2026.09.30-127) for US-session names: while the cash market is shut, the implied gap (perp now vs the perp at the last cash close), the capture fit at the latest checkpoint passed (20:00 / 04:00 / 09:00 ET vs 10:00 ET over 180 days), expected gap and 90% band; while it is open, this morning's 09:00 implied vs 10:00. Each fit carries se, bLo/bHi (90% CI on the capture slope, build 2026.10.15-130). home: the KRX/TSE/HKEX/SSE listings on their own exchange's clock, each row with ex, state, opensAt, its checkpoint labels and settle (10:00 local). ?coin= adds that name's last eight nights (no-store; US-session names). |
GET /api/attrib?coin=&win= | Site · markets (everyone) | Why it moved (build 2026.09.30-127): the move over h1|h4|d1|d7 split into market (90-day β × the benchmark), sector (peers beyond their own market share) and its own; the headlines, filing or print inside the window. No-store. |
GET /api/depth?coin=&usd=&capbp= · GET /api/depth/board | Site · markets (everyone) | Order-book depth (build 2026.09.30-127). /api/depth: one market's live l2Book (read at most every 15 seconds, 20 levels a side; on-demand reads share 30 a minute site-wide, past which the last book read is served, flagged busy; a read queued behind the rate limiter for more than 4 seconds is answered the same way while it finishes for the next ask) — spread, resting size within ±0.5/1/2% of mid, top levels, and the average fill against mid for $10K, $50K, $250K and an optional usd (≤ 50M) on each side. (Build 2026.10.16-131) capbp (1–500) adds cap: per side, the largest order whose average fill stays within that many bp of mid and inside the published book — the what-if panel's size cap. /api/depth/board: the Depth ±1% column's source, the server's slow rotation (two books every 15 s, busiest first; an entry is kept an hour). |
GET /api/since?t=&w=&coin= | Site · markets (everyone) | Since you left (build 2026.09.30-127): t = the browser's last look (within 30 days), w = up to 60 watched coins. Their moves since t, the tape's top movers, the public trigger events since (and your own rule fires), earnings reported since; for a member also the calls that closed since. coin (build 2026.10.15-130, the drawer's "since your last look" chip): that one name's move, and only its trigger events and closed calls, filtered before the list caps. No-store. |
GET /api/rewind?t= | Site · rewind (everyone) | The board at a past moment (build 2026.09.30-127): the archive's range, and with t the capture at or before it (within six hours) — per market: price, 24h %, funding and its percentile, OI, 1-day ΔOI, 24h volume, premium, sector. Captured every 15 minutes into snapshots.db; kept REWIND_DAYS (default 21). |
GET /api/trend | Site · trend (everyone) | The EMA 13/21 ladder; fast=&slow= for a custom pair. |
GET /api/corr-crypto?w= | Site · corr (everyone) | The crypto intraday correlation grid. |
GET /api/ema-feed | Site · ematouch (operator) | EMA Touch: every H4/D1 × EMA50/200 line within 1.5σ, plus the last 48h of touch and near-band cards. |
GET /api/retest-study?u=&def=&cd= · GET /api/duel | Site · backtest (operator) | The D1 retest study (def=board|touch, cooldown bars); the Score duel record. |
GET /api/fundamentals?coin= · GET /api/derivs?coin= | Site | Finnhub financials for an equity; Coinalyze context for a coin. |
POST /api/derivs/refresh | Operator · derivs.refresh | Force a Coinalyze refresh for one coin (per-coin cooldown). |
GET /api/fund/:t · GET /api/etf/:t | Site | SEC XBRL facts for a filer; N-PORT holdings for a fund. Cached; at most 20 new (uncached) lookups a minute site-wide. |
Signals, calendar, news
| Route | Access | Returns · limits |
|---|---|---|
GET /api/signals · GET /api/ledger?coin=&ev= | Site · signals (everyone; equity slice operator) | Live signals; the claim ledger and records. |
GET /api/triggers?since= | Site · signals | The alert event stream past your cursor; your own rule events only. Operator-only classes (ops, ma50, touch200, touch50, levels) are in the operator's stream only. regime lists the positioning episodes still open (start time, value now). |
GET /api/actionable | Site · actionable (operator) | The Actionable board. |
GET /api/export/ledger | Site · export.ledger (everyone) | The ledger as a download. |
GET /api/earnings · GET /api/earnings/setups | Site · earnings (everyone) | The calendar (upcoming, reported, reaction study); pre-earnings setup cards. |
POST /api/earnings/void · POST /api/earnings/backfill | Operator | Void a feed-garbage print; run the history backfill. |
GET /api/news · GET /api/news/channels | Site · news (everyone) | The tape; the Telegram channel list. |
POST /api/news/channels | Operator · news.write | Replace the channel list. |
GET /api/housing · GET /api/liquidity | Site · housing / liquidity (operator) | The FRED boards. |
GET /api/focus | Site · focus (operator) | Today's frozen list. |
GET|POST /api/focus/limits | Operator · focus.limits | The liquidity floors and the scan behind them. |
GET /api/whale | Site · funds (operator) | The 13F watchlist; fund=, holds=, season= views. |
POST /api/whale/watch | Operator · whale.write | Curate the watchlist (search, pull, add, remove, mute). seen (clear a fund's NEW badge for the desk) is the one verb a non-operator may send when the gate admits them, and it needs a signed-in member — 401 when signed out. |
GET /api/congress · GET /api/insiders | Site · congress / insiders (operator) | The PTR and Form 4 feeds. /api/congress (build 2026.10.16-131): chamber=H|S scopes the feed, the counts and the filer search to one chamber; a feed page carries paper — the newest Senate paper filings (member, date, link), listed, not parsed. /api/insiders (build 2026.10.15-130): cluster=1 keeps only open-market buys inside an insider cluster (3+ distinct insiders buying one ticker within 10 days); every feed page carries clusters — the spans for its tickers; ?ticker=&days= returns the roll plus the newest cluster in the window. |
POST /api/congress · POST /api/insiders | Operator | Lane maintenance (ingest, parse, requeue, backfill, status). (Build 2026.10.16-131) {op:"senate", n?, days?} starts a Senate eFD sync now. |
GET|POST /api/notes | Site · notes (operator); writes Operator · notes.write | Per-ticker notes. |
GET|POST /api/baskets · GET /api/ratio?num=&den=&tf= | Site · baskets (operator); writes Operator | Custom baskets; ratio pair candles. (Build 2026.10.16-131) each basket's daily series is its benchmark index (weights, late listings, delistings — notes lists who joined, who left, who has no data); a create may carry weights {TICKER: number}, one per member (normalised; ≤ 40 keys). |
GET /api/baskets/live | Site · baskets (operator) | (Build 2026.10.16-131) every server basket priced off the snapshot: level (the daily index carried to the live marks), ret h1 / h4 / d1 / d7 / d30 (the weighted mean of the members' own window returns; null under 60% coverage), cov, wt. ETag'd per snapshot. Reads the Δ vs ⬒ column and the ⬒ rows. |
GET /api/sector-audit · POST /api/sector-audit/run|apply|revert|ack | Operator | The classification audit. |
AI
| Route | Access | Returns · limits |
|---|---|---|
GET /api/ai-report?coin= · GET /api/ai-reports | Site · report (everyone) | A cached report and your quota; the shared recent-reports feed. |
POST /api/ai-report | Signed in · ai.generate (operator) | Generate a single-name or group report. Per member 3 a day and 20 a month, plus the shared daily pool; the operator is exempt. |
POST /api/ask | Signed in · ai.ask (operator) | The terminal's AI fallback. 5 a day per member plus the shared pool and a per-minute window; the question is capped at 1,000 characters, the body at 256 KB. Answers are cached per member. |
POST /api/dm/call-read | Account · ai.ask + dm.ask | An AI read of a chat call (text capped at 1,200 characters). |
GET /api/ai-status · GET /api/ai-sectors | Site | Lock and budget state (with held: AI work in flight — yours and the group's); the sector list for group reports. |
POST /api/ai-unlock · POST /api/ai-reset · POST /api/ai-lock | ADMIN_PASSWORD | Unlock unlimited AI for this browser; reset the daily budget; re-lock. 8 wrong passwords per IP lock it for 5 minutes. |
Without an account (shared password or Basic), AI quotas are counted per IP address.
Alerts and Telegram
| Route | Access | Returns · limits |
|---|---|---|
GET /api/alerts | Site (yours) | Your linked recipients, classes, quiet hours, schedules and a pending link code. |
POST /api/alerts/link|unlink|prefs|classes|test | Site (yours; operator for any) | Mint a link code (3 live per caller), unlink, set quiet hours / thresholds / schedules, pick classes, send a test (30 s cooldown per caller). |
POST /api/alerts/claim · POST /api/alerts/brief-test | Operator | Claim an unowned recipient; fire the brief / landscape / desk now. |
GET /api/alerts/adoptable · POST /api/alerts/adopt | Account | Adopt a chat the bot already serves: a 6-digit code goes to that chat. 3 codes per chat per 10 minutes and 5 guesses in total; a chat whose guesses run out stays locked for the window. |
GET|POST /api/alerts/webhooks | Member (your own) · webhooks (operator) | (Build 2026.10.16-131) Your webhook endpoints. GET lists them — label, kind, a mask (host + last four), classes, quiet, status, queue, sends this minute; never the URL or a secret. POST {op}: add {label, url, classes?, quiet?} (max 3; answers the generic endpoint's signing secret once) · update {id, label?, classes?, quiet?, enabled:true?} · remove {id} · test {id} (one immediate post; 429 within 30 s of your last test). 8 KB body cap; 401 without an account. |
GET|POST /api/alerts/rules | Site (yours; operator for any) | Your metric rules: list, add, delete. 60 per owner, 2,000 site-wide. (Build 2026.09.30-127) a rule may carry and: up to three extra level conditions (>, <, abs>; earnD = ET days to the next earnings print) that must hold when the first one fires; {text} (≤ 300 characters) adds a rule described in plain English or the /alert grammar and answers with echo, what will run. (Build 2026.10.16-131) basket (a built-in or saved basket's name, instead of a ticker) targets the basket's level (px) or its h1 / h4 / d1 / d7 / d30 return — no AND extras; 403 feature-gated where baskets are not open to you. The same per-owner cap. |
Messages
| Route | Access | Returns · limits |
|---|---|---|
GET /api/dm · GET /api/dm/sync?since= · GET /api/dm/:id | Account · dm | Threads, directory and boards, plus your current sync cursor (build 2026.10.15-130: a fresh page starts its sync there instead of pulling every retained message; a thread's history is read when it is opened); the cursor sync (your cursor is your own newest message); one thread's history. Every message carries a version v that rises on any change to it (edit, delete, pin, reaction, call close, note) — a client holding two copies keeps the higher. |
GET /api/dm/presence | Account · dm | The Messages tab's 45-second tick (build 2026.10.15-130): who is online, everyone's last-seen, your cursor, and per thread only {id, unread, lastAt, hidden, muted}. The tab pulls the full GET /api/dm only when this says the thread list changed, and when you come back to the tab. |
GET /api/calls/copy · GET /api/calls/portfolio · GET /api/calls/follows · GET /api/calls/insights · GET /api/calls/h2h · GET /api/calls/recap | Account · calls | Copy trading over the public record: one caller’s simulated follower (uid=, delay=0|15m|1h|1d, size=eq|conv, cost=0|1), your paper copy portfolio, who you follow, consensus and calibration, and a head-to-head (a=, b=); the weekly recap (week=YYYY-MM-DD, default last week). |
GET /api/calls/counter?coin=&side=&days= | Account · dm | The case against a call (build 2026.09.30-127), asked by the call confirmation: a crowded side (funding percentile), the carry it pays over the horizon, earnings inside it, live signals the other way with their record, a stacked trend it fades, range extremes, a stretched ribbon or premium, the desk's open calls the other way. Up to six, strongest first. It never blocks a call. |
GET|POST /api/calls/challenge | Account · calls | The weekly desk challenge (build 2026.09.30-127). GET (?week=YYYY-MM-DD, the ET Monday): that week's board — hidden until the lock except the count and your own pick; entry at the first cash open, live or final marks, rank — the week new picks go into, and the season. POST {ticker, side} picks (or replaces) yours for the next unlocked week; {remove:true} withdraws it. Locks at the week's first US cash open; the result is posted to the 📣 Calls recap topic once every pick has its closing price (or a day after the close). |
GET|POST /api/calls/duels | Account · calls | 1v1 duels. GET: your invites (incoming, sent), live and finished duels, the desk’s public duels, your record (Elo, W–L–T, head to head) and the duel ladder; ?id=N one duel’s card and its trash talk (yours, or a public one once accepted; 404 otherwise). POST {action}: create (opponent, fmt pvp | opp | dd, window, entry, markets, stop, ko — the max drawdown, 0.5–50, pub, talk, ticker + side, replaces to answer an invite with new rules) · accept · decline (mute) · cancel (an invite nobody has accepted, only) · pick · draw (on; both, inside the first 10% of the match) · talk (body, 280 characters) · mute (who, on). |
GET|POST /api/calls/contests | Account · calls | Custom contests. GET: open for picks, live, finished (14 days); ?id=N one contest’s card and its talk. POST {action}: create (title, rules — window, entry, markets, side, picks, scoring, race, stop, ko, hidden; refused in words when a value is outside the menu) · pick (id, slot, ticker, side) · unpick · cancel (its creator, before the lock) · talk. |
POST /api/calls/follow | Account · calls | Follow or unfollow a caller ({uid, on}): their next public call reaches your phone, and your paper portfolio copies them from that day. |
GET /api/calls · GET /api/calls/board | Account · calls | The public calls record (by= a person, ref= a market, id= one call) and the leaderboard (win=30|90|all). A DM call comes without its words; all=1 is operator-only. Each call carries dd (its max drawdown as a direction-adjusted return, ≤ 0; null when not measured) and ddAt; each caller carries avgDD, ddN and deepDD. |
GET /api/dm/search · GET /api/dm/calls · GET /api/dm/targets · GET /api/dm/export/:id | Account · dm | Search your threads; the calls record in your own conversations; price targets; one thread as JSON (from your own clear onward), or with ?format=ndjson streamed as NDJSON — one JSON object per line: {"type":"thread",…} (name, kind, members, exportedAt), then {"type":"message",…} per message in id order (same fields as the JSON export's messages), then {"type":"end","messages":N}; a file without the end line was cut short. all=1 views are operator-only. |
GET /api/dm/file/:id | Account · dm (members of that thread) | An attachment: sniffed type, nosniff, sandboxed, downloads unless a verified image or voice note; revalidated on every view. |
POST /api/dm | Account · dm | Every write verb: send, edit, delete, react, pin, read, mute, typing, groups and boards, calls (a send may carry a thesis; callNote appends an update or the post-mortem). A send may carry a nonce (8–64 of A-Z a-z 0-9 _ -): the same nonce from the same member within 10 minutes returns the message already stored (deduped:true) instead of posting it twice — the offline queue's retry key (build 2026.10.15-130; held in memory, so a redeploy forgets it). 20 messages per 10 s; 16 KB body; edits and call changes need you to still be in the thread; command results cannot use the alert / target labels the server posts under. |
POST /api/dm/upload | Account · dm | An attachment (png, jpeg, gif, webp, .txt, voice). 8 MB each; 30 uploads or 96 MB per member per 10 minutes; paused when the volume runs low on space. |
GET /api/dm/push-key · POST /api/dm/push-sub | Account · dm | Browser push: the public key; subscribe or unsubscribe. Only real push services are accepted (FCM, Mozilla, Windows, Apple) and a subscription stays with the account that registered it. {removeId} removes one of your devices by its listed id. |
GET /api/push/devices | Account · dm | Your browser-push devices (build 2026.10.15-130), newest first, at most 10: {id, svc, ua, addedAt} — the id is a hash of the subscription, never the endpoint or its keys. The Messages tab lists them under Browser notifications, marks this device, and removes one with POST /api/dm/push-sub {removeId}. |
POST /api/dm/targets | Operator | Run the price-target resolver now. |
Your account
| Route | Access | Returns · limits |
|---|---|---|
GET|POST /api/account | Site / Account | Who you are; change your password (the current-password check uses the login lock). |
GET|POST /api/prefs | Account | Synced watchlist, layouts and settings (allowlisted keys, 64 KB per value). |
GET|POST /api/positions | Account | Your linked wallet and its positions. Unlinking it (build 2026.09.30-127) also deletes its imported fills. |
GET /api/book/whatif?coin= | Site · whatif (operator) | (Build 2026.10.16-131) the what-if panel's inputs: the candidate name's replay leg (90-day β, funding, closed daily closes), and for a member with a linked wallet the book's own legs exactly as /api/book/risk builds them, the replay window with and without the candidate, the account equity, and the server's own run of the book (risk). member / wallet false say why the book part is absent. The wallet is always the caller's own. |
GET /api/book/risk | Account · book | Your linked wallet as one book (build 2026.09.30-127): beta dollars per 1% of the S&P and of BTC, the 7-day funding bill, today's positions replayed over 60 days (1-day σ, worst and best day, historical-sim 1-day 95% VaR and expected shortfall — var95, es95, build 2026.10.15-130 — each leg's risk share), clusters that moved as one bet (ρ ≥ 0.7), a linear stress line per benchmark. fund7dEst (build 2026.10.16-131, only where fundnext is open to the caller): the projected 7-day bill, + = you pay; null when a leg has no estimate. |
GET|POST /api/book/journal | Account · book | The fills journal (build 2026.09.30-127). GET: the imported fills folded into flat-to-flat trades, each tagged with a live signal with / against it at the open, whether you had called it, and the carry it started paying; P&L split by those tags. POST pulls new fills from Hyperliquid (from the last one, or 180 days back; up to three pages of 2,000 per sync, one import at a time site-wide) — once per member per 2 minutes (429), 502 when Hyperliquid does not answer. |
POST /api/usage · GET /api/usage/me · POST /api/usage/pause | Account (the beacon also Site, signed out) | The usage beacon (one per 30 s per page, 4 KB); your own usage card; pause or resume it. Signed out, the same beacon is counted as anonymous sitewide totals when public counting is on (no cookie or id; a daily-salted key held in memory only), capped at 20,000 visitors a day and 600 beacons a minute site-wide — past that it is dropped with a 204. |
Operator
| Route | Access | Returns · limits |
|---|---|---|
GET|POST /api/access | Operator | Members, invites and stats; mint or revoke invites, reset links, sign out, disable, promote, rename. (-131) Each member's passkey count and requirement; pk-clear removes a member's passkeys and their own requirement and signs them out everywhere, pk-ops switches the operator-wide passkey requirement — both audited. pk-clear, pk-ops off, and reset-link for yourself or for an account with a passkey requirement in force need a fresh check (403 {fresh:false} otherwise): fresh-pw here (your password, login lock and all) or a passkey through /api/passkeys; the break-glass sign-in is not asked. pk-clear on yourself is refused. The GET says whether this browser holds the check (fresh) and how many passkeys you have (pkMine). |
GET|POST /api/passkeys | Account · passkeys | (Build 2026.10.16-131.) Your passkeys (name, added, last used), whether one is required and why. Verbs: fresh-pw / fresh-options + fresh-verify (confirm it's you: your password — login lock and all — or a passkey; 5 minutes on this browser), reg-options + reg-verify (add, up to 5; needs the fresh check), rename, remove and require (both need the fresh check; the last passkey cannot go while one is required; a removal or the requirement switched on signs out every other session and re-issues this one — signedOutOthers: true). fresh-options and reg-options answer 429 past 20 per account (40 per address) in 10 minutes. 16 KB body. |
GET /api/access/dm · /api/access/dm/:id · /api/access/dm/search · /api/access/dm/audit | Operator | The message read-through; every thread read and search is written to the audit log. |
GET|POST /api/features · POST /api/nav-groups | Operator | Feature visibility; menu names and tab placement. |
GET /api/admin/usage · GET /api/admin/usage/member | Operator | Sitewide usage (members, and the public visitors' totals under pub); one member's drill-in (audited). |
POST /api/admin/usage/public | Operator | Switch public (signed-out) visitor counting on or off: {on: true|false}, 1 KB. USAGE_PUBLIC=0 still forces it off (the answer says forcedOff). |
POST /api/admin/usage/errors | Operator | Resolve or reopen a client error. |
GET|POST /api/admin/usage/digest · POST /api/admin/usage/digest/test | Operator | Weekly digest and lapsed-member reminder settings; send the digest now. |
Pages and assets
| Route | Access | Returns |
|---|---|---|
GET / · GET /index.html | Site | The app shell, with your feature flags, nav and identity injected (no-store). |
GET /docs · GET /docs.html · GET /docs/ref/:page · GET /docs/ref/img/:file | Site | This manual (sections about tabs closed to you are left out) and the reference pages. |
GET /app.js · /styles.css · /js/*.js · /fonts/* · /sw.js · /manifest.webmanifest · /icon.svg | Site (icon and manifest Open) | The client and PWA assets; build-stamped URLs cache for a year. The scripts and stylesheet are minified at boot (one file per module, never bundled) and served brotli/gzip; /fonts/ holds the self-hosted Space Grotesk and JetBrains Mono faces (.woff2) and their OFL license texts. |
14. Configuration — environment variables
Every variable the server reads. The value shown is the default when unset. Nothing reads a .env file automatically; on Railway set them on the service.
Storage and runtime
| Variable | Default | Meaning |
|---|---|---|
DATA_DIR | ./data | The mounted volume. Unset on Railway means an ephemeral directory inside the container — history resets on every redeploy. |
ACCOUNTS_BACKUP_DIR | <DATA_DIR>/backups | Where the rotated accounts.db copies go. |
PORT | 3000 | Listen port. Railway injects it; do not set it there. |
DEX | xyz | The HIP-3 dex whose universe the equities lane polls. |
CRYPTO | 1 | 0 disables the crypto universe entirely. |
TRUST_PROXY | on for Railway, off elsewhere | 1 = exactly one trusted edge appends the last X-Forwarded-For element, and per-IP limits key on it. Unset, it is on only when Railway's environment variables are present. 0 when exposed directly, or every caller could pick its own rate-limit key. |
PUBLIC_URL | — | The https://… origin used in push and Telegram links. Its origin is also allowed for passkeys. |
PUBLIC_ORIGIN | — | (Build 2026.10.16-131.) The address(es) members use, comma-separated https://host[:port], for passkeys: a WebAuthn ceremony runs only when the request's own origin (Host, and X-Forwarded-Proto behind a trusted proxy) is on this list (plus PUBLIC_URL's origin), and the passkey's RP ID is that host — never taken from the Host header alone. Neither set = passkeys work on localhost only and the passkey door says it is not configured (also in the boot log). Changing the host later strands passkeys registered under the old one. |
CLIENT_MINIFY | 1 | 0 serves the client scripts and stylesheet exactly as written instead of minified (same URLs, same caching) — for reading the code in the browser's devtools. A file that fails to minify is served as written anyway, named in the boot log. |
CSP_ENFORCE | 0 | 1 enforces the Content-Security-Policy (default report-only; watch the health ledger stay at zero first). |
XYZ_QUIET · XYZ_NO_NET | — | Silence the stdout log; reject every outbound request (tests and offline development). |
WS_CANDLES | 1 | The 5-minute candle WebSocket, display only (on by default since build -132): a second, separate Hyperliquid socket that pushes every tracked market's 5m bar (up to 600 subscriptions, paced, reconnecting with backoff, a coin that breaks the connection quarantined for 6h). Its bars only draw the provisional live edge of the 5-minute charts (Charts tab, MTF wall) and the drawer's forming hour, marked as unconfirmed; they never enter the 5m archive and nothing that computes reads them. The archive, and the 5m and hourly REST cadences, are identical with it on or off. When REST archives a bar the socket held, the two are compared and counted (Admin → Lanes: vs REST n agree / n disagree; about 1% disagree, because Hyperliquid can skip pushing a bar's last trade at the rollover). If more than 10% of at least 50 compared bars disagree, the socket is treated as broken: the live edge stops (until restart) and one ops warning is raised. 0 is the kill switch: no socket, no live edge, and every chart response is exactly the archive's. Status: Admin → Lanes, and wsCandles on the operator's /api/health. |
History depth is not an environment variable. Open interest keeps 365 days (the last 31 at full ~5-minute resolution, one sample per hour beyond; the crypto roster a flat 31), set by OI_RETENTION, OI_FULL_RES and MAIN_HIST_DAYS in src/poller.js — see §12 Retention.
Access
| Variable | Default | Meaning |
|---|---|---|
SITE_PASSWORD · SITE_USER | — · friend | The shared door. Unset = the read-only site is open to anyone with the link, and the AI routes and /claim are closed. |
LEGACY_SHARED_PASSWORD | 1 | 0 once everyone has an account: closes the shared door for good — the login form's shared password and HTTP Basic both. |
SESSION_SECRET · SESSION_DAYS | derived · 30 | HMAC key for legacy sessions (derived from the accounts secret when unset); session lifetime. |
ADMIN_PASSWORD · ADMIN_DAYS | — · 30 | Break-glass operator login and admin unlock; the admin cookie lifetime. Unset = no admin surface at all, and every operator-only feature is closed to everyone (said out loud at boot). |
USAGE_PUBLIC | — | 0 forces signed-out visitor counting off whatever the Admin → Usage toggle says (the toggle defaults to on). Any other value leaves it to the toggle. |
DM_ESCALATE_MS | 300000 | How long a message sits unread before it escalates to Telegram / push. |
Data sources
| Variable | Meaning |
|---|---|
SEC_CONTACT | Goes in the SEC user-agent; their fair-access policy wants a real contact. |
FINNHUB_TOKEN | Earnings calendar, company news, fundamentals. Without it the app runs, the Calendar tab explains what is missing (above the FOMC and macro rows, which still render) and no E badges render. |
FRED_KEY | Macro prints, Housing, Liquidity. Without it the FOMC rows still serve. |
COINALYZE_API_KEY | Crypto liquidation / OI lane. |
TG_BOT_TOKEN | Telegram alerts, DM escalation, password-reset codes. Unset = the transport does not exist. |
PUSH_VAPID_SUBJECT | Contact sent with web-push VAPID claims (mailto:…). |
LEDGER_BACKUP_REPO · LEDGER_BACKUP_TOKEN · LEDGER_BACKUP_BRANCH | Weekly off-site ledger backup through the GitHub contents API (owner/repo, a token with contents:write, falls back to GITHUB_TOKEN; branch main). Off unless both are set. |
AI
| Variable | Default | Meaning |
|---|---|---|
ANTHROPIC_API_KEY · OPENAI_API_KEY · AI_PROVIDER | — | Either key enables AI; AI_PROVIDER (anthropic | openai) picks when both are set (anthropic by default). |
AI_MODEL · AI_MODEL_FALLBACK | per provider | The report/ask model and the one tried when it errors. |
AI_CLASSIFY_MODEL · AI_CLASSIFY_EFFORT | per provider · low | The unattended news/sector classifier and its reasoning effort. |
AI_REPORT_EFFORT · AI_ASK_EFFORT · AI_REPORT_TTL_MIN | high · medium · 30 | Reasoning effort per surface; cached report lifetime (minimum 5). |
AI_REPORTS_PER_DAY · AI_USER_PER_DAY · AI_USER_PER_MONTH | 5 · 3 · 20 | Report budgets: group per day, per user per day and per month (operators exempt). Pools roll at midnight UTC and persist across redeploys. |
ASK_MAX_PER_MIN · ASK_MAX_PER_DAY · ASK_USER_PER_DAY | 12 · 50 · 5 | Terminal ask budgets. |
BRIEF_ENABLED · BRIEF_DEFAULT_HOUR · BRIEF_PER_DAY · BRIEF_EFFORT · BRIEF_MODEL · BRIEF_MODEL_FALLBACK · BRIEF_MAX_TOKENS | 1 · 10 · 6 · medium · AI_MODEL · … · 12000 | The morning brief: on/off, UTC hour, manual regenerations per day, effort, models, token budget. |
LANDSCAPE_ENABLED · LANDSCAPE_DEFAULT_HOUR · LANDSCAPE_PER_DAY · LANDSCAPE_EFFORT · LANDSCAPE_MODEL · LANDSCAPE_MODEL_FALLBACK · LANDSCAPE_MAX_TOKENS | 1 · registry default · 4 · high · BRIEF_MODEL · … · 12000 | The Landscape commentary. |
15. Running and deploying
Locally
npm install DATA_DIR=./data npm start # open http://localhost:3000
Node 22.5+ (the built-in WebSocket client and SQLite). On first boot the server backfills candle history for every market — the slow part, once, server-side; the table fills progressively and the status line shows syncing X/Y.
Railway
- Push the repository to GitHub; New Project → Deploy from GitHub repo. Nixpacks detects Node and runs
node server.jsfromrailway.json. - Add a volume mounted at
/dataand setDATA_DIR=/data. Without it every redeploy loses the OI history. - Set
ADMIN_PASSWORD, then the optional keys above. Do not setPORT. - Settings → Networking → Generate Domain; set
PUBLIC_URLto it so push and Telegram links resolve. - Open the domain: the first account is created at
/bootstrapwithADMIN_PASSWORD. Mint invites from Admin › Access.
Keep the service always-on: the whole benefit is the warm cache. Redeploys keep the OI history and candle archive; only stale spines are re-fetched. The build stamp in the status line is how you tell a fresh deploy from a stale one; every asset URL carries it, so a browser can never run last build's client against this build's API.
Tests, lint, bench
npm test # node:test, one file per module and area under test/, in parallel npm run test:cov # the same with Node's coverage table (what CI runs) npm run lint # ESLint flat config: server (CommonJS), client (ES modules), service worker npm run bench # event-loop cost of the poller's synchronous builds on a synthetic 150-market book
CI runs npm audit, lint and the covered test suite on every push and pull request. Many tests are pins: they read the source and assert that a convention still holds (every tab has a feature entry and a help entry, every inline script gets a nonce, every level ships from the server), so a change that breaks a contract fails the suite by design.
Project layout
server.js Fastify server: /public, the JSON API, auth, SSE, CSP, the docs routes src/hyperliquid.js REST client + weight-based rate limiter, WebSocket feed, Coinalyze src/compute.js stats and feature extraction, event studies, calendars, the feature manifest src/poller.js every data lane, the snapshot build, the ledger, alerts, Telegram, AI src/webhooks.js member webhook channels: URL/SSRF rules, pinned delivery, the webhook outbox src/store.js persistence on the volume src/oihist.js the in-memory OI/funding history, columnar src/accounts.js SQLite: members, invites, messages, prefs, wallets src/sectors.js curated sector / industry / display-name tables public/index.html the app shell · public/docs.html this manual public/app.js client entry; public/js/*.js one ES module per area public/styles.css styles · public/sw.js install-only service worker test/ node:test suites · docs/ design notes and reference pages
16. Security model
- Sessions are stateless HMAC tokens in
SameSite=Lax, HttpOnly cookies carrying the account id and a per-user epoch. Bumping the epoch (password reset, disable, sign out everywhere) invalidates one person's sessions only. - Two gates, in order. The site gate answers 401 to anyone without a session (the login page for pages, JSON for the API). The feature gate, registered last, answers 403 to a route whose owning feature is closed to the caller. Routes no feature claims pass through. The escalation path (health, login, logout, the AI unlock pair,
/api/features) can never be closed by a flag write. - Two locks on every privileged write. The manifest gate decides audience visibility; the handler's own admin check decides authorization; both must pass. Opening a tab to the group can never hand the group its write verbs.
- Cross-site writes are refused before any handler runs when the browser says the request came from another site (
Sec-Fetch-Site: cross-site). - Headers on every response:
nosniff,X-Frame-Options: DENY, same-origin referrer, HSTS over HTTPS, and a per-request-nonce Content-Security-Policy (report-only until the operator flipsCSP_ENFORCE=1; violations are counted on/api/health). - Server errors say nothing about the server: every
5xxbody is{error:"internal", id}, including errors raised while the response is being sent, and the id finds the stack in the log. The log keeps at most five such lines per client per minute, then one line counting the rest; the responses themselves are never held back. - Login throttling: eight wrong passwords from one IP lock it for 15 minutes, keyed on the trusted proxy's forwarded address. Reset codes are capped and the reset page never reveals whether a handle exists. A failed passkey assertion counts like a wrong password.
- Passkeys (WebAuthn,
@simplewebauthn/server): only public keys and signature counters are stored. Every challenge is single-use, expires in 5 minutes, and is bound to its ceremony, its account, its origin and the browser that asked. The origin and RP ID come fromPUBLIC_ORIGIN/PUBLIC_URL, never from the Host header alone. A counter that goes backwards (a cloned authenticator) is refused and logged. Adding or removing a passkey needs a fresh password or passkey check, and so do the operator's account-takeover-shaped powers (clear passkeys, the operator requirement off, a reset link for oneself or a passkey-guarded account); removing a passkey or turning the requirement on ends every other session. In-flight challenges are capped per source — per address at the sign-in door, per account for a member's own ceremonies, in separate pools — so one caller can only ever evict its own; an expired or evicted challenge is not counted as a failed sign-in. A required passkey survives a password reset; only an operator's audited clear passkeys lifts it, and break-glassADMIN_PASSWORDremains the operator's way back in. WebAuthn needs no CSP source (the browser's own credential manager, not a fetch), and no Permissions-Policy header restricts it on this origin. - Invites burn inside one database transaction; codes never reach a Referer header or the history. Attachments are typed by magic-byte sniff and non-image files are served as downloads with a sandboxed CSP. Text a member typed is escaped everywhere it renders, including inside inline scripts (the JSON is HTML-safe).
- Wallets are public addresses; the app reads clearinghouse state and holds no key.
- Operator read-through is a separate, admin-gated, audited surface (see Admin), and members are told.
- Rate budgets: a weight-based limiter keeps Hyperliquid calls under the per-IP budget; FRED, Finnhub and EDGAR lanes are paced to their published limits; AI spend is metered per user and per group.
17. Glossary
- HIP-3 · xyz
- Hyperliquid's permissionless perp-deployment standard;
xyzis the dex whose synthetic equity, index, FX and commodity perps this monitor covers. The "main dex" is Hyperliquid's native crypto book. - Perp · mark · oracle
- A perpetual future has no expiry; funding payments keep it near the reference price. The mark is the perp's own price; the oracle is the reference (the cash market, frozen near the last print while that market is closed). Prem is the gap between them.
- Funding · APR · carry
- The hourly payment between longs and shorts. Shown annualized (×24×365). Positive = longs pay. Carry is window-average funding per unit of realized vol.
- OI · ΔOI · regime tag
- Open interest — standing positioning. Its change with price tells one of four stories: longs+, squeeze, shorts+, unwind.
- Window
- The selected lookback (1h–30d) every windowed column shares.
- RVOL
- Relative volume, clock-hour matched against the prior month.
- vs tape · DownCap · Hit%
- Relative strength against the universe median; the share of red-tape moves a name ate; the share of red-tape bars it beat the median.
- Momentum · MOM+
- The board's −100…+100 score, and its candidate under adjudication.
- Squeeze · OI/Vol
- Short-squeeze susceptibility; standing positioning relative to daily flow.
- Gap · overnight · cash session · home session
- The close→open move; the hold from close to open (and Friday to Monday); the open→close hold; the exchange whose clock a foreign-listed name's session follows.
- Signal · claim · ledger · record
- An unusual condition; the frozen entry/void/target it was scored against; the store of every claim; the out-of-sample outcome statistics.
- Void · target · R · R:R · EV
- The level that invalidates a claim; where it aims; one unit of risk (entry-to-void distance, or 1σ where levels are σ-built); reward-to-risk; expectancy in R.
- Prime · confluence · decay · re-arm
- A high-quality signal at fire; same-side agreement across conditions; the fade past horizon; the one-episode-one-claim gate.
- Shadow · variant · promotion
- A candidate threshold or strategy ledgering silently; the process by which one replaces the incumbent on out-of-sample evidence.
- Setup · CONFIRMED
- An open claim on the Actionable board; one that clears the record and geometry gates and is announced as an alert.
- BMO · AMC · DMH · TBD
- Earnings sessions: before the open, after the close, during hours, date known but session not.
- 13F · N-PORT · Form 4 · PTR · 8-K
- Quarterly institutional holdings; monthly fund holdings; insider transactions; congressional Periodic Transaction Reports; a material-event filing.
- TGA · ON RRP · H.4.1 · SOFR · IORB
- The Treasury's account at the Fed; the overnight reverse repo facility; the Fed's weekly balance-sheet release; the overnight funding rate; interest on reserve balances.
- EMA 13/21 · rung · retest
- The trend ribbon; one of its four timeframes; a pullback into the ribbon that held.
- Scope · universe · lane
- Stocks or crypto; the set of markets in a scope; one server-side data or scan loop.
- Feature key · act · scope key
- A tab's visibility switch; a write verb's switch; a per-universe slice of a tab.
- Snapshot · spine · archive
- The cached table; the hourly candle series every feature is built on; the SQLite candle store.
18. Troubleshooting
- The dot is amber and "updated" is minutes old.
- The server's poller stalled or Hyperliquid is rate-limiting it; the browser is fine. It recovers on its own; an operator sees the ops alert and the health endpoint.
- A column shows dashes.
- Read the hover. Usually: history still backfilling after a deploy (hourly features fill within ~10 minutes), a coin or stock younger than the column's window (a long MA, YTD), a baseline that does not qualify (RVOL on 7d/30d, DownCap under 20 matched bars), or a feed key the deployment lacks.
- A tab I used to see is gone.
- The operator changed its visibility, or you flipped to a scope where it has no meaning. Gated tabs are absent, not hidden.
- I see the wrong build / a stale UI.
- Compare the build in the status line with the latest delivery. Every asset URL carries the build stamp, so a hard reload always fetches the current client.
- The refresh selector does nothing.
- Correct while the status reads push live: updates arrive as the server has them, and the selector only paces the fallback poll.
- No Telegram alerts arrive.
- Check the bell's Delivery section: linked, the class enabled, not inside quiet hours, not at the hourly cap (held messages are disclosed). The panel log shows Telegram's own error text.
/statusto the bot reports what it thinks. - The regenerate button on a report is disabled.
- The report is fresh for everyone; the freshness line counts down to the cooldown, or names the material change that unlocked it. The server enforces the same rule with a 429.
- The AI answered with 🔒 / "budget".
- Generation is locked or the group pool for today is spent; an operator can
admin unlockoradmin reset-reports. Your own daily cap is separate. - My phone shows different columns than my desktop.
- By design: the phone layout draws ticker, price and the one number you pick over the list; your column set is untouched (CSV export and the desktop layout still use it). On the desktop layout, the first phone visit starts from the mobile preset and the active layout is per browser. Saved layouts and the watchlist sync.
- A note or message "moved" after I edited it.
- It did not: the timestamp and the price stamp are written once; only the words changed, and the edit is disclosed.
- I lost access after a password reset.
- Intended — a reset signs out every other device. Sign in again with the new password.
- My password reset asked for a passkey, and I have lost it.
- Intended — a reset changes the password, not the passkey requirement. Ask an operator to clear passkeys on your row in Admin › Access (audited); then your password signs you in alone and you can register a new passkey.
19. Further reading
Longer reference pages, served from the same site and gated the same way:
- Explainer — the plain-language tour for people using the terminal: what each screen is for, what the numbers say, what is yours, what the app will not do.
- How to use this site — feature by feature: the value of each, the steps to use it properly, worked examples, and a daily routine.
- Signal, study & shadow reference — every event, threshold, stamp and knob, on four axes: trigger, measurement, display, alert.
- Feature map — every feature with its scope, audience and the code that owns it.
- System map — how the lanes, stores and surfaces connect.
- Mechanics — the computations behind the columns and studies, in detail.
The reference pages describe the engine as of the build noted on each; the code and its test pins remain the source of truth. This manual is regenerated with the app and its build stamp is at the top of the page.
Not investment advice. Everything here is a description of what the software computes and shows.