Methodology

How every number on the dashboard is constructed — universe, baskets, return math, the regime engine, panels, and the guardrails.

Universe & Data Sources

The stock universe is the large-cap slice of a VTI-derived warehouse — roughly 1,000 large-cap names tracked intraday, with small/mid strata followed at daily resolution. A ~95-symbol benchmark board covers the macro read: ETF proxies plus a set of bellwether single names.

Benchmark-board groups

index rates_credit commodities_fx global style sector theme single cross_asset (legacy)
VIX is not carried on the board — it is an index, not a security, with no tradeable-feed source; path-decayed futures ETPs (VIXY/VXX) are never silently substituted because showing one as "VIX" would misstate the level. The Regime volatility component and the Sigma Board carry the vol read.
USOIL is proxied by USO, disclosed in the display name "WTI Oil (USO — USOIL proxy)" — USOIL itself has no warehouse data. SPCX is a fund ticker, not SpaceX equity; SpaceX is private and exposure via the SPCX fund is indirect.

Editions are generated five times per trading day: premarket 07:00 morning 10:00 midday 13:00 close 16:00 wrap 19:00 (all ET).

Factor Baskets — How They're Built

Every stock is scored nightly on momentum, trend structure, volatility posture and institutional-accumulation traits, then assigned to exactly one production basket — the behavioral taxonomy that headlines the dashboard:

Thematic Momentum Leadership Smaller-Cap High-Beta Breakout Institutional Large-Cap Accumulation Low-Vol Aligned Uptrend Neutral / Transition Oversold / Mean-Reversion Watch Extended Momentum Risk Broken Momentum / Avoid Unclassified / Insufficient Data

Four more dimensions slice the same universe independently: theme (keyword/industry baskets), sector, size and style. A stock therefore appears in one basket per dimension — the drill-down drawer on any card shows the exact membership behind that cell.

Baskets with fewer than 10 members are flagged thin and can never headline a best/worst claim; "Unclassified / Insufficient Data" is a disclosure bucket, not a signal.
Show the math
bucket(name) = the FIRST warehouse CASE branch the name satisfies (branches are precedence-ordered) assigned upstream in v_vti_factor_production_scores_5b — BoltFactors never re-derives it downstream gates (this repo, contracts.py): thin n_members < 10 retained + flagged thin, no leadership claim report floor baskets with n_members >= 10 < 2 run rejected (fail closed) vocabulary label not in PRODUCTION_BASKETS error, run killed (drift) claim_eligible truthy may headline leaders / rollups / rotation / breadth
Basketneligible (n ≥ 10)?claimable?
Thematic Momentum Leadership24yesyes
Institutional Large-Cap Accumulation41yesyes
Oversold / Mean-Reversion Watch6nono — thin, excluded from broad claims
Unclassified / Insufficient Data3nono — disclosure bucket, any size
2 baskets clear n ≥ 10 → passes the ≥2 floor. A retired name such as "Mega-Cap Liquid Momentum Leadership" (merged 2026-07-17) → vocabulary-drift error, run killed.

The warehouse decides the bucket (first matching CASE branch wins); BoltFactors consumes the label from v_vti_factor_production_scores_5b, pins the vocabulary, fails closed on drift, and gates thin/disclosure baskets out of every leadership claim. Source: boltfactors/contracts.py · ingest/warehouse.py

Return Math

Basket returns are computed three ways and shown where each is honest: equal-weight (the default headline — every member votes equally), cap-weight (what the dollars did), and median (robust to outliers).

Show the math
member r_i = (now / anchor - 1) x 100 NaN if either side missing or non-positive winsor clip r_i to ±400 before every MEAN-based aggregate (not the median) equal-wt EW = mean( clip(r_i) ) over usable i cap-wt CW = Σ w_i·clip(r_i) / Σ w_i over i with r_i and w_i > 0 (else falls back to EW) median med = median( r_i ) NOT winsorized — already robust coverage = 100 x usable / total degraded when < 60%
Memberreturnwinsorizedcap wt
AAA+3+3100
BBB-2-250
CCC+8+830
DDD+700+40020
EEENaNdropped10
EW = +102.25% · Median = +5.5% · CW = +42.2% · coverage = 4/5 = 80% (not degraded; at 55% it would be). The one parabolic name dominates the mean even after the clip — exactly why the median exists.
Horizonanchor date — resolve_anchors(2026-07-23)
1D2026-07-22
WTD2026-07-17
1M2026-06-23

Source: boltfactors/compute/returns.py · compute/horizons.py

The Regime Engine

Six components — trend, breadth, volatility, rotation, size, style — are each scored from banded market inputs (SPY vs moving averages, % of the universe above the 200-day, advance/decline, sector-rank churn, small-vs-mega spreads, high-beta-vs-low-vol spreads). A weighted composite maps to one of five labels:

CompositeLabelHistorical read
< −0.70Risk-Off Stresshighest forward volatility
−0.70 … −0.15Distribution Riskelevated volatility, deteriorating breadth
−0.15 … +0.55Choppy Neutralthe modal state (~half of all days)
+0.55 … +0.90Constructive Rotationhealthy participation, lower volatility
> +0.90Risk-On Trendlowest forward volatility

The published label flips only after the raw label disagrees on two consecutive runs — that confirmation collapses daily churn from ~40% to ~16% and makes the label publishable five times a day.

Lifecycle stats on the hero card

Every stat is empirical, fitted on 2022→2026 daily history (~1,050 confirmed days, refit annually): p(stay) is the one-day transition-matrix diagonal, typical dwell the mean run length, the implied end date is the start date plus typical dwell in NYSE trading days, and the exit paths are where the label historically went conditional on leaving. Exits form a clean adjacency ladder — the tail states never jump straight across Choppy Neutral.

Regime progression timeline

The Regime Assessment card plots the composite trajectory of every published run over the trailing month (up to five editions a day) against the five label bands, so a same-day flip is visible as a line crossing a band edge rather than a single word that changed. The series is assembled at report-build time from the local run archive — never interpolated: an edition with no run is a gap, and the count of missing runs is printed. An amber ring on a point marks a run where the raw label disagreed with the published one — confirmation-lag pressure that has not yet (or may never) flip the published label. The vertical axis is clamped to ±1.3 (the composite's realized range with headroom to show all four band edges); a point beyond the clamp keeps its true value in its hover title. The hero also carries a compact three-day tape of the same data that deep-links here.

The composite is a risk/volatility-state gauge, not a direction call — it is monotone in forward volatility and contrarian at extremes. Size positions to the state; don't chase the label.
Show the math
per signal points(x) = pts of the band with lo <= x < hi missing / NaN -> 0 component score = clamp( Σ points, -2, +2 ) in [-2, +2] rounded 2 dp composite = Σ_c weight_c · score_c weights: trend 0.25 breadth 0.20 volatility 0.15 rotation 0.10 size 0.15 style 0.15 label band < -0.70 Risk-Off · -0.70..-0.15 Distribution · -0.15..+0.55 Choppy +0.55..+0.90 Constructive · > +0.90 Risk-On confirm the published label flips only after the raw label disagrees on TWO consecutive runs
Trend signalinputbandpoints
spy_vs_sma50_pct2.3[1, 4)+0.40
spy_vs_sma200_pct9.0[7, 11)+0.40
sma20_slope_sign1.0[0.5, inf)+0.25
d3_pct1.4[1, inf)+0.25
Σ points = trend score +1.30 (clamp is a no-op inside [-2, +2])
Componentscoreweightterm
trend1.300.250.325
breadth1.100.200.220
volatility0.500.150.075
rotation1.650.100.165
size0.000.150.000
style0.500.150.075
composite = 0.86 → 0.55 ≤ 0.86 < 0.90 → Constructive Rotation

Confirmation needs two consecutive disagreements: with prev published Choppy Neutral and prev raw Choppy Neutral it stays Choppy Neutral (only the first disagreement); with prev raw already Constructive Rotation it flips (the second run confirms).

Lifecycle for Constructive Rotation: p(stay) 0.80 · expected dwell 5.0 td · implied end = started_on + 5 td = 2026-07-30 · exit paths (conditional on leaving) Choppy Neutral 61.1% / Risk-On Trend 33.3% / Distribution Risk 5.6%.

Source: boltfactors/regime/thresholds.py · regime/engine.py

Analytics Panels

Each playbook ships only after clearing a standardized offline backtest gate (5-year lookback, split-adjusted daily bars, non-overlapping events per name): n ≥ 80, |t| ≥ 2.0, an economic floor above assumed trading cost (0.20% multi-day / 0.06% same-day, ~10 bps round-trip), a consistent per-year sign, and — for reversion plays whose edge historically inverted in the 2022 risk-off tape — a regime block that suppresses the setup in Distribution Risk / Risk-Off Stress. Every card prints a stats bar (sample size, win rate, average and median forward return, t-stat, lookback span) straight from that backtest, plus a collapsible method/provenance receipt; the numbers are baked at refit time (quarterly) and never recomputed on the page. Three honesty disclosures are explicit: the backtest measures a fixed-horizon hold with no intraday stop (the live invalidation line is a risk overlay, not part of the measured edge); the population is the current liquid cross-section (survivorship disclosed — there is no point-in-time membership in the warehouse); and the t-stat is the naive mean/(std/√n) with per-name non-overlap only — same-day cross-name correlation inflates it on the large pooled samples, so treat |t| ≥ 2.0 as a screen, not proof, and weight the per-year sign consistency at least as heavily. When nothing triggers, the panel shows the armed playbooks and the nearest miss rather than inventing a setup.

Show the math
z = (value - mean) / std std ddof=1; the trailing window EXCLUDES today gate publish only if n(trailing) >= max(20, win // 3) windows 60 and 252 state Wide z ≥ +1 · Compressed z ≤ -1 · Normal otherwise flag extreme when |z| ≥ 1.5
Metricvaluez252z60read
Sigma Board: Momentum-LowVol w1-2.5%-3.25 (n=252)-2.83 (n=60)extreme (headlines on z252)
Dispersion: Sectors σ1.09suppressed (n=78 < 84)+0.51 (n=60)Normal

The gate suppresses z252 for Dispersion because trailing n=78 is below max(20, 252 // 3)=84 — the honest n is still disclosed. Source: boltfactors/panels/sigma_board.py · panels/dispersion.py

Single-Stock Setups

A long-only confluence screen over a 4-hour-to-1-week window: a name qualifies only when its factor basket and its technical state agree. Two archetypes — momentum continuation and oversold reversion — with transparent 0–1 sub-scores (basket alignment, factor quintile, trend structure vs the 50/200-day, RSI zone, 1-week push) capped at 0.86, plus up to +0.14 of additive money-flow confirmation (MACD, CMF, Force Index, MFI, Bollinger position). Flow-confirmed names outrank flow-less ones by construction.

Show the math
quintile map quint = (5 - q) / 4 q1 -> 1.00 q2 -> 0.75 q3 -> 0.50 q4 -> 0.25 q5 -> 0 momentum base = 0.22·align + 0.18·quint + 0.24·trend + 0.14·rsi_z + 0.08·mom ≤ 0.86 reversion base = 0.20·align + 0.30·oversold + 0.14·pullback + 0.16·structure + 0.06·quality ≤ 0.86 flow score = (base + 0.14·flow_frac) x 100 flow_frac = true / present (0 when flow absent) penalty momentum x0.70 if overextended (rsi > 72 or 1w > 16) · reversion x0.45 if Broken Momentum / Avoid
Momentum factorvaluecontribution
align (Thematic Momentum Leadership)1.00.22
quint (q1)1.00.18
trend (110 > 100 > 90)1.00.24
rsi_z = bell(60; 56, 64)1.00.14
mom = bell(+5; 2, 8)1.00.08
base = 0.86 (hits the structural cap) + flow 4/4 → 0.14 → score = 100.0, conviction high. A flow-less snapshot of the same name scores 86.0 (base only) — it can never out-rank a flow-confirmed peer.

Source: boltfactors/single_stock.py

Guardrails — Why You Can Trust the Numbers