Signals

Not investment advice. This is the output of a personal model made by a private individual. It is not a recommendation to anyone to buy or sell any security. The author may hold positions in some of the securities mentioned.

Volume 4 · research notes

Research notes — the factor model, and the insider signal is “dead”

Five research notes written between 29 and 31 August 2026. They test whether the insider score from Volumes 1–3 carries any edge: first as one factor in a US quality/value/momentum long-short factor model, then on its own. The answer was no. The notes are reproduced as written; they are working documents, not a recommendation.

Generated (committed): 2026-08-29 06:51 UTC – 2026-08-31 10:55 UTC.

Note 1 of 5 · source file sarto-factor-model.md · committed added 2026-08-29 06:51 UTC, last edited 2026-08-29 07:12 UTC

Design doc — a Sarto-style systematic factor model on the docka engine

Status: Reviewed proposal, M-S0 built. The plan to build a cross-sectional quality/value/momentum long-short factor model on top of the existing insider-signal-bot plumbing, with the insider signal folded in as one factor. The concrete form of the direction in PROJECT.md.

Revision: v2 — revised after a two-critic review (architect + tester). v2 fixed four critical items the review caught: (1) balance-sheet factors need a new instant-fact as-of reader — the annual dedup reader silently drops instant facts; (2) TTM is not computable today, so v1 uses latest-annual and a real TTM is deferred to M-S8; (3) the market-cap wording no longer implies editing the scorer's stub (a new reader is used); (4) the consumer-layer boundary is now enforced by a sink guard in test_import_graph.py, and the rebalanced backtest moved to portfolio/backtest.py. Momentum now carries a split-refusal guard, missing factors are excluded (never zero-filled), and degenerate values (negative equity, zero denominators) are handled. M-S0 (scaffolding) is built and green; M-S1 is next.

Companion reading: PROJECT.md (why), README.md (what already exists and what it measured), SPEC §5–§9 (the raw/derived split, the entry rule, the holdout discipline, the stop-after-M9 posture — all of which this design inherits).


1. Objective and success criterion

Build a US-listed systematic long-short factor model that ranks the investable universe on quality, value, momentum (and the existing insider signal), holds the top and shorts the bottom, and is judged the way a real systematic fund is judged.

Success criterion (Sarto's bar, not the hype thread's): over a multi-year point-in-time backtest, the book beats a matched benchmark (SPY total return) by a small margin at equal or lower risk — a positive information ratio and a lower max drawdown than the benchmark — and the result survives a single holdout read. Not spectacular return. Not an in-sample number. A modest, risk-adjusted, out-of-sample edge.

Explicit non-goals

2. What Sarto does, decomposed

From the Ärileht profile (18.08.2026), the process is:

  1. Universe: developed markets (~10k names) → drop illiquid and sub-$300M market cap → ~5–6k ranked names.
  2. Three academically-grounded pillars — quality (low leverage, strong balance sheet, stable cash flow), value (low multiples), momentum (price trend). Only factors "the academy has validated."
  3. Rank the whole universe; act on the top and the bottom, never on a single-name thesis ("we don't hunt for needles; we buy the part of the haystack where they hide"). The edge is statistical — the law of large numbers.
  4. 200:100 book — 200% long the good names, 100% short the weak, short proceeds funding extra longs; net ~100% long, gross ~300%.
  5. Risk structural, not discretionary — 1,000–2,000 names, position cap 2.5% long / ~1.25% short; the short leg cushions crashes.
  6. Point-in-time data — tested on 30 years using the values as known then, not as later restated.
  7. The model decides; humans build and test it, never override a pick.

3. Scope decision — US-listed subset (and why)

Sarto is global; docka's reachable, point-in-time-clean, free data is US SEC + a free price feed. Ex-US fundamentals are neither free nor reliably point-in-time. So the faithful copy is of the method, on a US-listed common stock universe. This is stated as a limitation, not hidden: the model is "a US systematic quality/value/momentum long-short," a subset of Sarto's mandate. If it works, an ex-US data source is a later, separate project.

4. Architecture — a new layer, not a rewrite

~70% of the hard machinery exists. The new work is a factor/portfolio layer that consumes the existing raw tables through the existing point-in-time readers.

 EXISTING (reuse as-is)                         NEW (this design)
 ─────────────────────                          ────────────────
 edgar/tickers.py   ── listed CIK↔ticker↔exch ─▶ universe/            (eligible set, as-of)
 edgar/xbrl.py      ── companyfacts ───────────▶ (expand WANTED_CONCEPTS)
 db/xbrl_facts      ── point-in-time facts ────▶ factors/quality.py
 db/repo_xbrl.facts_for(as_of)                   factors/value.py
 prices/ + prices/lookup.bar_on_or_before ─────▶ factors/momentum.py
 score/history.market_cap_usd  (STUB, D5) ─────▶ (left untouched; new factors/marketcap.py::market_cap_as_of)
 prices/liquidity.passes_liquidity ────────────▶ universe/ liquidity leg
 backtest/entry.py (entry_date_for,             factors/rank.py       (winsorize, z, composite)
   adjusted_open)  ──────────────────────────▶  portfolio/construct.py (200:100, caps)
 backtest/engine.py, backtest_runs holdout ───▶ portfolio/backtest.py  (rebalanced L/S, in the SINK)
 score/scorer.py (insider score) ─────────────▶ factors/insider.py     (insider as a factor)

Key existing hooks confirmed in the code:

Isolation rule: the factor engine is a new consumer of the raw tables. It does not alter score/scorer.py, the insider signals table, or any existing backtest number. The insider factor (factors/insider.py) reads the existing score read-only. This keeps every current README measurement reproducible and respects the repo's "never silently change scoring" discipline.

One consequence, called out because it is easy to get wrong: score/history.py::market_cap_usd is a D5 stub returning None. It is wired to be called by the −25 small/penny penalty, but today score/rules.py's _small_or_penny_hit short-circuits and records {"market_cap": "unknown"} without calling it — so the stub is dormant. Implementing that stub would therefore change scoring the moment _small_or_penny_hit starts consuming it. To avoid any coupling, the factor layer gets its own market-cap reader (factors/marketcap.py::market_cap_as_of) and leaves market_cap_usd exactly as it is. Retiring the D5 stub and wiring the −25 penalty to a real market cap is a separate, later, ablation-gated change (tracked at M-S7), never a side effect of this work.

Boundary is enforced, not asserted. factors/, universe/ and portfolio/ are registered as a guarded sink set (CONSUMER_SINK_PACKAGES) in tests/unit/test_import_graph.py, with a reverse-arrow test (test_no_stage_imports_a_consumer_sink) mirroring the report/ guard. So "no stage depends on this layer" is a test, not a comment. The rebalanced long-short backtest therefore lives in the sink (portfolio/backtest.py), not inside the backtest stage — a stage placing it would import the sink and fail that test.

5. Schema additions

All additive (CREATE TABLE IF NOT EXISTS), following the v3/v4 overlay pattern. Raw stays append-only; the factor/portfolio tables are DERIVED (drop & rebuild).

A schema_version bump (5 → 6 — v5 already exists) with a forward-only migration, matching the existing pattern in db/connection.py. The three derived tables are added to _DERIVED_SHAPE_REQUIREMENTS; factor_backtest_runs (ops) is not. A reopen-preservation test pins that each required_column really exists in schema.sql, so the migration can never silently drop-and-wipe a derived table.

6. Point-in-time and anti-look-ahead guarantees

This is the section that decides whether any number is real. Every one of these reuses machinery that already exists here.

  1. Fundamentals as known then. All fundamental reads go through facts_for(..., as_of=rebalance_date), which clamps filed_date <= as_of — so a restatement filed later is invisible. This clamp is reused; the dedup is not fully reused, and the doc must not pretend it is:
    • _annual_deduped handles annual duration facts only (350–380-day band) and drops every instant fact (period_start IS NULL → continue). Every balance-sheet concept (Assets, StockholdersEquity, Liabilities, debt legs, shares outstanding) is instant, so reusing _annual_deduped for them returns nothing and silently skips the name.
    • Therefore M-S1 builds a new instant-fact as-of reader (newest disclosure ≤ as_of per period_end, no duration band), with its own property test, and a guard test that the duration path returns empty on instant facts so the two can never be swapped. (Sarto's central discipline; the clamp is built, the instant-fact dedup is new work.)
  2. Prices as known then. Momentum and market-cap price legs read bar_on_or_before(..., as_of=rebalance_date).

2b. Momentum is not immune to the split disease. This corpus's free-feed adj_close is not reliably split-adjusted (README "the split question"; penny_price's C54 note). A MOM_12_1 ratio spanning a split the feed never applied invents a ±60–200% return and rockets the name into a decile on a fabricated signal — the same defect entry.py refuses via unadjusted_split. So the momentum factor reuses prices.crosscheck's unadjusted-split detection and excludes (flags, never scores) any name with an unapplied split inside [t−252, t−21]. Momentum offsets are trading-day counts via the calendar, never timedelta days.

  1. Fills you could actually get. Entry/exit at each rebalance use entry_date_for (next session open, split-safe adjusted_open) — never the rebalance-day close.
  2. Coverage is a skip, not a miss. A name whose XBRL was never fetched returns the 4-state overlay_fetch_log status (_fundamentals_unavailable), so a half-populated corpus reports pending names rather than scoring them as "cheap/low-quality = 0".
  3. Survivorship. The historical universe must include names later delisted, or the long side inflates. The backtest already has DELISTED handling to build on; the universe builder keys off the historical ticker/exchange map, not just currently-listed names. Open risk — see §12.
  4. Holdout. A second distinct parameter set on the holdout split is refused (exit 4) unless --i-am-overfitting, logged. Factor definitions are pre-registered in this doc so tuning cannot masquerade as discovery.

7. Factor definitions (pre-registered, academically grounded)

Each factor is a named hypothesis, ablatable, with a citation — Sarto's "science only" rule. Concepts marked (new) are added to WANTED_CONCEPTS.

Fundamentals to add to the fetch: Assets, Liabilities, StockholdersEquity, LongTermDebtNoncurrent, DebtCurrent, NetCashProvidedByUsedInOperatingActivities, GrossProfit (fallback Revenues − CostOfGoodsAndServicesSold), and shares outstanding dei:EntityCommonStockSharesOutstanding (fallback CommonStockSharesOutstanding). Revenues and NetIncomeLoss already fetched.

v1 uses LATEST ANNUAL figures, not TTM. The critics were right: there is no point-in-time TTM assembler in the repo, and a correct one (stitch Q1–Q3 10-Qs + derive Q4, dedup each quarter to newest disclosure ≤ as_of, avoid the YTD-vs-discrete double-count) is its own milestone. v1 therefore uses the latest annual (FY) figure disclosed ≤ as_of for every flow concept (earnings, revenue, operating cash flow), read through the instant/annual as-of readers. A proper TTM is a deferred enhancement (M-S8), gated by its own restated-quarter and no-double-count property tests. Flow = latest FY; stock = latest instant.

Quality (higher = better)

Value (higher = cheaper = better) — all vs market cap

Momentum (higher = better)

Insider (higher = better) — the docka edge as a factor

Degenerate values (required, not optional) — mirror adjusted_open's guards:

Missing data — a skip, never a zero. A name missing a factor is removed from that factor's cross-section (its mean/std/rank), never assigned raw 0 or z 0 — zero-filling silently plants a no-data name mid-pack (or, for value, makes it look "average cheap"). Explicit coverage rules, pinned and tested:

Combination

Market cap is computed by a new reader in the factor layer, factors/marketcap.py::market_cap_as_of(conn, cik, as_of) = shares_outstanding(as_of, XBRL, unit shares) × close(as_of, bar_on_or_before, unit USD). It deliberately does not touch score/history.py::market_cap_usd (the scorer's D5 stub) — see the isolation note in §4. Three traps the tests must pin: (a) facts_for defaults unit="USD", so the shares leg must pass unit="shares" or the fact is invisible and the name drops no_market_cap; (b) both legs read on the same as_of — the as-of property test asserts neither the shares leg nor the price leg reads anything dated/filed after as_of; (c) a dual-class issuer reporting multiple EntityCommonStockSharesOutstanding rows needs a defined aggregation against the single priced ticker — recorded, not guessed. Also verify at M-S1 that edgar/xbrl.py actually walks the dei taxonomy block of companyfacts and stores the shares concept under a name facts_for matches; if the fetcher only iterates us-gaap, market cap has no shares leg and the whole universe drops.

8. Universe construction (insider universe --as-of DATE)

  1. Start from the historical listed set (ticker/exchange map).
  2. Common stock on a major exchange (NYSE/Nasdaq/NYSE American) — reuse the exchange gate; exchange_unknown is a meaningful exclusion (C32). Caveat (see §12): issuer_exchange is a current-state, one-row-per-CIK table, so this leg is not truly point-in-time; the as-of eligibility is anchored instead on price-series existence + the issuer_tickers first_seen/last_seen window, and the current-exchange snapshot is a supplementary filter, not the authority.
  3. Market cap ≥ $300M as-of (market_cap_as_of(D), not today's) — Sarto's floor.
  4. Liquidity leg — passes_liquidity (ADV ≥ $500k). ADV-only, spread_applied = False recorded (as the existing gate documents).
  5. Emit universe_membership with an explicit reason for every drop.

Every leg is evaluated as of D, never on today's state: a name sub-$300M or not-yet-listed at D but large/listed now must be dropped at D, and a name listed at D but delisted since must be eligible at D (the survivorship-inclusion case, tested at M-S2 — see §12). Target eligible set: on US-listed common stock this yields low thousands — a decile is hundreds of names, inside the caps.

9. Portfolio construction (portfolio/construct.py)

10. Rebalanced long-short backtest (insider port-backtest)

Distinct from the existing event-study backtest. Lives in the sink (portfolio/backtest.py), per §4.

11. CLI surface (new subcommands, same insider entry point)

insider fundamentals --universe            # fetch expanded concepts for all listed CIKs
insider universe --as-of DATE [--min-mcap 300e6]
insider factors --as-of DATE [--ablate CODE,...] [--sector-neutral]
insider port-backtest --from DATE --to DATE --rebalance 1m \
    [--long-quantile 0.1] [--gross-long 2.0 --gross-short 1.0] \
    [--cost-bps 50 --borrow-bps 50] [--i-am-overfitting]

12. Risks and open questions


13. Build plan to green

"Green" per milestone = pytest -q (offline suite) + ruff check . + mypy pass, and the milestone's own command runs. Overall green = M-S6: the rebalanced long-short backtest runs end-to-end under holdout discipline and prints its first benchmark-relative number — with no strategy verdict claimed. Each milestone is a separate PR off main, small enough to review.

Every milestone is built the repo's way: offline unit tests with the existing fakes/builders (tests/builders.py, tests/conftest.py), point-in-time reads only, and a property test where a look-ahead could hide.

The green criteria below are sharpened from the plan-critic review: for the factor layer, "green" is not "a test passes" but "the look-ahead / sign / missing- data trap has a property or numeric-pin test that would fail if the bug were present." A milestone that only tests the happy path is not green.

#MilestoneDeliverableGreen when
M-S0 ✅Scaffoldingfactors//universe//portfolio/ packages; schema v5→6; the four tables; _DERIVED_SHAPE_REQUIREMENTS updated; enforced sink guard in test_import_graph.pyDone. migration applies on a fresh DB; reopen-preservation test; sink-boundary tests; pytest(+9)/ruff/mypy green; no behaviour
M-S1Market cap + fundamentals depthexpand WANTED_CONCEPTS; new instant-fact as-of reader; factors/marketcap.py::market_cap_as_of (stub untouched); coverage reportinstant-fact reader pin + guard that the annual path returns empty on instant facts (C2); mcap unit="shares" pin + both-legs as-of property test (C6); dei-block fetch verified; coverage command asserts a non-trivial count
M-S2Universe builderinsider universe --as-of (exchange/mcap/liquidity legs, as-of); universe_membership with reasonsthe four drop reasons plus the survivorship-inclusion test (a since-delisted name is eligible at D) and as-of eligibility (sub-$300M-at-D-but-large-now → dropped at D)
M-S3Factor enginefactors/{quality,value,momentum}.py + rank.py; insider factors --as-ofper-factor numeric pins including negative-equity/zero-denominator/negative-earnings (C5); missing-factor → excluded-not-zero (C4); momentum split-refusal (C3); winsor-then-z order + degenerate/thin cross-section pins; ablation zeroes exactly one factor and changes scorer_version
M-S4Portfolio constructionportfolio/construct.py — 200:100, caps, quantiles, turnoverNaN composite excluded (never shorted as worst) + deterministic tie-break; caps-vs-gross conflict on a thin quantile is recorded, not silently re-weighted; same input → byte-identical book
M-S5Rebalanced backtestportfolio/backtest.py + insider port-backtest; costs, borrow, benchmark-relative metrics; holdoutfill is strictly after the rebalance (property, mirrors entry.py) + symmetric exit + delist-mid-hold exits at delisting; turnover charged on the traded delta only; holdout second-set → exit 4; 2-rebalance toy corpus pinned to a known book and known return
M-S6First honest numberrun M-S5 on the real corpus once history is deep; write results with "tiny sample, not a verdict" honestyreuses the 30-trade/1-year CAGR refusal; prints benchmark-relative metrics + holdout stamp; every §12 caveat (survivorship both legs) restated beside the number
M-S7Insider as a factorfactors/insider.py; ablation on dev split, validate once on holdoutinsider factor as-of property test (reads no signal with actionable_at > D) + defined sparsity/staleness fill; incremental IR stated, not assumed
M-S8 (deferred)Real TTM assemblerreplace latest-FY flow figures with point-in-time TTMrestated-quarter pin (newest disclosure ≤ as_of per quarter); YTD-vs-discrete no-double-count pin; missing-quarter → skip, never a 3-of-4 partial

Precondition running through all of it (from PROJECT.md): the EDGAR backfill and full-universe fundamentals/prices ingest. M-S0..M-S5 are offline, test-driven, and independent of that ingest (they use fixtures); only M-S6 needs the deep real corpus. So we can build and green the entire apparatus before the long ingest finishes.

14. Test strategy

Note 2 of 5 · source file sarto-first-number.md · committed 2026-08-29 15:33 UTC

Sarto factor model — the first honest number (M-S6)

Run date: 2026-08-29. Branch: sarto/fix-dualclass-preferreds (@ 85762f7 engine). Corpus: the research corpus database (read-only), measured on a WAL-safe backup-API snapshot. Verdict: RESEARCH-ONLY — not worth real money. The strategy fails 3 of the 5 go/no-go gates (2, 3, 5). The headline long-short return is large but it is (a) not risk-adjusted-better than SPY on drawdown, (b) statistically indistinguishable from repackaged market + momentum exposure (FF-alpha t = 0.61), and (c) resting on a long side that is a survivorship upper bound (0 % delisted-name inclusion).

This is the plan's explicitly-anticipated outcome: "a modest net information ratio or none — quite possibly no edge after costs and borrow." The deliverable is the measurement apparatus and its first defensible number, not a claim of alpha.


How it was run (discipline)

ItemValue
Snapshot mechanismSQLite backup API (sqlite3.Connection.backup), source opened mode=ro. Corpus never mutated. (VACUUM INTO / wal_checkpoint(TRUNCATE) were the alternatives; backup API chosen.)
Backtest window2023-01-01 → 2026-08-01, monthly rebalance; ~2021-12 price burn-in for 12-1 momentum
Evaluable rebalance-months42 (≥ MIN_MONTHS_FOR_VERDICT = 36 ✓)
Rebalances43
ParamsPre-registered defaults: long_quantile 0.1, gross_long 2.0 / gross_short 1.0, cap_long 0.025 / cap_short 0.0125, cost 50 bps, borrow 50 bps, equal-weight pillars, NOT sector-neutral, no ablation
Dev/holdout splitEntire window is "holdout" by the engine's date rule (HOLDOUT_START = 2022-01-01; any window ending ≥ then is holdout). There is no separate dev window: fundamentals only reach back to 2023-01 and prices to 2021-12, so a date_to < 2022-01-01 "tune" window is not constructible. Discipline was therefore honored by running the single pre-registered config once — no tuning, no exploration on the measured series, --i-am-overfitting never used.
DSR trial count (n_trials)1 (one distinct logged param hash; logged_floor = 1). The survivorship-subset rerun shares the identical param hash (same window/params) so it is not a second trial. Ledger started empty; the count is provably complete and honest.
Ablation / IC / French-validation live wiringDEFERRED (see "What was not run" below)

Pre-verdict guards all passed on the real data before any number was computed: FF series is decimal (RF 0.0028–0.0048 per month, ÷100 applied once), monthly cadence (42 consecutive months), coverage floor passes (5,053 computable market caps at 2026-06 vs a 1,200 floor), SPY total-return present, FF present. The scale/alignment trap tests (test_ff_series_scale_is_decimal, test_excess_sharpe_matched_scale, test_ff_alpha_port_ff_month_alignment, test_ir_port_spy_month_alignment) pass.


The five go/no-go gates

#GateValueVerdict
1Deflated Sharpe ≥ 0.95 (deflated by n_trials = 1)DSR ≈ 1.000 (excess Sharpe 1.04)PASS ⚠️ (see DSR caveat)
2IR vs SPY > 0 AND maxDD ≤ SPY'sIR +0.330 > 0 ✓, but book maxDD −12.9 % vs SPY −7.4 % ✗FAIL
3FF alpha with HAC t > 3alpha +0.56 %/mo, HAC t = 0.61FAIL
4Long-only variant sane (edge not only in shorts)long-only ann +15.6 %, mean > 0PASS
5Survivorship subset: IR ≥ 50 % of full AND t > 3 AND same sign AND delisted-inclusion ≥ 2 %retention ✓ (subset IR 0.360 ≥ 0.5·0.330), same sign ✓, but delisted inclusion 0.00 % ✗ and subset FF-t 0.37 ✗FAIL

worth_real_money = FALSE. Fails gates 2, 3, and 5.

Why each failing gate fails


Full diagnostics (reported regardless of pass/fail)

Long-short book (net of 50 bps cost + 50 bps borrow):

MetricValue
Annualized return+29.1 %
Annualized vol22.8 %
Raw Sharpe1.24
Excess Sharpe (RF-subtracted; the gate-1 DSR input)1.04
DSR (n_trials = 1)≈ 1.000
Total return over window+144.4 % (vs SPY +104.0 %)
Max drawdown (book / SPY)−12.9 % / −7.4 %
IR vs SPY+0.330
Beta vs SPY0.757
Tracking error vs SPY20.9 %
Monthly excess hit-rate (beat SPY)52.4 %
Mean one-rebalance turnover1.68

Long-only book (net): ann +15.6 %, Sharpe 0.87, total +66.1 % — trails SPY (+104 %). So the long leg on its own does not beat buy-and-hold; the long-short excess comes substantially from the short leg (which is the structurally-optimistic, borrow-cost- modelled, hard-to-borrow-screened leg).

Fama-French regression: alpha +0.56 %/mo, HAC t = 0.61 (Newey-West, maxlags by the pinned ⌊4·(T/100)^(2/9)⌋ rule at T = 42). Betas above.

Survivorship-sensitivity pair: full IR 0.330 → subset IR 0.360 (retention 109 %, same sign); full alpha +0.56 %/mo → subset alpha +0.34 %/mo (same sign); delisted inclusion rate 0.00 % (0 / 15,106 name-months).

Equity curves (book / long-only / SPY, growth of 1.0 over 42 months): 2.44 / 1.66 / 2.04. The full monthly return series for both the book and SPY are in the run JSON.


Every §6 caveat, restated beside the number

  1. Survivorship = optimistic UPPER BOUND (the #1 threat, measured). The delisted-name inclusion rate is 0.00 %. Of 9,945 tickers with price bars, 9,861 (99.2 %) still trade at 2026-08; only ~56 delisted before mid-2026. The free ticker map / yfinance feed is effectively currently-listed-only — it does not recover names that delisted inside the window. Both legs, especially the long side, are survivorship- inflated upper bounds. This alone forces research-only (gate 5b).
  2. Exchange-gate leak (second survivorship leak). issuer_exchange is current-state, one-row-per-CIK, demoted to supplementary; eligibility anchors on price-series existence
    • issuer_tickers bounds. Residual: those bounds are ingest-derived, bounded by the 2021-12 backfill / 2023-01 start.
  3. Fundamentals coverage %. At 2023-01: 2,315 eligible of 8,004 considered (~29 %); at 2026-06, 5,053 computable market caps. The universe is what has XBRL coverage — small filers with thin XBRL are skipped (never zero-filled). A stated shrinkage, above the decile-stability floor (top decile ≈ 230 names ≫ the 80 needed to fill the long book).
  4. Borrow cost is MODELLED, not real. 50 bps annual on surviving shorts by size bucket
    • a hard-to-borrow screen. No real borrow feed. The short leg — which carries most of the long-short excess — is an upper bound on real shortability.
  5. Fundamentals are latest-FY, not TTM (M-S8 deferred). Flow figures (earnings, cash flow, gross profit) use the latest annual disclosure ≤ as_of, not a point-in-time trailing-twelve-month stitch. An explicit, revisitable approximation.
  6. ~42-month sample is SHORT. Barely above the 36-month floor; sparse residual dof for the FF regression and the DSR. A 3–5y bar is the Sarto minimum, and this is the low end.
  7. Universe / coverage. ~29 % of considered names are eligible (caveat 3); the number is computed on that covered subset, not the full US market.
  8. Dual-class handling is PARTIAL (two cohorts, one stated bias). 423 names were excluded dual_class_unpriceable at 2023-01 (multi-listed classes caught by COUNT(DISTINCT ticker) > 1, plus a 5-name denylist: META, SNAP, F, PLTR, NKE). Single-listed-class dual-class names (second class unlisted) that escape both the ticker-count query and the denylist are priced off one class → understated market cap → overstated value ratios → a spurious long/value pull. A stated residual bias, not a clean exclusion.
  9. These are self-found PUBLIC factors. V/M/Q are the most-published factors in existence; McLean–Pontiff decay says only ⅓–½ of naive backtest edge survives publication, and Muravyev-style critique says an unmodelled short leg is structurally optimistic. Here there is no significant alpha to decay in the first place (gate 3), and the return is dominated by a 0.90 momentum beta — so the live-survival expectation is worse than the ⅓–½ rule of thumb, not better.

Real-data issues hit (reported, not worked around — for orchestrator triage)

  1. Gate-1 DSR input-Sharpe scale looks anti-conservatively saturated. excess_sharpe returns an annualized Sharpe (×√12 = 1.04), but deflated_sharpe's z-statistic z = (sharpe − sr0)·√(n_obs−1)/√var treats sharpe as a per-observation (monthly) Sharpe. Feeding the annualized 1.04 with n_obs = 42 gives z ≈ 6.0 → DSR ≈ 1.000. The BLdP-correct per-monthly Sharpe (1.04/√12 ≈ 0.30) gives z ≈ 1.92 → DSR ≈ 0.97. Gate 1 passes either way (0.97 and 1.00 both ≥ 0.95), so the overall verdict is unchanged (gates 2/3/5 fail), but this is a latent scale mismatch that would matter near the DSR boundary and should be triaged in the apparatus (it saturates, i.e. understates deflation). Not fixed here — this is an execution milestone.
  2. Survivorship-free price backfill did not actually capture delisted names. The Track-B price ingest was intended to be survivorship-free, but in practice the free feed returns almost exclusively currently-listed tickers (caveat 1). This is a data-ingest gap, not an engine bug, and it is precisely what gate 5 is designed to catch — but it means the long-side number cannot be de-biased from within this corpus. Recovering delisted-name prices (a paid feed, or CRSP-style delisting returns) is the prerequisite to a non-upper-bound long side.

What was NOT run, and why (honest trial budget)


Bottom line

A large gross long-short return (+29 %/yr, +144 % cumulative) that is not worth real money: it draws down deeper than SPY, has no statistically significant Fama-French alpha (t = 0.61) — it is repackaged market + momentum beta — and its long side sits on a survivorship upper bound (0 % delisted inclusion). Per SPEC §9 / plan §1, anything short of all five gates is research-only, no capital. The apparatus works, the number is honest, and the honest number says: no demonstrated edge after costs, factors, and survivorship.

Runner: scripts/ms6_first_number.py (the only new code — it calls the built run_port_backtest and evaluate_go_no_go, changes no engine code). Raw result block: the run's --out JSON.

Note 3 of 5 · source file sarto-insider-factor-result.md · committed 2026-08-29 19:43 UTC

Sarto factor model — does docka's insider signal add alpha over QVM? (M-S7)

Run date: 2026-08-29. Branch: sarto/m-s7-insider-factor (@ d3710ea engine + insider factor). Corpus: the research corpus database (read-only), measured on a WAL-safe backup-API snapshot. Verdict: INSIDER WASHES OUT — it adds no alpha, and marginally DEGRADES the book.

This milestone answers the decisive question the QVM-only result (M-S6) left open: does folding docka's insider signal in as a fourth pillar inject real, independent alpha that the quality/value/momentum book lacked? M-S6 produced a large long-short return (+29.1 %/yr) that was not worth real money — repackaged momentum (Fama-French alpha HAC t = 0.61), drawing down deeper than SPY, on a survivorship upper bound. The test here: BASE (QVM only) vs WITH_INSIDER, head to head, same window, same pre-registered book.

The answer, up front. Adding the insider factor moves the Fama-French alpha HAC t-stat from 0.61 to 0.62 — i.e. essentially not at all, and nowhere near the t > 3 bar a real edge would need. On every other axis it is flat-to-slightly-worse: information ratio vs SPY 0.330 → 0.320, annualized return 29.1 % → 28.7 %, max drawdown −12.9 % → −15.6 % (deeper), deflated Sharpe 0.949 → 0.940. The isolated marginal series (WITH − BASE) has an annualized return of −0.4 % with an FF-alpha HAC t = −0.18 (indistinguishable from zero, faintly negative). docka's insider signal, folded in this way over this window, carries no independent alpha; M-S6's research-only verdict stands unchanged.


Integrity disclosure — read this before the numbers

This is a controlled A/B on a corpus that is entirely post-2022, which forces two honest disclosures:

  1. There is NO separate untouched holdout on this data. The engine's split rule keys on a fixed HOLDOUT_START = 2022-01-01: any window ending after that date is labelled holdout. The Sarto corpus's fundamentals only reach back to 2023-01 (prices to 2021-12 for the momentum burn-in), so a date_to < 2022-01-01 "dev/tune" window is not constructible (established at M-S6). Both configs therefore share the same 2023-01 → 2026-08 window. There is no out-of-sample split here.
  2. --i-am-overfitting was used deliberately, and disclosed. BASE was the first (allowed) holdout run. The engine's anti-p-hacking guard then refuses a second distinct parameter set on the holdout split — exactly correct for iterative tuning, but it would block a legitimate pre-registered A/B. WITH_INSIDER is 1 of 2 pre-registered configs (base / with-insider), not an iterative search, so the flag was set to permit it. The flag only lifts the refusal and records i_am_overfitting: true in args_json; it changes NO computed metric. Both runs are logged in factor_backtest_runs, so the DSR is deflated by the full trial count (n_trials = 2), not by a laundered count of 1.

How to read the result. The honest statistical test here is the Fama-French alpha HAC t-stat comparison (a controlled marginal test, BASE vs WITH_INSIDER on the same window), not a clean out-of-sample holdout claim.

Staging the rigor (stated in advance, not after seeing the number):

We are in the second case. FF-alpha t went 0.61 → 0.62. No further validation is warranted; the answer is that the insider signal adds nothing after factors.

Note — ABLATION not run (deliberate). The plan also specifies an --ablate INSIDER_SCORE run (insider pillar present, its z zeroed). Zeroing the insider z is ≈ BASE, so BASE already serves as the ablation reference for the marginal test (WITH − BASE). The explicit ablation run is a third distinct holdout hash that added ~20 min and no verdict-relevant information; it was skipped to keep the honest trial count at 2. The marginal contribution is measured directly as WITH_INSIDER − BASE.


How it was run (discipline)

ItemValue
Snapshot mechanismSQLite backup API (sqlite3.Connection.backup), source opened mode=ro. Corpus never mutated; WAL-safe (committed frames included).
Backtest window2023-01-01 → 2026-08-01, monthly rebalance; ~2021-12 price burn-in for 12-1 momentum
Evaluable rebalance-months42 (≥ MIN_MONTHS_FOR_VERDICT = 36 ✓); 43 rebalances
Configs (pre-registered)BASE with_insider=off (reproduces M-S6, 3-pillar QVM, ≥2-of-3); WITH_INSIDER with_insider=on, 6-month staleness (4th pillar, composite ≥2-of-4)
Shared paramslong_quantile 0.1, gross 2.0/1.0, caps 2.5 %/1.25 %, cost 50 bps, borrow 50 bps, equal-weight pillars, NOT sector-neutral
Param hashesBASE ceafecb5f524 (scorer sarto-factors-v1), WITH_INSIDER d82722e1c0a9 (scorer sarto-factors-v1+insider=6m) — two distinct scorer_versions
DSR trial count (n_trials)2 (two distinct logged holdout hashes; logged_floor = 2). DSR deflated by 2.
Insider factorReads the signals table read-only: latest non-superseded cluster with actionable_at ≤ as_of, staleness cap 6 months, sparse (a name with no cluster is absent, never 0). Does not alter score/scorer.py.

The decisive comparison — BASE vs WITH_INSIDER

MetricBASE (QVM only)WITH_INSIDER (4 pillars)Change
Fama-French alpha (monthly)+0.560 %/mo+0.531 %/mo−0.029 pp
FF-alpha HAC t-stat ⭐0.6070.621+0.014 (both ≪ 3)
Annualized long-short return+29.09 %+28.75 %−0.35 pp
Annualized vol22.76 %22.22 %−0.54 pp
Raw Sharpe1.2391.253+0.014
Excess Sharpe (DSR input)1.0401.050+0.010
DSR (deflated by 2)0.9490.940−0.009
Information ratio vs SPY0.3300.320−0.010
Max drawdown (book / SPY)−12.95 % / −7.42 %−15.61 % / −7.42 %deeper
Monthly excess hit-rate52.38 %52.38 %0
Mean turnover1.6781.726+0.048

⭐ The load-bearing number. The whole point of M-S7 was whether the insider signal injects an alpha the QVM book lacked. It does not: the FF-alpha HAC t-stat goes 0.607 → 0.621 — a rounding- error move that leaves it an order of magnitude short of the t > 3 significance bar. The return is still overwhelmingly factor beta, not idiosyncratic alpha. Adding insider does not reduce the momentum dependence either: the UMD (momentum) loading stays dominant at 0.897 → 0.845.

Marginal contribution (WITH − BASE return series, FF-tested). Annualized-return difference −0.42 %/yr, monthly FF-alpha −0.029 %/mo at HAC t = −0.177. The marginal series delivered by the insider factor is statistically indistinguishable from zero and, if anything, faintly negative. Over this window and construction, the insider pillar's net effect on the book is nil-to-slightly-harmful: it trims a hair of return, deepens the worst drawdown, and lowers the deflated Sharpe and information ratio, while leaving the (absent) alpha absent.


Full metric blocks

BASE (QVM only — M-S6 reproduction)

MetricValue
Annualized return / vol+29.09 % / 22.76 %
Raw / excess Sharpe1.239 / 1.040
DSR (n_trials 2)0.949
Total return (book / SPY)+144.4 % / +104.0 %
Max drawdown (book / SPY)−12.95 % / −7.42 %
IR / beta / TE vs SPY0.330 / 0.757 / 20.88 %
Monthly excess hit-rate52.38 %
Mean turnover1.678
Long-only ann / Sharpe / total+15.60 % / 0.867 / +66.1 %
FF alpha / HAC t+0.560 %/mo / 0.607
FF betasmkt-RF 0.914, UMD 0.897, RMW 0.589, HML 0.236, SMB 0.147, CMA −0.061

WITH_INSIDER (4 pillars, 6-month staleness)

MetricValue
Annualized return / vol+28.75 % / 22.22 %
Raw / excess Sharpe1.253 / 1.050
DSR (n_trials 2)0.940
Total return (book / SPY)+142.1 % / +104.0 %
Max drawdown (book / SPY)−15.61 % / −7.42 %
IR / beta / TE vs SPY0.320 / 0.736 / 20.46 %
Monthly excess hit-rate52.38 %
Mean turnover1.726
Long-only ann / Sharpe / total+15.03 % / 0.855 / +63.2 %
FF alpha / HAC t+0.531 %/mo / 0.621
FF betasmkt-RF 0.957, UMD 0.845, RMW 0.576, HML 0.282, SMB 0.095, CMA 0.044

The two books are near-twins. The insider pillar reshuffles the top decile slightly (turnover ticks up, betas shift marginally) but does not change the character of the return: momentum-dominated, no significant alpha.


The five go/no-go gates on WITH_INSIDER

#GateValueVerdict
1Deflated Sharpe ≥ 0.95 (deflated by n_trials = 2)DSR 0.940FAIL
2IR vs SPY > 0 AND maxDD ≤ SPY'sIR +0.320 > 0 ✓, but book maxDD −15.6 % vs SPY −7.4 % ✗FAIL
3FF alpha HAC t > 3alpha +0.531 %/mo, t = 0.621FAIL
4Long-only variant sanelong-only ann +15.03 %, mean > 0PASS
5Survivorship subset (only if 1-4 pass)not evaluated — WITH failed gates 1-4, so per plan §5 the ~20-min subset rerun was correctly skippedn/a

worth_real_money = FALSE. WITH_INSIDER fails gates 1, 2, and 3.

On gate 1 (a note on trial-count sensitivity). At M-S6's n_trials = 1, BASE's DSR saturated near 1.000 and passed gate 1. At M-S7's honest n_trials = 2, both BASE (0.949) and WITH_INSIDER (0.940) fall just under the 0.95 bar — the deflation by two trials is what pushes them under. This does not change any verdict (both already fail gates 2 and 3), but it is the honest reading: with the insider trial declared, gate 1 no longer passes either. (The M-S6-flagged DSR scale quirk — the annualized excess Sharpe fed where a per-observation Sharpe is expected, which understates deflation — still applies; if anything the true DSR is lower, reinforcing the FAIL.)

Gate 5 was not reached: the plan (§5) runs the survivorship subset only if the candidate clears gates 1-4, precisely to avoid spending ~20 min on a book that has already failed. WITH_INSIDER failed at gate 1, so the subset rerun was skipped.


Every caveat, restated beside the number

  1. These signals are the box's PRODUCTION scorer v15, pre-computed — not a fresh re-score. The signals table was populated by docka's live v15 scorer (reflecting the box's price/catalyst snapshots up to ~2026-08-10), reused here read-only. A fresh local re-score could differ; this is a replay of production scores, not an independent scoring. A materially different scorer could in principle carry alpha this one does not — but on the production signal as it stands, the answer is a wash.
  2. The insider factor is SPARSE and spans only 2023-08 → 2026-08. 15,300 signals over 3,191 issuers; a name without a cluster ≤ as_of simply uses its QVM composite (absent from the insider cross-section, never zero-filled). So the insider factor contributes on ~36 of the 42 evaluable months, and only for the subset of names carrying a signal — expected sparsity. This sparsity is also why the marginal effect is small: on most name-months the composite is unchanged.
  3. Survivorship = optimistic UPPER BOUND (the #1 threat). The free price feed is effectively currently-listed-only; the delisted-name inclusion rate at M-S6 was 0.00 %. Both legs, especially the long side, are survivorship-inflated. Unchanged by adding the insider factor.
  4. Borrow cost is MODELLED, not real (50 bps annual + hard-to-borrow screen; no real borrow feed). The short leg — which carries most of the long-short excess — is an upper bound on real shortability.
  5. Fundamentals are latest-FY, not TTM (M-S8 deferred) — an explicit, revisitable approximation.
  6. ~42-month sample is SHORT — barely above the 36-month floor; sparse residual degrees of freedom for the FF regression and the DSR. The low end of the Sarto 3-5y minimum.
  7. Dual-class handling is PARTIAL — 423 names excluded dual_class_unpriceable; a stated residual single-listed-class market-cap-understatement bias remains.
  8. These are self-found PUBLIC factors (V/M/Q); McLean-Pontiff decay says only ⅓-½ of naive backtest edge survives publication. The insider signal was the one candidate for a non-public edge here — which is exactly why this test mattered, and it did not deliver one.

Bottom line

docka's insider signal does not add real, statistically-significant alpha on top of the QVM factors — it washes out. Head to head on the identical 42-month window and pre-registered book, folding the insider score in as a fourth pillar moved the Fama-French alpha HAC t-stat from 0.61 to 0.62 (both an order of magnitude below the t > 3 bar), and marginally worsened the book on information ratio (0.330 → 0.320), annualized return (29.1 % → 28.7 %), max drawdown (−12.9 % → −15.6 %) and deflated Sharpe (0.949 → 0.940). The isolated marginal series (WITH − BASE) is a statistically-zero −0.4 %/yr at FF-alpha t = −0.18. WITH_INSIDER fails go/no-go gates 1, 2, and 3; worth_real_money = FALSE.

Per the pre-stated rigor ladder, a wash-out is the terminal answer — no further validation is warranted, and no dev/holdout escalation is triggered. M-S6's RESEARCH-ONLY verdict stands: this book, with or without the insider pillar, shows no demonstrated edge after costs, factors, and survivorship. The insider signal, as scored by production v15 and folded in this way over this window, is not the missing alpha.

Runner: scripts/ms7_insider_factor.py (staged + resumable; the only new code — it calls the built run_port_backtest and evaluate_go_no_go, changes no engine code). Raw result block: the run's --out JSON.

Note 4 of 5 · source file sarto-insider-ic-test.md · committed 2026-08-31 09:47 UTC

Sarto — standalone insider-signal IC test (the audit's highest-powered re-test)

Run date: 2026-08-31. Branch: sarto/fix-audit-defects (bug-fixed seams @ 3e66101). Corpus: the research corpus database (opened read-only: URI mode=ro + PRAGMA query_only=1). Script: scripts/insider_ic_test.py (pinned by tests/unit/test_insider_ic_test.py).

Verdict up front: the insider score is DEAD as a positive return predictor at 1, 3 and 6 months on this sample — and the one statistically significant pattern in the family runs the WRONG way (the top-scored decile underperforms).

The prior test blended the score as a ¼-weight sparse pillar into a factor composite and measured book-level differences; the audit showed that design had ~no statistical power (70–88 % of the measured difference was structural churn), so the signal had been "shown nothing," not "shown dead." This test removes all portfolio machinery — no pillars, no dilution, no book construction — and asks the signal directly: does the docka insider score rank forward stock returns at all? The answer this time is not "nothing shown." It is: ~zero, measured with enough power to exclude the entire real-world-good band.


Pre-registered spec (implemented exactly; the multiplicity budget is THIS family)

The 6 headline numbers

HorizonMonthsMean ICIC NW-tIC>0 hit rateDecile spread / periodSpread NW-tSpread ann.
1m (21 sess, lag 0)34+0.0010+0.1350.0 %−2.50 %−3.31−30.0 %/yr
3m (63 sess, lag 2)33−0.0043−0.3742.4 %−7.15 %−3.01−28.6 %/yr
6m (126 sess, lag 5)30−0.0045−0.5453.3 %−8.18 %−2.99−16.4 %/yr

Hedged decomposition (mean per period, vs the covered-universe mean): 1m top −1.31 % / bottom +1.19 %; 3m top −4.23 % / bottom +2.92 %; 6m top −6.60 % / bottom +1.57 %. The negative spread is not one bad month: the monthly spread is negative in 24/34 (1m), 24/33 (3m) and 23/30 (6m) months, medians −2.4 % / −5.8 % / −5.4 %, and the hedged top leg is below the universe mean in 24/34, 31/33 and 27/30 months — the effect lives mostly in the top-scored decile, not the bottom.

Detectable-effect math (the pre-stated power claim, then the actuals)

Pre-stated: with ~34 monthly observations the test can detect a true mean IC ≈ 0.05 at t ≈ 2–3. Actuals are better, because the realized cross-sections are wide (247–1,256 names/month) and the monthly IC series is tight (sd 0.042–0.054): the NW standard errors of the mean IC came out 0.0078 (1m), 0.0117 (3m), 0.0083 (6m), so a true mean IC of 0.016 / 0.023 / 0.017 would have shown t ≥ 2 at 1m / 3m / 6m. Equivalently, the 95 % CIs on the mean IC are [−0.015, +0.017] (1m), [−0.028, +0.019] (3m), [−0.021, +0.012] (6m): every horizon's CI excludes the entire |IC| 0.02–0.05 "real-world-good" band on the positive side. This is the upgrade over the audited pillar test: not "no power, nothing shown" but "power enough, and ~zero is what's there."

Coverage

Per-month coverage below. candidates = ciks with a live in-window signal; unresolved = no resolvable ticker (12–20 %/month, mostly names with no price join in the corpus); no_entry = no bar within 5 sessions of the date; dup = second cik on an already-taken ticker (never occurred); usable = the covered cross-section.

Obs dateCandidatesUsableUnresolvedNo entry bar
2023-08-31308247583
2023-09-294933891022
2023-10-315954611295
2023-11-308036241736
2023-12-299237171997
2024-01-319697492164
2024-02-2910157982143
2024-03-2810318142134
2024-04-3010378202125
2024-05-3110258281943
2024-06-2810198251904
2024-07-3110108241824
2024-08-3010638731873
2024-09-309978171764
2024-10-319858131684
2024-11-299928181686
2024-12-319708011654
2025-01-319547821684
2025-02-289848091705
2025-03-3111169381726
2025-04-3011569721768
2025-05-30125810631887
2025-06-30125810701817
2025-07-31124410661726
2025-08-29131911381774
2025-09-30119210271605
2025-10-31115410041464
2025-11-28113010041215
2025-12-3111049781215
2026-01-3010939691204
2026-02-27112310021165
2026-03-31127711521223
2026-04-30132212001193
2026-05-29137712561192

Name-month totals: 29,648 (1m), 28,392 (3m), 25,038 (6m). Horizon month counts: the 6m window runs out of data after the 2026-01 observation (30 months), and the 3m window for 2026-05-29 lands one session past SPY's last bar (2026-08-27), so 3m has 33 months, not the pre-stated 34 — disclosed rather than patched, per the "exit on or before the last benchmark session" rule.

Delisted names, and a data surprise that matters

Caveats

Honest verdict (per the pre-stated bands)

Dead. The pre-registered band said IC t < ~1 means the signal is dead at that horizon on this sample: measured IC t = +0.13 / −0.37 / −0.54, with |mean IC| ≤ 0.005 everywhere — an order of magnitude below the 0.02–0.05 real-world-good band, and with confidence intervals tight enough to exclude that band outright at every horizon. As a positive return predictor — the signal's claim, and the direction the bot trades — the docka insider score does not predict forward returns at 1, 3 or 6 months on this corpus, and this time the test had the power to say so. The one band-crossing statistic (decile spread t ≈ −3.0 to −3.3 at all three horizons) has the wrong sign: the top-scored decile underperforms the bottom by ~25–30 %/yr equal-weight, concentrated in the top leg, while the IC sits at zero — i.e., a tail phenomenon (the most heavily insider-bought names do badly; ranks in the middle carry no information), not a monotone inverse signal. That inverted-tail pattern was not pre-registered as a directional hypothesis and is reported as an observation, not a discovery: if anyone wants to pursue "short the top insider decile," that is a NEW pre-registered study on fresh data with the small-cap/illiquidity/cost confounds controlled — this write-up makes no such claim. For the roadmap: the standalone read is strictly worse than "unvalidated"; there is no positive signal here to rescue with more sources at these horizons under scorer v15.

Note 5 of 5 · source file sarto-corrected-results.md · committed 2026-08-31 10:55 UTC

Sarto factor model — CORRECTED record (M-S6/M-S7 rerun on audit-fixed code)

Run date: 2026-08-31. Branch: sarto/fix-audit-defects (@ 3e66101). Corpus: the research corpus database (read-only), measured on a fresh WAL-safe backup-API snapshot.

SUPERSEDES-NOTE. This document supersedes the numeric records in docs/sarto-first-number.md (M-S6, BASE numbers) and docs/sarto-insider-factor-result.md (M-S7, BASE/WITH numbers + the WITH−BASE marginal). Those runs were made on code carrying four confirmed defects (below); their numbers are retracted in favor of the blocks here. Their verdicts — RESEARCH-ONLY, worth_real_money = FALSE, insider adds no alpha — are unchanged by the correction, and their methodology and integrity-disclosure text still applies except where restated here.

Verdict, corrected: STILL RESEARCH-ONLY — not worth real money — and the insider signal still adds nothing. Every headline number improved materially (the bugs deflated returns), but both configs still fail gates 2 and 3, the survivorship upper bound stands, and the newly completed ablation control isolates the insider signal's clean marginal contribution at exactly zero (FF-alpha HAC t = −0.009).


Audit context and the fix list

A 17-agent audit of the M-S6/M-S7 record confirmed four defects in the measurement code (fixed and green at 3e66101, 2688 tests; audit numbering retained):

#DefectFix
D1Cost double-charge — cost_bps (50bp, a round-trip guess) was charged on every one-way traded delta, i.e. 100bp round-tripEach one-way delta now pays cost_bps/2; establish + liquidate totals exactly 50bp. At ~1.7×/mo turnover this alone returns ≈ +5%/yr to the book
D3Preferred-ticker resolution — bar-count tie-break let a preferred fetched one session earlier beat the common (MS → MS-PF)Dollar volume ranks before bar count, and non-common listings (preferred/warrant/unit/right, bare fifth-letter, curated note/ETN set) are dropped from the candidate pool, with a fallback so a units-only issuer still resolves
D4False dual-class exclusion — bare-ticker baby bonds/ETNs/depositary listings under the same CIK branded ~81 single-common mega-cap financials/utilities (JPM, WFC, BAC, GS, T, NEE, DUK, SO, …) dual_class_unpriceableShared repo_meta.non_common_listings filter; the cohort is back in the universe
D5Rounding-triggered exclusions — exact-equality reconciliation of the two shares concepts excluded names over millions-rounding hairs (CAT: 0.009%)0.5% relative tolerance; genuine divergence still fires

Measured on this corpus (old vs new flag logic diffed head-to-head at as-of 2025-06-30): 118 CIKs un-flagged (the D4 cohort plus D5 tolerance restorations such as CAT), 0 newly flagged, 1,178 still flagged. GOOG/GOOGL, BRK-A/B and the genuine dual-class set are still detected.

How it was rerun (discipline)

ItemValue
SnapshotFresh SQLite backup-API copy of the corpus (source mode=ro); all three runs + the ledger on the snapshot; corpus never mutated
Window / paramsIdentical to the pre-registered record: 2023-01-01 → 2026-08-01, monthly; long_quantile 0.1, gross 2.0/1.0, caps 2.5%/1.25%, cost 50bp now correctly round-trip, borrow 50bp, equal pillars, not sector-neutral
Evaluable months42 (43 rebalances; FF ends 2026-06) — same as the superseded record
ConfigsBASE (3-pillar QVM), WITH_INSIDER (4th pillar, 6-mo staleness), ABLATION (--ablate INSIDER_SCORE — insider pillar present, z zeroed)
Param hashesBASE ceafecb5f524… and WITH d82722e1c0a9… — identical to the superseded record (the fixes change no pre-registered knob, so these are identical-hash reruns, always allowed on holdout). ABLATION 5a3fbe906576… is a new, third distinct hash
Overfitting flagBASE ran first on the empty ledger (allowed). WITH and ABLATION carry i_am_overfitting — WITH as the pre-registered A/B arm (disclosure unchanged from M-S7), ABLATION as the audit-ordered, pre-registered control completing the run M-S7 deliberately skipped. The flag lifts the second-distinct-hash refusal and changes no computed metric
DSR trial countn_trials = 3 (three distinct logged holdout hashes; floor = 3) — the honest count. DSR is reported at n_trials=3 (primary) and n_trials=1 (side-computation, both disclosed below)

Why the ablation matters. WITH−BASE mixes two effects: the insider signal and the 3→4-pillar structural churn (the ≥2-of-4 composite coverage rule reshuffles the eligible cross-section even where no insider z exists). ABLATION holds the 4-pillar structure and eligible sets identical to WITH but zeroes the insider z — so WITH − ABLATION is the only clean isolation of the signal, and ABLATION − BASE prices the structural churn.


THE KEY NEW NUMBER — the clean insider-signal isolation

Marginal series (42 mo)Mean monthlyAnn. return diffFF alpha (mo)HAC t
WITH − ABLATION (signal, clean) ⭐+0.001%−0.10%/yr−0.0012%−0.009
WITH − BASE (continuity w/ old record)+0.019%+0.14%/yr−0.0051%−0.035
ABLATION − BASE (structural churn)+0.018%+0.17%/yr−0.0039%−0.021

⭐ The audit-ordered control, now completed. The insider signal's clean marginal contribution is zero to the fourth decimal: +0.1bp/mo mean, FF-alpha t = −0.009. And the decomposition shows ~95% of the (tiny) WITH−BASE mean difference is structural churn, not signal (+1.78bp/mo of the +1.88bp/mo total). M-S7's shortcut of reading WITH−BASE as the marginal was directionally right but conflated: the honest split is churn ≈ everything, signal ≈ nothing. On the corrected code, over this window, docka's insider signal carries no independent alpha — same verdict as the buggy record, now on a defensible control.


Corrected metric blocks

MetricBASE (QVM)WITH_INSIDERABLATION
Annualized L-S return+35.58%+35.92%+35.77%
Annualized vol22.43%22.05%22.76%
Raw Sharpe1.4771.5131.465
Excess Sharpe (DSR input)1.2761.3081.267
DSR @ n_trials=3 (primary)0.9670.9560.963
DSR @ n_trials=1 (disclosed side-calc)0.9970.9950.996
IR vs SPY0.5760.5960.587
Beta / TE vs SPY0.754 / 20.54%0.744 / 20.21%0.805 / 20.51%
Max drawdown (book / SPY)−10.31% / −7.42%−12.79% / −7.42%−14.43% / −7.42%
Monthly excess hit-rate52.4%54.8%57.1%
Mean turnover1.6741.7231.689
Total return (book / SPY +104.0%)+190.2%+192.8%+191.6%
Long-only ann / Sharpe / total+17.73% / 0.968 / +77.1%+17.13% / 0.958 / +73.9%+17.00% / 0.930 / +73.2%
FF alpha (monthly) / HAC t+0.999% / t=1.092+0.994% / t=1.150+0.995% / t=1.119
FF betas (mkt-RF, SMB, HML, RMW, CMA, UMD)0.89, 0.18, 0.19, 0.57, −0.08, 0.880.96, 0.16, 0.26, 0.63, −0.03, 0.830.92, 0.17, 0.13, 0.46, 0.04, 0.90

The return is still overwhelmingly factor beta — UMD loads 0.83–0.90 in all three configs — and the alpha t-stat, though roughly doubled by the corrections, is still nowhere near the bar.

The corrected five-gate table (BASE and WITH_INSIDER)

#GateBASEWITH_INSIDER
1DSR ≥ 0.95 (deflated by the honest n_trials = 3)0.967 — PASS0.956 — PASS
2IR vs SPY > 0 and maxDD ≤ SPY'sIR 0.576 ✓ but −10.31% vs −7.42% ✗ — FAILIR 0.596 ✓ but −12.79% vs −7.42% ✗ — FAIL
3FF alpha HAC t > 3t = 1.09 — FAILt = 1.15 — FAIL
4Long-only sane (mean > 0)+17.7%/yr — PASS+17.1%/yr — PASS
5Survivorship subset (formally evaluated only if 1–4 pass)not reached (plan §5) — and the 5b evidence, measured directly: delisted-name inclusion 0.00% (0 of 15,183 name-months) → would FAIL the 2% floor; the long side remains an upper boundsame: 0.00% (0 of 15,178) → would FAIL

worth_real_money = FALSE for both. Gate 1 now passes under both DSR conventions (the cost fix raised the Sharpe; note the M-S6 DSR-scale quirk is also fixed — the per-observation de-annualisation is pinned by trap test, so these DSRs are on the correct scale). That changes nothing: gates 2 and 3 fail exactly as before, and gate 5's measured 0% delisted inclusion means every one of these return numbers is a survivorship-optimistic ceiling.

Sanity checks on the corrected cross-section

What changed vs the buggy record — honest read

Number (BASE)Buggy (superseded)CorrectedDriver
Ann. L-S return+29.1%+35.6%D1 halved effective costs (≈ +5%/yr at 1.67 turnover) + restored cross-section
Excess Sharpe / DSR1.04 / 0.949 @n=21.28 / 0.967 @n=3same; gate 1 flips to PASS despite the larger trial deflation
IR vs SPY0.330.58same
Max drawdown−12.9%−10.3%costs + the restored (lower-vol, mega-cap financial) longs
FF alpha / HAC t+0.56%/mo, t=0.61+1.00%/mo, t=1.09roughly doubled — but still ⅓ of the t>3 bar
WITH−BASE marginal−0.42%/yr, t=−0.18+0.14%/yr, t=−0.035noise around zero in both records
WITH−ABLATION (new)never run−0.10%/yr, t=−0.009the clean control: insider = zero

The bugs were material and conservative-biased (they suppressed returns ≈6.5%/yr and corrupted the cross-section), and fixing them makes the book look meaningfully better — but they were not verdict-relevant in either direction: the strategy remains a momentum-dominated, survivorship-capped factor book with no significant alpha, and the insider pillar's contribution, now cleanly isolated for the first time, is statistically and economically nil.

Every carried caveat, restated

  1. Survivorship = optimistic UPPER BOUND — 0.00% delisted-name inclusion, measured (the #1 threat, unchanged). Every return/Sharpe/IR number here is a ceiling, not an estimate.
  2. 42-month sample — barely above the 36-month verdict floor; thin FF residual dof.
  3. Gate-3 power limit — at this run's HAC SE (≈0.86–0.91%/mo), t > 3 needs ≈ 2.6–2.7%/mo alpha (~35%+/yr idiosyncratic); the test can only reject enormous alphas at this T.
  4. Insider signals are the box's production scorer v15, replayed read-only — not a fresh re-score; sparse (2023-08→2026-08, ~36 of 42 months, signal-carrying subset only).
  5. Whole-window holdout — no pre-2023 dev split is constructible on this corpus; the honest reading is the controlled A/B + ablation on one window, not out-of-sample validation.
  6. Borrow is modelled (50bp + HTB screen), fundamentals are latest-FY not TTM, and V/M/Q are self-found public factors (publication decay applies).
  7. AHL-PE / TRTN-PC residual (0.04% of name-months) via the documented resolver fallback.

Bottom line

The 17-agent audit was right that the numbers were wrong, and wrong about nothing else: on fixed code the book earns +35.6%/yr (BASE) at a 0.58 IR with a passing deflated Sharpe — and it is still repackaged momentum (UMD 0.88, FF-alpha t 1.09 ≪ 3) on a survivorship ceiling, still drawing down deeper than SPY, still not worth real money. The previously-skipped ablation control, now run, settles M-S7's question at the cleanest level available: WITH − ABLATION = −0.10%/yr at t = −0.009 — docka's insider signal, isolated from structural churn, contributes nothing. RESEARCH-ONLY stands.

Runner: scripts/ms7_insider_factor.py (staged, checkpoint-resumable; base → with → ablation → aggregate, each stage its own process). Raw blocks: the run's --out JSON; restored-cohort diff + DSR side-calcs: session analysis scripts against the same snapshot.