MilstScreenerDocumentation build 2026.10.29-147 how to use this site explainer ← back to the terminal

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.

Daily bars: sessions, on each instrument's own calendar

Hyperliquid's daily candles are UTC days, weekends included. On the stocks side every daily figure (moving averages, σ, the 200 lines, signals, studies, β, correlation) counts sessions: a UTC day on which the market under the perp does not trade folds into the next trading day's bar, so nothing is lost and a weekend is not two flat "days". Since build 2026.10.29-143 the calendar is the one of the market each perp tracks: US equities, ETFs, US indices the NYSE calendar; Seoul, Tokyo, Hong Kong and Shanghai listings their home exchange; energy and metals (gold, silver, copper, platinum, palladium, WTI, natural gas, heating oil) CME trade dates — the same full closures as the NYSE; Brent ICE Futures Europe, closed only on New Year's Day, Good Friday and Christmas Day; FX the weekend plus Christmas and New Year's Day; the xyz crypto and pre-IPO perps, which have no exchange underneath, calendar days, like the crypto side. A session bar's close is the perp at 00:00 UTC (20:00 ET in summer), not the exchange's own close. β and a pair's returns read only the days both instruments trade. BOW and Pulse never fold: they read TS9's native UTC candles.

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). Each address keeps its own session: on screener.milst.app the ↗ terminal.ts9.tools button beside it opens the same tab on the TS9 address, already signed in (on a phone, in More → All tabs). It goes one way: the TS9 address shows no such button.

No account yet — request access

Request access under the sign-in form on /login: your name, the handle and password you will sign in with (the same rules as an invite: handle free, password 12 characters or more), and optionally how to reach you. It waits in Admin › Access › Access requests (counted in “needs you”, announced in the Telegram ops chat and the bell). Accept creates the account — member or elevated — and you sign in straight away with what you chose; no invite link. Until then, signing in with them says the request is pending. Dismiss drops it. The password is stored only as a hash and cleared once the request is handled; handled requests are deleted after 30 days. One request per address per hour. The operator can close it in Admin › Features › Invite requests (invite.request).

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, a reset link for yourself or for an account a passkey guards, and (build -132) promoting or demoting an operator, disabling or re-enabling an account — 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 — the four audience tiers

Every tab and action in Admin › Feature visibility is opened to the lowest tier that may see it, and every tier above sees it too: public (everyone) · members · elevated · admin (operators) · or off (nobody). Alerts follow the tab they come from, on Telegram and webhooks as in the app.

  • Guest (no profile) — signed out. Only reaches the app on a deployment without SITE_PASSWORD; sees the features set to public. A legacy shared-password or HTTP Basic caller counts as a guest too: it is authenticated but not identified.
  • Member — signed in to an account: the public and members features, plus every personal surface: watchlist, layouts, alerts, notes you can read, messages, positions.
  • Elevated — a member an operator marked with make elevated in Admin › Access (audited): adds the elevated features. No operator powers. Taking it away (un-elevate) applies on the member's next request.
  • 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.

Privacy notice. On this deployment the operator can read every message, including conversations they are not in. Every such read is written to an audit log shown in the Admin panel, and the Messages tab says so plainly. Write accordingly.
Your usage. For signed-in members the terminal records which tab is on screen and for how long (only while the page is visible and you have used it in the last five minutes), roughly which hour of the week (Eastern time) that was, and the kind of device (desktop, mobile or tablet, installed or not). It also counts how many times you use a few features — calls, targets, alerts, shares, CSV exports, asks, AI reports, ticker drawer opens, linking Telegram, turning on push — as a number only, never which ticker. To catch bugs it records how long the page took to first show the markets table, which build your tab is running, how many times you load the page, and any JavaScript errors it hits: the error message with quoted text removed (anything in quotes is replaced by “…” in your browser and again on the server), cut to 200 characters, and file:line. It never records what you search, which tickers you look at, your filter values, or anything you type, and which filters or columns you set is never linked to you. The operator sees this summary for every member; opening one member's detail is written to the same audit log as a message read. You see your own numbers under Your usage in the Messages rail, and Pause for me there stops the recording for your account. Per-member numbers are kept 30 days, then only sitewide totals remain — except a yes/no for each week you were active, kept 8 weeks (plus the week in progress) so the operator can see whether new members stay, and one total per month (minutes on screen and active days), kept for this month and last, so the operator can compare month over month. Also collected, sitewide and not linked to you (totals with no member attached, kept 30 days): navigation paths — which tab people move to from which, and the first tab a page load settles on; control-usage counts from a fixed list — the Markets column picker (which column), window and scope buttons, filter presets (the preset name only), CSV and share buttons, drawer sections, and whether a search box was used (never the text); and screen time per tab by device class. Never text, never tickers, never filter values beyond those preset names; a paused account adds nothing to these either. The operator sees these only for ranges of 7 or more complete days (never today) in which at least 3 members contributed on one day; below that the fold says “not enough members to show without identifying someone (n<3)”, since a “sitewide” count from one or two people could identify them. Reminders: if the operator turns them on (they are off by default), a member who was active and then goes a week without using the terminal may get one friendly reminder (at most once every 30 days) with a few market lines — on the Telegram or browser push you already linked, never email or SMS, and nothing if you have neither; each one is written to the admin audit log. To never get one, pause usage (paused accounts are never reminded). The operator also gets a weekly usage digest on Telegram, which names members who lapsed or returned that week — never a paused one.
Signed-out visitors. When you use the site without signing in, it counts anonymous usage totals — the same kinds of numbers as above (which tabs are on screen and for how long, the hour of the week, tab-to-tab moves and the first tab, control counts from the fixed list, the device class, how long the markets table took to show, JavaScript errors) — added into sitewide totals with nothing attached to you. No cookies and no identifier of any kind is stored in your browser. The server tells visits apart only within one day, by a keyed hash of your IP address, browser (User-Agent) and the site's host name; the key is random, held in memory only, and replaced every midnight (Eastern time), so a visit is not linked across days — and the IP address, the User-Agent and the hash itself are never stored. What is kept (30 days) is per day: how many distinct visitors there were, how many opened each tab, a histogram of minutes, and the totals above; paths, controls and devices are shown to the operator only for ranges of 7 or more complete days with at least 3 visitors on one day. Days with fewer than 3 visitors aren't shown at all: for such a day every public figure (tab time, reach, minutes, the hour-of-week map, first paint and errors, the day's visitor count) is hidden from the operator (“<3”) and left out of range totals, and an anonymous online count of 1–2 reads “<3”. A JavaScript error that only signed-out pages hit keeps its file and line but not its message text, and never reopens an error the operator marked resolved. A server restart can count a returning visitor twice that day. The operator can switch this off (and 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:

FieldMeaning
markets · vol · OITotals for the active universe: the number of markets, 24h volume and open interest.
benchmarkThe name every "vs" column is measured against in this scope: the S&P 500 perp for stocks, BTC for crypto.
nextCountdown to the next fallback pull. Reads push live while the stream is up.
syncServer-side backfill progress: syncing X/Y while candle history is being fetched for new listings, then live.
updatedAge of the server's last successful poll — the number that goes amber when the poller stalls.
buildThe server build stamp (also in /api/health). If it does not match the latest delivery, the deploy is stale.
freshness trayOne 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 chipThe 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.
🔔 bellAlerts — 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

ControlWhat it does
Filter ticker…Narrows the table to matching tickers and names. Not part of a layout.
groupstocks · sectors · industries — the group lens (below). Industries are equities-only; crypto's curated sectors are already its fine grouping.
weightHow members average into a group cell: by volume (the tape's own weighting) or equal. Volume and OI are straight sums either way.
window1h · 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".
refreshFallback-poll cadence, used only when the push stream is down.
RefreshPull now.
Filters24h 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.
LayoutsSave 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.
ColumnsShow, hide and drag-reorder columns. Hidden-by-default columns (5m, 15m, H/4h/12h open, VWAP, DownCap, Hit%, MAs…) live here.
↓ CSVDownload 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

MarkMeaning
★ / ☆Watchlist. Synced across your devices when signed in.
E solid / hollowReports 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-itYou 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) · microlineForeign-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.

ColumnDefinition
TickerDisplay ticker; hover for the market's full name and coin id.
SessHome market of the reference line under the perp — KR (KRX), JP (TSE), HK (HKEX), CN (SSE), IN (NSE) 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.
PriceLive 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.
Short vol(Build 2026.10.25; hidden by default; operator-only while it soaks — key shortvol.) FINRA's daily short-sale volume for the US-listed names: the σ of the latest session's short share of off-exchange volume against the name's own previous 30 sessions, as a chip (red at +2σ, green at −2σ), with the share itself beside it. Sorts by σ. Market makers short to fill buyers every day, so 40–60% is ordinary — read the σ, not the level. Not short interest. A dash = not in FINRA's file (non-US lines, commodities, FX, indices) or not read yet. See Shorts.
Short float(Build 2026.10.26; hidden by default; operator-only while it soaks — key shortvol.) Short interest (FINRA, twice a month) ÷ a float estimated from SEC filings, shown as ≈ with a chip red from 20% and amber from 10%; † when the shares outstanding added since the 10-K's float date are 15% or more of the float. Sorts by short float. Hover for the shares short, the change since the previous settlement date, days to cover and the float. A dash = an ETF, a 20-F / 40-F filer, a listing without a 10-K, or not read yet. See Shorts.
PremPerp 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.
OI cap(Build 2026.10.25; hidden by default; operator-only while it soaks — key oicap.) Every HIP-3 market has an open-interest cap in dollars, set by the deployer. The cell is the market's open interest at the oracle as a share of that cap (amber from 90%); AT CAP when Hyperliquid itself lists the market at its cap (perpsAtOpenInterestCap) — orders that would add open interest are refused there, so the side that wants in cannot open until it falls back, while funding and the premium can drift. The at-cap state is the exchange's; the percentage is ours and can differ from its own check by a few percent. Sorts by utilization, capped markets first. The caps are read every 10 minutes, the at-cap list every minute.
5m · 15mLive 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 · 4hRolling window changes.
24hRolling 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 openChange 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 · 30dRolling window changes.
GapThe 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 trendSparkline of the 30-day price path; sorts by 30-day change.
vs S&PExcess return over the benchmark perp across the window (this market % − benchmark %). BTC is the benchmark in crypto scope.
vs tapeThis 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 31dDown-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.
RVOLRelative 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.
MomentumSelf-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 RangeMean 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 hiDistance below the 30-day high; 0% = at the high now.
Swing RStructural 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 hiDistance 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 openThe 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.
ΔOIOpen-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.
Squeeze0–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.
CascCrypto 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 LiqsCrypto only: total forced liquidations over 24h (Coinalyze). Hover for the long/short split.
CarryWindow-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 · OINotional traded over 24h; open interest. Both in USD.
PositionYour 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 · 200Simple 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/VolOpen 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). Who is shown it is an operator switch: Admin › Features › Markets action lists (markets.action, everyone by default).

  • 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.

Watchlist rail — your lists, stocks and crypto together (2026.10.23)

A drawer on the left that holds named lists split into groups and, unlike every board, ignores the Stocks | Crypto switch: one list can hold NVDA, BTC and GOLD side by side. It is the ★ set organised — a star anywhere lands in the open list (a coin joins the coins, a stock the stocks), and a name leaves the ★ set only when no list holds it any more. The first time it opens, Core holds your existing stars in a Stocks group and a Crypto group. Lists and groups sync to your account (signed in); which list is open stays per browser.

  • The dot: blue a stock, amber a coin, grey a commodity. Hollow = that market is closed right now; the change cell goes dotted, so a quiet closed stock never reads as a flat one.
  • Columns are the operator's pick for everyone (Admin › Feature visibility › Watchlist rail columns): up to four of price, D open (since today's UTC open), the window change, σ move, RVOL, funding APR, 24h volume, open interest, ΔOI, 7d change and momentum. The default is price · D open · RVOL. The first change column also drives the group averages and the collapsed strip.
  • Change (when it is one of the columns) follows the table's window (1h · 4h · 24h · 7d · 30d). Click its header for σ: the move divided by that market's usual move over the window (Vol (ann) per day, × √sessions for a stock, × √days for a coin). σ is the fair way to rank a stock against a coin; a dash means its volatility has not loaded yet. Group headers show the equal-weight move.
  • Conditions (the sections above the list) scan the open list, and they are yours to change. Each is a metric, an operator, a value and which universe it scans. + Add in the Conditions bar adds one. Every section shows its controls: ✎ edits it, × removes it (with Undo), and the ⠇ grip drags it to a new place (or press Alt+↑/↓ on its header, or use Up/Down in the editor); ⋯ › Restore default conditions brings back the four it starts with: Touching EMA now, Gap ≥ 2% (stocks), Funding extreme (its own 31-day percentile in either tail at 90), Earnings ≤ 1 day (stocks).
    • Metrics: move or σ move over the table's window; 1h, 4h, 24h, 7d, 30d change; gap (stocks); funding APR; funding percentile (own 31 days); RVOL (up to the 1d window); ΔOI over the window; distance from the 30-day high; momentum score; touching an EMA 50 or 200 (with the EMA Touch feature); earnings within N days (stocks).
    • Operators: ≥, ≤, beyond ± (either direction past the value, e.g. a gap beyond ±2%), and for the percentile in either tail (at 90 = the top or bottom 10%).
    • Up to 16. Leave the name blank and it is written from the condition. A chip shows that name's own value; click it to open the name. Conditions sync with your lists.
  • Editing: the search box adds from both universes, and finds baskets too (MAG7, a sector, one you made on Correlations): picked, a basket becomes a section of its name holding its members as listed now, each starred — a snapshot, nothing follows the basket afterwards; add it again for its current members. ⋯ › Basket → section does the same from a list. + New section at the foot of the list adds a named section in place (Enter adds, Esc cancels; up to 12 a list) — drag names into it, or drop them on its header. Drag to reorder or move a name to another section; double-click a section to rename it; ⋯ renames, adds and deletes lists and sections. Keyboard: ↑↓ walk the list, Enter opens the name's chart, Delete removes.
  • Space: docked at 1180px and wider; ‹ collapses it to a strip of tickers and live %, ✕ hides it, ★ Watchlist in the meta line brings it back. Narrower, and on phones, it slides over from that button. A click opens the name on the Charts tab, from wherever you are — the first pane of your grid (or the MTF wall's instrument), the way the drawer's full chart → does; already on Charts, the pane just takes the name. A name from the other universe switches the scope for the visit, and leaving the tab switches it back. A viewer whose tier has no Charts tab gets the ticker drawer instead.

Operator-only while it soaks: Admin › Features › Watchlist rail (markets.rail). It reads the rows already on the page and the wlists pref through /api/prefs; it owns no route.

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
Stays put while the body scrolls. The price leads, large, with the day move beside it and a tint on every tick; under it the instrument name and the coin id; then the action row — AI report →, ⚑ alert, full chart → (opens this name on the Charts tab in your own layout), ⧉ link (copies a link that opens this drawer) — and a row of chips that jump to each section (Chart, Metrics, Why it moved, Events, Ledger, News, Peers). ‹ › in the header, the ← → keys or a sideways swipe on a phone step to the previous or next name in the table's current order; the small counter says where you are. On a desktop wider than 900px, ⤢ widens the drawer to two columns with the chart across both; the choice is remembered.
Sections
The chart comes first (hourly candles, 3d to 90d; the daily closes stand in while a young listing's hourly spine fills), then Returns (1h · 4h · 1d · 7d · 30d), Market (volume, open interest, ΔOI, funding, premium, OI cap) and Character (momentum, β, volatility, gap, squeeze, carry, distance from the 30d high). Click a section's title to fold it; folded sections stay folded for this browser until you open them again (a jump chip opens one). Panels that load after the drawer opens reserve their space, so the body does not shift under your finger while they fill.
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"). #tags in 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.
Full chart →
In the action row, where the Charts tab is open to you: opens this name on the Charts tab in your own layout — the first GRID pane, or the MTF wall's instrument.
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.
OI cap
(Build 2026.10.25; xyz names, where oicap is open to you.) In the Metrics grid: the share of the deployer's open-interest cap the market uses and the cap itself, or AT CAP and for how long while Hyperliquid lists it there.
On other dexes
(Build 2026.10.16-131; xyz names, where crossdex is 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.

Watchlist, layouts and positions follow the account

  • Wallets (build 2026.10.30; where the wallets key is open to you) — Account › Wallets holds up to 5 public, read-only addresses, each with a label and the venues it has history on (Hyperliquid, Lighter, Arcus, checked when you add it). The positions overlay, Book and the what-if panel read every Hyperliquid wallet on it; the Journal reads them all, and opens with them loaded. A wallet added or removed in the Journal changes the list too, and the first time you open the Journal its own wallets join the list (up to 5; any beyond are named in its status line). Filters › Positions then shows the list's summary and Manage wallets → in place of its address box. Private: no route answers anyone else's list, and the operators' Usage shows counts only (members with wallets, wallets, accounts at 5, added in 30 days) — never an address or a label.
  • 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.

VerbWhat 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] · reportsActive 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|dropCustom 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 · congressForm 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 dropThe 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.
contestsCustom contests open for picks and in play.
book · journalYour 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 · cryptoFlip the scope.
help · clearThe 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.

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

SectionWhat it holds
Setups & filtersAlert on new triggers — a new CONFIRMED row on the Actionable board pops up in this tab. 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), and the family filter (2:1+ setups vs positive-EV grinders). Only names on my watchlist limits every per-name alert (setups, technicals, funding, filings, insider buys, AI flips) to the rows you starred on Markets; outcomes of setups you were told about, your own rules, calls and the calendar are never filtered. Pop up new signals (build 2026.10.26, this tab only) pops up the signal class at or above your signal score ≥ (default 55, also applied to your phone). Muted names: a Mute button on any per-name pop-up silences that name everywhere. Everything here is written to this tab and to every Telegram chat you linked, from the same control (build 2026.10.23-137 — mutes and the watchlist switch used to stay in the browser that set them).
RulesYour own alerts, private to you, evaluated server-side every 15 s against the exact snapshot the board renders, firing with no tab open. Write one in plain English (HOOD up 5% and crowded longs) or in the form: ticker or basket (blank = any market) · metric · comparison · threshold. Comparisons: above, below, beyond ± (abs>), crosses up, crosses down; up to three AND conditions that must hold when the first one fires (including earnings within N days). Edge-triggered with an arm band and a 30-minute cooldown per rule × market; 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. The alert button in a ticker's drawer opens this form with the name and its price filled in.
DeliveryLink Telegram when nothing is linked yet; then each linked chat with its alert chips grouped by topic, quiet hours and scheduled sends (below), your webhooks (where the webhooks feature is open to you), and Where each alert shows up: which classes pop up in an open tab, which only land in the bell, which reach your phone.
RecentThe server-held ring — survives a refresh, a closed tab, a redeploy. Each row is tagged with its class (hover a tag for its name; RES / VOID / TGT for setup outcomes) and reads the same line your phone got. 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 · funding 1d/7d avg APR % · premium (bp) · 24h volume · open interest · relative volume · 24h liquidations · ΔOI 1h/4h/1d/7d/30d % · % from 30d high · % from 30d low · % from 30d vwap · trend score (±4) · dist to D1 EMA21 % · % vs 200d MA · net crowding. 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

Every class below has one entry in the server's registry (compute.ALERT_REGISTRY): its name, its group, what fires it and its bell tag. The bell, the Delivery chips, the channel table and webhooks all read that one list, and the bell's line for an event is derived from the same formatter that writes the Telegram message, so the two can never describe one event differently.

Only what your tier can see (build 2026.10.26). The alerts panel lists, describes and offers a class only to a tier that may see what fires it: the same rule delivery applies. A class belongs to its tab (BOW, Pulse, Congress, Insiders, …), setups and their outcomes to the Actionable / Signals universes, and server health to operators. So when a tab is closed to members, members neither see its class in the panel, the Delivery chips, the "where each alert shows up" table or the webhook chips, nor receive it. The setup thresholds show only to a tier that receives setups. Rule metrics follow the same rule: trend score and distance to the D1 EMA21 belong to Trend, and days to earnings belongs to Earnings. They are offered and accepted (from the panel, plain English or /alert) only while that tab is open to you, and a rule set before the tab closed rests until it reopens. A Telegram chat counts as its owner without operator rights, because a chat may be a group: it receives what the tab is open to at that tier, unless an operator marks it an operator chat.

Class (chip)Fires whenDefault
New setups setupA claim newly reaches the Actionable CONFIRMED board, filtered by your EV / R:R / lateness / family / mutes / watchlist settings.on
New signals (fast) signal(Build 2026.10.26.) A signal on the Signals tab opens its claim: sent at that moment with the side, void and target the claim froze, the score on the card, its tier, E[R] and how often its target came first, its evidence line and horizon. Act tier only by default; Every signal, not only the Act tier in Setups & filters widens it. Faster than New setups — no swing horizon or 8-fire track record is required, so big moves, gaps, premium dislocations and cascades can reach your phone — and unvetted for the same reason. Never sent for a signal whose history lost money (neg exp) or that sits on the no-live-edge cap, never below score 45, never for a claim opened on the first build after a restart. Your signal score ≥ (default 55), sides and mutes apply; the setup thresholds (EV, R:R, lateness) do not. One message per claim. In a tab it pops up only with Pop up new signals on; it always lands in the bell. Sliced by the Signals tab's universe scopes, like setup outcomes.opt-in
Setup outcomes ledgerA setup you were already told about takes its void, reaches its target or resolves at its horizon. One class; the message says which. A void taken breaks through quiet hours and is the only outcome that pops up in the tab.on
BOW signals bow · Pulse signals pulse(Build 2026.10.26.) TS9's calls on the BOW and Pulse pages, on every timeframe TS9 scores (4h, 12h, 1D, 3D — 4h too, though the pages hide it by default) scoring 80 or more on both (you can choose 80, 90, 95 or 100 on BOW, where the default is 90, and 80, 90 or 100 on Pulse; the pages' own accountability floor stays BOW 90, Pulse 80); partial-coverage calls never alert. A new call: BOW on its first report, Pulse only once it is confirmed (TS9's one-notification-per-signal agreement). The message gives the score, the signal bar and its close, and what the call did to its leg — opened it, stacked on it (×N), or flipped it, with the move and length of the leg it closed — plus the move since its first call. One call is one alert, even when TS9 re-reports it under another id. A leg's end: a leg that cleared the floor is closed by an opposite call — with its reference price, close, move, calls and length. You choose which of these reach you (Alerts › BOW & Pulse, per engine): the events — new leg, flip, stack, and closed by a call you skip — the timeframes, the lowest score (a call's own; a leg's top score for its end) and the direction. Left alone it is the quiet set: new legs and flips, every timeframe, score 90+ on BOW and 80+ on Pulse, both directions. The choice filters the pop-up, the bell and its badge as well as your phone and webhooks, and is saved to your account (the ts9alerts synced setting), so every browser, phone and webhook you have follows it — a webhook with no phone linked included; the section previews how many of the server's recent BOW / Pulse alerts it would have let through. A leg closed by a call announced as a flip reaches you once: as the flip if you take it, otherwise as the leg's end (when you chose Closed). Checked after each 4-hour scoring run; the first run after a restart only records. Each class rides its tab's key (bow, pulse): operator-only while the pages are, and a ticker mute or "only my watchlist" applies.opt-in
My alert rules ruleOne of your rules fires (see Rules above). A rule bound to a conversation with /alert posts there instead.on
Calls callsAbout you or people you follow, never anyone else: a member you follow made a call (its side, price, horizon, target and thesis — never the message's words); a call of yours, or of someone you follow, hit its target, got stopped out or missed; the post-mortem reminder when one of your calls with a thesis closes; duel challenges, answers and results. Owner-addressed events on the same stream as everything else (build 2026.10.23-137 — they used to be direct sends that skipped quiet hours, the hourly cap and the bell). A chat whose class list was chosen before this class existed was given it once.on
Trend changes trendTrend-board state changes, close-confirmed: a new 4/4 stack, a retest, a cross. Scanned every 5 minutes; a full pass seeds state before anything fires. The message says which candle close made it true.opt-in
EMA200 closes ma200The 200 EMA on the rung's own candle (H4 and D1), close-confirmed: reclaim, breakdown, and bullish or bearish retests. Silent on names whose history cannot seed a 200 yet.opt-in
EMA50 closes ma50The same four events on the 50 EMA — its own class so the 50 can be off while the 200 is on.opt-in
EMA200 / EMA50 touches touch200 touch50Intrabar: the forming H4 or D1 candle reached the line. One per touch episode; the line re-arms only after a close at least 1σ clear of it. Held-vs-through is decided at the close (the close classes carry that). touch50 is the noisiest technical class on H4.opt-in
New price levels levelsA 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)
Funding extremes funding(Build 2026.10.23-137.) A name's funding reaches the far tail of its own history — the 98th percentile with at least +50% APR (longs paying shorts), or the 2nd with at most −50% (shorts paying longs). One alert per episode, re-armed only once the percentile is back inside 90 / 10; the first scan after a restart seeds rather than announces; at most 10 per scan (a whole tape at the extreme is the regime class's news).opt-in
Open-interest caps oicap(Build 2026.10.25.) A market enters Hyperliquid's at-cap list (orders that add open interest are refused, so new money is locked out) or leaves it (new positions can open again; the alert says how long it was capped). One alert per episode each way; the first read after a restart only records, so a market already capped at boot — or one that came off while the server was down — is not announced. Rides the oicap key: a tier the column is closed to never receives it.opt-in
Market-wide positioning regimeNet 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
SEC filings & 13Fs filingA material EDGAR filing on a roster name (8-K, 10-K, 10-Q, SC 13D, 6-K, 425), ≤6h fresh; and a new 13F from a watched fund. Ownership forms (3/4/5/144/13G) are deliberately excluded — see insider cluster buys.opt-in
Insider cluster buys insider(Build 2026.10.23-137.) Three or more different insiders bought the same stock in the open market within 10 days — the Insiders tab's cluster flag, checked after each Form 4 parse run. Once per cluster; the clusters already stored at first look are seeded, and one whose newest buy is more than 14 days old is history a backfill found, not news.opt-in
Congress trades congressA starred member's new congressional disclosure — House or Senate, ≤10 days old, one line per trade. Each chamber stays silent until its own first full run has completed.opt-in
Earnings earningsTwo legs: a name you hold an open, announced claim on reports today or tomorrow (one per ticker per report date); and one message at 17:00 ET listing tomorrow's scheduled prints and today's results across the roster.opt-in
Macro releases macroThe universe-wide calendar: the day before, the last hour, and the actual, for FOMC and CPI / NFP / PPI / retail sales / GDP / PCE. No ticker, by design; prior only — no consensus, so nothing is a beat or a miss.opt-in
AI report flips aiA regenerated AI report changed its stance against the cached one: a name's long / short / wait, or (build 2026.10.23-137) a sector or basket read's bias.opt-in
Focus list focus(Build 2026.10.23-137.) Today's focus watchlist was stamped at the US open: the names, once a day.opt-in
Data gaps on my claims coverageA name you hold an open, announced claim on is running on a stale spine — its live numbers come from old data.opt-in
Server health opsThe server telling on itself: poller stalls and recoveries, degraded feeds, access requests, and duplicate alert — the same setup, ledger, filing, earnings, macro, congress, insider or focus event firing twice inside 24h (one warning per pair a day, at most one per class an hour). Deploy notices are logged, never pushed. Operator-only, enforced in delivery and in the feed.on (operators)

Recipients with no selection get the default set (setups, outcomes, rules, calls, and server health for operators); every other class is opt-in, shown on its chip with how often it fired in the last day. A ticker mute and only my watchlist apply to the per-name classes: setups, technicals, levels, funding, filings, insider buys, earnings and AI flips.

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 pop-ups 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, calls ↔ Calls, funding ↔ Funding, insider ↔ Insiders, focus ↔ Focus. While a tab soaks operator-only, so do its 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

  1. Open the bell → Link Telegram (also in the Messages rail). The panel mints a single-use code (10 minutes) bound to your account.
  2. Tap Open Telegram and press START — the link carries the code, so there is nothing to type. On a computer, scan the QR with your phone instead. The panel flips to Linked by itself within a few seconds, with a Send me a test button. Fallbacks: send the bot /start CODE, or just the code on its own. The bot's @username is read from Telegram at boot (getMe), so renaming it in BotFather needs no change here.
  3. Open your chat's row and pick what you get: the chips are grouped by topic (Setups, You & people, Technicals, Positioning, Filings & insiders, Calendar, Research, System); hover one for what fires it. quiet opens an hour picker (your local time — alerts in the window are held and arrive when it ends, never dropped; a void taken and server stalls still come through); each scheduled send opens an hour picker and seven day toggles.

Bot commands: /start CODE link (a bare /start replies with how to link) · /alert … sets a price/threshold alert · /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:

URLPayload
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 elseJSON {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, ticker mutes and watchlist-only switch as your linked Telegram (they live on the chat; an endpoint with no linked chat behind it gets the class choice only), 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

SendWhat it isDefault
Morning briefIndices, 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 LandscapeWritten 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 digestDeterministic 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 per send, with the hour picker and day toggles on the send's chip in the Delivery fold. (An operator setting someone else's schedule in Admin still types the days: all, weekdays, mon,wed,fri or MWF.)

Browser push

Browser notifications in the bell shows whatever pops up in the tab (new setups, a void taken, your rules, calls, trend and EMA closes, funding extremes, insider cluster buys) as system notifications while a tab is open. Enabling notifications in the Messages rail adds web-push with the tab closed for unread messages (same 5-minute grace, mute and mention-piercing rules as Telegram), a followed member's call, the post-mortem reminder and duel events. 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 scored on edge (build 2026.10.26): each past occurrence minus what holding the market in that direction earned anyway (the market's own unconditional drift over the same horizon, in R) and minus the round trip (the taker fee in and out plus one spread — the depth lane's measured spread when fresh, else 3 bp; SIGNAL_TAKER_BP sets the fee, 4.5 bp by default). A long-only study in a rising year no longer earns points for the year, and a sub-cost edge on a quiet name scores as neg exp. The card's base rate is the edge (tagged net; the gross move is in its tooltip) and the play's target still uses the gross median. Claims state the gross base rate, so claimed vs live compares like with like.
  • 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 (build 2026.10.26, partial pooling): the market's own edge is pulled toward its asset-class pool with weight n / (n + 20), and the pool toward zero with weight d / (d + 20), where d counts the pool's distinct event-days (thirty names breaking down on one day are one observation). It replaces the old cliffs (own at n ≥ 8, else the pool at a 0.7 discount, else a token): a market's own rate at n = 8 now carries 29% of the weight. With nothing at all to go on, a token 6–8 points. The card shows the own rate from 8 occurrences and the pool below that, as before (unproven below 8). 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.
  • Costs and benchmark (build 2026.10.26) — every claim stamps its round trip at fire (cst, % of notional) and resolves a net-of-cost leg (realizedN), shown as net on the record. Equity claims now resolve a benchmark-excess leg against the S&P proxy, as crypto claims always did against BTC (vs S&P / vs BTC on the record).
  • 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.json plus an append-only archive on trim; optional weekly off-site backup to a GitHub repo.

Event roster

EventFires whenPlay · horizon · unitUniverse
Big move bigmoveToday's move ≥ 2σ of the market's own trailing 30d daily returns (variants 1.5 / 2 / 2.5).Continuation; next session (crypto 12h); R. Stop 1σ against the move.both
30d-high breakout breakoutThe mark above the high of the last 30 completed closes on the day of the first cross, or the next day if that close was the first cross.Long; 5 sessions (crypto 2d); R. Stop = the breakout level.both
30d-low breakdown breakdownThe breakout mirror below the 30-day low.Short; 5 sessions (2d); R.both
Outsized gap gapLive 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 fundflipDay-summed funding changed sign after ≥3 days pinned the other way.Side of the new crowd; 3 sessions (2d); R. Stop 1σ.both
Squeeze setup squeezeComposite ≥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 unwindMirror composite ≥60: crowded longs × OI building × price near range lows.Short; 3d; %.stocks
OI flush oiflush7d ΔOI collapsed below −2σ of its own distribution while price fell — forced deleveraging exhausting itself.Long; 5 sessions (2d); R.both
Funding–price divergence fpdiv7d price z-score diverging from the funding trajectory (|z| ≥ ~0.8σ).Divergence side; 3 sessions (2d); R.both
Premium dislocation premPerp vs oracle |z| ≥ 2σ of its own 7d premium baseline.Revert to the oracle; 12h; bp. No stop.stocks
Overnight drift ondriftThe 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 / tretestdnThe 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 cascA ≥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 fundextFunding 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
One horizon per event (build 2026.10.26): an equity claim resolves at the close N sessions after its trigger bar — the window its study measures (studies count session bars) — not N calendar days later; a crypto claim's evidence reads the study measured at its own 12h / 2d horizon. The OI-flush and funding-divergence studies book one event per forward window, as every other study does (a flush that held for a week used to count as one event per day).
Context flags that render but never ledger, scored at a fixed 6 evidence points: vol expansion (10d realized vol above its 90th percentile; build 2026.10.26 — its points used to come from a study signed long, which measured drift), 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

SourceFeedsCadenceNeeds
Hyperliquid REST + WebSocketUniverse, 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
FinnhubEarnings calendar and EPS actuals, company news, basic financials.Calendar ~6h in chunked windows; news every few minutes per name.FINNHUB_TOKEN
FREDMacro release schedule and prints; the Housing and Liquidity boards.~6h; Liquidity also after the Thursday H.4.1 release.FRED_KEY
SEC EDGARFilings 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)
alternative.meThe crypto Fear & Greed index (2026.11.05), 0–100 with its bands, read with 120 days of history for Home’s Regime card. Whole-market and daily; the card says so beside the number. The stocks side has no equivalent feed, so its mood is a composite built here from the book’s own inputs.One key-free read an hour.nothing (FNG_FEED=0 turns it off)
DeribitDVOL (2026.11.05): the 30-day implied volatility index of BTC options, the VIX’s counterpart, daily closes with a year of history for its percentile. The Regime card’s crypto column shows it beside BTC and HYPE.One key-free read an hour (public/get_volatility_index_data).nothing (DVOL_FEED=0 turns it off)
House ClerkCongressional PTRs.Ingest lane; OCR for scanned PDFs.nothing
Senate eFDSenate 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. A failed run is retried on a backoff (5 minutes, doubling, up to the 6-hour cadence). A 403 from the site's firewall is named as such. With FMP_API_KEY set, a run eFD refuses reads Financial Modeling Prep's transcription of the same eFD filings instead, keyed to the same filing ids. eFD is still tried first on every run.nothing
federalreserve.govFOMC 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
CryptoQuant ConsensusAnalyst consensus per asset (build 2026.10.23-135): the σ of their 30-day consensus MA against its own 90 days, the bullish share, the 7-day pair, the analyst count and the bull / bear sentences — key-free /api/v1/consensus. Which asset a listing reads is the curated table in src/consensus-map.js, checked by name (their NOK is Nokia, ours the krone); an unmapped listing has none. Rides each snapshot row as acon; credited in the Consensus tab's footer.One read per mapped asset per hour, about 20 a minute until the hour's round is done (their per-IP limit is 60). The free tier serves the latest point only, so the history is ours: consensus.db.nothing (CONSENSUS_FEED=0 turns it off)
FINRA Reg SHODaily short-sale volume per symbol, off-exchange (build 2026.10.25): cdn.finra.org/equity/regsho/daily/CNMSshvol<date>.txt, key-free, for the US-listed names on the roster. Rides each snapshot row as svol.The last 45 trading days on the first run (three files a minute), then one file a day after 23:00 UTC; a weekday with no file three days later is recorded as a holiday. A listing added later re-reads the dates it missed.nothing (SHORTVOL_FEED=0 turns it off)
FINRA short interestTwice-monthly consolidated short interest (build 2026.10.26): cdn.finra.org/equity/otcmarket/biweekly/shrt<date>.csv, key-free, for the same names — shares short, the previous figure, average daily volume, days to cover. Rides each row as sint.The last 12 settlement dates on the first run (one file a minute), then each new one from six days after its date, every six hours until it lands; a slot with no file three weeks on is recorded as skipped.nothing (SHORTINT_FEED=0 turns it off)
SEC EDGAR (floats)The float estimate behind short float (build 2026.10.26): the submissions index and the inline-XBRL cover of the latest 10-K (public float) and of two 10-Q / 10-K covers (shares outstanding), plus the closing price on the float date from Yahoo's chart endpoint.Two companies a minute while filling (about five requests each), then each company weekly; the price once per company per 10-K. Well inside SEC fair access, with the SEC_CONTACT User-Agent.nothing (SEC_CONTACT recommended)
Hyperliquid open-interest capsThe deployer's cap per market and for the dex (perpDexLimits) and the markets at their cap (perpsAtOpenInterestCap), build 2026.10.25. Rides each xyz row as ocap; the oicap alert class.The caps every 10 minutes, the at-cap list every minute: weight 20 each on the shared limiter.nothing (OICAP_FEED=0 turns it off)
CoinalyzeAggregated CEX liquidations and OI for crypto names; since 2026.11.06 also the daily OI of every perp on the major venues for Home’s OI dominance card.15-minute buckets; daily for the dominance chart.COINALYZE_API_KEY
TelegramAlert 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 / OpenAIReports, the ask fallback, the brief, the Landscape, the news/sector classifier.On demand and scheduled.ANTHROPIC_API_KEY or OPENAI_API_KEY

Retention

  • The permanent history archive (build 2026.11.01) — nothing the app has recorded ages out for good any more. The rolling windows below stay as they are (every study reads the same window it did), but what leaves them is kept: hourly candles in candles.db's candles_1h table (every closed hour of the spine, never evicted — seeded with the spine's ~180 days on the first refresh), every 5-minute bar (no eviction unless M5_KEEP_DAYS is set), and under archive/ the open-interest and funding samples leaving oi.log (one per market per hour), the Coinalyze rows leaving derivs.log (15 minutes), every regime sample, and one premium reading per market per hour (mark, oracle, premium in bp). Delisted markets' rows stay on disk too. The app never reads the archive back (no memory cost); the research scripts do, through GET /api/admin/history and scripts/history-pull.js. A compaction that cannot write the archive keeps the rows it would have dropped, tries again the next day and raises an ops warning while it lasts; an append is all or nothing and on disk (fsync) before the live copy is pruned. The hourly table is also backfilled once per market to the ~208 days Hyperliquid serves (crypto included, whose spine holds 90). The disk guard: under 30% free (or 2 GB) an ops warning; under 15% (or 1 GB) the 5-minute archive falls back to 370 days so the rest of the volume never runs out, with a warning; the daily candles.db.bak is skipped (the previous copy kept) when it would not fit with 1 GB to spare. Rows the archive files may repeat (a restart, an hour across two compactions) are the same reading twice — read them keyed by market and time.
  • 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 since the lane started (kept, build 2026.11.01), the hourly spine for ~180 days in memory and every closed hour in candles_1h, 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 roster 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.
  • Analyst consensus — one row per asset per clock hour in consensus.db, 400 days, pruned daily; the newest full reading per asset is kept beside it so a redeploy paints before the first read. CryptoQuant's free tier has no history, so this file is the only record of how a reading moved.
  • Short-sale volume — one row per roster symbol per trade date in shortvol.db, 400 days, pruned daily, with a record of which dates were read for which symbols. FINRA keeps its files online, so a lost file costs a re-download, never the history.
  • Short interest & floats — one row per symbol per settlement date in shortvol.db (400 days) with the files read, and each company's float estimate with its inputs, rebuilt weekly.
  • OI-cap episodes — which markets sat at their cap and when, in oicap.json: the open ones and the last 90 days (at most 400) of closed ones.
  • 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.

FileHolds
oi.logThe open-interest sample log.
candles.dbThe candle archive (5m, hourly, daily). Since build 2026.11.01 nothing in it is evicted: the 5m bars and the candles_1h hourly table are kept (about 1.5 GB a year of 5m bars at ~200 markets, and the daily candles.db.bak copy doubles it — size the volume for it, or set M5_KEEP_DAYS).
archive/The permanent history archive (build 2026.11.01): oi.tsv, derivs.tsv, regime.tsv, premium.tsv — tab-separated, append-only, what the rolling stores aged out. Never read by the app; served to the operator by GET /api/admin/history. Tens of MB a year.
accounts.dbMembers, invites, sessions' epochs, messages, threads, attachments, reactions, preferences, wallets, audit log. Rotated backups in backups/ (or ACCOUNTS_BACKUP_DIR).
hourly.ndjson · regime.jsonThe hourly candle spine and the regime state.
ledger.json · ledger-archive.jsonlThe signal ledger and its trimmed archive.
earnings.json · macro.json · fomc.jsonEarnings 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.jsonThe FRED boards.
news.json · tgchannels.jsonThe news cache; the Telegram channel configuration (its own file so a trimmed cache can never lose it).
fundamentals.jsonFinnhub basic financials cache.
triggers.json · alertpush.json · alertrules.json · webhooks.jsonThe 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.jsonThe 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.jsonNav menu labels; feature visibility and runtime flags.
baskets.json · notes.json · focus.json · whale.jsonCustom baskets; notes; the frozen Focus stamps and floors; the 13F watchlist.
whale13f.db · insiders.db · congress.db13F/N-PORT holdings; Form 4 transactions; PTR trades.
consensus.dbCryptoQuant analyst consensus: the hourly readings and each asset's latest full record (build 2026.10.23-135). Droppable: deleting it costs the history, never the app.
shortvol.db · oicap.jsonFINRA daily short-sale volume for the roster and the files read (build 2026.10.25); the open-interest cap episodes. Both droppable: deleting them costs a re-download and the episode record, never the app.
derivs.log · derivmap.jsonCoinalyze samples and the venue map.
oidom.json(2026.11.06) The OI dominance chart: one row a UTC day of BTC / ETH / other crypto / tradfi OI, each market’s frozen and open days, the market list.
duel.json · sector-audit.jsonThe Score duel record; the classification audit overlays.
ai-reports.jsonCached reports and the AI budget state.
webpush-keys.json · loop-history.json · volume-heartbeat.jsonVAPID 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.json is 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.db takes 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_PASSWORD set, every route needs a signed-in session (cookie), except the doors and the few routes marked Open. Signed out is 401. 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 once LEGACY_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 is 400.
  • 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 are HttpOnly, SameSite=Lax, Secure over HTTPS. Bodies are size-capped per route (413 past it).
  • Limits — a limited route answers 429, usually with Retry-After. Per-IP limits key on the last X-Forwarded-For element only when TRUST_PROXY is on (see Configuration).
  • Caching — cached payloads carry an ETag and revalidate (304); personal and operator bodies are no-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

RouteAccessReturns · limits
GET /api/healthOpenLiveness, 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"). (Build 2026.10.23-135) The slow lanes heal themselves: the Senate eFD, House index and FOMC lanes are retried while they grade failing or stale, ~5 minutes after the failure and then doubling up to the lane's own cadence (6h at most). A lane being retried carries heal (tries, nextAt, lastAt), and the Lanes fold shows self-healing · retry N · next in …. A recovery posts one ops line.
POST /api/csp-reportOpenBrowser 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-inviteOpenThe signed-out "request an invite" — pings the operator. 1 per address per hour (an IPv6 caller by its /64).
POST /api/request-inviteOpen · invite.request (everyone)The sign-in page's "Request access": a name, the handle and password to create the account with, and an optional way to reach you; held in Admin › Access › Access requests. Accept creates the account (POST /api/access {op:"req-accept", id, tier}); dismiss drops it (op:"req-dismiss"). Shares the throttle above (1 per address per hour, taken before the password is hashed and handed back if the request is refused) plus a site-wide hour that counts networks, not addresses: 20 networks (an IPv4 /24, an IPv6 /48) an hour, each up to 3 stored requests; 20 refused attempts per address an hour.
GET|POST /login · GET|POST /logoutOpenSign in (account, break-glass ADMIN_PASSWORD, or the legacy shared password) and out. 8 wrong passwords per IP = 15-minute lock.
GET|POST /api/handoffAccount(Build 2026.10.22-133.) Carry this session to the site's TS9 address: the ts9.tools addresses in the PUBLIC_ORIGIN list, from any other address — one way only, so a TS9 address lists none and refuses to mint. GET lists them; POST {to, next} answers {url}: a single-use link, good for 60 seconds, bound to the account and its sign-in epoch, to to and to next. At most 5 open per account. 400 for an address off the list or this one.
GET|POST /handoffOpen(Build 2026.10.22-133.) Redeems a handoff link at the address it names, through the same sign-in every door uses. Followed from one of the site's own addresses (the Referer), GET signs in and redirects to next; any other way in shows Continue as @handle and signs in only on that page's same-origin POST {code} — a link alone never signs anybody in. A code works once; an expired, spent or wrong-address code is a 400.
POST /login/passkey/options · POST /login/passkeyOpen(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 /joinOpenRedeem an invite or a reset link. Same per-IP damper.
GET|POST /reset · GET|POST /reset/codeOpenForgot 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). (-132) An account with no Telegram linked gets no code at all; the answer is the same either way.
GET|POST /claim · GET|POST /bootstrapOpen (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

RouteAccessReturns · limits
GET /api/snapshotSite · markets (everyone)The market table for both universes: price, funding, %-changes, reference prices, momentum, vol, OI deltas, earnings badges, note digests (only for callers the Notes tab is open to) and the analyst consensus numbers (acon, only for callers the Consensus tab is open to).
GET /api/snapshot?lean=1 · GET /api/snapshot/slow?since=Site · marketsWhat 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. The consensus numbers ride /slow, only for callers the Consensus tab is open to.
GET /api/dailySite · marketsDaily bars per market (correlation, beta, sparklines, backtest, drawdown).
GET /api/daily/hist · GET /api/daily/tailSite · marketsThe 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 · marketsPer-market OI and funding history.
GET /api/candles?coin=Site · marketsdays=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|3d the deep archive (3d: the native series TS9 scores, filled only where scoring runs), 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/eventsSiteThe 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/regimeSite · sessions (everyone)(2026.11.05) Home’s Regime card, both books in one body, cached a minute: per book the daily regime for the last 60 days (days[]: tr and br the strategy engine’s trend and breadth, vol the card’s own 5-day realized-vol rank against the year before, vol20 the engine’s 20-day read, bb the 200-day; now, run — how many days the trend has run and what came before), the benchmarks (bench[]: the S&P 500 perp, GOLD and the VIX — the CBOE close from FRED while the xyz VIX perp is delisted, src:"fred"; BTC, HYPE and Deribit’s DVOL; each market’s dd is its move since the day’s open, the board’s D open anchor, and its d1 the rolling 24h), today’s breadth, crowd-pays, OI and positioning mix (strip), the Positioning panel’s crowd and lev, the sparkline series (hist: up the share of names up by day, fund the OI-weighted funding APR by day, breadth the share above the 20-day VWAP) and the mood: alternative.me’s Fear & Greed for crypto, the composite built here for stocks (parts says which inputs went in). A book still banking says pending.
GET /api/oidomSite · sessions (everyone)(2026.11.06) Home’s OI dominance card: Coinalyze’s chart rebuilt from its API. rows[] is one UTC day each, [day, btc, eth, alt, trd, markets] — the summed open interest in USD of BTC, ETH, every other crypto perp and the tradfi perps (metals, energy, stocks, indices, FX, pre-IPO), over every perpetual on the venues read (byVenue), the last row today’s, still forming. The shares are the client’s: the Exclude tradfi derivatives toggle takes trd out of the total, unticked it counts in Others. fill is the first fill’s progress, topAlt / topTrd the biggest bases in Others and in tradfi. Without COINALYZE_API_KEY (or with OIDOM_FEED=0) enabled:false and the reason. ETag on the content clock.
GET /api/funding?u=Site · funding (everyone)The funding heatmap.
GET /api/consensus · GET /api/consensus?coin=Site · consensus (operator)CryptoQuant analyst consensus (build 2026.10.23-135). The board: every live listing with a reading, both universes — coin, t, uni, sym (the CryptoQuant asset; k + note when it is not the listing itself), z / b (σ vs the 90-day norm, bullish %), z7 / b7, an, reg, d, bull / bear and their fill kinds, old, lo (under 3 analysts: shown grey, counted neither way); memoized, ETag on its content clock. ?coin=: that listing's card — mapped, the same row and series (the hourly [h, z, b, z7] kept over 30 days); an unmapped listing says why (gap). No-store; a malformed coin is 400.
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/fundnextSite · 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/oicapSite · oicap (operator)Open-interest caps on the dex (build 2026.10.25): rows — every capped market: coin, t, c (the cap, $), oi (open interest at the oracle, $), u (oi ÷ c), at (Hyperliquid lists it at its cap), s (since, ms), near (u ≥ 0.9), capped first then by utilization; total (the dex-wide cap, the summed OI and its utilization); open; episodes (the last 60 closed: coin, t, from, to, mins); capsAt / listAt (when each list was read), stale, err. Rebuilt when an episode or a cap moves, or every 30 s; ETag on its content clock.
GET /api/shortvol · GET /api/shortvol?coin=Site · shortvol (operator)FINRA daily short-sale volume (build 2026.10.25). The board: every US-listed name with a reading — coin, t, sym (FINRA's spelling), nm, sector, d (the session, YYYYMMDD), r (short ÷ off-exchange volume), r5, m (the previous 30 sessions' mean), z (σ against them; 10 needed), n, tvx (volume against its mean), lo (thin), old, s (the last 31 sessions' ratios) — and status (files read, holidays, the newest file). Rebuilt on a new file, a roster change or the day; ETag on its content clock. Since build 2026.10.26 each row also carries si — d (settlement date), si (shares short), chg, dtc, adv, n, and with a float estimate sf (short float), fl, fd (the public float's date), adj (the share of the float added since) — and the body si status (files, newest, floats). ?coin= (no-store): eligible, row and series ([d, ratio, volume], 60 sessions), si, siSeries ([d, shares, dtc], 12 dates) and float (its inputs: pf, pfd, px, fl0, fl, adj, soN/soNd, soL/soLd, or none with the reason); a malformed coin is 400.
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/NSE listings on their own exchange's clock, each row with ex, state, opensAt, its checkpoint labels and settle (10:00 local; 10:30 on NSE, whose UTC+5:30 puts the hourly prints on the half hour). ?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/boardSite · 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 trigger events since that your tier sees, exactly as the bell does (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/trendSite · trend (everyone)The EMA 13/21 ladder; fast=&slow= for a custom pair.
GET /api/trend/e200Site · trend (everyone)Every name’s distance from its daily EMA200 and the line’s 20-bar slope, both universes.
GET /api/corr-crypto?w=Site · corr (everyone)The crypto intraday correlation grid.
GET /api/ts9/bowSite · bow (operator)TS9 BOW legs: every open RECOIL ▲ / SNAP ▼ leg (ranked: higher timeframe, stacked 100s, stack size, freshness) with its reference, latest and current price and the direction-adjusted move; the newest closed legs (by the opposite signal) with their result; summary statistics per timeframe over the last 120 bars (computed, not a headline yet); each market's weekly zone and tension; a leg built from partial-coverage signals carries partial, and history.byUni counts the signals used and the partial-coverage ones per universe. Operator-only while TS9 scoring is in shadow. no-store.
GET /api/ts9/pulseSite · pulse (operator)TS9 Pulse legs: the same for ABSORB ▲ / DIST ▼, with PULSE's flow trend per timeframe as the context. Operator-only while TS9 scoring is in shadow. no-store.
GET /api/ts9/chartSite · bow / pulse (operator)One market’s TS9 signals for a chart (?market=, e.g. xyz:NVDA): every recorded BOW and Pulse signal (engine, timeframe, signal, score, state, bar open time, price at the signal, and partial for a partial-coverage one) and the market’s open legs with their span and signal reference price (no stop: a leg ends only on the opposite call), plus the weekly zone and Pulse flow. It answers with only the engines whose tab the reader may open, and 403s when that is neither; 400 on a malformed market. Read by the leg chart (BOW / Pulse › Open chart) and the Charts tab’s TS9 overlay. no-store.
GET /api/experiments/stretchSite · experiments (operator)The VWAP stretch: every name in both universes against four session-anchored VWAPs in ADTRs — both pairings' stretch and percentile, the run, its speed, today's-VWAP trigger, the daily 200 and the setup it names, the replayed signals — plus each universe's event study and shadow model with its out-of-sample record. ?coin=&a=D|7|14|30&r=m|f: one name's detail chart instead (hourly closes, the VWAP and its ADTR per hour, the 200, the signals; no-store). Built on request, memoized 60 s; per-name work rides the spine's tail and refreshes in 40 ms slices.
GET|POST /api/experiments/simSite · experiments (operator; writes need the operator)The Simulator: every run with its result replayed from the setups' own trades (equity, drawdown, stats, by setup, trades, open trades, activity). POST creates a run (a leg can also be a paper trial, book|p_<trial>|side: every trial with a stop's distance. In Shadow and Live its trades go through like a setup's; a trial Live can't trade has each order logged with the reason and never sent; for the Live gate, a trial is proven when its record clears the trials' bar: 20+ trades, ≥ +0.25 R a trade after 23 bps, the low end above 0): {name, cap, size:{m:pct|usd|full, v, lev}, legs:[{id, w}], mode:back|fwd|both, from, maxOpen, costs:{crypto, stocks}} (bps a round trip).
GET /api/experiments/sim/:id/csvSite · experiments (operator)A run's every closed trade as CSV: entry, exit, name, setup, side, segment, result, R after costs, risk, P&L and the equity after it.
POST /api/experiments/autotradeSite · experiments (operator)(2026-11-06) Auto-trade in one call (The Lab › Simulator, the panel on top): { acct?, amount?, riskPct, dailyLossPct, maxOpen?, perName?, exec?, legs?, live, confirm? }, or { id, … } to change a running one. It creates a forward run of the picked setups, puts it in Shadow with limits that size every order at riskPct of the account's balance (the sub-account's equity as the signer reads it, while Live) and stop new orders for the rest of the UTC day once today's loss (paper or the account's real one, the worse) reaches dailyLossPct, and, with live: true, sets that account's signer caps (risk a trade 2× the plan, the daily loss 1.5× the limit, positions open, position size, each within its ceiling) and goes Live through the same gate (the setups proven, or confirm equal to the run's name). Without the signer, amount stands for the balance and nothing is sent. Real money needs the fresh check (password or passkey within 5 minutes). JSON and same-origin only.
POST /api/experiments/sim/capsSite · experiments (operator)Risk caps from the page (The Lab › Simulator › Risk caps): { acct?, caps: { maxRiskUsd, maxNotionalUsd, maxOpen, maxBasketLegs, maxDailyLossUsd, maxHedgeNotionalUsd } } for one signer account (name it when there are several). The signer refuses anything over its ceiling (SIGNER_CEIL_*, set only on the signer) and keeps the rest in its caps.json, over its environment's caps. Raising any cap needs your password or a passkey within 5 minutes (403 { fresh: false }, then again once confirmed); lowering doesn't. JSON and same-origin only.
POST /api/experiments/sim/killSite · experiments (operator)Live trading's kill switch, at the order signer: { on: true } stops every new order at once (open positions keep their exchange-side take profit and stop); { on: false, confirm: <the sub-account address> } resumes. JSON and same-origin only. 400 when no signer is configured (SIGNER_URL, SIGNER_SECRET).
GET|POST /api/experiments/sim/accountSite · experiments (operator)The read-only account link. POST { address } (a Hyperliquid address, 0x + 40 hex; null unlinks) — only the address is accepted, never a key. GET: { linked }, and when linked the account as the public /info API reports it — equity and margin per book (main and xyz), open positions, the last 7 days of fills — plus recon: each Shadow run's open orders matched against the positions (in both, shadow only, account only). Positions cached 30 s, fills 3 min. Every Simulator write (this, exec, create, the controls, delete) is JSON-only and same-origin-only; the body must be exactly { address: string | null }. Shadow's orders in the GET above go to operators only. Nothing here can place an order.
POST /api/experiments/sim/:id/execSite · experiments (operator)A run's execution mode: { mode: "sim" | "shadow" | "live", limits?: { perName, dailyLoss }, confirm? }. Live (2026-11-01, stage 2) starts from Shadow and needs the order signer configured and answering, and the gate: the run's setups proven, or confirm equal to the run's name as a typed override; its would-send orders then go to the signer, which applies its own caps. Shadow turns each new signal on the run into the exact order an account would get (sized by the run, rounded to the lot size, take profit and stop, checked against the rules: the run's own max open, then Shadow's per-name % of equity and daily loss %) and logs it; the run itself keeps paper-trading on its rules beside it, and the page's Rules & limits box shows the run's rules read-only above Shadow's own; nothing is ever sent and no key exists. live is refused. Each run in the GET carries exec and, in Shadow, shadow: counts, slippage against the simulator, latency, repaints caught, the orders and both P&L lines.
POST /api/experiments/sim/:id/:opSite · experiments (operator)A run's controls: pause, resume, stop, forward (a finished backtest goes forward from now). Answers with every run.
/api/experiments/sim/:idSite · experiments (operator)DELETE: removes a run (its settings and log; the trades stay in the record). Answers with every run.
GET|POST /api/experiments/myexitsSite · experiments (operator)My exits, the ledger of trades closed by hand. GET: { since, after, holdMax, me, why, exits, held }: every exit (the trade's leg, name, entry time and side, its entry and stop, when and where it closed, the R, why, note, who, from where, held when the ledger had kept it past the rules) with rules (the rules' own R, result and exit once settled; until then cur, their R now), and every position the ledger still keeps past the rules' exit with its R now. ?f=1 adds f to each exit (and to a half, half.f): the market at the ✋, read from the server's own closed candles, funding and OI with nothing after it (src/myxfeat.js, versioned by f.v): the trade's hours in, R, best and worst R so far and what it gave back; the move over 1h · 4h · 24h · 7d; RSI 14 on 1h · 4h · 1D, ATR 14 % on 1h · 1D, the distance from the EMA 20 in ATR on 1h · 4h · 1D; the 24h VWAP distance, the 20-day range position, 24h volume against the week before; funding now and over 24h, OI's 24h change; BTC over 1h and 24h; the UTC hour and weekday. A promoted spread's or basket's trade (p_alt, p_proxy, p_rot, p_tom; its name the trade's id, c@t) has no candles of its own, so from f.v 2 its snapshot is its own shape, marked kind: "spread" | "basket" (a name's has no kind and reads as at v1): the hours in, R, best and worst R and the giveback on the trade's index as the ledger measures it (100 at the entry, the stop at 100 − 100 × its distance), each closed hour at its legs' closes plus the mark, so best and worst are on closes, not highs and lows; funding now and over 24h as the index's (what a 1x long of it paid: a spread's A rate − β × B's, a basket's longs' average − its shorts'); BTC and the UTC hour and weekday; a spread's two legs each with a name's own readings (legs.a the name traded, legs.b what it trades against, without the BTC and clock readings); a basket only how many names it holds (nL, nS), its names' own readings left out. Without it, the view is as before. POST { leg: "book|tf|side" (tf: a setup, or p_<trial> for a promoted trial's trade), c, t, why: profit|loser|news|tape|margin|other, note?, from: "live" | "sim:<run id>" } closes one at the server's mark: once a signal (409 after), no undo. Nothing is sent and no run, record or position changes. JSON and same-origin only. Each Simulator run in GET /api/experiments/sim carries hand (the same run with the ledger's exits applied) when the ledger touches it.
GET /api/experiments/myexits/openSite · experiments (operator)My exits' hourly snapshots of the open trades: every hour, a few minutes past it, one JSON line for each trade a ✋ could still close (the rules still in, the rest of a half, a position the ledger keeps), { at, k: "book|tf|side|name|entry", dir, st: rules|part|keep|half, f }, f being the same market snapshot as a ✋'s (?f=1 above): the moments held, beside the moments closed. A file a month (UTC) in DATA_DIR/my-exits-open/, append-only, the last 6 months and 256 MB at most (the oldest go first; a torn last line after a crash is the reader's to skip). No query: { months: [{ m: "YYYY-MM", bytes }] }; ?m=YYYY-MM: that month's file (application/x-ndjson, 404 when there is none).
POST /api/experiments/myexits/afterSite · experiments (operator)What the ledger does when the rules close a trade at its target or time stop before a hand exit: { after: "keep" | "half" } (keep the whole position, or close half with the rules), from the next trade the rules close. JSON and same-origin only.
POST /api/experiments/live/closeSite · experiments (operator; a fresh check)Close a Live position for real, from the ✋ dialog (2026-11-04, src/live.js closeByHand): { leg, c, t, why, note? }, the trade as My exits keys it. The Live order behind it is closed through the order signer the way the app closes at a time stop — one name: that order's own fill through the signer's /close, its take profit and stop cancelled once the coin is flat; a pair or a basket: every leg — and marked closed by hand, so nothing closes it again. The exit is then written to My exits at the close's fill price (else the mark) and graded against the rules like any ✋; the setup's own trade runs on in the record. Real money: the password or a passkey within 5 minutes on this browser (403 { fresh: false } otherwise, as for going Live); 400 without the signer, 404 with no Live order behind the trade, 409 when it was never placed or is already closed, 502 when the signer refuses (the position keeps its exchange bracket). Answers My exits' view plus { ok, closed, px, keepsBracket, ledger }. JSON and same-origin only.
GET /api/experiments/handclosesSite · experiments (operator)The Live trades found closed on Hyperliquid outside the app (2026-11-04, src/extclose.js): every 2 minutes, a Live order whose position is gone from its account while its setup still holds it gets its closing fills read and told apart — how: hand (not at its take profit or stop: by hand or another tool), tp, sl, liq (a liquidation), unknown (no closing fill found in 6 h) — with the real exit (xp, x), its R over the plan's stop distance and its P&L. { at, since, settle, giveup, recs }; ?since= (ms) keeps the newer. A hand close is also written to My exits as yours (by “Hyperliquid”) and raised as an ops alert. Read-only; no-store.
GET|POST /api/experiments/weeklySite · experiments (operator)The weekly report (2026-11-04, src/weekly.js; The Lab › Weekly). GET: the shelf — { at, reports: [{ id, start, end, builtAt, closed, sumRc, pnl, win, notes }] (newest first), current, last: { id, due, built } }. A report is built by itself every Sunday at 20:00 ET for the UTC week that closed (Monday to Monday), kept in DATA_DIR/weekly-reports/, and announced on the bell and in the operators' Telegram chats; a Sunday the server slept through is built on the next hourly pass. POST { week: YYYY-MM-DD (its Monday), force?: true } builds a closed week's report by hand (409 when it exists, unless force; 400 for a week still open). JSON and same-origin only.
GET /api/experiments/weekly/:weekSite · experiments (operator)One week's report: :week its Monday, or current for the week in progress, built on the fly and never kept. ?as=md the report as Markdown, ?as=csv every closed trade of the week; ?dl=1 as a download. The JSON (v2): { week, builtAt, coverage, summary[] (the week in sentences), headline, days[], curve, recent[], prev, setups[] (each with week, all, verdict, thrive, skips), worked, didnt, top, open, regime, thrive, sim[] (the Simulator's runs), execution, exits: { mine, hand }, skips, notes[] (ranked, each with sure), trades[] }. Its trades are Track record's: the kept log merged with the board's seen-live signals.
GET|POST /api/experiments/promoteSite · experiments (operator)Promote and demote: what goes on Live. GET: { n, rt, rev, trials, rules, can, log }: each trial's state (auto or man: on Live; dem: demoted by hand), what each trial can be on Live (setup, filter on its parent setups, spread or basket (both legs, or every name, on the Live tab; orders from a Simulator run), or none with the reason), the built-in setups' switches, the last 60 changes. A trial Live can trade goes there by itself once it is green at n (5) closed trades: its average R a trade after rt (23 bps a round trip on crypto, 19 on stocks, over each trade's own stop distance) above 0; one demoted by hand never goes back by itself. POST { key: a trial's key | "rule:book|tf|side", dir: "up" | "dn" }; 409 when Live can't trade it or it is already there. JSON and same-origin only.
GET|POST /api/experiments/strategiesSite · experiments (operator; reads and writes)The Lab › Strategies. GET: the library (every strategy's stage, roster, backtest summary, forward record and signals now) and meta: the catalog of data a row can read (each item's settings, unit, type and where its history comes from), the operators, the timeframes, the limits and the templates. POST { spec } creates a draft, { template } a copy of a template. A spec: { name, book: crypto|stocks, side: long|short, tf: 1h|4h|1D, use: trade|filter, entry: [{ mode: all|any, link?: { k: and|or|then, n }, rows: [{ tf, subj: { k, p }, op, val: { mode: num|data|cat, … }, mod: { k: now|for|within, n } }] }], exits: [{ role: sl|tp|time|sig, type, p, size, groups }], opts: { be, once, maxOpen, pyr?: { n, step } }, hedge: { k: none|btc|eth|spx|ndx, size: beta|dollar, n } }, every field bounded (src/strat.js stratClean). A stop of type: chan (p: { n }) is the N-bar low under a long (high over a short) of the bars before each one, touched inside the bar, moving only in the trade's favour. opts.pyr adds units (pyramiding, up to 4): one more each time price moves step × N (the ATR of the strategy's ATR stop) past the last unit's fill, each unit with its own stop at the first stop's distance, the earlier stops moved step × N at each add; R sums every unit over the first one's risk, each unit pays its round trip, and it needs a trade strategy with a fixed stop, no targets and no other leg (no live test). The templates include the Turtles' System 1 and System 2, each side. hedge pairs every trade with the opposite position in the book's benchmark (crypto: BTC or ETH; stocks: the S&P 500 or Nasdaq-100 perp), sized by the name's rolling β over n bars or dollar for dollar; the backtest reports the pair and the name alone over the same trades. use: pair (phase 2) adds pairs: [{ a, b }] (1–10, any two market coins with history) and pair: { size: dollar|beta, n }: the rule reads the ratio A ÷ B, a row or a compared value can read one leg instead (leg: a|b), data only one market has (volume, funding, OI, VWAP) is refused on the ratio, the book is A's, and live is refused. GET also lists names (every market with history, for the pair pickers) and meta.notRatio. (Build -186) TS9's BOW and Pulse are catalog items: bowsig / pulsesig (p: { tf: any|4h|12h|1d|3d, side: same|bull|bear|either, min }, yes on the bar a call became available in; a Pulse call once confirmed) and bowleg / pulseleg (p: { tf, min }, the open leg up, down or none), read from TS9 Pine v2's history whatever the pages' v1 / v2 switch says (unknown where v2 doesn't run) and before its first call; on a pair, on a leg only. Templates bow90 and pulse90 go long on a 90+ bullish call of their engine, bow90s and pulse90s short on a 90+ bearish one; each leaves on a stop trailing 2 × the 4h ATR, half off at 2R, or after 5 days.
GET|POST /api/experiments/strategies/:idSite · experiments (operator)One strategy: its spec, log, the last backtest in full (stats, the baseline, train / test / hold-out, the z it reached and the z its tries require, the curve, trades), its forward record (the ledger plus open trades, and its verdict at 30 trades), its signals now on every name, and for a filter the built-in setups' records split by it. POST { spec } replaces a draft's spec (a frozen version refuses). DELETE removes a draft or a backtested strategy; one with a forward record can only be retired.
POST /api/experiments/strategies/:id/backtestSite · experiments (operator)Runs the backtest (optionally saving { spec } first) over every name of the book (a pair: over its pairs; 503 naming a market with no history), in 25 ms slices, one at a time (429 while another runs). Counts one more try. Answers with the strategy in full.
POST /api/experiments/strategies/:id/metaSite · experiments (operator)A strategy's name and alert message, at any stage (none of it is in the spec, so a frozen version keeps its backtest and record): { name } (up to 40 characters), { note } (its description on Telegram, up to 600) and { alertFmt: "A" | "B" } (A: one photo with the chart and the alert as its caption; B: the full text, then the chart and the next timeframe up). The whole body is checked before anything changes (400 otherwise). Answers with the strategy in full.
POST /api/experiments/strategies/:id/:opSite · experiments (operator)forward (freeze this version and start its forward record; needs an up-to-date backtest), roster / unroster (run it beside the built-in setups: Live, the Simulator, alerts; a filter tags the built-in signals of its side), live / stoplive (a Simulator run of its one leg in Shadow), retire, restore, fork (the next version as a draft), dup (a copy), alert / unalert (its signals as alerts, in the app and on Telegram; off by default). Writes are JSON-only and same-origin-only, like the Simulator's.
GET /api/ema-feedSite · 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/duelSite · backtest (operator)The D1 retest study (def=board|touch, cooldown bars); the Score duel record.
GET /api/fundamentals?coin= · GET /api/derivs?coin=SiteFinnhub financials for an equity; Coinalyze context for a coin.
POST /api/derivs/refreshOperator · derivs.refreshForce a Coinalyze refresh for one coin (per-coin cooldown).
GET /api/fund/:t · GET /api/etf/:tSiteSEC 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

RouteAccessReturns · 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 · signalsThe 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/actionableSite · actionable (operator)The Actionable board.
GET /api/export/ledgerSite · export.ledger (everyone)The ledger as a download.
GET /api/earnings · GET /api/earnings/setups · GET /api/earnings/printsSite · earnings (everyone)The calendar (upcoming, reported, reaction study); pre-earnings setup cards; (2026.11.07) one name's past earnings dates and sessions (?t= a ticker; anything else answers an empty list), for the Charts tab's event flags. private, max-age=600.
POST /api/earnings/void · POST /api/earnings/backfillOperatorVoid a feed-garbage print; run the history backfill.
GET /api/news · GET /api/news/channelsSite · news (everyone)The tape; the Telegram channel list.
POST /api/news/channelsOperator · news.writeReplace the channel list.
GET /api/housing · GET /api/liquiditySite · housing / liquidity (operator)The FRED boards.
GET /api/focusSite · focus (operator)Today's frozen list.
GET|POST /api/focus/limitsOperator · focus.limitsThe liquidity floors and the scan behind them.
GET /api/whaleSite · funds (operator)The 13F watchlist; fund=, holds=, season= views.
POST /api/whale/watchOperator · whale.writeCurate the watchlist (search, pull, add, remove, mute). Operator-only (an operator action: admin or off).
POST /api/whale/seenMember · fundsClear a fund's NEW badge for the desk ({key}) — whoever the Funds tab is open to, signed in (or on the legacy shared password while that door is open); 401 otherwise. (seen on /api/whale/watch still works for an operator.)
GET /api/congress · GET /api/insidersSite · 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/insidersOperatorLane maintenance (ingest, parse, requeue, backfill, status). (Build 2026.10.16-131) {op:"senate", n?, days?} starts a Senate eFD sync now.
GET|POST /api/notesSite · notes (operator); writes Operator · notes.writePer-ticker notes.
GET|POST /api/baskets · GET /api/ratio?num=&den=&tf=Site · baskets (operator); writes OperatorCustom 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/liveSite · 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|ackOperatorThe classification audit.

AI

RouteAccessReturns · limits
GET /api/ai-report?coin= · GET /api/ai-reportsSite · report (everyone)A cached report and your quota; the shared recent-reports feed.
POST /api/ai-reportSigned 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/askSigned 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-readAccount · ai.ask + dm.askAn AI read of a chat call (text capped at 1,200 characters).
GET /api/ai-status · GET /api/ai-sectorsSiteLock 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-lockADMIN_PASSWORDUnlock unlimited AI for this browser; reset the daily budget; re-lock. 8 wrong passwords per IP lock it for 5 minutes. A wrong password here also counts on the sign-in page's lockout (8 per address, an IPv6 caller by its /64, for 15 minutes) and on its site-wide break-glass budget; a locked address, or a spent budget, is refused here as at /login.

Without an account (shared password or Basic), AI quotas are counted per IP address.

Alerts and Telegram

RouteAccessReturns · limits
GET /api/alertsSite (yours)Your linked recipients, classes, quiet hours, schedules and a pending link code.
POST /api/alerts/link|unlink|prefs|classes|testSite (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-testOperatorClaim an unowned recipient; fire the brief / landscape / desk now.
GET /api/alerts/adoptable · POST /api/alerts/adoptAccountAdopt 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/webhooksMember (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/rulesSite (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

RouteAccessReturns · limits
GET /api/dm · GET /api/dm/sync?since= · GET /api/dm/:idAccount · dmThreads, 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/presenceAccount · dmThe 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/recapAccount · callsCopy 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 · dmThe 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/challengeAccount · callsThe 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/duelsAccount · calls1v1 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; -132: 20 invites a day, 8 to any one member, withdrawn ones counted) · pick · draw (on; both, inside the first 10% of the match) · talk (body, 280 characters) · mute (who, on).
GET|POST /api/calls/contestsAccount · callsCustom 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; -132: 6 created a day, called-off ones counted) · talk.
POST /api/calls/followAccount · callsFollow 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/boardAccount · callsThe 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/:idAccount · dmSearch 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/:idAccount · 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/dmAccount · dmEvery 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/uploadAccount · dmAn 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-subAccount · dmBrowser 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/devicesAccount · dmYour 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/targetsOperatorRun the price-target resolver now.

Your account

RouteAccessReturns · limits
GET|POST /api/accountSite / AccountWho you are; change your password (the current-password check uses the login lock).
GET /api/scripts/mine · GET /api/scripts/search · GET /api/scripts/shared · GET /api/scripts/getAccount(Build 2026.10.27) Published chart scripts: your own (with whom each is shared and how many members added it), members' public ones by name, description or author (?q=, sources not included), those shared with you, and the sources of the ones on your charts (?ids=, up to 24; a script no longer public or shared with you answers gone).
POST /api/scripts/publish · POST /api/scripts/unpublish · POST /api/scripts/share · POST /api/scripts/added · POST /api/scripts/leaveAccountPublish or update one of your scripts ({pid?, name, lang, src, desc?, vis: public|private}, 50 an account, 12,000 characters), unpublish it, share or unshare it with a member by handle ({pid, handle, on}, 100 a script), count an add for the search's order, and leave a script shared with you ({pid}). Only the owner changes a script.
GET|POST /api/prefsAccountSynced watchlist, layouts, the watchlist rail's lists and settings (allowlisted keys: watch, layouts, wlists, ts9alerts — the BOW / Pulse alert choices — and the Charts tab's chdraw (drawings), chinds (indicators and their settings), chscripts (your scripts) and chsets (indicator layouts); 64 KB per value).
GET|POST /api/positionsAccountYour wallets' positions (build 2026.10.30: every Hyperliquid wallet on your list, combined per market — positions nets the wallets, legs lists each wallet's leg, flat names markets your wallets net to zero; wallets the wallets read). POST is the one-wallet door for a member the list is not open to: linking makes that address the only wallet, unlinking empties it, and either deletes the replaced wallets' imported fills (409 for a member who manages a list).
GET|POST /api/walletsAccount · walletsYour wallet list (build 2026.10.30), up to 5. POST {op:"add", addr, label?} checks which venues the address has history on (Hyperliquid, Lighter, Arcus; 5 s each, no retries) unless the Journal sends venues; {op:"rename", addr, label}; {op:"venues", addr, venues}; {op:"remove", addr} (its imported fills go with it); {op:"merged"} records that the Journal has added the wallets it already held (once per account: GET answers merged, so another browser never adds back one you removed). A sixth address answers 409 (the Journal keeps one over the cap as its own, marked “journal only”); 20 adds an hour. Answers your own list only.
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/riskAccount · bookYour 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/journalAccount · bookThe 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/pauseAccount (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

RouteAccessReturns · limits
GET|POST /api/accessOperator(-134) avatar-clear and bio-clear remove a member’s picture or bio, audited. Members (each with its tier, online and lastActive — the last ET day the usage beacon counted them), invites (each with the tier it joins as) and stats; mint takes tier:"elevated" for an invite that joins elevated (there is no operator invite); mint or revoke invites, reset links, sign out, disable, promote, elevated (the elevated tier on or off; audited, no fresh check), 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. (2026.10.26) delete removes an account outright — the row and everything private to it in one transaction, then the alert engine's rules, linked chats, webhooks and events keyed by its uid; the uid is tombstoned so a browser still holding the handle it grew from can never adopt it again; refused for yourself and for the last operator; audited as account-delete, no undo. pk-clear, pk-ops off, admin, disable, enable (-132), delete, 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/profileAccount · profiles(Build 2026.10.22-134.) Your profile (me: display, handle, bio, av — the picture's cache key, 0 = none) and renameNextAt (0 = you may rename now). Verbs: name {display} (2–24 characters, the operator's rename rules, at most once every 30 days; written to the operator's audit list with the old name), bio {bio} (one line, 160 characters; empty removes it), avatar {data} (base64 PNG, JPEG or WebP judged by its bytes, 96 KB at most) and avatar-clear. Members only; 160 KB body.
GET /api/avatar/:uidMember · profiles(Build 2026.10.22-134.) A member's picture, served nosniff and sandboxed, revalidated on every load against an ETag of the member's av (304 when unchanged), so a removed picture stops showing at once; ?v= only makes a new picture a new URL. 404 when the member has none, or when the account is disabled and the caller is not an operator.
GET|POST /api/passkeysAccount · 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/auditOperatorThe message read-through; every thread read and search is written to the audit log.
GET|POST /api/admin/broadcastOperatorAdmin › Broadcast. GET: the sent and scheduled broadcasts with seen counts, and the audience (each member's tier, and whether a push device or a private Telegram is linked). POST {op}: send (kind note|heads|maint, body, aud {mode:"all"}|{mode:"tier",tiers}|{mode:"pick",uids}, push, tg, bannerH 0|24|168|-1, optional sendAt up to 30 days out), edit, retract (a scheduled one is cancelled), banner-off, send-now. Ten sends an hour per operator; needs an operator account (not the break-glass lease). Every step is in the Activity log.
GET|POST /api/nav-groupsOperator(Nav plan phase 2.) The navigation board. GET returns the live layout and its revision, the presets, each tab's tier, the limits and the start page. POST {op:"publish",layout:{menus:[{key?,label,views}],bar,order?}} replaces the whole layout after strict validation (1–8 menus, unique names of 18 characters at most, every movable tab placed exactly once; Messages and Admin stay out of menus; order lists every top-level bar item once — tabs by name, menus as menu:<key>, Markets, Messages and Admin included — and the shell injects it as window.__NAVORDER) and logs layout r<n> → r<n+1> in Activity; POST {op:"restore",rev} puts an earlier revision back (the Activity undo). The older {key,label} and {view,group} writes still work.
GET|POST /api/home-cardsOperator(2026.10.27) The Home tab's cards. GET returns the catalogue (each card's source tab and allowed settings), the live list and the default. POST {cards:[{k,o,t?,ops?}]} saves the whole ordered list (at most 10, each card once, every setting from its list, titles of 24 characters at most); ops:true shows a card to operators only. POST {op:"restore",rev} is the Activity undo. The shell injects each viewer's list as window.__HOME.
POST /api/start-pageOperatorThe home page. {tab} stars the tab that opening the site lands on and ⌂ goes to (any tab but Admin; Markets by default); {mode:"off"|"new",days} sends members younger than days (and signed-out visitors) to Home first. A field left out keeps its value. The shell injects each viewer's answer as window.__START; a change is an Activity row with an undo.
POST /api/rail-colsOperatorThe watchlist rail's columns for everyone: {cols:[…]}, one to four of px, dopen, chg, sig, rvol, fund, vol, oi, doi, d7, mom, in order. The answer carries cols and was; the change is a row in the Activity log, with an undo. Every page carries the current pick.
GET|POST /api/features · POST /api/nav-groupsOperatorFeature visibility; menu names and tab placement. POST {key,state} sets one feature; POST {keys:[…],state} sets several to one state in one all-or-nothing write (every key validated first; the answer carries was, each key's state before). Every change is a row in the Activity log.
GET /api/admin/summaryOperatorThe Admin panel's overview: members (online, lapsed, disabled, per tier), open invites and those expiring within a day, classification holds, alert recipients (linked, erroring, unclaimed), features (changed from default, off, quiet) and today's Activity count.
GET /api/admin/activity?limit=OperatorThe Activity log: every operator action (gates, menus, floors, invites, promote / demote, elevate, disable, delete, rename, sign-out, passkeys), newest first, with who did it. Reversible rows carry an undo the server derives from the row itself.
GET /api/admin/usage · GET /api/admin/usage/memberOperatorSitewide usage (members, and the public visitors' totals under pub); one member's drill-in (audited).
POST /api/admin/usage/publicOperatorSwitch 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/errorsOperatorResolve or reopen a client error.
GET|POST /api/admin/usage/digest · POST /api/admin/usage/digest/testOperatorWeekly digest and lapsed-member reminder settings; send the digest now.
GET|POST /api/admin/signal-modelOperator(Build 2026.10.26.) The fitted signal model, in shadow. GET: what is loaded ({model: {fitAt, families, accepted}} or null). POST {model} loads the JSON scripts/fit-signals.js --out wrote (validated: version 1, known features, finite coefficients), saved to signal-model.json on the volume; {clear:true} removes it. It scores every new claim (xm0) and the Signals tab's record checks it beside E[R]; no card, rank or alert reads it. A row in the Activity log. 400 on a malformed model. 256 KB body.
GET /api/admin/resourcesOperatorWhat each feature costs the server, for the Admin › System › Resources fold. ?w= h (the last hour, 5-minute buckets), d (the last 24 hours, the default), w (the last 7 days) or b (since this process started). Per feature: loop time held by its synchronous ticks, wall time of its yielding builds, requests and bytes served, outbound calls and failures, AI calls and estimated cost, and the memory its holders hold. Underneath: every tick, route, outside host (with the lane that called it) and AI surface × model; the loop-held chart's buckets; process samples (CPU, RSS, heap, external memory, event-loop utilization, open handles); the last memory pass by holder; the last disk scan of DATA_DIR; the event-loop reading and the 30-day reach per tab. The bucketed windows and the process rings persist in resources.json; memory and disk are the latest readings in every window. no-store.
GET /api/admin/historyOperatorThe permanent history archive, for the research scripts (scripts/history-pull.js, which signs in as the operator and keeps the cookie). No query: the index — every coin each candle table holds (5m, 1h, 4h, 12h, 1d; delisted markets included) with its first and last bar, and the archive files (oi, derivs, regime, premium) with their sizes. ?iv=&coin=&from=&to=: one page of bars [t,o,h,l,c,v], at most 20,000 bars, with next (the following page's from, null on the last). ?file=&offset=: an archive file (tab-separated, append-only) from a byte offset, so a pull resumes; x-archive-size is the size it read to. Read-only, no-store.
GET /api/admin/reviewOperatorThe strategy review's raw records (2026-11-03), for the research scripts; read-only, no-store. ?part=live: every signal seen live as it was shown (entry, stop, target, deadline, rule version, construction tags), the market when it was first seen (f: the move, RSI / ATR / EMA on 1h · 4h · 1D, VWAP, range, volume, funding, OI, BTC, the clock), its result, its path (pa: best and worst R and when, hours held) and what came after its exit (post: R 24 h and 72 h on, the best and worst inside each, hours to a later touch of its target or stop). ?part=skips: the signals the 1 : 1 R:R floor dropped, in the same shape (skip: "rr", their rr), settled as if taken — kept apart, never counted in a record. ?part=fills: each closed Live trade's real fills from its sub-account (entry and exit per leg, fees, funding, dollars, R on the plan's stop against the setup's R, slippage on each side). ?part=orders: every Shadow / Live order of every run. ?since= (ms) keeps the newer ones; anything else is a 400.
POST /api/admin/resourcesOperator{op:"measure"}: run a memory pass now instead of waiting for the 10-minute one. It walks the registered holders in ~4 ms slices that yield to the event loop; a pass already running is joined, never doubled. Answers {ok, at, ms, holders} when it lands. 400 on any other op. 1 KB body.
POST /api/admin/ts9OperatorTS9 scoring's switch controls for a coordinated calculation upgrade or rollback: {op:"pause"} (no new batch starts; the request in flight finishes; survives a restart), {op:"resume", runNow:true|false} (runNow re-scores the current boundary at once), {op:"allow", version:"…"|null} (allow exactly one next calculation version; until it is allowed, a response on any version other than the accepted one is refused and recorded), {op:"run"} (score the current boundary now without pausing; 409 while paused). Each is a row in the Activity log. 400 on a bad op or version, 409 when scoring is off on this server. 1 KB body.
GET /api/admin/ts9v2OperatorTS9 Pine v2 (BOW + PULSE, one stream per market and chart timeframe): the loop's status — which version the BOW / Pulse pages read, v2 alerts, the universes scored, the last cycle (requests, streams applied, new signals, what failed, waited or was blocked), the authenticated smoke round trip, stream and signal counts — every stream (lineage, sequence, last accepted candle, components, status and why; its checkpoint's hash, never the checkpoint) and the newest recorded signals. no-store.
POST /api/admin/ts9v2OperatorThe v2 controls: {op:"source", source:"v1"|"v2"} — the gate: which version the BOW / Pulse pages, the charts, the analyst's TS9 context and the alerts read (tab visibility stays with the feature flags); {op:"alerts", on} (v2's alert classes speak while the pages read v2, live calls only); {op:"universe", universe:"stocks"|"crypto", on}; {op:"pause"}, {op:"resume", runNow}, {op:"run"}; {op:"rebuild", market:"all"|id, tf?} (a new lineage at the next cycle; earlier evidence is kept); {op:"smoke"} (one authenticated synthetic round trip, recorded). Each is a row in the Activity log. 400 on a bad op or field, 409 when v2 is off on this server. 1 KB body.
GET /api/admin/ts9OperatorTS9 BOW/PULSE scoring, in shadow: the lane's mode and last cycle (markets, batches, series refreshed, new and retracted events, or why it was skipped), then every tracked market's nine components — the status to show (a stale one shows its latest reported status over the last good result it keeps), have/need, value, data cutoff — and the events active now with their pivot, availability and absolute expiry — plus, kept apart from those, provisional events (a partial trigger's: not exhaustive, never notified) and a previous calculation version's events still inside their original expiry, with that version's last component as evidence. Nothing here reaches members while scoring is in shadow. no-store.

Pages and assets

RouteAccessReturns
GET / · GET /index.htmlSiteThe 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/:fileSiteThis 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 · /favicon.icoSite (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

VariableDefaultMeaning
DATA_DIR./dataThe mounted volume. Unset on Railway means an ephemeral directory inside the container — history resets on every redeploy.
ACCOUNTS_BACKUP_DIR<DATA_DIR>/backupsWhere the rotated accounts.db copies go.
PORT3000Listen port. Railway injects it; do not set it there.
DEXxyzThe HIP-3 dex whose universe the equities lane polls.
CRYPTO10 disables the crypto universe entirely.
TRUST_PROXYon for Railway, off elsewhere1 = 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 site's https://… address. Its origin is also allowed for passkeys. Push, Telegram and webhook links use it only when no TS9 address is configured (below). A bare host (screener.milst.app, as Railway shows it) gets https://; quotes and spaces are dropped.
LINK_URL—(2026-10-28.) Where links in alerts point. Unset (or not an address) = the ts9.tools address on the PUBLIC_ORIGIN list, else PUBLIC_URL. When the address is not one Telegram accepts (localhost, a private name), messages still go out but their links are plain text: the boot log and the operator's alerts panel say so.
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_MINIFY10 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_ENFORCE1The Content-Security-Policy is enforced by default; 0 sends it report-only instead (the escape hatch if a page breaks — violations are reported to the health ledger either way).
XYZ_QUIET · XYZ_NO_NET—Silence the stdout log; fail the on-demand positions fetch locally instead of calling Hyperliquid (the test suite). The poller lanes still fetch when the server is started.
FNG_FEED · DVOL_FEED · VIX_FEED1The Regime card’s feeds (2026.11.05): alternative.me’s crypto Fear & Greed index and Deribit’s DVOL, each key-free and read hourly with its history, and the VIX close from FRED (VIXCLS, needs FRED_KEY, every six hours) because the xyz VIX perp is delisted. 0 turns one off; the card then leaves that reading out. Status: Admin → Lanes.
OIDOM_FEED · OIDOM_VENUES · OIDOM_MAX_SYMS · OIDOM_PASS_H1 · Binance,Bybit,OKX,Bitget,Hyperliquid,Gate · 2500 · 6(2026.11.06) The OI dominance lane: every perpetual on the listed Coinalyze venues, its daily OI history read once a pass (one call-unit a market, from the same 38/min budget as the derivs sweep, never during a sweep and at most 16 units a minute, so a pass of ~2,000 markets takes about two hours). 0 turns it off; it needs COINALYZE_API_KEY. State in oidom.json. Status: Admin → Lanes.
LAB_LAT_ALERT_MIN5(2026.11.05.) The Lab’s execution-speed alert: a run in Shadow or Live whose median bar close → order over the last 6 hours (3+ signals) is past this many minutes, or that skipped a signal as a backfill (seen past the fresh window, never traded), is an ops alert (the bell, Telegram), at most once an hour per run. The Executor tile on the Shadow panel shows the same median.
CONSENSUS_FEED1The CryptoQuant analyst-consensus lane (build 2026.10.23-135): one key-free read per mapped asset per hour, kept in consensus.db and shipped on each snapshot row as acon. 0 turns it off; rows then carry no acon. Status: Admin → Lanes (fresh / no data / unknown / failing).
OICAP_FEED1The open-interest cap lane (build 2026.10.25): perpDexLimits every 10 minutes and perpsAtOpenInterestCap every minute for the dex, shipped on each xyz row as ocap, with the oicap alert class. 0 turns it off; rows then carry no ocap. Status: Admin → Lanes.
SHORTVOL_FEED1The FINRA short-sale volume lane (build 2026.10.25): one key-free daily file per trade date for the US-listed names, kept in shortvol.db and shipped on each eligible row as svol. 0 turns it off; rows then carry no svol. Status: Admin → Lanes.
SHORTINT_FEED1The short-interest and float lane (build 2026.10.26): FINRA's twice-monthly short-interest file and the SEC-derived float estimate, shipped on each eligible row as sint. 0 turns it off; rows then carry no sint and the Shorts tab shows short-sale volume only. Status: Admin → Lanes.
TS9_SA_KEY—The Google service-account key (its JSON, or base64 of it) for TS9's private BOW / Pulse scoring service. Unset = no TS9 scoring lane and empty BOW / Pulse pages. Never commit it; it is kept out of logs.
TS9_SCORING—off turns the shadow scoring lane off even with a key set (the boot smoke check still runs).
TS9_SCORING_URLthe deployed serviceOverride of the TS9 scoring service's origin.
M5_KEEP_DAYS— (keep)(Build 2026.11.01.) The 5-minute archive is kept whole by default: it is the only copy of every 5m bar past Hyperliquid's ~17-day window. A number brings a rolling cut back, never under 370 days.
WS_CANDLES1The 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 300 compared bars (spread over at least 4 bar times) disagree, even with the worst bar time left out, 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

VariableDefaultMeaning
SITE_PASSWORD · SITE_USER— · friendThe shared door. Unset = the read-only site is open to anyone with the link, and the AI routes and /claim are closed.
LEGACY_SHARED_PASSWORD10 once everyone has an account: closes the shared door for good — the login form's shared password and HTTP Basic both.
SESSION_SECRET · SESSION_DAYSderived · 30HMAC key for legacy sessions (derived from the accounts secret when unset); session lifetime.
ADMIN_PASSWORD · ADMIN_DAYS— · 30Break-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_MS300000How long a message sits unread before it escalates to Telegram / push.

Data sources

VariableMeaning
SEC_CONTACTGoes in the SEC user-agent; their fair-access policy wants a real contact.
FINNHUB_TOKENEarnings 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_KEYMacro prints, Housing, Liquidity. Without it the FOMC rows still serve.
FMP_API_KEY(Build 2026.10.23-135) The Senate lane's fallback feed: Financial Modeling Prep's Senate disclosures, read only on a run where eFD refuses us. The plan must include Senate disclosures. Without it, a refused run fails and the self-heal keeps retrying eFD.
COINALYZE_API_KEYCrypto liquidation / OI lane.
TG_BOT_TOKENTelegram alerts, DM escalation, password-reset codes. Unset = the transport does not exist.
PUSH_VAPID_SUBJECTContact sent with web-push VAPID claims (mailto:…).
LEDGER_BACKUP_REPO · LEDGER_BACKUP_TOKEN · LEDGER_BACKUP_BRANCHWeekly 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

VariableDefaultMeaning
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_FALLBACKper provider (Anthropic: claude-opus-5-5 · claude-opus-4-8, 16000 output tokens)The report/ask model and the one tried when it errors or its output fails validation. A report the fallback wrote says so in its footer (fallback from …) with the reason; the signed-in /api/health AI block names every surface's model and effort.
AI_CLASSIFY_MODEL · AI_CLASSIFY_EFFORTper provider · lowThe unattended news/sector classifier and its reasoning effort.
AI_REPORT_EFFORT · AI_ASK_EFFORT · AI_REPORT_TTL_MINlow · medium · 30Reasoning effort per surface (reports: low — the read is structured and validated server-side, and on Opus 5.5 thinking is always on); cached report lifetime (minimum 5).
AI_REPORTS_PER_DAY · AI_USER_PER_DAY · AI_USER_PER_MONTH5 · 3 · 20Report 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_DAY12 · 50 · 5Terminal ask budgets.
BRIEF_ENABLED · BRIEF_DEFAULT_HOUR · BRIEF_PER_DAY · BRIEF_EFFORT · BRIEF_MODEL · BRIEF_MODEL_FALLBACK · BRIEF_MAX_TOKENS1 · 10 · 6 · medium · Anthropic claude-sonnet-5-5, else AI_MODEL · its fallback is AI_MODEL · 12000The 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_TOKENS1 · registry default · 4 · high · BRIEF_MODEL · … · 12000The 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

  1. Push the repository to GitHub; New Project → Deploy from GitHub repo. Nixpacks detects Node and runs node server.js from railway.json.
  2. Add a volume mounted at /data and set DATA_DIR=/data. Without it every redeploy loses the OI history.
  3. Set ADMIN_PASSWORD, then the optional keys above. Do not set PORT.
  4. Settings → Networking → Generate Domain; set PUBLIC_URL to it so push and Telegram links resolve.
  5. Open the domain: the first account is created at /bootstrap with ADMIN_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 (enforced; CSP_ENFORCE=0 sends it report-only; violations are counted on /api/health either way).
  • Server errors say nothing about the server: every 5xx body 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 from PUBLIC_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-glass ADMIN_PASSWORD remains 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; xyz is 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. /status to 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 unlock or admin 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.