Log the 21:14 orphan as a snapshot-build race, rewrite the context table to authoritative ICs only, and soften the vol-tilt warning. Add extender completion manifest + breadth refuse guard; strip intermediate/orphaned reports; park the thread (no book sim, no deploy).
217 lines
11 KiB
Markdown
217 lines
11 KiB
Markdown
# Broad-universe fip_id IC research (Phase B)
|
||
|
||
**Status:** **Parked / closed for now.** Unconditional fip not green; mom-conditional lead logged; breadth-momentum thesis challenged. No book sim until reopen.
|
||
**Production impact:** none. Display card remains context-only. No deploy from this work.
|
||
**Artifacts:** research log + compact reports + env-gated harness hooks; tooling stays for a future reopen.
|
||
|
||
## Scope
|
||
|
||
- Research only — production universe, gate, scanner, schedule unchanged.
|
||
- Snapshot: `research.sqlite` (~4,650 tickers = prod + nasdaq_all extend).
|
||
- Liquid mask: top **1,500** by point-in-time 63d median $vol, price ≥ **$5**/week.
|
||
- **Completion manifest required:** extender writes `<snapshot>.manifest.json`; breadth runners refuse without a matching complete manifest (see §Race guard).
|
||
|
||
## Caveats
|
||
|
||
- Survivorship bias (today’s constituents, history backfilled).
|
||
- IEX volume undercount → relative $vol rank only.
|
||
- Pool skew: Nasdaq-heavy; missing pure NYSE mid-caps.
|
||
- Do not mix multi-signal tables across universe baselines.
|
||
- **Do not cite orphaned 21:14 numbers** (see below).
|
||
|
||
---
|
||
|
||
## Fingerprint (505-name prod)
|
||
|
||
| | Expected | Observed |
|
||
|---|---:|---:|
|
||
| mean IC | −0.045 | **−0.045** |
|
||
| t-stat | −2.9 | **−2.91** |
|
||
| weeks / N / reliable | ≥12 / ~500 / true | 35 / 497.7 / true |
|
||
|
||
**Pass.** Formula + pipeline trustworthy.
|
||
|
||
Residual momentum on the same fingerprint (what the production book ranks on): **IC +0.055 / t +1.98**.
|
||
|
||
---
|
||
|
||
## The orphan (21:14) — root cause
|
||
|
||
| Source | fip IC (liquid ~1500) | t |
|
||
|---|---:|---:|
|
||
| Orphan run 21:14 (removed from tree; was `fip-breadth-20260718-211440-breadth.json`) | **+0.0575** | **+5.12** |
|
||
| Single-sourced recompute on complete snapshot (2026-07-19) | **−0.0168** | **−1.85** |
|
||
|
||
That is a **sign disagreement** on the same intended quantity. Method rule: the number you cannot reconcile is the number you cannot use.
|
||
|
||
### Verdict: orphaned — raced the snapshot build
|
||
|
||
**Not** “orphaned, unexplained.” The mechanism is derivable from the table itself:
|
||
|
||
1. **Code was not the difference.** Reconcile shows the old harness path and the new shared filter produce **identical** results on current data (−0.0168 / −1.85). The implementation fork is closed.
|
||
2. **Data was the difference.** On today’s complete snapshot the liquid mask **binds in 97.1% of weeks** at top-N = 1,500. Dense signals (e.g. `vol_6m`) post-mask at **exactly 1,500**. The orphaned report’s `vol_6m` averaged **~1,475** cross-section — a masked run on complete data cannot do that. At 21:14 the eligible pool was smaller than 1,500 and the mask never bound.
|
||
3. **Timeline fits.** Extender fixes landed ~20:32 / 20:34; full fetch takes ~30 minutes; breadth run fired **21:14** against a partially built `research.sqlite`. Every number in that report was computed on an incomplete universe.
|
||
|
||
**Do not cite +0.0575 / t +5.12.** It survived less than six hours of contact with project discipline — that is the system working, not time wasted. The orphan JSON was **deleted from the tree** (still in Git history) so it cannot be re-imported as evidence.
|
||
|
||
**Kept artifacts**
|
||
|
||
| File | Role |
|
||
|---|---|
|
||
| `reports/fip-reconcile-20260719-000520.json` | Authoritative single-sourced ICs (compact; membership dumps stripped) |
|
||
| `reports/fip-breadth-20260718-211440-fingerprint.json` | Prod fingerprint pass |
|
||
|
||
### Race guard (same class as calendar truncation)
|
||
|
||
| Piece | Behavior |
|
||
|---|---|
|
||
| `extend_snapshot_universe.py` | Clears any prior manifest on start; on full completion writes `<output>.manifest.json` with `complete=true`, ticker / OHLCV / rank_only counts, `finished_at`. `--limit` smoke runs write `complete=false`. |
|
||
| `run_fip_breadth_research.py` / `run_fip_breadth_diagnostics.py` | **Refuse** breadth mode unless a matching complete manifest exists and live counts equal the recorded totals. |
|
||
|
||
Helper: `scripts/research_snapshot_manifest.py`.
|
||
|
||
---
|
||
|
||
## Authoritative unconditional liquid fip (post-reconciliation)
|
||
|
||
| metric | value |
|
||
|---|---:|
|
||
| mean_ic | **−0.0168** |
|
||
| ic_t_stat | **−1.85** |
|
||
| weeks | 35 |
|
||
| avg_cross_section (**post-mask IC sample**) | 1471.2 |
|
||
| avg_raw_pool | 3214.4 |
|
||
| avg_eligible_pre_mask | **2338.4** |
|
||
| mask_binds_pct | **97.1%** |
|
||
| reliable | true |
|
||
|
||
**Mask binds hard** on complete data (eligible ≫ 1500). Post-mask IC N for fip is ~1471 because not every liquid name has a valid 12-1 fip path — that is signal availability, not a non-binding mask. Contrast orphan `vol_6m` avg N ~1475 vs complete-data `vol_6m` avg N **1500**.
|
||
|
||
Harness `_signal_evaluation` vs manual IC through the same filter: **exact match** (−0.0168 / −1.85).
|
||
|
||
**Iron rule unconditional:** **not green** (|IC| 0.017 < 0.03), correct mild-negative sign.
|
||
|
||
---
|
||
|
||
## Context table (orphaned 21:14 vs authoritative) — kill the myth numbers
|
||
|
||
The context table died with the orphan. **−0.16 must not survive in the log.**
|
||
|
||
| signal (liquid ~1500) | orphaned (21:14) | authoritative (shared filter) | consequence |
|
||
|---|---:|---:|---|
|
||
| **vol_6m** | −0.16 / t **−6.1** | **−0.048 / t −1.36** | “High-vol tilt harmful on breadth” **downgrades from finding to directional hypothesis** — not significant |
|
||
| **raw mom** (`mom_12_1`) | +0.10 / t +4.6 | **+0.046 / t +1.91** | Below iron-rule bar on this pool |
|
||
| **resid mom** (`mom_12_1_resid`) | +0.04 / t +2.3 | **+0.029 / t +1.33** | Ditto, and weaker than raw |
|
||
|
||
### Breadth-momentum thesis — challenged
|
||
|
||
That last pair is the sobering one. Momentum on liquid breadth is **marginal**. The “more breadth strengthens the momentum t-stat” thesis that motivated Phase B is **empirically wrong on this pool**: same 35 weeks, triple the names, residual-mom t-stat **fell** versus the 505-name fingerprint (**0.055 / 1.98** → **0.029 / 1.33**). The clean momentum edge lives in the large-cap universe already traded.
|
||
|
||
Meanwhile the strongest reliable signal on liquid breadth is now **mom-conditional fip** (−0.088 / −4.58) — but a fip tilt presupposes a breadth momentum book worth tilting, and that is no longer free.
|
||
|
||
---
|
||
|
||
## Compositional story (supported)
|
||
|
||
`fip_id = sign(PRET)×(%neg−%pos)` pools:
|
||
|
||
- **Continuous winners** → want **negative** IC
|
||
- **Continuous bleeders** → want **positive** IC
|
||
|
||
| check | IC | t | read |
|
||
|---|---:|---:|---|
|
||
| Prod-universe subset inside liquid | **−0.044** | **−2.88** | Matches fingerprint → compositional, not regime change |
|
||
| Tier 1–800 (senior) | **−0.035** | **−2.99** | Winner leg |
|
||
| Tier 801–1500 (junior) | **+0.014** | +1.25 | More bleeder / junk weight |
|
||
| Lagged membership (prior-week $vol) | −0.010 | −0.93 | Same sign as same-week; not a +5σ leak artifact |
|
||
|
||
**Do not log “on Nasdaq, jumpy paths outperform.”** That would mythologize an orphaned +0.06.
|
||
|
||
---
|
||
|
||
## Platform-relevant test: momentum-conditional fip
|
||
|
||
Among liquid top-1500, keep **mom_12_1 ≥ P80** (~294 names/week):
|
||
|
||
| metric | value |
|
||
|---|---:|
|
||
| mean_ic | **−0.0879** |
|
||
| ic_t_stat | **−4.58** |
|
||
| ic_positive_pct | 22.9% |
|
||
| weeks | 35 |
|
||
| reliable | **true** |
|
||
|
||
Computed on the **same single-sourced path** as the authoritative −0.017. This is the paper’s claim and the only version a gate could consume.
|
||
|
||
| Decision | |
|
||
|---|---|
|
||
| Unconditional fip | **Closed** for production |
|
||
| Mom-conditional fip | **Alive as book-tilt candidate only** — and only after a baseline breadth book proves itself |
|
||
| Display card | Stays |
|
||
| Production change | **None** |
|
||
|
||
---
|
||
|
||
## Vol-tilt warning (softened)
|
||
|
||
| signal (liquid, single-sourced) | IC | t |
|
||
|---|---:|---:|
|
||
| vol_6m | −0.048 | **−1.36** |
|
||
| mom_12_1 | +0.046 | +1.91 |
|
||
| mom_12_1_resid | +0.029 | +1.33 |
|
||
|
||
High-vol names **tend** to underperform on this pool relative to a clean S&P-like book — that is a **directional hypothesis**, not a finding. Production **80/20 high-vol tilt** was validated on S&P-like names. If the universe ever broadens in production, re-validate that tilt; do not treat the orphaned −0.16 / t −6.1 as evidence.
|
||
|
||
---
|
||
|
||
## What this means for the book experiment
|
||
|
||
A fip tilt presupposes a breadth momentum book worth tilting — **that is no longer free.**
|
||
|
||
**Caution against over-reacting the other way:** modest cross-sectional IC does not preclude a good book. The 505-name book turns resid-mom IC ~0.055 into Sharpe ~2 because the gate trades the **extreme tail**, not the linear sort. The breadth book might still work; it just has to **prove it** before the fip arm means anything. If the baseline cannot clearly beat the existing production book’s territory, fip’s future is a footnote regardless of −4.58.
|
||
|
||
### Parked next step (if reopened): pre-registered two-arm design
|
||
|
||
Not started — **design only**, pre-register before any sim:
|
||
|
||
| Arm | Definition |
|
||
|---|---|
|
||
| **A — baseline** | Top-quintile residual (or raw — pick one and lock) momentum book on liquid-1500; **no fip**; honest costs; next-open or near-close fills; production-like capacity / risk / stops |
|
||
| **B — +fip tilt** | Same book + mom-conditional fip tilt (among mom winners, prefer smoother paths / negative fip_id) |
|
||
|
||
| Grade on | Spec |
|
||
|---|---|
|
||
| Split | Entry-date train / validation (`BACKTEST_HOLDOUT_SPLIT` naming — not pristine holdout) |
|
||
| Metrics | Sharpe + Mertens/Lo SE, PSR, **DSR**; max DD; turnover; cost drag |
|
||
| Promote bar | Arm A must be in production-book territory first; Arm B must beat A on validation with DSR-aware multiple-testing honesty |
|
||
| Fail-closed | If A fails, fip is a footnote; do not shop tilts on a dead baseline |
|
||
|
||
---
|
||
|
||
## How to re-run (research branch only)
|
||
|
||
```powershell
|
||
# 1) Full extend writes completion manifest (required)
|
||
.\.venv\Scripts\python.exe scripts\extend_snapshot_universe.py `
|
||
--source backtest_snapshots\prod.sqlite `
|
||
--output backtest_snapshots\research.sqlite
|
||
|
||
# 2) Breadth / diagnostics refuse without matching manifest
|
||
.\.venv\Scripts\python.exe scripts\run_fip_breadth_diagnostics.py `
|
||
--research-snapshot backtest_snapshots\research.sqlite `
|
||
--prod-snapshot backtest_snapshots\prod.sqlite `
|
||
--workers 6 --allow-spawn
|
||
```
|
||
|
||
---
|
||
|
||
## Bottom line
|
||
|
||
1. Formal iron-rule screen: **not green** either before or after reconciliation.
|
||
2. **+0.0575 / +5.12 is orphaned: raced the snapshot build** — authoritative unconditional liquid fip is **−0.017 / −1.9**; mask binds (~97%) on complete data.
|
||
3. Context-table myths die with the orphan: **vol −0.16 is not real**; authoritative vol is **−0.048 / t −1.36** (directional only).
|
||
4. Compositional tug-of-war is the right story; jumpiness premium is not.
|
||
5. **Breadth does not strengthen residual-mom t-stat** on this pool (0.055/1.98 → 0.029/1.33).
|
||
6. **Mom-conditional −0.088 / −4.6 stands** on the single-sourced path → optional next step is a **pre-registered two-arm breadth book** (baseline first), not a gate wire-in.
|
||
7. Manifest guard is in place so the race cannot recur silently.
|