Methods
How every number on TRAI is produced: what it is, what it means in plain English, where the data come from, the exact formula, its limitations, and how it feeds the daily picks. All calculations are deterministic and reproducible from public data. Screening/research, not investment advice.
Daily screen & buckets (core engine) · Signal scorecard & walk-forward backtest · Live-rule scorecard (10:00 entry, ATR stop/target, 60-min time stop) · S-103 — Fade-long by VIX>22 (pre-registered split) · S-105 — 60-min MFE/MAE + shadow sqrt-time brackets · Regime model · Earnings event study & gap-risk flags · Correlation & clustering · Intraday patterns (time of day, ORB, VWAP reversion) · Options-implied volatility · Macro & sector rotation · Daily Overview video & explainers
Daily screen & buckets (core engine)
Page · JSON /trai/api/latest
- What it is
- Twice each weekday (~8:45 AM and ~4:20 PM ET) every name in the 156-stock universe is scored on relative volume, residual (market-adjusted) return, 1-day z-score, position in the day range, VWAP and opening range, then sorted into three buckets.
- Plain English
- Long momentum = stocks rising on heavy volume beyond what the market explains, closing strong. Short momentum = the mirror image. Mean reversion = unusually large moves on ordinary volume, which are candidates to fade.
- Data source
- Yahoo Finance chart API: 6 months of daily bars and 5 days of 5-minute bars (incl. pre/post market) per symbol.
- Formula
RelVol = session volume / 20-day average volumeβ60 = cov(r, r_SPY) / var(r_SPY) over 60 sessions ending the prior day; residual = r − β·r_SPY; residual z = residual / σ(residuals, 60d)1d z = r / σ(r, 20d); range position = (C − L) / (H − L); ATR14 = Wilder average true rangeLong momentum: RelVol > 1.5, residual > 0, range pos ≥ 0.7, close > VWAP. Short momentum: mirror. Mean reversion: |1d z| > 2 with 0.7 ≤ RelVol ≤ 1.5Liquidity filter: price > $10, 20d ADV > $500M, 2% ≤ ATR% ≤ 8%
- Limitations
- Thresholds are rules of thumb, not optimized.
- Sector "clusters" in the dashboard are a coarse proxy; the correlation clusters below are more precise.
- How it feeds picks
- This is the pick list itself. Every other method below either tests it (scorecard) or adds context/risk flags to it.
Signal scorecard & walk-forward backtest
Page · JSON /trai/api/scorecard
- What it is
- Answers "has each bucket actually made money the next day?" two ways: (1) a walk-forward replay that re-computes the engine's daily features on each of the last ~126 sessions using only data available that day, and (2) live scoring of every stored engine run once its next session closes.
- Plain English
- Expectancy is the average net profit per trade after costs. A positive number with a t-stat above ~2 is evidence of an edge; below that, results could easily be luck. Liquidity-adjusted Sharpe additionally charges a market-impact cost for a $1M ticket.
- Data source
- Yahoo daily bars (2 years) for the universe + SPY; TRAI's own stored runs in Vercel Blob; regime labels from the regime model.
- Formula
Trade for signal on day t: enter at open of t+1, exit at close of t+1 (O→C); also close t → close t+1 (C→C). Short trades flip the sign.Net return = gross − 5 bp round-trip cost. ATR units = (C₁ − O₁)/ATR14 on the signal day.Hit rate = share of trades with positive gross O→C; payoff = avg win / |avg loss|; t = mean / (sd/√n)Daily portfolio = equal weight of that day's picks; Sharpe = mean/sd × √252 (only with ≥ 10 active days)Liq-adj Sharpe subtracts square-root impact: 2 × 0.5 × σ_daily × √($1M / ADV20) per tradeTop-3 = only the 3 strongest names per bucket per day (ranked by |residual z| for momentum, |1d z| for mean reversion)Regime breakdowns use the VIX label (calm/transitional/stress), the quant regime label and the ADX trend state as of the signal day
- Limitations
- The replay approximates VWAP with the typical price (H+L+C)/3 because historical 5-minute bars only go back 60 days — the VWAP condition is therefore approximate.
- Survivorship bias: the universe is today's list of large liquid names, applied to the past.
- ~6 months is one market environment; small samples (n < 100) are very noisy. Costs are assumptions, not your actual fills.
- Live scoring starts with the first stored run (2026-10-22) and grows by ~2 runs per day.
- How it feeds picks
- Tells you which buckets to trust and size up or down, and in which regimes. The dashboard links to it next to the regime.
Live-rule scorecard (10:00 entry, ATR stop/target, 60-min time stop)
Page · JSON /trai/api/scorecard
- What it is
- The main scorecard grades picks as "buy at the next open, sell at the next close". TRAI's live rules trade differently, so every stored live pick is also re-scored the way those rules would actually have traded it on the next session, using 5-minute price bars.
- Plain English
- It answers "what would the picks have done under the rules we actually use?" R is the result in units of the stock's normal daily range (ATR): +2 means the target was hit, −1 means the stop was hit, something in between means the 60-minute time limit closed the trade. This is measurement only: no claim that a bucket works until at least 30 different days have been scored, because picks from the same day tend to move together. The day-level bootstrap 90% CI is on the per-day mean R (one number per signal day), not on the per-trade meanR — those are different estimands, so a per-trade meanR can sit outside the per-day CI without any fault. When TARGET exits are rare (< 2% of STOP+TARGET+TIME scored exits; NO_DATA excluded), the liveRules column is labeled "target unreachable — effective rule is time-stop" — a measurement label only, not a change to the engine's live trading rules.
- Data source
- Yahoo Finance 5-minute regular-session bars (Yahoo keeps about 60 days) for the outcome session; TRAI's stored runs in Vercel Blob for the picks and their ATR(14).
- Formula
Entry P0 = open of the first 5-minute bar starting at or after 10:00 ET on the session after the signal (no entries 9:30–10:00). No bar → NO_DATA (excluded from averages, counted in coverage).Stop = P0 − side × 1 × ATR14, target = P0 + side × 2 × ATR14, ATR14 as of the signal-day close (known when the pick was made).Walk bars from the entry bar: the first bar that touches a level exits there; if one bar touches both, the STOP is assumed (conservative); a bar that opens beyond the stop exits at that open. Otherwise exit at the close of the bar ending 11:00 ET (TIME).Net return = side × (Pexit / P0 − 1) − 5 bp − square-root impact (same cost model as the open→close columns); R = side × (Pexit − P0) / ATR minus the cost converted to ATR units.Per bucket and per mean-reversion leg: picks, coverage, hit rate, mean/median R (per-trade), exit-reason mix, distinct days, the mean of one-number-per-day R, and a day-level bootstrap 90% CI of that per-day mean only (2000 day-resamples, fixed seed) — not a CI on the per-trade meanR.
- Limitations
- Measurement only — no edge claim until ≥ 30 distinct days are scored. Same-day picks are correlated, so per-pick hit rates overstate the evidence.
- If TARGET / (STOP+TARGET+TIME) < 2%, the UI flags the aggregate as "target unreachable — effective rule is time-stop". Display/measurement only; the engine still uses the same 1×ATR stop / 2×ATR target / 60-min time stop.
- Inside a 5-minute bar the order of high and low is unknown; assuming the stop first can understate results slightly.
- Fills are assumed exactly at the bar open / stop / target level; real fills can slip, especially at the stop.
- Only live picks are scored this way; the 6-month replay still uses the daily open→close proxy because 5-minute history only goes back ~60 days.
- How it feeds picks
- Shown on the scorecard next to the open→close live numbers, and in /api/scorecard as liveRules (per-pick detail in liveRulesTrades).
S-103 — Fade-long by VIX>22 (pre-registered split)
Page · JSON /trai/api/scorecard
- What it is
- A pre-registered measurement split of mean-reversion fade-long trades into signal-day VIX > 22 vs ≤ 22. Threshold 22 is the existing risk-halving rule, not fitted on this sample. Published on both the replay open→close proxy and the liveRules 5-minute path, with distinct-day counts on each side.
- Plain English
- Asks whether fade-longs behave differently on high-VIX days (the same days where risk is already halved). Measurement only — no action tag. Sides with fewer than 4 distinct signal days are labelled "<4 days: not interpretable" and greyed out rather than suppressed, so an empty calm-VIX side in an all-STRESS window is visible.
- Data source
- Same as the scorecard: Yahoo daily bars + regime VIX history for the replay half; liveRules trades + signal-day VIX for the liveRules half.
- Formula
Filter: bucket = mean_reversion AND side = long (fade after a big down day)Split on signal-day VIX from the regime history: > 22 vs ≤ 22 (existing sizing threshold)Replay half: open→close net of 5 bp; report n, distinct days, hit rate, mean net, t, mean O→C ATR, per-day meanLiveRules half: scored picks only (reason ≠ NO_DATA); report n, distinct days, hit rate, mean R, t, per-day mean Rinterpretable = distinctDays ≥ 4; otherwise UI greys the row and shows "<4 days: not interpretable"
- Limitations
- Pre-registered before the Nov 6 2026 publish; threshold 22 was already the live sizing rule.
- With a short all-STRESS liveRules window one side may be near-empty — that is labelled, not hidden.
- Replay and liveRules answer different questions (full-day proxy vs 60-min path); do not pool them.
- How it feeds picks
- Shown on /scorecard as "S-103 — Fade-long by VIX>22"; /api/scorecard field s103.
S-105 — 60-min MFE/MAE + shadow sqrt-time brackets
Page · JSON /trai/api/scorecard
- What it is
- For every liveRules trade, records the 60-minute max favourable and max adverse excursion in daily-ATR units, and re-runs the same bars under a shadow bracket scaled by k = √(60/390) ≈ 0.392 (stop k×1×ATR, target k×2×ATR). Also runs a k=1 regression shadow that must reproduce the primary exit mix. Measurement only — live stop/target/time stay 1×ATR / 2×ATR / 60 min.
- Plain English
- Tells you what range is actually reachable in the first hour and whether a tighter, horizon-scaled bracket would ever hit TARGET. k is a scaling heuristic (ATR≈range, intraday vol is U-shaped), not a fitted parameter and not a derivation. Cost becomes a larger share of the tighter stop: ≈ costRT × P0 / (k·ATR).
- Data source
- Same Yahoo 5-minute bars already used by liveRules; no new data source.
- Formula
MFE = max favourable excursion / ATR14 over bars from entry through the 11:00 ET time-stop window; MAE = max adverse magnitude / ATR14 (both ≥ 0)Per bucket: p50 / p90 / max of MFE and of MAE; n and distinct days; label "insufficient" when distinctDays < 3Shadow: stop = k×1×ATR, target = k×2×ATR, same 60-min time stop, stop-first same-bar tie-break; publish only k ∈ {1, 0.392}Shadow R is net in units of the shadow stop distance (k×ATR); k=1 must match the primary STOP/TARGET/TIME counts
- Limitations
- Measurement / diagnostic only — does not change live trading rules or sizing.
- k = √(60/390) assumes constant intraday variance and ATR≈σ-range; both are false, so treat 0.392 as a heuristic.
- No k-grid: publishing other k values after seeing results would be curve-fitting.
- Inside a 5-minute bar the high/low order is unknown; stop-first can understate TARGET hits slightly.
- How it feeds picks
- Shown on /scorecard under the liveRules section; /api/scorecard liveRules.mfeMae and liveRules.shadow.
Regime model
Page · JSON /trai/api/regime
- What it is
- A composite risk-on / risk-off score from five independent inputs, plus a trend-strength (ADX) state, recomputed daily with a ~1-year history.
- Plain English
- Positive = calm, trending, broad market (conditions where momentum usually works and stops get hit less). Negative = volatile, narrow, inverted vol curve (cut size, favor mean reversion, expect gaps).
- Data source
- Yahoo daily bars for SPY, ^VIX, ^VIX9D, ^VIX3M and the full universe (for breadth).
- Formula
Each component scaled to −1…+1 (positive = risk-on): vol = 1 − 2·pctRank(SPY RV20, 252d); vix = 1 − 2·pctRank(VIX, 252d); term = clamp((0.95 − VIX/VIX3M)/0.10); trend = ½·clamp((C/MA50 − 1)/3%) + ½·clamp((MA50/MA200 − 1)/3%); breadth = avg(2·%>MA20 − 1, 2·%>MA50 − 1)Score = 100 × (0.20·vol + 0.15·vix + 0.20·term + 0.25·trend + 0.20·breadth); ≥ +25 risk-on, ≤ −25 risk-off, else neutralTrend state: ADX14 (Wilder) on SPY ≥ 25 trending, < 20 choppy, between = mixedA/D line = cumulative Σ (advancers − decliners)/names across the universe
- Limitations
- Weights are fixed by judgment, not fitted, to avoid curve-fitting — the score is descriptive, not predictive by construction.
- Breadth uses TRAI's 156-name universe, not the whole NYSE.
- How it feeds picks
- Shown on the dashboard beside the legacy VIX label; used to slice scorecard results by regime.
Earnings event study & gap-risk flags
Page · JSON /trai/api/events
- What it is
- For each name's last 4 reported quarters: the opening gap on the first session that could trade the news, day-1 drift from open to close, whether the gap filled, relative volume, and drift over days 2–5. Also the next scheduled report and a gap-risk flag.
- Plain English
- Shows how violently a stock usually reacts to earnings and whether the first move tends to stick or reverse. Gap-risk flags warn you that a pick reports within days.
- Data source
- Yahoo quoteSummary (calendarEvents, earnings modules) for dates/EPS; Yahoo daily bars for prices.
- Formula
Reaction session = report day if before open (BMO), next session if after close (AMC); unknown timing → the candidate session with the larger gapGap = O_react / C_prev − 1; gap in ATR units = (O − C_prev)/ATR14; day-1 drift = C/O − 1; filled = session range crossed C_prevContinuation = day-1 drift in the same direction as the gapGap risk: imminent if the next session trades the report (≤ 1 session), elevated if within 5 sessions
- Limitations
- Only 4 quarters per name are available publicly — per-ticker stats are rough; use the cross-sectional table for base rates.
- Future dates may be estimates (flagged "Est.") and holidays are ignored when counting sessions.
- How it feeds picks
- Earnings column and warnings in the dashboard buckets and screener; implied-vs-historical comparison on the Options page.
Correlation & clustering
Page · JSON /trai/api/correlations
- What it is
- A 60-day correlation matrix of daily returns for the universe, hierarchical clusters of names that trade together, and the effective number of independent bets.
- Plain English
- Six picks that are all semiconductors are really one bet. The effective-bets number tells you how diversified a list actually is; rising average correlation means the market is moving as one (macro-driven).
- Data source
- Yahoo daily bars.
- Formula
ρ_ij = Pearson correlation of daily close-to-close returns over the last 60 sessionsDistance = 1 − ρ; average-linkage agglomerative clustering; clusters cut where average within-cluster ρ ≥ 0.5Effective bets (participation ratio) N_eff = N² / Σ_i Σ_j ρ_ij² — equals N for uncorrelated names and 1 for identical onesRolling series recompute avg ρ and N_eff on each of the last ~120 days
- Limitations
- 60 observations give noisy correlations (±0.13 standard error near zero).
- Correlations jump toward 1 in sell-offs, exactly when diversification is needed.
- How it feeds picks
- Each dashboard bucket shows "N names ≈ X independent bets"; screener shows the correlation-cluster id.
Intraday patterns (time of day, ORB, VWAP reversion)
Page · JSON /trai/api/intraday
- What it is
- Uses ~60 sessions of 5-minute bars to measure when volatility and volume happen, and tests two classic intraday setups across the universe.
- Plain English
- The U-shape tells you when liquidity is deepest and moves are biggest. ORB and VWAP-fade results tell you whether those well-known setups have paid in this universe recently, after costs.
- Data source
- Yahoo chart API 5-minute bars (regular session only; maximum 60 days available).
- Formula
Relative vol by slot = mean |5m return| in that slot / stock's mean |5m return|, averaged across names; volume share = slot volume / session volumeORB: opening range = 9:30–10:00 high/low; first later bar that trades through the range → enter at the boundary (stop entry), exit at close; follow-through = exit beyond entry; R = P&L / range widthVWAP reversion: first 5m close (10:00–14:59) at least 0.3 × ATR14 away from session VWAP → fade toward VWAP, exit at close; touch rate = VWAP traded again before closeAll P&L net of 5 bp round trip
- Limitations
- Only ~3 months of history; one regime.
- Bar-close fills ignore slippage at the boundary; no stops/targets modeled beyond exit at close.
- How it feeds picks
- Context for entry timing (TRAI avoids 9:30–10:00 entries) and whether to act on ORB/VWAP-style triggers.
Options-implied volatility
Page · JSON /trai/api/options
- What it is
- Daily snapshot of at-the-money implied vol (IV30), realized vol (RV20), the volatility risk premium, term-structure slope, and the move implied into the next earnings report.
- Plain English
- IV is what the options market expects; RV is what happened. IV well above RV = options are "expensive" / market expects turbulence. The implied earnings move is the size of move the market prices for the report.
- Data source
- Yahoo options chains (via cookie + crumb session) and daily bars; risk-free rate = 13-week T-bill (^IRX).
- Formula
IV per contract by inverting Black-Scholes on the bid/ask mid (bisection); ATM = strike nearest spot with two-sided quotes on both call and putIV30 = variance-interpolated ATM IV between the expiries bracketing 30 days (near ≥ 7 DTE)RV20 = stdev(20 daily log returns) × √252; VRP = IV30 − RV20; term slope = IV(far) − IV(near)Implied earnings move = (ATM call mid + ATM put mid) / spot at the first expiry on/after the reaction session (≤ 45 DTE)
- Limitations
- Yahoo options data are delayed/unofficial; outside market hours mids can be stale (page warns).
- Black-Scholes on American options ignores early exercise and dividends — fine for ATM short-dated, not exact.
- If the earnings expiry is far after the report, the straddle includes ordinary variance and overstates the event move.
- How it feeds picks
- VRP and implied moves add context to gap-risk flags and to position sizing around events.
Macro & sector rotation
Page · JSON /trai/api/macro
- What it is
- Key rates, curve, dollar and credit-spread series, plus relative strength of 12 sector ETFs against SPY with a relative-rotation (RRG-style) view.
- Plain English
- Rising yields/dollar and widening credit spreads are headwinds for risk; sector rotation shows which groups the market is favoring now (where momentum picks are more likely to keep working).
- Data source
- FRED (Federal Reserve Bank of St. Louis) public CSV: DGS10, DGS2, DGS3MO, T10Y2Y, T10Y3M, DFII10, T10YIE, DTWEXBGS, BAMLH0A0HYM2, BAMLC0A0CM. If FRED is unreachable from the server (it can time out from cloud IPs), rates/curve/real yield/breakeven fall back to the US Treasury daily par and real yield-curve CSVs (home.treasury.gov — the same underlying data), the dollar to ICE DXY (Yahoo DX-Y.NYB) and credit spreads to HYG/IEF and LQD/IEF price ratios (proxies, labeled as such). Yahoo daily bars for SPY and XLK, SMH, XLC, XLY, XLF, XLI, XLB, XLE, XLV, XLP, XLU, XLRE.
- Formula
Changes over 5 / 21 / 63 observationsRS = ETF / SPY; RS-Ratio = 100 × RS / SMA50(RS); RS-Momentum = 100 × RS-Ratio / RS-Ratio 10 sessions agoQuadrants: leading (ratio ≥ 100, mom ≥ 100), weakening (≥ 100, < 100), lagging (< 100, < 100), improving (< 100, ≥ 100)
- Limitations
- This is a simplified RRG (the commercial JdK version uses proprietary normalization).
- FRED series lag by a day; the broad dollar index updates weekly.
- How it feeds picks
- Context for whether the tape favors the sectors your picks are in.
Daily Overview video & explainers
Page · JSON /trai/api/daily
- What it is
- After the post-close research jobs, a generator on the research box (scripts/daily-overview/) turns that session’s TRAI datasets into a ~2-minute narrated video with 9 slides, burned-in captions plus a WebVTT track, a written summary, and plain-English explainer cards for every quant term used.
- Plain English
- A plain-English recap of what the engine saw today: the volatility regime, what moved and (when the data show it) why, the top names per bucket, what the scorecard says has or hasn’t worked, correlation clusters, upcoming earnings gap risk and sector rotation.
- Data source
- Only TRAI’s own public JSON for that session: /api/latest (or the matching /api/history run), /api/regime, /api/scorecard, /api/events, /api/correlations, /api/macro, /api/options. The narration is template-generated from those numbers — no outside news, no language-model text — and a move is only given a cause when the data contain one (an earnings report in /api/events); otherwise it is called unexplained. Learn-more links are re-checked (HTTP 200) on every build.
- Formula
facts.py: fetch datasets → check each dataset is as of the session date (abort otherwise) → flat fact sheetnarrate.py: fact sheet → 9 segments of sentences (each quant term defined in-line) → speech text (%, ±, tickers normalized)slides.py: matplotlib 1920×1080 dark slides with date, data source and “not investment advice” footermedia.py: edge-tts neural voice (fallback espeak-ng) per sentence → silence-trimmed WAV timeline → caption cues → ffmpeg H.264/AAC MP4 (CRF 27, 24 fps, faststart, mov_text subs)publish.mjs: Bearer CRON_SECRET → /api/daily/upload issues short-lived single-path Blob client tokens → files uploaded to daily/<date>/<build>/ → /api/daily/publish writes daily/<date>/overview.json, daily/index.json, daily/latest.json
- Limitations
- “Why” is limited to earnings events and the numbers in TRAI’s datasets; news-driven moves are labeled unexplained.
- The neural voice uses Microsoft’s online edge-tts service; if it is unreachable the robotic espeak-ng fallback is used (noted in the JSON as tts).
- Runs off-platform (box cron or GitHub Actions), not on Vercel, because rendering video needs ffmpeg and minutes of CPU.
- Caption timing is proportional to characters within each spoken sentence, so it can drift by a fraction of a second.
- How it feeds picks
- Doesn’t change picks — it explains them. Every number comes from the datasets above for that date.
Schedule (weekdays, Eastern Time)
- 7:30 AM — earnings events & gap-risk flags
- 8:45 AM — premarket engine run
- 3:30 PM — options-implied volatility snapshot (during the session so quotes are live)
- 4:20 PM — post-close engine run
- 4:40 PM — regime, correlations, macro, scorecard
- 5:05 PM — intraday patterns
- ~5:35 PM — Daily Overview video (generated off-platform; skipped if datasets aren’t as of today)