TRAI quant day-trading research

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 range
  • Long 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.5
  • Liquidity 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 trade
  • Top-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 mean
  • LiveRules half: scored picks only (reason ≠ NO_DATA); report n, distinct days, hit rate, mean R, t, per-day mean R
  • interpretable = 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 < 3
  • Shadow: 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 neutral
  • Trend state: ADX14 (Wilder) on SPY ≥ 25 trending, < 20 choppy, between = mixed
  • A/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 gap
  • Gap = O_react / C_prev − 1; gap in ATR units = (O − C_prev)/ATR14; day-1 drift = C/O − 1; filled = session range crossed C_prev
  • Continuation = day-1 drift in the same direction as the gap
  • Gap 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 sessions
  • Distance = 1 − ρ; average-linkage agglomerative clustering; clusters cut where average within-cluster ρ ≥ 0.5
  • Effective bets (participation ratio) N_eff = N² / Σ_i Σ_j ρ_ij² — equals N for uncorrelated names and 1 for identical ones
  • Rolling 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 volume
  • ORB: 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 width
  • VWAP 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 close
  • All 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 put
  • IV30 = 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 observations
  • RS = ETF / SPY; RS-Ratio = 100 × RS / SMA50(RS); RS-Momentum = 100 × RS-Ratio / RS-Ratio 10 sessions ago
  • Quadrants: 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 sheet
  • narrate.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” footer
  • media.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)

Each job has two UTC cron entries (EDT and EST) and only executes in the matching ET hour.