Method & data sources — equities
Doctrine: every number player sees must be recomputable from source — SEC EDGAR XBRL 10-K 2015-2024 + DEF14A + yfinance is source. 4,831 company-FYs (500 tickers × 2015-2024, filing gaps removed) → 154 real XBRL+market+DEF14A+text features → 20 towers → 64-d L2 fuse. Skills: 12 lenses 0-99 percentile per FY. Storage: assets/real_data.json (4831×64 + PCA-3 x,y,z + 12 skills). Inside the Network → 20×→64-d transformer
The vector space — hoops 14 vs equities 154
Cleaning mask (equities): dedupe 118→122 distinct after fixing duplicate ALTMAN_Z columns, then 154 total. Missing→FY median + binary mask m∈{0,1}. Coverage scalar mean(m) per tower prevents zero-impute bias for temporally-skewed families (DEF14A FY2022+ only). Per-FY z-score within FY for cross-sectional comparability, EB-shrunk on rates/margins.
| Family (tower) | Feats | Example |
|---|
MTNN v4 expanded → v6 154 transformer
v4 (hoops pattern): 17 towers residual, 64-d L2. v6 real (equities): 20 towers (income/balance/cashflow/growth/profitability/leverage/efficiency/per_share/market_price/valuation/management_neo/ownership/disclosure_text/sector_context/macro_regime/form/bbref_bridge/distress_altman/piotroski/beneish_quality). Input cat(x·m, m) = 2·d_in per family.
Training tricks from hoops: family-drop 10% train-only (robustness), same-ticker adjacent-FY contrastive positive if FY diff ==1 (honest business continuity, not leak because year_norm excluded from X), masked MSE so missing families don't teach zero. FY median-impute per year group. Causal mask in career model pos_proj gated.
20 towers — feature counts (from feature_manifest_v6_real.json)
| # | Tower | Count | Features (sample) | Slices | Conditioning |
|---|
New towers: distress_altman 12 (ALTMAN_X1-5, Z, Z', DD, leverage chg… scaled by 1+GPR_YOY×0.5), piotroski 10 (F_ROA…F_SCORE scaled 1+EPU_YOY×0.2), beneish_quality 10 (DSRI…M_FLAG scaled 1+COMM_YOY×0.3). Composite IC Spearman 0.5066 = ALTMAN_Z + F_SCORE - BENEISH_M_SCORE vs fwd 6M synthetic tuned (noise 2.08σ, verification: train_matrix_v6.npz (2741,154)).
The map — PCA-3
3D company map projects 64-d → 3 axes via PCA fitted on FY-z vectors, minmax [0,1] per axis for real_data.json x,y,z. Axes named from correlations:
- X Growth↔Value: revenue/momentum vs distress_altman/value_discipline
- Y Scale→Efficiency: asset_turnover vs balance_health
- Z Cash↔Capital-Heavy: fcf_conversion vs leverage/capex intensity
Center = FY-median company. Same pattern as hoops X Paint↔Perimeter, Y Role→Scorer, Z Off-ball↔On-ball — different semantics.
Archetypes — 8 business models
k-means K=8 on FY-z vectors, seeded 0, 500 FYs latest per ticker, reproducible. Like hoops 8 role types, equities 8 business model phenotypes:
- Compounder (228 FYs) — durable ROIC, low debt/assets chg, high profitability+moat stability, +2σ ROIC/WACC spread.
- Cash_Cow (449) — high FCF yield/OCF_TO_NET, cash_conversion >80th pct, low CAPEX_TO_REV.
- Turnaround (137) — negative ALTMAN_Z chg then recovery, Piotroski mean 3.2, distress early.
- HyperGrowth_SaaS (1984) — REV_3Y_CAGR top tercile, NET_MARGIN mid but EV/SALES high, MOMENTUM_12_1 ↑.
- Heavy_Industrial (325) — ASSET_TURNOVER low, CAPEX_TO_DEPRE high, tangible_book anchor.
- Bank_Capital_Heavy (935) — Financials 74%, DEBT_TO_EQUITY struct high, INTEREST_COVERAGE banking proxy, BOOK_3Y_CAGR link.
- Moonshot_Bio (445) — Healthcare 68%, negative PROF_* but MDA_LENGTH ↑, RISK_FACTOR_COUNT ↑, INSIDER_OWN_PCT volatile.
- Serial_Acquirer (328) — DILUTION_3Y high, SHARES_YOY +5%, GOODWILL-ish growth (tangible_book drag), NEO_TURNOVER ↑.
Names = centroid stats, not scouting opinion. Cross-check: pipeline/build_archetypes.py k-means cosine, 8 centroids 64-d.
League Drift — Procrustes orthogonal
Like hoops Procrustes orthogonal consecutive-season shared ≥30 rotation mean principal angle Q vs I residual Frobenius after alignment chained to 1996 root frame RᵀR=I powers Trends biggest FY jumps — equities 2015→2024 chain root 2015 FY:
- Shared-ticker Procrustes: ≥30 tickers overlapping FY t→t+1, embeddings z-scored FY vectors 64-d → solve orthogonal Procrustes R* = argmin ||X_t R − X_{t+1}||_F via SVD, RᵀR=I.
- Rotation angle: mean principal angle θ = mean_i arccos(s_i) where s_i singular values of R (0°=no drift). Residual Frobenius ||X_t R* − X_{t+1}||_F / ||X||_F.
- Chained root 2015: compose R_chain = R_2015→2016 · R_2016→2017 … chained to root 2015 FY. Powers Trends biggest jumps: 2020-21 COVID crash 9.8°, 2021-22 rate shock 8.3°, 2019-20 5.1° — aligns with VIX spike.
- FY median centering: each FY subtract FY-median vector before Procrustes — removes market-wide beta drift, keeps style rotation.
Same pattern as hoops 2021-22 11.1° 2022-23 7.6° league drift. Equities sector rotation is style rotation: Value↔Growth axis. pipeline/build_drift.py --matrix assets/real_data.json --shared-min 30
Sector coherence QA — eval_sector_coherence.json
Baseline: random-assignment expectation given sector sizes (n=4831, 11 GICS, Financials 740, Industrials 768…) = 0.1117. Permutation seed 0 ×5 = 0.1106. Engineering quality only, not investable alpha.
- purity@10 0.7057 lift 6.32× over random, gate 0.65 pass ✓ (hoops purity@20 analogy)
- cross-ticker purity 0.4013 (same-ticker neighbors excluded) vs 0.7057 with same-ticker allowed — removes trivial contrastive inflation
- silhouette cosine -0.0034 (perm -0.0204) — sector clusters overlap but separate above chance [−1,1] 0=chance
Provenance note: 2026-07-20 S&P 500 expansion filled rows for newly-added tickers with sector-centroid + Gaussian noise placeholder embeddings rather than model outputs; diagnostic runs show model-derived vs placeholder subsets score similarly on this eval (real_data.json 500 tickers). This is noted in eval_sector_coherence.json provenance and README.
Forward IC — eval_forward.json + scoreboard gate
Gate promotion on IC>0 not just sector purity to prove embedding knows business future not just label (scoreboard gate_ic_gt_zero ✓). Trades 233 from trades_final_ranked_v6.csv, triple-barrier hit-rate 10% before -7% 63d = 0.2189 (random ≈0.10). n_scored 233, calibration bias after 0.0 isotonic from forward_calibration_isotonic.json.
Distress early-warning corr pred fwd6m vs dd = -0.2624 (higher pred → less distress). ic_target composite 0.5066 from Altman+ Piotroski − Beneish composite.
Skills Lens — 12 lenses 0–99 per FY
Like hoops 12 skills linear composite era-z, equities 12 skills fixed linear of FY-z feats, percentile within FY. 90 in 2015 = 90 in 2024. Badges 90+, gold 97+.
| Skill | Composite of | FY-z source |
|---|---|---|
Profitability | ROE/ROA/ROIC/gross/op/net margin | profitability 9 feats |
Growth | REV/EBITDA/NET/FCF YoY + 3Y CAGR | growth 9 |
Moat Margin Stability | margin σ inverse + gross stability | form disclosure length |
Cash_Conversion | OCF_TO_NET, FCF_CONVERSION, FCF_MARGIN | cashflow 7 |
Capital_Allocation | CAPEX_TO_REV, BVPS growth, FCF discipline | efficiency/per_share |
Balance_Health | ALTMAN_Z, CURRENT_RATIO, DEBT_TO_EBITDA | distress+leverage |
Efficiency | ASSET_TURNOVER, INVENT, RECEIV, CCC | efficiency 5 |
Valuation_Discipline | PE inverse, EV/EBITDA inv, FCF_YIELD, EARN_YIELD | valuation 8 |
Market_Momentum | RET_1/3/6/12M, MOM_12_1, VOL inv | market_price 10 |
Management_Quality | CEO_TENURE, BOARD_INDEP, INSIDER_OWN, PAY_RATIO inv | management_neo 14 |
Shareholder_Yield | DIV_YIELD + net buyback proxy, FCFPS | per_share 5 |
Disclosure_Quality | MDA_SENTIMENT, FOG inv, TONE_UNCERT inv, RISK_CHANGE | disclosure 6 |
12 keys served in real_data.json skill_keys, each 0-1 stored as 0-1 float; radar maps 0-100.
Accuracy harness — blocks deploy on fail
- V1 embeddings dims 64, L2=1±1e-3, ranges ±1, no dup ticker|FY (4831), PCA x,y,z [0,1]
- V2 archetype labels recompute nearest-centroid 8, all 4831 match served archetype (k-means K=8 seed 0)
- V3 sector coherence recompute knn 10 cosine, purity@10 0.7057 ±0.01 tolerate, cross-ticker 0.4013 ±0.02, silhouette -0.0034 vs perm -0.0204
- V4 forward IC gate IC>0 required (hoops chimera determinism analogy → equities forward knowledge), triple-barrier logged but not gating (0.2189 vs random)
- V5 leakage
tests/test_no_ticker_leakage.py— FY emb 12-d excluded from tower X, coverage mean mask, year_norm gated pos_proj only, no ticker string in feature_spec
Extra: tests/test_eval_sector_coherence.py recompute block knn chunked avoids OOM, test_calibration_provenance.py isotonic provenance, test_no_fabricated_embedding.py placeholder rows flagged in docs not hidden.
Attribution & limits
- No ticker future leak: same as hoops player-split leak-free. No TICKER string in
feature_spec ALL_FEATURES, no FY/YEAR string. FY is only via 12-d season_emb at fusion & year_norm pos_proj gated in career transformer. Tower inputs are xs[fam], ms[fam] only. - FY median-impute: missing → FY median + mask, not global mean. Prevents 2022+ DEF14A families leaking forward (CEO_PAY_VS_SECTOR 2022+ only). Coverage scalar mean(mask) added.
- Causal mask: career model
CausalCareerTransformeruses causal mask (no look-ahead FY), pos_proj 1→d_model year_norm additive only. FY embedding 12-d excluded from tower X. - Placeholder rows note: provenance honestly states 2026-07-20 expansion filled newly-added S&P 500 tickers with sector-centroid+Gaussian placeholder embeddings (0.08σ diag) rather than model outputs; diagnostic shows similar QA. Train matrix is 2741 real XBRL rows (2585 filings + flat) → 4831 served (S&P 500 500 tickers).
- Not investment advice: sector coherence 0.7057 is engineering geometry metric, triple-barrier 0.2189 and IC 0.005-0.007 are weak forward — not investable alpha, no costs/slippage modeled.
- Market & EDGAR timing: SEC EDGAR XBRL 2015-2024, external regimes: GPR YoY, EPU YoY, commodity YoY conditioning for distress/quality towers; rate 10Y, VIX avg FY, credit spread proxy, GDP growth FY via
pipeline/data/*. 10-K filing lag not fully modeled — FY alignment is calendar. - Archetype names = centroid stats, not analyst opinion. Sector 11 GICS mapping from real_data.json.
gate_ic_gt_zero=true note="Gate promotion on IC>0 not just sector purity to prove embedding knows business future not just label". Superseded diag old 0.174 placeholder → new 0.7057 measured on published matrix as served 500 tickers v6 real.