Factor Watch — Methodology
How every number is computed. This file is the spec; factors.py and
analytics.py implement it. If the code changes, change this file in the
same commit. Transparency is a product feature: Barra and the S&P factor
indices are black boxes, this is not.
1. Universe
S&P 500 constituents, point-in-time. Membership at any past date is reconstructed from FMP's historical add/remove event feed by walking events backward from the current 503-name list. A stock acquired in January 2026 is therefore in the universe for rebalances before its removal date and out afterward — no survivorship bias from using today's list, to the extent the event feed is accurate.
Secondary share classes (GOOG, FOX, NWS, and — in the 2020–2022 stretch of the backfilled era — DISCK and UA) are dropped: FMP reports the full company market cap on both lines, so keeping both double-counts the company (Alphabet was 2x weighted before this fix). The primary line (GOOGL, FOXA, NWSA, DISCA, UAA) carries the company.
History depth. The series is backfilled to the 2019-12-31 first
rebalance (returns from January 2020) so the era ruler in §5 covers all of
"modern markets", COVID crash included. Ticker renames whose history FMP
re-homes under the new symbol are mapped in ingest.RENAMES (FB→META);
ingest.survivorship_audit checks every ever-member's price coverage
against its actual membership window and reports holes to the run log and
data/raw/meta.json, so a recycled ticker can never silently vanish from
past portfolios.
Known limits:
- Ticker renames sometimes appear as add/remove pairs in the feed (noise, small; the audit above catches the harmful cases).
- A handful of removed/delisted tickers have no usable price history on FMP (e.g. INFO — IHS Markit — whose ticker was recycled by a later listing); they drop out of past portfolios at formation, and the survivorship audit documents each one.
2. Data
All from FMP (Premium tier), pulled by ingest.py:
| Dataset | Endpoint | Notes |
|---|---|---|
| Prices | historical-price-eod/dividend-adjusted | split and dividend adjusted close → returns are total returns. The default full endpoint is unadjusted — do not use it for returns. |
| Income statement | income-statement (quarterly, 44q) | includes filingDate — basis for point-in-time visibility |
| Balance sheet | balance-sheet-statement (quarterly, 44q) | includes filingDate |
| Cash flow | cash-flow-statement (quarterly, 44q) | operating cash flow for the experimental accruals metric (not in the quality composite — see §10) |
| Market cap, ROE | key-metrics (quarterly, 44q) | no filing date; joined to income-statement quarters |
| Dividends | dividends | per-share cash dividends by ex-date |
| Membership events | historical-sp500-constituent | see §1 |
The FW 3000 universe builder (universe.py, §13) additionally uses
company-screener, delisted-companies, historical-market-capitalization,
and sp500-constituent.
Point-in-time rule: at a rebalance date t, a quarterly report is
visible only if its filingDate ≤ t. No look-ahead into not-yet-filed
quarters. TTM aggregates use the last 4 visible quarters.
Market cap at t: last reported quarter's market cap, drifted to t by the adjusted-price ratio. (Drifting with total-return prices slightly overstates cap for high-yield names between reports; immaterial for quintile ranking.)
Data-hygiene guards (2026-07). Vendor price errors in the broad
universe's small end produced two published artifacts — an unadjusted
~160x print (ESPR, 2026-07-08) that moved five FW 3000 spread series by
±27–33% in one day, and a block of stale micro-cap tapes snapping back at
once (2025-12-03, broad size spread +14%). Three guards now apply, all in
factors.py:
- Split splice: a single print moving more than 8x against its neighbor is treated as an unadjusted corporate action or vendor error; the earlier price segment is rescaled so the step disappears (what a correct split adjustment would have done — within-segment returns are unchanged, the jump day goes flat). Real one-day moves beyond 8x are essentially unattested; the rare true collapse past −87.5% is knowingly flattened, costing an equal-weighted leg ~0.2% versus the ~30% a single unadjusted print injects. Every splice is logged.
- Stale tapes score no vol/beta: a name whose last 15 closes are identical is repeating a vendor placeholder, not trading; its fake zero volatility/beta would stack the low-vol book and the short beta leg, so it is excluded from those two scores at that rebalance (other factors are unaffected). The eventual catch-up print below the splice bound can still land as a one-day move — attenuated, no longer concentrated in the vol/beta books.
- Big-day attribution: any book moving more than 10% in a day logs its top single-name contributors in the run log, so the next bad print names itself instead of surfacing as an unexplained spike.
3. Factor definitions
Scores are computed cross-sectionally over the point-in-time universe at each rebalance. Raw metrics are winsorized at 2.5%/97.5%, then z-scored. Composites average the available component z-scores (a name missing one component is scored on the rest; missing all → excluded from that factor's portfolios).
| Factor | Definition (higher score = stronger membership) |
|---|---|
| Momentum | 12-1 month total return: P(t-21d) / P(t-252d) - 1 (skips the most recent month, standard reversal exclusion) |
| Value | mean z of: E/P (TTM net income / mktcap), B/P (common equity / mktcap, equity>0 only), S/P (TTM revenue / mktcap) |
| Quality | mean z of: ROE (TTM net income / avg of current & year-ago equity), −D/E (total debt / equity), −earnings variability (std of YoY quarterly diluted-EPS growth, last 12 obs, min 8) |
| Size | −log(market cap) — "small within the S&P 500" |
| Low volatility | −std of daily total returns, trailing 252d (min 200 obs) |
| High beta | OLS beta vs SPY, trailing 252d daily returns |
| Dividend yield | TTM dividends per share (by ex-date) / price |
| EPS revisions | not yet computed — requires ≥ ~1 month of daily consensus snapshots from collect_revisions.py; see §8 |
4. Portfolio construction
At each month-end rebalance (last trading day):
1. Rank scored names; split into quintiles (~100 names each in the S&P 500, ~570 in the FW 3000).
2. Long-only factor series (<factor>_long): top quintile,
cap-weighted with a 5% single-name cap (excess redistributed
pro-rata, S&P-style). Without the cap, the quality and momentum longs
degenerate into a mega-tech beta bet (6 names ≈ 60% of weight) and stop
measuring the factor — this showed up directly in validation. This is
the comparable to the S&P factor indices / factor ETFs and feeds the
quilt.
3. Spread series (<factor>_spread): top quintile minus bottom
quintile, both equal-weighted (academic convention; cap-weighted
spreads are dominated by megacaps).
4. Benchmark (bench): full universe, cap-weighted.
5. Sectors (sector_*): cap-weighted within each GICS sector
(current-constituent mapping; sector history not reconstructed).
Between rebalances portfolios are buy-and-hold: weights drift with prices; no daily re-ranking (daily re-ranking is the classic way to fake a factor series and leak turnover-free alpha). A name whose prices stop mid-month (delisting/acquisition) is frozen at its last price — economically a cash-out reinvested pro-rata at next rebalance.
Rebalances start at the first month-end on/after 2019-12-31, so daily series begin January 2020 — the era backfill of 2026-07 (previously 2024-06-30). The start needs a 12m momentum lookback, so price history is pulled from 2018-12-01. Note the backfill also deepened the quarterly fundamentals pull (20→44 quarters), which lets more names clear the earnings-variability minimum at recent rebalances too — recent quality quintiles can differ marginally from the pre-backfill series.
4a. Published constituent books (factor_books.json, Factors page)
The books are published, not just their returns. At every rebalance the
membership captured during series construction (never recomputed after
the fact, so the lists are exactly the portfolios behind the series) feeds
data/derived/factor_books.json:
- Current books: every top-quintile name with its long-index cap weight, and every bottom-quintile name (the spread's equal-weighted short leg), with the rebalance score. A
nullweight marks a name that scored into the quintile but had no point-in-time market cap — it is carried in the equal-weighted spread legs but absent from the cap-weighted long index. - Tenure (
since): the start of the name's current unbroken run in the book, counted over computed rebalances. A skipped book (see the §4 gap warnings) neither breaks nor extends runs — diffs span to the next computed rebalance. - Changes & turnover: adds/drops between consecutive computed rebalances of the long book; turnover is adds ÷ book size (share of the book that is new). The site shows the last 12 rebalances; the full history stays in the artifact.
- Names and sectors are today's mapping (same compromise as the sector series, §4 point 5): historical adds/drops display current metadata, and delisted names may show no sector.
- Per-name 20d/YTD returns are descriptive convenience stats on adjusted closes, not portfolio attribution.
5. Spread monitor (z-scores)
For each factor, two relative series:
rel: long-only minus benchmark (what an ETF-vs-SPY watcher sees)spread: the Q5−Q1 series (the cleaner factor signal)
For horizons 1d / 5d / 20d / 60d: current h-day compounded return compared to the trailing 252 overlapping h-day returns of the same series (current excluded). Report z-score and percentile (returns are fat-tailed; the percentile keeps the z honest). |z| ≥ 2 is the headline flag.
Era context (the modern-markets ruler). Once a series has ≥750
overlapping observations (~3 years), each horizon also reports how the
current move ranks against the series' entire history — the S&P series is
backfilled to January 2020, COVID crash included, so this answers "how big
is this move by modern-markets standards" on a near-fixed baseline. The
block carries the empirical rank (nth_worst / nth_best, with the dates
of the nearest prior worse/better move), the empirical percentile, and a
fixed-ruler z. The empirical rank leads in all copy — with fat tails the
statement "2nd-worst week since 2020" is honest where "−5σ" merely
gestures. The trailing-year z remains the flagging engine: it answers
"unusual lately", adapts to volatility regimes within a year, and is
deliberately not replaced by the era ruler.
Caveat by design: with overlapping windows the baseline observations are autocorrelated — fine for "is this unusual?", not a t-test. A second caveat, discovered live in the June–July 2026 momentum unwind: after a volatility-regime shift, the trailing-year σ is calibrated on the calmer prior regime, so repeated 3σ+ prints can describe ONE persistent episode. The era rank and the percentile are the antidotes; read them together.
6. Rotation detector
On trailing-20d returns of the long-only series:
- Leadership flip: leader = top factor by trailing 20d return. A flip is flagged only after the new leader holds 3 consecutive sessions, and only if the leadership it displaced was itself established for 3+ sessions — a one-day wobble inside a longer run is churn, not rotation. Flagged flips stay visible while fresh (≤10 sessions).
- Quartile jump: factor ranked bottom-quartile 20 trading days ago now top-quartile (or the reverse). The rank condition must hold 3 consecutive sessions to confirm, and a confirmed jump stays flagged for 10 sessions from confirmation rather than flickering as the 20-day-ago baseline drifts.
7. Performance quilt
Calendar-month total returns of the long-only factor series + benchmark, ranked best→worst per month, trailing 13 months. Monthly granularity only — a daily quilt reshuffles too much to read; daily action lives in the spread monitor.
8. EPS revisions (V2, collection running now)
collect_revisions.py snapshots consensus EPS/revenue (avg/high/low,
analyst counts) for all constituents daily. **This series cannot be
backfilled — non-Enterprise vendors don't sell daily revision history —
which is exactly why it compounds into a moat.** Snapshots are committed to
git because they are irreplaceable. Planned outputs once ≥1 month
accumulates: net up/down revision breadth across the index; revision
leaders-vs-laggards spread (the Counterpoint-style edge).
9. Factor seasonality
Our own series is too short for seasonality — 2 years gives n=2 per
calendar month, which is astrology. So seasonality.py uses the Ken French
library monthly factor returns (momentum to 1927, HML/SMB/RMW to 1963),
free: mean/median return and hit rate per calendar month, full history and
trailing 30y. The total US market (Mkt-RF + RF; CRSP all-US, labeled "US
market", not strictly the S&P 500) is included as a reference row. The
dashboard renders the full factor × month grid as a mosaic; the digest
uses the current month's baselines. The snapshot carries the current month's baseline next to the
live monitor ("June is historically momentum's strongest month, 70% hit
rate — and it's currently -2.4σ"). Definition mismatch (French universe ≠
S&P 500 quintiles) is acceptable for a seasonal-baseline view and disclosed
in the payload. Mapping: momentum→UMD, value→HML, size→SMB,
quality→RMW(profitability); low vol / dividend yield / high beta have no
French analogue and are omitted.
10. Validation
validate.py checks the computed series against two independent references
(report: data/derived/validation.md):
1. Published S&P 500 factor index monthly returns (Invesco dashboard
quilt, Bloomberg-sourced, May 2025–Apr 2026): per-factor correlation,
sign agreement, mean abs difference; per-month quilt rank correlation.
2. Factor ETFs (SPMO, RPV, SPHQ, RSP, SPLV, SPHD, SPHB vs SPY): daily
return correlation, absolute and benchmark-relative.
Exact agreement is not expected (S&P indices are ~100-name score-weighted
baskets; RPV is style-weighted "pure value"). What must hold: high
correlation, consistent sign, same ordering most months. If a change to
factors.py moves these checks materially, that's a regression until
explained.
Current status (2026-06): monthly corr vs published 0.90-1.00 across all factors, MAD 0.6-2.0pp; daily corr vs ETFs 0.87-1.00. Weakest link is quality's benchmark-relative correlation vs SPHQ (~0.5).
Tested and rejected (2026-06): S&P-style accruals in quality. Swapping
earnings variability for NI−OCF/assets accruals regressed every quality
check (monthly corr 0.91→0.76, relative corr vs SPHQ 0.51→0.02); a
4-component mix was also worse (0.79). Probable cause: NI−OCF accruals are
meaningless for financials (~15% of the universe) and FMP quarterly OCF is
noisy. The accruals metric is still computed in factors.py for future
experiments (e.g. excluding financials) but is not in the composite.
Methodology changes must beat the validation harness to ship.
11. Breadth
factors.py also writes data/derived/breadth_daily.csv: each day, the
share of point-in-time index members trading above their own 50-day and
200-day moving averages (equal count, secondary share classes excluded,
names without enough history for the MA dropped from that day's
denominator). Used as a participation check on factor moves — a factor
rally with collapsing breadth is a different animal from a broad one.
Validation spot checks: validate.py compares every factor against
the published indices (monthly) and its ETF analogue (daily) on each
close. The references differ in construction by design, so they are spot
checks, not anchors: a monthly correlation below 0.75 or daily ETF
correlation below 0.80 — comfortably under the current 0.90–1.00 /
0.87–1.00 levels — is flagged prominently at the top of this page's
validation report and in the run log, but never blocks the daily run.
Known measurement compromises (audited 2026-07): dividend yield's
numerator is the split-adjusted TTM dividend while its denominator is the
dividend-AND-split-adjusted price, so historical yields are inflated in
proportion to dividends paid after the measurement date (decaying to zero
at the latest session); market caps drifted with an adjusted-price ratio
overstate caps by the yield accrued over the (≤1 quarter) drift window.
Both distortions are ~monotone in true yield, so rankings shift only at
quintile margins. Chart series carry cumulative levels flat across any
missing sessions rather than interpolating; validate.py's internal
consistency guards flag any such gap. A quilt month built from fewer than
15 sessions is marked with *.
12. Thematic baskets
Definitions live in baskets/*.json, one file per basket: thesis,
members (each with an added date and a written rationale, plus a
removed date when dropped), and a changelog. Membership is curated by
the maintaining agent; **every add/drop is dated and justified in the
changelog — the audit trail is part of the product.**
Construction (baskets.py): equal-weighted across active members,
buy-and-hold between rebalances — the same discipline as the factor
portfolios (no daily re-ranking, frozen prices on delisting).
Rebalances fall at month-ends and on any dated membership change,
so adds and drops take effect on their changelog date with an
equal-weight reset rather than waiting for the next month-end. Members
without price data are excluded from the series and surfaced in the
dashboard payload for review; names that listed mid-history simply
enter the series when their price tape begins. Series before a
basket's creation date are a **backtest of the membership as of
creation**; live tracking starts at creation. Benchmark-relative figures
compound the basket and the computed cap-weighted benchmark over the
same window.
Membership aims for a **wide cross-section of each theme inside the
S&P 500** rather than a concentrated best-ideas list: every leg of the
theme with a listed index constituent should be represented. Some
themes are structurally thin in a large-cap universe (cybersecurity's
pure plays mostly trade outside the index); their changelogs say so
rather than padding the basket with weak fits. Members that leave the
S&P 500 are flagged by the pipeline (ex_index in the payload,
surfaced on the dashboard) and removed with a dated changelog entry.
Two universes. Each basket is computed over both the S&P 500 and
the FW 3000 (§13). Members tagged "universe": "broad" in the basket
definition exist only in the FW 3000 version — off-index pure plays
like ZS or OKTA in cybersecurity, mid-cap banks in regionals — while
untagged members count in both (every S&P 500 name is an FW 3000 name
by construction). One file, one changelog, one thesis per theme; names
that leave the S&P 500 but remain in the FW 3000 move to a broad entry.
Reference-ETF cross-check. Where a listed sector/industry ETF — or
a simple blend, e.g. 50/50 XLP+XLU for the defensives basket —
approximates a basket's theme, the basket definition pins it as a
reference, and baskets.py reports the daily return correlation
against it: absolute, and benchmark-relative (basket minus our computed
benchmark vs ETF minus SPY) so agreement is not just shared market
beta. The numbers render on each basket's dashboard section in both
universes; the S&P versions also appear in the validation report. References are sanity checks, not tracking targets:
they are mostly cap-weighted, hold names outside the index, and follow
different rules, so correlations should be high but not 1.0. A
persistent drop is a prompt to review membership — deliberately not an
automated failure, unlike the factor guardrails in §10.
13. FW 3000: the broad-universe mirror
The computation chain — ingest, factor construction, baskets, and the analytics snapshot — runs twice: once over the S&P 500 (the default views) and once over the FW 3000, our self-built Russell 3000-style broad universe, selected with the universe switch on the Overview and Baskets pages. Same factor definitions, same quintile construction, same z-scoring — only the universe changes. The revisions collection (§8), seasonality baselines (§9), the published-index factor validation (§10), and the daily digest run once, on the S&P side; the FW 3000 carries its own universe-level guardrail (below).
Construction (universe.py): the largest 3,000 actively trading
US common stocks by market capitalization (NYSE/NASDAQ/AMEX, price >
$1, one share class per company with known secondary classes
deprioritized explicitly; preferred shares, warrants, rights, units,
funds, and exchange notes excluded), always including every current
S&P 500 member (some index members have foreign headquarters or fresh
ticker renames that screeners mishandle). Reconstitutions are at
calendar quarter ends, plus a provisional segment maintained by the
weekly rebuild: the membership cut since the last quarter end is re-cut
each Monday and only becomes final at the next quarter end. Known
limit: history candidates are today's largest ~4,500 actives plus names
delisted since mid-2024, so a still-listed stock that fell far out of
today's top ~4,500 can be missing from historical quarters it belonged
to — a mild winner bias the survivorship stats below do not capture. FMP carries no licensed Russell membership, so we build
our own the way Bloomberg builds its B3000 — this is **not the Russell
3000**, and we never label it as such; it is a Russell 3000-style
universe under the published rules in this section. Membership history
back to mid-2024 is reconstructed point-in-time from historical market
caps, including delisted names.
Survivorship is measured, not assumed away. At the first build,
98.4% of the 1,779 names delisted since mid-2024 had enough market-cap
history to be ranked, 675 of them entered the reconstructed membership,
and 17.7% of the June-2024 membership is no longer trading — those
names stay in the series for the quarters they belonged. The numbers
live in data/universe/fw3000_meta.json and refresh with each weekly
universe rebuild.
Validation: the FW 3000 cap-weighted benchmark is checked against IWV (iShares Russell 3000 ETF) on every close run; a daily correlation below 0.97 raises a prominent spot-check warning (it does not block the run — one transient bad print once cost a full close). First build: correlation 0.997 over the full series, ~1.3% annualized tracking error, cumulative return within ~0.6pp of IWV over the backtest window. Seasonal baselines come from the Ken French library (§9), whose all-exchange universe is much closer to the FW 3000 than to the S&P 500.
Cadence: the FW 3000 refreshes on the same schedule as the S&P 500 — hourly through the session (prices only, off the cached fundamentals) and again at the close. Fundamentals refresh on close runs (Mondays and the month-end window, so rebalance inputs are fresh); the universe membership rebuilds on Mondays. Both universes therefore carry the same "as of" date through the day, and the intraday/close marker on each page distinguishes a partial session from a finalized close. The broad intraday refresh is best-effort: if it is ever unavailable, the FW 3000 pages fall back to the last committed close rather than block the S&P update.
14. Estimate revisions
Factor Watch snapshots the analyst FY consensus (EPS and revenue: mean,
high, low, analyst count) once per market day (collect_revisions.py):
every S&P 500 constituent since 2026-06-10, widened to the full FW 3000
membership on 2026-07-04. Small caps without analyst coverage store no
rows — that absence is itself the coverage truth. The S&P series is
era-aware: broad-era snapshots are intersected with the committed S&P
membership so its trend never silently changes universe; the FW 3000
series publishes once it has six broad snapshots. Published
history of when consensus moved cannot be bought later — the archive
only exists because it is collected daily, which is why the site treats
it as its most defensible dataset.
Net revision breadth (site section "Estimate revisions", sp500): for each constituent, compare today's FY1 EPS consensus to the reading 5 sessions earlier, matched on the same fiscal year (a fiscal-year roll is never counted as a revision). A name counts as raised/lowered when the consensus moved by at least 0.1% relative; names covered by fewer than 3 analysts are excluded. Net breadth = (% raised − % lowered).
Factor-quintile cut: the same breadth measured inside each factor's
top and bottom quintile, using the committed membership from the latest
monthly rebalance (latest_portfolios.json). A positive top-minus-
bottom spread reads as fundamental confirmation of the factor; a
negative one as analysts leaning against it.
Measurement notes: consensus levels come from the vendor's estimate aggregation and can jump when analysts are added/dropped, not only when targets change; the 0.1% threshold and the analyst-count floor damp but don't eliminate this. The series' trailing depth is bounded by the collection start date above.
Differences vs the big-shop dashboards (deliberate)
- Quintile spreads instead of score-weighted index baskets: simpler, fully reproducible, and symmetric (you see the short leg).
- Equal-weighted spread legs: standard in the literature, less megacap contamination.
- Composite definitions are minimal (3 components max) and listed above in full. No proprietary descriptors, no opaque "crowding" overlays.
Disclaimer
Factor Watch is provided for informational and educational purposes only. Nothing on this site, in the email brief, or in any other output of this project is investment advice, a recommendation, or an offer or solicitation to buy or sell any security.
The pipeline is automated and depends on third-party data sources. Everything here is provided "as is", without warranty of any kind: no guarantee that any figure is accurate, complete, or timely, or that the site and pipeline are free of errors or bugs. Data may be delayed, revised, or simply wrong; runs can fail or publish incorrect output. Verify independently before relying on anything shown here.
We accept no liability for any loss or damage arising from the use of this site or its data. Consult a qualified financial adviser before making investment decisions.
Factor Watch validation report
Computed series: 2020-01-02 to 2026-07-27. References: S&P 500 factor index monthly returns (Invesco dashboard quilt, as of 2026-04-30) and factor ETF total returns (FMP, dividend-adjusted).
momentum (ETF ref: SPMO)
| Month | Ours | ETF | Published index |
|---|---|---|---|
| 2025-05 | +5.2% | +11.4% | +11.4% |
| 2025-06 | +2.2% | +7.0% | +6.9% |
| 2025-07 | +0.0% | +2.9% | +2.9% |
| 2025-08 | +0.4% | +0.7% | +0.6% |
| 2025-09 | +6.2% | +4.1% | +4.2% |
| 2025-10 | +1.0% | +0.5% | +0.6% |
| 2025-11 | -1.5% | -1.3% | -1.3% |
| 2025-12 | +0.2% | -0.4% | -0.4% |
| 2026-01 | +4.8% | +0.5% | +0.4% |
| 2026-02 | +1.6% | -0.3% | -0.3% |
| 2026-03 | -6.0% | -5.9% | -5.8% |
| 2026-04 | +18.8% | +19.3% | +19.3% |
Monthly corr vs published: 0.90 | sign agreement: 83% | mean abs diff: 2.0pp
value (ETF ref: RPV)
| Month | Ours | ETF | Published index |
|---|---|---|---|
| 2025-05 | +4.2% | +2.4% | +2.6% |
| 2025-06 | +5.3% | +4.1% | +4.0% |
| 2025-07 | -1.3% | -1.8% | -1.8% |
| 2025-08 | +7.6% | +6.6% | +6.4% |
| 2025-09 | +1.9% | +2.1% | +2.1% |
| 2025-10 | -1.5% | -0.3% | -0.3% |
| 2025-11 | +3.7% | +3.5% | +3.6% |
| 2025-12 | +1.8% | +1.3% | +1.3% |
| 2026-01 | +1.9% | +3.8% | +3.9% |
| 2026-02 | +3.6% | +4.7% | +4.7% |
| 2026-03 | -3.0% | -3.8% | -3.9% |
| 2026-04 | +6.2% | +3.6% | +3.7% |
Monthly corr vs published: 0.92 | sign agreement: 100% | mean abs diff: 1.1pp
quality (ETF ref: SPHQ)
| Month | Ours | ETF | Published index |
|---|---|---|---|
| 2025-05 | +5.3% | +6.3% | +6.2% |
| 2025-06 | +3.7% | +1.6% | +1.7% |
| 2025-07 | -0.1% | +0.2% | +0.1% |
| 2025-08 | +2.5% | +1.4% | +1.3% |
| 2025-09 | +2.1% | +1.6% | +1.6% |
| 2025-10 | +1.0% | +1.0% | +1.1% |
| 2025-11 | +1.9% | +0.9% | +0.9% |
| 2025-12 | -0.1% | +0.7% | +0.7% |
| 2026-01 | +1.3% | +3.1% | +3.1% |
| 2026-02 | +1.1% | +4.6% | +4.7% |
| 2026-03 | -6.5% | -6.8% | -6.8% |
| 2026-04 | +9.2% | +7.8% | +7.8% |
Monthly corr vs published: 0.91 | sign agreement: 83% | mean abs diff: 1.2pp
size (ETF ref: RSP)
| Month | Ours | ETF | Published index |
|---|---|---|---|
| 2025-05 | +4.1% | +4.3% | +4.3% |
| 2025-06 | +1.7% | +3.4% | +3.4% |
| 2025-07 | +0.9% | +1.0% | +1.0% |
| 2025-08 | +4.2% | +2.7% | +2.7% |
| 2025-09 | -1.1% | +1.0% | +1.1% |
| 2025-10 | -2.5% | -0.9% | -0.9% |
| 2025-11 | +3.5% | +1.9% | +1.9% |
| 2025-12 | +0.3% | +0.4% | +0.4% |
| 2026-01 | +3.6% | +3.4% | +3.4% |
| 2026-02 | +3.1% | +3.5% | +3.5% |
| 2026-03 | -6.9% | -6.0% | -6.0% |
| 2026-04 | +4.0% | +6.0% | +6.0% |
Monthly corr vs published: 0.93 | sign agreement: 92% | mean abs diff: 1.0pp
lowvol (ETF ref: SPLV)
| Month | Ours | ETF | Published index |
|---|---|---|---|
| 2025-05 | +1.3% | +1.0% | +1.1% |
| 2025-06 | -1.2% | -0.7% | -0.8% |
| 2025-07 | -0.5% | -0.3% | -0.3% |
| 2025-08 | +3.1% | +1.6% | +1.6% |
| 2025-09 | -0.6% | +0.2% | +0.2% |
| 2025-10 | -3.4% | -3.7% | -3.7% |
| 2025-11 | +3.2% | +3.8% | +3.9% |
| 2025-12 | -1.4% | -2.2% | -2.2% |
| 2026-01 | +3.7% | +3.3% | +3.3% |
| 2026-02 | +5.8% | +5.3% | +5.4% |
| 2026-03 | -5.8% | -5.3% | -5.3% |
| 2026-04 | +1.5% | +2.0% | +2.0% |
Monthly corr vs published: 0.98 | sign agreement: 92% | mean abs diff: 0.6pp
divyield (ETF ref: SPHD)
| Month | Ours | ETF | Published index |
|---|---|---|---|
| 2025-05 | +1.4% | +0.4% | +0.4% |
| 2025-06 | +2.0% | +0.4% | +0.4% |
| 2025-07 | +0.5% | +0.6% | +0.6% |
| 2025-08 | +4.9% | +4.1% | +4.2% |
| 2025-09 | -1.0% | +0.3% | +0.4% |
| 2025-10 | -2.3% | -4.0% | -4.0% |
| 2025-11 | +3.8% | +3.2% | +3.2% |
| 2025-12 | +0.4% | -0.9% | -0.9% |
| 2026-01 | +7.1% | +5.2% | +5.2% |
| 2026-02 | +5.3% | +4.7% | +4.7% |
| 2026-03 | -3.2% | -5.0% | -5.0% |
| 2026-04 | +3.4% | +2.0% | +2.1% |
Monthly corr vs published: 0.96 | sign agreement: 83% | mean abs diff: 1.2pp
bench (ETF ref: SPY)
| Month | Ours | ETF | Published index |
|---|---|---|---|
| 2025-05 | +6.1% | +6.3% | +6.3% |
| 2025-06 | +5.0% | +5.1% | +5.1% |
| 2025-07 | +2.3% | +2.3% | +2.2% |
| 2025-08 | +2.0% | +2.1% | +2.0% |
| 2025-09 | +3.7% | +3.6% | +3.6% |
| 2025-10 | +2.2% | +2.4% | +2.3% |
| 2025-11 | +0.3% | +0.2% | +0.2% |
| 2025-12 | +0.0% | +0.1% | +0.1% |
| 2026-01 | +1.4% | +1.5% | +1.5% |
| 2026-02 | -0.9% | -0.9% | -0.8% |
| 2026-03 | -5.0% | -4.9% | -5.0% |
| 2026-04 | +10.5% | +10.5% | +10.5% |
Monthly corr vs published: 1.00 | sign agreement: 100% | mean abs diff: 0.1pp
Daily return correlation vs ETF analogue
| Factor | ETF | Corr (1y) | Corr (rel, 1y) | Corr (full history) | Sessions |
|---|---|---|---|---|---|
| momentum | SPMO | 0.957 | 0.898 | 0.938 | 1649 |
| value | RPV | 0.877 | 0.890 | 0.972 | 1649 |
| quality | SPHQ | 0.879 | 0.593 | 0.963 | 1649 |
| size | RSP | 0.860 | 0.911 | 0.955 | 1649 |
| lowvol | SPLV | 0.943 | 0.975 | 0.964 | 1649 |
| divyield | SPHD | 0.933 | 0.964 | 0.970 | 1649 |
| highbeta | SPHB | 0.954 | 0.879 | 0.962 | 1649 |
| bench | SPY | 0.996 | — | 0.998 | 1649 |
Sector series vs SPDR sector ETFs (daily corr, last 252 trading days)
| Sector | ETF | Corr (daily) | Corr (relative-to-SPY daily) |
|---|---|---|---|
| Basic Materials | XLB | 0.972 | 0.961 |
| Communication Services | XLC | 0.841 | 0.645 |
| Consumer Cyclical | XLY | 0.979 | 0.945 |
| Consumer Defensive | XLP | 0.958 | 0.977 |
| Energy | XLE | 0.997 | 0.998 |
| Financial Services | XLF | 0.997 | 0.995 |
| Healthcare | XLV | 0.999 | 0.998 |
| Industrials | XLI | 0.991 | 0.979 |
| Real Estate | XLRE | 0.998 | 0.996 |
| Technology | XLK | 0.978 | 0.941 |
| Utilities | XLU | 0.892 | 0.916 |
Quilt rank agreement (Spearman, ours vs published, per month)
| Month | Rank corr |
|---|---|
| 2025-05 | 0.82 |
| 2025-06 | 0.61 |
| 2025-07 | 0.79 |
| 2025-08 | 0.89 |
| 2025-09 | 0.82 |
| 2025-10 | 0.86 |
| 2025-11 | 0.75 |
| 2025-12 | 0.39 |
| 2026-01 | 0.29 |
| 2026-02 | 0.78 |
| 2026-03 | 0.95 |
| 2026-04 | 0.96 |
Thematic baskets vs reference ETFs
Each basket that has a listed sector/industry analogue declares it as
a reference in its definition; baskets.py reports the daily return
correlation, absolute and benchmark-relative (basket − our benchmark
vs ETF − SPY). References differ in universe and weighting, so high —
not perfect — agreement is the expectation. Informational, no guardrail.
| Basket | Reference | Daily corr | Relative corr | Days |
|---|---|---|---|---|
| AI Infrastructure Leaders | SMH | 0.95 | 0.87 | 1649 |
| AI Software & Platforms | IGV | 0.96 | 0.90 | 1649 |
| Cybersecurity | CIBR | 0.92 | 0.82 | 1649 |
| Optics & Memory | SMH | 0.81 | 0.62 | 1649 |
| Retail | XRT | 0.83 | 0.60 | 1649 |
| Travel & Experiences | PEJ | 0.89 | 0.77 | 1649 |
| Power & Grid Buildout | GRID | 0.85 | 0.53 | 1649 |
| US Energy Complex | XLE | 0.98 | 0.96 | 1649 |
| Capital Markets Cycle | KCE | 0.96 | 0.86 | 1649 |
| Payments & Fintech | IPAY | 0.95 | 0.85 | 1649 |
| Regional Banks | KRE | 0.96 | 0.94 | 1649 |
| Managed Care & Health Insurers | IHF | 0.93 | 0.92 | 1649 |
| Defense & Aerospace | ITA | 0.95 | 0.88 | 1649 |
| Reshoring & Industrial Capex | XLI | 0.94 | 0.78 | 1649 |
| Defensives | 50/50 XLP+XLU | 0.94 | 0.94 | 1649 |
| Housing Chain | XHB | 0.98 | 0.95 | 1649 |
| Magnificent Seven | MAGS | 0.99 | 0.96 | 825 |
FW 3000 benchmark vs IWV (iShares Russell 3000 ETF)
Daily correlation 0.9945 over 1636 sessions; annualized tracking error 2.18%; cumulative +128.4% (FW 3000) vs +141.9% (IWV). Spot-check floor: 0.97.
Internal consistency (gap and corrupted-book guards)
All factor series continuous, no outlier months. Clear.