Post-Refresh Fundamentals-Signal Test Protocol
Authored 2026-05-29 (II) AFK. Consolidates everything that session learned into a single executable checklist, so the next session (with refreshed, period-correct fundamentals) can run a rigorous, pre-registered orthogonal- signal test without re-deriving the traps. Canonical record:
RESEARCH.md.
Why this exists
The 2026-05-29(II) session hardened the PiT fundamentals pipeline and built an
IC-triage tool, but could NOT run the strongest candidate (Sloan accruals)
because the period-correct re-download is hook-blocked inside AFK
(pretool_guard.sh denies thales fetch-). The legacy parquet has
period-collapsed flow concepts (quarterly net income silently paired with YTD
cash flow), so accruals computed on it are corrupt — compute_accrual_scores
fails safe (returns {}) until the period schema exists. This protocol is
the turnkey plan for the moment the data is refreshed out-of-session.
Step 0 — Refresh the data (OUT of AFK; one command, ~15 min)
thales fetch-fundamentals # repopulates with fiscal_period / period_start
thales validate-fundamentals # duplicate/stale/unit/no-annual checks + coverage
Confirm the coverage report now shows non-zero annual-flow counts (the
legacy schema showed 0 because it lacked fiscal_period). Spot-check a few
symbols against data/fundamentals/quarterly_fundamentals.backup-2026-05-29.parquet.
Step 1 — IC-triage FIRST (cheap; do this BEFORE any backtest)
The session's central lesson: a 5-window walk-forward is far too low-powered to tell a weak edge from noise (asset-growth showed +0.0135 Sharpe / 4-of-5 windows yet collapsed to IR +0.01 under a 109-obs IC test). So triage before spending a SHIP-gate run:
thales ic-triage # raw, 21d + (rerun with) --fwd-horizon 126
thales ic-triage --sector-neutral # the professional lens
Add an accruals_low signal to the menu (oriented −accrual, since LOW accruals
outperform) using compute_accrual_scores. Decision rule:
- mean IC < 0 (wrong sign) or |IR| < ~2 at BOTH horizons, raw AND sector-neutral → do NOT spend a SHIP run; log the null. (This is what killed asset-growth.)
- Right sign with |IR| ≳ 2 → proceed, but first decompose IC by year to rule out regime-decay (size_small looked decent full-sample but was a 2017–20 artifact, dead since 2021).
Step 2 — HARD prerequisites for the accruals signal (validated on real SEC data)
- Exclude Financials (and consider Utilities). Bank/insurer "operating cash
flow" is intermediation/trading flow — huge and often NEGATIVE (JPM FY2025
OCF = −$147B), so
(NI−OCF)/TAis meaningless for them. Pass the Financials tickers viacompute_accrual_scores(..., exclude_symbols=...). This is not optional — it is why Sloan and the literature drop financials/utilities. - FY flows only. Use
fiscal_period="FY"(already enforced) — never mix quarterly NI with YTD OCF. - Continuous tilt, not a hard drop. Hard fundamental drops were REJECTED
(quality_filter). Use the
accrual_tiltmachinery (already wired, gated OFF):w *= exp(-strength·clip(z)), missing-data names neutral. - Pre-register exact params + the full gate BEFORE running. ONE test. No
sweeps.
thales research draft-hypothesis --slug accrual_tilt --reason "...".
Step 3 — Full SHIP gate (only if Step 1 says "proceed")
Screen → escalate only if Sharpe Δ ≥ +0.10, CI not crossing 0, no MaxDD blowup:
thales evaluate -p strategy.construction.accrual_tilt.enabled=true \
-p strategy.construction.accrual_tilt.strength=<preregistered> \
--name accrual_tilt --desc "<hypothesis>" --tag afk-<date>
thales cpcv --workers 8 # standard universe
# if it passes: survivorship-free confirmation (the honest test)
thales cpcv --survivorship-free --workers 8
SHIP requires ALL: walk-forward Sharpe verdict BETTER & ΔSharpe ≥ +0.15; CPCV
PBO ≤ 0.30; OOS Sharpe Δ ≥ +0.10; IS-OOS corr ≥ +0.01 (sign stable across n=6
AND n=8, AND seed=123); survivorship-free confirmation holds; no window
regresses ≥0.05; crisis veto (thales stress, no named crisis MaxDD worse by
≥5pp vs the 2026-05-29 baseline crisis profile); DSR reported; a stated
mechanism. Default verdict for ambiguous = PROMISING, not SHIP.
Candidate priority (from this session's triage)
- Accruals (Sloan) — strongest prior, genuinely orthogonal, well-covered; the reason to refresh. IC-triage it first.
- Low-vol (Ang et al) — IC-triage CANNOT assess it (its edge is risk-ADJUSTED; raw-return IC is the wrong lens and is negative by construction). If pursued, test as a vol-scaled tilt and judge on Sharpe, not IC — but beware redundancy with HRP + vol-targeting already in production.
- Asset growth — REJECTED in Russell-1000 2017–26 (IR −1.09 universe / +0.01 within-sleeve, wrong-sign, robust to sector-neutralization). Do not pursue here.
- Accounting-size / 52w-high / 1m-reversal — all NOISE; size was a 2017–20-only artifact. Do not pursue.
Standing caveat
No backtest closes the in-sample gap. Any SHIP candidate is "ship-ready pending a 30-day live paper-trade A/B," not "deploy tomorrow."