From 496ce49e2103291f62b09326dc85853b0ae9bd69 Mon Sep 17 00:00:00 2001 From: Scout Date: Thu, 24 Sep 2026 02:34:30 -0400 Subject: [PATCH 1/8] Record the mover v3 SOTA sweep: dated research plan, draft protocol and catalog decisions blueprints/us-equities/mover-v3/README.md (2026-09-24) turns the verified sweep (37 items: 5 survive, 24 contested, 8 killed) into a plan. It uses only the surviving items and the contested items whose refutation the synthesis judged weak, labelled as contested. It covers why continuation is not supported (BCW 2011 MAX, Hong et al. days to cover, the Barber/Odean prior, #162's 0/768 and v2 no-pass), H1-H6 with entry, exit, sizing, falsifier and data status, the data plan (available now vs Databento/Norgate/FINRA gated), the broker-held exit and leverage-ladder policy, the call-cost table for the 200/min and 1000/min tiers (throughput is capacity, not a target; the plain limit pattern is corrected to 2/f calls per round trip), simulation fidelity, and the dropped items with reasons. protocol-draft.json is status draft_pending_independent_pre_outcome_review, not frozen. It sets windows disjoint from v1/v2 (development 2017-2019, validation 2020, a prospective holdout) and names the prior exposures. One Holm family of 37 items at 0.05 covers H1-H6, with a lineage sensitivity over 1,012 earlier rule-exits and MDE tables. The cost model reuses #162's v1 table by sha256 be50cbdf, makes it monotone in dollar volume (7 cells) and adds capacity caps plus an impact term. The holdout gate has a committed access log. catalogs/us-equities/mover-v3-sweep-20260924.json holds the add, compare and no-change records with their evidence levels. Its /repository_decisions (7 existing repositories) is registered via catalog_decisions.py --write --supplement; the repository count is unchanged (844), and there are 1,968 references. manifests/evidence.json updates the reviewed hashes of the two changed registered files. PR #162's files are untouched. This is literature and metadata evidence only: no native execution, no data fetched, and no outcome computed. Co-Authored-By: Claude Opus 5.5 (1M context) --- blueprints/us-equities/mover-v3/README.md | 340 ++++++++++++++++++ .../us-equities/mover-v3/protocol-draft.json | 300 ++++++++++++++++ catalogs/us-equities/README.md | 5 + catalogs/us-equities/decision-index.json | 64 +++- .../us-equities/mover-v3-sweep-20260924.json | 315 ++++++++++++++++ manifests/evidence.json | 8 +- 6 files changed, 1027 insertions(+), 5 deletions(-) create mode 100644 blueprints/us-equities/mover-v3/README.md create mode 100644 blueprints/us-equities/mover-v3/protocol-draft.json create mode 100644 catalogs/us-equities/mover-v3-sweep-20260924.json diff --git a/blueprints/us-equities/mover-v3/README.md b/blueprints/us-equities/mover-v3/README.md new file mode 100644 index 000000000..7ca4e2541 --- /dev/null +++ b/blueprints/us-equities/mover-v3/README.md @@ -0,0 +1,340 @@ +# Mover study v3: research plan (2026-09-24) + +**Status: draft plan. Nothing here is frozen, and no outcome has been computed.** The preregistration draft is +[`protocol-draft.json`](protocol-draft.json), status `draft_pending_independent_pre_outcome_review`. The catalog +decisions from the same sweep are in +[`catalogs/us-equities/mover-v3-sweep-20260924.json`](../../../catalogs/us-equities/mover-v3-sweep-20260924.json). + +**Evidence class.** This plan rests on literature and metadata evidence, plus a few unpinned source reads, all from a +read-only SOTA sweep on 2026-09-24. It is not native execution. The sweep had five angles (literature, repositories, +data, exits and sizing, execution) and proposed 37 items. At least two independent verifiers checked each item: +**5 survive, 24 are contested and 8 were killed.** This plan uses only the surviving items, plus contested items whose +refutation the sweep's synthesis judged weak. Those are labelled *contested* wherever they are used. No market data +was fetched, no study code was run, and no paper order was placed. The sweep input (sha256 `e2120c05...64d1`) stays +private because its verifier notes contain host paths. This README and the supplement carry its content. + +**Relation to PR #162.** PR #162 (open, owner active) holds the mover early-entry v1 and v2 studies +(`blueprints/us-equities/mover-early-entry/**`). **v1: 0 of 768 rule-exits pass development. v2: no pass in families +E, F or P (commit `92062e8`).** v3 does not modify those files. It reuses their pipeline only by reference, pinned to +commit `92062e8`, and it does not read their reserved holdout. The 2021 control segment of the catalyst experiment is +permanently inspected and is not a fresh holdout. + +## Why continuation is not supported + +The v1 premise is that an extreme recent mover with high volume keeps rising early in its move, net of costs. The +evidence below points the other way. It is consistent with #162's result, not a sign of an engineering bug: + +| Evidence | What it says | Sweep status | +|---|---|---| +| Bali, Cakici, Whitelaw (2011), "Maxing Out", *JFE* 99(2) ([PDF](https://pages.stern.nyu.edu/~rwhitela/papers/max%20jfe.pdf); [NBER w14804](https://www.nber.org/system/files/working_papers/w14804/w14804.pdf)) | Stocks with the largest recent daily return (MAX) earn *lower* subsequent risk-adjusted returns: lottery demand, then reversal | **survives** (both PDFs opened) | +| Hong, Li, Ni, Scheinkman, Yan, "Days to Cover and Stock Returns" ([NBER w21166](https://www.nber.org/system/files/working_papers/w21166/w21166.pdf)) | High days-to-cover predicts *lower* returns. Squeezes are episodic, not a base rate | **survives** (opened) | +| Barber & Odean (2008), *RFS* 21(2) ([PDF](https://faculty.haas.berkeley.edu/odean/papers/Attention/All%20that%20Glitters.pdf)); Barber, Huang, Odean, Schwarz (2022), *JF* ([10.1111/jofi.13183](https://onlinelibrary.wiley.com/doi/abs/10.1111/jofi.13183)); Barber, Lin, Odean (2023), *JFQA* | Retail buying of extreme-return, high-volume names is well documented and not profitable for the buyer | *contested*: the refutation concerned a citation and the fit, not the direction. It is used only as a prior on retail P&L, not as a strategy backtest | +| Lou, Polk, Skouras (2019), "A Tug of War: Overnight versus Intraday Expected Returns", *JFE*; Akbas, Boehmer, Jiang, Koch (2022), *JFE* | Overnight and intraday legs behave asymmetrically, so a flat multi-session hold mixes two opposing effects | *contested*: the original citation pointed at a secondary summary. The corrected primaries still need a direct read (DOIs unverified). Evidence comes from large, liquid names | +| Martineau (2022), *Critical Finance Review* 11:613-646, and the 2025 revival papers | Whether PEAD survives costs is unsettled | *contested*: deferred, not a v3 hypothesis | + +Repository evidence points the same way. #162's v1 has **no pass among 768 rule-exits**. All 704 with at least 200 +development trades have a negative mean net return. v2 has no pass across its 244 rule-exits (216 E, 16 F, 12 P). +The broad-universe study (`blueprints/us-equities/broad-universe/README.md`) reports that its flags "find +*volatility*, not direction". + +The literature is monthly, cross-sectional and mostly large-cap. Whether it carries over to catalyst-conditioned +movers priced at $1 to $50 is what v3 tests; it is not an assumed result. + +## Hypotheses H1-H6 + +v3 tests the effect's **sign and conditioning**, not continuation alone. `protocol-draft.json` gives the exact arms, +primary tests, minimum samples and windows. Two populations are common to H1-H5, and both reuse #162's definitions +from `rules.py` by reference: + +- **D (daily lane):** a split-consistent close-to-close gain of at least +20%, a price of at least $1 and a + completed-session dollar volume of at least $1M. The decision is at 16:15 ET, and the entry is the first eligible + quote at or after 09:35 ET on the next session. +- **I (intraday lane):** the first minute from 09:35 to 10:30 ET at which `gain_at_t >= 0.20` and + `dollar_volume_at_t >= $1M`, with `price_at_t >= $1`. The entry is the first eligible quote at or after + decision + 60 s. + +The sweep's phrase "v1 lanes" mixes two protocols: the catalyst experiment's daily and intraday lanes, and #162's ++20% grid. v3 settles this with D and I above. G = 0.20 and V = $1M are v1's loosest gain and middle volume floor, +chosen for sample size. They were chosen after v1's dev/val results were seen, in which every cell is negative; the +exposure registry records this. + +| H | Question | Entry | Exit arms | Sizing | Falsified if | Data | +|---|---|---|---|---|---|---| +| H1 | Does MAX (lottery) conditioning flip the sign? | D and I, with trailing MAX(21 sessions) terciles within each decision date as a covariate; no new thresholds | (a) exit at min(entry + 30 min, 15:55); (b) the lane horizon (D: 15:55 on the 5th session; I: entry + 60 min); (c) same-session 15:55 | 1x, equal notional, capacity-capped | high-MAX minus low-MAX cost-net mean for arm (b) is not negative at the corrected level; or no arm-by-tercile cell passes. A high-MAX outperformance rejects the reversal prior | **available now** (Alpaca SIP bars) | +| H2 | Do catalyst-verified moves beat pure attention spikes? | D and I, split by an 8-K Item 1.01 accepted before the decision (the existing `catalyst.py` contract) vs no qualifying filing | the lane horizon only | 1x | catalyst minus attention-only is not above 0 at the corrected level, or both groups fail | **available now** (SEC EDGAR, 10 req/s; SIP bars) | +| H3 | Do the overnight and intraday legs differ? | D | (a) intraday only: enter at 09:35, exit at 15:55 the same session; (b) overnight only: enter at 15:55, exit at the first eligible quote after the next 09:30 open; (c) the 5-session hold split ex post into its legs (a diagnostic, not traded) | 1x. Arm (b) stays at 1x in any later paper use until the 2x rung and its overnight cap are clean | the legs do not differ at the corrected level, or neither traded arm passes | **available now** | +| H4 | Does a halt-conditional exit beat ignoring the halt? | No new entry. Applies to every H1-H3 traded-arm trade that meets an LULD limit state or pause (not only passing arms, so that a no-pass outcome still leaves a sample) | (a) exit at the reopening print or the first post-reopen quote; (b) hold to the arm's horizon. No fills during a halt. Pause length is logged as a covariate, not a signal | inherits the parent arm | the (a) minus (b) difference fails the corrected level, or fewer than the minimum halted trades occur (then the halt state stays a safety rule only) | **gated** (Databento XNAS.ITCH status schema, paid; Nasdaq Trader history as a short pilot) | +| H5 | Does days-to-cover crowding filter movers? | D split by days-to-cover tercile (short interest / ADV), using the latest FINRA figure published before the decision, with its settlement lag | the lane horizon | 1x; a filter, never a size input | primary: high minus low DTC is not negative. Secondary squeeze test: the high-DTC tercile does not beat the others | **gated** (FINRA short-interest access and terms) | +| H6 | Engineering, not alpha: do broker-held exits match the client-side ratchet at fewer calls? | positions of the paper candidate, assigned to an arm by hash; no extra orders | (a) the current `exits.py` chain with cancel-then-new; (b) an Alpaca `trailing_stop` (trail_percent from `effective_trailing_bps` at entry) plus an OCO for the stop and take-profit legs. Time, risk-off, force and halt exits stay client-side | parent sizing, 1x | median exit slippage vs the same trigger reference differs by more than 25 bps (TOST); or paper partial-fill or trigger behaviour departs from the docs; or arm (b) does not use at least 2x fewer REST calls per closed trade | **available now** (Alpaca paper), after an adapter change | + +H1 rests on BCW 2011 (**survives**) and the relative-volume/price-return ranking in +`catalyst-experiment/protocol.json`, which is structurally a MAX-like selection. H2 rests on the Barber/Odean line +(*contested*). H3 rests on LPS 2019 and ABJK 2022 (*contested*). A VWAP-loss or ladder arm, from MarĂ³y's SSRN 5095349 +(posted 2025-01-12, returned HTTP 403, never read), is **excluded from the confirmatory family**. It can be added only +by an amendment made before the freeze. + +H4's mechanics come from the SEC DERA LULD papers +([1](https://www.sec.gov/files/dera-luld-white-paper.pdf), +[2](https://www.sec.gov/files/marketstructure/research/dera_wp_luld_and_extraordinary_transitory_volatility.pdf); +*contested*). The execution-state idea survives. The claimed "pause duration predicts post-reopen volatility" is a +statement from DERA's literature review, not a DERA finding. The rest comes from Nasdaq's LULD material (a 15 s limit +state, a 5-minute pause, and pauses in the last 10 minutes folding into the close; opened by the reviewer, *contested*) +and the [Nasdaq Trader halt pages](https://www.nasdaqtrader.com/trader.aspx?id=TradeHalts) (**survives**; a web/RSS +interface with a limited lookback, not a bulk archive). + +H5 rests on Hong et al. (**survives**). H6 rests on +[orders-at-alpaca](https://docs.alpaca.markets/docs/orders-at-alpaca). `trailing_stop` **survives**. The bracket +proposal was *contested* and corrected to OCO: a bracket's take-profit closes the whole position, so it cannot +express `take_profit_fraction` < 1. + +## Data plan + +| Need | Source | Status now | Gate | +|---|---|---|---| +| PIT daily and minute SIP bars, official prices, corporate actions (H1-H3, H6) | Alpaca market data (SIP), already entitled; used by `broad-universe` and `extreme-gainer-audit` | **available** (daily bars from 2016-01-04, minute bars from 2016-01-01) | private hashed snapshots. Before 2020, survivorship is limited (`broad-universe/README.md`), so results for 2017-2019 carry that label | +| 8-K Item 1.01 acceptance times (H2) | SEC EDGAR through the catalyst-provenance and catalyst-dataset recipes | **available** | acceptance must precede the decision, with zero late-source leakage | +| Shares outstanding and public float (screen only) | [SEC XBRL frames](https://www.sec.gov/search-filings/edgar-application-programming-interfaces) | **available** as a screen (*contested*: one misattributed source corrected) | frames returns the latest filed fact with no accession, so a name that clears the screen must be confirmed from companyfacts' dated per-accession fact | +| Historical halt, LULD and status events (H4, halt fidelity in simulation) | Databento XNAS.ITCH normalized status schema (`databento/dbn`, Apache-2.0), cross-checked against the Nasdaq Trader halt history | **gated on paid data the user must buy** (Databento is metered). A free short-window pilot is possible | get a `Historical.metadata.get_cost` quote for one symbol-year; confirm the coverage start and the licence. ITCH covers Nasdaq-listed names only | +| Survivorship-free delisted history before 2020 | Norgate Data US Platinum ([prices](https://norgatedata.com/prices.php)) vs the in-repo SEC Form-25 proxy | **gated on paid data the user must buy** | read the EULA's derived-data terms and what happens when the subscription lapses. Compare only | +| Short interest (H5) | FINRA consolidated short interest (`blueprints/us-equities/acceptance-wave/factors-regimes.md`) | **gated** on access and terms the user must establish | model the publication and settlement lag; a bi-monthly overlay only | +| Order latency and fill agreement | Alpaca paper `trade_updates` timestamps plus `x-ratelimit-*` headers | **available** (paper) | probe orders come out of the 20-call management reserve and are labelled as measurement | +| Earnings surprise (deferred lane) | Benzinga via Alpaca news, or a licensed calendar with revision history | gated on paid data; **not a v3 priority** | PEAD after costs is contested | + +## Automatic exits and leverage + +**Exits are deterministic and preregistered, and each sits at the cheapest correct layer.** + +1. **Broker-held legs handle price triggers where the semantics fit.** For a position already held, an OCO carries + the stop-loss and take-profit legs. A bracket is used only when both legs close the whole position. An Alpaca + `trailing_stop` with `trail_percent` replaces the client-side cancel-and-resubmit ratchet. Its percentage is sized + from `effective_trailing_bps` at entry and re-armed only on a fixed schedule. A partial take-profit becomes + separate tranches, each with its own OCO. Paper-fill semantics for these order classes are undocumented, and H6 + measures them before anything relies on them. +2. **Client-side rules keep everything that depends on state**, in the existing precedence: force_exit, risk_off, + halt state, quote_stale, price rules, time_exit. Gap risk stays in `native_strategy.evaluate_gap_risk`. A + client-side exit first cancels that position's broker-held legs. Cancel-then-new stays the default because of the + PATCH-replace fill race already recorded in `blueprints/us-equities/order-contract/README.md`. +3. **The halt state machine runs halted, then reopening auction, then post-auction.** No fills are simulated through + a halt. Exits are priced at the auction or the first post-reopen print. Only halt-driven exits are exempt from the + quote-staleness and spread admission checks, never entries. A resting stop on a halted name fills through the gap + at the first print, not at its stop price. +4. **Kill switch and step-down.** One `DELETE /v2/orders` call cancels every open order + ([reference](https://docs.alpaca.markets/reference/deleteallorders-1); **survives**). HTTP 207 returns a result + for each order, and failed entries are reconciled one by one against the durable ledger. Positions are then + flattened with closing orders. Today `native_adapter.py` cancels in a loop, one call per order (line 391). The + close-all-positions endpoint was not verified in this sweep. + +**Automatic leverage is a ceiling, never a target.** The effective ceiling for new entries is the minimum of: + +- `max_leverage` (at most 4); +- the session x regime cell; +- the drawdown-ladder step (`leverage.py`: 4x at 0 drawdown, 2x at 0.25, 1x at 0.5, 0x at 0.75); +- the overnight cap (`OVERNIGHT_MAX` = 2, `INTRADAY_MAX` = 4); +- the kill switch; +- the account multiplier proven at preflight. + +This is the `leverage-schedule-v1-20260922` contract in `adaptive-paper/README-safety.md`. The hard caps in +`safety.py` (`max_order_notional_usd`, `max_gross_exposure_usd`) apply whatever the policy says. + +Size = inverse-vol weight x liquidity cap x edge shrink, clipped to that ceiling: + +- **Liquidity cap:** each order is at most a preregistered fraction of trailing ADV and of recent minute volume. The + sweep found no ADV term in `strategies_v1.py`. +- **Edge shrink:** 0 until a v3 rule passes its untouched holdout. After that it becomes fractional Kelly, at most + half, with estimation-error shrinkage (MacLean, Thorp, Ziemba 2010, *Quantitative Finance* 10(7):681-687; + *contested*: citation only, full text not opened). `cvxgrp/kelly_code` stays `discovery_only` because it is GPL-3.0 + and has not been pushed since 2020. + +**Promotion is automatic only inside the gates.** A strategy runs at 1x until it passes. It moves up one rung at a time +(1x, then 2x, then 4x), and only after the rung below completes with `needs_attention == 0` in its row of +[`catalogs/us-equities/gates-20260922.json`](../../../catalogs/us-equities/gates-20260922.json) +(`leverage-ladder-1x`, `-2x` and `-4x`, all `not_established` today). Any of these steps a strategy down one rung +automatically: + +- any `needs_attention`; +- any `seconds_above_next_lower_rung_ceiling` breach (`runner.py`); +- a preflight multiplier below the request. + +The ladder qualifies the engine, not a strategy. Leverage stays paper-only, and live trading is out of scope. The +FINRA 4210 intraday-margin change is already tracked in `catalogs/us-equities/convergence-program/trading.md` and is +not re-added. + +## High-rate execution design + +**Throughput is capacity, not a trading target.** `adaptive-paper/README.md` (line 40): "No strategy manufactures +trades to hit a throughput target." For movers, the binding limits are candidate count, liquidity and per-share cost, +not API calls. No design below may add orders to use spare budget. + +The execution path: + +- **No polling.** Fills and state changes arrive on the `trade_updates` stream. REST reads are for reconciliation + only, about 3 per minute (account, positions and open orders). +- **Rate limiting.** A client token bucket is smoothed to about 3 actions/s on the 200 tier and about 15/s on the + 1000 tier. Nautilus `LiveRiskEngineConfig(max_order_submit_rate, max_order_modify_rate)` matches it; today that is + `180/00:01:00` (`native_adapter.py` lines 462 and 486-487). The client honours `x-ratelimit-remaining`, + `x-ratelimit-reset` and `retry-after`, and backs off on 429. +- **Tier selection.** The higher tier is used only when preflight sees `x-ratelimit-limit` > 200 on the account's + real endpoint. Paper reports 200 today. Other clients on the account share the quota. + +**Cost per API action** (sources: [usage limit](https://alpaca.markets/support/usage-limit-api-calls), the +[staff note](https://forum.alpaca.markets/t/what-constitutes-an-api-call/16502/3) cited in `README-transport.md`, and +[cancel-all](https://docs.alpaca.markets/reference/deleteallorders-1)): + +| Action | Calls | +|---|---| +| submit (any order class, including a bracket that creates entry plus two legs) | 1 | +| single cancel | 1 | +| `DELETE /v2/orders` cancel-all, whatever the number of open orders | 1 | +| PATCH replace (not adopted: fill race) | 1 | +| each REST read (account, positions, orders) | 1 | +| broker-held OCO/trailing/bracket leg that later fills or is cancelled by the broker | 0 | + +**Completed round trips per minute.** *f* is the entry fill rate. The 200 tier keeps 20 calls for management, leaving +180 order actions. The 1000 tier keeps about 100, leaving 900. + +| Pattern | Calls per completed round trip | 200 tier, f = 1 | 200 tier, f = 0.5 | 1000 tier, f = 1 | 1000 tier, f = 0.5 | +|---|---|---|---|---|---| +| DAY limit entry, limit exit, cancel unfilled entries | 2 / f | 90 | 45 | 450 | 225 | +| IOC marketable-limit entry, limit exit (IOC unverified on the adapter) | 1 / f + 1 | 90 | 60 | 450 | 300 | +| bracket (entry plus OCO legs in one POST), legs never changed, cancel unfilled parents | (2 - f) / f | 180 | 60 | 900 | 300 | +| entry, then one `trailing_stop` after the fill | 2 / f | 90 | 45 | 450 | 225 | +| entry plus the current client ratchet, r ratchet steps (r = 3 shown) | 2 / f + 2r | 22 | 18 | 112 | 90 | + +The sweep's synthesis gave "2 + (1 - f) / f, about 60 round trips/min at f = 0.5" for the plain limit pattern. That +counts the cancels but leaves out the extra *submits* of unfilled entries. Each attempt costs one submit plus either +a cancel or an exit, so a completed round trip costs 2 / f calls. That is 45 per minute at f = 0.5, not 60. +The 60 belongs to the IOC pattern, where an unfilled entry needs no cancel. + +**What cannot be reached.** 1000 round trips per minute on one account needs at least 2000 plain-order calls, or 1000 +bracket calls with no cancels, no reads and a 100% fill rate. Both exceed a 1000/min budget. Even 1000 *order +actions* per minute is out of reach once any read or cancel happens. + +The **cost check:** 900 actions/min x 100 shares x $0.004/share is $360 a minute, about $140k per 390-minute session if +sustained. Cost and liquidity rule out sustained high rates long before the call budget does. + +There are two 1000/min paths, compared but not adopted (*contested* only on decision value): + +- [Non-retail](https://alpaca.markets/support/increase-api-rate-limit): 1000/min at $0.004/share, giving up wholesale + routing. +- [Elite](https://alpaca.markets/elite): 1000/min with a $30k deposit, priced at $0.0040 All-in or $0.0025 and lower + (Cost Plus). + +The Elite DMA gateway is excluded (see Dropped). + +**v3 adapter changes this design needs, none of them made here:** + +- OCO, bracket and `trailing_stop` order paths in `native_adapter.py` (which builds only LIMIT/DAY orders today); +- batch cancel-all on step-down; +- IOC entries, once IOC is verified; +- an audit of fail-closed handling across *both* vocabularies. The `trade_updates` event names (`calculated`, + `suspended`, `stopped`, `done_for_day`, `order_replace_rejected`, ...) differ from the order `status` field that + `transport.py`'s `TERMINAL` set checks. + +## Simulation fidelity + +- **Engine.** NautilusTrader 2.0.0rc5 backtest (the selected destination) with its native fill and latency models. + The pinned review of `fill-models.md` and `reconciliation.md` at `27a8e54` is in + `catalogs/us-equities/architecture/trading.json`. +- **Rate limits in simulation.** The same `LiveRiskEngine` throttle runs, at 180/min (or 900/min for the higher-tier + scenario). Every submit, cancel, replace and read uses a simulated token. Going over budget returns a simulated 429 + and a `retry-after` delay, so queueing shows up as worse entry prices. +- **Fills on minute SIP bars, with NBBO where entitled:** + - a passive limit fills only when the bar trades *through* it, with a queue haircut; + - a marketable order fills at the far touch plus participation-scaled impact, with participation capped; + anything over the cap fills partially; + - there are no fills during halts. Reopens use the auction print, and stops fill through the gap; + - broker-held legs are simulated with their documented cancel-the-other-leg semantics; + - the LEAN execution-realism grid (0, 5 and 20 bps plus $1 per order) is the minimum cost-scenario set. +- **Latency.** Calibration follows hftbacktest's documented protocol + ([Order Latency Data](https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html); + *contested* only as a new-row proposal, and kept as a note on the existing row). Submit, ack and fill/cancel + latencies are measured from `trade_updates` during normal paper operation. At most 2 unfillable far-from-mid probes + per minute may be added from the management reserve, labelled as measurement and never as trades. Whether probes + comply with the no-manufactured-trades rule needs an explicit decision (see Open questions). hftbacktest itself + (MIT, catalogued at `5f3ec40`, no push since 2025-12-23) stays a comparison harness on a tick subset only. +- **Sim-to-paper agreement.** The same decisions are replayed in simulation and on paper, with preregistered + thresholds for: + - fill rate per order type (binomial CI); + - slippage vs arrival mid (median and p90, paper minus sim); + - latency (two-sample KS); + - an exit-reason confusion matrix; + - P&L tracking error; + - 429 and throttle-delay counts. + + Size starts minimal, and the fill model widens only while agreement holds. **Alpaca paper fills are themselves + simulated** and do not simulate `advanced_instructions`. This measures sim-to-paper agreement, not sim-to-market + agreement. A high-rate capacity replay is an infrastructure test, never strategy evidence. + +## Catalog decisions + +The sweep's catalog actions are dated records in +[`catalogs/us-equities/mover-v3-sweep-20260924.json`](../../../catalogs/us-equities/mover-v3-sweep-20260924.json): + +- **Registered with the decision index** (`python3 scripts/catalog_decisions.py --write --supplement + catalogs/us-equities/mover-v3-sweep-20260924.json#/repository_decisions`): the records that name a GitHub + repository (`alpacahq/alpaca-py`, `databento/dbn`, `nkaz001/hftbacktest`, `cvxgrp/kelly_code`, + `microsoft/RD-Agent`, `microsoft/qlib`, `polakowo/vectorbt`). Every one of these repositories was already in the + union, so the repository count is unchanged. +- **Kept as data in the same file** (`/non_repository_decisions`): papers, broker APIs, SEC/Nasdaq pages and data + vendors. The union accepts only repository records. + +Inclusion never upgrades evidence. Each record carries its sweep status and its evidence level: literature source +review, metadata, or an unpinned source read. None records native execution. + +## Dropped + +Killed by verification: + +| Item | Reason | +|---|---| +| intraday-volume-imbalance-cross-section | primary source misattributed (Heston, Korajczyk, Sadka is about return periodicity); the U-shape evidence is from index futures | +| fda-catalyst-timing-profile | journal misreported; effects are small on average. A possible future lane, not v3 | +| nautilus-halt-status-model (as a v3 candidate) | background only; halts remain a data-provider responsibility | +| event-study-tooling-gap | the search excluded notebook repositories, so the claimed negative result is unreliable | +| backtrader-family-closed | duplicates the catalog | +| p3-atr-chandelier-stop | `volatility_bps` is not an ATR approximation, and the catalog-status claim was false | +| p8-finra-4210 flag | already catalogued with stronger evidence (`convergence-program/trading.md`) | +| replace-order-patch-semantics | already in `order-contract/README.md` | + +Contested items not carried forward. For each, the synthesis judged the refutation strong, or the item was folded +into another: + +| Item | Reason | +|---|---| +| p4-massive-flat-files | the repository does not scan one symbol at a time, so it has no decision value | +| p5-iex-hist | a single venue; not an execution-design input | +| dma-gateway-elite-only | Elite-only and cannot be simulated in paper, so not actionable | +| trade-updates-full-event-taxonomy (as stated) | conflated event names with status values; replaced by the fail-closed audit above | +| order-action-budget-arithmetic (as a catalog artifact) | a duplicate; its arithmetic (corrected) is used directly in the table above | +| pead-cost-adjusted-contested (as a v3 hypothesis) | wrong journal cited (pii S0148619524000584 is the *Journal of Economics and Business*); the evidence is unsettled | +| p6-oto-single-leg-order | folded into the OCO/trailing path; the savings are marginal | +| qullamaggie-breakout-scanner (as a new entry) | duplicates the catalogued `tradermonty/claude-trading-skills` vcp-screener; recorded as no-change | + +## Open questions + +- Do Alpaca paper fills for `trailing_stop`, OCO and bracket orders (partials, trigger price, trigger reference) match + the docs? H6 depends on it. +- Does paper `x-ratelimit-limit` ever show an upgraded tier, and can a higher tier be qualified in paper at all? +- Are IOC and FOK supported on the Nautilus/`native_adapter` path, and are IOC semantics in Alpaca paper documented? +- Can SIP trade and quote conditions rebuild historical halts and LULD states, or is XNAS.ITCH (Nasdaq only) plus an + NYSE feed required? What are the cost and the coverage start? +- Should v3 add a short side for the MAX-reversal prior? Borrow, hard-to-borrow fees and SEC Rule 201 were not + examined. +- Do latency probes (unfillable, then cancelled) comply with the engine's no-manufactured-trades rule? This needs an + explicit decision. +- What are the terms of FINRA short-interest access and of the Norgate EULA on derived data? +- The citations that need a direct read before exact wording is cited: the LPS 2019 and ABJK 2022 DOIs, SSRN 5095349 + and the full MacLean-Thorp-Ziemba text. + +## Before any freeze + +`protocol-draft.json` lists the preconditions (`freeze_preconditions`). The main ones: + +- an independent pre-outcome review, from a different session and model family, with every finding resolved in a + dated record; +- the direct citation reads above, or removal of the claims that depend on them; +- a development-only cost re-measure for 2017-2019 and fee rates dated for 2017-2020; +- metadata-only coverage checks for H4 and H5; +- study code committed with synthetic tests before any outcome is computed. + +Until all of that is done, this is a plan, and no window it names may be read for outcomes. diff --git a/blueprints/us-equities/mover-v3/protocol-draft.json b/blueprints/us-equities/mover-v3/protocol-draft.json new file mode 100644 index 000000000..996b97e74 --- /dev/null +++ b/blueprints/us-equities/mover-v3/protocol-draft.json @@ -0,0 +1,300 @@ +{ + "schema_version": 1, + "id": "mover-v3-draft-20260924", + "kind": "draft_preregistration", + "status": "draft_pending_independent_pre_outcome_review", + "frozen_before_outcomes": false, + "drafted_at": "2026-09-24", + "base_commit": "4a4c8a285630412ecfa8715c290a1e049ed03cb2", + "plan": "blueprints/us-equities/mover-v3/README.md", + "catalog_decisions": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "evidence_class": "Literature and metadata evidence, plus unpinned source reads, from a read-only sweep on 2026-09-24 (5 angles, 37 items; 5 survive, 24 contested, 8 killed). No native execution, no market data fetched and no outcome computed. Not a frozen protocol: no window named here may be read for outcomes until status is frozen under freeze_preconditions.", + "sweep_input": { + "sha256": "e2120c0578dd02c9784c0abcd0af89e5e61d3dfb004a9bed55d278891b8764d1", + "counts": {"survives": 5, "contested": 24, "killed": 8}, + "publication": "private scratch input, not committed: its verifier notes contain host paths. The README and the catalog supplement carry its content." + }, + "relation_to_prior_protocols": { + "mover-early-entry-v1-20260924": "PR #162 (open). 0 of 768 rule-exits pass development; the holdout was not read. Reused by reference only, pinned to commit 92062e8: rules.py definitions (ref, price_at_t, dollar_volume_at_t, gain_at_t, official prices, split factor), quotes.py cost-sample method and evidence/cost-table-run-v1.json. v3 never modifies blueprints/us-equities/mover-early-entry/**.", + "mover-followup-v2-20260924": "PR #162, commit 92062e8: no pass in families E (216), F (16) or P (12); no holdout read. Not reused.", + "catalyst-experiment-v1-20260920": "Its lane timing (16:15 decision, 09:35 entry, +60 s minimum latency, 5-session and 60-minute horizons) and the catalyst.py Item 1.01 contract are reused by reference. Its 2021Q1 ETF control is permanently inspected and is never a holdout.", + "broad-universe-mover-v1-20260921": "Different population (close >= $5, median dollar volume >= $20M). Its evaluated segments are named in exposure_registry." + }, + "question": "Among US-equity extreme movers, which conditioning (MAX lottery demand, catalyst versus attention, overnight versus intraday leg, halt state, days-to-cover crowding) changes the sign or size of the cost-net forward return, and can broker-held exits match client-side exits at fewer API calls? Continuation is one tested outcome, not the premise.", + "direction": "long only (a short side for the MAX-reversal prior is an open question; borrow, hard-to-borrow fees and SEC Rule 201 were not examined)", + "exposure_registry": { + "already_looked_at": [ + {"window": ["2021-01-04", "2023-12-29"], "what": "mover-early-entry v1 and v2 development outcomes (1,012 rule-exits) on the +20% candidate universe; the v1 cost table's quote sample was drawn from these development sessions"}, + {"window": ["2024-01-02", "2025-12-31"], "what": "mover-early-entry v1 and v2 validation outcomes"}, + {"window": ["2026-01-02", "2026-09-18"], "what": "the reserved holdout of mover-early-entry v1/v2, unread by #162 and owned by it. It is also exposed at event level: extreme-gainer-audit computed T+1/T+5/T+20 forward returns for 957 package events from 2021 to September 2026. v3 excludes this window."}, + {"window": ["2017-01-01", "2026-08-14"], "what": "broad-universe-mover-v1: development 2017-2021, validation 2022-2023 and reserved 2024-01-01 to 2026-08-14 were evaluated for daily signals on the close >= $5, median dollar volume >= $20M universe, including +20% one-day labels. Only liquid names overlap with the v3 population."}, + {"window": ["2019-12-02", "2020-04-30"], "what": "historical-simulation: six LEAN margin-stress scenarios on bundled SPY history; no mover-population outcome"}, + {"window": ["2021-01-01", "2021-03-31"], "what": "catalyst-experiment 2021Q1 ETF control, permanently inspected"}, + {"window": "2020 and 2022 single-name samples", "what": "LEAN/AAPL 2020, FB/META 2022, SPY parity and corporate-action diagnostics; engine and data checks, not mover outcomes"} + ], + "draft_author_exposure": "Before this draft the author read: #162's README results and v1 dev/val summary aggregates (per-rule means; P5/P95 of the 07:00|G0.20|V1000000 rule-exits, used only for sigma planning below); the v1 cost-table cells; the #162 review findings; the broad-universe README headlines; the sweep. No 2017-2020 mover-population outcome, and no halt, short-interest or MAX statistic for any window, has been computed or seen.", + "update_rule": "Every later read of data in any v3 window, before the freeze, is appended here with its date and purpose. A read of outcomes in a v3 validation or holdout window before the freeze voids that window." + }, + "populations": { + "D_daily": "symbol-sessions t with split-consistent official-close gain close[t]/ref - 1 >= 0.20, official close >= $1.00 and completed regular-session dollar volume >= $1,000,000; decision at 16:15 ET on t; entry at the first eligible quote at or after 09:35 ET on t+1 and at or after decision + 60 s; entry timeout 300 s, else no entry (counted)", + "I_intraday": "the first minute m in 09:35-10:30 ET on a session at which gain_at_t >= 0.20, dollar_volume_at_t >= $1,000,000 and price_at_t >= $1.00 (rules.py definitions at 92062e8); entry at the first eligible quote at or after m + 60 s; timeout 60 s", + "exclusions": "OTC; warrants, units and rights by #162's regex; a known halt at decision time blocks entry (as catalyst-experiment); symbols with fewer than 21 prior sessions of bars", + "parameter_provenance": "G = 0.20 and V = $1M are v1's loosest gain and middle volume floor, fixed for sample size after v1's all-negative dev/val grid was seen (disclosed exposure). They are not tuned on any v3 window." + }, + "chronology": { + "warmup": ["2016-01-04", "2016-12-30"], + "development": ["2017-01-03", "2019-12-31"], + "validation": ["2020-01-02", "2020-12-31"], + "holdout": { + "kind": "prospective", + "window": "the first 252 full NYSE sessions strictly after the session in which the frozen protocol commit is pushed", + "extension": "if any holdout-eligible item has fewer than its minimum holdout trades, extend in 63-session blocks, at most 2, decided on trade counts alone with outcome labels uncomputed", + "why_prospective": "No retrospective window disjoint from v1/v2 remains: 2021-2025 is v1/v2 dev/val, 2026-01-02 to 2026-09-18 is #162's reserved holdout and is event-level exposed, and 2016 is warm-up." + }, + "labels": { + "development": "disjoint from v1/v2; overlaps broad-universe development for liquid names; survivorship-limited before 2020 (names delisted in 2016-2019 without a retained corporate action cannot be discovered)", + "validation": "disjoint from v1/v2; overlaps broad-universe development for liquid names; the 2020 COVID regime is reported separately and never dropped", + "holdout": "untouched by construction" + }, + "boundaries": "Segments are assigned by entry session. A trade whose exit crosses a segment end is censored and counted. Multi-session arms leave a 6-session gap at each boundary." + }, + "hypotheses": { + "H1": { + "name": "MAX/lottery conditioning of mover outcomes", + "evidence": [ + {"source": "Bali, Cakici, Whitelaw (2011) JFE 99(2), https://pages.stern.nyu.edu/~rwhitela/papers/max%20jfe.pdf; NBER w14804", "status": "survives"}, + {"source": "catalyst-experiment/protocol.json ranks by relative_volume then price_return, a MAX-like selection", "status": "repository source read"} + ], + "populations": ["D_daily", "I_intraday"], + "covariate": "MAX21 = the largest split-consistent daily close-to-close return over the 21 sessions ending t-1 (D) or the previous session (I), from data known at the decision; terciles within each decision date (ties by symbol)", + "exit_arms": { + "a_fast": "first eligible quote at or after min(entry + 30 min, 15:55 ET)", + "b_lane": "D: first eligible quote at or after 15:55 ET on the 5th holding session (entry session = 1); I: at or after min(entry + 60 min, 15:55)", + "c_session": "first eligible quote at or after 15:55 ET on the entry session" + }, + "stops": "none beyond the engine margin rule", + "sizing": "1x equal notional, capacity-capped (cost_model.notional)", + "primary_tests": [ + {"id": "H1-D", "statistic": "mean net(b_lane | high MAX tercile) - mean net(b_lane | low MAX tercile), population D", "alternative": "less than 0"}, + {"id": "H1-I", "statistic": "same, population I", "alternative": "less than 0"} + ], + "tradable_cells": "2 populations x 3 arms x 3 terciles = 18; each tested for mean net > 0", + "falsifier": "Falsified if H1-D and H1-I both fail the corrected level, or if no tradable cell passes development. A significant high-MAX outperformance rejects the reversal prior.", + "data_status": "available now (Alpaca SIP daily and minute bars, official prices)", + "chronology": "common (development 2017-2019, validation 2020, prospective holdout)" + }, + "H2": { + "name": "Catalyst-verified movers versus pure attention spikes", + "evidence": [ + {"source": "Barber & Odean (2008) RFS 21(2); Barber, Huang, Odean, Schwarz (2022) JF doi 10.1111/jofi.13183; Barber, Lin, Odean (2023) JFQA", "status": "contested: refutation concerned a citation and the fit, not the direction; used as a prior on retail P&L only"} + ], + "populations": ["D_daily", "I_intraday"], + "split": "catalyst: an 8-K with Item 1.01 whose EDGAR acceptance time precedes the decision and falls after the previous regular close (D) or within the prior 3 sessions (I), per catalyst.py; attention: no qualifying filing. As-known identities only; an unparsed filing is unknown, reported apart and excluded from both groups.", + "exit_arms": {"b_lane": "as H1"}, + "sizing": "1x equal notional, capacity-capped", + "primary_tests": [ + {"id": "H2-D", "statistic": "mean net(catalyst) - mean net(attention), population D", "alternative": "greater than 0"}, + {"id": "H2-I", "statistic": "same, population I", "alternative": "greater than 0"} + ], + "tradable_cells": "2 populations x 2 groups = 4", + "falsifier": "Falsified if H2-D and H2-I both fail the corrected level, or both groups fail development: the catalyst filter adds nothing beyond the volume filter.", + "data_status": "available now (SEC EDGAR, public domain, 10 req/s, existing recipes; Alpaca SIP)", + "chronology": "common" + }, + "H3": { + "name": "Overnight versus intraday legs of mover holds", + "evidence": [ + {"source": "Lou, Polk, Skouras (2019) 'A Tug of War: Overnight Versus Intraday Expected Returns', JFE; Akbas, Boehmer, Jiang, Koch (2022) JFE", "status": "contested: original citation was a secondary summary; substance not refuted; DOIs unverified; large, liquid names"}, + {"source": "Maroy, SSRN 5095349 (posted 2025-01-12), VWAP and ladder exits", "status": "contested: HTTP 403, not read; excluded from the confirmatory family"} + ], + "populations": ["D_daily"], + "exit_arms": { + "a_intraday": "enter at the first eligible quote at or after 09:35 on t+1; exit at or after 15:55 the same session", + "b_overnight": "enter at the first eligible quote at or after 15:55 on t+1; exit at the first eligible quote at or after 09:30 on t+2", + "c_decomposition": "diagnostic only, not traded: the 5-session b_lane hold split into overnight (prior 15:55 to next open) and intraday (open to 15:55) legs" + }, + "sizing": "1x; arm b stays at 1x in any later paper use until leverage-ladder-2x and its overnight cap are clean", + "primary_tests": [ + {"id": "H3-a", "statistic": "mean net(a_intraday)", "alternative": "greater than 0"}, + {"id": "H3-b", "statistic": "mean net(b_overnight)", "alternative": "greater than 0"}, + {"id": "H3-c", "statistic": "mean over trades of (sum of overnight leg returns - sum of intraday leg returns) within the 5-session hold, gross", "alternative": "not equal to 0"} + ], + "tradable_cells": "H3-a and H3-b are the tradable cells (counted once)", + "falsifier": "Falsified if H3-c fails the corrected level, or neither H3-a nor H3-b passes after costs.", + "data_status": "available now (Alpaca SIP minute bars with session open and prior close)", + "chronology": "common" + }, + "H4": { + "name": "Halt-conditional exit around LULD pauses", + "evidence": [ + {"source": "SEC DERA LULD white papers, https://www.sec.gov/files/dera-luld-white-paper.pdf and https://www.sec.gov/files/marketstructure/research/dera_wp_luld_and_extraordinary_transitory_volatility.pdf", "status": "contested: the execution-state idea survives; the pause-duration claim is a literature-review statement, not a DERA finding"}, + {"source": "Nasdaq 'All About LULDs' and the NasdaqTrader LULD FAQ (15 s limit state, 5-minute pause, last-10-minute pauses fold into the close)", "status": "contested: opened by the reviewer; band tiers must be checked against the current plan text before any hardcoding"}, + {"source": "https://www.nasdaqtrader.com/trader.aspx?id=TradeHalts plus halt search and history", "status": "survives: web/RSS, limited lookback"} + ], + "population": "every trade of the H1-H3 traded arms whose security enters an LULD limit state or trading pause between entry and exit. The sweep conditioned this on passing arms; v3 uses all traded-arm trades, so a no-pass outcome still leaves a sample.", + "exit_arms": { + "a_reopen": "exit at the reopening-auction print, else the first eligible post-reopen quote", + "b_ignore": "hold to the parent arm's exit, ignoring the halt" + }, + "rules": "No fills during a halt; a resting stop fills through the gap at the first print. Pause duration is logged as a covariate only.", + "sizing": "inherits the parent arm", + "primary_tests": [ + {"id": "H4", "statistic": "mean over halted trades of net(a_reopen) - net(b_ignore)", "alternative": "not equal to 0"} + ], + "minimum_halted_trades": {"development": 60, "validation": 40, "holdout": 40}, + "falsifier": "Falsified if H4 fails the corrected level or a stage has fewer than its minimum halted trades; the halt state then stays a safety rule only.", + "data_status": "gated: Databento XNAS.ITCH status schema (paid, metered; Nasdaq-listed only; cost and coverage start unverified), or Nasdaq Trader halt history (free, limited lookback, pilot only). Whether SIP conditions can rebuild halts is unverified.", + "chronology": "common, restricted to the halt source's covered listings and sessions as established by metadata before any outcome. A Nasdaq Trader pilot is exploratory and cannot pass. If the covered development span is shorter than 1 year, H4 is not tested and scores p = 1 in the family." + }, + "H5": { + "name": "Days-to-cover crowding overlay", + "evidence": [ + {"source": "Hong, Li, Ni, Scheinkman, Yan, 'Days to Cover and Stock Returns', NBER w21166, SSRN 10.2139/ssrn.2568768", "status": "survives: high DTC predicts lower returns"}, + {"source": "blueprints/us-equities/acceptance-wave/factors-regimes.md: FINRA consolidatedShortInterest is not a realtime squeeze signal", "status": "repository source read"} + ], + "population": "D_daily", + "covariate": "DTC = the most recent FINRA short interest published before the decision (publication and settlement lag respected) / 20-session median share volume ending t; terciles within each decision date", + "exit_arms": {"b_lane": "as H1"}, + "sizing": "1x; the overlay filters and never sizes", + "primary_tests": [ + {"id": "H5-p", "statistic": "mean net(high DTC) - mean net(low DTC)", "alternative": "less than 0"}, + {"id": "H5-s", "statistic": "mean net(high DTC) - mean net(middle and low DTC pooled)", "alternative": "greater than 0 (squeeze)"} + ], + "tradable_cells": "3 DTC terciles", + "falsifier": "H5-p is falsified if the difference is not negative at the corrected level; H5-s if the high-DTC tercile does not beat the rest.", + "data_status": "gated on FINRA short-interest access and terms, which the user must establish; bi-monthly, an overlay only", + "chronology": "common, restricted to sessions whose decision has a published figure. If published history does not cover at least 1 development year and the whole validation year, H5 is not tested and both tests score p = 1." + }, + "H6": { + "name": "Execution parity: broker-held exits versus client-side ratchet (engineering, not alpha)", + "evidence": [ + {"source": "https://docs.alpaca.markets/docs/orders-at-alpaca (trailing_stop with trail_price or trail_percent; OCO; bracket; OTO)", "status": "trailing_stop survives; bracket contested and corrected to OCO for a held position"}, + {"source": "https://docs.alpaca.markets/reference/deleteallorders-1", "status": "survives"}, + {"source": "native_adapter.py builds only LIMIT/DAY orders; native_strategy.replace_exit cancels and resubmits when the price basis moves", "status": "repository source read"} + ], + "population": "positions opened in Alpaca paper by the paper candidate (mechanics-only unless a v3 rule has passed its holdout), after the OCO and trailing_stop adapter paths exist. Each position's arm is the parity of the first byte of sha256(client_order_id). No order is created for the test itself.", + "exit_arms": { + "a_client": "the current exits.py chain with cancel-then-new replacement", + "b_broker": "trailing_stop with trail_percent = effective_trailing_bps at entry / 100, plus an OCO for the stop and take-profit legs; time, risk-off, force and halt exits stay client-side" + }, + "shadow": "for each position, the other arm's trigger time and reference price are computed from the same quote stream for paired diagnostics, never traded", + "primary_tests": [ + {"id": "H6-e", "statistic": "median of (realized exit price - trigger reference) in bps, arm b minus arm a", "alternative": "equivalence within +/-25 bps (TOST)"}, + {"id": "H6-c", "statistic": "log(mean REST calls per closed position, arm a / arm b)", "alternative": "greater than log(2)"} + ], + "also_falsified_by": "any paper partial-fill, trigger-price or trigger-reference behaviour that departs from the documented semantics (recorded per event)", + "sizing": "parent sizing at 1x", + "data_status": "available now (Alpaca paper) once the adapter change lands; paper fills are simulated, so this is sim-to-paper evidence, not market evidence", + "chronology": {"development": "first 40 closed positions per arm", "validation": "next 60 per arm", "holdout": "next 150 per arm", "note": "prospective paper only; disjoint from every historical window"} + } + }, + "multiple_testing": { + "family": "one family across H1-H6: the 10 non-tradable primary tests (H1-D, H1-I, H2-D, H2-I, H3-c, H4, H5-p, H5-s, H6-e, H6-c) plus the 27 tradable cells (H1 18, H2 4, H3 2, H5 3), m = 37", + "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout. An item not carried to a stage scores p = 1 there. Two-sided tests use 2 x min(one-sided p).", + "strictest_level": 0.0013514, + "development_screen": "unadjusted p <= 0.05 in the stated direction, with the minimum sample; development only nominates items for validation", + "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) on other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 37) = 4.77e-5. An item that passes Holm but not this level is labelled v3-family significant, lineage-unconfirmed.", + "tests_not_in_family": "BH (q = 0.10) diagnostics across tercile, year, price-tier and halt-covariate splits are reported but can never open the holdout" + }, + "minimum_sample": { + "tradable_cell": {"development": 300, "validation": 150, "holdout": 150}, + "difference_test_per_group": {"development": 150, "validation": 100, "holdout": 100}, + "rule": "A stage below its minimum reports the item as underpowered; it cannot pass that stage." + }, + "minimum_detectable_effect": { + "formula": "MDE = (z(1 - 0.05/37) + z(0.80)) x sigma x sqrt(DEFF / n) for one cell; x sqrt(2) for a two-group difference with n per group", + "z_sum": 3.8413, + "design_effect_DEFF": 1.5, + "sigma_planning": { + "30_to_60_minute_arms": 0.10, + "same_session_close_arms": 0.25, + "5_session_arms": 0.35 + }, + "sigma_provenance": "0.10 and 0.25 are (P95 - P5) / 3.29 from #162 v1 development aggregates (07:00|G0.20|V1000000 X2 and X1: P5 -15.0% / P95 +18.0%, and P5 -42.1% / P95 +39.5%); normal approximation, which understates fat tails. 0.35 is an assumption with no measured source. DEFF 1.5 for session clustering is an assumption.", + "mde_percent_single_cell": { + "n100": {"sigma_0.10": 4.70, "sigma_0.25": 11.76, "sigma_0.35": 16.47}, + "n200": {"sigma_0.10": 3.33, "sigma_0.25": 8.32, "sigma_0.35": 11.64}, + "n300": {"sigma_0.10": 2.72, "sigma_0.25": 6.79, "sigma_0.35": 9.51}, + "n600": {"sigma_0.10": 1.92, "sigma_0.25": 4.80, "sigma_0.35": 6.72} + }, + "mde_percent_two_group_difference": { + "n100": {"sigma_0.10": 6.65, "sigma_0.25": 16.63, "sigma_0.35": 23.29}, + "n200": {"sigma_0.10": 4.70, "sigma_0.25": 11.76, "sigma_0.35": 16.47}, + "n300": {"sigma_0.10": 3.84, "sigma_0.25": 9.60, "sigma_0.35": 13.44}, + "n600": {"sigma_0.10": 2.72, "sigma_0.25": 6.79, "sigma_0.35": 9.51} + }, + "h6_equivalence": "TOST margin 25 bps at the strictest family level (two one-sided tests at 0.05/37, a 1 - 2 x 0.00135 interval, z = 3.00); planning sigma of exit slippage 50 bps (assumption). At the 150-per-arm holdout the interval half-width is about 3.00 x 50 x sqrt(2/150) = 17 bps, so equivalence passes only if the observed difference is under about 8 bps; at 60 per arm the half-width (27 bps) exceeds the margin and no pass is possible", + "reading": "Only large effects can pass. A miss at these sizes is reported as underpowered for smaller effects, never as proof of no effect." + }, + "cost_model": { + "base_table": { + "path": "blueprints/us-equities/mover-early-entry/evidence/cost-table-run-v1.json", + "commit": "92062e8", + "git_blob": "197e71ada3c8249f2b49b168e4ce00a2cdf28628", + "sha256": "be50cbdfd75c5c4b0a66286bf9edb40c9ce49add719b6b5e1b0cdf93064eaa13", + "note": "the same sha256 is recorded as inputs.cost_table_sha256 in #162's evidence/summary-dev-val-run-v1.json. Reused by reference; v3 refuses a table with another hash." + }, + "fix_1_monotone_dollar_volume": { + "finding": "#162 review item costs-F1: cells under $1M dollar volume fall back to a time x price pool that costs about half the $1-5M cell (for example 3|2|0 0.24% vs 3|2|1 0.75%)", + "rule": "for each time x price bucket, hs'(>= $5M) = hs(>= $5M); hs'($1-5M) = max(hs($1-5M), hs'(>= $5M)); hs'(< $1M) = max(hs(< $1M), hs'($1-5M)). A less liquid tier is never cheaper than a more liquid one.", + "cells_changed_bps_before_after": { + "1|0|0": [34.29, 38.04], + "1|1|0": [27.97, 44.16], + "1|2|0": [31.27, 65.29], + "2|2|0": [31.62, 61.83], + "3|0|0": [40.01, 58.97], + "3|1|0": [27.04, 57.09], + "3|2|0": [24.09, 74.50] + }, + "cell_key": "time_bucket|price_tier|dv_tier; time buckets 04:00-08:00, 08:00-09:30, 09:30-10:00, 10:00-16:01 ET; price tiers < $2, $2-5, $5-20, >= $20; dv tiers < $1M, $1-5M, >= $5M" + }, + "fix_2_impact_and_capacity": { + "finding": "#162 review item costs-F4: pass statistics use a fixed $20k notional with no impact term and assume full fills", + "notional": "filled notional = min($20,000; 1% of the 20-session median dollar volume ending before the decision; 10% of the entry bar's dollar volume, or the opening auction's). The excess is not filled. A trade with filled notional under $1,000 is a no-fill and is counted.", + "impact": "impact per side (return) = c x sigma_d x sqrt(q), where q = filled notional / 20-session median dollar volume and sigma_d = 20-session standard deviation of daily log returns, both known at the decision. Primary c = 1.0; sensitivities c = 0.5 and 2.0.", + "provenance": "The square-root functional form and the c grid are design choices bracketing the unknown; the sweep supplies no source for them." + }, + "out_of_period_spreads": "The base table was measured on 2021-2023 quotes. Before the freeze, a 1-in-20 hash sample of development (2017-2019) entries is quoted with quotes.py's method at 92062e8; each cell's cost is then max(hs' from the base table, the 2017-2019 cell). Quotes are read at exit-agnostic timestamps only; no exit path is computed.", + "per_side": "1.25 x hs' + impact, for continuous and auction sides alike (v1's 0.5x auction discount is not reused)", + "fees": "#162 fees.json (sha256 fa1f468b80f78dc0c050948123116f577e38ae9f8c03f9fb360d11042a3fed3d) by reference, extended before the freeze with SEC Section 31 and FINRA TAF rates in force during 2017-2020 from primary sources; Alpaca commission 0; IBKR tiered as a broker variant", + "stress": "2 x (spread + slippage) with c = 2.0", + "break_even": "the cost multiple at which each cell's mean net return reaches 0 is reported" + }, + "statistics": { + "estimator": "trade-level mean net return; also median, 1%-winsorised mean, mean without the top 5 sessions and mean of per-session means", + "bootstrap": "circular session block bootstrap, B = 20,000; block length 5 sessions for same-session arms and 10 sessions for multi-session arms; difference tests resample sessions jointly for both groups; seed = the first 8 bytes of sha256(frozen protocol id), big-endian", + "p_value": "one-sided p = (1 + #{b : stat_b on the null side of 0}) / (B + 1)", + "robustness_required_for_a_pass": "a tradable cell's validation and holdout passes also need a positive winsorised mean, mean without the top 5 sessions and mean of per-session means", + "sensitivities": ["two-way (session, symbol) clustering", "without split-day and fallback-price rows", "2020 reported alone", "survivorship-limited label before 2020"] + }, + "holdout_gate": { + "opens_only_for": "items that passed validation: a primary test or tradable cell with Holm-adjusted validation p <= 0.05 in the 37-item family, the minimum validation sample and, for a tradable cell, the robustness means. At most 5 tradable cells (lowest validation p) and every validated primary test.", + "no_pass": "If nothing validates, the holdout is never labelled or read, and that is the result.", + "prospective_collection": "Holdout-window data are collected forward into a sealed private snapshot whose sha256 is committed per collection batch. No outcome label, forward return or exit is computed from it before the gate opens.", + "evaluator_refusals": [ + "protocol status is not frozen, or its sha256 differs from the frozen commit", + "the validation results file is missing or its sha256 differs from the one committed", + "the list of validated items is empty", + "a requested item is not in that list", + "the access log has no committed 'granted' entry for this run", + "a previous holdout run exists (the holdout is read once; failed or partial runs stay in the log and are never called untouched)" + ], + "access_log": { + "path": "blueprints/us-equities/mover-v3/holdout-access-log.jsonl", + "mode": "append-only JSON lines, committed and pushed before the read it authorizes", + "fields": ["utc", "actor_role", "protocol_id", "protocol_sha256", "code_revision", "validation_results_sha256", "requested_items", "purpose", "decision (granted or refused)", "snapshot_sha256", "rows_read", "results_sha256"], + "rule": "Any holdout read without a preceding committed granted entry voids the holdout for every item. Refusals are logged too. Collection batches are logged with purpose 'collect' and rows_read 0." + } + }, + "leverage_and_exits": "Research runs use 1x equal notional. Paper use of any passing rule follows the README's exit and leverage policy: broker-held OCO/trailing legs where the semantics fit, the leverage-schedule-v1-20260922 ceiling, and the leverage-ladder-1x/2x/4x gate rows in catalogs/us-equities/gates-20260922.json, one rung at a time with automatic step-down. The edge-shrink (fractional Kelly) term stays 0 until a holdout pass.", + "freeze_preconditions": [ + "An independent pre-outcome review of this draft from a different session and model family, with every finding resolved in a dated record", + "Direct reads of Lou-Polk-Skouras 2019, Akbas-Boehmer-Jiang-Koch 2022 and MacLean-Thorp-Ziemba 2010, or removal of every claim that depends on them", + "The 2017-2019 development cost sample fixed by rule and measured; fees extended to 2017-2020 from primary sources", + "Metadata-only coverage checks: the Databento get_cost quote and XNAS.ITCH coverage start (H4); FINRA short-interest history start and terms (H5). No outcome is computed.", + "Study code implementing this protocol committed with synthetic tests before any outcome, and the evaluator's holdout refusals tested", + "The exposure registry updated with everything the author saw since this draft", + "status set to frozen, frozen_before_outcomes true, and the protocol sha256 recorded in a pushed commit" + ], + "overturn": "Any headline result is overturned if a rerun on the same frozen inputs and runtime does not reproduce it byte for byte, if an independent quote sample puts the cost table off by more than 2x, or if the access log shows a holdout read that no committed granted entry preceded." +} diff --git a/catalogs/us-equities/README.md b/catalogs/us-equities/README.md index 5ddaf2116..7385a32eb 100644 --- a/catalogs/us-equities/README.md +++ b/catalogs/us-equities/README.md @@ -28,6 +28,11 @@ the reasons for retaining or comparing the CLI, MCP and simulation alternatives. Its bounded operational paper lane uses 120 requests/minute against the observed 200/minute account limit; Elite's advertised 1,000 is API calls, not fills. +The [September 24 mover v3 sweep](mover-v3-sweep-20260924.json) records the +catalog actions behind the [mover v3 research plan](../../blueprints/us-equities/mover-v3/README.md): +literature and metadata evidence only, with each record's sweep status. Its +repository records are registered in the decision index. + The [current token-practice audit](../../docs/token-practice.md) maps the then-selected 52 components to their evidence levels and records ten exact artifact comparisons plus four accepted native coding trials. Catalog inclusion does not supply a diff --git a/catalogs/us-equities/decision-index.json b/catalogs/us-equities/decision-index.json index 0bdbdc82c..3c1d4c2e2 100644 --- a/catalogs/us-equities/decision-index.json +++ b/catalogs/us-equities/decision-index.json @@ -332,6 +332,12 @@ "repository_field": "repository", "kind": "research_supplement" }, + { + "path": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "collection": "/repository_decisions", + "repository_field": "repository", + "kind": "research_supplement" + }, { "path": "catalogs/us-equities/security-identity-review.json", "collection": "/decisions", @@ -380,7 +386,7 @@ "repositories": 844, "public_star_repositories": 342, "beyond_public_stars": 502, - "references": 1961, + "references": 1968, "repositories_by_record_type": { "catalog_card": 148, "component_record": 69, @@ -1504,6 +1510,14 @@ "decision": "retain_prior_alias_evidence_without_refetch", "evidence_level": "prior_native_receipt_reuse" }, + { + "kind": "research_supplement", + "path": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "pointer": "/repository_decisions/0", + "id": "mover-v3-alpaca-py-order-classes", + "decision": "add_execution_method_candidate", + "evidence_level": "official_docs_metadata" + }, { "kind": "research_supplement", "path": "catalogs/us-equities/security-identity-review.json", @@ -5219,6 +5233,14 @@ "pointer": "/candidates/102", "decision": "discovery_only", "evidence_depth": "Attributed documentary discovery; no runtime execution or controlled comparative acceptance" + }, + { + "kind": "research_supplement", + "path": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "pointer": "/repository_decisions/3", + "id": "mover-v3-kelly-code-discovery-only", + "decision": "retain_discovery_only", + "evidence_level": "live_repository_metadata" } ] }, @@ -5572,6 +5594,14 @@ "pointer": "/entries/7", "decision": "add_conditional_exact_market_record_format", "evidence_level": "source_sections_reviewed" + }, + { + "kind": "research_supplement", + "path": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "pointer": "/repository_decisions/1", + "id": "mover-v3-dbn-status-schema", + "decision": "compare_status_schema_for_halt_reconstruction", + "evidence_level": "unpinned_source_read" } ] }, @@ -12373,6 +12403,14 @@ "decision": "conditional", "evidence_level": "source_review" }, + { + "kind": "research_supplement", + "path": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "pointer": "/repository_decisions/5", + "id": "mover-v3-qlib-recheck", + "decision": "no_change_existing_card_current", + "evidence_level": "live_repository_metadata" + }, { "kind": "research_supplement", "path": "catalogs/us-equities/security-identity-review.json", @@ -12396,6 +12434,14 @@ "pointer": "/candidates/248", "decision": "discovery_only", "evidence_depth": "Attributed documentary discovery; no runtime execution or controlled comparative acceptance" + }, + { + "kind": "research_supplement", + "path": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "pointer": "/repository_decisions/4", + "id": "mover-v3-rd-agent-recheck", + "decision": "no_change_existing_card_current", + "evidence_level": "live_repository_metadata" } ] }, @@ -13558,6 +13604,14 @@ "pointer": "/entries/6", "decision": "microstructure_reference_if_horizon_justifies_tick_data", "review_depth": "Selected implementation/documentation/test/license sections; source and metadata review only, no native tests or model runs." + }, + { + "kind": "research_supplement", + "path": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "pointer": "/repository_decisions/2", + "id": "mover-v3-hftbacktest-latency-protocol", + "decision": "no_change_add_latency_protocol_gate_note", + "evidence_level": "existing_record" } ] }, @@ -15651,6 +15705,14 @@ "id": "vectorbt", "decision": "alternative", "evidence_level": "source_review" + }, + { + "kind": "research_supplement", + "path": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "pointer": "/repository_decisions/6", + "id": "mover-v3-vectorbt-recheck", + "decision": "no_change_existing_card_current", + "evidence_level": "live_repository_metadata" } ] }, diff --git a/catalogs/us-equities/mover-v3-sweep-20260924.json b/catalogs/us-equities/mover-v3-sweep-20260924.json new file mode 100644 index 000000000..0d8a5571c --- /dev/null +++ b/catalogs/us-equities/mover-v3-sweep-20260924.json @@ -0,0 +1,315 @@ +{ + "schema_version": 1, + "kind": "mover_v3_sota_sweep_catalog_decisions", + "checked_at": "2026-09-24", + "baseline_commit": "4a4c8a285630412ecfa8715c290a1e049ed03cb2", + "plan": "blueprints/us-equities/mover-v3/README.md", + "protocol_draft": "blueprints/us-equities/mover-v3/protocol-draft.json", + "scope": "Catalog actions from a read-only SOTA sweep for the mover v3 research plan: 5 angles, 37 items, each checked by at least two independent verifiers (5 survive, 24 contested, 8 killed). Only surviving items and contested items whose refutation the synthesis judged weak are recorded; killed items are listed in the plan's Dropped section. No record here involves installation, native execution, a paper order or a market-data fetch. Inclusion never upgrades an evidence level.", + "sweep_input_sha256": "e2120c0578dd02c9784c0abcd0af89e5e61d3dfb004a9bed55d278891b8764d1", + "evidence_levels": { + "literature_source_review": "the primary paper was opened and read by a verifier; bibliographic and abstract-level claims only", + "literature_metadata": "the citation was located; the primary text was not read", + "official_docs_metadata": "an official vendor, broker, exchange or regulator page was read live on 2026-09-24", + "unpinned_source_read": "source files were fetched live without a pinned commit", + "live_repository_metadata": "a live GitHub API recheck of push date, licence and archive state", + "existing_record": "already recorded elsewhere in this repository with equal or stronger evidence" + }, + "registration": "Only /repository_decisions is registered with the decision index (python3 scripts/catalog_decisions.py --write --supplement catalogs/us-equities/mover-v3-sweep-20260924.json#/repository_decisions), because the union accepts only records that name a GitHub repository. /non_repository_decisions holds the papers, APIs, exchange pages and vendors as data.", + "repository_decisions": [ + { + "id": "mover-v3-alpaca-py-order-classes", + "repository": "https://github.com/alpacahq/alpaca-py", + "catalog_action": "add", + "decision": "add_execution_method_candidate", + "sweep_status": "trailing_stop and DELETE /v2/orders survive; bracket contested and corrected to OCO for a held position; OTO folded into the OCO/trailing path", + "evidence_level": "official_docs_metadata", + "native_execution": "none; Alpaca paper semantics for trailing_stop, OCO and bracket are undocumented and are what H6 measures", + "reason": "The SDK path to the broker-held exit order classes and single-call cancel-all that the v3 exit policy needs. native_adapter.py builds only LIMIT/DAY orders and cancels in a per-order loop today. A verifier named TradingClient.cancel_orders() as the SDK method for DELETE /v2/orders; that method was not source-read in this record.", + "links": ["mover-v3-alpaca-order-classes", "mover-v3-alpaca-cancel-all"], + "sources": [ + "https://docs.alpaca.markets/docs/orders-at-alpaca", + "https://docs.alpaca.markets/reference/deleteallorders-1" + ] + }, + { + "id": "mover-v3-dbn-status-schema", + "repository": "https://github.com/databento/dbn", + "catalog_action": "compare", + "decision": "compare_status_schema_for_halt_reconstruction", + "sweep_status": "contested: corrected from raw ITCH H/h/J messages to Databento's normalized status schema", + "evidence_level": "unpinned_source_read", + "native_execution": "none; the cost quote and XNAS.ITCH coverage start are unverified", + "reason": "Candidate source for historical halt and LULD state (H4 and simulation halt fidelity), closing the open item in evidence/artifacts/gap-wave2-20260923/us-equities__market-data-reference/README.md. ITCH covers Nasdaq-listed names only. The existing pinned record in catalogs/us-equities/architecture/trading.json is unchanged; access goes through databento/databento-python (already conditional), whose Historical.metadata.get_cost gives the quote.", + "sources": [ + "https://databento.com/docs/venues-and-datasets/xnas-itch", + "https://github.com/databento/dbn/blob/main/rust/dbn/src/record.rs", + "https://github.com/databento/dbn/blob/main/rust/dbn/src/enums.rs", + "https://www.nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/NQTVITCHSpecification.pdf" + ] + }, + { + "id": "mover-v3-hftbacktest-latency-protocol", + "repository": "https://github.com/nkaz001/hftbacktest", + "catalog_action": "no-change", + "decision": "no_change_add_latency_protocol_gate_note", + "sweep_status": "contested as a new candidate (duplicate); the documented latency-collection protocol was verified on the primary page", + "evidence_level": "existing_record", + "native_execution": "none", + "reason": "Keep the existing pinned review (5f3ec40, trading.json) and its refuted_keep_but_compare disposition. Add only a gate note: calibrate simulated latency from real paper order latencies (trade_updates timestamps, plus at most 2 unfillable probes a minute from the management reserve, labelled as measurement), not an invented constant. Live metadata 2026-09-24: MIT, not archived, last push 2025-12-23.", + "sources": [ + "https://hftbacktest.readthedocs.io/en/latest/tutorials/Order%20Latency%20Data.html" + ] + }, + { + "id": "mover-v3-kelly-code-discovery-only", + "repository": "https://github.com/cvxgrp/kelly_code", + "catalog_action": "no-change", + "decision": "retain_discovery_only", + "sweep_status": "contested: the fractional-Kelly overlay idea stands; the code is not adopted", + "evidence_level": "live_repository_metadata", + "native_execution": "none", + "reason": "The v3 sizing overlay (mover-v3-fractional-kelly) reimplements the idea. The repository stays discovery_only: GPL-3.0, last push 2020-10-02.", + "links": ["mover-v3-fractional-kelly"], + "sources": [ + "https://github.com/cvxgrp/kelly_code" + ] + }, + { + "id": "mover-v3-rd-agent-recheck", + "repository": "https://github.com/microsoft/RD-Agent", + "catalog_action": "no-change", + "decision": "no_change_existing_card_current", + "sweep_status": "contested as a new candidate: duplicates the existing targeted_candidate card", + "evidence_level": "live_repository_metadata", + "native_execution": "none", + "reason": "Live recheck 2026-09-24 matches the existing card: MIT, not archived, pushed 2026-09-23, release v1.0.0 on 2026-09-23. Any use needs a licensed US PIT dataset in Qlib format, and a final segment its own feedback loop never sees.", + "sources": [ + "https://github.com/microsoft/RD-Agent", + "https://github.com/microsoft/RD-Agent/releases/latest" + ] + }, + { + "id": "mover-v3-qlib-recheck", + "repository": "https://github.com/microsoft/qlib", + "catalog_action": "no-change", + "decision": "no_change_existing_card_current", + "sweep_status": "contested: duplicates the existing conditional card", + "evidence_level": "live_repository_metadata", + "native_execution": "none", + "reason": "Live recheck 2026-09-24: MIT, not archived, pushed 2026-09-22. The US-PIT adaptation gate stays open. The bundled examples default to China costs, limits and calendar.", + "sources": [ + "https://github.com/microsoft/qlib" + ] + }, + { + "id": "mover-v3-vectorbt-recheck", + "repository": "https://github.com/polakowo/vectorbt", + "catalog_action": "no-change", + "decision": "no_change_existing_card_current", + "sweep_status": "contested: duplicates the existing alternative card", + "evidence_level": "live_repository_metadata", + "native_execution": "none", + "reason": "Live recheck 2026-09-24: pushed 2026-09-17, not archived. The licence is Apache-2.0 with a Commons Clause; GitHub reports NOASSERTION. A pre-filter only, never the acceptance engine; any wide sweep stays inside the preregistered family and holdout discipline.", + "sources": [ + "https://github.com/polakowo/vectorbt" + ] + } + ], + "non_repository_decisions": [ + { + "id": "mover-v3-max-lottery", + "item": "Bali, Cakici, Whitelaw (2011) MAX/lottery effect, as a caution prior for mover selection (H1)", + "catalog_action": "add", + "sweep_status": "survives", + "evidence_level": "literature_source_review", + "native_execution": "none", + "reason": "Stocks selected on the largest recent daily return earn lower subsequent risk-adjusted returns. Monthly, cross-sectional evidence; carrying it over to catalyst-conditioned $1-50 movers is the H1 test.", + "sources": [ + "https://pages.stern.nyu.edu/~rwhitela/papers/max%20jfe.pdf", + "https://www.nber.org/system/files/working_papers/w14804/w14804.pdf" + ] + }, + { + "id": "mover-v3-overnight-intraday", + "item": "Lou, Polk, Skouras (2019) and Akbas, Boehmer, Jiang, Koch (2022), the overnight/intraday tug of war (H3)", + "catalog_action": "add", + "sweep_status": "contested: the original citation was a secondary Lachance (2023) summary; the substance was not refuted", + "evidence_level": "literature_metadata", + "native_execution": "none", + "reason": "The corrected primaries replace the secondary summary. Both still need a direct read (DOIs unverified). Evidence comes from large, liquid names.", + "sources": [ + "https://onlinelibrary.wiley.com/doi/full/10.1002/rfe.1180" + ] + }, + { + "id": "mover-v3-attention-buying", + "item": "Barber-Odean attention-buying line, downscoped to a prior on retail P&L (H2)", + "catalog_action": "add", + "sweep_status": "contested: the refutation concerned a citation and the fit, not the direction", + "evidence_level": "literature_source_review", + "native_execution": "none", + "reason": "Retail buying of extreme-return, high-volume names is documented and not profitable for the buyer. This is not a rules-based backtest.", + "sources": [ + "https://faculty.haas.berkeley.edu/odean/papers/Attention/All%20that%20Glitters.pdf", + "https://onlinelibrary.wiley.com/doi/abs/10.1111/jofi.13183", + "https://www.cambridge.org/core/journals/journal-of-financial-and-quantitative-analysis/article/resolving-a-paradox-retail-trades-positively-predict-returns-but-are-not-profitable/6AAA9078F50C2597F44D73FA6A8E3F0D" + ] + }, + { + "id": "mover-v3-days-to-cover", + "item": "Hong, Li, Ni, Scheinkman, Yan, days to cover (NBER w21166; SSRN 10.2139/ssrn.2568768), attached to the existing FINRA short-interest entry in blueprints/us-equities/acceptance-wave/factors-regimes.md (H5)", + "catalog_action": "add", + "sweep_status": "survives", + "evidence_level": "literature_source_review", + "native_execution": "none", + "reason": "Days to cover beats the raw short ratio, and high days to cover predicts lower returns. Squeezes are episodic. Bi-monthly publication makes it an overlay only.", + "sources": [ + "https://www.nber.org/system/files/working_papers/w21166/w21166.pdf" + ] + }, + { + "id": "mover-v3-alpaca-order-classes", + "item": "Alpaca trailing_stop, OCO, bracket and OTO order classes as execution methods", + "catalog_action": "add", + "sweep_status": "trailing_stop survives; bracket contested and corrected to OCO for a held position (a bracket's take-profit closes the whole position); OTO folded", + "evidence_level": "official_docs_metadata", + "native_execution": "none; native paper execution is missing (H6)", + "reason": "Broker-held legs replace the client-side cancel-and-resubmit ratchet and cost no further calls when they fill.", + "sources": [ + "https://docs.alpaca.markets/docs/orders-at-alpaca" + ] + }, + { + "id": "mover-v3-alpaca-cancel-all", + "item": "Alpaca DELETE /v2/orders batch cancel-all (HTTP 207 with a result for each order)", + "catalog_action": "add", + "sweep_status": "survives", + "evidence_level": "official_docs_metadata", + "native_execution": "none", + "reason": "One call whatever the number of open orders, against native_adapter.py's one-call-per-order loop (line 391). Partial per-order failures must be reconciled against the durable ledger before adoption.", + "sources": [ + "https://docs.alpaca.markets/reference/deleteallorders-1" + ] + }, + { + "id": "mover-v3-nasdaq-trader-halts", + "item": "Nasdaq Trader halt search, history and RSS pages", + "catalog_action": "add", + "sweep_status": "survives", + "evidence_level": "official_docs_metadata", + "native_execution": "none", + "reason": "Free current and recent halts with reason codes. A web/RSS interface with a limited lookback, not a bulk archive: a pilot and a cross-check, not the primary historical halt source.", + "sources": [ + "https://www.nasdaqtrader.com/trader.aspx?id=TradeHalts", + "https://www.nasdaqtrader.com/trader.aspx?id=TradingHaltHistory", + "https://www.nasdaqtrader.com/trader.aspx?id=tradinghaltsearch" + ] + }, + { + "id": "mover-v3-luld-mechanics", + "item": "LULD limit-state and reopening-auction execution states (SEC DERA papers; Nasdaq LULD material)", + "catalog_action": "add", + "sweep_status": "contested: the execution-state idea survives; 'pause duration predicts post-reopen volatility' is corrected to a statement from DERA's literature review", + "evidence_level": "official_docs_metadata", + "native_execution": "none", + "reason": "Halted, then reopening auction, then post-auction: no fills through halts, exits priced at the auction or the first post-reopen print. Band tiers must be checked against the current LULD plan text before any hardcoding.", + "sources": [ + "https://www.sec.gov/files/dera-luld-white-paper.pdf", + "https://www.sec.gov/files/marketstructure/research/dera_wp_luld_and_extraordinary_transitory_volatility.pdf", + "https://www.nasdaq.com/articles/all-about-lulds", + "https://nasdaqtrader.com/content/MarketRegulation/LULD_FAQ.pdf" + ] + }, + { + "id": "mover-v3-sec-xbrl-frames", + "item": "SEC XBRL frames API as a float and shares-outstanding screen, confirmed through companyfacts", + "catalog_action": "add", + "sweep_status": "contested: one misattributed source corrected; the claim stands", + "evidence_level": "official_docs_metadata", + "native_execution": "none", + "reason": "Public domain, 10 req/s. Frames returns the latest filed fact with no accession ID, so a name that clears the screen must be confirmed from companyfacts' dated per-accession fact before the float counts as point-in-time.", + "sources": [ + "https://www.sec.gov/search-filings/edgar-application-programming-interfaces", + "https://www.sec.gov/os/webmaster-faq" + ] + }, + { + "id": "mover-v3-alpaca-1000-tiers", + "item": "Alpaca non-retail 1000/min ($0.004/share, wholesale routing given up) versus Elite 1000/min ($30k deposit), as an addendum to the Elite entries in catalogs/us-equities/runtime-target.json and paper-practice-20260921.md", + "catalog_action": "compare", + "sweep_status": "contested: facts confirmed; contested only on decision value", + "evidence_level": "official_docs_metadata", + "native_execution": "none; paper observes 200/min", + "reason": "Capacity planning only. Throughput is capacity, not a trading target; 1000 round trips a minute is unreachable on one account, and sustained high rates are cost-bound first.", + "sources": [ + "https://alpaca.markets/support/increase-api-rate-limit", + "https://alpaca.markets/elite", + "https://alpaca.markets/support/usage-limit-api-calls" + ] + }, + { + "id": "mover-v3-norgate", + "item": "Norgate Data US Platinum versus the in-repo SEC Form-25 delisting proxy", + "catalog_action": "compare", + "sweep_status": "contested: facts confirmed; not new (already named in catalogs/sota-convergence/manifest-20260923.json)", + "evidence_level": "official_docs_metadata", + "native_execution": "none; gated on a paid subscription the user must buy; EULA not read", + "reason": "The only named candidate for survivorship-free delisted history before 2020. Access ends when a subscription lapses, so any derived receipt needs its own licence review.", + "sources": [ + "https://norgatedata.com/prices.php" + ] + }, + { + "id": "mover-v3-vwap-ladder-exit", + "item": "VWAP-loss and ladder exit (Maroy, SSRN 5095349, posted 2025-01-12)", + "catalog_action": "compare", + "sweep_status": "contested: HTTP 403, not read; the '2026' date was corrected to 2025", + "evidence_level": "literature_metadata", + "native_execution": "none", + "reason": "A single working paper on SPY. An exploratory arm only, outside the v3 confirmatory family unless a pre-freeze amendment adds it.", + "sources": [ + "https://papers.ssrn.com/sol3/papers.cfm?abstract_id=5095349" + ] + }, + { + "id": "mover-v3-fractional-kelly", + "item": "MacLean, Thorp, Ziemba (2010) fractional Kelly, as a sizing overlay applied only after a holdout pass", + "catalog_action": "add", + "sweep_status": "contested: a repository path in the proposal was misattributed; the idea stands", + "evidence_level": "literature_metadata", + "native_execution": "none", + "reason": "Edge shrink stays 0 until a v3 rule passes its holdout, then at most half-Kelly with estimation-error shrinkage, always under the leverage-schedule-v1 ceiling and safety.py hard caps. The full text (Quantitative Finance 10(7):681-687) was not opened.", + "links": ["mover-v3-kelly-code-discovery-only"], + "sources": [ + "https://www.worldscientific.com/doi/10.1142/9789811285530_0015" + ] + }, + { + "id": "mover-v3-no-change-duplicates", + "item": "Qullamaggie breakout scanner (duplicates the catalogued tradermonty/claude-trading-skills vcp-screener) and the backtrader family", + "catalog_action": "no-change", + "sweep_status": "scanner contested (duplicate); backtrader-family-closed killed (duplicates the catalog)", + "evidence_level": "live_repository_metadata", + "native_execution": "none", + "reason": "Recorded so the next sweep does not re-search them. The scanner is a single-maintainer, yfinance-only project (MIT, pushed 2026-09-16); only its feature vocabulary is of interest, and it is already covered. backtrader: GPL-3.0, last push 2024-08-19.", + "sources": [ + "https://github.com/VladPetrariu/Qullamaggie-breakout-scanner", + "https://github.com/mementum/backtrader" + ] + }, + { + "id": "mover-v3-already-recorded", + "item": "FINRA 4210 intraday margin standard; PATCH replace fill race; NautilusTrader halt-status model", + "catalog_action": "no-change", + "sweep_status": "4210 flag and replace semantics killed as duplicates; the Nautilus halt-status model was killed as a v3 candidate (background only)", + "evidence_level": "existing_record", + "native_execution": "none", + "reason": "Already recorded in catalogs/us-equities/convergence-program/trading.md, blueprints/us-equities/order-contract/README.md and the runtime target respectively. Halts remain a data-provider responsibility.", + "sources": [ + "https://alpaca.markets/blog/finra-retires-the-pdt-rule-introducing-alpacas-new-intraday-margin-framework/", + "https://docs.alpaca.markets/us/reference/patchorderbyorderid-1" + ] + } + ] +} diff --git a/manifests/evidence.json b/manifests/evidence.json index 1d5d8bcea..d42ca0a0e 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -9153,8 +9153,8 @@ }, { "path": "catalogs/us-equities/README.md", - "sha256": "7f0977dadd2f04ae9563b2456777f7ec00d07c3e23aac79fccd1f27cf4d70557", - "bytes": 15337 + "sha256": "9add859b3528ad05c7e830261b1ae42c2deb4dca5961108ee876ac02a9c5819e", + "bytes": 15649 }, { "path": "catalogs/us-equities/agents-operations.json", @@ -9298,8 +9298,8 @@ }, { "path": "catalogs/us-equities/decision-index.json", - "sha256": "f305a462b8ef3b51c90f5998164ecb88c48c79875de9d354be0eab67a5ff12d0", - "bytes": 719950 + "sha256": "64ff0e784d915e37fb8f8235e9d1d249bbad4f7570751b2eedaf3ecc0065dacd", + "bytes": 722539 }, { "path": "catalogs/us-equities/engines-strategies.json", From 0b924e761f7be0a80570df174f49e0ae5c17b20c Mon Sep 17 00:00:00 2001 From: Scout Date: Thu, 24 Sep 2026 03:05:15 -0400 Subject: [PATCH 2/8] Mover v3 review fixes: exposure, H6 split, test design, source corrections, repin to merged #162 Rebased on main aa6fc79 (PR #162 merged) and repinned v1 reuse to main with blob and sha256 hashes for rules.py, quotes.py (with the D7 stamp-window guard) and the unchanged cost table; cite D7 and the v2 results sha 75a5004b. Protocol draft: record broad-universe's $1/$2M descriptive lane and the S2 headline as aggregate exposure of 2017-2020, so development and validation are screening only and only the prospective holdout is confirmatory. Move H6 to its own engineering draft and two-test family (m = 35 alpha family), with TOST-sized stages, a TOST p for Holm and a hash key the client cannot re-roll. Trailing 252-session tercile breakpoints; three preregistered outcome labels; leg-matched H3-c on official auction prints; no halt data in H1-H3; a single deduplicated H4 parent set with a scoped holdout read; B = 100,000 so the lineage level is reachable; two-sided MDEs; pinned cost lookup inputs, sample key and sparse-cell fallbacks; holdout starts after the validation results are committed, with accrual exposure logged; embargo side stated. Plan and catalog: one broker-held closing order per share (Alpaca docs quoted), TotalView-ITCH coverage and frames accn corrected, squeeze claim detached from Hong et al., overnight/intraday primaries with Crossref-resolved DOIs, contested labels on Databento and Norgate, backtrader registered and the Qullamaggie scanner left unregistered with its reason; evidence hashes for the new files. Co-Authored-By: Claude Opus 5.5 (1M context) --- blueprints/us-equities/mover-v3/README.md | 99 ++++++--- .../mover-v3/h6-execution-parity-draft.json | 75 +++++++ .../us-equities/mover-v3/protocol-draft.json | 204 ++++++++++-------- catalogs/us-equities/decision-index.json | 10 +- .../us-equities/mover-v3-sweep-20260924.json | 47 ++-- manifests/evidence.json | 30 ++- 6 files changed, 322 insertions(+), 143 deletions(-) create mode 100644 blueprints/us-equities/mover-v3/h6-execution-parity-draft.json diff --git a/blueprints/us-equities/mover-v3/README.md b/blueprints/us-equities/mover-v3/README.md index 7ca4e2541..7f22b81e6 100644 --- a/blueprints/us-equities/mover-v3/README.md +++ b/blueprints/us-equities/mover-v3/README.md @@ -1,7 +1,8 @@ # Mover study v3: research plan (2026-09-24) **Status: draft plan. Nothing here is frozen, and no outcome has been computed.** The preregistration draft is -[`protocol-draft.json`](protocol-draft.json), status `draft_pending_independent_pre_outcome_review`. The catalog +[`protocol-draft.json`](protocol-draft.json), status `draft_pending_independent_pre_outcome_review`. The engineering +hypothesis H6 has its own draft, [`h6-execution-parity-draft.json`](h6-execution-parity-draft.json). The catalog decisions from the same sweep are in [`catalogs/us-equities/mover-v3-sweep-20260924.json`](../../../catalogs/us-equities/mover-v3-sweep-20260924.json). @@ -13,11 +14,13 @@ refutation the sweep's synthesis judged weak. Those are labelled *contested* whe was fetched, no study code was run, and no paper order was placed. The sweep input (sha256 `e2120c05...64d1`) stays private because its verifier notes contain host paths. This README and the supplement carry its content. -**Relation to PR #162.** PR #162 (open, owner active) holds the mover early-entry v1 and v2 studies -(`blueprints/us-equities/mover-early-entry/**`). **v1: 0 of 768 rule-exits pass development. v2: no pass in families -E, F or P (commit `92062e8`).** v3 does not modify those files. It reuses their pipeline only by reference, pinned to -commit `92062e8`, and it does not read their reserved holdout. The 2021 control segment of the catalyst experiment is -permanently inspected and is not a fresh holdout. +**Relation to PR #162.** PR #162 merged to main on 2026-09-24 as squash commit `aa6fc79`. It holds the mover +early-entry v1 and v2 studies (`blueprints/us-equities/mover-early-entry/**`). **v1: 0 of 768 rule-exits pass +development. v2: no pass in families E, F or P** (results sha256 `75a5004b...` after deviation D7). v3 does not modify +those files. It reuses their pipeline only by reference, pinned to main at `aa6fc79` with git-blob and sha256 hashes +for `rules.py`, `quotes.py` and the cost table (`protocol-draft.json`). The pin includes D7's stamp-window guard in +`quotes.py`. The earlier pin `92062e8` survives only on the PR branch. v3 does not read #162's reserved holdout. The +2021 control segment of the catalyst experiment is permanently inspected and is not a fresh holdout. ## Why continuation is not supported @@ -27,9 +30,9 @@ evidence below points the other way. It is consistent with #162's result, not a | Evidence | What it says | Sweep status | |---|---|---| | Bali, Cakici, Whitelaw (2011), "Maxing Out", *JFE* 99(2) ([PDF](https://pages.stern.nyu.edu/~rwhitela/papers/max%20jfe.pdf); [NBER w14804](https://www.nber.org/system/files/working_papers/w14804/w14804.pdf)) | Stocks with the largest recent daily return (MAX) earn *lower* subsequent risk-adjusted returns: lottery demand, then reversal | **survives** (both PDFs opened) | -| Hong, Li, Ni, Scheinkman, Yan, "Days to Cover and Stock Returns" ([NBER w21166](https://www.nber.org/system/files/working_papers/w21166/w21166.pdf)) | High days-to-cover predicts *lower* returns. Squeezes are episodic, not a base rate | **survives** (opened) | +| Hong, Li, Ni, Scheinkman, Yan, "Days to Cover and Stock Returns" ([NBER w21166](https://www.nber.org/system/files/working_papers/w21166/w21166.pdf)) | High days-to-cover predicts *lower* returns. The paper makes no squeeze claim | **survives** (opened) | | Barber & Odean (2008), *RFS* 21(2) ([PDF](https://faculty.haas.berkeley.edu/odean/papers/Attention/All%20that%20Glitters.pdf)); Barber, Huang, Odean, Schwarz (2022), *JF* ([10.1111/jofi.13183](https://onlinelibrary.wiley.com/doi/abs/10.1111/jofi.13183)); Barber, Lin, Odean (2023), *JFQA* | Retail buying of extreme-return, high-volume names is well documented and not profitable for the buyer | *contested*: the refutation concerned a citation and the fit, not the direction. It is used only as a prior on retail P&L, not as a strategy backtest | -| Lou, Polk, Skouras (2019), "A Tug of War: Overnight versus Intraday Expected Returns", *JFE*; Akbas, Boehmer, Jiang, Koch (2022), *JFE* | Overnight and intraday legs behave asymmetrically, so a flat multi-session hold mixes two opposing effects | *contested*: the original citation pointed at a secondary summary. The corrected primaries still need a direct read (DOIs unverified). Evidence comes from large, liquid names | +| Lou, Polk, Skouras (2019), "A tug of war: Overnight versus intraday expected returns", *JFE* 134(1):192-213 ([10.1016/j.jfineco.2019.03.011](https://doi.org/10.1016/j.jfineco.2019.03.011)); Akbas, Boehmer, Jiang, Koch (2022), "Overnight returns, daytime reversals, and future stock returns", *JFE* 145(3):850-875 ([10.1016/j.jfineco.2021.09.019](https://doi.org/10.1016/j.jfineco.2021.09.019)) | Overnight and intraday legs behave asymmetrically, so a flat multi-session hold mixes two opposing effects | *contested*: the original citation pointed at a secondary summary. Both DOIs resolve in Crossref (metadata checked 2026-09-24); the full texts still need a direct read. Evidence comes from large, liquid names | | Martineau (2022), *Critical Finance Review* 11:613-646, and the 2025 revival papers | Whether PEAD survives costs is unsettled | *contested*: deferred, not a v3 hypothesis | Repository evidence points the same way. #162's v1 has **no pass among 768 rule-exits**. All 704 with at least 200 @@ -37,6 +40,9 @@ development trades have a negative mean net return. v2 has no pass across its 24 The broad-universe study (`blueprints/us-equities/broad-universe/README.md`) reports that its flags "find *volatility*, not direction". +The sweep also stated that "squeezes are episodic, not a base rate". Its only source for that is an unverified S3 +Partners conference draft (EFMA 2025), not Hong et al., so v3 gives its squeeze test (H5-s) no directional prior. + The literature is monthly, cross-sectional and mostly large-cap. Whether it carries over to catalyst-conditioned movers priced at $1 to $50 is what v3 tests; it is not an assumed result. @@ -53,19 +59,35 @@ from `rules.py` by reference: `dollar_volume_at_t >= $1M`, with `price_at_t >= $1`. The entry is the first eligible quote at or after decision + 60 s. +H1-H3 use no halt data. As in the catalyst experiment, a halt is never inferred from missing data: fills need an +eligible quote, so a trade set never depends on a later halt-data purchase. Halt state enters only H4. + The sweep's phrase "v1 lanes" mixes two protocols: the catalyst experiment's daily and intraday lanes, and #162's +20% grid. v3 settles this with D and I above. G = 0.20 and V = $1M are v1's loosest gain and middle volume floor, chosen for sample size. They were chosen after v1's dev/val results were seen, in which every cell is negative; the exposure registry records this. -| H | Question | Entry | Exit arms | Sizing | Falsified if | Data | +| H | Question | Entry | Exit arms | Sizing | Not supported if | Data | |---|---|---|---|---|---|---| -| H1 | Does MAX (lottery) conditioning flip the sign? | D and I, with trailing MAX(21 sessions) terciles within each decision date as a covariate; no new thresholds | (a) exit at min(entry + 30 min, 15:55); (b) the lane horizon (D: 15:55 on the 5th session; I: entry + 60 min); (c) same-session 15:55 | 1x, equal notional, capacity-capped | high-MAX minus low-MAX cost-net mean for arm (b) is not negative at the corrected level; or no arm-by-tercile cell passes. A high-MAX outperformance rejects the reversal prior | **available now** (Alpaca SIP bars) | +| H1 | Does MAX (lottery) conditioning flip the sign? | D and I, with MAX(21 sessions) terciles as a covariate. Breakpoints come from the same population's events in the prior 252 sessions, so they are known at the decision; no new thresholds | (a) exit at min(entry + 30 min, 15:55); (b) the lane horizon (D: 15:55 on the 5th session; I: entry + 60 min); (c) same-session 15:55 | 1x, equal notional, capacity-capped | not supported: the high-MAX minus low-MAX cost-net mean for arm (b) excludes a negative effect of the MDE size, and no arm-by-tercile cell passes. A high-MAX outperformance is outside the one-sided test and supports no claim | **available now** (Alpaca SIP bars) | | H2 | Do catalyst-verified moves beat pure attention spikes? | D and I, split by an 8-K Item 1.01 accepted before the decision (the existing `catalyst.py` contract) vs no qualifying filing | the lane horizon only | 1x | catalyst minus attention-only is not above 0 at the corrected level, or both groups fail | **available now** (SEC EDGAR, 10 req/s; SIP bars) | -| H3 | Do the overnight and intraday legs differ? | D | (a) intraday only: enter at 09:35, exit at 15:55 the same session; (b) overnight only: enter at 15:55, exit at the first eligible quote after the next 09:30 open; (c) the 5-session hold split ex post into its legs (a diagnostic, not traded) | 1x. Arm (b) stays at 1x in any later paper use until the 2x rung and its overnight cap are clean | the legs do not differ at the corrected level, or neither traded arm passes | **available now** | -| H4 | Does a halt-conditional exit beat ignoring the halt? | No new entry. Applies to every H1-H3 traded-arm trade that meets an LULD limit state or pause (not only passing arms, so that a no-pass outcome still leaves a sample) | (a) exit at the reopening print or the first post-reopen quote; (b) hold to the arm's horizon. No fills during a halt. Pause length is logged as a covariate, not a signal | inherits the parent arm | the (a) minus (b) difference fails the corrected level, or fewer than the minimum halted trades occur (then the halt state stays a safety rule only) | **gated** (Databento XNAS.ITCH status schema, paid; Nasdaq Trader history as a short pilot) | -| H5 | Does days-to-cover crowding filter movers? | D split by days-to-cover tercile (short interest / ADV), using the latest FINRA figure published before the decision, with its settlement lag | the lane horizon | 1x; a filter, never a size input | primary: high minus low DTC is not negative. Secondary squeeze test: the high-DTC tercile does not beat the others | **gated** (FINRA short-interest access and terms) | -| H6 | Engineering, not alpha: do broker-held exits match the client-side ratchet at fewer calls? | positions of the paper candidate, assigned to an arm by hash; no extra orders | (a) the current `exits.py` chain with cancel-then-new; (b) an Alpaca `trailing_stop` (trail_percent from `effective_trailing_bps` at entry) plus an OCO for the stop and take-profit legs. Time, risk-off, force and halt exits stay client-side | parent sizing, 1x | median exit slippage vs the same trigger reference differs by more than 25 bps (TOST); or paper partial-fill or trigger behaviour departs from the docs; or arm (b) does not use at least 2x fewer REST calls per closed trade | **available now** (Alpaca paper), after an adapter change | +| H3 | Do the overnight and intraday legs differ? | D | (a) intraday only: enter at 09:35, exit at 15:55 the same session; (b) overnight only: enter at 15:55, exit at the first eligible quote after the next 09:30 open; (c) the 5-session hold split ex post into 4 overnight legs (official close to official open) and 4 intraday legs (official open to official close), comparing mean per-leg returns (a diagnostic, not traded) | 1x. Arm (b) stays at 1x in any later paper use until the 2x rung and its overnight cap are clean | not supported: the per-leg difference excludes the MDE, and neither traded arm passes | **available now** | +| H4 | Does a halt-conditional exit beat ignoring the halt? | No new entry. Applies to every D and I lane-horizon (b_lane) trade, deduplicated by symbol, session and entry time, that meets a halt or, for a Nasdaq-listed name, an LULD pause (not only passing arms, so that a no-pass outcome still leaves a sample) | (a) exit at the reopening print or the first post-reopen quote; (b) hold to the lane horizon. No fills during a halt. Pause length is logged as a covariate, not a signal | inherits the parent arm | not supported: the (a) minus (b) difference excludes the MDE. Below the minimum halted trades it is underpowered. Either way the halt state stays a safety rule only | **gated** (Databento XNAS.ITCH status schema, paid, *contested*: corrected from raw ITCH messages to the normalized schema; Nasdaq Trader history as a short pilot) | +| H5 | Does days-to-cover crowding filter movers? | D split by days-to-cover tercile (short interest / ADV; trailing 252-session breakpoints), using the latest FINRA figure published before the decision, with its settlement lag | the lane horizon | 1x; a filter, never a size input | primary: high minus low DTC excludes a negative effect of the MDE size. Secondary squeeze test (no sourced prior): the high-DTC tercile excludes an outperformance of the MDE size | **gated** (FINRA short-interest access and terms) | +| H6 | Engineering, not alpha, in its own draft and two-test family: do broker-held exits match the client-side ratchet at fewer calls? | positions of the paper candidate, assigned to an arm by a hash of symbol, session and entry count, written to the ledger before the order is sent; no extra orders | (a) the current `exits.py` chain with cancel-then-new; (b) the quantity split into two tranches, each with exactly one broker-held closing order: an OCO (take-profit and stop) on the take-profit tranche and a `trailing_stop` (trail_percent from `effective_trailing_bps` at entry) on the rest. Time, risk-off, force and halt exits stay client-side | parent sizing, 1x | not supported: median exit slippage vs the same trigger reference is not shown equivalent within ±25 bps (TOST, 200 per arm per confirmatory stage); or paper partial-fill or trigger behaviour departs from the docs; or arm (b) does not use at least 2x fewer REST calls per closed trade | **available now** (Alpaca paper), after an adapter change | + +**Outcome labels.** Each test ends in one of three preregistered labels: *supported*; *not supported*, with the +excluded effect size stated; or *underpowered*. At the minimum samples the detectable effects are 4-24% (the MDE table +in the protocol draft), far above the literature's roughly 1% a month, so a miss on H1, H2 or H5 will most likely be +labelled underpowered. v3 can detect only large mover-specific effects. + +**Exposure and chronology.** Development is 2017-2019, validation 2020, and the holdout is the first 252 sessions +after the validation results are committed. The 2017-2020 windows are disjoint from #162's v1 and v2, but not +unseen. The broad-universe study computed outcomes for 2017-2021 on a descriptive lane covering every name with a +close of at least $1 and a median dollar volume of at least $2M. That lane is a superset of most of D and I. This +plan's author also read its S2 volume-shock headline for that period. Development and validation are therefore +**screening stages only**: validation decides which items the holdout opens for, and only the prospective holdout can +support a confirmatory claim. H1 rests on BCW 2011 (**survives**) and the relative-volume/price-return ranking in `catalyst-experiment/protocol.json`, which is structurally a MAX-like selection. H2 rests on the Barber/Odean line @@ -82,10 +104,13 @@ state, a 5-minute pause, and pauses in the last 10 minutes folding into the clos and the [Nasdaq Trader halt pages](https://www.nasdaqtrader.com/trader.aspx?id=TradeHalts) (**survives**; a web/RSS interface with a limited lookback, not a bulk archive). -H5 rests on Hong et al. (**survives**). H6 rests on +H5 rests on Hong et al. (**survives**) for H5-p only. H6 rests on [orders-at-alpaca](https://docs.alpaca.markets/docs/orders-at-alpaca). `trailing_stop` **survives**. The bracket proposal was *contested* and corrected to OCO: a bracket's take-profit closes the whole position, so it cannot -express `take_profit_fraction` < 1. +express `take_profit_fraction` < 1. The same page limits how legs combine: "Trailing stop orders are currently +supported only with single orders", and "you would not be able to submit two conditional closing orders since +Alpaca's system would view one of the two orders as exceeding the available position quantity". So a share can carry +either a trailing stop or an OCO, never both. ## Data plan @@ -93,9 +118,9 @@ express `take_profit_fraction` < 1. |---|---|---|---| | PIT daily and minute SIP bars, official prices, corporate actions (H1-H3, H6) | Alpaca market data (SIP), already entitled; used by `broad-universe` and `extreme-gainer-audit` | **available** (daily bars from 2016-01-04, minute bars from 2016-01-01) | private hashed snapshots. Before 2020, survivorship is limited (`broad-universe/README.md`), so results for 2017-2019 carry that label | | 8-K Item 1.01 acceptance times (H2) | SEC EDGAR through the catalyst-provenance and catalyst-dataset recipes | **available** | acceptance must precede the decision, with zero late-source leakage | -| Shares outstanding and public float (screen only) | [SEC XBRL frames](https://www.sec.gov/search-filings/edgar-application-programming-interfaces) | **available** as a screen (*contested*: one misattributed source corrected) | frames returns the latest filed fact with no accession, so a name that clears the screen must be confirmed from companyfacts' dated per-accession fact | -| Historical halt, LULD and status events (H4, halt fidelity in simulation) | Databento XNAS.ITCH normalized status schema (`databento/dbn`, Apache-2.0), cross-checked against the Nasdaq Trader halt history | **gated on paid data the user must buy** (Databento is metered). A free short-window pilot is possible | get a `Historical.metadata.get_cost` quote for one symbol-year; confirm the coverage start and the licence. ITCH covers Nasdaq-listed names only | -| Survivorship-free delisted history before 2020 | Norgate Data US Platinum ([prices](https://norgatedata.com/prices.php)) vs the in-repo SEC Form-25 proxy | **gated on paid data the user must buy** | read the EULA's derived-data terms and what happens when the subscription lapses. Compare only | +| Shares outstanding and public float (screen only) | [SEC XBRL frames](https://www.sec.gov/search-filings/edgar-application-programming-interfaces) | **available** as a screen (*contested*: one misattributed source corrected) | frames give one last-filed value per entity. Each row carries `accn` but no filed date, and superseded vintages are absent. A name that clears the screen must be confirmed from companyfacts (or submissions by `accn`) for the filed date and the original vintage before the float counts as point-in-time | +| Historical halt, LULD and status events (H4, halt fidelity in simulation) | Databento XNAS.ITCH normalized status schema (`databento/dbn`, Apache-2.0; *contested*: corrected from raw ITCH H/h/J messages to the normalized schema), cross-checked against the Nasdaq Trader halt history | **gated on paid data the user must buy** (Databento is metered). A free short-window pilot is possible | get a `Historical.metadata.get_cost` quote for one symbol-year; confirm the coverage start and the licence. Per the TotalView-ITCH 5.0 spec, halt and resume state (H, Q, T) covers every security traded on the Nasdaq execution system, including other exchanges' listings; the LULD pause state (P) and the auction collar (J) are for Nasdaq-listed names only, and a pause in another listing appears as a halt | +| Survivorship-free delisted history before 2020 | Norgate Data US Platinum ([prices](https://norgatedata.com/prices.php); *contested*: facts confirmed, but not new, since it is already named in `catalogs/sota-convergence/manifest-20260923.json`) vs the in-repo SEC Form-25 proxy | **gated on paid data the user must buy** | read the EULA's derived-data terms and what happens when the subscription lapses. Compare only | | Short interest (H5) | FINRA consolidated short interest (`blueprints/us-equities/acceptance-wave/factors-regimes.md`) | **gated** on access and terms the user must establish | model the publication and settlement lag; a bi-monthly overlay only | | Order latency and fill agreement | Alpaca paper `trade_updates` timestamps plus `x-ratelimit-*` headers | **available** (paper) | probe orders come out of the 20-call management reserve and are labelled as measurement | | Earnings surprise (deferred lane) | Benzinga via Alpaca news, or a licensed calendar with revision history | gated on paid data; **not a v3 priority** | PEAD after costs is contested | @@ -104,12 +129,15 @@ express `take_profit_fraction` < 1. **Exits are deterministic and preregistered, and each sits at the cheapest correct layer.** -1. **Broker-held legs handle price triggers where the semantics fit.** For a position already held, an OCO carries - the stop-loss and take-profit legs. A bracket is used only when both legs close the whole position. An Alpaca - `trailing_stop` with `trail_percent` replaces the client-side cancel-and-resubmit ratchet. Its percentage is sized - from `effective_trailing_bps` at entry and re-armed only on a fixed schedule. A partial take-profit becomes - separate tranches, each with its own OCO. Paper-fill semantics for these order classes are undocumented, and H6 - measures them before anything relies on them. +1. **Broker-held legs handle price triggers where the semantics fit, with at most one closing order per share.** + Alpaca rejects a second conditional closing order on the same shares, and a `trailing_stop` cannot be an OCO or + bracket leg (both quoted above). So the position is split into tranches, and each tranche carries exactly one + broker-held order. The take-profit tranche carries an OCO (stop-loss and take-profit). The rest carries an Alpaca + `trailing_stop` with `trail_percent`, which replaces the client-side cancel-and-resubmit ratchet and is that + tranche's only stop. Its percentage is sized from `effective_trailing_bps` at entry and re-armed only on a fixed + schedule. A bracket is used only for a new entry whose two legs close the whole position. The docs define these + order classes, but Alpaca's simulated paper fills for them (partials, trigger price, trigger reference) are + unmeasured. H6 measures them before anything relies on them. 2. **Client-side rules keep everything that depends on state**, in the existing precedence: force_exit, risk_off, halt state, quote_stale, price rules, time_exit. Gap risk stays in `native_strategy.evaluate_gap_risk`. A client-side exit first cancels that position's broker-held legs. Cancel-then-new stays the default because of the @@ -271,10 +299,14 @@ The sweep's catalog actions are dated records in [`catalogs/us-equities/mover-v3-sweep-20260924.json`](../../../catalogs/us-equities/mover-v3-sweep-20260924.json): - **Registered with the decision index** (`python3 scripts/catalog_decisions.py --write --supplement - catalogs/us-equities/mover-v3-sweep-20260924.json#/repository_decisions`): the records that name a GitHub + catalogs/us-equities/mover-v3-sweep-20260924.json#/repository_decisions`): every record that names a GitHub repository (`alpacahq/alpaca-py`, `databento/dbn`, `nkaz001/hftbacktest`, `cvxgrp/kelly_code`, - `microsoft/RD-Agent`, `microsoft/qlib`, `polakowo/vectorbt`). Every one of these repositories was already in the - union, so the repository count is unchanged. + `microsoft/RD-Agent`, `microsoft/qlib`, `polakowo/vectorbt`, `mementum/backtrader`), with one intentional + exception below. Every registered repository was already in the union, so the repository count is unchanged. +- **Intentionally not registered:** the no-change record for `VladPetrariu/Qullamaggie-breakout-scanner`. The scanner + is absent from the union, and registering a duplicate that is not adopted would add a new identity to the grand + union and to the token-native saturation audit's identity partition, for no decision value. The record stays in + `/non_repository_decisions` so the next sweep still finds it. - **Kept as data in the same file** (`/non_repository_decisions`): papers, broker APIs, SEC/Nasdaq pages and data vendors. The union accepts only repository records. @@ -308,7 +340,7 @@ into another: | order-action-budget-arithmetic (as a catalog artifact) | a duplicate; its arithmetic (corrected) is used directly in the table above | | pead-cost-adjusted-contested (as a v3 hypothesis) | wrong journal cited (pii S0148619524000584 is the *Journal of Economics and Business*); the evidence is unsettled | | p6-oto-single-leg-order | folded into the OCO/trailing path; the savings are marginal | -| qullamaggie-breakout-scanner (as a new entry) | duplicates the catalogued `tradermonty/claude-trading-skills` vcp-screener; recorded as no-change | +| qullamaggie-breakout-scanner (as a new entry) | duplicates the catalogued `tradermonty/claude-trading-skills` vcp-screener; recorded as no-change (not registered; see Catalog decisions) | ## Open questions @@ -316,8 +348,10 @@ into another: the docs? H6 depends on it. - Does paper `x-ratelimit-limit` ever show an upgraded tier, and can a higher tier be qualified in paper at all? - Are IOC and FOK supported on the Nautilus/`native_adapter` path, and are IOC semantics in Alpaca paper documented? -- Can SIP trade and quote conditions rebuild historical halts and LULD states, or is XNAS.ITCH (Nasdaq only) plus an - NYSE feed required? What are the cost and the coverage start? +- Can SIP trade and quote conditions rebuild historical halts and LULD states? XNAS.ITCH carries halt state for every + Nasdaq-traded security but distinguishes an LULD pause only for Nasdaq-listed names. Is another venue's feed needed + to separate pauses from halts in NYSE-listed names, or is H4's pause stratum restricted to Nasdaq listings? What are + the cost and the coverage start? - Should v3 add a short side for the MAX-reversal prior? Borrow, hard-to-borrow fees and SEC Rule 201 were not examined. - Do latency probes (unfillable, then cancelled) comply with the engine's no-manufactured-trades rule? This needs an @@ -337,4 +371,7 @@ into another: - metadata-only coverage checks for H4 and H5; - study code committed with synthetic tests before any outcome is computed. +H6 has its own preconditions in `h6-execution-parity-draft.json`, including a test that one share never carries two +closing orders. + Until all of that is done, this is a plan, and no window it names may be read for outcomes. diff --git a/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json new file mode 100644 index 000000000..3efc33252 --- /dev/null +++ b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json @@ -0,0 +1,75 @@ +{ + "schema_version": 1, + "id": "mover-v3-h6-execution-parity-draft-20260924", + "kind": "draft_engineering_preregistration", + "status": "draft_pending_independent_pre_outcome_review", + "frozen_before_outcomes": false, + "drafted_at": "2026-09-24", + "base_commit": "aa6fc791bbd4af3499317673989d2e90a522c257", + "parent_plan": "blueprints/us-equities/mover-v3/README.md", + "split_from": "blueprints/us-equities/mover-v3/protocol-draft.json (H6), in the 2026-09-24 review fix round", + "evidence_class": "Official broker documentation read on 2026-09-24 plus repository source reads. No native execution, no paper order and no outcome. Paper fills are simulated by Alpaca, so any result is sim-to-paper evidence, not market evidence.", + "question": "Do broker-held exits (Alpaca trailing_stop and OCO) realize exit prices equivalent to the client-side cancel-then-new ratchet, relative to the same trigger reference, at fewer REST calls per closed position?", + "independence": "Engineering, not alpha. This protocol has its own two-test family and its own paper timeline. It uses no slot of the mover-v3 alpha family (m = 35), H1-H5 never wait for it, and no result here opens or informs the alpha holdout. Its paper positions are mechanics-only unless a v3 rule has passed its holdout; they are logged as accrual exposure under protocol-draft.json chronology.holdout.accrual_exposure.", + "evidence": [ + {"source": "https://docs.alpaca.markets/docs/orders-at-alpaca", "status": "read on 2026-09-24. trailing_stop (trail_price or trail_percent) and OCO for a held position ('the entry order is already filled, and you can submit the take-profit and stop-loss in one order submission') are documented. Constraints quoted verbatim: 'Trailing stop orders are currently supported only with single orders. However, we plan to support trailing stop as the stop loss leg of bracket/OCO orders in the future.' and 'you would not be able to submit two conditional closing orders since Alpaca's system would view one of the two orders as exceeding the available position quantity'."}, + {"source": "https://docs.alpaca.markets/reference/deleteallorders-1", "status": "survives"}, + {"source": "native_adapter.py builds only LIMIT/DAY orders; native_strategy.replace_exit cancels and resubmits when the price basis moves; exits.py rule chain (stop_loss, take_profit with take_profit_fraction, trailing)", "status": "repository source read at aa6fc79"} + ], + "population": "positions opened in Alpaca paper by the paper candidate after the OCO and trailing_stop adapter paths exist. No order is created for the test itself.", + "arm_assignment": { + "rule": "arm = a if the first byte of sha256('mover-v3-h6|' + symbol + '|' + entry session (YYYY-MM-DD) + '|' + k) is even, else b, where k is the 1-based count of this symbol's entries in this session in decision order", + "commit": "the assignment is written to the durable order ledger before the entry order is sent. client_order_id is not an input, so the client that generates IDs cannot re-roll an assignment. A position whose ledger assignment is missing or written after the submit is excluded and counted." + }, + "exit_arms": { + "a_client": "the current exits.py chain on the whole position with cancel-then-new replacement", + "b_broker": { + "constraint": "each share carries at most one broker-held closing order (the documented rule above): a trailing_stop cannot be an OCO leg, and a second conditional closing order on the same shares is rejected as exceeding the available quantity", + "structure": "the filled quantity q is split into two tranches. Tranche P (take-profit tranche) = floor(q x f_p) shares, carrying one OCO (take-profit limit at take_profit_bps and stop at stop_bps). Tranche T (trailing tranche) = q - P shares, carrying one trailing_stop with trail_percent = effective_trailing_bps at entry / 100, which is also that tranche's only stop. f_p = take_profit_fraction when it is below 1; when take_profit_fraction = 1 (the strategies_v1 default), f_p = 0.5, fixed here. A tranche of 0 shares is not sent, and the whole position carries the other tranche's single order.", + "client_side": "time, risk-off, force, quote-stale and halt exits stay client-side. A client-side exit first cancels both tranche orders (one call each), then sends one closing order for the remaining quantity.", + "difference_from_a": "arm b is not a byte-identical copy of arm a's rule chain: tranche T has no separate fixed stop and tranche P no trailing rule. H6-e therefore compares execution quality against each fill's own trigger reference, not P&L." + } + }, + "shadow": "for each position, the other arm's trigger time and reference price are computed from the same quote stream for paired diagnostics, never traded", + "call_count_expectation": { + "arm_a": "1 entry + 1 initial exit order + 2 per ratchet step (cancel, new): 2 + 2r per closed position at a full fill; 8 at r = 3", + "arm_b_broker_closed": "1 entry + 1 OCO + 1 trailing_stop = 3 calls, with no further calls when the broker legs fill (1 entry + 1 order when a tranche is empty = 2)", + "arm_b_client_closed": "3 + 2 cancels + 1 closing order = 6 calls", + "ratio": "with a share s of positions closed client-side, arm b averages 3 + 3s calls. At r = 3 the H6-c alternative (a/b > 2) needs s <= 1/3. The realized s and r are reported with H6-c." + }, + "primary_tests": [ + {"id": "H6-e", "statistic": "median of (realized exit price - trigger reference) in bps, arm b minus arm a", "alternative": "equivalence within +/-25 bps (TOST)", "p_value": "p = max(p_lower, p_upper), the larger of the two one-sided bootstrap p-values; this p enters Holm"}, + {"id": "H6-c", "statistic": "log(mean REST calls per closed position, arm a / arm b)", "alternative": "greater than log(2)"} + ], + "also_not_supported_if": "any paper partial-fill, trigger-price or trigger-reference behaviour that departs from the documented semantics, or any rejection of a tranche order as exceeding the available quantity (recorded per event)", + "multiple_testing": { + "family": "H6-e and H6-c only, m = 2", + "procedure": "Holm at family-wise alpha 0.05 at validation and at the holdout; strictest level 0.025", + "development_screen": "unadjusted p <= 0.05 with the development sample; nominates for validation only" + }, + "stages": { + "order": "closed positions in close-time order, counted per arm", + "development": "first 100 closed positions per arm", + "validation": "next 200 per arm", + "holdout": "next 200 per arm, read only for items that passed validation, with an access log entry in blueprints/us-equities/mover-v3/h6-access-log.jsonl committed before the read", + "minimum": "each stage's size is its minimum; a stage that has not reached it is underpowered and cannot pass", + "note": "prospective paper only; disjoint from every historical window and independent of the alpha protocol's stages" + }, + "power": { + "planning_sigma_bps": 50, + "sigma_provenance": "assumption, no measured source", + "median_efficiency": "standard error of a median difference taken as sigma x sqrt(2/n) x sqrt(pi/2) (normal approximation)", + "development": "n = 100 per arm, alpha 0.05: SE 8.9 bps, TOST interval half-width 14.6 bps, equivalence passes if the observed difference is within +/-10.4 bps, power about 0.76 at a true difference of 0", + "validation_and_holdout": "n = 200 per arm, alpha 0.025: SE 6.3 bps, half-width 12.3 bps, passes within +/-12.7 bps, power about 0.96 at a true difference of 0", + "previous_draft": "the earlier 40 / 60 / 150 per arm at 0.05/37 could not pass development or validation by construction (half-width above the 25 bps margin at 60 per arm)" + }, + "outcome_reporting": "the three labels of protocol-draft.json outcome_reporting (supported; not supported with the excluded size stated; underpowered)", + "sizing": "parent sizing at 1x", + "data_status": "available now (Alpaca paper) once the adapter change lands; paper fills are simulated, so this is sim-to-paper evidence, not market evidence", + "freeze_preconditions": [ + "An independent pre-outcome review from a different session and model family, findings resolved in a dated record", + "Adapter paths for OCO and trailing_stop committed with synthetic tests, including a test that one share never carries two closing orders", + "The arm-assignment ledger write tested to precede the entry submit", + "status set to frozen and the sha256 recorded in a pushed commit" + ] +} diff --git a/blueprints/us-equities/mover-v3/protocol-draft.json b/blueprints/us-equities/mover-v3/protocol-draft.json index 996b97e74..904cfe089 100644 --- a/blueprints/us-equities/mover-v3/protocol-draft.json +++ b/blueprints/us-equities/mover-v3/protocol-draft.json @@ -5,9 +5,11 @@ "status": "draft_pending_independent_pre_outcome_review", "frozen_before_outcomes": false, "drafted_at": "2026-09-24", - "base_commit": "4a4c8a285630412ecfa8715c290a1e049ed03cb2", + "revised_at": "2026-09-24 (review fix round: exposure registry, H6 moved to its own engineering draft, trailing breakpoints, three-outcome reporting, leg-matched H3-c, halt-free H1-H3, H4 parent set, cost lookup pins, repin to merged #162)", + "base_commit": "aa6fc791bbd4af3499317673989d2e90a522c257", "plan": "blueprints/us-equities/mover-v3/README.md", "catalog_decisions": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "engineering_protocol": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json", "evidence_class": "Literature and metadata evidence, plus unpinned source reads, from a read-only sweep on 2026-09-24 (5 angles, 37 items; 5 survive, 24 contested, 8 killed). No native execution, no market data fetched and no outcome computed. Not a frozen protocol: no window named here may be read for outcomes until status is frozen under freeze_preconditions.", "sweep_input": { "sha256": "e2120c0578dd02c9784c0abcd0af89e5e61d3dfb004a9bed55d278891b8764d1", @@ -15,48 +17,66 @@ "publication": "private scratch input, not committed: its verifier notes contain host paths. The README and the catalog supplement carry its content." }, "relation_to_prior_protocols": { - "mover-early-entry-v1-20260924": "PR #162 (open). 0 of 768 rule-exits pass development; the holdout was not read. Reused by reference only, pinned to commit 92062e8: rules.py definitions (ref, price_at_t, dollar_volume_at_t, gain_at_t, official prices, split factor), quotes.py cost-sample method and evidence/cost-table-run-v1.json. v3 never modifies blueprints/us-equities/mover-early-entry/**.", - "mover-followup-v2-20260924": "PR #162, commit 92062e8: no pass in families E (216), F (16) or P (12); no holdout read. Not reused.", - "catalyst-experiment-v1-20260920": "Its lane timing (16:15 decision, 09:35 entry, +60 s minimum latency, 5-session and 60-minute horizons) and the catalyst.py Item 1.01 contract are reused by reference. Its 2021Q1 ETF control is permanently inspected and is never a holdout.", - "broad-universe-mover-v1-20260921": "Different population (close >= $5, median dollar volume >= $20M). Its evaluated segments are named in exposure_registry." + "mover-early-entry-v1-20260924": "PR #162, merged to main on 2026-09-24 as squash commit aa6fc79. 0 of 768 rule-exits pass development; the holdout was not read. Reused by reference only, pinned to main at aa6fc79 (paths under blueprints/us-equities/mover-early-entry/): rules.py (git blob 9be164c9e1a49a451d3a2381205349ed5003a993, sha256 d66156b3e57c6299f10e41a3f25c921c8be52571bd84417040190d926bf49450; definitions ref, price_at_t, dollar_volume_at_t, gain_at_t, official prices, split factor), quotes.py (git blob caebf6f6a72fee6bb7ae637ebb698f8c5f6b3850, sha256 4116bf2809e0952f842f0e0fe35fd8b5ab11ea295637734ffe0d719ca72ee85e; the cost-sample method including the D7 stamp-window guard: a completion is reused only for the same timestamp, and the table refuses quotes outside [ts - 60 s, ts]) and evidence/cost-table-run-v1.json (hashes under cost_model.base_table). The draft's earlier pin 92062e8 is reachable only from the PR branch, and its quotes.py lacks the D7 guard. v3 never modifies blueprints/us-equities/mover-early-entry/**.", + "mover-followup-v2-20260924": "PR #162 at aa6fc79: no pass in families E (216), F (16) or P (12); no holdout read. After deviation D7 (P exits across splits and ex-dividend dates), the v2 results sha256 is 75a5004b... (README and deviations.json at aa6fc79). Not reused.", + "catalyst-experiment-v1-20260920": "Its lane timing (16:15 decision, 09:35 entry, +60 s minimum latency, 5-session and 60-minute horizons) and the catalyst.py Item 1.01 contract are reused by reference, as is its rule 'Do not infer a halt from missing data'. Its 2021Q1 ETF control is permanently inspected and is never a holdout.", + "broad-universe-mover-v1-20260921": "Main universe close >= $5 and median dollar volume >= $20M, plus a descriptive lane on the unrestricted $1 / $2M universe (protocol.json descriptive_microcap_lane; evaluation-results.json calls it a strict superset of the main universe). Both lanes computed outcomes over development 2017-2021, validation 2022-2023 and reserved 2024-01-01 to 2026-08-14. Most D-population symbol-sessions with median dollar volume >= $2M lie inside that lane, so 2017-2020 is exposed at aggregate level (exposure_registry)." }, - "question": "Among US-equity extreme movers, which conditioning (MAX lottery demand, catalyst versus attention, overnight versus intraday leg, halt state, days-to-cover crowding) changes the sign or size of the cost-net forward return, and can broker-held exits match client-side exits at fewer API calls? Continuation is one tested outcome, not the premise.", + "question": "Among US-equity extreme movers, which conditioning (MAX lottery demand, catalyst versus attention, overnight versus intraday leg, halt state, days-to-cover crowding) changes the sign or size of the cost-net forward return? Continuation is one tested outcome, not the premise. Whether broker-held exits match client-side exits at fewer API calls is a separate engineering question (engineering_protocol).", "direction": "long only (a short side for the MAX-reversal prior is an open question; borrow, hard-to-borrow fees and SEC Rule 201 were not examined)", "exposure_registry": { "already_looked_at": [ {"window": ["2021-01-04", "2023-12-29"], "what": "mover-early-entry v1 and v2 development outcomes (1,012 rule-exits) on the +20% candidate universe; the v1 cost table's quote sample was drawn from these development sessions"}, {"window": ["2024-01-02", "2025-12-31"], "what": "mover-early-entry v1 and v2 validation outcomes"}, {"window": ["2026-01-02", "2026-09-18"], "what": "the reserved holdout of mover-early-entry v1/v2, unread by #162 and owned by it. It is also exposed at event level: extreme-gainer-audit computed T+1/T+5/T+20 forward returns for 957 package events from 2021 to September 2026. v3 excludes this window."}, - {"window": ["2017-01-01", "2026-08-14"], "what": "broad-universe-mover-v1: development 2017-2021, validation 2022-2023 and reserved 2024-01-01 to 2026-08-14 were evaluated for daily signals on the close >= $5, median dollar volume >= $20M universe, including +20% one-day labels. Only liquid names overlap with the v3 population."}, + {"window": ["2017-01-01", "2021-12-31"], "what": "broad-universe-mover-v1 development segment, computed on two lanes: the main universe (close >= $5, median dollar volume >= $20M) and the descriptive lane on the unrestricted close >= $1, median dollar volume >= $2M universe (a strict superset of the main universe; receipt.json reports its development outcomes, for example 4,563,385 C0 H1 events). Outcomes include +20% one-day labels and S2 volume-shock continuation (r1 >= 5%, dv >= 3 x med20, clv >= 0.5), a superset of D's same-session arm on names with median dollar volume >= $2M. This covers all of v3 development (2017-2019) and validation (2020) at aggregate level."}, + {"window": ["2022-01-01", "2026-08-14"], "what": "broad-universe-mover-v1 validation 2022-2023 and reserved 2024-01-01 to 2026-08-14, both lanes"}, {"window": ["2019-12-02", "2020-04-30"], "what": "historical-simulation: six LEAN margin-stress scenarios on bundled SPY history; no mover-population outcome"}, {"window": ["2021-01-01", "2021-03-31"], "what": "catalyst-experiment 2021Q1 ETF control, permanently inspected"}, {"window": "2020 and 2022 single-name samples", "what": "LEAN/AAPL 2020, FB/META 2022, SPY parity and corporate-action diagnostics; engine and data checks, not mover outcomes"} ], - "draft_author_exposure": "Before this draft the author read: #162's README results and v1 dev/val summary aggregates (per-rule means; P5/P95 of the 07:00|G0.20|V1000000 rule-exits, used only for sigma planning below); the v1 cost-table cells; the #162 review findings; the broad-universe README headlines; the sweep. No 2017-2020 mover-population outcome, and no halt, short-interest or MAX statistic for any window, has been computed or seen.", - "update_rule": "Every later read of data in any v3 window, before the freeze, is appended here with its date and purpose. A read of outcomes in a v3 validation or holdout window before the freeze voids that window." + "draft_author_exposure": "Before this draft the author read: #162's README results and v1 dev/val summary aggregates (per-rule means; P5/P95 of the 07:00|G0.20|V1000000 rule-exits, used only for sigma planning below); the v1 cost-table cells; the #162 review findings; the broad-universe README headlines, including the development | validation | reserved table of date-clustered mean net excess over C0 for S1-S5 at H1/H5/H20 (for example S2 H1 development -0.33%, 2017-2021, main universe) and its reserved-segment precision and recall for +10% and +20% next-day movers; the sweep. The review fix round (same day) reread broad-universe protocol.json (lane definitions), evaluation-results.json's descriptive_microcap_scope note and the same README table, and opened no descriptive-lane outcome row; the 4,563,385 event count above is a count quoted from the review finding, not a return. No v3-defined statistic (a D or I outcome, a MAX, halt or short-interest tercile) has been computed or seen for any window.", + "consequence": "2017-2020 is exposed at aggregate level through broad-universe development (both lanes) and the S2 headline above. v3 therefore treats development (2017-2019) and validation (2020) as screening stages only. Validation still decides which items the holdout gate opens for, but a validation pass is labelled 'screened', never confirmatory. Only the prospective holdout can support a confirmatory claim. A validation-window statistic restricted to names that lane never covered (20-session median dollar volume < $2M at the decision) is reported beside each validation result as the least-exposed slice; it is a sensitivity, not a separate test.", + "update_rule": "Every later read of data in any v3 window, before the freeze, is appended here with its date and purpose. A read of v3-defined outcomes in the validation window before the freeze voids validation; a read in the holdout window before the gate opens voids the holdout." }, "populations": { "D_daily": "symbol-sessions t with split-consistent official-close gain close[t]/ref - 1 >= 0.20, official close >= $1.00 and completed regular-session dollar volume >= $1,000,000; decision at 16:15 ET on t; entry at the first eligible quote at or after 09:35 ET on t+1 and at or after decision + 60 s; entry timeout 300 s, else no entry (counted)", - "I_intraday": "the first minute m in 09:35-10:30 ET on a session at which gain_at_t >= 0.20, dollar_volume_at_t >= $1,000,000 and price_at_t >= $1.00 (rules.py definitions at 92062e8); entry at the first eligible quote at or after m + 60 s; timeout 60 s", - "exclusions": "OTC; warrants, units and rights by #162's regex; a known halt at decision time blocks entry (as catalyst-experiment); symbols with fewer than 21 prior sessions of bars", + "I_intraday": "the first minute m in 09:35-10:30 ET on a session at which gain_at_t >= 0.20, dollar_volume_at_t >= $1,000,000 and price_at_t >= $1.00 (rules.py definitions at aa6fc79); entry at the first eligible quote at or after m + 60 s; timeout 60 s", + "exclusions": "OTC; warrants, units and rights by #162's regex; symbols with fewer than 21 prior sessions of bars. No halt exclusion: H1-H3 use no halt data (see halts).", + "halts": "H1-H3 use no halt source, historically or prospectively, so their trade sets cannot depend on a later data purchase. As in catalyst-experiment, a halt is never inferred from missing data: an entry or exit fills only at an eligible quote (bid > 0, ask >= bid, quote age <= 1000 ms), an entry without one inside its timeout is a counted no-entry, and an exit without one waits for the next eligible quote. Limitation: a trade that sits through an unobserved halt is not flagged, and a fill at the first post-halt quote is not distinguished from a normal fill. Halt state enters only H4, as an overlay on already-defined trades.", "parameter_provenance": "G = 0.20 and V = $1M are v1's loosest gain and middle volume floor, fixed for sample size after v1's all-negative dev/val grid was seen (disclosed exposure). They are not tuned on any v3 window." }, + "tercile_rule": { + "breakpoints": "For a covariate X (MAX21 in H1, DTC in H5) and a population P, the breakpoints for an event decided in session s are the 1/3 and 2/3 empirical quantiles (numpy 'linear' method) of X over all P events whose decision session lies in the 252 sessions strictly before s. Every input is known at the decision, including for I, whose later same-morning triggers are never used.", + "assignment": "low if X <= the 1/3 breakpoint, high if X > the 2/3 breakpoint, middle otherwise (a value equal to a breakpoint goes to the lower tercile)", + "minimum": "if fewer than 60 prior P events lie in the trailing window, the event is unassigned: it is counted and reported, and it enters no tercile cell or difference test. The 2016 warm-up year supplies the first window.", + "small_dates": "Terciles never depend on the number of movers on the event's own date, so a date with fewer than 3 movers needs no special rule." + }, "chronology": { "warmup": ["2016-01-04", "2016-12-30"], "development": ["2017-01-03", "2019-12-31"], "validation": ["2020-01-02", "2020-12-31"], "holdout": { "kind": "prospective", - "window": "the first 252 full NYSE sessions strictly after the session in which the frozen protocol commit is pushed", + "window": "the first 252 full NYSE sessions strictly after the session in which the validation results file's sha256 is committed and pushed (which follows the freeze)", "extension": "if any holdout-eligible item has fewer than its minimum holdout trades, extend in 63-session blocks, at most 2, decided on trade counts alone with outcome labels uncomputed", - "why_prospective": "No retrospective window disjoint from v1/v2 remains: 2021-2025 is v1/v2 dev/val, 2026-01-02 to 2026-09-18 is #162's reserved holdout and is event-level exposed, and 2016 is warm-up." + "why_prospective": "No retrospective window free of prior mover outcomes remains: 2021-2025 is v1/v2 dev/val, 2026-01-02 to 2026-09-18 is #162's reserved holdout and is event-level exposed, 2017-2026-08-14 is broad-universe exposed at aggregate level, and 2016 is warm-up.", + "accrual_exposure": "Any paper operation that trades mover positions during the holdout window (including the mechanics-only positions of the engineering protocol) logs, per collection batch, which symbol-sessions it traded and whether their P&L was seen. Those symbol-sessions are then excluded from the holdout sample for every item, and the count is reported." + }, + "labels": { + "development": "disjoint from v1/v2; exposed at aggregate level through broad-universe development (both lanes, 2017-2021); screening only; survivorship-limited before 2020 (names delisted in 2016-2019 without a retained corporate action cannot be discovered)", + "validation": "disjoint from v1/v2; exposed at aggregate level through broad-universe development (both lanes include 2020) and the S2 headline the author read; screening only, never confirmatory; the 2020 COVID regime is reported separately and never dropped", + "holdout": "prospective; untouched only if the validation results sha256 was pushed before its first session, no read preceded a committed granted access-log entry, and accrual exposure was logged and excluded as above" }, + "boundaries": "Segments are assigned by entry session. A trade whose exit crosses a segment end is censored and counted in the earlier segment. For multi-session arms, the later segment gives up its first 6 sessions as entry sessions (an embargo after the censored crossing trades); same-session arms need no gap." + }, + "outcome_reporting": { "labels": { - "development": "disjoint from v1/v2; overlaps broad-universe development for liquid names; survivorship-limited before 2020 (names delisted in 2016-2019 without a retained corporate action cannot be discovered)", - "validation": "disjoint from v1/v2; overlaps broad-universe development for liquid names; the 2020 COVID regime is reported separately and never dropped", - "holdout": "untouched by construction" + "supported": "the item passes its stage rule (development: the screen; validation: Holm in the 35-item family plus minimum sample and, for a tradable cell, the robustness means, labelled 'screened'; holdout: the same at the holdout, labelled confirmatory)", + "not_supported_mde_excluded": "the stage has at least its minimum sample, the item does not pass, and the one-sided confidence bound in the hypothesized direction at the item's corrected level excludes an effect of the preregistered MDE for the realized n (minimum_detectable_effect)", + "underpowered": "the stage is below its minimum sample, or the item neither passes nor excludes the MDE" }, - "boundaries": "Segments are assigned by entry session. A trade whose exit crosses a segment end is censored and counted. Multi-session arms leave a 6-session gap at each boundary." + "rule": "Only these three labels are used. A miss is never reported as 'falsified' or as proof of no effect; 'not supported' always states the size it excludes. A result in the direction opposite to a one-sided alternative is reported descriptively with its estimate and interval and supports no claim, because it was not tested." }, "hypotheses": { "H1": { @@ -66,7 +86,7 @@ {"source": "catalyst-experiment/protocol.json ranks by relative_volume then price_return, a MAX-like selection", "status": "repository source read"} ], "populations": ["D_daily", "I_intraday"], - "covariate": "MAX21 = the largest split-consistent daily close-to-close return over the 21 sessions ending t-1 (D) or the previous session (I), from data known at the decision; terciles within each decision date (ties by symbol)", + "covariate": "MAX21 = the largest split-consistent daily close-to-close return over the 21 sessions ending t-1 (D) or the previous session (I), from data known at the decision; terciles by tercile_rule (trailing 252-session breakpoints of the same population)", "exit_arms": { "a_fast": "first eligible quote at or after min(entry + 30 min, 15:55 ET)", "b_lane": "D: first eligible quote at or after 15:55 ET on the 5th holding session (entry session = 1); I: at or after min(entry + 60 min, 15:55)", @@ -79,7 +99,7 @@ {"id": "H1-I", "statistic": "same, population I", "alternative": "less than 0"} ], "tradable_cells": "2 populations x 3 arms x 3 terciles = 18; each tested for mean net > 0", - "falsifier": "Falsified if H1-D and H1-I both fail the corrected level, or if no tradable cell passes development. A significant high-MAX outperformance rejects the reversal prior.", + "not_supported_if": "H1-D and H1-I are both labelled not_supported_mde_excluded, and no tradable cell passes development. A high-MAX outperformance is outside the one-sided alternative, is reported descriptively only, and supports no claim about the reversal prior.", "data_status": "available now (Alpaca SIP daily and minute bars, official prices)", "chronology": "common (development 2017-2019, validation 2020, prospective holdout)" }, @@ -97,113 +117,101 @@ {"id": "H2-I", "statistic": "same, population I", "alternative": "greater than 0"} ], "tradable_cells": "2 populations x 2 groups = 4", - "falsifier": "Falsified if H2-D and H2-I both fail the corrected level, or both groups fail development: the catalyst filter adds nothing beyond the volume filter.", + "not_supported_if": "H2-D and H2-I are both labelled not_supported_mde_excluded and neither group passes development: the catalyst filter adds nothing beyond the volume filter at the detectable size.", "data_status": "available now (SEC EDGAR, public domain, 10 req/s, existing recipes; Alpaca SIP)", "chronology": "common" }, "H3": { "name": "Overnight versus intraday legs of mover holds", "evidence": [ - {"source": "Lou, Polk, Skouras (2019) 'A Tug of War: Overnight Versus Intraday Expected Returns', JFE; Akbas, Boehmer, Jiang, Koch (2022) JFE", "status": "contested: original citation was a secondary summary; substance not refuted; DOIs unverified; large, liquid names"}, + {"source": "Lou, Polk, Skouras (2019) 'A tug of war: Overnight versus intraday expected returns', JFE 134(1):192-213, doi 10.1016/j.jfineco.2019.03.011; Akbas, Boehmer, Jiang, Koch (2022) 'Overnight returns, daytime reversals, and future stock returns', JFE 145(3):850-875, doi 10.1016/j.jfineco.2021.09.019", "status": "contested: the original citation was a secondary summary; substance not refuted; both DOIs resolved in Crossref metadata on 2026-09-24, full texts not read; large, liquid names"}, {"source": "Maroy, SSRN 5095349 (posted 2025-01-12), VWAP and ladder exits", "status": "contested: HTTP 403, not read; excluded from the confirmatory family"} ], "populations": ["D_daily"], + "price_sources": "official open = the primary listing's opening auction print from the auctions collection (collect.py, sessions_io.official_price 'o' at aa6fc79); official close likewise ('c'). A leg with a missing official print is dropped and counted; no bar or quote substitutes in H3-c.", "exit_arms": { "a_intraday": "enter at the first eligible quote at or after 09:35 on t+1; exit at or after 15:55 the same session", "b_overnight": "enter at the first eligible quote at or after 15:55 on t+1; exit at the first eligible quote at or after 09:30 on t+2", - "c_decomposition": "diagnostic only, not traded: the 5-session b_lane hold split into overnight (prior 15:55 to next open) and intraday (open to 15:55) legs" + "c_decomposition": "diagnostic only, not traded: over the sessions t+1 .. t+5 of the D b_lane hold, overnight legs k = 1..4 are official close of t+k to official open of t+k+1, and intraday legs k = 2..5 are official open to official close of t+k. The partial first-session leg is excluded, so each trade has 4 legs of each kind, and together they decompose official close(t+1) to official close(t+5)." }, "sizing": "1x; arm b stays at 1x in any later paper use until leverage-ladder-2x and its overnight cap are clean", "primary_tests": [ {"id": "H3-a", "statistic": "mean net(a_intraday)", "alternative": "greater than 0"}, {"id": "H3-b", "statistic": "mean net(b_overnight)", "alternative": "greater than 0"}, - {"id": "H3-c", "statistic": "mean over trades of (sum of overnight leg returns - sum of intraday leg returns) within the 5-session hold, gross", "alternative": "not equal to 0"} + {"id": "H3-c", "statistic": "mean over trades of (mean of the 4 overnight leg log returns - mean of the 4 intraday leg log returns), gross, legs as in c_decomposition", "alternative": "not equal to 0"} ], "tradable_cells": "H3-a and H3-b are the tradable cells (counted once)", - "falsifier": "Falsified if H3-c fails the corrected level, or neither H3-a nor H3-b passes after costs.", - "data_status": "available now (Alpaca SIP minute bars with session open and prior close)", + "not_supported_if": "H3-c is labelled not_supported_mde_excluded, and neither H3-a nor H3-b passes after costs.", + "data_status": "available now (Alpaca SIP minute bars; official opening and closing auction prints)", "chronology": "common" }, "H4": { - "name": "Halt-conditional exit around LULD pauses", + "name": "Halt-conditional exit around LULD pauses and halts", "evidence": [ {"source": "SEC DERA LULD white papers, https://www.sec.gov/files/dera-luld-white-paper.pdf and https://www.sec.gov/files/marketstructure/research/dera_wp_luld_and_extraordinary_transitory_volatility.pdf", "status": "contested: the execution-state idea survives; the pause-duration claim is a literature-review statement, not a DERA finding"}, {"source": "Nasdaq 'All About LULDs' and the NasdaqTrader LULD FAQ (15 s limit state, 5-minute pause, last-10-minute pauses fold into the close)", "status": "contested: opened by the reviewer; band tiers must be checked against the current plan text before any hardcoding"}, - {"source": "https://www.nasdaqtrader.com/trader.aspx?id=TradeHalts plus halt search and history", "status": "survives: web/RSS, limited lookback"} + {"source": "https://www.nasdaqtrader.com/trader.aspx?id=TradeHalts plus halt search and history", "status": "survives: web/RSS, limited lookback"}, + {"source": "Nasdaq TotalView-ITCH 5.0 specification (NQTVITCHSpecification.pdf) section 1.2.2 Stock Trading Action: coverage is 'all exchange-listed equities securities traded via the Nasdaq execution system'; state H is 'Halted across all U.S. equity markets / SROs'; P (paused) is 'Nasdaq-listed securities only', and 'Trading pauses on non-NASDAQ listed securities will be treated simply as a halt'; the J LULD Auction Collar applies to a paused security reopening", "status": "read on 2026-09-24 in the review fix round; the Databento XNAS.ITCH status schema that carries it is contested (sweep p1-databento-xnas-itch-halt-luld) and its cost and coverage start are unverified"} ], - "population": "every trade of the H1-H3 traded arms whose security enters an LULD limit state or trading pause between entry and exit. The sweep conditioned this on passing arms; v3 uses all traded-arm trades, so a no-pass outcome still leaves a sample.", + "parent_trades": "the b_lane trades of populations D and I (the trades H1, H2 and H5 share), deduplicated by symbol|entry session|entry timestamp. Other arms (H1 a_fast and c_session, H3 a and b) are not parents. The unit of analysis and of the session block bootstrap is one deduplicated parent trade.", + "population": "every parent trade whose security, during the holding interval, is in state H (halted across all US markets) or, for a Nasdaq-listed security, P (LULD pause), per the covered halt source. The two strata are reported separately: an LULD pause is identified only for Nasdaq-listed names; for other listings the source reports a pause as a halt.", "exit_arms": { "a_reopen": "exit at the reopening-auction print, else the first eligible post-reopen quote", - "b_ignore": "hold to the parent arm's exit, ignoring the halt" + "b_ignore": "hold to the parent b_lane exit, ignoring the halt" }, "rules": "No fills during a halt; a resting stop fills through the gap at the first print. Pause duration is logged as a covariate only.", "sizing": "inherits the parent arm", "primary_tests": [ - {"id": "H4", "statistic": "mean over halted trades of net(a_reopen) - net(b_ignore)", "alternative": "not equal to 0"} + {"id": "H4", "statistic": "mean over halted parent trades of net(a_reopen) - net(b_ignore)", "alternative": "not equal to 0"} ], "minimum_halted_trades": {"development": 60, "validation": 40, "holdout": 40}, - "falsifier": "Falsified if H4 fails the corrected level or a stage has fewer than its minimum halted trades; the halt state then stays a safety rule only.", - "data_status": "gated: Databento XNAS.ITCH status schema (paid, metered; Nasdaq-listed only; cost and coverage start unverified), or Nasdaq Trader halt history (free, limited lookback, pilot only). Whether SIP conditions can rebuild halts is unverified.", - "chronology": "common, restricted to the halt source's covered listings and sessions as established by metadata before any outcome. A Nasdaq Trader pilot is exploratory and cannot pass. If the covered development span is shorter than 1 year, H4 is not tested and scores p = 1 in the family." + "not_supported_if": "H4 is labelled not_supported_mde_excluded. A stage below its minimum halted trades is underpowered, not a miss; either way the halt state stays a safety rule only.", + "data_status": "gated: Databento XNAS.ITCH status schema (paid, metered; covers halt and resume state for every security traded on the Nasdaq execution system, with the P pause state and J auction collar for Nasdaq-listed securities only; cost and coverage start unverified; *contested*), or Nasdaq Trader halt history (free, limited lookback, pilot only). Whether SIP conditions can rebuild halts is unverified.", + "chronology": "common, restricted to the halt source's covered sessions as established by metadata before any outcome. A Nasdaq Trader pilot is exploratory and cannot pass. If the covered development span is shorter than 1 year, H4 is not tested and scores p = 1 in the family. The halt source is used only for H4 and never changes the H1-H3 trade sets." }, "H5": { "name": "Days-to-cover crowding overlay", "evidence": [ - {"source": "Hong, Li, Ni, Scheinkman, Yan, 'Days to Cover and Stock Returns', NBER w21166, SSRN 10.2139/ssrn.2568768", "status": "survives: high DTC predicts lower returns"}, + {"source": "Hong, Li, Ni, Scheinkman, Yan, 'Days to Cover and Stock Returns', NBER w21166, SSRN 10.2139/ssrn.2568768", "status": "survives: high DTC predicts lower returns. The paper makes no squeeze claim."}, + {"source": "S3 Partners crowding and short-squeeze paper, EFMA 2025 conference draft (http://www.efmaefm.org/0EFMAMEETINGS/EFMA%20ANNUAL%20MEETINGS/2025-Greece/papers/Manuscript-with-author-details223.pdf)", "status": "unverified conference draft, not confirmed as published; the sweep's only source for 'squeezes are episodic, not a base rate'. Not a prior for H5-s."}, {"source": "blueprints/us-equities/acceptance-wave/factors-regimes.md: FINRA consolidatedShortInterest is not a realtime squeeze signal", "status": "repository source read"} ], "population": "D_daily", - "covariate": "DTC = the most recent FINRA short interest published before the decision (publication and settlement lag respected) / 20-session median share volume ending t; terciles within each decision date", + "covariate": "DTC = the most recent FINRA short interest published before the decision (publication and settlement lag respected) / 20-session median share volume ending t; terciles by tercile_rule (trailing 252-session breakpoints of D)", "exit_arms": {"b_lane": "as H1"}, "sizing": "1x; the overlay filters and never sizes", "primary_tests": [ {"id": "H5-p", "statistic": "mean net(high DTC) - mean net(low DTC)", "alternative": "less than 0"}, - {"id": "H5-s", "statistic": "mean net(high DTC) - mean net(middle and low DTC pooled)", "alternative": "greater than 0 (squeeze)"} + {"id": "H5-s", "statistic": "mean net(high DTC) - mean net(middle and low DTC pooled)", "alternative": "greater than 0 (squeeze)", "prior": "none sourced: Hong et al. point the other way (H5-p) and the squeeze base-rate statement rests on an unverified draft. H5-s is a secondary test with no directional prior and is expected to be not supported."} ], "tradable_cells": "3 DTC terciles", - "falsifier": "H5-p is falsified if the difference is not negative at the corrected level; H5-s if the high-DTC tercile does not beat the rest.", + "not_supported_if": "H5-p (or H5-s) is labelled not_supported_mde_excluded.", "data_status": "gated on FINRA short-interest access and terms, which the user must establish; bi-monthly, an overlay only", "chronology": "common, restricted to sessions whose decision has a published figure. If published history does not cover at least 1 development year and the whole validation year, H5 is not tested and both tests score p = 1." }, "H6": { "name": "Execution parity: broker-held exits versus client-side ratchet (engineering, not alpha)", - "evidence": [ - {"source": "https://docs.alpaca.markets/docs/orders-at-alpaca (trailing_stop with trail_price or trail_percent; OCO; bracket; OTO)", "status": "trailing_stop survives; bracket contested and corrected to OCO for a held position"}, - {"source": "https://docs.alpaca.markets/reference/deleteallorders-1", "status": "survives"}, - {"source": "native_adapter.py builds only LIMIT/DAY orders; native_strategy.replace_exit cancels and resubmits when the price basis moves", "status": "repository source read"} - ], - "population": "positions opened in Alpaca paper by the paper candidate (mechanics-only unless a v3 rule has passed its holdout), after the OCO and trailing_stop adapter paths exist. Each position's arm is the parity of the first byte of sha256(client_order_id). No order is created for the test itself.", - "exit_arms": { - "a_client": "the current exits.py chain with cancel-then-new replacement", - "b_broker": "trailing_stop with trail_percent = effective_trailing_bps at entry / 100, plus an OCO for the stop and take-profit legs; time, risk-off, force and halt exits stay client-side" - }, - "shadow": "for each position, the other arm's trigger time and reference price are computed from the same quote stream for paired diagnostics, never traded", - "primary_tests": [ - {"id": "H6-e", "statistic": "median of (realized exit price - trigger reference) in bps, arm b minus arm a", "alternative": "equivalence within +/-25 bps (TOST)"}, - {"id": "H6-c", "statistic": "log(mean REST calls per closed position, arm a / arm b)", "alternative": "greater than log(2)"} - ], - "also_falsified_by": "any paper partial-fill, trigger-price or trigger-reference behaviour that departs from the documented semantics (recorded per event)", - "sizing": "parent sizing at 1x", - "data_status": "available now (Alpaca paper) once the adapter change lands; paper fills are simulated, so this is sim-to-paper evidence, not market evidence", - "chronology": {"development": "first 40 closed positions per arm", "validation": "next 60 per arm", "holdout": "next 150 per arm", "note": "prospective paper only; disjoint from every historical window"} + "moved_to": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json", + "why": "H6 accrues on a live paper timeline unrelated to the 2020 validation window or the 252-session holdout, and its equivalence test needs larger stages than this family's. It is its own engineering protocol with its own two-test family; it uses no slot of the alpha family, H1-H5 never wait for it, and its result never opens this protocol's holdout." } }, "multiple_testing": { - "family": "one family across H1-H6: the 10 non-tradable primary tests (H1-D, H1-I, H2-D, H2-I, H3-c, H4, H5-p, H5-s, H6-e, H6-c) plus the 27 tradable cells (H1 18, H2 4, H3 2, H5 3), m = 37", - "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout. An item not carried to a stage scores p = 1 there. Two-sided tests use 2 x min(one-sided p).", - "strictest_level": 0.0013514, + "family": "one family across H1-H5: the 8 non-tradable primary tests (H1-D, H1-I, H2-D, H2-I, H3-c, H4, H5-p, H5-s) plus the 27 tradable cells (H1 18, H2 4, H3 2, H5 3), m = 35. H6 is not in this family.", + "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout. An item not carried to a stage, or not tested for lack of data, scores p = 1 there. Two-sided tests (H3-c, H4) use 2 x min(one-sided p).", + "strictest_level": 0.0014286, "development_screen": "unadjusted p <= 0.05 in the stated direction, with the minimum sample; development only nominates items for validation", - "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) on other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 37) = 4.77e-5. An item that passes Holm but not this level is labelled v3-family significant, lineage-unconfirmed.", + "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) on other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 35) = 4.78e-5. With B = 100,000 the smallest bootstrap p is 1.0e-5 one-sided and 2.0e-5 two-sided, both below that level. Each p is reported beside a normal-tail p from the same bootstrap standard error. An item that passes Holm but not this level is labelled v3-family significant, lineage-unconfirmed.", "tests_not_in_family": "BH (q = 0.10) diagnostics across tercile, year, price-tier and halt-covariate splits are reported but can never open the holdout" }, "minimum_sample": { "tradable_cell": {"development": 300, "validation": 150, "holdout": 150}, "difference_test_per_group": {"development": 150, "validation": 100, "holdout": 100}, - "rule": "A stage below its minimum reports the item as underpowered; it cannot pass that stage." + "rule": "A stage below its minimum reports the item as underpowered (outcome_reporting); it cannot pass that stage." }, "minimum_detectable_effect": { - "formula": "MDE = (z(1 - 0.05/37) + z(0.80)) x sigma x sqrt(DEFF / n) for one cell; x sqrt(2) for a two-group difference with n per group", - "z_sum": 3.8413, + "formula": "MDE = (z_alpha + z(0.80)) x sigma x sqrt(DEFF / n) for one cell; x sqrt(2) for a two-group difference with n per group. z_alpha = z(1 - 0.05/35) = 2.983 for one-sided tests and z(1 - 0.05/70) = 3.189 for the two-sided H3-c and H4.", + "z_sum_one_sided": 3.8243, + "z_sum_two_sided": 4.0304, "design_effect_DEFF": 1.5, "sigma_planning": { "30_to_60_minute_arms": 0.10, @@ -211,32 +219,41 @@ "5_session_arms": 0.35 }, "sigma_provenance": "0.10 and 0.25 are (P95 - P5) / 3.29 from #162 v1 development aggregates (07:00|G0.20|V1000000 X2 and X1: P5 -15.0% / P95 +18.0%, and P5 -42.1% / P95 +39.5%); normal approximation, which understates fat tails. 0.35 is an assumption with no measured source. DEFF 1.5 for session clustering is an assumption.", - "mde_percent_single_cell": { - "n100": {"sigma_0.10": 4.70, "sigma_0.25": 11.76, "sigma_0.35": 16.47}, - "n200": {"sigma_0.10": 3.33, "sigma_0.25": 8.32, "sigma_0.35": 11.64}, - "n300": {"sigma_0.10": 2.72, "sigma_0.25": 6.79, "sigma_0.35": 9.51}, - "n600": {"sigma_0.10": 1.92, "sigma_0.25": 4.80, "sigma_0.35": 6.72} + "mde_percent_single_cell_one_sided": { + "n100": {"sigma_0.10": 4.68, "sigma_0.25": 11.71, "sigma_0.35": 16.39}, + "n200": {"sigma_0.10": 3.31, "sigma_0.25": 8.28, "sigma_0.35": 11.59}, + "n300": {"sigma_0.10": 2.70, "sigma_0.25": 6.76, "sigma_0.35": 9.46}, + "n600": {"sigma_0.10": 1.91, "sigma_0.25": 4.78, "sigma_0.35": 6.69} }, - "mde_percent_two_group_difference": { - "n100": {"sigma_0.10": 6.65, "sigma_0.25": 16.63, "sigma_0.35": 23.29}, - "n200": {"sigma_0.10": 4.70, "sigma_0.25": 11.76, "sigma_0.35": 16.47}, - "n300": {"sigma_0.10": 3.84, "sigma_0.25": 9.60, "sigma_0.35": 13.44}, - "n600": {"sigma_0.10": 2.72, "sigma_0.25": 6.79, "sigma_0.35": 9.51} + "mde_percent_two_group_difference_one_sided": { + "n100": {"sigma_0.10": 6.62, "sigma_0.25": 16.56, "sigma_0.35": 23.18}, + "n200": {"sigma_0.10": 4.68, "sigma_0.25": 11.71, "sigma_0.35": 16.39}, + "n300": {"sigma_0.10": 3.82, "sigma_0.25": 9.56, "sigma_0.35": 13.39}, + "n600": {"sigma_0.10": 2.70, "sigma_0.25": 6.76, "sigma_0.35": 9.46} }, - "h6_equivalence": "TOST margin 25 bps at the strictest family level (two one-sided tests at 0.05/37, a 1 - 2 x 0.00135 interval, z = 3.00); planning sigma of exit slippage 50 bps (assumption). At the 150-per-arm holdout the interval half-width is about 3.00 x 50 x sqrt(2/150) = 17 bps, so equivalence passes only if the observed difference is under about 8 bps; at 60 per arm the half-width (27 bps) exceeds the margin and no pass is possible", - "reading": "Only large effects can pass. A miss at these sizes is reported as underpowered for smaller effects, never as proof of no effect." + "mde_percent_two_group_difference_two_sided": { + "n100": {"sigma_0.10": 6.98, "sigma_0.25": 17.45, "sigma_0.35": 24.43}, + "n200": {"sigma_0.10": 4.94, "sigma_0.25": 12.34, "sigma_0.35": 17.28}, + "n300": {"sigma_0.10": 4.03, "sigma_0.25": 10.08, "sigma_0.35": 14.11}, + "n600": {"sigma_0.10": 2.85, "sigma_0.25": 7.12, "sigma_0.35": 9.97} + }, + "reading": "Only large effects can pass or be excluded. The literature's effects (about 1% a month for MAX and days to cover) are far below these MDEs, so a miss on H1-D, H1-I or H5 will most likely be labelled underpowered, and v3 is informative only about large mover-specific effects." }, "cost_model": { "base_table": { "path": "blueprints/us-equities/mover-early-entry/evidence/cost-table-run-v1.json", - "commit": "92062e8", + "commit": "aa6fc791bbd4af3499317673989d2e90a522c257", "git_blob": "197e71ada3c8249f2b49b168e4ce00a2cdf28628", "sha256": "be50cbdfd75c5c4b0a66286bf9edb40c9ce49add719b6b5e1b0cdf93064eaa13", - "note": "the same sha256 is recorded as inputs.cost_table_sha256 in #162's evidence/summary-dev-val-run-v1.json. Reused by reference; v3 refuses a table with another hash." + "note": "unchanged from the PR branch commit 92062e8 to main at aa6fc79; D7 records that the frozen table reproduces byte for byte under the stamp-window check. The same sha256 is recorded as inputs.cost_table_sha256 in #162's evidence/summary-dev-val-run-v1.json. Reused by reference; v3 refuses a table with another hash." + }, + "lookup": { + "cell_key": "time_bucket|price_tier|dv_tier, per side, from that side's fill: time bucket of the fill timestamp, price tier of the fill price, and dv tier of the session's cumulative dollar volume at the fill timestamp (rules.py Bars.cum_dv, the summed vwap x volume of bars starting before the timestamp; the same input v1 evaluate.py passes as entry_cum_dv and exit_cum_dv). For a D entry at 09:35 this is t+1's cumulative dollar volume at 09:35; for a 5-session exit it is the exit session's cumulative dollar volume at the exit timestamp.", + "buckets": "time buckets 04:00-08:00, 08:00-09:30, 09:30-10:00, 10:00-16:01 ET; price tiers < $2, $2-5, $5-20, >= $20; dv tiers < $1M, $1-5M, >= $5M" }, "fix_1_monotone_dollar_volume": { "finding": "#162 review item costs-F1: cells under $1M dollar volume fall back to a time x price pool that costs about half the $1-5M cell (for example 3|2|0 0.24% vs 3|2|1 0.75%)", - "rule": "for each time x price bucket, hs'(>= $5M) = hs(>= $5M); hs'($1-5M) = max(hs($1-5M), hs'(>= $5M)); hs'(< $1M) = max(hs(< $1M), hs'($1-5M)). A less liquid tier is never cheaper than a more liquid one.", + "rule": "for each time x price bucket, hs'(>= $5M) = hs(>= $5M); hs'($1-5M) = max(hs($1-5M), hs'(>= $5M)); hs'(< $1M) = max(hs(< $1M), hs'($1-5M)). A less liquid tier is never cheaper than a more liquid one. Applied last, after out_of_period_spreads.", "cells_changed_bps_before_after": { "1|0|0": [34.29, 38.04], "1|1|0": [27.97, 44.16], @@ -246,7 +263,7 @@ "3|1|0": [27.04, 57.09], "3|2|0": [24.09, 74.50] }, - "cell_key": "time_bucket|price_tier|dv_tier; time buckets 04:00-08:00, 08:00-09:30, 09:30-10:00, 10:00-16:01 ET; price tiers < $2, $2-5, $5-20, >= $20; dv tiers < $1M, $1-5M, >= $5M" + "note": "the before/after values are for the base table alone; the final values follow the 2017-2019 re-measure." }, "fix_2_impact_and_capacity": { "finding": "#162 review item costs-F4: pass statistics use a fixed $20k notional with no impact term and assume full fills", @@ -254,42 +271,47 @@ "impact": "impact per side (return) = c x sigma_d x sqrt(q), where q = filled notional / 20-session median dollar volume and sigma_d = 20-session standard deviation of daily log returns, both known at the decision. Primary c = 1.0; sensitivities c = 0.5 and 2.0.", "provenance": "The square-root functional form and the c grid are design choices bracketing the unknown; the sweep supplies no source for them." }, - "out_of_period_spreads": "The base table was measured on 2021-2023 quotes. Before the freeze, a 1-in-20 hash sample of development (2017-2019) entries is quoted with quotes.py's method at 92062e8; each cell's cost is then max(hs' from the base table, the 2017-2019 cell). Quotes are read at exit-agnostic timestamps only; no exit path is computed.", + "out_of_period_spreads": { + "sample": "Before the freeze, 1 in 20 development (2017-2019) D and I entries is selected by quotes.py's sample_key at aa6fc79: int.from_bytes(sha256('symbol|session|hhmm')[:8], 'big') % 20 == 0, with the v3 entry's symbol, entry session and entry HH:MM ET. Each selected entry is stamped at its entry timestamp, entry + 15 min, entry + 60 min and 5 minutes before that session's close (the v1 stamp set), all exit-agnostic; no exit path is computed.", + "table": "cells are built with quotes.py's table method at aa6fc79, including the D7 stamp-window guard and v1's sparse-cell fallbacks: a cell with >= 30 observations (MIN_CELL) uses its trimmed mean, else the time x price pool if it has >= 30, else the table's 90th percentile", + "combine": "each cell's cost is max(hs from the base table, hs from the 2017-2019 table); fix_1 is then applied to the result" + }, "per_side": "1.25 x hs' + impact, for continuous and auction sides alike (v1's 0.5x auction discount is not reused)", - "fees": "#162 fees.json (sha256 fa1f468b80f78dc0c050948123116f577e38ae9f8c03f9fb360d11042a3fed3d) by reference, extended before the freeze with SEC Section 31 and FINRA TAF rates in force during 2017-2020 from primary sources; Alpaca commission 0; IBKR tiered as a broker variant", + "fees": "#162 fees.json at aa6fc79 (sha256 fa1f468b80f78dc0c050948123116f577e38ae9f8c03f9fb360d11042a3fed3d) by reference, extended before the freeze with SEC Section 31 and FINRA TAF rates in force during 2017-2020 from primary sources; Alpaca commission 0; IBKR tiered as a broker variant", "stress": "2 x (spread + slippage) with c = 2.0", "break_even": "the cost multiple at which each cell's mean net return reaches 0 is reported" }, "statistics": { "estimator": "trade-level mean net return; also median, 1%-winsorised mean, mean without the top 5 sessions and mean of per-session means", - "bootstrap": "circular session block bootstrap, B = 20,000; block length 5 sessions for same-session arms and 10 sessions for multi-session arms; difference tests resample sessions jointly for both groups; seed = the first 8 bytes of sha256(frozen protocol id), big-endian", - "p_value": "one-sided p = (1 + #{b : stat_b on the null side of 0}) / (B + 1)", + "bootstrap": "circular session block bootstrap, B = 100,000; block length 5 sessions for same-session arms and 10 sessions for multi-session arms; difference tests resample sessions jointly for both groups; seed = the first 8 bytes of sha256(frozen protocol id), big-endian", + "p_value": "one-sided p = (1 + #{b : stat_b on the null side of 0}) / (B + 1); two-sided p = min(1, 2 x the smaller one-sided p)", "robustness_required_for_a_pass": "a tradable cell's validation and holdout passes also need a positive winsorised mean, mean without the top 5 sessions and mean of per-session means", - "sensitivities": ["two-way (session, symbol) clustering", "without split-day and fallback-price rows", "2020 reported alone", "survivorship-limited label before 2020"] + "sensitivities": ["two-way (session, symbol) clustering", "without split-day and fallback-price rows", "2020 reported alone", "survivorship-limited label before 2020", "validation restricted to names with 20-session median dollar volume < $2M (outside broad-universe's descriptive lane)"] }, "holdout_gate": { - "opens_only_for": "items that passed validation: a primary test or tradable cell with Holm-adjusted validation p <= 0.05 in the 37-item family, the minimum validation sample and, for a tradable cell, the robustness means. At most 5 tradable cells (lowest validation p) and every validated primary test.", + "opens_only_for": "items that passed validation: a primary test or tradable cell with Holm-adjusted validation p <= 0.05 in the 35-item family, the minimum validation sample and, for a tradable cell, the robustness means. At most 5 tradable cells (lowest validation p) and every validated primary test.", + "h4_scope": "a validated H4 authorizes computing, in the holdout, the a_reopen and b_ignore exits of halted parent trades only (H4 parent_trades), whether or not any parent cell validated. It authorizes no non-halted parent outcome and no tradable-cell statistic.", "no_pass": "If nothing validates, the holdout is never labelled or read, and that is the result.", "prospective_collection": "Holdout-window data are collected forward into a sealed private snapshot whose sha256 is committed per collection batch. No outcome label, forward return or exit is computed from it before the gate opens.", "evaluator_refusals": [ "protocol status is not frozen, or its sha256 differs from the frozen commit", - "the validation results file is missing or its sha256 differs from the one committed", + "the validation results file is missing, its sha256 differs from the one committed, or that commit is not earlier than the first holdout session", "the list of validated items is empty", - "a requested item is not in that list", + "a requested item is not in that list, or a requested H4 read asks for more than h4_scope", "the access log has no committed 'granted' entry for this run", "a previous holdout run exists (the holdout is read once; failed or partial runs stay in the log and are never called untouched)" ], "access_log": { "path": "blueprints/us-equities/mover-v3/holdout-access-log.jsonl", "mode": "append-only JSON lines, committed and pushed before the read it authorizes", - "fields": ["utc", "actor_role", "protocol_id", "protocol_sha256", "code_revision", "validation_results_sha256", "requested_items", "purpose", "decision (granted or refused)", "snapshot_sha256", "rows_read", "results_sha256"], - "rule": "Any holdout read without a preceding committed granted entry voids the holdout for every item. Refusals are logged too. Collection batches are logged with purpose 'collect' and rows_read 0." + "fields": ["utc", "actor_role", "protocol_id", "protocol_sha256", "code_revision", "validation_results_sha256", "requested_items", "purpose", "decision (granted or refused)", "snapshot_sha256", "rows_read", "results_sha256", "exposed_symbol_sessions"], + "rule": "Any holdout read without a preceding committed granted entry voids the holdout for every item. Refusals are logged too. Collection batches are logged with purpose 'collect' and rows_read 0, and list the accrual exposure of that batch (chronology.holdout.accrual_exposure)." } }, - "leverage_and_exits": "Research runs use 1x equal notional. Paper use of any passing rule follows the README's exit and leverage policy: broker-held OCO/trailing legs where the semantics fit, the leverage-schedule-v1-20260922 ceiling, and the leverage-ladder-1x/2x/4x gate rows in catalogs/us-equities/gates-20260922.json, one rung at a time with automatic step-down. The edge-shrink (fractional Kelly) term stays 0 until a holdout pass.", + "leverage_and_exits": "Research runs use 1x equal notional. Paper use of any passing rule follows the README's exit and leverage policy: at most one broker-held closing order per share (a trailing_stop or an OCO per quantity tranche, as the Alpaca docs require), the leverage-schedule-v1-20260922 ceiling, and the leverage-ladder-1x/2x/4x gate rows in catalogs/us-equities/gates-20260922.json, one rung at a time with automatic step-down. The edge-shrink (fractional Kelly) term stays 0 until a holdout pass.", "freeze_preconditions": [ "An independent pre-outcome review of this draft from a different session and model family, with every finding resolved in a dated record", - "Direct reads of Lou-Polk-Skouras 2019, Akbas-Boehmer-Jiang-Koch 2022 and MacLean-Thorp-Ziemba 2010, or removal of every claim that depends on them", + "Direct full-text reads of Lou-Polk-Skouras 2019, Akbas-Boehmer-Jiang-Koch 2022 and MacLean-Thorp-Ziemba 2010, or removal of every claim that depends on them (the two JFE DOIs resolve in Crossref; that is metadata only)", "The 2017-2019 development cost sample fixed by rule and measured; fees extended to 2017-2020 from primary sources", "Metadata-only coverage checks: the Databento get_cost quote and XNAS.ITCH coverage start (H4); FINRA short-interest history start and terms (H5). No outcome is computed.", "Study code implementing this protocol committed with synthetic tests before any outcome, and the evaluator's holdout refusals tested", diff --git a/catalogs/us-equities/decision-index.json b/catalogs/us-equities/decision-index.json index 3c1d4c2e2..8b406d29f 100644 --- a/catalogs/us-equities/decision-index.json +++ b/catalogs/us-equities/decision-index.json @@ -386,7 +386,7 @@ "repositories": 844, "public_star_repositories": 342, "beyond_public_stars": 502, - "references": 1968, + "references": 1969, "repositories_by_record_type": { "catalog_card": 148, "component_record": 69, @@ -12035,6 +12035,14 @@ "id": "backtrader", "decision": "alternative", "evidence_level": "source_review" + }, + { + "kind": "research_supplement", + "path": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "pointer": "/repository_decisions/7", + "id": "mover-v3-backtrader-no-change", + "decision": "no_change_duplicate_of_catalog", + "evidence_level": "live_repository_metadata" } ] }, diff --git a/catalogs/us-equities/mover-v3-sweep-20260924.json b/catalogs/us-equities/mover-v3-sweep-20260924.json index 0d8a5571c..0a3e7ef8d 100644 --- a/catalogs/us-equities/mover-v3-sweep-20260924.json +++ b/catalogs/us-equities/mover-v3-sweep-20260924.json @@ -15,7 +15,8 @@ "live_repository_metadata": "a live GitHub API recheck of push date, licence and archive state", "existing_record": "already recorded elsewhere in this repository with equal or stronger evidence" }, - "registration": "Only /repository_decisions is registered with the decision index (python3 scripts/catalog_decisions.py --write --supplement catalogs/us-equities/mover-v3-sweep-20260924.json#/repository_decisions), because the union accepts only records that name a GitHub repository. /non_repository_decisions holds the papers, APIs, exchange pages and vendors as data.", + "registration": "Only /repository_decisions is registered with the decision index (python3 scripts/catalog_decisions.py --write --supplement catalogs/us-equities/mover-v3-sweep-20260924.json#/repository_decisions), because the union accepts only records that name a GitHub repository. Every record that names a GitHub repository is in /repository_decisions, including the no-change duplicate mementum/backtrader, with one intentional exception: the no-change record for VladPetrariu/Qullamaggie-breakout-scanner (mover-v3-qullamaggie-scanner-no-change) stays in /non_repository_decisions, because the scanner is absent from the union and registering a non-adopted duplicate would add a new identity to the grand union and the token-native saturation audit partition for no decision value. Every registered repository was already in the union, so the repository count is unchanged. /non_repository_decisions holds the papers, APIs, exchange pages and vendors as data.", + "revised_at": "2026-09-24 review fix round, rebased on main aa6fc791bbd4af3499317673989d2e90a522c257 (PR #162 merged)", "repository_decisions": [ { "id": "mover-v3-alpaca-py-order-classes", @@ -24,7 +25,8 @@ "decision": "add_execution_method_candidate", "sweep_status": "trailing_stop and DELETE /v2/orders survive; bracket contested and corrected to OCO for a held position; OTO folded into the OCO/trailing path", "evidence_level": "official_docs_metadata", - "native_execution": "none; Alpaca paper semantics for trailing_stop, OCO and bracket are undocumented and are what H6 measures", + "native_execution": "none; Alpaca's simulated paper fills for trailing_stop, OCO and bracket (partials, trigger price, trigger reference) are unmeasured and are what the H6 engineering draft measures", + "documented_constraints": "orders-at-alpaca, read 2026-09-24: 'Trailing stop orders are currently supported only with single orders. However, we plan to support trailing stop as the stop loss leg of bracket/OCO orders in the future.' and 'you would not be able to submit two conditional closing orders since Alpaca's system would view one of the two orders as exceeding the available position quantity'. Each share therefore carries at most one broker-held closing order: a trailing_stop alone or an OCO alone, per quantity tranche.", "reason": "The SDK path to the broker-held exit order classes and single-call cancel-all that the v3 exit policy needs. native_adapter.py builds only LIMIT/DAY orders and cancels in a per-order loop today. A verifier named TradingClient.cancel_orders() as the SDK method for DELETE /v2/orders; that method was not source-read in this record.", "links": ["mover-v3-alpaca-order-classes", "mover-v3-alpaca-cancel-all"], "sources": [ @@ -40,7 +42,7 @@ "sweep_status": "contested: corrected from raw ITCH H/h/J messages to Databento's normalized status schema", "evidence_level": "unpinned_source_read", "native_execution": "none; the cost quote and XNAS.ITCH coverage start are unverified", - "reason": "Candidate source for historical halt and LULD state (H4 and simulation halt fidelity), closing the open item in evidence/artifacts/gap-wave2-20260923/us-equities__market-data-reference/README.md. ITCH covers Nasdaq-listed names only. The existing pinned record in catalogs/us-equities/architecture/trading.json is unchanged; access goes through databento/databento-python (already conditional), whose Historical.metadata.get_cost gives the quote.", + "reason": "Candidate source for historical halt and LULD state (H4 and simulation halt fidelity), closing the open item in evidence/artifacts/gap-wave2-20260923/us-equities__market-data-reference/README.md. Per the TotalView-ITCH 5.0 specification (section 1.2.2, read 2026-09-24), ITCH covers 'all exchange-listed equities securities traded via the Nasdaq execution system': halt and resume state (H, Q, T) is available for every Nasdaq-traded security, while the LULD pause state (P, 'Nasdaq-listed securities only') and the J auction collar are Nasdaq-listed only, and a pause in another listing is 'treated simply as a halt'. The existing pinned record in catalogs/us-equities/architecture/trading.json is unchanged; access goes through databento/databento-python (already conditional), whose Historical.metadata.get_cost gives the quote.", "sources": [ "https://databento.com/docs/venues-and-datasets/xnas-itch", "https://github.com/databento/dbn/blob/main/rust/dbn/src/record.rs", @@ -114,6 +116,19 @@ "sources": [ "https://github.com/polakowo/vectorbt" ] + }, + { + "id": "mover-v3-backtrader-no-change", + "repository": "https://github.com/mementum/backtrader", + "catalog_action": "no-change", + "decision": "no_change_duplicate_of_catalog", + "sweep_status": "killed: backtrader-family-closed duplicates the catalog", + "evidence_level": "live_repository_metadata", + "native_execution": "none", + "reason": "Already catalogued. Recorded so the next sweep does not re-search it: GPL-3.0, last push 2024-08-19.", + "sources": [ + "https://github.com/mementum/backtrader" + ] } ], "non_repository_decisions": [ @@ -137,10 +152,12 @@ "sweep_status": "contested: the original citation was a secondary Lachance (2023) summary; the substance was not refuted", "evidence_level": "literature_metadata", "native_execution": "none", - "reason": "The corrected primaries replace the secondary summary. Both still need a direct read (DOIs unverified). Evidence comes from large, liquid names.", + "reason": "The corrected primaries replace the secondary summary: Lou, Polk, Skouras, 'A tug of war: Overnight versus intraday expected returns', JFE 134(1):192-213 (2019); Akbas, Boehmer, Jiang, Koch, 'Overnight returns, daytime reversals, and future stock returns', JFE 145(3):850-875 (2022). Both DOIs resolved in Crossref metadata on 2026-09-24; neither full text was read. The sweep's original source, Lachance (2023), Review of Financial Economics, doi 10.1002/rfe.1180, is a secondary summary, kept only as the refuted original citation and not as evidence. Evidence comes from large, liquid names.", "sources": [ - "https://onlinelibrary.wiley.com/doi/full/10.1002/rfe.1180" - ] + "https://doi.org/10.1016/j.jfineco.2019.03.011", + "https://doi.org/10.1016/j.jfineco.2021.09.019" + ], + "refuted_original_citation": "https://onlinelibrary.wiley.com/doi/full/10.1002/rfe.1180" }, { "id": "mover-v3-attention-buying", @@ -163,7 +180,7 @@ "sweep_status": "survives", "evidence_level": "literature_source_review", "native_execution": "none", - "reason": "Days to cover beats the raw short ratio, and high days to cover predicts lower returns. Squeezes are episodic. Bi-monthly publication makes it an overlay only.", + "reason": "Days to cover beats the raw short ratio, and high days to cover predicts lower returns. The paper makes no squeeze claim: the sweep's 'squeezes are episodic, not a base rate' came from an unverified S3 Partners EFMA 2025 conference draft, which is not recorded as evidence here, so the H5 squeeze test has no sourced prior. Bi-monthly publication makes it an overlay only.", "sources": [ "https://www.nber.org/system/files/working_papers/w21166/w21166.pdf" ] @@ -175,7 +192,7 @@ "sweep_status": "trailing_stop survives; bracket contested and corrected to OCO for a held position (a bracket's take-profit closes the whole position); OTO folded", "evidence_level": "official_docs_metadata", "native_execution": "none; native paper execution is missing (H6)", - "reason": "Broker-held legs replace the client-side cancel-and-resubmit ratchet and cost no further calls when they fill.", + "reason": "Broker-held legs replace the client-side cancel-and-resubmit ratchet and cost no further calls when they fill. Documented constraint: a trailing_stop is supported only as a single order, and a second conditional closing order on the same shares is rejected as exceeding the available quantity, so each quantity tranche carries either one trailing_stop or one OCO.", "sources": [ "https://docs.alpaca.markets/docs/orders-at-alpaca" ] @@ -228,7 +245,7 @@ "sweep_status": "contested: one misattributed source corrected; the claim stands", "evidence_level": "official_docs_metadata", "native_execution": "none", - "reason": "Public domain, 10 req/s. Frames returns the latest filed fact with no accession ID, so a name that clears the screen must be confirmed from companyfacts' dated per-accession fact before the float counts as point-in-time.", + "reason": "Public domain, 10 req/s. Frames give one last-filed value per entity for the requested period; each row carries accn, cik, entityName, loc, end and val (checked on a live frames response on 2026-09-24) but no filed date, and superseded vintages are absent. A name that clears the screen must be confirmed from companyfacts (or submissions by accn) for the filed date and the original vintage before the float counts as point-in-time.", "sources": [ "https://www.sec.gov/search-filings/edgar-application-programming-interfaces", "https://www.sec.gov/os/webmaster-faq" @@ -286,16 +303,16 @@ ] }, { - "id": "mover-v3-no-change-duplicates", - "item": "Qullamaggie breakout scanner (duplicates the catalogued tradermonty/claude-trading-skills vcp-screener) and the backtrader family", + "id": "mover-v3-qullamaggie-scanner-no-change", + "item": "VladPetrariu/Qullamaggie-breakout-scanner (https://github.com/VladPetrariu/Qullamaggie-breakout-scanner), a duplicate of the catalogued tradermonty/claude-trading-skills vcp-screener", "catalog_action": "no-change", - "sweep_status": "scanner contested (duplicate); backtrader-family-closed killed (duplicates the catalog)", + "decision": "no_change_duplicate_not_adopted", + "sweep_status": "contested: duplicates the catalogued tradermonty/claude-trading-skills vcp-screener", "evidence_level": "live_repository_metadata", "native_execution": "none", - "reason": "Recorded so the next sweep does not re-search them. The scanner is a single-maintainer, yfinance-only project (MIT, pushed 2026-09-16); only its feature vocabulary is of interest, and it is already covered. backtrader: GPL-3.0, last push 2024-08-19.", + "reason": "Recorded so the next sweep does not re-search it; not adopted and not a candidate. A single-maintainer, yfinance-only project (MIT, pushed 2026-09-16); only its feature vocabulary is of interest, and that is already covered. Intentionally not registered in the decision index: it is the only repository-naming record here that is absent from the union, and registering a non-adopted duplicate would add a new identity to the grand union and to the token-native saturation audit's identity partition for no decision value.", "sources": [ - "https://github.com/VladPetrariu/Qullamaggie-breakout-scanner", - "https://github.com/mementum/backtrader" + "https://github.com/VladPetrariu/Qullamaggie-breakout-scanner" ] }, { diff --git a/manifests/evidence.json b/manifests/evidence.json index d42ca0a0e..594870470 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -918,7 +918,7 @@ "prometheus", "loki" ], - "claim": "After native sign-in restored readiness, the freshly reproduced SDK completed a real LEAN backtest\u2192Dagu\u2192DuckDB\u2192GPT-6 Astra\u2192Claude Opus 5 prepared-packet research workflow; both native reports passed structural citation/value/unit/scope checks and the accepted Astra report was bound into Claude's prompt by verified hashes.", + "claim": "After native sign-in restored readiness, the freshly reproduced SDK completed a real LEAN backtest→Dagu→DuckDB→GPT-6 Astra→Claude Opus 5 prepared-packet research workflow; both native reports passed structural citation/value/unit/scope checks and the accepted Astra report was bound into Claude's prompt by verified hashes.", "limitations": [ "This validates a bounded historical simulation research workflow on the authoring host, not a profitable strategy, current market source, broker execution or unattended production service.", "The two reported model tasks used no model tools. Their native hooks and telemetry remain separate evidence; this does not repeat every MCP, memory or GPU-RAG behavior.", @@ -1900,7 +1900,7 @@ "component_ids": [ "ai-memory" ], - "claim": "Mac arm64 synthetic2.3.1\u21922.3.2 migration, scoped exact reads/FTS, reserved and Unicode paths, restart and no-capture checks passed.", + "claim": "Mac arm64 synthetic2.3.1→2.3.2 migration, scoped exact reads/FTS, reserved and Unicode paths, restart and no-capture checks passed.", "limitations": [ "Separate synthetic store and explicit no-model policy only; no production migration claimed by this receipt.", "OS filesystem/network confinement was not established; failed restricted-sandbox attempt retained. Native execution succeeded without security overrides; Gatekeeper assessment rejected the ad-hoc distribution.", @@ -5392,7 +5392,7 @@ "bytes": 126 }, { - "path": "blueprints/convergence-practice/wsl-retrieval/seed/corpus/primary/notes/a\u00e9\u00e9.md", + "path": "blueprints/convergence-practice/wsl-retrieval/seed/corpus/primary/notes/aéé.md", "sha256": "cb36cd06858449475efbfaddaedfd78d20a2b7b4f30993bca02cd1cb360e0cb3", "bytes": 111 }, @@ -8446,6 +8446,21 @@ "sha256": "8af0f97b8f6581b9d74aa98f5eea7d76af2b782d0175747270c0345bd9f5344b", "bytes": 3358 }, + { + "path": "blueprints/us-equities/mover-v3/README.md", + "sha256": "10a7456a92fa9e0e1eadeee9e275b049bcca73e6207e1b18f38eee4832821f2f", + "bytes": 34215 + }, + { + "path": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json", + "sha256": "49f220eb6400a36f1041d3d943c8ae6fc6ae7dd19f424dac0b2b23e37117498f", + "bytes": 8570 + }, + { + "path": "blueprints/us-equities/mover-v3/protocol-draft.json", + "sha256": "8ed1070adbe17d380c70c49d58f6bb13ca05063892b7df35d9f878497fe30c44", + "bytes": 41093 + }, { "path": "blueprints/us-equities/nanosecond-replay/README.md", "sha256": "de230378bc13af4ffd17d39a142ae8c8a624a80bcc95272487ae0f0693b63644", @@ -9298,8 +9313,8 @@ }, { "path": "catalogs/us-equities/decision-index.json", - "sha256": "64ff0e784d915e37fb8f8235e9d1d249bbad4f7570751b2eedaf3ecc0065dacd", - "bytes": 722539 + "sha256": "43e3b625548ffffa63035c3562ab934748dfe074a371e1f8ab7ac299ef88a394", + "bytes": 722880 }, { "path": "catalogs/us-equities/engines-strategies.json", @@ -9371,6 +9386,11 @@ "sha256": "798b07a2703bb53cc253caa5b2a460fb75ed3b81ea9ebce7ecf24ef33671ad76", "bytes": 21253 }, + { + "path": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "sha256": "2efd9d4f533743b82465f74a43702c88512190c5bef1e9753fcdf59b962785d3", + "bytes": 23704 + }, { "path": "catalogs/us-equities/native-workflows.md", "sha256": "aae8aa3cf0232052cb4573df9ba553ef05523c5c0ed0180d98cea0fccd89c583", From 9f4b9f27b7d4e05654785aca20f0963c9f1a89cd Mon Sep 17 00:00:00 2001 From: Scout Date: Thu, 24 Sep 2026 03:25:13 -0400 Subject: [PATCH 3/8] Mover v3 review round 2: lock directions, fix data status at freeze, gate H2 and H6 Resolve the 27 findings of the two rereviews of 0b924e7 in the draft protocol, the H6 engineering draft and the README. Status stays draft_pending_independent_pre_outcome_review; frozen_before_outcomes false. - H3-c and H4 are two-sided only at development; the development sign locks a one-sided test at validation and the holdout. - H2, H4 and H5 are recorded at the freeze as tested or permanently not tested; later data needs a new protocol version. - The 2017-2019 cost sample moves after the freeze (its stamps are outcome prices). - Defined MDE sigmas, label bound, unequal groups, H3-c leg rule, holdout breakpoints outside #162's window, fixed holdout start, count-only access, segment-end censoring, early-close times, mid fills, D7 split/dividend accounting (evaluate_v2.py pinned), lineage denominator 1,107, tie-breaks and PCG64 streams. - H2 moved to gated: catalyst.py fails every historical filing's availability and has no Item parser or historical CIK map. - H6 gated with no position source; H6-c withdrawn as a test because the client-side arm needs fewer calls by construction. - Evidence manifest hashes recomputed for the three changed files. Co-Authored-By: Claude Opus 5.5 (1M context) --- blueprints/us-equities/mover-v3/README.md | 103 +++++++----- .../mover-v3/h6-execution-parity-draft.json | 65 +++++--- .../us-equities/mover-v3/protocol-draft.json | 157 ++++++++++-------- manifests/evidence.json | 12 +- 4 files changed, 198 insertions(+), 139 deletions(-) diff --git a/blueprints/us-equities/mover-v3/README.md b/blueprints/us-equities/mover-v3/README.md index 7f22b81e6..30ea5c40b 100644 --- a/blueprints/us-equities/mover-v3/README.md +++ b/blueprints/us-equities/mover-v3/README.md @@ -18,8 +18,8 @@ private because its verifier notes contain host paths. This README and the suppl early-entry v1 and v2 studies (`blueprints/us-equities/mover-early-entry/**`). **v1: 0 of 768 rule-exits pass development. v2: no pass in families E, F or P** (results sha256 `75a5004b...` after deviation D7). v3 does not modify those files. It reuses their pipeline only by reference, pinned to main at `aa6fc79` with git-blob and sha256 hashes -for `rules.py`, `quotes.py` and the cost table (`protocol-draft.json`). The pin includes D7's stamp-window guard in -`quotes.py`. The earlier pin `92062e8` survives only on the PR branch. v3 does not read #162's reserved holdout. The +for `rules.py`, `quotes.py`, the cost table and `evaluate_v2.py` (for D7's split and ex-dividend accounting only; +`protocol-draft.json`). The pin includes D7's stamp-window guard in `quotes.py`. The earlier pin `92062e8` survives only on the PR branch. v3 does not read #162's reserved holdout. The 2021 control segment of the catalyst experiment is permanently inspected and is not a fresh holdout. ## Why continuation is not supported @@ -69,23 +69,32 @@ exposure registry records this. | H | Question | Entry | Exit arms | Sizing | Not supported if | Data | |---|---|---|---|---|---|---| -| H1 | Does MAX (lottery) conditioning flip the sign? | D and I, with MAX(21 sessions) terciles as a covariate. Breakpoints come from the same population's events in the prior 252 sessions, so they are known at the decision; no new thresholds | (a) exit at min(entry + 30 min, 15:55); (b) the lane horizon (D: 15:55 on the 5th session; I: entry + 60 min); (c) same-session 15:55 | 1x, equal notional, capacity-capped | not supported: the high-MAX minus low-MAX cost-net mean for arm (b) excludes a negative effect of the MDE size, and no arm-by-tercile cell passes. A high-MAX outperformance is outside the one-sided test and supports no claim | **available now** (Alpaca SIP bars) | -| H2 | Do catalyst-verified moves beat pure attention spikes? | D and I, split by an 8-K Item 1.01 accepted before the decision (the existing `catalyst.py` contract) vs no qualifying filing | the lane horizon only | 1x | catalyst minus attention-only is not above 0 at the corrected level, or both groups fail | **available now** (SEC EDGAR, 10 req/s; SIP bars) | -| H3 | Do the overnight and intraday legs differ? | D | (a) intraday only: enter at 09:35, exit at 15:55 the same session; (b) overnight only: enter at 15:55, exit at the first eligible quote after the next 09:30 open; (c) the 5-session hold split ex post into 4 overnight legs (official close to official open) and 4 intraday legs (official open to official close), comparing mean per-leg returns (a diagnostic, not traded) | 1x. Arm (b) stays at 1x in any later paper use until the 2x rung and its overnight cap are clean | not supported: the per-leg difference excludes the MDE, and neither traded arm passes | **available now** | -| H4 | Does a halt-conditional exit beat ignoring the halt? | No new entry. Applies to every D and I lane-horizon (b_lane) trade, deduplicated by symbol, session and entry time, that meets a halt or, for a Nasdaq-listed name, an LULD pause (not only passing arms, so that a no-pass outcome still leaves a sample) | (a) exit at the reopening print or the first post-reopen quote; (b) hold to the lane horizon. No fills during a halt. Pause length is logged as a covariate, not a signal | inherits the parent arm | not supported: the (a) minus (b) difference excludes the MDE. Below the minimum halted trades it is underpowered. Either way the halt state stays a safety rule only | **gated** (Databento XNAS.ITCH status schema, paid, *contested*: corrected from raw ITCH messages to the normalized schema; Nasdaq Trader history as a short pilot) | -| H5 | Does days-to-cover crowding filter movers? | D split by days-to-cover tercile (short interest / ADV; trailing 252-session breakpoints), using the latest FINRA figure published before the decision, with its settlement lag | the lane horizon | 1x; a filter, never a size input | primary: high minus low DTC excludes a negative effect of the MDE size. Secondary squeeze test (no sourced prior): the high-DTC tercile excludes an outperformance of the MDE size | **gated** (FINRA short-interest access and terms) | -| H6 | Engineering, not alpha, in its own draft and two-test family: do broker-held exits match the client-side ratchet at fewer calls? | positions of the paper candidate, assigned to an arm by a hash of symbol, session and entry count, written to the ledger before the order is sent; no extra orders | (a) the current `exits.py` chain with cancel-then-new; (b) the quantity split into two tranches, each with exactly one broker-held closing order: an OCO (take-profit and stop) on the take-profit tranche and a `trailing_stop` (trail_percent from `effective_trailing_bps` at entry) on the rest. Time, risk-off, force and halt exits stay client-side | parent sizing, 1x | not supported: median exit slippage vs the same trigger reference is not shown equivalent within ±25 bps (TOST, 200 per arm per confirmatory stage); or paper partial-fill or trigger behaviour departs from the docs; or arm (b) does not use at least 2x fewer REST calls per closed trade | **available now** (Alpaca paper), after an adapter change | - -**Outcome labels.** Each test ends in one of three preregistered labels: *supported*; *not supported*, with the -excluded effect size stated; or *underpowered*. At the minimum samples the detectable effects are 4-24% (the MDE table -in the protocol draft), far above the literature's roughly 1% a month, so a miss on H1, H2 or H5 will most likely be -labelled underpowered. v3 can detect only large mover-specific effects. - -**Exposure and chronology.** Development is 2017-2019, validation 2020, and the holdout is the first 252 sessions -after the validation results are committed. The 2017-2020 windows are disjoint from #162's v1 and v2, but not -unseen. The broad-universe study computed outcomes for 2017-2021 on a descriptive lane covering every name with a -close of at least $1 and a median dollar volume of at least $2M. That lane is a superset of most of D and I. This -plan's author also read its S2 volume-shock headline for that period. Development and validation are therefore +| H1 | Does MAX (lottery) conditioning flip the sign? | D and I, with MAX(21 sessions) terciles as a covariate. Breakpoints come from the same population's events in the prior 252 sessions, so they are known at the decision; no new thresholds | (a) exit at min(entry + 30 min, 15:55); (b) the lane horizon (D: 15:55 on the 5th session; I: entry + 60 min); (c) same-session 15:55 | 1x, equal notional, capacity-capped | not supported: at the verdict stage, both high-MAX minus low-MAX tests for arm (b) (D and I) exclude a negative effect of the MDE size, and no H1 arm-by-tercile cell passes. A high-MAX outperformance is outside the one-sided test and supports no claim | **expected available** (Alpaca SIP bars and quotes); 2016-2020 quote and auction coverage is a freeze precondition | +| H2 | Do catalyst-verified moves beat pure attention spikes? | D and I, split by an 8-K Item 1.01 accepted before the decision vs a complete, parsed filing index with no qualifying filing; unknown identity or items are excluded from both groups | the lane horizon only | 1x | not supported: at the verdict stage, H2-D and H2-I (catalyst minus attention) are both labelled not supported with the MDE excluded, and no H2 cell passes | **gated**: `catalyst.py` makes every 2016-2020 filing ineligible (availability = max(acceptance, local observation)) and has no Item parser or historical ticker-to-CIK map. Needs a preregistered acceptance-time availability rule, an Item parser, an as-known CIK source and a coverage check before the freeze, else not tested | +| H3 | Do the overnight and intraday legs differ? | D | (a) intraday only: enter at 09:35, exit at 15:55 the same session; (b) overnight only: enter at 15:55 (300 s timeout), exit at the first eligible quote after the next 09:30 open; (c) the 5-session hold split ex post into 4 overnight legs (official close to official open) and 4 intraday legs (official open to official close), comparing mean per-leg returns (a diagnostic, not traded; a trade missing any leg is excluded). Two-sided at development; its direction is then locked | 1x. Arm (b) stays at 1x in any later paper use until the 2x rung and its overnight cap are clean | not supported: at the verdict stage, the per-leg difference excludes the MDE, and neither traded arm passes | **expected available**; 2016-2020 quote and auction coverage is a freeze precondition | +| H4 | Does a halt-conditional exit beat ignoring the halt? | No new entry. Applies to every D and I lane-horizon (b_lane) trade, deduplicated by symbol, session and entry time, that meets a halt or, for a Nasdaq-listed name, an LULD pause (not only passing arms, so that a no-pass outcome still leaves a sample) | (a) exit at the reopening-cross print (source fixed before the freeze) or the first post-reopen quote; (b) hold to the lane horizon. No fills during a halt. Both sides costed at the wider of the table and the realized post-reopen spread. Pause length is logged as a covariate, not a signal. Two-sided at development; its direction is then locked | inherits the parent arm | not supported: at the verdict stage, the (a) minus (b) difference excludes the MDE. Below the minimum halted trades it is underpowered. Either way the halt state stays a safety rule only | **gated** (Databento XNAS.ITCH status schema, paid, *contested*: corrected from raw ITCH messages to the normalized schema; Nasdaq Trader history as a short pilot) | +| H5 | Does days-to-cover crowding filter movers? | D split by days-to-cover tercile (short interest / ADV, both in the decision session's share basis; trailing 252-session breakpoints), using the latest FINRA figure published before the decision, with its settlement lag | the lane horizon | 1x; a filter, never a size input | primary (H5-p): high minus low DTC is labelled not supported with a negative effect of the MDE size excluded. Secondary squeeze test (H5-s, no sourced prior): high DTC minus the pooled middle and low terciles, reported separately. Either way at the verdict stage and with no H5 cell passing | **gated** (FINRA short-interest access and terms; history must cover the 2016 warm-up) | +| H6 | Engineering, not alpha, in its own draft and one-test family: do broker-held exits realize exit prices equivalent to client-side exits? | a named mechanics-only strategy at a fixed minimal notional, on symbol-sessions outside that session's D and I populations, assigned to an arm by a hash of symbol, session and entry count written to the ledger before the order is sent. Not yet defined, so H6 cannot accrue | (a) the current `exits.py` chain: client-side triggers, one exit limit, replace disabled (the shipped default); (b) the quantity split into two tranches, each with exactly one broker-held GTC closing order: an OCO (take-profit and stop) on the take-profit tranche and a `trailing_stop` (trail_percent from `effective_trailing_bps` at entry) on the rest. Time, risk-off, force and halt exits stay client-side | the strategy's fixed minimal notional | not supported: the median exit slippage of stop-type fills vs the same trigger reference is not shown equivalent within ±25 bps (TOST, 200 per arm per confirmatory stage); or paper partial-fill or trigger behaviour departs from the docs. REST calls per position are reported, not tested: arm (a) needs about 2, arm (b) at least 3 | **gated**: no position source; needs the strategy, a recorded decision on the no-manufactured-trades rule, the adapter change and a paper smoke test | + +**Outcome labels.** Each test ends in one of three preregistered labels: a pass; *not supported*, with the excluded +effect size stated; or *underpowered*. A pass is named by its stage: *nominated* at development, *screened* at +validation and *supported (confirmatory)* only at the holdout. Each hypothesis-level verdict names its stage too. At the +minimum samples the detectable effects are roughly 3-26% (the MDE tables in the protocol draft), far above the +literature's roughly 1% a month, so a miss on H1, H2 or H5 will most likely be labelled underpowered. v3 can detect +only large mover-specific effects. + +**Data status is fixed at the freeze.** H2, H4 and H5 are each recorded then as tested (data in hand) or not tested +(p = 1 at every stage, permanently). Data bought or obtained after the freeze can only start a new protocol version, +so it cannot change this family's Holm thresholds after results are seen. + +**Exposure and chronology.** Development is 2017-2019 and validation 2020. The holdout is 252 full sessions starting +at the 40th session after the freeze commit, provided the validation results are pushed before then (otherwise at the +next 63-session block boundary), so its start is not chosen after the results are seen. The 2017-2020 windows are +disjoint from #162's v1 and v2, but not unseen. The broad-universe study computed forward returns for every eligible +symbol-session in 2017-2021 (its C0 control), including a descriptive lane covering names with a close of at least $1, +a median dollar volume of at least $2M, 60 prior contiguous bars and no gap. C0 on that lane is the superset exposure +for most of D and I; its S2 volume-shock signal overlaps D but is not a superset. This plan's author also read its S2 +headline for that period. Development and validation are therefore **screening stages only**: validation decides which items the holdout opens for, and only the prospective holdout can support a confirmatory claim. @@ -116,8 +125,8 @@ either a trailing stop or an OCO, never both. | Need | Source | Status now | Gate | |---|---|---|---| -| PIT daily and minute SIP bars, official prices, corporate actions (H1-H3, H6) | Alpaca market data (SIP), already entitled; used by `broad-universe` and `extreme-gainer-audit` | **available** (daily bars from 2016-01-04, minute bars from 2016-01-01) | private hashed snapshots. Before 2020, survivorship is limited (`broad-universe/README.md`), so results for 2017-2019 carry that label | -| 8-K Item 1.01 acceptance times (H2) | SEC EDGAR through the catalyst-provenance and catalyst-dataset recipes | **available** | acceptance must precede the decision, with zero late-source leakage | +| PIT daily and minute SIP bars, quotes, official auction prices, corporate actions (H1-H3, H6) | Alpaca market data (SIP), already entitled; used by `broad-universe` and `extreme-gainer-audit` | bars **available** (daily from 2016-01-04, minute from 2016-01-01); quote and auction-print coverage for 2016-2020 **unverified** | a metadata coverage check before the freeze; private hashed snapshots pinned with their asof date. Before 2020, survivorship is limited (`broad-universe/README.md`), so results for 2017-2019 carry that label | +| 8-K Item 1.01 acceptance times (H2) | SEC EDGAR through the catalyst-provenance and catalyst-dataset recipes | **gated**: `catalyst.py` parses no Item and has no historical ticker-to-CIK map, and its availability rule fails every historical filing | a preregistered acceptance-time availability rule, an Item parser, an as-known CIK source and an index-completeness check, all before the freeze | | Shares outstanding and public float (screen only) | [SEC XBRL frames](https://www.sec.gov/search-filings/edgar-application-programming-interfaces) | **available** as a screen (*contested*: one misattributed source corrected) | frames give one last-filed value per entity. Each row carries `accn` but no filed date, and superseded vintages are absent. A name that clears the screen must be confirmed from companyfacts (or submissions by `accn`) for the filed date and the original vintage before the float counts as point-in-time | | Historical halt, LULD and status events (H4, halt fidelity in simulation) | Databento XNAS.ITCH normalized status schema (`databento/dbn`, Apache-2.0; *contested*: corrected from raw ITCH H/h/J messages to the normalized schema), cross-checked against the Nasdaq Trader halt history | **gated on paid data the user must buy** (Databento is metered). A free short-window pilot is possible | get a `Historical.metadata.get_cost` quote for one symbol-year; confirm the coverage start and the licence. Per the TotalView-ITCH 5.0 spec, halt and resume state (H, Q, T) covers every security traded on the Nasdaq execution system, including other exchanges' listings; the LULD pause state (P) and the auction collar (J) are for Nasdaq-listed names only, and a pause in another listing appears as a halt | | Survivorship-free delisted history before 2020 | Norgate Data US Platinum ([prices](https://norgatedata.com/prices.php); *contested*: facts confirmed, but not new, since it is already named in `catalogs/sota-convergence/manifest-20260923.json`) vs the in-repo SEC Form-25 proxy | **gated on paid data the user must buy** | read the EULA's derived-data terms and what happens when the subscription lapses. Compare only | @@ -133,9 +142,10 @@ either a trailing stop or an OCO, never both. Alpaca rejects a second conditional closing order on the same shares, and a `trailing_stop` cannot be an OCO or bracket leg (both quoted above). So the position is split into tranches, and each tranche carries exactly one broker-held order. The take-profit tranche carries an OCO (stop-loss and take-profit). The rest carries an Alpaca - `trailing_stop` with `trail_percent`, which replaces the client-side cancel-and-resubmit ratchet and is that - tranche's only stop. Its percentage is sized from `effective_trailing_bps` at entry and re-armed only on a fixed - schedule. A bracket is used only for a new entry whose two legs close the whole position. The docs define these + `trailing_stop` with `trail_percent`, which replaces the client-side trailing rule and is that tranche's only + stop. Its percentage is sized from `effective_trailing_bps` at entry and is not changed afterwards. Both tranche + orders are GTC, so a multi-session hold needs no daily resubmission, and they are sent only once the entry order + is terminal. A bracket is used only for a new entry whose two legs close the whole position. The docs define these order classes, but Alpaca's simulated paper fills for them (partials, trigger price, trigger reference) are unmeasured. H6 measures them before anything relies on them. 2. **Client-side rules keep everything that depends on state**, in the existing precedence: force_exit, risk_off, @@ -149,7 +159,8 @@ either a trailing stop or an OCO, never both. 4. **Kill switch and step-down.** One `DELETE /v2/orders` call cancels every open order ([reference](https://docs.alpaca.markets/reference/deleteallorders-1); **survives**). HTTP 207 returns a result for each order, and failed entries are reconciled one by one against the durable ledger. Positions are then - flattened with closing orders. Today `native_adapter.py` cancels in a loop, one call per order (line 391). The + flattened with closing orders, sent only after every cancel is acknowledged as terminal, because shares still + held for an open order would make a closing order exceed the available quantity. Today `native_adapter.py` cancels in a loop, one call per order (line 391). The close-all-positions endpoint was not verified in this sweep. **Automatic leverage is a ceiling, never a target.** The effective ceiling for new entries is the minimum of: @@ -168,7 +179,8 @@ Size = inverse-vol weight x liquidity cap x edge shrink, clipped to that ceiling - **Liquidity cap:** each order is at most a preregistered fraction of trailing ADV and of recent minute volume. The sweep found no ADV term in `strategies_v1.py`. -- **Edge shrink:** 0 until a v3 rule passes its untouched holdout. After that it becomes fractional Kelly, at most +- **Edge shrink:** 0 until a v3 rule passes its untouched holdout. H6's mechanics-only strategy is exempt because it + makes no alpha claim; it trades a fixed minimal notional. After that it becomes fractional Kelly, at most half, with estimation-error shrinkage (MacLean, Thorp, Ziemba 2010, *Quantitative Finance* 10(7):681-687; *contested*: citation only, full text not opened). `cvxgrp/kelly_code` stays `discovery_only` because it is GPL-3.0 and has not been pushed since 2020. @@ -197,10 +209,12 @@ The execution path: - **No polling.** Fills and state changes arrive on the `trade_updates` stream. REST reads are for reconciliation only, about 3 per minute (account, positions and open orders). -- **Rate limiting.** A client token bucket is smoothed to about 3 actions/s on the 200 tier and about 15/s on the - 1000 tier. Nautilus `LiveRiskEngineConfig(max_order_submit_rate, max_order_modify_rate)` matches it; today that is - `180/00:01:00` (`native_adapter.py` lines 462 and 486-487). The client honours `x-ratelimit-remaining`, - `x-ratelimit-reset` and `retry-after`, and backs off on 429. +- **Rate limiting.** One client token bucket covers every REST call (submits, cancels, replaces and reads), smoothed + to about 3 actions/s on the 200 tier and about 15/s on the 1000 tier. Nautilus + `LiveRiskEngineConfig(max_order_submit_rate, max_order_modify_rate)` is only a subset of it: `build_node` sets two + separate `180/00:01:00` limits for submits and modifies (`native_adapter.py` lines 462 and 486-487), which together + allow 360/min, and it throttles no cancel or read; `_cancel_all_orders` (line 391) loops unthrottled. The client + honours `x-ratelimit-remaining`, `x-ratelimit-reset` and `retry-after`, and backs off on 429. - **Tier selection.** The higher tier is used only when preflight sees `x-ratelimit-limit` > 200 on the account's real endpoint. Paper reports 200 today. Other clients on the account share the quota. @@ -226,7 +240,8 @@ The execution path: | IOC marketable-limit entry, limit exit (IOC unverified on the adapter) | 1 / f + 1 | 90 | 60 | 450 | 300 | | bracket (entry plus OCO legs in one POST), legs never changed, cancel unfilled parents | (2 - f) / f | 180 | 60 | 900 | 300 | | entry, then one `trailing_stop` after the fill | 2 / f | 90 | 45 | 450 | 225 | -| entry plus the current client ratchet, r ratchet steps (r = 3 shown) | 2 / f + 2r | 22 | 18 | 112 | 90 | +| entry plus the current client-side exit (`exits.py` triggers, one exit limit, replace disabled by default) | 2 / f | 90 | 45 | 450 | 225 | +| the same with `exits.replace_enabled`, r re-prices of an unfilled exit (at most 3; r = 3 shown) | 2 / f + 2r | 22 | 18 | 112 | 90 | The sweep's synthesis gave "2 + (1 - f) / f, about 60 round trips/min at f = 0.5" for the plain limit pattern. That counts the cancels but leaves out the extra *submits* of unfilled entries. Each attempt costs one submit plus either @@ -263,8 +278,9 @@ The Elite DMA gateway is excluded (see Dropped). - **Engine.** NautilusTrader 2.0.0rc5 backtest (the selected destination) with its native fill and latency models. The pinned review of `fill-models.md` and `reconciliation.md` at `27a8e54` is in `catalogs/us-equities/architecture/trading.json`. -- **Rate limits in simulation.** The same `LiveRiskEngine` throttle runs, at 180/min (or 900/min for the higher-tier - scenario). Every submit, cancel, replace and read uses a simulated token. Going over budget returns a simulated 429 +- **Rate limits in simulation.** The same single client token bucket as paper runs over every REST call, at 180/min + (or 900/min for the higher-tier scenario); the `LiveRiskEngine` submit and modify throttles run inside it as a + subset. Every submit, cancel, replace and read uses a simulated token. Going over budget returns a simulated 429 and a `retry-after` delay, so queueing shows up as worse entry prices. - **Fills on minute SIP bars, with NBBO where entitled:** - a passive limit fills only when the bar trades *through* it, with a queue haircut; @@ -355,7 +371,7 @@ into another: - Should v3 add a short side for the MAX-reversal prior? Borrow, hard-to-borrow fees and SEC Rule 201 were not examined. - Do latency probes (unfillable, then cancelled) comply with the engine's no-manufactured-trades rule? This needs an - explicit decision. + explicit decision. The same decision is needed for H6's mechanics-only strategy; if the answer is no, H6 is dropped. - What are the terms of FINRA short-interest access and of the Norgate EULA on derived data? - The citations that need a direct read before exact wording is cited: the LPS 2019 and ABJK 2022 DOIs, SSRN 5095349 and the full MacLean-Thorp-Ziemba text. @@ -367,11 +383,18 @@ into another: - an independent pre-outcome review, from a different session and model family, with every finding resolved in a dated record; - the direct citation reads above, or removal of the claims that depend on them; -- a development-only cost re-measure for 2017-2019 and fee rates dated for 2017-2020; -- metadata-only coverage checks for H4 and H5; -- study code committed with synthetic tests before any outcome is computed. - -H6 has its own preconditions in `h6-execution-parity-draft.json`, including a test that one share never carries two -closing orders. +- the 2017-2019 cost re-measure fixed by rule only; it runs as the first step after the freeze, because its quote + stamps coincide with outcome prices; fee rates dated for 2017-2020; +- metadata-only coverage checks: 2016-2020 quotes and auction prints (H1-H3), H2's identity and filing-index gates, + H4's halt and reopening-print sources, H5's short-interest history including the 2016 warm-up; +- H2, H4 and H5 each recorded as tested or permanently not tested; +- the universe and daily-dataset snapshot pinned with its asof date; +- study code committed with synthetic tests before any outcome is computed, including split and ex-dividend + crossings, early-close sessions and segment-end censoring. + +H6 has its own preconditions in `h6-execution-parity-draft.json`, including a named mechanics-only strategy, a test +that one share never carries two closing orders and a paper smoke test of the tranche pair. + +The second review round's findings and their resolution are recorded in `protocol-draft.json` (`review_record`). Until all of that is done, this is a plan, and no window it names may be read for outcomes. diff --git a/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json index 3efc33252..caf4a50c5 100644 --- a/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json +++ b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json @@ -5,54 +5,65 @@ "status": "draft_pending_independent_pre_outcome_review", "frozen_before_outcomes": false, "drafted_at": "2026-09-24", + "revised_at": "2026-09-24 (round 2: population named as a gated mechanics-only strategy, H6-c restated as descriptive because arm a needs fewer calls by construction, arm a configuration pinned, H6-e unit and trigger strata, GTC and tranche timing, calendar cap, paper smoke test)", "base_commit": "aa6fc791bbd4af3499317673989d2e90a522c257", "parent_plan": "blueprints/us-equities/mover-v3/README.md", "split_from": "blueprints/us-equities/mover-v3/protocol-draft.json (H6), in the 2026-09-24 review fix round", "evidence_class": "Official broker documentation read on 2026-09-24 plus repository source reads. No native execution, no paper order and no outcome. Paper fills are simulated by Alpaca, so any result is sim-to-paper evidence, not market evidence.", - "question": "Do broker-held exits (Alpaca trailing_stop and OCO) realize exit prices equivalent to the client-side cancel-then-new ratchet, relative to the same trigger reference, at fewer REST calls per closed position?", - "independence": "Engineering, not alpha. This protocol has its own two-test family and its own paper timeline. It uses no slot of the mover-v3 alpha family (m = 35), H1-H5 never wait for it, and no result here opens or informs the alpha holdout. Its paper positions are mechanics-only unless a v3 rule has passed its holdout; they are logged as accrual exposure under protocol-draft.json chronology.holdout.accrual_exposure.", + "question": "Do broker-held exits (Alpaca trailing_stop and OCO) realize exit prices equivalent to client-side exits, relative to the same trigger reference? REST calls per closed position are reported for both arms as a description, not tested.", + "independence": "Engineering, not alpha. This protocol has its own one-test family and its own paper timeline. It uses no slot of the mover-v3 alpha family (m = 35), H1-H5 never wait for it, and no result here opens or informs the alpha holdout. Its positions are mechanics-only; any overlap with the alpha holdout is logged and excluded under protocol-draft.json chronology.holdout.accrual_exposure.", + "data_status": "gated. H6 has no source of positions today: v3 defines no paper candidate, the v3 edge-shrink term is 0 until a holdout pass (so every alpha-sized position is 0), and this draft creates no order for the test itself. It cannot accrue any closed position until the population below is committed and the preconditions hold.", "evidence": [ {"source": "https://docs.alpaca.markets/docs/orders-at-alpaca", "status": "read on 2026-09-24. trailing_stop (trail_price or trail_percent) and OCO for a held position ('the entry order is already filled, and you can submit the take-profit and stop-loss in one order submission') are documented. Constraints quoted verbatim: 'Trailing stop orders are currently supported only with single orders. However, we plan to support trailing stop as the stop loss leg of bracket/OCO orders in the future.' and 'you would not be able to submit two conditional closing orders since Alpaca's system would view one of the two orders as exceeding the available position quantity'."}, {"source": "https://docs.alpaca.markets/reference/deleteallorders-1", "status": "survives"}, - {"source": "native_adapter.py builds only LIMIT/DAY orders; native_strategy.replace_exit cancels and resubmits when the price basis moves; exits.py rule chain (stop_loss, take_profit with take_profit_fraction, trailing)", "status": "repository source read at aa6fc79"} + {"source": "adaptive-paper at aa6fc79: exits.py evaluates stop, take-profit (take_profit_fraction) and trailing rules client-side with no resting broker stop; when a rule fires, native_strategy.py (about lines 755-819) sends one DAY limit at limit_price(bid, ask); replace_exit re-prices only an unfilled resting exit, is off by default (runner.py line 323, exits.replace_enabled False; strategies_v1.py exit_replace_enabled False) and is capped at exit_replace_max_attempts = 3; native_adapter.py builds and accepts only LIMIT/DAY orders (lines 344, 354, 368)", "status": "repository source read in the round 2 fix"} ], - "population": "positions opened in Alpaca paper by the paper candidate after the OCO and trailing_stop adapter paths exist. No order is created for the test itself.", + "population": { + "rule": "positions opened in Alpaca paper by one named mechanics-only strategy and configuration, at a fixed minimal notional, committed before the freeze. It makes no alpha claim and is exempt from edge shrink for that reason. It trades only symbol-sessions outside that session's mover-v3 D and I populations (decision-time data), so it does not deplete the alpha holdout.", + "open_decision": "Whether such a strategy is compatible with adaptive-paper README's rule 'No strategy manufactures trades to hit a throughput target' needs an explicit, recorded decision before the freeze. If the decision is no, H6 is dropped.", + "rate": "the expected closed positions per session are estimated before the freeze from retained adaptive-paper trial ledgers (metadata and counts only) and recorded here", + "calendar_cap": "each stage has at most 126 full NYSE sessions; a stage that has not reached its minimum by then is labelled underpowered and the protocol stops" + }, "arm_assignment": { "rule": "arm = a if the first byte of sha256('mover-v3-h6|' + symbol + '|' + entry session (YYYY-MM-DD) + '|' + k) is even, else b, where k is the 1-based count of this symbol's entries in this session in decision order", "commit": "the assignment is written to the durable order ledger before the entry order is sent. client_order_id is not an input, so the client that generates IDs cannot re-roll an assignment. A position whose ledger assignment is missing or written after the submit is excluded and counted." }, "exit_arms": { - "a_client": "the current exits.py chain on the whole position with cancel-then-new replacement", + "a_client": { + "rule": "the current exits.py chain on the whole position: triggers are evaluated client-side, and a fired rule sends one DAY limit at limit_price(bid, ask)", + "config": "exits.replace_enabled = false (the shipped default), so an unfilled exit is not re-priced; exit_replace_max_attempts = 3, exit_replace_tolerance_bps = 0 and exit_replace_min_interval_seconds = 0 are recorded but inactive. Any other setting is a new protocol version." + }, "b_broker": { "constraint": "each share carries at most one broker-held closing order (the documented rule above): a trailing_stop cannot be an OCO leg, and a second conditional closing order on the same shares is rejected as exceeding the available quantity", "structure": "the filled quantity q is split into two tranches. Tranche P (take-profit tranche) = floor(q x f_p) shares, carrying one OCO (take-profit limit at take_profit_bps and stop at stop_bps). Tranche T (trailing tranche) = q - P shares, carrying one trailing_stop with trail_percent = effective_trailing_bps at entry / 100, which is also that tranche's only stop. f_p = take_profit_fraction when it is below 1; when take_profit_fraction = 1 (the strategies_v1 default), f_p = 0.5, fixed here. A tranche of 0 shares is not sent, and the whole position carries the other tranche's single order.", - "client_side": "time, risk-off, force, quote-stale and halt exits stay client-side. A client-side exit first cancels both tranche orders (one call each), then sends one closing order for the remaining quantity.", - "difference_from_a": "arm b is not a byte-identical copy of arm a's rule chain: tranche T has no separate fixed stop and tranche P no trailing rule. H6-e therefore compares execution quality against each fill's own trigger reference, not P&L." + "timing": "tranche orders are submitted only once the entry order is terminal (filled, cancelled or expired), sized from the final filled quantity; no top-up for later fills", + "time_in_force": "GTC for both tranche orders, so multi-session holds need no daily resubmission. If the paper smoke test shows GTC rejected for an order class, that class uses DAY plus one resubmission per tranche at each session's first eligible quote, and those calls are counted.", + "client_side": "time, risk-off, force, quote-stale and halt exits stay client-side. A client-side exit first cancels both tranche orders (one call each), waits for both cancels to be terminal, then sends one closing order for the remaining quantity.", + "difference_from_a": "arm b is not a byte-identical copy of arm a's rule chain: tranche T has no separate fixed stop and tranche P no trailing rule, and arm a sells the whole position on take-profit (take_profit_fraction = 1) while arm b sells tranche P. H6-e therefore compares execution quality against each fill's own trigger reference, not P&L, and only for stop-type triggers." } }, "shadow": "for each position, the other arm's trigger time and reference price are computed from the same quote stream for paired diagnostics, never traded", - "call_count_expectation": { - "arm_a": "1 entry + 1 initial exit order + 2 per ratchet step (cancel, new): 2 + 2r per closed position at a full fill; 8 at r = 3", - "arm_b_broker_closed": "1 entry + 1 OCO + 1 trailing_stop = 3 calls, with no further calls when the broker legs fill (1 entry + 1 order when a tranche is empty = 2)", - "arm_b_client_closed": "3 + 2 cancels + 1 closing order = 6 calls", - "ratio": "with a share s of positions closed client-side, arm b averages 3 + 3s calls. At r = 3 the H6-c alternative (a/b > 2) needs s <= 1/3. The realized s and r are reported with H6-c." + "call_count_description": { + "arm_a": "2 calls per closed position at a full fill (entry plus one exit limit). With replace disabled no further calls are made; an unfilled exit expires at the close and is handled by the existing recovery path, whose calls are counted.", + "arm_b": "3 calls when broker legs close the position (entry, OCO, trailing_stop; 2 when a tranche is empty), 6 when closed client-side (plus 2 cancels and 1 closing order), plus any DAY resubmissions", + "consequence": "arm b needs more calls than arm a by construction (3 + 3s against about 2), so the previous test 'arm b uses at least 2x fewer calls' could not pass and is withdrawn. The call counts per closed position, the realized client-closed share s and any resubmissions are reported per arm as a description only." }, "primary_tests": [ - {"id": "H6-e", "statistic": "median of (realized exit price - trigger reference) in bps, arm b minus arm a", "alternative": "equivalence within +/-25 bps (TOST)", "p_value": "p = max(p_lower, p_upper), the larger of the two one-sided bootstrap p-values; this p enters Holm"}, - {"id": "H6-c", "statistic": "log(mean REST calls per closed position, arm a / arm b)", "alternative": "greater than log(2)"} + {"id": "H6-e", "unit": "one closed position with at least one stop-type exit fill (arm a: stop_loss or trailing rule; arm b: the OCO stop leg or the trailing_stop). The position's value is the quantity-weighted mean over its stop-type exit fills of (fill price - trigger reference) / trigger reference, in bps. Positions closed only by take-profit or client-side rules are excluded from H6-e, counted and reported by trigger type.", "statistic": "median of the position values, arm b minus arm a", "alternative": "equivalence within +/-25 bps (TOST)", "p_value": "p = max(p_lower, p_upper), the larger of the two one-sided bootstrap p-values"} ], + "descriptive": ["H6-c: log(mean REST calls per closed position, arm a / arm b), with a bootstrap interval; no pass rule", "take-profit fills: the same statistic, reported separately", "paired shadow differences per trigger type"], "also_not_supported_if": "any paper partial-fill, trigger-price or trigger-reference behaviour that departs from the documented semantics, or any rejection of a tranche order as exceeding the available quantity (recorded per event)", "multiple_testing": { - "family": "H6-e and H6-c only, m = 2", - "procedure": "Holm at family-wise alpha 0.05 at validation and at the holdout; strictest level 0.025", + "family": "H6-e only, m = 1", + "procedure": "TOST (two one-sided tests, each at alpha 0.05) at validation and at the holdout; no multiplicity adjustment with m = 1", "development_screen": "unadjusted p <= 0.05 with the development sample; nominates for validation only" }, "stages": { - "order": "closed positions in close-time order, counted per arm", - "development": "first 100 closed positions per arm", + "order": "H6-e-eligible closed positions in close-time order, counted per arm", + "development": "first 100 eligible closed positions per arm", "validation": "next 200 per arm", - "holdout": "next 200 per arm, read only for items that passed validation, with an access log entry in blueprints/us-equities/mover-v3/h6-access-log.jsonl committed before the read", - "minimum": "each stage's size is its minimum; a stage that has not reached it is underpowered and cannot pass", + "holdout": "next 200 per arm, read only if H6-e passed validation, with an access log entry in blueprints/us-equities/mover-v3/h6-access-log.jsonl committed before the read", + "minimum": "each stage's size is its minimum; a stage that has not reached it within population.calendar_cap is underpowered and cannot pass", "note": "prospective paper only; disjoint from every historical window and independent of the alpha protocol's stages" }, "power": { @@ -60,15 +71,17 @@ "sigma_provenance": "assumption, no measured source", "median_efficiency": "standard error of a median difference taken as sigma x sqrt(2/n) x sqrt(pi/2) (normal approximation)", "development": "n = 100 per arm, alpha 0.05: SE 8.9 bps, TOST interval half-width 14.6 bps, equivalence passes if the observed difference is within +/-10.4 bps, power about 0.76 at a true difference of 0", - "validation_and_holdout": "n = 200 per arm, alpha 0.025: SE 6.3 bps, half-width 12.3 bps, passes within +/-12.7 bps, power about 0.96 at a true difference of 0", - "previous_draft": "the earlier 40 / 60 / 150 per arm at 0.05/37 could not pass development or validation by construction (half-width above the 25 bps margin at 60 per arm)" + "validation_and_holdout": "n = 200 per arm, alpha 0.05 (m = 1): SE 6.3 bps, half-width 10.3 bps, passes within +/-14.7 bps, power about 0.98 at a true difference of 0", + "previous_drafts": "40 / 60 / 150 per arm at 0.05/37 could not pass by construction; the round 1 draft used alpha 0.025 for a two-test family whose call-count test could not pass" }, - "outcome_reporting": "the three labels of protocol-draft.json outcome_reporting (supported; not supported with the excluded size stated; underpowered)", - "sizing": "parent sizing at 1x", - "data_status": "available now (Alpaca paper) once the adapter change lands; paper fills are simulated, so this is sim-to-paper evidence, not market evidence", + "outcome_reporting": "the three labels of protocol-draft.json outcome_reporting (a stage-specific pass label; not supported with the excluded size stated; underpowered)", + "sizing": "the fixed minimal notional of the mechanics-only strategy (population.rule)", "freeze_preconditions": [ "An independent pre-outcome review from a different session and model family, findings resolved in a dated record", - "Adapter paths for OCO and trailing_stop committed with synthetic tests, including a test that one share never carries two closing orders", + "The mechanics-only strategy, its configuration and notional committed, and the no-manufactured-trades decision recorded (population.open_decision)", + "The positions-per-session estimate recorded from retained paper ledgers (population.rate)", + "Adapter paths for OCO and trailing_stop committed with synthetic tests, including a test that one share never carries two closing orders and a test that a client-side exit waits for cancel acknowledgements before its closing order", + "A paper mechanics smoke test of the tranche pair (one OCO plus one trailing_stop on split tranches of one position, GTC), labelled as measurement, with its result recorded", "The arm-assignment ledger write tested to precede the entry submit", "status set to frozen and the sha256 recorded in a pushed commit" ] diff --git a/blueprints/us-equities/mover-v3/protocol-draft.json b/blueprints/us-equities/mover-v3/protocol-draft.json index 904cfe089..69a324578 100644 --- a/blueprints/us-equities/mover-v3/protocol-draft.json +++ b/blueprints/us-equities/mover-v3/protocol-draft.json @@ -5,7 +5,7 @@ "status": "draft_pending_independent_pre_outcome_review", "frozen_before_outcomes": false, "drafted_at": "2026-09-24", - "revised_at": "2026-09-24 (review fix round: exposure registry, H6 moved to its own engineering draft, trailing breakpoints, three-outcome reporting, leg-matched H3-c, halt-free H1-H3, H4 parent set, cost lookup pins, repin to merged #162)", + "revised_at": "2026-09-24 (review fix round: exposure registry, H6 moved to its own engineering draft, trailing breakpoints, three-outcome reporting, leg-matched H3-c, halt-free H1-H3, H4 parent set, cost lookup pins, repin to merged #162); round 2 on the same day (review_record): directions locked after development for two-sided items, data status of H2, H4 and H5 fixed at the freeze, freeze before any quote is sampled, defined MDE and label bound, holdout breakpoint, start and count-read rules, segment-end and early-close rules, mid-quote fills, split and dividend accounting, H2 moved to gated, lineage count 1,107", "base_commit": "aa6fc791bbd4af3499317673989d2e90a522c257", "plan": "blueprints/us-equities/mover-v3/README.md", "catalog_decisions": "catalogs/us-equities/mover-v3-sweep-20260924.json", @@ -17,9 +17,9 @@ "publication": "private scratch input, not committed: its verifier notes contain host paths. The README and the catalog supplement carry its content." }, "relation_to_prior_protocols": { - "mover-early-entry-v1-20260924": "PR #162, merged to main on 2026-09-24 as squash commit aa6fc79. 0 of 768 rule-exits pass development; the holdout was not read. Reused by reference only, pinned to main at aa6fc79 (paths under blueprints/us-equities/mover-early-entry/): rules.py (git blob 9be164c9e1a49a451d3a2381205349ed5003a993, sha256 d66156b3e57c6299f10e41a3f25c921c8be52571bd84417040190d926bf49450; definitions ref, price_at_t, dollar_volume_at_t, gain_at_t, official prices, split factor), quotes.py (git blob caebf6f6a72fee6bb7ae637ebb698f8c5f6b3850, sha256 4116bf2809e0952f842f0e0fe35fd8b5ab11ea295637734ffe0d719ca72ee85e; the cost-sample method including the D7 stamp-window guard: a completion is reused only for the same timestamp, and the table refuses quotes outside [ts - 60 s, ts]) and evidence/cost-table-run-v1.json (hashes under cost_model.base_table). The draft's earlier pin 92062e8 is reachable only from the PR branch, and its quotes.py lacks the D7 guard. v3 never modifies blueprints/us-equities/mover-early-entry/**.", + "mover-early-entry-v1-20260924": "PR #162, merged to main on 2026-09-24 as squash commit aa6fc79. 0 of 768 rule-exits pass development; the holdout was not read. Reused by reference only, pinned to main at aa6fc79 (paths under blueprints/us-equities/mover-early-entry/): rules.py (git blob 9be164c9e1a49a451d3a2381205349ed5003a993, sha256 d66156b3e57c6299f10e41a3f25c921c8be52571bd84417040190d926bf49450; definitions ref, price_at_t, dollar_volume_at_t, gain_at_t, official prices, split factor), quotes.py (git blob caebf6f6a72fee6bb7ae637ebb698f8c5f6b3850, sha256 4116bf2809e0952f842f0e0fe35fd8b5ab11ea295637734ffe0d719ca72ee85e; the cost-sample method including the D7 stamp-window guard: a completion is reused only for the same timestamp, and the table refuses quotes outside [ts - 60 s, ts]) evidence/cost-table-run-v1.json (hashes under cost_model.base_table) and evaluate_v2.py (git blob 2248580f4c673da3fcde25d85c6d20799ffa994e, sha256 d2007077ac7367e16061e49a645a54802fbb58c41d79d3b2da6417b0275b2fe4; only deviation D7's split and ex-dividend accounting, restated for v3 in populations.corporate_actions). The draft's earlier pin 92062e8 is reachable only from the PR branch, and its quotes.py lacks the D7 guard. v3 never modifies blueprints/us-equities/mover-early-entry/**.", "mover-followup-v2-20260924": "PR #162 at aa6fc79: no pass in families E (216), F (16) or P (12); no holdout read. After deviation D7 (P exits across splits and ex-dividend dates), the v2 results sha256 is 75a5004b... (README and deviations.json at aa6fc79). Not reused.", - "catalyst-experiment-v1-20260920": "Its lane timing (16:15 decision, 09:35 entry, +60 s minimum latency, 5-session and 60-minute horizons) and the catalyst.py Item 1.01 contract are reused by reference, as is its rule 'Do not infer a halt from missing data'. Its 2021Q1 ETF control is permanently inspected and is never a holdout.", + "catalyst-experiment-v1-20260920": "Its lane timing (16:15 decision, 09:35 entry, +60 s minimum latency, 5-session and 60-minute horizons) is reused by reference, as is its rule 'Do not infer a halt from missing data'. Its catalyst-provenance/catalyst.py is not a usable H2 source as it stands: it parses only form type and acceptance headers (no Item parser, no ticker-to-CIK map), and its available_at = max(acceptance, local first observation) makes every 2016-2020 filing ineligible at its historical decision (catalyst-experiment README, 'Unknown original availability ... fails historical eligibility'). H2 is therefore gated (hypotheses.H2). Its 2021Q1 ETF control is permanently inspected and is never a holdout.", "broad-universe-mover-v1-20260921": "Main universe close >= $5 and median dollar volume >= $20M, plus a descriptive lane on the unrestricted $1 / $2M universe (protocol.json descriptive_microcap_lane; evaluation-results.json calls it a strict superset of the main universe). Both lanes computed outcomes over development 2017-2021, validation 2022-2023 and reserved 2024-01-01 to 2026-08-14. Most D-population symbol-sessions with median dollar volume >= $2M lie inside that lane, so 2017-2020 is exposed at aggregate level (exposure_registry)." }, "question": "Among US-equity extreme movers, which conditioning (MAX lottery demand, catalyst versus attention, overnight versus intraday leg, halt state, days-to-cover crowding) changes the sign or size of the cost-net forward return? Continuation is one tested outcome, not the premise. Whether broker-held exits match client-side exits at fewer API calls is a separate engineering question (engineering_protocol).", @@ -29,25 +29,29 @@ {"window": ["2021-01-04", "2023-12-29"], "what": "mover-early-entry v1 and v2 development outcomes (1,012 rule-exits) on the +20% candidate universe; the v1 cost table's quote sample was drawn from these development sessions"}, {"window": ["2024-01-02", "2025-12-31"], "what": "mover-early-entry v1 and v2 validation outcomes"}, {"window": ["2026-01-02", "2026-09-18"], "what": "the reserved holdout of mover-early-entry v1/v2, unread by #162 and owned by it. It is also exposed at event level: extreme-gainer-audit computed T+1/T+5/T+20 forward returns for 957 package events from 2021 to September 2026. v3 excludes this window."}, - {"window": ["2017-01-01", "2021-12-31"], "what": "broad-universe-mover-v1 development segment, computed on two lanes: the main universe (close >= $5, median dollar volume >= $20M) and the descriptive lane on the unrestricted close >= $1, median dollar volume >= $2M universe (a strict superset of the main universe; receipt.json reports its development outcomes, for example 4,563,385 C0 H1 events). Outcomes include +20% one-day labels and S2 volume-shock continuation (r1 >= 5%, dv >= 3 x med20, clv >= 0.5), a superset of D's same-session arm on names with median dollar volume >= $2M. This covers all of v3 development (2017-2019) and validation (2020) at aggregate level."}, + {"window": ["2017-01-01", "2021-12-31"], "what": "broad-universe-mover-v1 development segment, computed on two lanes: the main universe (close >= $5, median dollar volume >= $20M) and the descriptive lane on the unrestricted close >= $1, median dollar volume >= $2M universe (a strict superset of the main universe; receipt.json reports its development outcomes, for example 4,563,385 C0 H1 events). Outcomes include +20% one-day labels and, through the C0_all_eligible control, H1/H5/H20 forward returns for every eligible symbol-session on both instrument lanes (primary operating company and all instruments). C0 is the event-level superset exposure: it covers every D and I symbol-session that was a lane decision, that is, raw close >= $1, med20 (median of raw close x volume over the 20 prior sessions) >= $2M, at least 60 prior contiguous bars and no has_gap flag. S2 volume-shock continuation (r1 >= 5%, dv >= 3 x med20, clv >= 0.5) overlaps D's same-session arm but is not a superset of it (a +20% day with dv < 3 x med20 or clv < 0.5 is outside S2). This covers v3 development (2017-2019) and validation (2020) at aggregate level."}, {"window": ["2022-01-01", "2026-08-14"], "what": "broad-universe-mover-v1 validation 2022-2023 and reserved 2024-01-01 to 2026-08-14, both lanes"}, {"window": ["2019-12-02", "2020-04-30"], "what": "historical-simulation: six LEAN margin-stress scenarios on bundled SPY history; no mover-population outcome"}, {"window": ["2021-01-01", "2021-03-31"], "what": "catalyst-experiment 2021Q1 ETF control, permanently inspected"}, {"window": "2020 and 2022 single-name samples", "what": "LEAN/AAPL 2020, FB/META 2022, SPY parity and corporate-action diagnostics; engine and data checks, not mover outcomes"} ], - "draft_author_exposure": "Before this draft the author read: #162's README results and v1 dev/val summary aggregates (per-rule means; P5/P95 of the 07:00|G0.20|V1000000 rule-exits, used only for sigma planning below); the v1 cost-table cells; the #162 review findings; the broad-universe README headlines, including the development | validation | reserved table of date-clustered mean net excess over C0 for S1-S5 at H1/H5/H20 (for example S2 H1 development -0.33%, 2017-2021, main universe) and its reserved-segment precision and recall for +10% and +20% next-day movers; the sweep. The review fix round (same day) reread broad-universe protocol.json (lane definitions), evaluation-results.json's descriptive_microcap_scope note and the same README table, and opened no descriptive-lane outcome row; the 4,563,385 event count above is a count quoted from the review finding, not a return. No v3-defined statistic (a D or I outcome, a MAX, halt or short-interest tercile) has been computed or seen for any window.", - "consequence": "2017-2020 is exposed at aggregate level through broad-universe development (both lanes) and the S2 headline above. v3 therefore treats development (2017-2019) and validation (2020) as screening stages only. Validation still decides which items the holdout gate opens for, but a validation pass is labelled 'screened', never confirmatory. Only the prospective holdout can support a confirmatory claim. A validation-window statistic restricted to names that lane never covered (20-session median dollar volume < $2M at the decision) is reported beside each validation result as the least-exposed slice; it is a sensitivity, not a separate test.", - "update_rule": "Every later read of data in any v3 window, before the freeze, is appended here with its date and purpose. A read of v3-defined outcomes in the validation window before the freeze voids validation; a read in the holdout window before the gate opens voids the holdout." + "draft_author_exposure": "Before this draft the author read: #162's README results and v1 dev/val summary aggregates (per-rule means; P5/P95 of the 07:00|G0.20|V1000000 rule-exits, used only for sigma planning below); the v1 cost-table cells; the #162 review findings; the broad-universe README headlines, including the development | validation | reserved table of date-clustered mean net excess over C0 for S1-S5 at H1/H5/H20 (for example S2 H1 development -0.33%, 2017-2021, main universe) and its reserved-segment precision and recall for +10% and +20% next-day movers; the sweep. The review fix round (same day) reread broad-universe protocol.json (lane definitions), evaluation-results.json's descriptive_microcap_scope note and the same README table, and opened no descriptive-lane outcome row; the 4,563,385 event count above is a count quoted from the review finding, not a return. The second fix round (same day) read the rereview findings, broad-universe protocol.json (eligibility, window_contiguity, trial_policy, promotion), catalyst-provenance/catalyst.py, the catalyst-experiment README availability section, mover-early-entry evaluate_v2.py and deviations.json D7 (method text only), the rules.py function list and the adaptive-paper order and exit code paths (native_strategy.py, runner.py, strategies_v1.py, native_adapter.py, exits.py); it opened no outcome row, return or results file. No v3-defined statistic (a D or I outcome, a MAX, halt or short-interest tercile) has been computed or seen for any window.", + "consequence": "2017-2020 is exposed at aggregate level through broad-universe development (both lanes) and the S2 headline above. v3 therefore treats development (2017-2019) and validation (2020) as screening stages only. Validation still decides which items the holdout gate opens for, but a validation pass is labelled 'screened', never confirmatory. Only the prospective holdout can support a confirmatory claim. A validation-window statistic restricted to symbol-sessions that were not a broad-universe lane decision (med20 from raw close x volume < $2M, fewer than 60 prior contiguous bars, or has_gap, all as broad-universe protocol.json defines them) is reported beside each validation result as the least-exposed slice; it is a sensitivity, not a separate test.", + "update_rule": "Every later read of data in any v3 window, before the freeze, is appended here with its date and purpose. No quote, bar or official price at a v3 entry or exit stamp is fetched before the freeze: the 2017-2019 cost sample is the first step of the frozen evaluation (cost_model.out_of_period_spreads). A pre-freeze read of development-window prices at v3 stamps is logged here and named in the freeze commit. A read of v3-defined outcomes in the validation window before the freeze voids validation; a read in the holdout window before the gate opens voids the holdout." }, "populations": { - "D_daily": "symbol-sessions t with split-consistent official-close gain close[t]/ref - 1 >= 0.20, official close >= $1.00 and completed regular-session dollar volume >= $1,000,000; decision at 16:15 ET on t; entry at the first eligible quote at or after 09:35 ET on t+1 and at or after decision + 60 s; entry timeout 300 s, else no entry (counted)", + "D_daily": "symbol-sessions t with split-consistent official-close gain close[t]/ref - 1 >= 0.20 (close[t] is the primary listing's official closing print; a session without one is not an event and is counted, with no bar fallback; ref as rules.py defines it), official close >= $1.00 and completed regular-session dollar volume >= $1,000,000; decision at 16:15 ET on t; entry at the first eligible quote at or after 09:35 ET on t+1 and at or after decision + 60 s; entry timeout 300 s, else no entry (counted)", "I_intraday": "the first minute m in 09:35-10:30 ET on a session at which gain_at_t >= 0.20, dollar_volume_at_t >= $1,000,000 and price_at_t >= $1.00 (rules.py definitions at aa6fc79); entry at the first eligible quote at or after m + 60 s; timeout 60 s", + "session_times": "Every '15:55 ET' in this protocol means the scheduled close minus 5 minutes (12:55 ET on a 13:00 early-close session). Eligible quotes are regular-session quotes, at or after 09:30 ET and before the scheduled close. An exit that finds no eligible quote before the scheduled close takes the first eligible quote of the next session. D decisions stay at 16:15 ET on early-close sessions.", + "fill_price": "Every entry and exit fills at the mid (bid + ask) / 2 of the eligible quote that fills it, so gross returns are mid-to-mid and cost_model adds the per-side cost on top. The realized half-spread of each fill quote is recorded and reported as a sensitivity (cost = 1.25 x max(table hs', realized half-spread)).", + "corporate_actions": "For every multi-session arm (D b_lane, H3 b_overnight, the H4 parents) and each H3-c leg, deviation D7's method is restated: the exit uses the raw exit mid (or raw official print for H3-c); shares change by the product of rules.py split factors between the entry session and the exit session (a factor counts when it differs from 1 by more than 1e-2); the rest of the adjusted close-to-close return over the hold is booked as cash per original share, without spread or fees. Fees use the actual exit shares and raw exit price. H3-c legs use log((raw end print x split factor + cash) / raw start print). H5's short interest and 20-session volume are both expressed in session t's share basis with the same split factors. Study code must pass a synthetic test that crosses a split and an ex-dividend date before the freeze.", "exclusions": "OTC; warrants, units and rights by #162's regex; symbols with fewer than 21 prior sessions of bars. No halt exclusion: H1-H3 use no halt data (see halts).", "halts": "H1-H3 use no halt source, historically or prospectively, so their trade sets cannot depend on a later data purchase. As in catalyst-experiment, a halt is never inferred from missing data: an entry or exit fills only at an eligible quote (bid > 0, ask >= bid, quote age <= 1000 ms), an entry without one inside its timeout is a counted no-entry, and an exit without one waits for the next eligible quote. Limitation: a trade that sits through an unobserved halt is not flagged, and a fill at the first post-halt quote is not distinguished from a normal fill. Halt state enters only H4, as an overlay on already-defined trades.", "parameter_provenance": "G = 0.20 and V = $1M are v1's loosest gain and middle volume floor, fixed for sample size after v1's all-negative dev/val grid was seen (disclosed exposure). They are not tuned on any v3 window." }, "tercile_rule": { - "breakpoints": "For a covariate X (MAX21 in H1, DTC in H5) and a population P, the breakpoints for an event decided in session s are the 1/3 and 2/3 empirical quantiles (numpy 'linear' method) of X over all P events whose decision session lies in the 252 sessions strictly before s. Every input is known at the decision, including for I, whose later same-morning triggers are never used.", + "breakpoints": "For a covariate X (MAX21 in H1, DTC in H5) and a population P, the breakpoints for an event decided in session s are the 1/3 and 2/3 empirical quantiles (numpy 'linear' method) of X over all P events whose decision session lies in the 252 sessions strictly before s. Every P event counts, whether it entered, had no entry or had no fill. Every input is known at the decision, including for I, whose later same-morning triggers are never used.", + "holdout_breakpoints": "For a holdout event the trailing window is the 252 most recent sessions before s that lie outside 2026-01-02 to 2026-09-18 (#162's reserved holdout, which v3 never reads). It reads only covariates and event membership, never an outcome: in 2025 (v1/v2 validation, already exposed and recorded in the exposure registry) and in earlier holdout sessions (decision-time inputs of the sealed snapshot, read only after the gate opens).", "assignment": "low if X <= the 1/3 breakpoint, high if X > the 2/3 breakpoint, middle otherwise (a value equal to a breakpoint goes to the lower tercile)", "minimum": "if fewer than 60 prior P events lie in the trailing window, the event is unassigned: it is counted and reported, and it enters no tercile cell or difference test. The 2016 warm-up year supplies the first window.", "small_dates": "Terciles never depend on the number of movers on the event's own date, so a date with fewer than 3 movers needs no special rule." @@ -58,24 +62,26 @@ "validation": ["2020-01-02", "2020-12-31"], "holdout": { "kind": "prospective", - "window": "the first 252 full NYSE sessions strictly after the session in which the validation results file's sha256 is committed and pushed (which follows the freeze)", - "extension": "if any holdout-eligible item has fewer than its minimum holdout trades, extend in 63-session blocks, at most 2, decided on trade counts alone with outcome labels uncomputed", + "window": "252 full NYSE sessions starting at session N0 = the 40th full NYSE session after the session of the freeze commit. The validation results file's sha256 must be committed and pushed before N0. If it is not, N0 moves to the first block boundary 40 + 63k (k = 1, 2, ...) after the push, and the delay is logged. The start is therefore fixed by the freeze date and the push, not chosen after the validation results or the live market are seen.", + "extension": "if any carried item has fewer than its minimum holdout trades after 252 sessions, extend in 63-session blocks, at most 2. Each extension decision uses one 'count' read of the sealed snapshot (holdout_gate.access_log): entries and tercile, group or halted-parent assignments per carried item, never an exit or outcome price", "why_prospective": "No retrospective window free of prior mover outcomes remains: 2021-2025 is v1/v2 dev/val, 2026-01-02 to 2026-09-18 is #162's reserved holdout and is event-level exposed, 2017-2026-08-14 is broad-universe exposed at aggregate level, and 2016 is warm-up.", - "accrual_exposure": "Any paper operation that trades mover positions during the holdout window (including the mechanics-only positions of the engineering protocol) logs, per collection batch, which symbol-sessions it traded and whether their P&L was seen. Those symbol-sessions are then excluded from the holdout sample for every item, and the count is reported." + "accrual_exposure": "Any paper operation that trades during the holdout window logs, per collection batch, which symbol-sessions it traded and whether their P&L was seen. An exposed D trade is keyed on every session in [decision t, planned exit]; an exposed I trade on its event session. Those trades are excluded from the holdout sample for every item, and the excluded count and fraction are reported per carried item, tercile and group. The engineering protocol's mechanics-only positions trade only symbol-sessions outside that session's D and I populations, so they should exclude nothing; any overlap is still excluded and reported. Any other computation on holdout-window mover prices (dashboards, ad hoc reads) is logged as exposure in the same way." }, "labels": { "development": "disjoint from v1/v2; exposed at aggregate level through broad-universe development (both lanes, 2017-2021); screening only; survivorship-limited before 2020 (names delisted in 2016-2019 without a retained corporate action cannot be discovered)", "validation": "disjoint from v1/v2; exposed at aggregate level through broad-universe development (both lanes include 2020) and the S2 headline the author read; screening only, never confirmatory; the 2020 COVID regime is reported separately and never dropped", - "holdout": "prospective; untouched only if the validation results sha256 was pushed before its first session, no read preceded a committed granted access-log entry, and accrual exposure was logged and excluded as above" + "holdout": "prospective; untouched only if the validation results sha256 was pushed before N0, no read preceded a committed granted access-log entry, and accrual exposure was logged and excluded as above" }, - "boundaries": "Segments are assigned by entry session. A trade whose exit crosses a segment end is censored and counted in the earlier segment. For multi-session arms, the later segment gives up its first 6 sessions as entry sessions (an embargo after the censored crossing trades); same-session arms need no gap." + "boundaries": "Segments are assigned by entry session. A trade whose planned exit falls after its segment's last session is censored: it exits at the first eligible quote at or after 15:55 ET on that last session, is flagged, stays in the earlier segment's means and is reported as a count. H3 b_overnight makes no entry on a segment's last session (counted), and an H3-c trade whose legs cross the segment end is excluded from H3-c and counted. For multi-session arms, the later segment gives up its first 6 sessions as entry sessions (an embargo after the censored crossing trades); same-session arms need no gap." }, "outcome_reporting": { "labels": { - "supported": "the item passes its stage rule (development: the screen; validation: Holm in the 35-item family plus minimum sample and, for a tradable cell, the robustness means, labelled 'screened'; holdout: the same at the holdout, labelled confirmatory)", - "not_supported_mde_excluded": "the stage has at least its minimum sample, the item does not pass, and the one-sided confidence bound in the hypothesized direction at the item's corrected level excludes an effect of the preregistered MDE for the realized n (minimum_detectable_effect)", + "passes_stage": "the item passes its stage rule, with a stage-specific name: 'nominated' at development (the screen), 'screened' at validation (Holm in the 35-item family plus minimum sample and, for a tradable cell, the robustness means), 'supported (confirmatory)' at the holdout (the same at the holdout). Only the holdout label is a claim.", + "not_supported_mde_excluded": "the stage has at least its minimum sample, the item does not pass, and the one-sided bootstrap-percentile confidence bound (from the same B draws) at Bonferroni level 0.05/35 in the item's direction excludes an effect of the preregistered MDE for the realized n (minimum_detectable_effect). For H3-c and H4 at development, where no direction is locked yet, both one-sided bounds at 0.05/70 must exclude the MDE in their own directions. The fixed Bonferroni level is used because Holm's step-down level is not defined for items after the first retained hypothesis.", "underpowered": "the stage is below its minimum sample, or the item neither passes nor excludes the MDE" }, + "hypothesis_verdict": "A hypothesis-level verdict (each not_supported_if) is computed at validation, reported as a screening verdict, and, for a hypothesis with a carried item, at the holdout, reported as the confirmatory verdict. Each uses the labels of its own stage only: 'no tradable cell passes' means no tradable cell of that hypothesis passes that stage.", + "validation_reporting": "Every item's validation estimate, interval and label are computed and reported descriptively, whether or not development nominated it; only nominated items receive a Holm p, and the rest score p = 1 in the Holm run.", "rule": "Only these three labels are used. A miss is never reported as 'falsified' or as proof of no effect; 'not supported' always states the size it excludes. A result in the direction opposite to a one-sided alternative is reported descriptively with its estimate and interval and supports no claim, because it was not tested." }, "hypotheses": { @@ -99,8 +105,8 @@ {"id": "H1-I", "statistic": "same, population I", "alternative": "less than 0"} ], "tradable_cells": "2 populations x 3 arms x 3 terciles = 18; each tested for mean net > 0", - "not_supported_if": "H1-D and H1-I are both labelled not_supported_mde_excluded, and no tradable cell passes development. A high-MAX outperformance is outside the one-sided alternative, is reported descriptively only, and supports no claim about the reversal prior.", - "data_status": "available now (Alpaca SIP daily and minute bars, official prices)", + "not_supported_if": "at the verdict stage (outcome_reporting.hypothesis_verdict), H1-D and H1-I are both labelled not_supported_mde_excluded and no H1 tradable cell passes. A high-MAX outperformance is outside the one-sided alternative, is reported descriptively only, and supports no claim about the reversal prior.", + "data_status": "expected available (Alpaca SIP daily and minute bars, quotes and official prices); historical quote and auction-print coverage for 2016-2020 is unverified and is a freeze precondition", "chronology": "common (development 2017-2019, validation 2020, prospective holdout)" }, "H2": { @@ -109,7 +115,7 @@ {"source": "Barber & Odean (2008) RFS 21(2); Barber, Huang, Odean, Schwarz (2022) JF doi 10.1111/jofi.13183; Barber, Lin, Odean (2023) JFQA", "status": "contested: refutation concerned a citation and the fit, not the direction; used as a prior on retail P&L only"} ], "populations": ["D_daily", "I_intraday"], - "split": "catalyst: an 8-K with Item 1.01 whose EDGAR acceptance time precedes the decision and falls after the previous regular close (D) or within the prior 3 sessions (I), per catalyst.py; attention: no qualifying filing. As-known identities only; an unparsed filing is unknown, reported apart and excluded from both groups.", + "split": "catalyst: an 8-K with Item 1.01 whose EDGAR acceptance time precedes the decision and falls after the previous regular close (D) or in [the regular open of session t-3, decision minute m) (I); attention: the symbol's as-known CIK has a complete, parsed filing index for that window with no qualifying filing. A symbol with no as-known CIK, an incomplete index or an unparsed filing is 'identity/item unknown', reported apart and excluded from both groups; absence of a filing is never inferred from silence.", "exit_arms": {"b_lane": "as H1"}, "sizing": "1x equal notional, capacity-capped", "primary_tests": [ @@ -117,9 +123,9 @@ {"id": "H2-I", "statistic": "same, population I", "alternative": "greater than 0"} ], "tradable_cells": "2 populations x 2 groups = 4", - "not_supported_if": "H2-D and H2-I are both labelled not_supported_mde_excluded and neither group passes development: the catalyst filter adds nothing beyond the volume filter at the detectable size.", - "data_status": "available now (SEC EDGAR, public domain, 10 req/s, existing recipes; Alpaca SIP)", - "chronology": "common" + "not_supported_if": "at the verdict stage (outcome_reporting.hypothesis_verdict), H2-D and H2-I are both labelled not_supported_mde_excluded and no H2 tradable cell passes: the catalyst filter adds nothing beyond the volume filter at the detectable size.", + "data_status": "gated: cannot be computed with the repository's code as it stands. catalyst.py sets available_at = max(acceptance, local first observation), so every 2016-2020 filing observed in 2026 fails historical eligibility; it has no Item 1.01 parser and no historical ticker-to-CIK map. Before the freeze H2 needs (1) a preregistered historical availability rule, EDGAR acceptance time taken as original availability with that limitation stated, which departs from the catalyst-experiment rule and must be recorded as such; (2) a committed Item parser (for example from the header's ITEM INFORMATION lines) with synthetic tests; (3) a dated as-known ticker-to-CIK source; (4) a metadata coverage check of identity and index completeness for D and I symbol-sessions in 2016-2020. If any is missing at the freeze, H2 is recorded as not tested (multiple_testing.data_status_at_freeze).", + "chronology": "common, if H2 is tested" }, "H3": { "name": "Overnight versus intraday legs of mover holds", @@ -128,21 +134,21 @@ {"source": "Maroy, SSRN 5095349 (posted 2025-01-12), VWAP and ladder exits", "status": "contested: HTTP 403, not read; excluded from the confirmatory family"} ], "populations": ["D_daily"], - "price_sources": "official open = the primary listing's opening auction print from the auctions collection (collect.py, sessions_io.official_price 'o' at aa6fc79); official close likewise ('c'). A leg with a missing official print is dropped and counted; no bar or quote substitutes in H3-c.", + "price_sources": "official open = the primary listing's opening auction print from the auctions collection (collect.py, sessions_io.official_price 'o' at aa6fc79); official close likewise ('c'). A trade with any missing official print among its 8 legs is excluded from H3-c and counted; no bar or quote substitutes in H3-c, and no trade is averaged over fewer legs.", "exit_arms": { "a_intraday": "enter at the first eligible quote at or after 09:35 on t+1; exit at or after 15:55 the same session", - "b_overnight": "enter at the first eligible quote at or after 15:55 on t+1; exit at the first eligible quote at or after 09:30 on t+2", + "b_overnight": "enter at the first eligible quote at or after 15:55 on t+1, timeout 300 s, else no entry (counted); exit at the first eligible quote at or after 09:30 on t+2", "c_decomposition": "diagnostic only, not traded: over the sessions t+1 .. t+5 of the D b_lane hold, overnight legs k = 1..4 are official close of t+k to official open of t+k+1, and intraday legs k = 2..5 are official open to official close of t+k. The partial first-session leg is excluded, so each trade has 4 legs of each kind, and together they decompose official close(t+1) to official close(t+5)." }, "sizing": "1x; arm b stays at 1x in any later paper use until leverage-ladder-2x and its overnight cap are clean", "primary_tests": [ {"id": "H3-a", "statistic": "mean net(a_intraday)", "alternative": "greater than 0"}, {"id": "H3-b", "statistic": "mean net(b_overnight)", "alternative": "greater than 0"}, - {"id": "H3-c", "statistic": "mean over trades of (mean of the 4 overnight leg log returns - mean of the 4 intraday leg log returns), gross, legs as in c_decomposition", "alternative": "not equal to 0"} + {"id": "H3-c", "statistic": "mean over trades of (mean of the 4 overnight leg log returns - mean of the 4 intraday leg log returns), gross, legs as in c_decomposition", "alternative": "not equal to 0 at development; after development, one-sided in the direction of the development estimate (multiple_testing.direction_lock)", "minimum_sample": "one-sample paired statistic: the tradable_cell minima apply to trades with all 8 legs"} ], "tradable_cells": "H3-a and H3-b are the tradable cells (counted once)", - "not_supported_if": "H3-c is labelled not_supported_mde_excluded, and neither H3-a nor H3-b passes after costs.", - "data_status": "available now (Alpaca SIP minute bars; official opening and closing auction prints)", + "not_supported_if": "at the verdict stage (outcome_reporting.hypothesis_verdict), H3-c is labelled not_supported_mde_excluded and neither H3-a nor H3-b passes after costs.", + "data_status": "expected available (Alpaca SIP minute bars and quotes; official opening and closing auction prints); 2016-2020 quote and auction-print coverage is a freeze precondition", "chronology": "common" }, "H4": { @@ -156,18 +162,18 @@ "parent_trades": "the b_lane trades of populations D and I (the trades H1, H2 and H5 share), deduplicated by symbol|entry session|entry timestamp. Other arms (H1 a_fast and c_session, H3 a and b) are not parents. The unit of analysis and of the session block bootstrap is one deduplicated parent trade.", "population": "every parent trade whose security, during the holding interval, is in state H (halted across all US markets) or, for a Nasdaq-listed security, P (LULD pause), per the covered halt source. The two strata are reported separately: an LULD pause is identified only for Nasdaq-listed names; for other listings the source reports a pause as a halt.", "exit_arms": { - "a_reopen": "exit at the reopening-auction print, else the first eligible post-reopen quote", + "a_reopen": "exit at the primary listing's reopening-cross print, else the mid of the first eligible post-reopen quote. The print source is fixed before the freeze by metadata (for example XNAS.ITCH cross-trade messages for Nasdaq listings, or SIP trade conditions that mark the reopening cross); the Databento status schema carries no trades. Without a fixed print source, a_reopen uses the first eligible post-reopen quote only.", "b_ignore": "hold to the parent b_lane exit, ignoring the halt" }, - "rules": "No fills during a halt; a resting stop fills through the gap at the first print. Pause duration is logged as a covariate only.", + "rules": "No fills during a halt; a resting stop fills through the gap at the first print. Pause duration is logged as a covariate only. Both arms' sides are costed at 1.25 x max(table hs', the realized half-spread of the fill quote, or of the first eligible post-reopen quote for a cross print), because post-reopen spreads on halted movers are wider than the table's time-bucket cells. Bootstrap blocks are 10 sessions for all H4 parents.", "sizing": "inherits the parent arm", "primary_tests": [ - {"id": "H4", "statistic": "mean over halted parent trades of net(a_reopen) - net(b_ignore)", "alternative": "not equal to 0"} + {"id": "H4", "statistic": "mean over halted parent trades of net(a_reopen) - net(b_ignore) (paired, one-sample)", "alternative": "not equal to 0 at development; after development, one-sided in the direction of the development estimate (multiple_testing.direction_lock)"} ], "minimum_halted_trades": {"development": 60, "validation": 40, "holdout": 40}, - "not_supported_if": "H4 is labelled not_supported_mde_excluded. A stage below its minimum halted trades is underpowered, not a miss; either way the halt state stays a safety rule only.", + "not_supported_if": "at the verdict stage (outcome_reporting.hypothesis_verdict), H4 is labelled not_supported_mde_excluded. A stage below its minimum halted trades is underpowered, not a miss; either way the halt state stays a safety rule only.", "data_status": "gated: Databento XNAS.ITCH status schema (paid, metered; covers halt and resume state for every security traded on the Nasdaq execution system, with the P pause state and J auction collar for Nasdaq-listed securities only; cost and coverage start unverified; *contested*), or Nasdaq Trader halt history (free, limited lookback, pilot only). Whether SIP conditions can rebuild halts is unverified.", - "chronology": "common, restricted to the halt source's covered sessions as established by metadata before any outcome. A Nasdaq Trader pilot is exploratory and cannot pass. If the covered development span is shorter than 1 year, H4 is not tested and scores p = 1 in the family. The halt source is used only for H4 and never changes the H1-H3 trade sets." + "chronology": "common, restricted to the halt source's covered sessions as established by metadata before any outcome. A Nasdaq Trader pilot is exploratory and cannot pass. If the covered development span is shorter than 1 year, or the data are not in hand at the freeze, H4 is not tested and scores p = 1 at every stage, permanently (multiple_testing.data_status_at_freeze). The halt source is used only for H4 and never changes the H1-H3 trade sets." }, "H5": { "name": "Days-to-cover crowding overlay", @@ -177,66 +183,76 @@ {"source": "blueprints/us-equities/acceptance-wave/factors-regimes.md: FINRA consolidatedShortInterest is not a realtime squeeze signal", "status": "repository source read"} ], "population": "D_daily", - "covariate": "DTC = the most recent FINRA short interest published before the decision (publication and settlement lag respected) / 20-session median share volume ending t; terciles by tercile_rule (trailing 252-session breakpoints of D)", + "covariate": "DTC = the most recent FINRA short interest published before the decision (publication and settlement lag respected) / 20-session median share volume ending t, both in session t's share basis (populations.corporate_actions); terciles by tercile_rule (trailing 252-session breakpoints of D)", "exit_arms": {"b_lane": "as H1"}, "sizing": "1x; the overlay filters and never sizes", "primary_tests": [ {"id": "H5-p", "statistic": "mean net(high DTC) - mean net(low DTC)", "alternative": "less than 0"}, - {"id": "H5-s", "statistic": "mean net(high DTC) - mean net(middle and low DTC pooled)", "alternative": "greater than 0 (squeeze)", "prior": "none sourced: Hong et al. point the other way (H5-p) and the squeeze base-rate statement rests on an unverified draft. H5-s is a secondary test with no directional prior and is expected to be not supported."} + {"id": "H5-s", "statistic": "mean net(high DTC) - mean net(middle and low DTC pooled); unequal groups, so its MDE uses sqrt(1/n1 + 1/n2)", "alternative": "greater than 0 (squeeze)", "prior": "none sourced: Hong et al. point the other way (H5-p) and the squeeze base-rate statement rests on an unverified draft. H5-s is a secondary test with no directional prior and is expected to be not supported."} ], "tradable_cells": "3 DTC terciles", - "not_supported_if": "H5-p (or H5-s) is labelled not_supported_mde_excluded.", + "not_supported_if": "at the verdict stage (outcome_reporting.hypothesis_verdict), H5-p (or H5-s, reported separately) is labelled not_supported_mde_excluded and no H5 tradable cell passes.", "data_status": "gated on FINRA short-interest access and terms, which the user must establish; bi-monthly, an overlay only", - "chronology": "common, restricted to sessions whose decision has a published figure. If published history does not cover at least 1 development year and the whole validation year, H5 is not tested and both tests score p = 1." + "chronology": "common, restricted to sessions whose decision has a published figure. Published history must cover the 2016 warm-up (tercile_rule needs 60 prior D events), at least 1 development year and the whole validation year. If it does not, or the data are not in hand at the freeze, H5 is not tested and both tests score p = 1 at every stage, permanently (multiple_testing.data_status_at_freeze)." }, "H6": { - "name": "Execution parity: broker-held exits versus client-side ratchet (engineering, not alpha)", + "name": "Execution parity: broker-held exits versus client-side exits (engineering, not alpha)", "moved_to": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json", - "why": "H6 accrues on a live paper timeline unrelated to the 2020 validation window or the 252-session holdout, and its equivalence test needs larger stages than this family's. It is its own engineering protocol with its own two-test family; it uses no slot of the alpha family, H1-H5 never wait for it, and its result never opens this protocol's holdout." + "status": "gated in its own draft: it has no position source yet, and its call-count test was restated as descriptive because the client-side arm needs fewer calls by construction", + "why": "H6 accrues on a live paper timeline unrelated to the 2020 validation window or the 252-session holdout, and its equivalence test needs larger stages than this family's. It is its own engineering protocol with its own one-test family; it uses no slot of the alpha family, H1-H5 never wait for it, and its result never opens this protocol's holdout." } }, "multiple_testing": { "family": "one family across H1-H5: the 8 non-tradable primary tests (H1-D, H1-I, H2-D, H2-I, H3-c, H4, H5-p, H5-s) plus the 27 tradable cells (H1 18, H2 4, H3 2, H5 3), m = 35. H6 is not in this family.", - "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout. An item not carried to a stage, or not tested for lack of data, scores p = 1 there. Two-sided tests (H3-c, H4) use 2 x min(one-sided p).", + "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout. An item not carried to a stage, or not tested for lack of data, scores p = 1 there. The validation results file, when committed, contains all 35 validation p-values (1 for items not carried or not tested); an incomplete file is refused by the evaluator.", + "direction_lock": "H3-c and H4 are two-sided only at development: the development screen uses the two-sided p = 2 x min(one-sided p), and the sign of the development estimate is recorded in the development results file. Validation and the holdout then use the one-sided test in that recorded direction, in the same Holm slot. A significant result in the opposite direction at validation or the holdout is reported descriptively and cannot pass.", + "data_status_at_freeze": "At the freeze each of H2, H4 and H5 is recorded as either 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently'. Data acquired after the freeze can support only a new protocol version with its own family; it never enters this family's Holm run, so a late small p cannot loosen other items' thresholds.", "strictest_level": 0.0014286, - "development_screen": "unadjusted p <= 0.05 in the stated direction, with the minimum sample; development only nominates items for validation", - "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) on other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 35) = 4.78e-5. With B = 100,000 the smallest bootstrap p is 1.0e-5 one-sided and 2.0e-5 two-sided, both below that level. Each p is reported beside a normal-tail p from the same bootstrap standard error. An item that passes Holm but not this level is labelled v3-family significant, lineage-unconfirmed.", + "development_screen": "unadjusted p <= 0.05 with the minimum sample: one-sided in the stated direction for directional items, two-sided for H3-c and H4 (direction_lock); development only nominates items for validation", + "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) and broad-universe-mover-v1's 60 predeclared signal variants (5 signals x 3 horizons x 2 selections x 2 instrument lanes) on overlapping or other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 60 + 35) = 4.52e-5. broad-universe's 2 controls and its descriptive lane are not counted: they are base-rate reports with no pass rule (its promotion 'never'); that exclusion is a stated limitation. The label compares the bootstrap p with 4.52e-5; with B = 100,000 the smallest one-sided bootstrap p is 1.0e-5, below that level. Each p is reported beside a normal-tail p from the same bootstrap standard error. An item that passes Holm but not this level is labelled v3-family significant, lineage-unconfirmed.", + "tie_breaks": "Where items tie on a p (for example at the floor 1 / (B + 1)), in the holdout cap of 5 tradable cells or in Holm ordering, ties are broken by the normal-tail p, then by item id.", "tests_not_in_family": "BH (q = 0.10) diagnostics across tercile, year, price-tier and halt-covariate splits are reported but can never open the holdout" }, "minimum_sample": { "tradable_cell": {"development": 300, "validation": 150, "holdout": 150}, "difference_test_per_group": {"development": 150, "validation": 100, "holdout": 100}, + "one_sample_paired": "H3-c uses the tradable_cell minima (trades with all 8 legs); H4 uses its minimum_halted_trades", "rule": "A stage below its minimum reports the item as underpowered (outcome_reporting); it cannot pass that stage." }, "minimum_detectable_effect": { - "formula": "MDE = (z_alpha + z(0.80)) x sigma x sqrt(DEFF / n) for one cell; x sqrt(2) for a two-group difference with n per group. z_alpha = z(1 - 0.05/35) = 2.983 for one-sided tests and z(1 - 0.05/70) = 3.189 for the two-sided H3-c and H4.", + "formula": "MDE = (z_alpha + z(0.80)) x sigma x sqrt(DEFF) x sqrt(1/n) for one cell or a one-sample paired statistic (H3-c, H4); x sqrt(1/n1 + 1/n2) in place of sqrt(1/n) for a two-group difference (so sqrt(2/n) for equal groups; realized trailing terciles are not exactly 1/3 each, and H5-s pools two terciles). z_alpha = z(1 - 0.05/35) = 2.983 for one-sided tests, including H3-c and H4 after development (direction_lock), and z(1 - 0.05/70) = 3.189 for H3-c and H4 at development. The MDE is computed from the planning sigma below and the realized n; it is never re-estimated from outcomes.", "z_sum_one_sided": 3.8243, "z_sum_two_sided": 4.0304, "design_effect_DEFF": 1.5, "sigma_planning": { "30_to_60_minute_arms": 0.10, "same_session_close_arms": 0.25, - "5_session_arms": 0.35 + "5_session_arms": 0.35, + "overnight_arm_H3_b": 0.15, + "leg_difference_H3_c": 0.10, + "halt_difference_H4": 0.35 }, - "sigma_provenance": "0.10 and 0.25 are (P95 - P5) / 3.29 from #162 v1 development aggregates (07:00|G0.20|V1000000 X2 and X1: P5 -15.0% / P95 +18.0%, and P5 -42.1% / P95 +39.5%); normal approximation, which understates fat tails. 0.35 is an assumption with no measured source. DEFF 1.5 for session clustering is an assumption.", + "sigma_provenance": "0.10 and 0.25 are (P95 - P5) / 3.29 from #162 v1 development aggregates (07:00|G0.20|V1000000 X2 and X1: P5 -15.0% / P95 +18.0%, and P5 -42.1% / P95 +39.5%); normal approximation, which understates fat tails. Those rule-exits entered at 07:00 ET in the pre-market, so their spread for 09:35 entries is not measured. 0.35, 0.15 (one 15:55-to-09:30 leg), 0.10 (the difference of two 4-leg means of log returns) and 0.35 for H4 (dominated by the remaining b_ignore path) are assumptions with no measured source. DEFF 1.5 for session clustering is an assumption.", "mde_percent_single_cell_one_sided": { - "n100": {"sigma_0.10": 4.68, "sigma_0.25": 11.71, "sigma_0.35": 16.39}, - "n200": {"sigma_0.10": 3.31, "sigma_0.25": 8.28, "sigma_0.35": 11.59}, - "n300": {"sigma_0.10": 2.70, "sigma_0.25": 6.76, "sigma_0.35": 9.46}, - "n600": {"sigma_0.10": 1.91, "sigma_0.25": 4.78, "sigma_0.35": 6.69} + "n40": {"sigma_0.10": 7.41, "sigma_0.15": 11.11, "sigma_0.25": 18.51, "sigma_0.35": 25.92}, + "n100": {"sigma_0.10": 4.68, "sigma_0.15": 7.03, "sigma_0.25": 11.71, "sigma_0.35": 16.39}, + "n200": {"sigma_0.10": 3.31, "sigma_0.15": 4.97, "sigma_0.25": 8.28, "sigma_0.35": 11.59}, + "n300": {"sigma_0.10": 2.70, "sigma_0.15": 4.06, "sigma_0.25": 6.76, "sigma_0.35": 9.46}, + "n600": {"sigma_0.10": 1.91, "sigma_0.15": 2.87, "sigma_0.25": 4.78, "sigma_0.35": 6.69} }, - "mde_percent_two_group_difference_one_sided": { - "n100": {"sigma_0.10": 6.62, "sigma_0.25": 16.56, "sigma_0.35": 23.18}, - "n200": {"sigma_0.10": 4.68, "sigma_0.25": 11.71, "sigma_0.35": 16.39}, - "n300": {"sigma_0.10": 3.82, "sigma_0.25": 9.56, "sigma_0.35": 13.39}, - "n600": {"sigma_0.10": 2.70, "sigma_0.25": 6.76, "sigma_0.35": 9.46} + "mde_percent_two_group_difference_one_sided_equal_groups": { + "n100": {"sigma_0.10": 6.62, "sigma_0.15": 9.94, "sigma_0.25": 16.56, "sigma_0.35": 23.18}, + "n200": {"sigma_0.10": 4.68, "sigma_0.15": 7.03, "sigma_0.25": 11.71, "sigma_0.35": 16.39}, + "n300": {"sigma_0.10": 3.82, "sigma_0.15": 5.74, "sigma_0.25": 9.56, "sigma_0.35": 13.39}, + "n600": {"sigma_0.10": 2.70, "sigma_0.15": 4.06, "sigma_0.25": 6.76, "sigma_0.35": 9.46} }, - "mde_percent_two_group_difference_two_sided": { - "n100": {"sigma_0.10": 6.98, "sigma_0.25": 17.45, "sigma_0.35": 24.43}, - "n200": {"sigma_0.10": 4.94, "sigma_0.25": 12.34, "sigma_0.35": 17.28}, - "n300": {"sigma_0.10": 4.03, "sigma_0.25": 10.08, "sigma_0.35": 14.11}, - "n600": {"sigma_0.10": 2.85, "sigma_0.25": 7.12, "sigma_0.35": 9.97} + "mde_percent_single_cell_two_sided_development_only": { + "n60": {"sigma_0.10": 6.37, "sigma_0.35": 22.30}, + "n100": {"sigma_0.10": 4.94, "sigma_0.35": 17.28}, + "n300": {"sigma_0.10": 2.85, "sigma_0.35": 9.97}, + "n600": {"sigma_0.10": 2.02, "sigma_0.35": 7.05} }, + "table_note": "The two-sided table applies only to H3-c (sigma 0.10) and H4 (sigma 0.35) at development; both are one-sample paired statistics. The earlier two-group two-sided table was misapplied and is removed. n40 and n60 are H4's validation/holdout and development minima.", "reading": "Only large effects can pass or be excluded. The literature's effects (about 1% a month for MAX and days to cover) are far below these MDEs, so a miss on H1-D, H1-I or H5 will most likely be labelled underpowered, and v3 is informative only about large mover-specific effects." }, "cost_model": { @@ -272,21 +288,21 @@ "provenance": "The square-root functional form and the c grid are design choices bracketing the unknown; the sweep supplies no source for them." }, "out_of_period_spreads": { - "sample": "Before the freeze, 1 in 20 development (2017-2019) D and I entries is selected by quotes.py's sample_key at aa6fc79: int.from_bytes(sha256('symbol|session|hhmm')[:8], 'big') % 20 == 0, with the v3 entry's symbol, entry session and entry HH:MM ET. Each selected entry is stamped at its entry timestamp, entry + 15 min, entry + 60 min and 5 minutes before that session's close (the v1 stamp set), all exit-agnostic; no exit path is computed.", + "sample": "The first step of the frozen evaluation, after the freeze and before any other outcome: 1 in 20 development (2017-2019) D and I entries is selected by quotes.py's sample_key at aa6fc79: int.from_bytes(sha256('symbol|session|hhmm')[:8], 'big') % 20 == 0, with the v3 entry's symbol, entry session and entry HH:MM ET. Each selected entry is stamped at its entry timestamp, entry + 15 min, entry + 60 min and 5 minutes before that session's close (the v1 stamp set). Those stamps coincide with D c_session and I b_lane exits, so they are outcome prices; measuring them before the freeze would expose development outcomes, which #162 v1 avoided by freezing first. The sampling rule, the table method and the combine rule are fixed here, so no value is chosen after the sample is seen.", "table": "cells are built with quotes.py's table method at aa6fc79, including the D7 stamp-window guard and v1's sparse-cell fallbacks: a cell with >= 30 observations (MIN_CELL) uses its trimmed mean, else the time x price pool if it has >= 30, else the table's 90th percentile", "combine": "each cell's cost is max(hs from the base table, hs from the 2017-2019 table); fix_1 is then applied to the result" }, - "per_side": "1.25 x hs' + impact, for continuous and auction sides alike (v1's 0.5x auction discount is not reused)", + "per_side": "1.25 x hs' + impact, for continuous and auction sides alike (v1's 0.5x auction discount is not reused); H4 sides use the halt-specific rule in hypotheses.H4.rules", "fees": "#162 fees.json at aa6fc79 (sha256 fa1f468b80f78dc0c050948123116f577e38ae9f8c03f9fb360d11042a3fed3d) by reference, extended before the freeze with SEC Section 31 and FINRA TAF rates in force during 2017-2020 from primary sources; Alpaca commission 0; IBKR tiered as a broker variant", "stress": "2 x (spread + slippage) with c = 2.0", "break_even": "the cost multiple at which each cell's mean net return reaches 0 is reported" }, "statistics": { "estimator": "trade-level mean net return; also median, 1%-winsorised mean, mean without the top 5 sessions and mean of per-session means", - "bootstrap": "circular session block bootstrap, B = 100,000; block length 5 sessions for same-session arms and 10 sessions for multi-session arms; difference tests resample sessions jointly for both groups; seed = the first 8 bytes of sha256(frozen protocol id), big-endian", + "bootstrap": "circular session block bootstrap, B = 100,000; block length 5 sessions for same-session arms and 10 sessions for multi-session arms and for H4 (whose parents mix both); difference tests resample sessions jointly for both groups; generator numpy.random.Generator(PCG64), seeded by numpy.random.SeedSequence(the first 8 bytes of sha256(frozen protocol id), big-endian), with one spawned child stream per item in ascending item-id order", "p_value": "one-sided p = (1 + #{b : stat_b on the null side of 0}) / (B + 1); two-sided p = min(1, 2 x the smaller one-sided p)", "robustness_required_for_a_pass": "a tradable cell's validation and holdout passes also need a positive winsorised mean, mean without the top 5 sessions and mean of per-session means", - "sensitivities": ["two-way (session, symbol) clustering", "without split-day and fallback-price rows", "2020 reported alone", "survivorship-limited label before 2020", "validation restricted to names with 20-session median dollar volume < $2M (outside broad-universe's descriptive lane)"] + "sensitivities": ["two-way (session, symbol) clustering", "without split-day and fallback-price rows", "2020 reported alone", "survivorship-limited label before 2020", "validation restricted to symbol-sessions that were not a broad-universe lane decision (exposure_registry.consequence)", "cost from the realized half-spread of each fill quote (populations.fill_price)", "censored segment-end trades removed (chronology.boundaries)"] }, "holdout_gate": { "opens_only_for": "items that passed validation: a primary test or tradable cell with Holm-adjusted validation p <= 0.05 in the 35-item family, the minimum validation sample and, for a tradable cell, the robustness means. At most 5 tradable cells (lowest validation p) and every validated primary test.", @@ -299,24 +315,31 @@ "the list of validated items is empty", "a requested item is not in that list, or a requested H4 read asks for more than h4_scope", "the access log has no committed 'granted' entry for this run", - "a previous holdout run exists (the holdout is read once; failed or partial runs stay in the log and are never called untouched)" + "a previous holdout run exists (the holdout is read once; failed or partial runs stay in the log and are never called untouched). A granted 'count' entry is not a holdout run", + "a 'count' request asks for anything beyond entry and assignment counts per carried item, or a 'count' read is requested other than once at the end of the 252 sessions and once at the end of each extension block" ], "access_log": { "path": "blueprints/us-equities/mover-v3/holdout-access-log.jsonl", "mode": "append-only JSON lines, committed and pushed before the read it authorizes", "fields": ["utc", "actor_role", "protocol_id", "protocol_sha256", "code_revision", "validation_results_sha256", "requested_items", "purpose", "decision (granted or refused)", "snapshot_sha256", "rows_read", "results_sha256", "exposed_symbol_sessions"], - "rule": "Any holdout read without a preceding committed granted entry voids the holdout for every item. Refusals are logged too. Collection batches are logged with purpose 'collect' and rows_read 0, and list the accrual exposure of that batch (chronology.holdout.accrual_exposure)." + "purposes": {"collect": "a collection batch, rows_read 0, listing that batch's accrual exposure", "count": "one at the end of the 252 sessions and one at the end of each extension block: entry and tercile, catalyst-group or halted-parent assignment counts per carried item, never an exit or outcome price; used only for the extension decision", "read": "the single outcome read for carried items"}, + "rule": "Any holdout read without a preceding committed granted entry of the matching purpose voids the holdout for every item. Refusals are logged too. The 'count' path is tested in the synthetic suite, including a test that it computes no exit price." } }, "leverage_and_exits": "Research runs use 1x equal notional. Paper use of any passing rule follows the README's exit and leverage policy: at most one broker-held closing order per share (a trailing_stop or an OCO per quantity tranche, as the Alpaca docs require), the leverage-schedule-v1-20260922 ceiling, and the leverage-ladder-1x/2x/4x gate rows in catalogs/us-equities/gates-20260922.json, one rung at a time with automatic step-down. The edge-shrink (fractional Kelly) term stays 0 until a holdout pass.", "freeze_preconditions": [ "An independent pre-outcome review of this draft from a different session and model family, with every finding resolved in a dated record", "Direct full-text reads of Lou-Polk-Skouras 2019, Akbas-Boehmer-Jiang-Koch 2022 and MacLean-Thorp-Ziemba 2010, or removal of every claim that depends on them (the two JFE DOIs resolve in Crossref; that is metadata only)", - "The 2017-2019 development cost sample fixed by rule and measured; fees extended to 2017-2020 from primary sources", - "Metadata-only coverage checks: the Databento get_cost quote and XNAS.ITCH coverage start (H4); FINRA short-interest history start and terms (H5). No outcome is computed.", - "Study code implementing this protocol committed with synthetic tests before any outcome, and the evaluator's holdout refusals tested", + "The 2017-2019 development cost sample fixed by rule (not measured: it is the first step after the freeze); fees extended to 2017-2020 from primary sources", + "Metadata-only coverage checks, no outcome computed: Alpaca SIP historical quote coverage and eligible-quote rates, and official opening and closing auction-print coverage, for 2016-2020 (H1-H3); the Databento get_cost quote, XNAS.ITCH coverage start and a reopening-cross print source (H4); FINRA short-interest history start, including the 2016 warm-up, and terms (H5); identity and filing-index completeness for H2's gates", + "The data status of H2, H4 and H5 recorded as 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently' (multiple_testing.data_status_at_freeze)", + "The universe and daily-dataset snapshot pinned by sha256 with its asof date and asof policy", + "Study code implementing this protocol committed with synthetic tests before any outcome, including a split and ex-dividend crossing, early-close sessions, segment-end censoring, the direction lock and the evaluator's holdout refusals and 'count' path", "The exposure registry updated with everything the author saw since this draft", "status set to frozen, frozen_before_outcomes true, and the protocol sha256 recorded in a pushed commit" ], + "review_record": [ + {"date": "2026-09-24", "round": 2, "input": "two independent pre-outcome rereviews of commit 0b924e7 (findings S1-S14 and F1-F13)", "resolution": "S1 direction_lock; S2 data_status_at_freeze and complete validation file; S3 and F12 cost sample moved after the freeze; S4 sigma entries, label bound, unequal-group formula, H3-c minimum and leg rule, two-sided table replaced; S5 holdout_breakpoints; S6 'count' purpose; S7 and F6 boundaries, session_times and H3-b timeout; S8 stage labels and hypothesis_verdict; S9 README H2/H5 rows; S10 fixed holdout start and accrual keys; S11 tie_breaks, H4 block length, PCG64 streams; S12 C0 as superset and lane-decision slice; S13 lineage 1,107; S14 official-close rule, I window, breakpoints over all events; F3 H2 gated; F4 corporate_actions and evaluate_v2.py pin; F5 fill_price and coverage preconditions; F9 accrual keys and disjoint H6 symbols; F10 reopen print source and halt cost; F11 README rate-limit text; F13 preconditions. F1, F2, F7 and F8 are resolved in the H6 draft (H6 gated, H6-c descriptive). No finding rejected."} + ], "overturn": "Any headline result is overturned if a rerun on the same frozen inputs and runtime does not reproduce it byte for byte, if an independent quote sample puts the cost table off by more than 2x, or if the access log shows a holdout read that no committed granted entry preceded." } diff --git a/manifests/evidence.json b/manifests/evidence.json index 594870470..8ccc11020 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -8448,18 +8448,18 @@ }, { "path": "blueprints/us-equities/mover-v3/README.md", - "sha256": "10a7456a92fa9e0e1eadeee9e275b049bcca73e6207e1b18f38eee4832821f2f", - "bytes": 34215 + "sha256": "27da45936e27b06c672e0155a330499fb3efdc0946192466e217ed1836c928e4", + "bytes": 38573 }, { "path": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json", - "sha256": "49f220eb6400a36f1041d3d943c8ae6fc6ae7dd19f424dac0b2b23e37117498f", - "bytes": 8570 + "sha256": "e9dfdfb0f73aba4362d1cc4b3d4b6957d7b06a6b45808f0617de65191a943c1e", + "bytes": 12600 }, { "path": "blueprints/us-equities/mover-v3/protocol-draft.json", - "sha256": "8ed1070adbe17d380c70c49d58f6bb13ca05063892b7df35d9f878497fe30c44", - "bytes": 41093 + "sha256": "60a06002b4ab9954b42c411eefec9972f6c0e43e69d0446e8401eff770af27f9", + "bytes": 57171 }, { "path": "blueprints/us-equities/nanosecond-replay/README.md", From 3053b31a5acc73ed76c17443b07fb507018e9d01 Mon Sep 17 00:00:00 2001 From: Scout Date: Thu, 24 Sep 2026 03:40:25 -0400 Subject: [PATCH 4/8] Mover v3 review round 3: holdout exposure, pre-freeze access, calendar, fees and H6 references Protocol draft (status unchanged: draft_pending_independent_pre_outcome_review, frozen_before_outcomes false): - Every holdout trade enters the primary statistic; paper-order exposure is logged at order time, excluded only in a sensitivity, and capped at 5% per carried item; an ad hoc read voids rather than excludes; missing accrual logs are an evaluator refusal. - Count-only pre-freeze access path with sealed raw pages and non-v3 coverage stamps. - Accepted official-print labels (closing_print_other_exchange rejected), restated ref/gain formulas with no bar fallback, sessions_io.py pinned. - v3-owned XNYS calendar (exchange_calendars 4.13.2) and fees file; the pinned rules.py close-time and fee functions are barred for v3 dates. - N0 fixed by the freeze commit; a late validation push voids the holdout. - H4 halt edge cases, holdout breakpoint gap, 35 preregistered item ids with per-stage SeedSequence spawning, common extension window, raw-p cap ranking, fill-quote eligibility and fetch-failure rule, H5 DTC event set, lineage label on both bootstrap and normal-tail p. - Suspected unadjusted splits excluded before membership; coverage rule with H1 and H3 data status now fixed at the freeze. H6 draft: per-arm trigger references plus a common NBBO-bid basis, stop-market OCO leg, intention-to-treat H6-e, flat-symbol entries, 30 s entry cancel and tranche-reject fallback, pilot-based power precondition, alpha-disjoint and sealed population. README: exits.py precedence corrected, holdout and data-status text aligned. Evidence manifest hashes recomputed for the three changed files. Co-Authored-By: Claude Opus 5.5 (1M context) --- blueprints/us-equities/mover-v3/README.md | 51 ++++++---- .../mover-v3/h6-execution-parity-draft.json | 38 +++++--- .../us-equities/mover-v3/protocol-draft.json | 93 ++++++++++++------- manifests/evidence.json | 12 +-- 4 files changed, 125 insertions(+), 69 deletions(-) diff --git a/blueprints/us-equities/mover-v3/README.md b/blueprints/us-equities/mover-v3/README.md index 30ea5c40b..9bc8c147f 100644 --- a/blueprints/us-equities/mover-v3/README.md +++ b/blueprints/us-equities/mover-v3/README.md @@ -18,8 +18,11 @@ private because its verifier notes contain host paths. This README and the suppl early-entry v1 and v2 studies (`blueprints/us-equities/mover-early-entry/**`). **v1: 0 of 768 rule-exits pass development. v2: no pass in families E, F or P** (results sha256 `75a5004b...` after deviation D7). v3 does not modify those files. It reuses their pipeline only by reference, pinned to main at `aa6fc79` with git-blob and sha256 hashes -for `rules.py`, `quotes.py`, the cost table and `evaluate_v2.py` (for D7's split and ex-dividend accounting only; -`protocol-draft.json`). The pin includes D7's stamp-window guard in `quotes.py`. The earlier pin `92062e8` survives only on the PR branch. v3 does not read #162's reserved holdout. The +for `rules.py`, `quotes.py`, `sessions_io.py` (`official_price` only), the cost table and `evaluate_v2.py` (for D7's +split and ex-dividend accounting only; `protocol-draft.json`). The pin includes D7's stamp-window guard in `quotes.py`. +That code is valid for 2021-2026 only: its early-close list starts in 2021 and its fee file has no SEC rate before +2020. v3 therefore owns an XNYS session calendar (`exchange_calendars` 4.13.2) and a fee file with sourced 2017-2020 +rows, both pinned at the freeze, and never calls `rules.py`'s close-time or fee functions. The earlier pin `92062e8` survives only on the PR branch. v3 does not read #162's reserved holdout. The 2021 control segment of the catalyst experiment is permanently inspected and is not a fresh holdout. ## Why continuation is not supported @@ -59,6 +62,12 @@ from `rules.py` by reference: `dollar_volume_at_t >= $1M`, with `price_at_t >= $1`. The entry is the first eligible quote at or after decision + 60 s. +The draft restates these formulas exactly, since `rules.py` does not define `ref` or the gain. Official opens and +closes count only from the listing exchange's auction print or official close. A close printed only on another +exchange, or a missing previous close, means no event, with no bar fallback. A session whose raw close ratio sits +within 1% of an integer split ratio with no adjustment is treated as an unadjusted split and is excluded before +membership. Every such case is counted. + H1-H3 use no halt data. As in the catalyst experiment, a halt is never inferred from missing data: fills need an eligible quote, so a trade set never depends on a later halt-data purchase. Halt state enters only H4. @@ -69,12 +78,12 @@ exposure registry records this. | H | Question | Entry | Exit arms | Sizing | Not supported if | Data | |---|---|---|---|---|---|---| -| H1 | Does MAX (lottery) conditioning flip the sign? | D and I, with MAX(21 sessions) terciles as a covariate. Breakpoints come from the same population's events in the prior 252 sessions, so they are known at the decision; no new thresholds | (a) exit at min(entry + 30 min, 15:55); (b) the lane horizon (D: 15:55 on the 5th session; I: entry + 60 min); (c) same-session 15:55 | 1x, equal notional, capacity-capped | not supported: at the verdict stage, both high-MAX minus low-MAX tests for arm (b) (D and I) exclude a negative effect of the MDE size, and no H1 arm-by-tercile cell passes. A high-MAX outperformance is outside the one-sided test and supports no claim | **expected available** (Alpaca SIP bars and quotes); 2016-2020 quote and auction coverage is a freeze precondition | +| H1 | Does MAX (lottery) conditioning flip the sign? | D and I, with MAX(21 sessions) terciles as a covariate. Breakpoints come from the same population's events in the prior 252 sessions, so they are known at the decision; no new thresholds | (a) exit at min(entry + 30 min, 15:55); (b) the lane horizon (D: 15:55 on the 5th session; I: entry + 60 min); (c) same-session 15:55 | 1x, equal notional, capacity-capped | not supported: at the verdict stage, both high-MAX minus low-MAX tests for arm (b) (D and I) exclude a negative effect of the MDE size, and no H1 arm-by-tercile cell passes. A high-MAX outperformance is outside the one-sided test and supports no claim | **expected available** (Alpaca SIP bars and quotes); 2016-2020 quote and auction coverage is unverified; H1 is tested only if the preregistered coverage rule keeps enough years, decided at the freeze | | H2 | Do catalyst-verified moves beat pure attention spikes? | D and I, split by an 8-K Item 1.01 accepted before the decision vs a complete, parsed filing index with no qualifying filing; unknown identity or items are excluded from both groups | the lane horizon only | 1x | not supported: at the verdict stage, H2-D and H2-I (catalyst minus attention) are both labelled not supported with the MDE excluded, and no H2 cell passes | **gated**: `catalyst.py` makes every 2016-2020 filing ineligible (availability = max(acceptance, local observation)) and has no Item parser or historical ticker-to-CIK map. Needs a preregistered acceptance-time availability rule, an Item parser, an as-known CIK source and a coverage check before the freeze, else not tested | -| H3 | Do the overnight and intraday legs differ? | D | (a) intraday only: enter at 09:35, exit at 15:55 the same session; (b) overnight only: enter at 15:55 (300 s timeout), exit at the first eligible quote after the next 09:30 open; (c) the 5-session hold split ex post into 4 overnight legs (official close to official open) and 4 intraday legs (official open to official close), comparing mean per-leg returns (a diagnostic, not traded; a trade missing any leg is excluded). Two-sided at development; its direction is then locked | 1x. Arm (b) stays at 1x in any later paper use until the 2x rung and its overnight cap are clean | not supported: at the verdict stage, the per-leg difference excludes the MDE, and neither traded arm passes | **expected available**; 2016-2020 quote and auction coverage is a freeze precondition | -| H4 | Does a halt-conditional exit beat ignoring the halt? | No new entry. Applies to every D and I lane-horizon (b_lane) trade, deduplicated by symbol, session and entry time, that meets a halt or, for a Nasdaq-listed name, an LULD pause (not only passing arms, so that a no-pass outcome still leaves a sample) | (a) exit at the reopening-cross print (source fixed before the freeze) or the first post-reopen quote; (b) hold to the lane horizon. No fills during a halt. Both sides costed at the wider of the table and the realized post-reopen spread. Pause length is logged as a covariate, not a signal. Two-sided at development; its direction is then locked | inherits the parent arm | not supported: at the verdict stage, the (a) minus (b) difference excludes the MDE. Below the minimum halted trades it is underpowered. Either way the halt state stays a safety rule only | **gated** (Databento XNAS.ITCH status schema, paid, *contested*: corrected from raw ITCH messages to the normalized schema; Nasdaq Trader history as a short pilot) | +| H3 | Do the overnight and intraday legs differ? | D | (a) intraday only: enter at 09:35, exit at 15:55 the same session; (b) overnight only: enter at 15:55 (300 s timeout), exit at the first eligible quote after the next 09:30 open; (c) the 5-session hold split ex post into 4 overnight legs (official close to official open) and 4 intraday legs (official open to official close), comparing mean per-leg returns (a diagnostic, not traded; a trade missing any leg is excluded). Two-sided at development; its direction is then locked | 1x. Arm (b) stays at 1x in any later paper use until the 2x rung and its overnight cap are clean | not supported: at the verdict stage, the per-leg difference excludes the MDE, and neither traded arm passes | **expected available**; 2016-2020 quote and auction coverage is unverified; H3 is tested only if the coverage rule keeps enough years, decided at the freeze | +| H4 | Does a halt-conditional exit beat ignoring the halt? | No new entry. Applies to every D and I lane-horizon (b_lane) trade, deduplicated by symbol, session and entry time, that meets a halt or, for a Nasdaq-listed name, an LULD pause (not only passing arms, so that a no-pass outcome still leaves a sample) | (a) exit at the reopening of the first halt that starts after the entry fill, at the reopening-cross print (source fixed before the freeze) or the first post-reopen quote; (b) hold to the lane horizon. A halt still in force at the planned exit gives both arms the same post-reopen price (difference 0); one that spans a segment end is excluded; a pause folded into the closing cross exits at the official close. No fills during a halt. Both sides costed at the wider of the table and the realized post-reopen spread. Pause length is logged as a covariate, not a signal. Two-sided at development; its direction is then locked | inherits the parent arm | not supported: at the verdict stage, the (a) minus (b) difference excludes the MDE. Below the minimum halted trades it is underpowered. Either way the halt state stays a safety rule only | **gated** (Databento XNAS.ITCH status schema, paid, *contested*: corrected from raw ITCH messages to the normalized schema; Nasdaq Trader history as a short pilot) | | H5 | Does days-to-cover crowding filter movers? | D split by days-to-cover tercile (short interest / ADV, both in the decision session's share basis; trailing 252-session breakpoints), using the latest FINRA figure published before the decision, with its settlement lag | the lane horizon | 1x; a filter, never a size input | primary (H5-p): high minus low DTC is labelled not supported with a negative effect of the MDE size excluded. Secondary squeeze test (H5-s, no sourced prior): high DTC minus the pooled middle and low terciles, reported separately. Either way at the verdict stage and with no H5 cell passing | **gated** (FINRA short-interest access and terms; history must cover the 2016 warm-up) | -| H6 | Engineering, not alpha, in its own draft and one-test family: do broker-held exits realize exit prices equivalent to client-side exits? | a named mechanics-only strategy at a fixed minimal notional, on symbol-sessions outside that session's D and I populations, assigned to an arm by a hash of symbol, session and entry count written to the ledger before the order is sent. Not yet defined, so H6 cannot accrue | (a) the current `exits.py` chain: client-side triggers, one exit limit, replace disabled (the shipped default); (b) the quantity split into two tranches, each with exactly one broker-held GTC closing order: an OCO (take-profit and stop) on the take-profit tranche and a `trailing_stop` (trail_percent from `effective_trailing_bps` at entry) on the rest. Time, risk-off, force and halt exits stay client-side | the strategy's fixed minimal notional | not supported: the median exit slippage of stop-type fills vs the same trigger reference is not shown equivalent within ±25 bps (TOST, 200 per arm per confirmatory stage); or paper partial-fill or trigger behaviour departs from the docs. REST calls per position are reported, not tested: arm (a) needs about 2, arm (b) at least 3 | **gated**: no position source; needs the strategy, a recorded decision on the no-manufactured-trades rule, the adapter change and a paper smoke test | +| H6 | Engineering, not alpha, in its own draft and one-test family: do broker-held exits realize exit prices equivalent to client-side exits? | a named mechanics-only strategy at a fixed minimal notional, entering after 10:31 ET only in symbols that are flat, have no open order, are not an I event that session and had no D decision in the previous 5 sessions, assigned to an arm by a hash of symbol, session and entry count written to the ledger before the order is sent. Not yet defined, so H6 cannot accrue | (a) the current `exits.py` chain: client-side triggers, one exit limit, replace disabled (the shipped default); (b) the quantity split into two tranches, each with exactly one broker-held GTC closing order: an OCO (take-profit and a stop-market stop) on the take-profit tranche and a `trailing_stop` (trail_percent from `effective_trailing_bps` at entry) on the rest. Time, risk-off, force and halt exits stay client-side | the strategy's fixed minimal notional | not supported: the median exit slippage of positions whose first exit was stop-type, taken intention to treat to their final close, is not shown equivalent within ±25 bps against each arm's preregistered trigger level and against the NBBO bid at trigger (TOST, 200 per arm per confirmatory stage); or paper partial-fill or trigger behaviour departs from the docs. REST calls per position are reported, not tested: arm (a) needs about 2, arm (b) at least 3 | **gated**: no position source; needs the strategy, a recorded decision on the no-manufactured-trades rule, the adapter change, a paper smoke test and a paper pilot projecting at least 1.5x each stage minimum within 126 sessions (else dropped) | **Outcome labels.** Each test ends in one of three preregistered labels: a pass; *not supported*, with the excluded effect size stated; or *underpowered*. A pass is named by its stage: *nominated* at development, *screened* at @@ -84,12 +93,17 @@ literature's roughly 1% a month, so a miss on H1, H2 or H5 will most likely be l only large mover-specific effects. **Data status is fixed at the freeze.** H2, H4 and H5 are each recorded then as tested (data in hand) or not tested -(p = 1 at every stage, permanently). Data bought or obtained after the freeze can only start a new protocol version, +(p = 1 at every stage, permanently). So are H1 and H3: a 2016-2020 year below 90% official-close coverage or 80% +eligible-quote coverage is dropped, and with fewer than 2 development years left, or without 2020, both are not +tested. Data bought or obtained after the freeze can only start a new protocol version, so it cannot change this family's Holm thresholds after results are seen. -**Exposure and chronology.** Development is 2017-2019 and validation 2020. The holdout is 252 full sessions starting -at the 40th session after the freeze commit, provided the validation results are pushed before then (otherwise at the -next 63-session block boundary), so its start is not chosen after the results are seen. The 2017-2020 windows are +**Exposure and chronology.** Development is 2017-2019 and validation 2020. The holdout is 252 sessions (early closes +included) starting at the 40th session after the freeze commit. If the validation results are not pushed before then, +the holdout of this protocol version is void; there is no deferral, so its start cannot be chosen after the results are +seen. Every holdout trade enters the primary statistic. Paper orders in holdout symbol-sessions are logged at order time +and removed only in a sensitivity, and more than 5% exposed trades labels an item's holdout contaminated. An ad hoc read +of holdout prices voids the holdout rather than removing trades. The 2017-2020 windows are disjoint from #162's v1 and v2, but not unseen. The broad-universe study computed forward returns for every eligible symbol-session in 2017-2021 (its C0 control), including a descriptive lane covering names with a close of at least $1, a median dollar volume of at least $2M, 60 prior contiguous bars and no gap. C0 on that lane is the superset exposure @@ -148,8 +162,9 @@ either a trailing stop or an OCO, never both. is terminal. A bracket is used only for a new entry whose two legs close the whole position. The docs define these order classes, but Alpaca's simulated paper fills for them (partials, trigger price, trigger reference) are unmeasured. H6 measures them before anything relies on them. -2. **Client-side rules keep everything that depends on state**, in the existing precedence: force_exit, risk_off, - halt state, quote_stale, price rules, time_exit. Gap risk stays in `native_strategy.evaluate_gap_risk`. A +2. **Client-side rules keep everything that depends on state**, in the existing `exits.py` precedence: force_exit, + risk_off, quote_stale, stop, take-profit, trailing, time_exit. A market-halt rule is a proposed v3 adapter + addition; today's `halted` in `native_strategy.py` is the ledger's risk halt, not a trading halt. Gap risk stays in `native_strategy.evaluate_gap_risk`. A client-side exit first cancels that position's broker-held legs. Cancel-then-new stays the default because of the PATCH-replace fill race already recorded in `blueprints/us-equities/order-contract/README.md`. 3. **The halt state machine runs halted, then reopening auction, then post-auction.** No fills are simulated through @@ -385,9 +400,12 @@ into another: - the direct citation reads above, or removal of the claims that depend on them; - the 2017-2019 cost re-measure fixed by rule only; it runs as the first step after the freeze, because its quote stamps coincide with outcome prices; fee rates dated for 2017-2020; -- metadata-only coverage checks: 2016-2020 quotes and auction prints (H1-H3), H2's identity and filing-index gates, - H4's halt and reopening-print sources, H5's short-interest history including the 2016 warm-up; -- H2, H4 and H5 each recorded as tested or permanently not tested; +- metadata-only coverage checks, run only by committed count-only code that emits counts and rates and seals the raw + pages unopened: 2016-2020 official prints by source label, quotes at non-v3 coverage stamps, split and dividend + records and suspected unadjusted splits (H1-H3); H2's identity and filing-index gates, H4's halt and reopening-print + sources, H5's short-interest history including the 2016 warm-up; +- the v3 session calendar and fee file pinned; +- H1 and H3 (by the coverage rule) and H2, H4 and H5 each recorded as tested or permanently not tested; - the universe and daily-dataset snapshot pinned with its asof date; - study code committed with synthetic tests before any outcome is computed, including split and ex-dividend crossings, early-close sessions and segment-end censoring. @@ -395,6 +413,7 @@ into another: H6 has its own preconditions in `h6-execution-parity-draft.json`, including a named mechanics-only strategy, a test that one share never carries two closing orders and a paper smoke test of the tranche pair. -The second review round's findings and their resolution are recorded in `protocol-draft.json` (`review_record`). +The second and third review rounds' findings and their resolution are recorded in `protocol-draft.json` +(`review_record`). Until all of that is done, this is a plan, and no window it names may be read for outcomes. diff --git a/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json index caf4a50c5..4eb66e3e1 100644 --- a/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json +++ b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json @@ -5,13 +5,13 @@ "status": "draft_pending_independent_pre_outcome_review", "frozen_before_outcomes": false, "drafted_at": "2026-09-24", - "revised_at": "2026-09-24 (round 2: population named as a gated mechanics-only strategy, H6-c restated as descriptive because arm a needs fewer calls by construction, arm a configuration pinned, H6-e unit and trigger strata, GTC and tranche timing, calendar cap, paper smoke test)", + "revised_at": "2026-09-24 (round 2: population named as a gated mechanics-only strategy, H6-c restated as descriptive because arm a needs fewer calls by construction, arm a configuration pinned, H6-e unit and trigger strata, GTC and tranche timing, calendar cap, paper smoke test); round 3 on the same day: trigger references per arm plus a common NBBO-bid basis, stop-market OCO leg, intention-to-treat H6-e unit, flat-symbol entry rule, entry cancel timeout and tranche-reject fallback, a pilot-based power precondition, and a disjoint, sealed population)", "base_commit": "aa6fc791bbd4af3499317673989d2e90a522c257", "parent_plan": "blueprints/us-equities/mover-v3/README.md", "split_from": "blueprints/us-equities/mover-v3/protocol-draft.json (H6), in the 2026-09-24 review fix round", "evidence_class": "Official broker documentation read on 2026-09-24 plus repository source reads. No native execution, no paper order and no outcome. Paper fills are simulated by Alpaca, so any result is sim-to-paper evidence, not market evidence.", "question": "Do broker-held exits (Alpaca trailing_stop and OCO) realize exit prices equivalent to client-side exits, relative to the same trigger reference? REST calls per closed position are reported for both arms as a description, not tested.", - "independence": "Engineering, not alpha. This protocol has its own one-test family and its own paper timeline. It uses no slot of the mover-v3 alpha family (m = 35), H1-H5 never wait for it, and no result here opens or informs the alpha holdout. Its positions are mechanics-only; any overlap with the alpha holdout is logged and excluded under protocol-draft.json chronology.holdout.accrual_exposure.", + "independence": "Engineering, not alpha. This protocol has its own one-test family and its own paper timeline. It uses no slot of the mover-v3 alpha family (m = 35), H1-H5 never wait for it, and no result here opens or informs the alpha holdout. Its positions are mechanics-only and follow population.alpha_disjointness; each H6 paper order is logged at order time in the alpha protocol's accrual log (protocol-draft.json chronology.holdout.accrual_exposure), where it counts toward that protocol's 5% contamination cap and its exposure sensitivity.", "data_status": "gated. H6 has no source of positions today: v3 defines no paper candidate, the v3 edge-shrink term is 0 until a holdout pass (so every alpha-sized position is 0), and this draft creates no order for the test itself. It cannot accrue any closed position until the population below is committed and the preconditions hold.", "evidence": [ {"source": "https://docs.alpaca.markets/docs/orders-at-alpaca", "status": "read on 2026-09-24. trailing_stop (trail_price or trail_percent) and OCO for a held position ('the entry order is already filled, and you can submit the take-profit and stop-loss in one order submission') are documented. Constraints quoted verbatim: 'Trailing stop orders are currently supported only with single orders. However, we plan to support trailing stop as the stop loss leg of bracket/OCO orders in the future.' and 'you would not be able to submit two conditional closing orders since Alpaca's system would view one of the two orders as exceeding the available position quantity'."}, @@ -19,10 +19,14 @@ {"source": "adaptive-paper at aa6fc79: exits.py evaluates stop, take-profit (take_profit_fraction) and trailing rules client-side with no resting broker stop; when a rule fires, native_strategy.py (about lines 755-819) sends one DAY limit at limit_price(bid, ask); replace_exit re-prices only an unfilled resting exit, is off by default (runner.py line 323, exits.replace_enabled False; strategies_v1.py exit_replace_enabled False) and is capped at exit_replace_max_attempts = 3; native_adapter.py builds and accepts only LIMIT/DAY orders (lines 344, 354, 368)", "status": "repository source read in the round 2 fix"} ], "population": { - "rule": "positions opened in Alpaca paper by one named mechanics-only strategy and configuration, at a fixed minimal notional, committed before the freeze. It makes no alpha claim and is exempt from edge shrink for that reason. It trades only symbol-sessions outside that session's mover-v3 D and I populations (decision-time data), so it does not deplete the alpha holdout.", + "rule": "positions opened in Alpaca paper by one named mechanics-only strategy and configuration, at a fixed minimal notional, committed before the freeze. It makes no alpha claim and is exempt from edge shrink for that reason.", + "alpha_disjointness": "An H6 entry in symbol x on session s is allowed only if x had no mover-v3 D decision in sessions s-5 .. s-1, x is not an I event on s, and the entry is at or after 10:31 ET (after the I window closes, so I membership is known). A position that is still open at the close of a session on which x becomes a D event is closed at the next session's first eligible quote by a client-side exit and counted. Residual overlaps (x becoming a D event at s's close) are logged in the alpha accrual log.", + "flat_symbol": "Alpaca holds one net position per symbol, so an entry in a symbol is sent only when the account is flat in that symbol and has no open order in it (for arm b, after both tranche orders are terminal). Skipped entries are counted per arm. A synthetic test covers a second entry while the first position or a tranche order is open.", + "entry_order": "Both arms use the same entry: a DAY limit at limit_price(bid, ask) whose unfilled remainder is cancelled after 30 s (IOC is unverified on the adapter). The position is the filled quantity once the entry is terminal. Arm a's client-side rules are active from the first fill; arm b's tranche orders are sent once the entry is terminal, so arm b has at most 30 s plus the cancel acknowledgement without a price stop, and that interval is reported per position.", + "sealed_ledger": "H6 fills and closed positions go to a sealed ledger (sha256 committed per session batch). No per-position exit price or P&L of H6 positions is displayed or computed during a stage; stage statistics are computed only through h6-access-log.jsonl entries.", "open_decision": "Whether such a strategy is compatible with adaptive-paper README's rule 'No strategy manufactures trades to hit a throughput target' needs an explicit, recorded decision before the freeze. If the decision is no, H6 is dropped.", - "rate": "the expected closed positions per session are estimated before the freeze from retained adaptive-paper trial ledgers (metadata and counts only) and recorded here", - "calendar_cap": "each stage has at most 126 full NYSE sessions; a stage that has not reached its minimum by then is labelled underpowered and the protocol stops" + "rate": "estimated before the freeze by a short paper pilot of the committed mechanics-only strategy and configuration (both arms, labelled as measurement, never counted in any stage). The pilot records the exit-type mix per arm and the H6-e-eligible positions per arm per session. If that rate x 126 is below 1.5 x the largest stage minimum (1.5 x 200 = 300 eligible positions per arm), H6 is dropped. Retained adaptive-paper trial ledgers of other strategies are not an estimate of this rate.", + "calendar_cap": "each stage has at most 126 sessions (protocol-draft.json populations.session_calendar, early closes included); a stage that has not reached its minimum by then is labelled underpowered and the protocol stops" }, "arm_assignment": { "rule": "arm = a if the first byte of sha256('mover-v3-h6|' + symbol + '|' + entry session (YYYY-MM-DD) + '|' + k) is even, else b, where k is the 1-based count of this symbol's entries in this session in decision order", @@ -30,29 +34,36 @@ }, "exit_arms": { "a_client": { - "rule": "the current exits.py chain on the whole position: triggers are evaluated client-side, and a fired rule sends one DAY limit at limit_price(bid, ask)", + "rule": "the current exits.py chain on the whole position: triggers are evaluated client-side on the bid (strategies_v1.py: pnl_bps = bid / average_price - 1, trail_bps = bid / high_bid - 1), and a fired rule sends one DAY limit at limit_price(bid, ask), which for a sell is bid - $0.02", "config": "exits.replace_enabled = false (the shipped default), so an unfilled exit is not re-priced; exit_replace_max_attempts = 3, exit_replace_tolerance_bps = 0 and exit_replace_min_interval_seconds = 0 are recorded but inactive. Any other setting is a new protocol version." }, "b_broker": { "constraint": "each share carries at most one broker-held closing order (the documented rule above): a trailing_stop cannot be an OCO leg, and a second conditional closing order on the same shares is rejected as exceeding the available quantity", - "structure": "the filled quantity q is split into two tranches. Tranche P (take-profit tranche) = floor(q x f_p) shares, carrying one OCO (take-profit limit at take_profit_bps and stop at stop_bps). Tranche T (trailing tranche) = q - P shares, carrying one trailing_stop with trail_percent = effective_trailing_bps at entry / 100, which is also that tranche's only stop. f_p = take_profit_fraction when it is below 1; when take_profit_fraction = 1 (the strategies_v1 default), f_p = 0.5, fixed here. A tranche of 0 shares is not sent, and the whole position carries the other tranche's single order.", + "structure": "the filled quantity q is split into two tranches. Tranche P (take-profit tranche) = floor(q x f_p) shares, carrying one OCO (take-profit limit at take_profit_bps and a stop-market stop leg, stop_price only and no limit_price, at stop_bps). Tranche T (trailing tranche) = q - P shares, carrying one trailing_stop with trail_percent = effective_trailing_bps at entry / 100, which is also that tranche's only stop. f_p = take_profit_fraction when it is below 1; when take_profit_fraction = 1 (the strategies_v1 default), f_p = 0.5, fixed here. A tranche of 0 shares is not sent, and the whole position carries the other tranche's single order.", "timing": "tranche orders are submitted only once the entry order is terminal (filled, cancelled or expired), sized from the final filled quantity; no top-up for later fills", "time_in_force": "GTC for both tranche orders, so multi-session holds need no daily resubmission. If the paper smoke test shows GTC rejected for an order class, that class uses DAY plus one resubmission per tranche at each session's first eligible quote, and those calls are counted.", + "tranche_reject": "If a tranche order is rejected (for example because the price has already crossed its stop level, or as exceeding the available quantity), that tranche falls back to arm a's client-side rule chain for its shares. The position stays in arm b for H6-e (intention to treat), and fallbacks are counted and reported per arm and reason.", "client_side": "time, risk-off, force, quote-stale and halt exits stay client-side. A client-side exit first cancels both tranche orders (one call each), waits for both cancels to be terminal, then sends one closing order for the remaining quantity.", "difference_from_a": "arm b is not a byte-identical copy of arm a's rule chain: tranche T has no separate fixed stop and tranche P no trailing rule, and arm a sells the whole position on take-profit (take_profit_fraction = 1) while arm b sells tranche P. H6-e therefore compares execution quality against each fill's own trigger reference, not P&L, and only for stop-type triggers." } }, + "trigger_reference": { + "arm_a": "stop_loss: average_price x (1 - effective stop_bps / 1e4); trailing: high_bid x (1 - effective trailing_bps / 1e4), with high_bid as of the trigger tick. Both are the rule's level, not the bid that crossed it.", + "arm_b": "the order's stop_price at trigger as read from the order or its trade_updates event (for trailing_stop, the stop_price Alpaca reports, with its hwm logged).", + "common_basis": "for both arms, also the NBBO bid at each arm's trigger timestamp (arm a: the triggering tick; arm b: the trade_updates timestamp of the stop leg's trigger or fill) from the same quote stream. H6-e is computed on the per-arm reference and on this common basis; both are reported, and a pass requires both.", + "trigger_basis_check": "The round 3 review found that the cited orders-at-alpaca page does not state which price series triggers a stop or trailing_stop (last trade, bid or NBBO); this round did not reopen the page. The paper smoke test records the trigger basis; if it cannot be determined, H6-e uses the common basis only." + }, "shadow": "for each position, the other arm's trigger time and reference price are computed from the same quote stream for paired diagnostics, never traded", "call_count_description": { - "arm_a": "2 calls per closed position at a full fill (entry plus one exit limit). With replace disabled no further calls are made; an unfilled exit expires at the close and is handled by the existing recovery path, whose calls are counted.", + "arm_a": "2 calls per closed position at a full fill (entry plus one exit limit), plus the entry cancel when the entry is not filled in 30 s. With replace disabled no further calls are made; an unfilled exit expires at the close and is handled by the existing recovery path, whose calls are counted.", "arm_b": "3 calls when broker legs close the position (entry, OCO, trailing_stop; 2 when a tranche is empty), 6 when closed client-side (plus 2 cancels and 1 closing order), plus any DAY resubmissions", "consequence": "arm b needs more calls than arm a by construction (3 + 3s against about 2), so the previous test 'arm b uses at least 2x fewer calls' could not pass and is withdrawn. The call counts per closed position, the realized client-closed share s and any resubmissions are reported per arm as a description only." }, "primary_tests": [ - {"id": "H6-e", "unit": "one closed position with at least one stop-type exit fill (arm a: stop_loss or trailing rule; arm b: the OCO stop leg or the trailing_stop). The position's value is the quantity-weighted mean over its stop-type exit fills of (fill price - trigger reference) / trigger reference, in bps. Positions closed only by take-profit or client-side rules are excluded from H6-e, counted and reported by trigger type.", "statistic": "median of the position values, arm b minus arm a", "alternative": "equivalence within +/-25 bps (TOST)", "p_value": "p = max(p_lower, p_upper), the larger of the two one-sided bootstrap p-values"} + {"id": "H6-e", "unit": "intention to treat: one closed position whose first fired exit is stop-type (arm a: the stop_loss or trailing rule fires first; arm b: the OCO stop leg or the trailing_stop triggers first). Its value is the quantity-weighted mean, over every fill that closes the shares covered by that trigger (arm a: the whole position; arm b: the triggering tranche, the other tranche being excluded from the value), of (fill price - trigger_reference) / trigger_reference in bps, whichever order closes them: the exit limit, the recovery path, a time exit or a next-session close. Positions whose first fired exit is a take-profit or a client-side rule are excluded from H6-e, counted and reported by trigger type. Arm a's stop exit is a DAY limit at bid - $0.02 that can rest unfilled; intention to treat keeps those positions at their final realized close, so the arm is not censored to filled limits.", "statistic": "median of the position values, arm b minus arm a", "alternative": "equivalence within +/-25 bps (TOST)", "p_value": "p = max(p_lower, p_upper), the larger of the two one-sided bootstrap p-values"} ], - "descriptive": ["H6-c: log(mean REST calls per closed position, arm a / arm b), with a bootstrap interval; no pass rule", "take-profit fills: the same statistic, reported separately", "paired shadow differences per trigger type"], - "also_not_supported_if": "any paper partial-fill, trigger-price or trigger-reference behaviour that departs from the documented semantics, or any rejection of a tranche order as exceeding the available quantity (recorded per event)", + "descriptive": ["H6-c: log(mean REST calls per closed position, arm a / arm b), with a bootstrap interval; no pass rule", "stop-exit fill rate and time from trigger to final close, per arm", "H6-e restricted to positions whose first stop-type exit order filled (the censored comparison), as a sensitivity", "take-profit fills: the same statistic, reported separately", "paired shadow differences per trigger type"], + "also_not_supported_if": "any paper partial-fill, trigger-price or trigger-reference behaviour that departs from the documented semantics, a trigger basis that the smoke test cannot determine and that differs between arms on the common basis, or any rejection of a tranche order as exceeding the available quantity (recorded per event)", "multiple_testing": { "family": "H6-e only, m = 1", "procedure": "TOST (two one-sided tests, each at alpha 0.05) at validation and at the holdout; no multiplicity adjustment with m = 1", @@ -79,9 +90,10 @@ "freeze_preconditions": [ "An independent pre-outcome review from a different session and model family, findings resolved in a dated record", "The mechanics-only strategy, its configuration and notional committed, and the no-manufactured-trades decision recorded (population.open_decision)", - "The positions-per-session estimate recorded from retained paper ledgers (population.rate)", + "The paper pilot run and its exit-type mix and eligible-position rate recorded, with H6 dropped if the rate misses population.rate's bound", "Adapter paths for OCO and trailing_stop committed with synthetic tests, including a test that one share never carries two closing orders and a test that a client-side exit waits for cancel acknowledgements before its closing order", - "A paper mechanics smoke test of the tranche pair (one OCO plus one trailing_stop on split tranches of one position, GTC), labelled as measurement, with its result recorded", + "A paper mechanics smoke test of the tranche pair (one OCO with a stop-market leg plus one trailing_stop on split tranches of one position, GTC), labelled as measurement, with its result recorded, including the trigger price series Alpaca uses (trigger_reference.trigger_basis_check) and a tranche rejection after the stop level is crossed", + "Synthetic tests for the flat-symbol rule, the 30 s entry cancel before tranche submission, the tranche-reject fallback and alpha_disjointness", "The arm-assignment ledger write tested to precede the entry submit", "status set to frozen and the sha256 recorded in a pushed commit" ] diff --git a/blueprints/us-equities/mover-v3/protocol-draft.json b/blueprints/us-equities/mover-v3/protocol-draft.json index 69a324578..bf18b18ff 100644 --- a/blueprints/us-equities/mover-v3/protocol-draft.json +++ b/blueprints/us-equities/mover-v3/protocol-draft.json @@ -5,7 +5,7 @@ "status": "draft_pending_independent_pre_outcome_review", "frozen_before_outcomes": false, "drafted_at": "2026-09-24", - "revised_at": "2026-09-24 (review fix round: exposure registry, H6 moved to its own engineering draft, trailing breakpoints, three-outcome reporting, leg-matched H3-c, halt-free H1-H3, H4 parent set, cost lookup pins, repin to merged #162); round 2 on the same day (review_record): directions locked after development for two-sided items, data status of H2, H4 and H5 fixed at the freeze, freeze before any quote is sampled, defined MDE and label bound, holdout breakpoint, start and count-read rules, segment-end and early-close rules, mid-quote fills, split and dividend accounting, H2 moved to gated, lineage count 1,107", + "revised_at": "2026-09-24 (review fix round: exposure registry, H6 moved to its own engineering draft, trailing breakpoints, three-outcome reporting, leg-matched H3-c, halt-free H1-H3, H4 parent set, cost lookup pins, repin to merged #162); round 2 on the same day (review_record): directions locked after development for two-sided items, data status of H2, H4 and H5 fixed at the freeze, freeze before any quote is sampled, defined MDE and label bound, holdout breakpoint, start and count-read rules, segment-end and early-close rules, mid-quote fills, split and dividend accounting, H2 moved to gated, lineage count 1,107; round 3 on the same day (review_record): every holdout trade in the primary statistic with a contamination cap, a count-only pre-freeze access path, accepted official-print labels, a v3-owned XNYS calendar and fees file, a fixed N0 with no push discretion, H4 halt edge cases, preregistered item ids and stream spawning, extension scope, quote eligibility and fetch failures, H5 DTC scope, the lineage label rule, unadjusted-split exclusion, coverage consequences for H1 and H3, and restated membership formulas", "base_commit": "aa6fc791bbd4af3499317673989d2e90a522c257", "plan": "blueprints/us-equities/mover-v3/README.md", "catalog_decisions": "catalogs/us-equities/mover-v3-sweep-20260924.json", @@ -17,7 +17,7 @@ "publication": "private scratch input, not committed: its verifier notes contain host paths. The README and the catalog supplement carry its content." }, "relation_to_prior_protocols": { - "mover-early-entry-v1-20260924": "PR #162, merged to main on 2026-09-24 as squash commit aa6fc79. 0 of 768 rule-exits pass development; the holdout was not read. Reused by reference only, pinned to main at aa6fc79 (paths under blueprints/us-equities/mover-early-entry/): rules.py (git blob 9be164c9e1a49a451d3a2381205349ed5003a993, sha256 d66156b3e57c6299f10e41a3f25c921c8be52571bd84417040190d926bf49450; definitions ref, price_at_t, dollar_volume_at_t, gain_at_t, official prices, split factor), quotes.py (git blob caebf6f6a72fee6bb7ae637ebb698f8c5f6b3850, sha256 4116bf2809e0952f842f0e0fe35fd8b5ab11ea295637734ffe0d719ca72ee85e; the cost-sample method including the D7 stamp-window guard: a completion is reused only for the same timestamp, and the table refuses quotes outside [ts - 60 s, ts]) evidence/cost-table-run-v1.json (hashes under cost_model.base_table) and evaluate_v2.py (git blob 2248580f4c673da3fcde25d85c6d20799ffa994e, sha256 d2007077ac7367e16061e49a645a54802fbb58c41d79d3b2da6417b0275b2fe4; only deviation D7's split and ex-dividend accounting, restated for v3 in populations.corporate_actions). The draft's earlier pin 92062e8 is reachable only from the PR branch, and its quotes.py lacks the D7 guard. v3 never modifies blueprints/us-equities/mover-early-entry/**.", + "mover-early-entry-v1-20260924": "PR #162, merged to main on 2026-09-24 as squash commit aa6fc79. 0 of 768 rule-exits pass development; the holdout was not read. Reused by reference only, pinned to main at aa6fc79 (paths under blueprints/us-equities/mover-early-entry/): rules.py (git blob 9be164c9e1a49a451d3a2381205349ed5003a993, sha256 d66156b3e57c6299f10e41a3f25c921c8be52571bd84417040190d926bf49450; it defines Bars.state, Bars.cum_dv, signal_cutoff and split_factor only: ref and gain are computed in features.py and mover_scan.py, which also fall back to a daily bar close that v3 forbids, so populations.formulas restates every membership formula exactly and does not rely on those files), sessions_io.py (git blob 0f7791a87685c73635efe94c226031d9afe7bd7c, sha256 2a753cb8f9164f48c1dceb952eb42eeae17811f636fddf5fca082731c708e59a; official_price only, with the accepted labels in populations.official_prints), quotes.py (git blob caebf6f6a72fee6bb7ae637ebb698f8c5f6b3850, sha256 4116bf2809e0952f842f0e0fe35fd8b5ab11ea295637734ffe0d719ca72ee85e; the cost-sample method including the D7 stamp-window guard: a completion is reused only for the same timestamp, and the table refuses quotes outside [ts - 60 s, ts]) evidence/cost-table-run-v1.json (hashes under cost_model.base_table) and evaluate_v2.py (git blob 2248580f4c673da3fcde25d85c6d20799ffa994e, sha256 d2007077ac7367e16061e49a645a54802fbb58c41d79d3b2da6417b0275b2fe4; only deviation D7's split and ex-dividend accounting, restated for v3 in populations.corporate_actions). The draft's earlier pin 92062e8 is reachable only from the PR branch, and its quotes.py lacks the D7 guard. v3 never modifies blueprints/us-equities/mover-early-entry/**. The pinned code is valid for 2021-2026 only: rules.EARLY_CLOSES lists 2021-2026 early closes, so rules.close_hhmm returns 16:00 on every 2016-2020 and 2027 early close, and rules.FEES (fees.json) has no SEC Section 31 row before 2020-01-01 (its 2020 start is lookup convenience, not a sourced rate), so rules.sell_fees raises KeyError on 2017-2019 dates. v3 study code therefore never calls rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return or rules.net_return_np, and never uses quotes.py cmd_sample stamping; it uses session_calendar and cost_model.fees instead.", "mover-followup-v2-20260924": "PR #162 at aa6fc79: no pass in families E (216), F (16) or P (12); no holdout read. After deviation D7 (P exits across splits and ex-dividend dates), the v2 results sha256 is 75a5004b... (README and deviations.json at aa6fc79). Not reused.", "catalyst-experiment-v1-20260920": "Its lane timing (16:15 decision, 09:35 entry, +60 s minimum latency, 5-session and 60-minute horizons) is reused by reference, as is its rule 'Do not infer a halt from missing data'. Its catalyst-provenance/catalyst.py is not a usable H2 source as it stands: it parses only form type and acceptance headers (no Item parser, no ticker-to-CIK map), and its available_at = max(acceptance, local first observation) makes every 2016-2020 filing ineligible at its historical decision (catalyst-experiment README, 'Unknown original availability ... fails historical eligibility'). H2 is therefore gated (hypotheses.H2). Its 2021Q1 ETF control is permanently inspected and is never a holdout.", "broad-universe-mover-v1-20260921": "Main universe close >= $5 and median dollar volume >= $20M, plus a descriptive lane on the unrestricted $1 / $2M universe (protocol.json descriptive_microcap_lane; evaluation-results.json calls it a strict superset of the main universe). Both lanes computed outcomes over development 2017-2021, validation 2022-2023 and reserved 2024-01-01 to 2026-08-14. Most D-population symbol-sessions with median dollar volume >= $2M lie inside that lane, so 2017-2020 is exposed at aggregate level (exposure_registry)." @@ -37,22 +37,42 @@ ], "draft_author_exposure": "Before this draft the author read: #162's README results and v1 dev/val summary aggregates (per-rule means; P5/P95 of the 07:00|G0.20|V1000000 rule-exits, used only for sigma planning below); the v1 cost-table cells; the #162 review findings; the broad-universe README headlines, including the development | validation | reserved table of date-clustered mean net excess over C0 for S1-S5 at H1/H5/H20 (for example S2 H1 development -0.33%, 2017-2021, main universe) and its reserved-segment precision and recall for +10% and +20% next-day movers; the sweep. The review fix round (same day) reread broad-universe protocol.json (lane definitions), evaluation-results.json's descriptive_microcap_scope note and the same README table, and opened no descriptive-lane outcome row; the 4,563,385 event count above is a count quoted from the review finding, not a return. The second fix round (same day) read the rereview findings, broad-universe protocol.json (eligibility, window_contiguity, trial_policy, promotion), catalyst-provenance/catalyst.py, the catalyst-experiment README availability section, mover-early-entry evaluate_v2.py and deviations.json D7 (method text only), the rules.py function list and the adaptive-paper order and exit code paths (native_strategy.py, runner.py, strategies_v1.py, native_adapter.py, exits.py); it opened no outcome row, return or results file. No v3-defined statistic (a D or I outcome, a MAX, halt or short-interest tercile) has been computed or seen for any window.", "consequence": "2017-2020 is exposed at aggregate level through broad-universe development (both lanes) and the S2 headline above. v3 therefore treats development (2017-2019) and validation (2020) as screening stages only. Validation still decides which items the holdout gate opens for, but a validation pass is labelled 'screened', never confirmatory. Only the prospective holdout can support a confirmatory claim. A validation-window statistic restricted to symbol-sessions that were not a broad-universe lane decision (med20 from raw close x volume < $2M, fewer than 60 prior contiguous bars, or has_gap, all as broad-universe protocol.json defines them) is reported beside each validation result as the least-exposed slice; it is a sensitivity, not a separate test.", - "update_rule": "Every later read of data in any v3 window, before the freeze, is appended here with its date and purpose. No quote, bar or official price at a v3 entry or exit stamp is fetched before the freeze: the 2017-2019 cost sample is the first step of the frozen evaluation (cost_model.out_of_period_spreads). A pre-freeze read of development-window prices at v3 stamps is logged here and named in the freeze commit. A read of v3-defined outcomes in the validation window before the freeze voids validation; a read in the holdout window before the gate opens voids the holdout." + "update_rule": "Every later read of data in any v3 window, before the freeze, is appended here with its date and purpose. No quote, bar or official price at a v3 entry or exit stamp is fetched before the freeze: the 2017-2019 cost sample is the first step of the frozen evaluation (cost_model.out_of_period_spreads). A pre-freeze read of development-window prices at v3 stamps is logged here and named in the freeze commit. A read of v3-defined outcomes in the validation window before the freeze voids validation; a read in the holdout window before the gate opens voids the holdout. The only permitted pre-freeze access is pre_freeze_access_path; data fetched and read only through it are not a read in this sense and do not void validation.", + "pre_freeze_access_path": { + "rule": "Before the freeze, 2016-2020 data (and the 2024-11-01 to 2025-12-31 covariate sessions of tercile_rule.holdout_breakpoints) are fetched only by committed count-only code. Raw pages go straight into a private snapshot sealed by sha256 and are never opened by a person or by any other code. The count-only code emits only counts and rates: no price, spread, return, gain, covariate value, event list or symbol-level row. Its committed output is the input to the freeze preconditions (coverage checks, corporate-action checks and the snapshot pin). The same sealed snapshot is the frozen evaluation's input.", + "outputs": "per calendar year and listing exchange: symbol-sessions with a daily bar, with a raw close >= $1, with each sessions_io.official_price source label for the open and the close (populations.official_prints), with split, reverse-split and cash-dividend records, and with a suspected unadjusted split (populations.suspected_unadjusted_split); coverage-stamp quote counts (quote_coverage_stamps)", + "quote_coverage_stamps": "Eligible-quote rates are measured only at stamps that are never v3 entry or exit stamps: 12:17 ET and 14:43 ET (12:17 and 12:43 ET on a 13:00 early close) on symbol-sessions selected by int.from_bytes(sha256('mover-v3-coverage|' + symbol + '|' + session)[:8], 'big') % 500 == 0 among all symbol-sessions with a daily bar and raw close >= $1, with no reference to event membership. The code reports, per year, the stamps with an eligible quote (populations.halts), without one, and fetch-incomplete (populations.fetch_failures), and nothing else.", + "not_allowed": "no pre-freeze fetch or read at a D or I entry stamp, an exit stamp, or a cost-sample stamp; no computation of D or I membership, MAX21, DTC or any outcome" + } }, "populations": { - "D_daily": "symbol-sessions t with split-consistent official-close gain close[t]/ref - 1 >= 0.20 (close[t] is the primary listing's official closing print; a session without one is not an event and is counted, with no bar fallback; ref as rules.py defines it), official close >= $1.00 and completed regular-session dollar volume >= $1,000,000; decision at 16:15 ET on t; entry at the first eligible quote at or after 09:35 ET on t+1 and at or after decision + 60 s; entry timeout 300 s, else no entry (counted)", - "I_intraday": "the first minute m in 09:35-10:30 ET on a session at which gain_at_t >= 0.20, dollar_volume_at_t >= $1,000,000 and price_at_t >= $1.00 (rules.py definitions at aa6fc79); entry at the first eligible quote at or after m + 60 s; timeout 60 s", - "session_times": "Every '15:55 ET' in this protocol means the scheduled close minus 5 minutes (12:55 ET on a 13:00 early-close session). Eligible quotes are regular-session quotes, at or after 09:30 ET and before the scheduled close. An exit that finds no eligible quote before the scheduled close takes the first eligible quote of the next session. D decisions stay at 16:15 ET on early-close sessions.", + "D_daily": "symbol-sessions t with split-consistent official-close gain close[t]/ref(t) - 1 >= 0.20 (close[t] is the accepted official close of populations.official_prints; a session without one is not an event and is counted, with no bar fallback; ref(t) as populations.formulas states), official close >= $1.00 and completed regular-session dollar volume >= $1,000,000; decision at 16:15 ET on t; entry at the first eligible quote at or after 09:35 ET on t+1 and at or after decision + 60 s; entry timeout 300 s, else no entry (counted)", + "I_intraday": "the first minute m in 09:35-10:30 ET on a session at which gain_at_m >= 0.20, dollar_volume_at_m >= $1,000,000 and price_at_m >= $1.00 (populations.formulas); entry at the first eligible quote at or after m + 60 s; timeout 60 s", + "formulas": "Restated exactly, so membership does not depend on unpinned code. f(t) = rules.split_factor(prev_raw_c, prev_all_c, raw_c, all_c) at aa6fc79 = (raw close / all-adjusted close on the previous XNYS session) / (the same on t), set to 1 when |f - 1| <= 1e-2 or any input is missing (daily bars, raw and adjustment=all). ref(t) = the accepted official close of the previous XNYS session / f(t); if that session has no accepted official close, ref(t) is undefined and t is neither a D nor an I event (counted). There is no daily-bar or last-bar fallback (features.py and mover_scan.py at aa6fc79 fall back to the daily bar close; v3 does not). For minute m on session t: (price_at_m, dollar_volume_at_m) = rules.Bars.state(epoch of m ET) at aa6fc79, the close of the last 1-minute SIP bar starting before m and the summed vwap x volume of the session's bars from 04:00 ET starting before m; gain_at_m = price_at_m / ref(t) - 1, compared with 0.20 - 1e-9. A synthetic test fixes each formula, including a split day and a missing previous close.", + "official_prints": "Accepted source labels of sessions_io.official_price at aa6fc79 (listing exchange = the exchange of the largest condition-O opening print; no O print means no listing exchange for that session). Official open: opening_print_listing_exchange only. Official close: closing_print_listing_exchange or official_close_listing_exchange only. closing_print_other_exchange, and a session with no O print, count as no official close and are counted per year. The same rule applies to close[t], to ref(t)'s previous close, to every H3-c leg and to H4 closing-cross folds. The synthetic suite has one case per source label, including the rejected label and a session with a closing print but no opening print.", + "suspected_unadjusted_split": "Applied before membership. For session t, r = raw daily close[t] / raw daily close[previous XNYS session]. If f(t) = 1 (no adjustment) and |r - n| <= 0.01 x n or |r - 1/n| <= 0.01 / n for some integer n in 2..100, t is a suspected unadjusted split: it is not a D or I event, any trade whose hold spans t (entry session through exit session) is excluded, and any event whose MAX21 window contains t is unassigned for H1 (tercile_rule.minimum treatment). All three are counted per stage. This also removes genuine moves within 1% of an integer multiple (for example +100%); that loss is preregistered and counted, not corrected.", + "fetch_failures": "Every quote request is retried up to 3 times with exponential backoff (1 s, 4 s, 16 s). A stamp whose pages are still incomplete (quotes.py stamp_incomplete, or any HTTP error) is fetch-incomplete: the trade that needs it is excluded, not counted as a no-entry or a delayed exit, and the count is reported per item. If more than 1% of the stamps a stage requires are fetch-incomplete, that stage is void: every item scores p = 1 and is labelled underpowered there, with the reason stated.", + "session_calendar": "XNYS from exchange_calendars 4.13.2 (the version rules.py C26 used for 2021-2026), for 2016-01-01 through the end of the last possible holdout extension. The session list, with each session's scheduled open and close, is written to a v3-owned file (blueprints/us-equities/mover-v3/session-calendar.json) and pinned by sha256 at the freeze. A 'full NYSE session' or 'session' anywhere in this protocol is every scheduled XNYS trading session, early closes included; no session count excludes early closes. Synthetic tests use the 2017-07-03, 2017-11-24, 2018-11-23, 2019-12-24 and 2020-11-27 early closes.", + "session_times": "Every '15:55 ET' in this protocol means the scheduled close (session_calendar) minus 5 minutes (12:55 ET on a 13:00 early-close session). Eligible quotes are regular-session quotes, at or after 09:30 ET and before the scheduled close. An exit that finds no eligible quote before the scheduled close takes the first eligible quote of the next session. D decisions stay at 16:15 ET on early-close sessions.", + "fill_quote": "For a fill at or after time X, the quote is the NBBO prevailing at X (the latest quote update stamped at or before X) if it is eligible and X minus its timestamp is at most 1000 ms; otherwise the first eligible quote update stamped after X, with fill time equal to its timestamp. Quote age is the fill-decision time minus the quote timestamp. Eligible: bid > 0 and ask > bid (locked and crossed quotes excluded), regular session (session_times). The same eligibility applies to fills and to cost-table cells (quotes.py valid_quote at aa6fc79 excludes locked and crossed quotes). Timeouts run from X. Synthetic tests cover a fresh prevailing quote, a stale one, a locked one and no quote inside the timeout.", "fill_price": "Every entry and exit fills at the mid (bid + ask) / 2 of the eligible quote that fills it, so gross returns are mid-to-mid and cost_model adds the per-side cost on top. The realized half-spread of each fill quote is recorded and reported as a sensitivity (cost = 1.25 x max(table hs', realized half-spread)).", "corporate_actions": "For every multi-session arm (D b_lane, H3 b_overnight, the H4 parents) and each H3-c leg, deviation D7's method is restated: the exit uses the raw exit mid (or raw official print for H3-c); shares change by the product of rules.py split factors between the entry session and the exit session (a factor counts when it differs from 1 by more than 1e-2); the rest of the adjusted close-to-close return over the hold is booked as cash per original share, without spread or fees. Fees use the actual exit shares and raw exit price. H3-c legs use log((raw end print x split factor + cash) / raw start print). H5's short interest and 20-session volume are both expressed in session t's share basis with the same split factors. Study code must pass a synthetic test that crosses a split and an ex-dividend date before the freeze.", "exclusions": "OTC; warrants, units and rights by #162's regex; symbols with fewer than 21 prior sessions of bars. No halt exclusion: H1-H3 use no halt data (see halts).", - "halts": "H1-H3 use no halt source, historically or prospectively, so their trade sets cannot depend on a later data purchase. As in catalyst-experiment, a halt is never inferred from missing data: an entry or exit fills only at an eligible quote (bid > 0, ask >= bid, quote age <= 1000 ms), an entry without one inside its timeout is a counted no-entry, and an exit without one waits for the next eligible quote. Limitation: a trade that sits through an unobserved halt is not flagged, and a fill at the first post-halt quote is not distinguished from a normal fill. Halt state enters only H4, as an overlay on already-defined trades.", + "halts": "H1-H3 use no halt source, historically or prospectively, so their trade sets cannot depend on a later data purchase. As in catalyst-experiment, a halt is never inferred from missing data: an entry or exit fills only at an eligible quote (populations.fill_quote), an entry without one inside its timeout is a counted no-entry, and an exit without one waits for the next eligible quote. Limitation: a trade that sits through an unobserved halt is not flagged, and a fill at the first post-halt quote is not distinguished from a normal fill. Halt state enters only H4, as an overlay on already-defined trades.", "parameter_provenance": "G = 0.20 and V = $1M are v1's loosest gain and middle volume floor, fixed for sample size after v1's all-negative dev/val grid was seen (disclosed exposure). They are not tuned on any v3 window." }, + "coverage_rule": { + "scope": "H1 and H3, which rest on 2016-2020 SIP quotes and official auction prints. Decided at the freeze from the count-only outputs (exposure_registry.pre_freeze_access_path), before any outcome, and recorded beside multiple_testing.data_status_at_freeze.", + "no_fallback": "A session or symbol without an accepted official print or an eligible quote stays a counted non-event, no-entry or excluded trade under its own rule; no bar, quote or other-exchange substitute is ever used.", + "year_rule": "A calendar year of 2016-2020 whose accepted official-close rate (symbol-sessions with an accepted official close / symbol-sessions with a daily bar and raw close >= $1) is below 90%, or whose eligible-quote rate at the coverage stamps is below 80%, is dropped from its stage entirely (every session of that year; for 2016, the warm-up, events of early 2017 then stay unassigned under tercile_rule.minimum until 60 prior events accrue). The thresholds are design choices, not measured values.", + "item_rule": "If fewer than 2 development years remain, or 2020 is dropped, H1 and H3 are not tested and every H1 and H3 item scores p = 1 at every stage, permanently. The same year rule applies to D and I alike.", + "limitation": "The coverage stamps sample the whole $1+ universe, not movers; mover quote coverage may be lower. Per-stage no-entry and excluded counts are reported so that this is visible." + }, "tercile_rule": { "breakpoints": "For a covariate X (MAX21 in H1, DTC in H5) and a population P, the breakpoints for an event decided in session s are the 1/3 and 2/3 empirical quantiles (numpy 'linear' method) of X over all P events whose decision session lies in the 252 sessions strictly before s. Every P event counts, whether it entered, had no entry or had no fill. Every input is known at the decision, including for I, whose later same-morning triggers are never used.", - "holdout_breakpoints": "For a holdout event the trailing window is the 252 most recent sessions before s that lie outside 2026-01-02 to 2026-09-18 (#162's reserved holdout, which v3 never reads). It reads only covariates and event membership, never an outcome: in 2025 (v1/v2 validation, already exposed and recorded in the exposure registry) and in earlier holdout sessions (decision-time inputs of the sealed snapshot, read only after the gate opens).", + "holdout_breakpoints": "For a holdout event the trailing window is the 252 most recent sessions before s that lie outside 2026-01-02 to session N0 - 1. That gap covers #162's reserved holdout (2026-01-02 to 2026-09-18, which v3 never reads) and every session from 2026-09-21 to N0 - 1, which neither snapshot holds. The window therefore draws only on (a) the 252 sessions ending 2025-12-31 (v1/v2 validation, already exposed), whose covariates and event membership come from the pre-freeze sealed snapshot (pre_freeze_access_path; daily bars, auctions and corporate actions from 2024-11-01, for the MAX21 and ref lookback, to 2025-12-31, asof the freeze date) and are computed only after the gate opens; and (b) earlier holdout sessions, from the sealed holdout snapshot's decision-time inputs, read only after the gate opens. It reads only covariates and event membership, never an outcome.", "assignment": "low if X <= the 1/3 breakpoint, high if X > the 2/3 breakpoint, middle otherwise (a value equal to a breakpoint goes to the lower tercile)", + "h5_event_set": "For H5, P is the set of D events with a defined DTC (a FINRA figure published before the decision). D events without DTC are counted and reported per stage but are not H5 events: they are outside the H5 breakpoint set, the 60-event minimum and every H5 cell.", "minimum": "if fewer than 60 prior P events lie in the trailing window, the event is unassigned: it is counted and reported, and it enters no tercile cell or difference test. The 2016 warm-up year supplies the first window.", "small_dates": "Terciles never depend on the number of movers on the event's own date, so a date with fewer than 3 movers needs no special rule." }, @@ -62,15 +82,15 @@ "validation": ["2020-01-02", "2020-12-31"], "holdout": { "kind": "prospective", - "window": "252 full NYSE sessions starting at session N0 = the 40th full NYSE session after the session of the freeze commit. The validation results file's sha256 must be committed and pushed before N0. If it is not, N0 moves to the first block boundary 40 + 63k (k = 1, 2, ...) after the push, and the delay is logged. The start is therefore fixed by the freeze date and the push, not chosen after the validation results or the live market are seen.", - "extension": "if any carried item has fewer than its minimum holdout trades after 252 sessions, extend in 63-session blocks, at most 2. Each extension decision uses one 'count' read of the sealed snapshot (holdout_gate.access_log): entries and tercile, group or halted-parent assignments per carried item, never an exit or outcome price", + "window": "252 sessions (session_calendar) starting at session N0 = the 40th session after the session of the freeze commit. N0 is fixed by the freeze commit alone. Development and validation run by committed automation started at the freeze, and the validation results file's sha256 must be committed and pushed before N0. If it is not, the holdout of this protocol version is void for every item, whatever the reason; a new protocol version is required. There is no deferral, so the start cannot be chosen after the validation results or the live market are seen.", + "extension": "if any carried item has fewer than its minimum holdout trades after 252 sessions, extend in 63-session blocks, at most 2. The holdout window is common to every carried item: once extended, every carried item is evaluated over the extended window. A carried item still below its minimum after the second block is labelled underpowered. Each extension decision uses one 'count' read of the sealed snapshot (holdout_gate.access_log): entries and tercile, group or halted-parent assignments per carried item, never an exit or outcome price", "why_prospective": "No retrospective window free of prior mover outcomes remains: 2021-2025 is v1/v2 dev/val, 2026-01-02 to 2026-09-18 is #162's reserved holdout and is event-level exposed, 2017-2026-08-14 is broad-universe exposed at aggregate level, and 2016 is warm-up.", - "accrual_exposure": "Any paper operation that trades during the holdout window logs, per collection batch, which symbol-sessions it traded and whether their P&L was seen. An exposed D trade is keyed on every session in [decision t, planned exit]; an exposed I trade on its event session. Those trades are excluded from the holdout sample for every item, and the excluded count and fraction are reported per carried item, tercile and group. The engineering protocol's mechanics-only positions trade only symbol-sessions outside that session's D and I populations, so they should exclude nothing; any overlap is still excluded and reported. Any other computation on holdout-window mover prices (dashboards, ad hoc reads) is logged as exposure in the same way." + "accrual_exposure": "The primary holdout statistic of every item uses every holdout trade; nothing is excluded for exposure. Paper exposure is recorded only for symbol-sessions in which a paper order was submitted, logged at order time (symbol, session, order id, strategy) in the collection batch's accrual log before any outcome of that trade exists. A holdout D trade is paper-exposed if any session in [decision t, planned exit] is such a symbol-session; an I trade if its event session is. The result without paper-exposed trades is reported only as a sensitivity. If the paper-exposed fraction of any carried item's holdout trades exceeds 5%, the holdout is labelled contaminated for that item, which can then not be labelled supported (confirmatory). An ad hoc read, dashboard or other computation on holdout-window mover prices outside holdout_gate never excludes a trade: it is a holdout read without a granted entry and voids the holdout for every item (holdout_gate.access_log.rule). The engineering protocol's positions follow its population rule and are logged the same way." }, "labels": { "development": "disjoint from v1/v2; exposed at aggregate level through broad-universe development (both lanes, 2017-2021); screening only; survivorship-limited before 2020 (names delisted in 2016-2019 without a retained corporate action cannot be discovered)", "validation": "disjoint from v1/v2; exposed at aggregate level through broad-universe development (both lanes include 2020) and the S2 headline the author read; screening only, never confirmatory; the 2020 COVID regime is reported separately and never dropped", - "holdout": "prospective; untouched only if the validation results sha256 was pushed before N0, no read preceded a committed granted access-log entry, and accrual exposure was logged and excluded as above" + "holdout": "prospective; untouched only if the validation results sha256 was pushed before N0, no read preceded a committed granted access-log entry, every collection batch has its committed accrual log, and no carried item's paper-exposed fraction exceeds 5% (accrual_exposure)" }, "boundaries": "Segments are assigned by entry session. A trade whose planned exit falls after its segment's last session is censored: it exits at the first eligible quote at or after 15:55 ET on that last session, is flagged, stays in the earlier segment's means and is reported as a count. H3 b_overnight makes no entry on a segment's last session (counted), and an H3-c trade whose legs cross the segment end is excluded from H3-c and counted. For multi-session arms, the later segment gives up its first 6 sessions as entry sessions (an embargo after the censored crossing trades); same-session arms need no gap." }, @@ -106,7 +126,7 @@ ], "tradable_cells": "2 populations x 3 arms x 3 terciles = 18; each tested for mean net > 0", "not_supported_if": "at the verdict stage (outcome_reporting.hypothesis_verdict), H1-D and H1-I are both labelled not_supported_mde_excluded and no H1 tradable cell passes. A high-MAX outperformance is outside the one-sided alternative, is reported descriptively only, and supports no claim about the reversal prior.", - "data_status": "expected available (Alpaca SIP daily and minute bars, quotes and official prices); historical quote and auction-print coverage for 2016-2020 is unverified and is a freeze precondition", + "data_status": "expected available (Alpaca SIP daily and minute bars, quotes and official prices), but conditional: historical quote and auction-print coverage for 2016-2020 is unverified, and H1 is tested only if coverage_rule keeps enough years (multiple_testing.data_status_at_freeze)", "chronology": "common (development 2017-2019, validation 2020, prospective holdout)" }, "H2": { @@ -134,9 +154,9 @@ {"source": "Maroy, SSRN 5095349 (posted 2025-01-12), VWAP and ladder exits", "status": "contested: HTTP 403, not read; excluded from the confirmatory family"} ], "populations": ["D_daily"], - "price_sources": "official open = the primary listing's opening auction print from the auctions collection (collect.py, sessions_io.official_price 'o' at aa6fc79); official close likewise ('c'). A trade with any missing official print among its 8 legs is excluded from H3-c and counted; no bar or quote substitutes in H3-c, and no trade is averaged over fewer legs.", + "price_sources": "official open and official close as populations.official_prints accepts them (sessions_io.official_price at aa6fc79, accepted labels only; closing_print_other_exchange counts as missing). A trade with any missing official print among its 8 legs is excluded from H3-c and counted; no bar or quote substitutes in H3-c, and no trade is averaged over fewer legs.", "exit_arms": { - "a_intraday": "enter at the first eligible quote at or after 09:35 on t+1; exit at or after 15:55 the same session", + "a_intraday": "enter at the first eligible quote at or after 09:35 on t+1, timeout 300 s as for D, else no entry (counted); exit at or after 15:55 the same session. H3-a's trades are the H1 D c_session trades pooled across terciles, plus unassigned events; the overlap is intended and is reported, and H3-a keeps its own Holm slot.", "b_overnight": "enter at the first eligible quote at or after 15:55 on t+1, timeout 300 s, else no entry (counted); exit at the first eligible quote at or after 09:30 on t+2", "c_decomposition": "diagnostic only, not traded: over the sessions t+1 .. t+5 of the D b_lane hold, overnight legs k = 1..4 are official close of t+k to official open of t+k+1, and intraday legs k = 2..5 are official open to official close of t+k. The partial first-session leg is excluded, so each trade has 4 legs of each kind, and together they decompose official close(t+1) to official close(t+5)." }, @@ -148,7 +168,7 @@ ], "tradable_cells": "H3-a and H3-b are the tradable cells (counted once)", "not_supported_if": "at the verdict stage (outcome_reporting.hypothesis_verdict), H3-c is labelled not_supported_mde_excluded and neither H3-a nor H3-b passes after costs.", - "data_status": "expected available (Alpaca SIP minute bars and quotes; official opening and closing auction prints); 2016-2020 quote and auction-print coverage is a freeze precondition", + "data_status": "expected available (Alpaca SIP minute bars and quotes; official opening and closing auction prints), but conditional: 2016-2020 quote and auction-print coverage is unverified, and H3 is tested only if coverage_rule keeps enough years (multiple_testing.data_status_at_freeze)", "chronology": "common" }, "H4": { @@ -160,12 +180,13 @@ {"source": "Nasdaq TotalView-ITCH 5.0 specification (NQTVITCHSpecification.pdf) section 1.2.2 Stock Trading Action: coverage is 'all exchange-listed equities securities traded via the Nasdaq execution system'; state H is 'Halted across all U.S. equity markets / SROs'; P (paused) is 'Nasdaq-listed securities only', and 'Trading pauses on non-NASDAQ listed securities will be treated simply as a halt'; the J LULD Auction Collar applies to a paused security reopening", "status": "read on 2026-09-24 in the review fix round; the Databento XNAS.ITCH status schema that carries it is contested (sweep p1-databento-xnas-itch-halt-luld) and its cost and coverage start are unverified"} ], "parent_trades": "the b_lane trades of populations D and I (the trades H1, H2 and H5 share), deduplicated by symbol|entry session|entry timestamp. Other arms (H1 a_fast and c_session, H3 a and b) are not parents. The unit of analysis and of the session block bootstrap is one deduplicated parent trade.", - "population": "every parent trade whose security, during the holding interval, is in state H (halted across all US markets) or, for a Nasdaq-listed security, P (LULD pause), per the covered halt source. The two strata are reported separately: an LULD pause is identified only for Nasdaq-listed names; for other listings the source reports a pause as a halt.", + "population": "every parent trade whose security enters state H (halted across all US markets) or, for a Nasdaq-listed security, P (LULD pause), per the covered halt source, at a time after the entry fill and before the planned b_lane exit. A halt already in force at the entry stamp delays or prevents the entry (populations.fill_quote) and does not make the parent halted. The two strata are reported separately: an LULD pause is identified only for Nasdaq-listed names; for other listings the source reports a pause as a halt.", "exit_arms": { - "a_reopen": "exit at the primary listing's reopening-cross print, else the mid of the first eligible post-reopen quote. The print source is fixed before the freeze by metadata (for example XNAS.ITCH cross-trade messages for Nasdaq listings, or SIP trade conditions that mark the reopening cross); the Databento status schema carries no trades. Without a fixed print source, a_reopen uses the first eligible post-reopen quote only.", + "a_reopen": "exit at the reopening of the first halt or pause that starts after the entry fill (later halts in the same hold are ignored by a_reopen and logged), at the primary listing's reopening-cross print, else the mid of the first eligible post-reopen quote. The print source is fixed before the freeze by metadata (for example XNAS.ITCH cross-trade messages for Nasdaq listings, or SIP trade conditions that mark the reopening cross); the Databento status schema carries no trades. Without a fixed print source, a_reopen uses the first eligible post-reopen quote only.", "b_ignore": "hold to the parent b_lane exit, ignoring the halt" }, - "rules": "No fills during a halt; a resting stop fills through the gap at the first print. Pause duration is logged as a covariate only. Both arms' sides are costed at 1.25 x max(table hs', the realized half-spread of the fill quote, or of the first eligible post-reopen quote for a cross print), because post-reopen spreads on halted movers are wider than the table's time-bucket cells. Bootstrap blocks are 10 sessions for all H4 parents.", + "edge_cases": "(1) A halt that is still in force at the planned b_lane exit: both arms exit at the same first eligible post-reopen price, so the pair difference is 0; the case stays in the sample and is counted. (2) A halt still in force at the end of the segment's last session (chronology.boundaries): the parent is excluded from H4 and counted. (3) A pause in the last 10 minutes that folds into the closing cross: the reopening is the closing cross, so a_reopen exits at the accepted official close (populations.official_prints), else the first eligible quote of the next session. (4) Several halts in one hold: a_reopen uses the first; b_ignore holds through all of them. Each case has a synthetic test.", + "rules": "No fills during a halt; a resting stop fills through the gap at the first print. Pause duration is logged as a covariate only. Both arms' sides are costed at 1.25 x max(table hs', the realized half-spread of the fill quote, or of the first eligible post-reopen quote for a cross print), because post-reopen spreads on halted movers are wider than the table's time-bucket cells. Bootstrap blocks are 10 sessions for all H4 parents.", "sizing": "inherits the parent arm", "primary_tests": [ {"id": "H4", "statistic": "mean over halted parent trades of net(a_reopen) - net(b_ignore) (paired, one-sample)", "alternative": "not equal to 0 at development; after development, one-sided in the direction of the development estimate (multiple_testing.direction_lock)"} @@ -183,7 +204,7 @@ {"source": "blueprints/us-equities/acceptance-wave/factors-regimes.md: FINRA consolidatedShortInterest is not a realtime squeeze signal", "status": "repository source read"} ], "population": "D_daily", - "covariate": "DTC = the most recent FINRA short interest published before the decision (publication and settlement lag respected) / 20-session median share volume ending t, both in session t's share basis (populations.corporate_actions); terciles by tercile_rule (trailing 252-session breakpoints of D)", + "covariate": "DTC = the most recent FINRA short interest published before the decision (publication and settlement lag respected) / 20-session median share volume ending t, both in session t's share basis (populations.corporate_actions); terciles by tercile_rule over the H5 event set (tercile_rule.h5_event_set: D events with a defined DTC; D events without one are counted and reported but are not H5 events)", "exit_arms": {"b_lane": "as H1"}, "sizing": "1x; the overlay filters and never sizes", "primary_tests": [ @@ -204,13 +225,15 @@ }, "multiple_testing": { "family": "one family across H1-H5: the 8 non-tradable primary tests (H1-D, H1-I, H2-D, H2-I, H3-c, H4, H5-p, H5-s) plus the 27 tradable cells (H1 18, H2 4, H3 2, H5 3), m = 35. H6 is not in this family.", - "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout. An item not carried to a stage, or not tested for lack of data, scores p = 1 there. The validation results file, when committed, contains all 35 validation p-values (1 for items not carried or not tested); an incomplete file is refused by the evaluator.", + "item_ids": ["H1-D", "H1-D-a_fast-high", "H1-D-a_fast-low", "H1-D-a_fast-middle", "H1-D-b_lane-high", "H1-D-b_lane-low", "H1-D-b_lane-middle", "H1-D-c_session-high", "H1-D-c_session-low", "H1-D-c_session-middle", "H1-I", "H1-I-a_fast-high", "H1-I-a_fast-low", "H1-I-a_fast-middle", "H1-I-b_lane-high", "H1-I-b_lane-low", "H1-I-b_lane-middle", "H1-I-c_session-high", "H1-I-c_session-low", "H1-I-c_session-middle", "H2-D", "H2-D-b_lane-attention", "H2-D-b_lane-catalyst", "H2-I", "H2-I-b_lane-attention", "H2-I-b_lane-catalyst", "H3-a", "H3-b", "H3-c", "H4", "H5-D-b_lane-high", "H5-D-b_lane-low", "H5-D-b_lane-middle", "H5-p", "H5-s"], + "item_id_rule": "Tradable cell ids are hypothesis-population-arm-group (H3-a and H3-b are both primary tests and tradable cells). item_ids is the complete list, in bytewise ASCII order, which is the order used for stream spawning, for the final tie-break and for the validation results file.", + "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout. An item not carried to a stage, or not tested for lack of data, scores p = 1 there. The validation results file, when committed, contains all 35 validation p-values keyed by item_ids (1 for items not carried or not tested); an incomplete file is refused by the evaluator.", "direction_lock": "H3-c and H4 are two-sided only at development: the development screen uses the two-sided p = 2 x min(one-sided p), and the sign of the development estimate is recorded in the development results file. Validation and the holdout then use the one-sided test in that recorded direction, in the same Holm slot. A significant result in the opposite direction at validation or the holdout is reported descriptively and cannot pass.", - "data_status_at_freeze": "At the freeze each of H2, H4 and H5 is recorded as either 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently'. Data acquired after the freeze can support only a new protocol version with its own family; it never enters this family's Holm run, so a late small p cannot loosen other items' thresholds.", + "data_status_at_freeze": "At the freeze each of H1 and H3 (coverage_rule) and each of H2, H4 and H5 is recorded as either 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently'. Data acquired after the freeze can support only a new protocol version with its own family; it never enters this family's Holm run, so a late small p cannot loosen other items' thresholds.", "strictest_level": 0.0014286, "development_screen": "unadjusted p <= 0.05 with the minimum sample: one-sided in the stated direction for directional items, two-sided for H3-c and H4 (direction_lock); development only nominates items for validation", - "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) and broad-universe-mover-v1's 60 predeclared signal variants (5 signals x 3 horizons x 2 selections x 2 instrument lanes) on overlapping or other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 60 + 35) = 4.52e-5. broad-universe's 2 controls and its descriptive lane are not counted: they are base-rate reports with no pass rule (its promotion 'never'); that exclusion is a stated limitation. The label compares the bootstrap p with 4.52e-5; with B = 100,000 the smallest one-sided bootstrap p is 1.0e-5, below that level. Each p is reported beside a normal-tail p from the same bootstrap standard error. An item that passes Holm but not this level is labelled v3-family significant, lineage-unconfirmed.", - "tie_breaks": "Where items tie on a p (for example at the floor 1 / (B + 1)), in the holdout cap of 5 tradable cells or in Holm ordering, ties are broken by the normal-tail p, then by item id.", + "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) and broad-universe-mover-v1's 60 predeclared signal variants (5 signals x 3 horizons x 2 selections x 2 instrument lanes) on overlapping or other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 60 + 35) = 4.52e-5. broad-universe's 2 controls and its descriptive lane are not counted: they are base-rate reports with no pass rule (its promotion 'never'); that exclusion is a stated limitation. Each p is reported beside a normal-tail p from the same bootstrap standard error. With B = 100,000 a bootstrap p at 4.52e-5 means at most 3 null-side draws, so the bootstrap p alone cannot resolve this level (its Monte Carlo error is about the size of the threshold). An item is lineage-confirmed only if both its bootstrap p and its normal-tail p are at or below 4.52e-5; an item that passes Holm but not both is labelled v3-family significant, lineage-unconfirmed.", + "tie_breaks": "Where items tie on a p (for example at the floor 1 / (B + 1)), in the holdout cap of 5 tradable cells or in Holm ordering, ties are broken by the normal-tail p, then by item id in bytewise ASCII order (item_ids).", "tests_not_in_family": "BH (q = 0.10) diagnostics across tercile, year, price-tier and halt-covariate splits are reported but can never open the holdout" }, "minimum_sample": { @@ -288,24 +311,24 @@ "provenance": "The square-root functional form and the c grid are design choices bracketing the unknown; the sweep supplies no source for them." }, "out_of_period_spreads": { - "sample": "The first step of the frozen evaluation, after the freeze and before any other outcome: 1 in 20 development (2017-2019) D and I entries is selected by quotes.py's sample_key at aa6fc79: int.from_bytes(sha256('symbol|session|hhmm')[:8], 'big') % 20 == 0, with the v3 entry's symbol, entry session and entry HH:MM ET. Each selected entry is stamped at its entry timestamp, entry + 15 min, entry + 60 min and 5 minutes before that session's close (the v1 stamp set). Those stamps coincide with D c_session and I b_lane exits, so they are outcome prices; measuring them before the freeze would expose development outcomes, which #162 v1 avoided by freezing first. The sampling rule, the table method and the combine rule are fixed here, so no value is chosen after the sample is seen.", + "sample": "The first step of the frozen evaluation, after the freeze and before any other outcome: 1 in 20 development (2017-2019) D and I entries is selected by quotes.py's sample_key at aa6fc79: int.from_bytes(sha256('symbol|session|hhmm')[:8], 'big') % 20 == 0, with the v3 entry's symbol, entry session and entry HH:MM ET. Each selected entry is stamped at its entry timestamp, entry + 15 min, entry + 60 min and 5 minutes before that session's scheduled close from session_calendar (the v1 stamp set; quotes.py cmd_sample's stamping is not reused, because rules.close_hhmm misses 2016-2020 early closes). Those stamps coincide with D c_session and I b_lane exits, so they are outcome prices; measuring them before the freeze would expose development outcomes, which #162 v1 avoided by freezing first. The sampling rule, the table method and the combine rule are fixed here, so no value is chosen after the sample is seen.", "table": "cells are built with quotes.py's table method at aa6fc79, including the D7 stamp-window guard and v1's sparse-cell fallbacks: a cell with >= 30 observations (MIN_CELL) uses its trimmed mean, else the time x price pool if it has >= 30, else the table's 90th percentile", "combine": "each cell's cost is max(hs from the base table, hs from the 2017-2019 table); fix_1 is then applied to the result" }, "per_side": "1.25 x hs' + impact, for continuous and auction sides alike (v1's 0.5x auction discount is not reused); H4 sides use the halt-specific rule in hypotheses.H4.rules", - "fees": "#162 fees.json at aa6fc79 (sha256 fa1f468b80f78dc0c050948123116f577e38ae9f8c03f9fb360d11042a3fed3d) by reference, extended before the freeze with SEC Section 31 and FINRA TAF rates in force during 2017-2020 from primary sources; Alpaca commission 0; IBKR tiered as a broker variant", + "fees": "a v3-owned file, blueprints/us-equities/mover-v3/fees-v3.json, pinned by sha256 at the freeze. It carries SEC Section 31 and FINRA TAF rows in force from 2017-01-01 through the holdout, each with its primary source and quote; #162 fees.json at aa6fc79 (sha256 fa1f468b80f78dc0c050948123116f577e38ae9f8c03f9fb360d11042a3fed3d) is left unchanged and is not used, because its SEC rows start at 2020-01-01 for lookup convenience only. Rates announced after the freeze for holdout dates are appended from primary sources before the holdout read, logged in the access log, and never change an earlier row. v3 code computes fees from this file, not from rules.sell_fees. Alpaca commission 0; IBKR tiered as a broker variant. A synthetic test looks up a 2017 and a 2019 sale.", "stress": "2 x (spread + slippage) with c = 2.0", "break_even": "the cost multiple at which each cell's mean net return reaches 0 is reported" }, "statistics": { "estimator": "trade-level mean net return; also median, 1%-winsorised mean, mean without the top 5 sessions and mean of per-session means", - "bootstrap": "circular session block bootstrap, B = 100,000; block length 5 sessions for same-session arms and 10 sessions for multi-session arms and for H4 (whose parents mix both); difference tests resample sessions jointly for both groups; generator numpy.random.Generator(PCG64), seeded by numpy.random.SeedSequence(the first 8 bytes of sha256(frozen protocol id), big-endian), with one spawned child stream per item in ascending item-id order", + "bootstrap": "circular session block bootstrap, B = 100,000; block length 5 sessions for same-session arms and 10 sessions for multi-session arms and for H4 (whose parents mix both); difference tests resample sessions jointly for both groups; generator numpy.random.Generator(PCG64), seeded per stage: stage_index 0 development, 1 validation, 2 holdout; each stage uses numpy.random.SeedSequence(entropy = the first 8 bytes of sha256(frozen protocol id) read big-endian, spawn_key = (stage_index,)).spawn(35), and child i drives item_ids[i] (multiple_testing.item_ids order), so no stage reuses another stage's stream", "p_value": "one-sided p = (1 + #{b : stat_b on the null side of 0}) / (B + 1); two-sided p = min(1, 2 x the smaller one-sided p)", "robustness_required_for_a_pass": "a tradable cell's validation and holdout passes also need a positive winsorised mean, mean without the top 5 sessions and mean of per-session means", - "sensitivities": ["two-way (session, symbol) clustering", "without split-day and fallback-price rows", "2020 reported alone", "survivorship-limited label before 2020", "validation restricted to symbol-sessions that were not a broad-universe lane decision (exposure_registry.consequence)", "cost from the realized half-spread of each fill quote (populations.fill_price)", "censored segment-end trades removed (chronology.boundaries)"] + "sensitivities": ["two-way (session, symbol) clustering", "without split-day rows (v3 has no fallback-price rows: populations.formulas and official_prints)", "2020 reported alone", "survivorship-limited label before 2020", "validation restricted to symbol-sessions that were not a broad-universe lane decision (exposure_registry.consequence)", "cost from the realized half-spread of each fill quote (populations.fill_price)", "censored segment-end trades removed (chronology.boundaries)", "holdout without paper-exposed trades (chronology.holdout.accrual_exposure)"] }, "holdout_gate": { - "opens_only_for": "items that passed validation: a primary test or tradable cell with Holm-adjusted validation p <= 0.05 in the 35-item family, the minimum validation sample and, for a tradable cell, the robustness means. At most 5 tradable cells (lowest validation p) and every validated primary test.", + "opens_only_for": "items that passed validation: a primary test or tradable cell with Holm-adjusted validation p <= 0.05 in the 35-item family, the minimum validation sample and, for a tradable cell, the robustness means. At most 5 tradable cells, ranked by raw (unadjusted) validation bootstrap p with multiple_testing.tie_breaks, and every validated primary test.", "h4_scope": "a validated H4 authorizes computing, in the holdout, the a_reopen and b_ignore exits of halted parent trades only (H4 parent_trades), whether or not any parent cell validated. It authorizes no non-halted parent outcome and no tradable-cell statistic.", "no_pass": "If nothing validates, the holdout is never labelled or read, and that is the result.", "prospective_collection": "Holdout-window data are collected forward into a sealed private snapshot whose sha256 is committed per collection batch. No outcome label, forward return or exit is computed from it before the gate opens.", @@ -315,6 +338,7 @@ "the list of validated items is empty", "a requested item is not in that list, or a requested H4 read asks for more than h4_scope", "the access log has no committed 'granted' entry for this run", + "any collection batch lacks its committed accrual log (chronology.holdout.accrual_exposure), or a batch's log was committed after the batch's last session's outcomes could exist", "a previous holdout run exists (the holdout is read once; failed or partial runs stay in the log and are never called untouched). A granted 'count' entry is not a holdout run", "a 'count' request asks for anything beyond entry and assignment counts per carried item, or a 'count' read is requested other than once at the end of the 252 sessions and once at the end of each extension block" ], @@ -330,16 +354,17 @@ "freeze_preconditions": [ "An independent pre-outcome review of this draft from a different session and model family, with every finding resolved in a dated record", "Direct full-text reads of Lou-Polk-Skouras 2019, Akbas-Boehmer-Jiang-Koch 2022 and MacLean-Thorp-Ziemba 2010, or removal of every claim that depends on them (the two JFE DOIs resolve in Crossref; that is metadata only)", - "The 2017-2019 development cost sample fixed by rule (not measured: it is the first step after the freeze); fees extended to 2017-2020 from primary sources", - "Metadata-only coverage checks, no outcome computed: Alpaca SIP historical quote coverage and eligible-quote rates, and official opening and closing auction-print coverage, for 2016-2020 (H1-H3); the Databento get_cost quote, XNAS.ITCH coverage start and a reopening-cross print source (H4); FINRA short-interest history start, including the 2016 warm-up, and terms (H5); identity and filing-index completeness for H2's gates", - "The data status of H2, H4 and H5 recorded as 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently' (multiple_testing.data_status_at_freeze)", + "The 2017-2019 development cost sample fixed by rule (not measured: it is the first step after the freeze); fees-v3.json committed with sourced 2017-2020 SEC and TAF rows and pinned (cost_model.fees); session-calendar.json generated from exchange_calendars 4.13.2 XNYS and pinned (populations.session_calendar)", + "Metadata-only coverage checks through exposure_registry.pre_freeze_access_path only, no outcome computed: Alpaca SIP eligible-quote rates at the coverage stamps and accepted official-print rates per label, for 2016-2020, with coverage_rule applied (H1-H3); forward-split, reverse-split and cash-dividend record counts per year for 2016-2020 and the count of suspected unadjusted splits (populations.suspected_unadjusted_split); the Databento get_cost quote, XNAS.ITCH coverage start and a reopening-cross print source (H4); FINRA short-interest history start, including the 2016 warm-up, and terms (H5); identity and filing-index completeness for H2's gates", + "The data status of H1 and H3 (coverage_rule) and of H2, H4 and H5 recorded as 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently' (multiple_testing.data_status_at_freeze)", "The universe and daily-dataset snapshot pinned by sha256 with its asof date and asof policy", - "Study code implementing this protocol committed with synthetic tests before any outcome, including a split and ex-dividend crossing, early-close sessions, segment-end censoring, the direction lock and the evaluator's holdout refusals and 'count' path", + "Study code implementing this protocol committed with synthetic tests before any outcome, including a split and ex-dividend crossing, a suspected unadjusted split, each official-print source label, the fill-quote cases, 2016-2020 early-close sessions from session_calendar, a 2017 fee lookup, segment-end censoring, the H4 edge cases, the direction lock and the evaluator's holdout refusals (including a missing accrual log) and 'count' path. The code imports none of rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return and rules.net_return_np, and a test asserts this", "The exposure registry updated with everything the author saw since this draft", "status set to frozen, frozen_before_outcomes true, and the protocol sha256 recorded in a pushed commit" ], "review_record": [ - {"date": "2026-09-24", "round": 2, "input": "two independent pre-outcome rereviews of commit 0b924e7 (findings S1-S14 and F1-F13)", "resolution": "S1 direction_lock; S2 data_status_at_freeze and complete validation file; S3 and F12 cost sample moved after the freeze; S4 sigma entries, label bound, unequal-group formula, H3-c minimum and leg rule, two-sided table replaced; S5 holdout_breakpoints; S6 'count' purpose; S7 and F6 boundaries, session_times and H3-b timeout; S8 stage labels and hypothesis_verdict; S9 README H2/H5 rows; S10 fixed holdout start and accrual keys; S11 tie_breaks, H4 block length, PCG64 streams; S12 C0 as superset and lane-decision slice; S13 lineage 1,107; S14 official-close rule, I window, breakpoints over all events; F3 H2 gated; F4 corporate_actions and evaluate_v2.py pin; F5 fill_price and coverage preconditions; F9 accrual keys and disjoint H6 symbols; F10 reopen print source and halt cost; F11 README rate-limit text; F13 preconditions. F1, F2, F7 and F8 are resolved in the H6 draft (H6 gated, H6-c descriptive). No finding rejected."} + {"date": "2026-09-24", "round": 2, "input": "two independent pre-outcome rereviews of commit 0b924e7 (findings S1-S14 and F1-F13)", "resolution": "S1 direction_lock; S2 data_status_at_freeze and complete validation file; S3 and F12 cost sample moved after the freeze; S4 sigma entries, label bound, unequal-group formula, H3-c minimum and leg rule, two-sided table replaced; S5 holdout_breakpoints; S6 'count' purpose; S7 and F6 boundaries, session_times and H3-b timeout; S8 stage labels and hypothesis_verdict; S9 README H2/H5 rows; S10 fixed holdout start and accrual keys; S11 tie_breaks, H4 block length, PCG64 streams; S12 C0 as superset and lane-decision slice; S13 lineage 1,107; S14 official-close rule, I window, breakpoints over all events; F3 H2 gated; F4 corporate_actions and evaluate_v2.py pin; F5 fill_price and coverage preconditions; F9 accrual keys and disjoint H6 symbols; F10 reopen print source and halt cost; F11 README rate-limit text; F13 preconditions. F1, F2, F7 and F8 are resolved in the H6 draft (H6 gated, H6-c descriptive). No finding rejected."}, + {"date": "2026-09-24", "round": 3, "input": "rereview of commit 9f4b9f2 (findings S-1 to S-13 and X1 to X11), each checked against this draft and the named code at aa6fc79 or origin/main", "resolution": "S-1 accrual_exposure (every trade in the primary statistic, paper-order exposure logged at order time, 5% contamination cap, ad hoc reads void rather than exclude, missing-accrual-log refusal); S-2 exposure_registry.pre_freeze_access_path with count-only outputs and non-v3 coverage stamps; S-3 populations.official_prints (closing_print_other_exchange rejected); S-4 and X4 populations.session_calendar, v3-owned fees-v3.json, the pinned rules.py fee and close functions barred; S-5 N0 fixed, validation by committed automation, a late push voids the holdout; S-6 H4 population and edge_cases; S-7 holdout_breakpoints excludes 2026-01-02 to N0 - 1 and names the 2024-11 to 2025-12 covariate source; S-8 multiple_testing.item_ids and per-stage SeedSequence spawning; S-9 common extended window, underpowered after 2 blocks, cap by raw p; S-10 and X10 populations.fill_quote (one eligibility rule for fills and cost cells), fetch_failures and the H3-a timeout and overlap note; S-11 tercile_rule.h5_event_set; S-12 lineage label needs both bootstrap and normal-tail p; S-13 README precedence corrected to exits.py; X3 populations.suspected_unadjusted_split and the corporate-action coverage precondition; X5 coverage_rule, with H1 and H3 data status now fixed at the freeze; X6 populations.formulas restated and sessions_io.py pinned. X1, X2, X7, X8, X9 and X11 are resolved in the H6 draft. No finding rejected."} ], "overturn": "Any headline result is overturned if a rerun on the same frozen inputs and runtime does not reproduce it byte for byte, if an independent quote sample puts the cost table off by more than 2x, or if the access log shows a holdout read that no committed granted entry preceded." } diff --git a/manifests/evidence.json b/manifests/evidence.json index 8ccc11020..6d27a24a5 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -8448,18 +8448,18 @@ }, { "path": "blueprints/us-equities/mover-v3/README.md", - "sha256": "27da45936e27b06c672e0155a330499fb3efdc0946192466e217ed1836c928e4", - "bytes": 38573 + "sha256": "3c9c7a7ded69c2c7a50d719e9eeeafbecd308e7630f0ce0913372ef5854c02d5", + "bytes": 41186 }, { "path": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json", - "sha256": "e9dfdfb0f73aba4362d1cc4b3d4b6957d7b06a6b45808f0617de65191a943c1e", - "bytes": 12600 + "sha256": "a884eaebb9cdec0f38f0eb250fe4d0262c2479117f5ca07f686982cc2e4a513d", + "bytes": 17723 }, { "path": "blueprints/us-equities/mover-v3/protocol-draft.json", - "sha256": "60a06002b4ab9954b42c411eefec9972f6c0e43e69d0446e8401eff770af27f9", - "bytes": 57171 + "sha256": "3483eb4bc3b26dcf7d7edca9a8b5b248feb11e171d19fe8d52a0270d6326424a", + "bytes": 74207 }, { "path": "blueprints/us-equities/nanosecond-replay/README.md", From ba08ad4d4b4b455d69821ed8f0434312eead694a Mon Sep 17 00:00:00 2001 From: Scout Date: Thu, 24 Sep 2026 03:59:28 -0400 Subject: [PATCH 5/8] Mover v3 review round 4: run discipline, sealed fetches, covariate snapshot, coverage over H1-H5, exact formulas Resolve the 29 findings from the rereview of 3053b31 in the drafts; none rejected. - protocol-draft.json: study-code pin, append-only run log, once rule and deviation rule (run_discipline); sealed post-freeze fetches with one preregistered re-fetch; full pre-freeze snapshot contents including 2024-11..2025-12 minute bars, prints and FINRA short interest if H5 is tested; coverage rule over every D/I item with dropped-year rules; exact DV_reg, MAX21, sizing window, med20, sigma_d and entry bar; split factors from adjustment=split bars (stated departure from D7); narrow holdout-read definition; mechanical freeze commit, N0 and deadline; holdout collection from the freeze session with quotes fetched only under granted entries; prospective data commitments for H2/H4/H5; count unit; MDE bound per alternative and sigma_by_item; literal exclusion regex and as-known listing; label qualifiers; calendar amendments; fetch-time precondition; two registry entries. - h6-execution-parity-draft.json: disjointness screen with no v3 definition, transport/safety/reconciliation preconditions, session-cut stages, statistics section, SIP feed and 2-share entries. - README.md and manifests/evidence.json updated to match. Status stays draft_pending_independent_pre_outcome_review; nothing frozen. Co-Authored-By: Claude Opus 5.5 (1M context) --- blueprints/us-equities/mover-v3/README.md | 53 +++++-- .../mover-v3/h6-execution-parity-draft.json | 34 +++-- .../us-equities/mover-v3/protocol-draft.json | 129 +++++++++++------- manifests/evidence.json | 12 +- 4 files changed, 148 insertions(+), 80 deletions(-) diff --git a/blueprints/us-equities/mover-v3/README.md b/blueprints/us-equities/mover-v3/README.md index 9bc8c147f..24e7617af 100644 --- a/blueprints/us-equities/mover-v3/README.md +++ b/blueprints/us-equities/mover-v3/README.md @@ -56,7 +56,7 @@ primary tests, minimum samples and windows. Two populations are common to H1-H5, from `rules.py` by reference: - **D (daily lane):** a split-consistent close-to-close gain of at least +20%, a price of at least $1 and a - completed-session dollar volume of at least $1M. The decision is at 16:15 ET, and the entry is the first eligible + regular-session dollar volume of at least $1M (summed from 1-minute bars from 09:30 ET to the scheduled close). The decision is at 16:15 ET, and the entry is the first eligible quote at or after 09:35 ET on the next session. - **I (intraday lane):** the first minute from 09:35 to 10:30 ET at which `gain_at_t >= 0.20` and `dollar_volume_at_t >= $1M`, with `price_at_t >= $1`. The entry is the first eligible quote at or after @@ -66,7 +66,11 @@ The draft restates these formulas exactly, since `rules.py` does not define `ref closes count only from the listing exchange's auction print or official close. A close printed only on another exchange, or a missing previous close, means no event, with no bar fallback. A session whose raw close ratio sits within 1% of an integer split ratio with no adjustment is treated as an unadjusted split and is excluded before -membership. Every such case is counted. +membership. Split factors come from split-adjusted bars, so a large cash dividend is booked as cash, not as a share +change (a stated departure from #162's D7 code, which used all-adjusted bars). MAX21, the sizing window, the +20-session median dollar volume, the volatility used for impact and the entry bar are each defined exactly. OTC +names are excluded by as-known session data (no listing-exchange opening print), never by today's asset flags. +Every such case is counted. H1-H3 use no halt data. As in the catalyst experiment, a halt is never inferred from missing data: fills need an eligible quote, so a trade set never depends on a later halt-data purchase. Halt state enters only H4. @@ -83,7 +87,7 @@ exposure registry records this. | H3 | Do the overnight and intraday legs differ? | D | (a) intraday only: enter at 09:35, exit at 15:55 the same session; (b) overnight only: enter at 15:55 (300 s timeout), exit at the first eligible quote after the next 09:30 open; (c) the 5-session hold split ex post into 4 overnight legs (official close to official open) and 4 intraday legs (official open to official close), comparing mean per-leg returns (a diagnostic, not traded; a trade missing any leg is excluded). Two-sided at development; its direction is then locked | 1x. Arm (b) stays at 1x in any later paper use until the 2x rung and its overnight cap are clean | not supported: at the verdict stage, the per-leg difference excludes the MDE, and neither traded arm passes | **expected available**; 2016-2020 quote and auction coverage is unverified; H3 is tested only if the coverage rule keeps enough years, decided at the freeze | | H4 | Does a halt-conditional exit beat ignoring the halt? | No new entry. Applies to every D and I lane-horizon (b_lane) trade, deduplicated by symbol, session and entry time, that meets a halt or, for a Nasdaq-listed name, an LULD pause (not only passing arms, so that a no-pass outcome still leaves a sample) | (a) exit at the reopening of the first halt that starts after the entry fill, at the reopening-cross print (source fixed before the freeze) or the first post-reopen quote; (b) hold to the lane horizon. A halt still in force at the planned exit gives both arms the same post-reopen price (difference 0); one that spans a segment end is excluded; a pause folded into the closing cross exits at the official close. No fills during a halt. Both sides costed at the wider of the table and the realized post-reopen spread. Pause length is logged as a covariate, not a signal. Two-sided at development; its direction is then locked | inherits the parent arm | not supported: at the verdict stage, the (a) minus (b) difference excludes the MDE. Below the minimum halted trades it is underpowered. Either way the halt state stays a safety rule only | **gated** (Databento XNAS.ITCH status schema, paid, *contested*: corrected from raw ITCH messages to the normalized schema; Nasdaq Trader history as a short pilot) | | H5 | Does days-to-cover crowding filter movers? | D split by days-to-cover tercile (short interest / ADV, both in the decision session's share basis; trailing 252-session breakpoints), using the latest FINRA figure published before the decision, with its settlement lag | the lane horizon | 1x; a filter, never a size input | primary (H5-p): high minus low DTC is labelled not supported with a negative effect of the MDE size excluded. Secondary squeeze test (H5-s, no sourced prior): high DTC minus the pooled middle and low terciles, reported separately. Either way at the verdict stage and with no H5 cell passing | **gated** (FINRA short-interest access and terms; history must cover the 2016 warm-up) | -| H6 | Engineering, not alpha, in its own draft and one-test family: do broker-held exits realize exit prices equivalent to client-side exits? | a named mechanics-only strategy at a fixed minimal notional, entering after 10:31 ET only in symbols that are flat, have no open order, are not an I event that session and had no D decision in the previous 5 sessions, assigned to an arm by a hash of symbol, session and entry count written to the ledger before the order is sent. Not yet defined, so H6 cannot accrue | (a) the current `exits.py` chain: client-side triggers, one exit limit, replace disabled (the shipped default); (b) the quantity split into two tranches, each with exactly one broker-held GTC closing order: an OCO (take-profit and a stop-market stop) on the take-profit tranche and a `trailing_stop` (trail_percent from `effective_trailing_bps` at entry) on the rest. Time, risk-off, force and halt exits stay client-side | the strategy's fixed minimal notional | not supported: the median exit slippage of positions whose first exit was stop-type, taken intention to treat to their final close, is not shown equivalent within ±25 bps against each arm's preregistered trigger level and against the NBBO bid at trigger (TOST, 200 per arm per confirmatory stage); or paper partial-fill or trigger behaviour departs from the docs. REST calls per position are reported, not tested: arm (a) needs about 2, arm (b) at least 3 | **gated**: no position source; needs the strategy, a recorded decision on the no-manufactured-trades rule, the adapter change, a paper smoke test and a paper pilot projecting at least 1.5x each stage minimum within 126 sessions (else dropped) | +| H6 | Engineering, not alpha, in its own draft and one-test family: do broker-held exits realize exit prices equivalent to client-side exits? | a named mechanics-only strategy on a committed large-cap and ETF list, 2 shares per entry on the SIP feed, entering after 10:31 ET only in symbols that are flat, have no open order and had no absolute daily return of 10% or more in the previous 5 sessions or at entry (a screen that uses no v3 definition), assigned to an arm by a hash of symbol, session and entry count written to the ledger before the order is sent. Not yet defined, so H6 cannot accrue | (a) the current `exits.py` chain: client-side triggers, one exit limit, replace disabled (the shipped default); (b) the quantity split into two tranches, each with exactly one broker-held GTC closing order: an OCO (take-profit and a stop-market stop) on the take-profit tranche and a `trailing_stop` (trail_percent from `effective_trailing_bps` at entry) on the rest. Time, risk-off, force and halt exits stay client-side | the strategy's fixed 2-share entry | not supported: the median exit slippage of positions whose first exit was stop-type, taken intention to treat to their final close, is not shown equivalent within ±25 bps against each arm's preregistered trigger level and against the NBBO bid at trigger (TOST, 200 per arm per confirmatory stage, with stages cut by session so both arms cover the same period); or paper partial-fill or trigger behaviour departs from the docs. REST calls per position are reported, not tested: arm (a) needs about 2, arm (b) at least 3 | **gated**: no position source; needs the strategy, a recorded decision on the no-manufactured-trades rule, the adapter, transport, safety-reservation and reconciliation changes for non-limit orders, a paper smoke test and a paper pilot projecting at least 1.5x each stage minimum within 126 sessions (else dropped) | **Outcome labels.** Each test ends in one of three preregistered labels: a pass; *not supported*, with the excluded effect size stated; or *underpowered*. A pass is named by its stage: *nominated* at development, *screened* at @@ -93,17 +97,27 @@ literature's roughly 1% a month, so a miss on H1, H2 or H5 will most likely be l only large mover-specific effects. **Data status is fixed at the freeze.** H2, H4 and H5 are each recorded then as tested (data in hand) or not tested -(p = 1 at every stage, permanently). So are H1 and H3: a 2016-2020 year below 90% official-close coverage or 80% -eligible-quote coverage is dropped, and with fewer than 2 development years left, or without 2020, both are not -tested. Data bought or obtained after the freeze can only start a new protocol version, -so it cannot change this family's Holm thresholds after results are seen. +(p = 1 at every stage, permanently). One coverage rule covers all of H1-H5, with one set of kept years: a 2016-2020 +year below 90% official-close coverage or 80% eligible-quote coverage is dropped for every item, and with fewer than 2 development years left, or without 2020, no item of H1-H5 is +tested, since every item uses the same D and I trades. A dropped year's edges are segment boundaries, its events +leave the tercile windows and the bootstrap wraps over the kept sessions. Data from a new source or a new historical +range obtained after the freeze can only start a new protocol version, so it cannot change this family's Holm +thresholds after results are seen. Each tested item that needs holdout data beyond the SIP collection (H2 filings, H4 +halts, H5 short interest) must have a prospective source, licence and budget committed at the freeze; if those data +later fail to arrive, the item is labelled underpowered, never dropped. **Exposure and chronology.** Development is 2017-2019 and validation 2020. The holdout is 252 sessions (early closes -included) starting at the 40th session after the freeze commit. If the validation results are not pushed before then, -the holdout of this protocol version is void; there is no deferral, so its start cannot be chosen after the results are +included) starting at the 40th session after the freeze. The freeze commit is the first commit on `main` whose +protocol status is frozen (pull requests are squash-merged), timed by its committer timestamp in New York. If the +validation results commit is not on `main` before 09:30 ET on that 40th session, the holdout of this protocol version +is void; there is no deferral, so its start cannot be chosen after the results are seen. Every holdout trade enters the primary statistic. Paper orders in holdout symbol-sessions are logged at order time -and removed only in a sensitivity, and more than 5% exposed trades labels an item's holdout contaminated. An ad hoc read -of holdout prices voids the holdout rather than removing trades. The 2017-2020 windows are +and removed only in a sensitivity, and more than 5% exposed trades labels an item's holdout contaminated. A holdout read is defined narrowly: computing any v3 outcome from +holdout data outside a granted access-log entry. It voids the holdout rather than removing trades. Live paper +decisions and orders are exposure, logged in the accrual log, not reads; metadata-only dashboards are neither. +Development and validation each run once from a pinned study-code revision, every run is logged (failed runs +included), and a code change after the first outcome is a numbered deviation that never changes which items the +holdout opens for. The 2017-2020 windows are disjoint from #162's v1 and v2, but not unseen. The broad-universe study computed forward returns for every eligible symbol-session in 2017-2021 (its C0 control), including a descriptive lane covering names with a close of at least $1, a median dollar volume of at least $2M, 60 prior contiguous bars and no gap. C0 on that lane is the superset exposure @@ -195,7 +209,7 @@ Size = inverse-vol weight x liquidity cap x edge shrink, clipped to that ceiling - **Liquidity cap:** each order is at most a preregistered fraction of trailing ADV and of recent minute volume. The sweep found no ADV term in `strategies_v1.py`. - **Edge shrink:** 0 until a v3 rule passes its untouched holdout. H6's mechanics-only strategy is exempt because it - makes no alpha claim; it trades a fixed minimal notional. After that it becomes fractional Kelly, at most + makes no alpha claim; it trades a fixed 2 shares per entry. After that it becomes fractional Kelly, at most half, with estimation-error shrinkage (MacLean, Thorp, Ziemba 2010, *Quantitative Finance* 10(7):681-687; *contested*: citation only, full text not opened). `cvxgrp/kelly_code` stays `discovery_only` because it is GPL-3.0 and has not been pushed since 2020. @@ -281,7 +295,11 @@ The Elite DMA gateway is excluded (see Dropped). **v3 adapter changes this design needs, none of them made here:** -- OCO, bracket and `trailing_stop` order paths in `native_adapter.py` (which builds only LIMIT/DAY orders today); +- OCO, bracket and `trailing_stop` order paths in `native_adapter.py` (which builds only LIMIT/DAY orders today), and + the same order classes through `transport.normalize_intent` (which rejects any type but `limit`, any + time_in_force but `day` and a missing limit price), `safety.py`'s sell reservations (two OCO legs must reserve + their tranche once, not twice) and `order_report` reconciliation (which hard-codes LIMIT/DAY and parses a limit + price that stop-market and trailing orders lack); - batch cancel-all on step-down; - IOC entries, once IOC is verified; - an audit of fail-closed handling across *both* vocabularies. The `trade_updates` event names (`calculated`, @@ -398,6 +416,10 @@ into another: - an independent pre-outcome review, from a different session and model family, with every finding resolved in a dated record; - the direct citation reads above, or removal of the claims that depend on them; +- a study-code revision and tree pinned in the frozen protocol, with an append-only run log and a deviation rule, + and post-freeze fetches sealed with their fetch-incomplete rate committed before any outcome; +- the documented market-data rate limit and a fetch-time estimate with margin under the 40 sessions before the + holdout; - the 2017-2019 cost re-measure fixed by rule only; it runs as the first step after the freeze, because its quote stamps coincide with outcome prices; fee rates dated for 2017-2020; - metadata-only coverage checks, run only by committed count-only code that emits counts and rates and seals the raw @@ -406,14 +428,15 @@ into another: sources, H5's short-interest history including the 2016 warm-up; - the v3 session calendar and fee file pinned; - H1 and H3 (by the coverage rule) and H2, H4 and H5 each recorded as tested or permanently not tested; -- the universe and daily-dataset snapshot pinned with its asof date; +- the pre-freeze snapshot pinned with its asof date, including the 2024-11 to 2025-12 minute bars, official prints + and (if H5 is tested) short interest that the holdout tercile breakpoints need; - study code committed with synthetic tests before any outcome is computed, including split and ex-dividend crossings, early-close sessions and segment-end censoring. H6 has its own preconditions in `h6-execution-parity-draft.json`, including a named mechanics-only strategy, a test that one share never carries two closing orders and a paper smoke test of the tranche pair. -The second and third review rounds' findings and their resolution are recorded in `protocol-draft.json` +The second, third and fourth review rounds' findings and their resolution are recorded in `protocol-draft.json` (`review_record`). Until all of that is done, this is a plan, and no window it names may be read for outcomes. diff --git a/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json index 4eb66e3e1..8fea3e763 100644 --- a/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json +++ b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json @@ -5,7 +5,7 @@ "status": "draft_pending_independent_pre_outcome_review", "frozen_before_outcomes": false, "drafted_at": "2026-09-24", - "revised_at": "2026-09-24 (round 2: population named as a gated mechanics-only strategy, H6-c restated as descriptive because arm a needs fewer calls by construction, arm a configuration pinned, H6-e unit and trigger strata, GTC and tranche timing, calendar cap, paper smoke test); round 3 on the same day: trigger references per arm plus a common NBBO-bid basis, stop-market OCO leg, intention-to-treat H6-e unit, flat-symbol entry rule, entry cancel timeout and tranche-reject fallback, a pilot-based power precondition, and a disjoint, sealed population)", + "revised_at": "2026-09-24 (round 2: population named as a gated mechanics-only strategy, H6-c restated as descriptive because arm a needs fewer calls by construction, arm a configuration pinned, H6-e unit and trigger strata, GTC and tranche timing, calendar cap, paper smoke test); round 3 on the same day: trigger references per arm plus a common NBBO-bid basis, stop-market OCO leg, intention-to-treat H6-e unit, flat-symbol entry rule, entry cancel timeout and tranche-reject fallback, a pilot-based power precondition, and a disjoint, sealed population); round 4 on the same day: disjointness by a screen that uses no v3 definition, adapter preconditions for transport intents, safety reservations and reconciliation reports, session-based stages, a statistics section, the SIP feed pinned, and a 2-share minimum)", "base_commit": "aa6fc791bbd4af3499317673989d2e90a522c257", "parent_plan": "blueprints/us-equities/mover-v3/README.md", "split_from": "blueprints/us-equities/mover-v3/protocol-draft.json (H6), in the 2026-09-24 review fix round", @@ -16,11 +16,12 @@ "evidence": [ {"source": "https://docs.alpaca.markets/docs/orders-at-alpaca", "status": "read on 2026-09-24. trailing_stop (trail_price or trail_percent) and OCO for a held position ('the entry order is already filled, and you can submit the take-profit and stop-loss in one order submission') are documented. Constraints quoted verbatim: 'Trailing stop orders are currently supported only with single orders. However, we plan to support trailing stop as the stop loss leg of bracket/OCO orders in the future.' and 'you would not be able to submit two conditional closing orders since Alpaca's system would view one of the two orders as exceeding the available position quantity'."}, {"source": "https://docs.alpaca.markets/reference/deleteallorders-1", "status": "survives"}, - {"source": "adaptive-paper at aa6fc79: exits.py evaluates stop, take-profit (take_profit_fraction) and trailing rules client-side with no resting broker stop; when a rule fires, native_strategy.py (about lines 755-819) sends one DAY limit at limit_price(bid, ask); replace_exit re-prices only an unfilled resting exit, is off by default (runner.py line 323, exits.replace_enabled False; strategies_v1.py exit_replace_enabled False) and is capped at exit_replace_max_attempts = 3; native_adapter.py builds and accepts only LIMIT/DAY orders (lines 344, 354, 368)", "status": "repository source read in the round 2 fix"} + {"source": "adaptive-paper at aa6fc79: exits.py evaluates stop, take-profit (take_profit_fraction) and trailing rules client-side with no resting broker stop; when a rule fires, native_strategy.py (about lines 755-819) sends one DAY limit at limit_price(bid, ask); replace_exit re-prices only an unfilled resting exit, is off by default (runner.py line 323, exits.replace_enabled False; strategies_v1.py exit_replace_enabled False) and is capped at exit_replace_max_attempts = 3; native_adapter.py builds and accepts only LIMIT/DAY orders (lines 344, 354, 368)", "status": "repository source read in the round 2 fix"}, + {"source": "adaptive-paper on origin/main (unchanged in these paths since aa6fc79): transport.normalize_intent rejects any type other than 'limit', any time_in_force other than 'day' and a missing or non-positive limit_price, and allows only whole-share buys; safety.py reserves each sell intent against held quantity minus pending sells (sell_exceeds_owned_unreserved_position) and stores a limit_price per intent, so two OCO legs on tranche P would reserve 2P; native_adapter.order_report hard-codes OrderType.LIMIT and TimeInForce.DAY and parses Price.from_str(row['limit_price']), which fails on a stop-market or trailing_stop order with a null limit price; config.json uses feed 'iex' and max_order_quantity 1 (config-sip.json exists)", "status": "repository source read in the round 4 fix"} ], "population": { - "rule": "positions opened in Alpaca paper by one named mechanics-only strategy and configuration, at a fixed minimal notional, committed before the freeze. It makes no alpha claim and is exempt from edge shrink for that reason.", - "alpha_disjointness": "An H6 entry in symbol x on session s is allowed only if x had no mover-v3 D decision in sessions s-5 .. s-1, x is not an I event on s, and the entry is at or after 10:31 ET (after the I window closes, so I membership is known). A position that is still open at the close of a session on which x becomes a D event is closed at the next session's first eligible quote by a client-side exit and counted. Residual overlaps (x becoming a D event at s's close) are logged in the alpha accrual log.", + "rule": "positions opened in Alpaca paper by one named mechanics-only strategy and configuration, committed before the freeze, on a committed symbol list of large-cap stocks and ETFs, at a fixed entry quantity of 2 shares (so tranche P and tranche T each hold 1 share at a full fill). It makes no alpha claim and is exempt from edge shrink for that reason. The configuration uses feed 'sip' (as config-sip.json does), max_order_quantity >= 2 and max_order_notional_usd >= 2 x the highest price the symbol list allows; a SIP entitlement check is part of the paper smoke test. An entry whose final filled quantity is below 2 is counted per arm and excluded from H6-e in both arms, and H6-e is also reported by the arm-b order type that triggered.", + "alpha_disjointness": "Disjointness uses no mover-v3 definition, so running it computes no v3 membership on holdout-window prices. An H6 entry in symbol x on session s is allowed only if x is on the committed symbol list, the absolute raw close-to-close return of x (split-adjusted daily bars) was below 10% on every session s-5 .. s-1, the latest SIP trade before the entry decision is within 10% of the previous session's split-adjusted close, and the entry is at or after 10:31 ET. A position still open at the close of a session whose absolute raw return reached 10% is closed at the next session's first eligible quote by a client-side exit and counted. Every H6 order is logged at order time in the alpha accrual log (protocol-draft.json chronology.holdout.accrual_exposure), so any residual overlap with v3 D or I trades is identified there after the fact, when the alpha holdout is read, and counts toward its contamination cap.", "flat_symbol": "Alpaca holds one net position per symbol, so an entry in a symbol is sent only when the account is flat in that symbol and has no open order in it (for arm b, after both tranche orders are terminal). Skipped entries are counted per arm. A synthetic test covers a second entry while the first position or a tranche order is open.", "entry_order": "Both arms use the same entry: a DAY limit at limit_price(bid, ask) whose unfilled remainder is cancelled after 30 s (IOC is unverified on the adapter). The position is the filled quantity once the entry is terminal. Arm a's client-side rules are active from the first fill; arm b's tranche orders are sent once the entry is terminal, so arm b has at most 30 s plus the cancel acknowledgement without a price stop, and that interval is reported per position.", "sealed_ledger": "H6 fills and closed positions go to a sealed ledger (sha256 committed per session batch). No per-position exit price or P&L of H6 positions is displayed or computed during a stage; stage statistics are computed only through h6-access-log.jsonl entries.", @@ -50,7 +51,7 @@ "trigger_reference": { "arm_a": "stop_loss: average_price x (1 - effective stop_bps / 1e4); trailing: high_bid x (1 - effective trailing_bps / 1e4), with high_bid as of the trigger tick. Both are the rule's level, not the bid that crossed it.", "arm_b": "the order's stop_price at trigger as read from the order or its trade_updates event (for trailing_stop, the stop_price Alpaca reports, with its hwm logged).", - "common_basis": "for both arms, also the NBBO bid at each arm's trigger timestamp (arm a: the triggering tick; arm b: the trade_updates timestamp of the stop leg's trigger or fill) from the same quote stream. H6-e is computed on the per-arm reference and on this common basis; both are reported, and a pass requires both.", + "common_basis": "for both arms, also the NBBO bid at each arm's trigger timestamp (arm a: the triggering tick; arm b: the trade_updates timestamp of the stop leg's trigger or fill) from the same SIP quote stream (population.rule pins feed 'sip'; on the IEX feed this basis does not exist). H6-e is computed on the per-arm reference and on this common basis; both are reported, and a pass requires both.", "trigger_basis_check": "The round 3 review found that the cited orders-at-alpaca page does not state which price series triggers a stop or trailing_stop (last trade, bid or NBBO); this round did not reopen the page. The paper smoke test records the trigger basis; if it cannot be determined, H6-e uses the common basis only." }, "shadow": "for each position, the other arm's trigger time and reference price are computed from the same quote stream for paired diagnostics, never traded", @@ -60,7 +61,7 @@ "consequence": "arm b needs more calls than arm a by construction (3 + 3s against about 2), so the previous test 'arm b uses at least 2x fewer calls' could not pass and is withdrawn. The call counts per closed position, the realized client-closed share s and any resubmissions are reported per arm as a description only." }, "primary_tests": [ - {"id": "H6-e", "unit": "intention to treat: one closed position whose first fired exit is stop-type (arm a: the stop_loss or trailing rule fires first; arm b: the OCO stop leg or the trailing_stop triggers first). Its value is the quantity-weighted mean, over every fill that closes the shares covered by that trigger (arm a: the whole position; arm b: the triggering tranche, the other tranche being excluded from the value), of (fill price - trigger_reference) / trigger_reference in bps, whichever order closes them: the exit limit, the recovery path, a time exit or a next-session close. Positions whose first fired exit is a take-profit or a client-side rule are excluded from H6-e, counted and reported by trigger type. Arm a's stop exit is a DAY limit at bid - $0.02 that can rest unfilled; intention to treat keeps those positions at their final realized close, so the arm is not censored to filled limits.", "statistic": "median of the position values, arm b minus arm a", "alternative": "equivalence within +/-25 bps (TOST)", "p_value": "p = max(p_lower, p_upper), the larger of the two one-sided bootstrap p-values"} + {"id": "H6-e", "unit": "intention to treat: one closed position whose first fired exit is stop-type (arm a: the stop_loss or trailing rule fires first; arm b: the OCO stop leg or the trailing_stop triggers first). Its value is the quantity-weighted mean, over every fill that closes the shares covered by that trigger (arm a: the whole position; arm b: the triggering tranche, the other tranche being excluded from the value), of (fill price - trigger_reference) / trigger_reference in bps, whichever order closes them: the exit limit, the recovery path, a time exit or a next-session close. Positions whose first fired exit is a take-profit or a client-side rule are excluded from H6-e, counted and reported by trigger type. Arm a's stop exit is a DAY limit at bid - $0.02 that can rest unfilled; intention to treat keeps those positions at their final realized close, so the arm is not censored to filled limits.", "statistic": "median of the position values, arm b minus arm a", "alternative": "equivalence within +/-25 bps (TOST)", "p_value": "p = max(p_lower, p_upper), the larger of the two one-sided bootstrap p-values, as statistics.tost_p defines them"} ], "descriptive": ["H6-c: log(mean REST calls per closed position, arm a / arm b), with a bootstrap interval; no pass rule", "stop-exit fill rate and time from trigger to final close, per arm", "H6-e restricted to positions whose first stop-type exit order filled (the censored comparison), as a sensitivity", "take-profit fills: the same statistic, reported separately", "paired shadow differences per trigger type"], "also_not_supported_if": "any paper partial-fill, trigger-price or trigger-reference behaviour that departs from the documented semantics, a trigger basis that the smoke test cannot determine and that differs between arms on the common basis, or any rejection of a tranche order as exceeding the available quantity (recorded per event)", @@ -70,11 +71,11 @@ "development_screen": "unadjusted p <= 0.05 with the development sample; nominates for validation only" }, "stages": { - "order": "H6-e-eligible closed positions in close-time order, counted per arm", - "development": "first 100 eligible closed positions per arm", - "validation": "next 200 per arm", - "holdout": "next 200 per arm, read only if H6-e passed validation, with an access log entry in blueprints/us-equities/mover-v3/h6-access-log.jsonl committed before the read", - "minimum": "each stage's size is its minimum; a stage that has not reached it within population.calendar_cap is underpowered and cannot pass", + "order": "Stages are cut by session, not per arm. A stage starts at the session after the previous stage's end (the first session with an entry, for development) and ends at the close of the first session on which both arms have reached the stage minimum of H6-e-eligible closed positions. Every eligible position closed within that session range belongs to the stage for both arms, so both arms cover the same calendar period; the per-arm counts and any imbalance are reported. Positions open at a stage's end belong to the stage in which they close.", + "development": "minimum 100 eligible closed positions per arm", + "validation": "minimum 200 per arm", + "holdout": "minimum 200 per arm, read only if H6-e passed validation, with an access log entry in blueprints/us-equities/mover-v3/h6-access-log.jsonl committed before the read", + "minimum": "a stage that has not reached its minimum in both arms within population.calendar_cap is underpowered and cannot pass", "note": "prospective paper only; disjoint from every historical window and independent of the alpha protocol's stages" }, "power": { @@ -85,13 +86,22 @@ "validation_and_holdout": "n = 200 per arm, alpha 0.05 (m = 1): SE 6.3 bps, half-width 10.3 bps, passes within +/-14.7 bps, power about 0.98 at a true difference of 0", "previous_drafts": "40 / 60 / 150 per arm at 0.05/37 could not pass by construction; the round 1 draft used alpha 0.025 for a two-test family whose call-count test could not pass" }, + "statistics": { + "unit_and_resampling": "circular session block bootstrap over the stage's sessions in date order, block length 5 sessions, B = 100,000; each draw resamples sessions jointly for both arms (the stage covers the same sessions for both, stages.order) and recomputes the median of each arm's position values and their difference, arm b minus arm a", + "generator": "numpy.random.Generator(PCG64) seeded per stage with numpy.random.SeedSequence(entropy = the first 8 bytes of sha256(frozen H6 protocol id) read big-endian, spawn_key = (stage_index,)), stage_index 0 development, 1 validation, 2 holdout", + "tost_p": "with d_b the bootstrap differences in bps and margin 25 bps: p_lower = (1 + #{b : d_b <= -25}) / (B + 1) and p_upper = (1 + #{b : d_b >= +25}) / (B + 1); p = max(p_lower, p_upper), and equivalence passes when p <= 0.05, which is the percentile 90% interval lying inside +/-25 bps", + "evaluator": "the H6 evaluator is committed with synthetic tests (a known equivalent pair, a known shifted pair, the seed derivation and the session-cut stages) before any stage read" + }, "outcome_reporting": "the three labels of protocol-draft.json outcome_reporting (a stage-specific pass label; not supported with the excluded size stated; underpowered)", - "sizing": "the fixed minimal notional of the mechanics-only strategy (population.rule)", + "sizing": "the fixed entry quantity of 2 shares of the mechanics-only strategy (population.rule)", "freeze_preconditions": [ "An independent pre-outcome review from a different session and model family, findings resolved in a dated record", "The mechanics-only strategy, its configuration and notional committed, and the no-manufactured-trades decision recorded (population.open_decision)", "The paper pilot run and its exit-type mix and eligible-position rate recorded, with H6 dropped if the rate misses population.rate's bound", "Adapter paths for OCO and trailing_stop committed with synthetic tests, including a test that one share never carries two closing orders and a test that a client-side exit waits for cancel acknowledgements before its closing order", + "The same order classes carried through every layer that today accepts only DAY limits, each with synthetic tests: transport.normalize_intent (stop-market, trailing_stop and OCO intents, GTC, no limit_price where the order has none); safety.py reservations and caps priced from the stop price or trail reference, with an OCO's two legs reserving its tranche once, so the pair never raises sell_exceeds_owned_unreserved_position; and native_adapter order_report and reconciliation for non-limit, GTC and OCO-leg orders with a null limit_price, including a startup reconciliation that meets an open trailing_stop and open OCO legs", + "The mechanics-only configuration pinned to feed 'sip', with max_order_quantity >= 2, and the SIP entitlement confirmed in the paper smoke test", + "The H6 evaluator committed with its synthetic tests (statistics.evaluator)", "A paper mechanics smoke test of the tranche pair (one OCO with a stop-market leg plus one trailing_stop on split tranches of one position, GTC), labelled as measurement, with its result recorded, including the trigger price series Alpaca uses (trigger_reference.trigger_basis_check) and a tranche rejection after the stop level is crossed", "Synthetic tests for the flat-symbol rule, the 30 s entry cancel before tranche submission, the tranche-reject fallback and alpha_disjointness", "The arm-assignment ledger write tested to precede the entry submit", diff --git a/blueprints/us-equities/mover-v3/protocol-draft.json b/blueprints/us-equities/mover-v3/protocol-draft.json index bf18b18ff..757b193a3 100644 --- a/blueprints/us-equities/mover-v3/protocol-draft.json +++ b/blueprints/us-equities/mover-v3/protocol-draft.json @@ -5,7 +5,7 @@ "status": "draft_pending_independent_pre_outcome_review", "frozen_before_outcomes": false, "drafted_at": "2026-09-24", - "revised_at": "2026-09-24 (review fix round: exposure registry, H6 moved to its own engineering draft, trailing breakpoints, three-outcome reporting, leg-matched H3-c, halt-free H1-H3, H4 parent set, cost lookup pins, repin to merged #162); round 2 on the same day (review_record): directions locked after development for two-sided items, data status of H2, H4 and H5 fixed at the freeze, freeze before any quote is sampled, defined MDE and label bound, holdout breakpoint, start and count-read rules, segment-end and early-close rules, mid-quote fills, split and dividend accounting, H2 moved to gated, lineage count 1,107; round 3 on the same day (review_record): every holdout trade in the primary statistic with a contamination cap, a count-only pre-freeze access path, accepted official-print labels, a v3-owned XNYS calendar and fees file, a fixed N0 with no push discretion, H4 halt edge cases, preregistered item ids and stream spawning, extension scope, quote eligibility and fetch failures, H5 DTC scope, the lineage label rule, unadjusted-split exclusion, coverage consequences for H1 and H3, and restated membership formulas", + "revised_at": "2026-09-24 (review fix round: exposure registry, H6 moved to its own engineering draft, trailing breakpoints, three-outcome reporting, leg-matched H3-c, halt-free H1-H3, H4 parent set, cost lookup pins, repin to merged #162); round 2 on the same day (review_record): directions locked after development for two-sided items, data status of H2, H4 and H5 fixed at the freeze, freeze before any quote is sampled, defined MDE and label bound, holdout breakpoint, start and count-read rules, segment-end and early-close rules, mid-quote fills, split and dividend accounting, H2 moved to gated, lineage count 1,107; round 3 on the same day (review_record): every holdout trade in the primary statistic with a contamination cap, a count-only pre-freeze access path, accepted official-print labels, a v3-owned XNYS calendar and fees file, a fixed N0 with no push discretion, H4 halt edge cases, preregistered item ids and stream spawning, extension scope, quote eligibility and fetch failures, H5 DTC scope, the lineage label rule, unadjusted-split exclusion, coverage consequences for H1 and H3, and restated membership formulas; round 4 on the same day (review_record): study-code pin, a run log and a deviation rule (run_discipline), sealed post-freeze fetches with one re-fetch, the full pre-freeze covariate snapshot, the coverage rule over every D and I item with kept-year rules, exact MAX21, sigma_d, med20 and dollar-volume formulas, split factors from split-adjusted bars, a narrow holdout-read definition, a mechanical freeze commit and N0, the holdout collection scope from the freeze session, prospective data commitments for H2, H4 and H5, count units, the MDE bound and sigma map, the literal exclusion regex, label qualifiers, calendar amendments and a fetch-time bound", "base_commit": "aa6fc791bbd4af3499317673989d2e90a522c257", "plan": "blueprints/us-equities/mover-v3/README.md", "catalog_decisions": "catalogs/us-equities/mover-v3-sweep-20260924.json", @@ -17,7 +17,7 @@ "publication": "private scratch input, not committed: its verifier notes contain host paths. The README and the catalog supplement carry its content." }, "relation_to_prior_protocols": { - "mover-early-entry-v1-20260924": "PR #162, merged to main on 2026-09-24 as squash commit aa6fc79. 0 of 768 rule-exits pass development; the holdout was not read. Reused by reference only, pinned to main at aa6fc79 (paths under blueprints/us-equities/mover-early-entry/): rules.py (git blob 9be164c9e1a49a451d3a2381205349ed5003a993, sha256 d66156b3e57c6299f10e41a3f25c921c8be52571bd84417040190d926bf49450; it defines Bars.state, Bars.cum_dv, signal_cutoff and split_factor only: ref and gain are computed in features.py and mover_scan.py, which also fall back to a daily bar close that v3 forbids, so populations.formulas restates every membership formula exactly and does not rely on those files), sessions_io.py (git blob 0f7791a87685c73635efe94c226031d9afe7bd7c, sha256 2a753cb8f9164f48c1dceb952eb42eeae17811f636fddf5fca082731c708e59a; official_price only, with the accepted labels in populations.official_prints), quotes.py (git blob caebf6f6a72fee6bb7ae637ebb698f8c5f6b3850, sha256 4116bf2809e0952f842f0e0fe35fd8b5ab11ea295637734ffe0d719ca72ee85e; the cost-sample method including the D7 stamp-window guard: a completion is reused only for the same timestamp, and the table refuses quotes outside [ts - 60 s, ts]) evidence/cost-table-run-v1.json (hashes under cost_model.base_table) and evaluate_v2.py (git blob 2248580f4c673da3fcde25d85c6d20799ffa994e, sha256 d2007077ac7367e16061e49a645a54802fbb58c41d79d3b2da6417b0275b2fe4; only deviation D7's split and ex-dividend accounting, restated for v3 in populations.corporate_actions). The draft's earlier pin 92062e8 is reachable only from the PR branch, and its quotes.py lacks the D7 guard. v3 never modifies blueprints/us-equities/mover-early-entry/**. The pinned code is valid for 2021-2026 only: rules.EARLY_CLOSES lists 2021-2026 early closes, so rules.close_hhmm returns 16:00 on every 2016-2020 and 2027 early close, and rules.FEES (fees.json) has no SEC Section 31 row before 2020-01-01 (its 2020 start is lookup convenience, not a sourced rate), so rules.sell_fees raises KeyError on 2017-2019 dates. v3 study code therefore never calls rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return or rules.net_return_np, and never uses quotes.py cmd_sample stamping; it uses session_calendar and cost_model.fees instead.", + "mover-early-entry-v1-20260924": "PR #162, merged to main on 2026-09-24 as squash commit aa6fc79. 0 of 768 rule-exits pass development; the holdout was not read. Reused by reference only, pinned to main at aa6fc79 (paths under blueprints/us-equities/mover-early-entry/): rules.py (git blob 9be164c9e1a49a451d3a2381205349ed5003a993, sha256 d66156b3e57c6299f10e41a3f25c921c8be52571bd84417040190d926bf49450; it defines Bars.state, Bars.cum_dv, signal_cutoff and split_factor only: ref and gain are computed in features.py and mover_scan.py, which also fall back to a daily bar close that v3 forbids, so populations.formulas restates every membership formula exactly and does not rely on those files), sessions_io.py (git blob 0f7791a87685c73635efe94c226031d9afe7bd7c, sha256 2a753cb8f9164f48c1dceb952eb42eeae17811f636fddf5fca082731c708e59a; official_price only, with the accepted labels in populations.official_prints), quotes.py (git blob caebf6f6a72fee6bb7ae637ebb698f8c5f6b3850, sha256 4116bf2809e0952f842f0e0fe35fd8b5ab11ea295637734ffe0d719ca72ee85e; the cost-sample method including the D7 stamp-window guard: a completion is reused only for the same timestamp, and the table refuses quotes outside [ts - 60 s, ts]) evidence/cost-table-run-v1.json (hashes under cost_model.base_table) and evaluate_v2.py (git blob 2248580f4c673da3fcde25d85c6d20799ffa994e, sha256 d2007077ac7367e16061e49a645a54802fbb58c41d79d3b2da6417b0275b2fe4; only deviation D7's split and ex-dividend accounting, restated for v3 in populations.corporate_actions with split factors from adjustment=split bars, a stated departure). The draft's earlier pin 92062e8 is reachable only from the PR branch, and its quotes.py lacks the D7 guard. v3 never modifies blueprints/us-equities/mover-early-entry/**. The pinned code is valid for 2021-2026 only: rules.EARLY_CLOSES lists 2021-2026 early closes, so rules.close_hhmm returns 16:00 on every 2016-2020 and 2027 early close, and rules.FEES (fees.json) has no SEC Section 31 row before 2020-01-01 (its 2020 start is lookup convenience, not a sourced rate), so rules.sell_fees raises KeyError on 2017-2019 dates. v3 study code therefore never calls rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return or rules.net_return_np, and never uses quotes.py cmd_sample stamping; it uses session_calendar and cost_model.fees instead.", "mover-followup-v2-20260924": "PR #162 at aa6fc79: no pass in families E (216), F (16) or P (12); no holdout read. After deviation D7 (P exits across splits and ex-dividend dates), the v2 results sha256 is 75a5004b... (README and deviations.json at aa6fc79). Not reused.", "catalyst-experiment-v1-20260920": "Its lane timing (16:15 decision, 09:35 entry, +60 s minimum latency, 5-session and 60-minute horizons) is reused by reference, as is its rule 'Do not infer a halt from missing data'. Its catalyst-provenance/catalyst.py is not a usable H2 source as it stands: it parses only form type and acceptance headers (no Item parser, no ticker-to-CIK map), and its available_at = max(acceptance, local first observation) makes every 2016-2020 filing ineligible at its historical decision (catalyst-experiment README, 'Unknown original availability ... fails historical eligibility'). H2 is therefore gated (hypotheses.H2). Its 2021Q1 ETF control is permanently inspected and is never a holdout.", "broad-universe-mover-v1-20260921": "Main universe close >= $5 and median dollar volume >= $20M, plus a descriptive lane on the unrestricted $1 / $2M universe (protocol.json descriptive_microcap_lane; evaluation-results.json calls it a strict superset of the main universe). Both lanes computed outcomes over development 2017-2021, validation 2022-2023 and reserved 2024-01-01 to 2026-08-14. Most D-population symbol-sessions with median dollar volume >= $2M lie inside that lane, so 2017-2020 is exposed at aggregate level (exposure_registry)." @@ -33,44 +33,49 @@ {"window": ["2022-01-01", "2026-08-14"], "what": "broad-universe-mover-v1 validation 2022-2023 and reserved 2024-01-01 to 2026-08-14, both lanes"}, {"window": ["2019-12-02", "2020-04-30"], "what": "historical-simulation: six LEAN margin-stress scenarios on bundled SPY history; no mover-population outcome"}, {"window": ["2021-01-01", "2021-03-31"], "what": "catalyst-experiment 2021Q1 ETF control, permanently inspected"}, - {"window": "2020 and 2022 single-name samples", "what": "LEAN/AAPL 2020, FB/META 2022, SPY parity and corporate-action diagnostics; engine and data checks, not mover outcomes"} + {"window": "2020 and 2022 single-name samples", "what": "LEAN/AAPL 2020, FB/META 2022, SPY parity and corporate-action diagnostics; engine and data checks, not mover outcomes"}, + {"window": ["2015-01-02", "2020-12-31"], "what": "research-evaluation/plan.json (daily-etf-chronological-control-v1) development window: five-session momentum and cash outcomes on SPY, QQQ and IWM only; no single-name or mover outcome"}, + {"window": "2020-03-02 (one source day)", "what": "catalyst-dataset/native-receipt.json: EDGAR 8-K header metadata for source day 2020-03-02 (forms and item declarations, 5 headers parsed of 371 identities); filing metadata relevant to H2, with no ticker join, no price and no return"} ], "draft_author_exposure": "Before this draft the author read: #162's README results and v1 dev/val summary aggregates (per-rule means; P5/P95 of the 07:00|G0.20|V1000000 rule-exits, used only for sigma planning below); the v1 cost-table cells; the #162 review findings; the broad-universe README headlines, including the development | validation | reserved table of date-clustered mean net excess over C0 for S1-S5 at H1/H5/H20 (for example S2 H1 development -0.33%, 2017-2021, main universe) and its reserved-segment precision and recall for +10% and +20% next-day movers; the sweep. The review fix round (same day) reread broad-universe protocol.json (lane definitions), evaluation-results.json's descriptive_microcap_scope note and the same README table, and opened no descriptive-lane outcome row; the 4,563,385 event count above is a count quoted from the review finding, not a return. The second fix round (same day) read the rereview findings, broad-universe protocol.json (eligibility, window_contiguity, trial_policy, promotion), catalyst-provenance/catalyst.py, the catalyst-experiment README availability section, mover-early-entry evaluate_v2.py and deviations.json D7 (method text only), the rules.py function list and the adaptive-paper order and exit code paths (native_strategy.py, runner.py, strategies_v1.py, native_adapter.py, exits.py); it opened no outcome row, return or results file. No v3-defined statistic (a D or I outcome, a MAX, halt or short-interest tercile) has been computed or seen for any window.", "consequence": "2017-2020 is exposed at aggregate level through broad-universe development (both lanes) and the S2 headline above. v3 therefore treats development (2017-2019) and validation (2020) as screening stages only. Validation still decides which items the holdout gate opens for, but a validation pass is labelled 'screened', never confirmatory. Only the prospective holdout can support a confirmatory claim. A validation-window statistic restricted to symbol-sessions that were not a broad-universe lane decision (med20 from raw close x volume < $2M, fewer than 60 prior contiguous bars, or has_gap, all as broad-universe protocol.json defines them) is reported beside each validation result as the least-exposed slice; it is a sensitivity, not a separate test.", - "update_rule": "Every later read of data in any v3 window, before the freeze, is appended here with its date and purpose. No quote, bar or official price at a v3 entry or exit stamp is fetched before the freeze: the 2017-2019 cost sample is the first step of the frozen evaluation (cost_model.out_of_period_spreads). A pre-freeze read of development-window prices at v3 stamps is logged here and named in the freeze commit. A read of v3-defined outcomes in the validation window before the freeze voids validation; a read in the holdout window before the gate opens voids the holdout. The only permitted pre-freeze access is pre_freeze_access_path; data fetched and read only through it are not a read in this sense and do not void validation.", + "update_rule": "Every later read of data in any v3 window, before the freeze, is appended here with its date and purpose. No quote, bar or official price at a v3 entry or exit stamp is fetched before the freeze: the 2017-2019 cost sample is the first step of the frozen evaluation (cost_model.out_of_period_spreads). Such a pre-freeze read is forbidden, not tolerated. If one happens anyway it is appended here and recorded as a numbered deviation before the freeze, and it voids development screening for the affected population (D or I): no item that uses that population can be nominated at development, so each such item scores p = 1 in the validation Holm run. A read of v3-defined outcomes in the validation window before the freeze voids validation; a read in the holdout window before the gate opens voids the holdout. The only permitted pre-freeze access is pre_freeze_access_path; data fetched and read only through it are not a read in this sense and do not void validation.", "pre_freeze_access_path": { - "rule": "Before the freeze, 2016-2020 data (and the 2024-11-01 to 2025-12-31 covariate sessions of tercile_rule.holdout_breakpoints) are fetched only by committed count-only code. Raw pages go straight into a private snapshot sealed by sha256 and are never opened by a person or by any other code. The count-only code emits only counts and rates: no price, spread, return, gain, covariate value, event list or symbol-level row. Its committed output is the input to the freeze preconditions (coverage checks, corporate-action checks and the snapshot pin). The same sealed snapshot is the frozen evaluation's input.", - "outputs": "per calendar year and listing exchange: symbol-sessions with a daily bar, with a raw close >= $1, with each sessions_io.official_price source label for the open and the close (populations.official_prints), with split, reverse-split and cash-dividend records, and with a suspected unadjusted split (populations.suspected_unadjusted_split); coverage-stamp quote counts (quote_coverage_stamps)", - "quote_coverage_stamps": "Eligible-quote rates are measured only at stamps that are never v3 entry or exit stamps: 12:17 ET and 14:43 ET (12:17 and 12:43 ET on a 13:00 early close) on symbol-sessions selected by int.from_bytes(sha256('mover-v3-coverage|' + symbol + '|' + session)[:8], 'big') % 500 == 0 among all symbol-sessions with a daily bar and raw close >= $1, with no reference to event membership. The code reports, per year, the stamps with an eligible quote (populations.halts), without one, and fetch-incomplete (populations.fetch_failures), and nothing else.", - "not_allowed": "no pre-freeze fetch or read at a D or I entry stamp, an exit stamp, or a cost-sample stamp; no computation of D or I membership, MAX21, DTC or any outcome" + "rule": "Before the freeze, 2016-2020 data (and the 2024-11-01 to 2025-12-31 covariate sessions of tercile_rule.holdout_breakpoints) are fetched only by committed count-only code. Raw pages go straight into a private snapshot sealed by sha256 and are never opened by a person or by any other code. The count-only code emits only counts and rates: no price, spread, return, gain, covariate value, event list or symbol-level row. Its committed output is the input to the freeze preconditions (coverage checks, corporate-action checks and the snapshot pin). The same sealed snapshot is the frozen evaluation's input for what it contains; everything else a stage needs is fetched after the freeze under run_discipline.fetch.", + "snapshot_contents": "(1) 2016-01-04 to 2020-12-31, the whole exchange-listed universe: daily bars raw, adjustment=split and adjustment=all; official prints with their sessions_io.official_price source labels; split, reverse-split and cash-dividend records; the coverage-stamp quotes of quote_coverage_stamps. No minute bar and no other quote for 2016-2020 is fetched before the freeze. (2) 2024-11-01 to 2025-12-31, the whole exchange-listed universe, for holdout breakpoints only: daily bars raw, adjustment=split and adjustment=all; official prints with their labels; corporate-action records; 1-minute SIP bars from 04:00 ET to the scheduled close (I membership needs 04:00-10:30, D's regular-session dollar volume needs 09:30 to the close); and, only if H5 is recorded as tested, FINRA consolidated short interest with its settlement and publication dates. No quote for 2024-11-01 to 2025-12-31 is fetched. Every part is pinned by sha256 with its fetch date as asof (freeze_preconditions).", + "outputs": "per calendar year and listing exchange: symbol-sessions with a daily bar, with a raw close >= $1, with each sessions_io.official_price source label for the open and the close (populations.official_prints), with split, reverse-split and cash-dividend records, and with a suspected unadjusted split (populations.suspected_unadjusted_split); coverage-stamp quote counts (quote_coverage_stamps); for 2024-11-01 to 2025-12-31 also symbol-sessions with at least one 1-minute bar before 10:30 ET and, if fetched, short-interest records per settlement date; for H2's gate (hypotheses.H2), identity and filing-index completeness counts over the coverage-stamp sample", + "quote_coverage_stamps": "Eligible-quote rates are measured only at stamps that are never v3 entry or exit stamps: 12:17 ET and 14:43 ET (12:17 and 12:43 ET on a 13:00 early close) on symbol-sessions selected by int.from_bytes(sha256('mover-v3-coverage|' + symbol + '|' + session)[:8], 'big') % 500 == 0 among all symbol-sessions with a daily bar and raw close >= $1, with no reference to event membership. A stamp has an eligible quote if the NBBO prevailing at the stamp is eligible (populations.fill_quote) and at most 1000 ms old, or if the first eligible quote update after the stamp arrives within 60 s. The code reports, per year, the stamps with an eligible quote (populations.halts), without one, and fetch-incomplete (populations.fetch_failures), and nothing else.", + "runs": "The count-only code runs once, from a committed revision named in its output. Every run, including a failed or partial one, is appended to this registry with its revision, date and output sha256. A rerun is allowed only after a failed or incomplete run, and never after its rates were read; the first complete run's output governs coverage_rule.", + "not_allowed": "no pre-freeze fetch or read of a quote at a D or I entry stamp, an exit stamp, or a cost-sample stamp; no opening of the sealed snapshot's bars or prints by a person or by code other than the count-only code; no computation of D or I membership, MAX21, DTC or any outcome" } }, "populations": { - "D_daily": "symbol-sessions t with split-consistent official-close gain close[t]/ref(t) - 1 >= 0.20 (close[t] is the accepted official close of populations.official_prints; a session without one is not an event and is counted, with no bar fallback; ref(t) as populations.formulas states), official close >= $1.00 and completed regular-session dollar volume >= $1,000,000; decision at 16:15 ET on t; entry at the first eligible quote at or after 09:35 ET on t+1 and at or after decision + 60 s; entry timeout 300 s, else no entry (counted)", + "D_daily": "symbol-sessions t with split-consistent official-close gain close[t]/ref(t) - 1 >= 0.20 (close[t] is the accepted official close of populations.official_prints; a session without one is not an event and is counted, with no bar fallback; ref(t) as populations.formulas states), official close >= $1.00 and regular-session dollar volume DV_reg(t) >= $1,000,000 (populations.formulas); decision at 16:15 ET on t; entry at the first eligible quote at or after 09:35 ET on t+1 and at or after decision + 60 s; entry timeout 300 s, else no entry (counted)", "I_intraday": "the first minute m in 09:35-10:30 ET on a session at which gain_at_m >= 0.20, dollar_volume_at_m >= $1,000,000 and price_at_m >= $1.00 (populations.formulas); entry at the first eligible quote at or after m + 60 s; timeout 60 s", - "formulas": "Restated exactly, so membership does not depend on unpinned code. f(t) = rules.split_factor(prev_raw_c, prev_all_c, raw_c, all_c) at aa6fc79 = (raw close / all-adjusted close on the previous XNYS session) / (the same on t), set to 1 when |f - 1| <= 1e-2 or any input is missing (daily bars, raw and adjustment=all). ref(t) = the accepted official close of the previous XNYS session / f(t); if that session has no accepted official close, ref(t) is undefined and t is neither a D nor an I event (counted). There is no daily-bar or last-bar fallback (features.py and mover_scan.py at aa6fc79 fall back to the daily bar close; v3 does not). For minute m on session t: (price_at_m, dollar_volume_at_m) = rules.Bars.state(epoch of m ET) at aa6fc79, the close of the last 1-minute SIP bar starting before m and the summed vwap x volume of the session's bars from 04:00 ET starting before m; gain_at_m = price_at_m / ref(t) - 1, compared with 0.20 - 1e-9. A synthetic test fixes each formula, including a split day and a missing previous close.", + "formulas": "Restated exactly, so membership does not depend on unpinned code. f(t) = (raw close / split-adjusted close on the previous XNYS session) / (the same on t), from daily bars with adjustment=raw and adjustment=split, set to 1 when |f - 1| <= 1e-2 or any input is missing. This is rules.split_factor's arithmetic at aa6fc79 with adjustment=split closes in place of adjustment=all closes: with adjustment=all, a cash dividend of about 1% or more makes |f - 1| > 1e-2 on its ex-date, so rules.split_factor (and #162's D7 share_mult in evaluate_v2.py) books it as a share change; v3 departs from that deliberately. adjustment=all is used only for the cash remainder (populations.corporate_actions). ref(t) = the accepted official close of the previous XNYS session / f(t); if that session has no accepted official close, ref(t) is undefined and t is neither a D nor an I event (counted). There is no daily-bar or last-bar fallback (features.py and mover_scan.py at aa6fc79 fall back to the daily bar close; v3 does not). For minute m on session t: (price_at_m, dollar_volume_at_m) = rules.Bars.state(epoch of m ET) at aa6fc79, the close of the last 1-minute SIP bar starting before m and the summed vwap x volume of the session's bars from 04:00 ET starting before m; gain_at_m = price_at_m / ref(t) - 1, compared with 0.20 - 1e-9. DV_reg(t) = the summed vwap x volume of the 1-minute SIP bars of session t starting at or after 09:30 ET and before the scheduled close (session_calendar); a session with no such bar has DV_reg 0. Split-consistent official-close return of session j: r(j) = C(j) x f(j) / C(j-1) - 1, where C is the accepted official close (official_prints); r(j) is undefined if either close is missing. MAX21(t) = max over k = 1..21 of r(t-k), that is the 21 returns of sessions t-21 .. t-1 from the 22 accepted official closes of sessions t-22 .. t-1, for D and I alike (a D event's own session t is not in its window). If any of those 22 closes is missing, or any session t-21 .. t-1 is a suspected unadjusted split, MAX21 is undefined and the event is unassigned for H1 (counted, tercile_rule.minimum treatment); there is no maximum over fewer returns. Sizing window W(t) = the 20 sessions ending at t for D (its close is known at the 16:15 decision) and ending at t-1 for I. med20 = the median of DV_reg over W(t); sigma_d = the sample standard deviation (ddof = 1) of log(1 + r(j)) over the 20 sessions j of W(t). If any DV_reg or r(j) in W(t) is undefined, the trade cannot be sized and is a counted no-fill (cost_model.fix_2_impact_and_capacity). Entry bar = the last 1-minute SIP bar of the entry session (from 04:00 ET) that is complete at the fill-decision time X (it starts at or before X - 60 s); if there is none, the entry-bar cap is 0 and the trade is a counted no-fill. A synthetic test fixes each formula, including a split day, a missing previous close, a missing close inside the MAX21 window, a suspected unadjusted split inside it, a D versus I sizing window and an entry with no completed bar.", "official_prints": "Accepted source labels of sessions_io.official_price at aa6fc79 (listing exchange = the exchange of the largest condition-O opening print; no O print means no listing exchange for that session). Official open: opening_print_listing_exchange only. Official close: closing_print_listing_exchange or official_close_listing_exchange only. closing_print_other_exchange, and a session with no O print, count as no official close and are counted per year. The same rule applies to close[t], to ref(t)'s previous close, to every H3-c leg and to H4 closing-cross folds. The synthetic suite has one case per source label, including the rejected label and a session with a closing print but no opening print.", - "suspected_unadjusted_split": "Applied before membership. For session t, r = raw daily close[t] / raw daily close[previous XNYS session]. If f(t) = 1 (no adjustment) and |r - n| <= 0.01 x n or |r - 1/n| <= 0.01 / n for some integer n in 2..100, t is a suspected unadjusted split: it is not a D or I event, any trade whose hold spans t (entry session through exit session) is excluded, and any event whose MAX21 window contains t is unassigned for H1 (tercile_rule.minimum treatment). All three are counted per stage. This also removes genuine moves within 1% of an integer multiple (for example +100%); that loss is preregistered and counted, not corrected.", - "fetch_failures": "Every quote request is retried up to 3 times with exponential backoff (1 s, 4 s, 16 s). A stamp whose pages are still incomplete (quotes.py stamp_incomplete, or any HTTP error) is fetch-incomplete: the trade that needs it is excluded, not counted as a no-entry or a delayed exit, and the count is reported per item. If more than 1% of the stamps a stage requires are fetch-incomplete, that stage is void: every item scores p = 1 and is labelled underpowered there, with the reason stated.", - "session_calendar": "XNYS from exchange_calendars 4.13.2 (the version rules.py C26 used for 2021-2026), for 2016-01-01 through the end of the last possible holdout extension. The session list, with each session's scheduled open and close, is written to a v3-owned file (blueprints/us-equities/mover-v3/session-calendar.json) and pinned by sha256 at the freeze. A 'full NYSE session' or 'session' anywhere in this protocol is every scheduled XNYS trading session, early closes included; no session count excludes early closes. Synthetic tests use the 2017-07-03, 2017-11-24, 2018-11-23, 2019-12-24 and 2020-11-27 early closes.", + "suspected_unadjusted_split": "Applied before membership. For session t, r = raw daily close[t] / raw daily close[previous XNYS session]. If f(t) = 1 (no split adjustment in the adjustment=split bars, populations.formulas) and |r - n| <= 0.01 x n or |r - 1/n| <= 0.01 / n for some integer n in 2..100, t is a suspected unadjusted split: it is not a D or I event, any trade whose hold spans t (entry session through exit session) is excluded, and any event whose MAX21 window contains t is unassigned for H1 (tercile_rule.minimum treatment). All three are counted per stage. This also removes genuine moves within 1% of an integer multiple (for example +100%); that loss is preregistered and counted, not corrected.", + "fetch_failures": "Every quote request is retried up to 3 times with exponential backoff (1 s, 4 s, 16 s). A stamp whose pages are still incomplete (quotes.py stamp_incomplete, or any HTTP error) is fetch-incomplete: the trade that needs it is excluded, not counted as a no-entry or a delayed exit, and the count is reported per item. If more than 1% of the stamps a stage requires are fetch-incomplete after the single re-fetch of run_discipline.fetch, that stage is void: every item scores p = 1 and is labelled underpowered there, with the reason stated. The rate is committed before any outcome of the stage, and a void is final once that re-fetch is done; there is no further fetch attempt.", + "session_calendar": "XNYS from exchange_calendars 4.13.2 (the version rules.py C26 used for 2021-2026), for 2016-01-01 through the end of the last possible holdout extension. The session list, with each session's scheduled open and close, is written to a v3-owned file (blueprints/us-equities/mover-v3/session-calendar.json) and pinned by sha256 at the freeze. A 'full NYSE session' or 'session' anywhere in this protocol is every scheduled XNYS trading session, early closes included; no session count excludes early closes. Synthetic tests use the 2017-07-03, 2017-11-24, 2018-11-23, 2019-12-24 and 2020-11-27 early closes. Amendment: the pinned file cannot know unscheduled future closures (for example a national day of mourning). Such a closure is removed from the calendar by an append-only amendment that cites its primary source (NYSE notice) and is logged in the holdout access log before the next session. The session of the freeze and N0 keep the dates computed at the freeze from the pinned file, and if N0's date itself closes, the holdout starts at the next session. The 252-session holdout and each 63-session block count actual sessions, so their end dates move later by one session per removed closure. A synthetic test covers a removed session inside the holdout.", "session_times": "Every '15:55 ET' in this protocol means the scheduled close (session_calendar) minus 5 minutes (12:55 ET on a 13:00 early-close session). Eligible quotes are regular-session quotes, at or after 09:30 ET and before the scheduled close. An exit that finds no eligible quote before the scheduled close takes the first eligible quote of the next session. D decisions stay at 16:15 ET on early-close sessions.", "fill_quote": "For a fill at or after time X, the quote is the NBBO prevailing at X (the latest quote update stamped at or before X) if it is eligible and X minus its timestamp is at most 1000 ms; otherwise the first eligible quote update stamped after X, with fill time equal to its timestamp. Quote age is the fill-decision time minus the quote timestamp. Eligible: bid > 0 and ask > bid (locked and crossed quotes excluded), regular session (session_times). The same eligibility applies to fills and to cost-table cells (quotes.py valid_quote at aa6fc79 excludes locked and crossed quotes). Timeouts run from X. Synthetic tests cover a fresh prevailing quote, a stale one, a locked one and no quote inside the timeout.", "fill_price": "Every entry and exit fills at the mid (bid + ask) / 2 of the eligible quote that fills it, so gross returns are mid-to-mid and cost_model adds the per-side cost on top. The realized half-spread of each fill quote is recorded and reported as a sensitivity (cost = 1.25 x max(table hs', realized half-spread)).", - "corporate_actions": "For every multi-session arm (D b_lane, H3 b_overnight, the H4 parents) and each H3-c leg, deviation D7's method is restated: the exit uses the raw exit mid (or raw official print for H3-c); shares change by the product of rules.py split factors between the entry session and the exit session (a factor counts when it differs from 1 by more than 1e-2); the rest of the adjusted close-to-close return over the hold is booked as cash per original share, without spread or fees. Fees use the actual exit shares and raw exit price. H3-c legs use log((raw end print x split factor + cash) / raw start print). H5's short interest and 20-session volume are both expressed in session t's share basis with the same split factors. Study code must pass a synthetic test that crosses a split and an ex-dividend date before the freeze.", - "exclusions": "OTC; warrants, units and rights by #162's regex; symbols with fewer than 21 prior sessions of bars. No halt exclusion: H1-H3 use no halt data (see halts).", + "corporate_actions": "For every multi-session arm (D b_lane, H3 b_overnight, the H4 parents) and each H3-c leg, deviation D7's method is restated: the exit uses the raw exit mid (or raw official print for H3-c); shares change by the product of the split factors f(j) of populations.formulas (from adjustment=split bars, a factor counting when it differs from 1 by more than 1e-2) over the sessions after the entry session through the exit session; the rest of the adjustment=all close-to-close return over the hold is booked as cash per original share, without spread or fees. This departs from D7 as implemented in evaluate_v2.py at aa6fc79, whose share_mult comes from adjustment=all closes and so counts a cash dividend of about 1% or more as a share change. Fees use the actual exit shares and raw exit price. H3-c legs use log((raw end print x split factor + cash) / raw start print). H5's short interest and 20-session volume are both expressed in session t's share basis with the same split factors. Study code must pass synthetic tests before the freeze that cross a split, an ordinary ex-dividend date and a special cash dividend of 5% (booked as cash with f = 1, never as a share change, and not flagged as a suspected unadjusted split).", + "exclusions": "(1) Not exchange-listed on the session: a symbol-session is eligible only if it has a listing exchange under populations.official_prints (an exchange's condition-O opening print on that session), so OTC-only trading is excluded by as-known session data; the current Alpaca asset exchange and tradable flags (used by mover_scan.py at aa6fc79) are never used, being look-ahead and survivorship-prone for 2016-2020. (2) Warrants, units and rights, by this literal rule (candidates.py at aa6fc79; mover_scan.py's DERIV = ^[A-Z]{4}[WUR]$|\\.(WS|U|R|W) is the same set): exclude the symbol if (len(symbol) == 5 and re.search('[A-Z]{4}[WUR]$', symbol)) or re.search('\\.(WS|U|R|W)', symbol) (Python re, unanchored search), and, as mover_scan.py also does, any symbol containing '/'. (3) Symbols with fewer than 21 prior XNYS sessions with a daily bar. Each exclusion is counted per stage, with a synthetic test per rule. No halt exclusion: H1-H3 use no halt data (see halts).", "halts": "H1-H3 use no halt source, historically or prospectively, so their trade sets cannot depend on a later data purchase. As in catalyst-experiment, a halt is never inferred from missing data: an entry or exit fills only at an eligible quote (populations.fill_quote), an entry without one inside its timeout is a counted no-entry, and an exit without one waits for the next eligible quote. Limitation: a trade that sits through an unobserved halt is not flagged, and a fill at the first post-halt quote is not distinguished from a normal fill. Halt state enters only H4, as an overlay on already-defined trades.", "parameter_provenance": "G = 0.20 and V = $1M are v1's loosest gain and middle volume floor, fixed for sample size after v1's all-negative dev/val grid was seen (disclosed exposure). They are not tuned on any v3 window." }, "coverage_rule": { - "scope": "H1 and H3, which rest on 2016-2020 SIP quotes and official auction prints. Decided at the freeze from the count-only outputs (exposure_registry.pre_freeze_access_path), before any outcome, and recorded beside multiple_testing.data_status_at_freeze.", + "scope": "Every D- and I-based item, that is all of H1-H5 (H2, H4 and H5 use the same D and I trades, 2016-2020 quotes and official closes, and H4's parents are the H1/H2/H5 b_lane trades), with one set of kept years per stage shared by every item. H2, H4 and H5 also need their own data gates. Decided at the freeze from the count-only outputs (exposure_registry.pre_freeze_access_path), before any outcome, and recorded beside multiple_testing.data_status_at_freeze.", "no_fallback": "A session or symbol without an accepted official print or an eligible quote stays a counted non-event, no-entry or excluded trade under its own rule; no bar, quote or other-exchange substitute is ever used.", "year_rule": "A calendar year of 2016-2020 whose accepted official-close rate (symbol-sessions with an accepted official close / symbol-sessions with a daily bar and raw close >= $1) is below 90%, or whose eligible-quote rate at the coverage stamps is below 80%, is dropped from its stage entirely (every session of that year; for 2016, the warm-up, events of early 2017 then stay unassigned under tercile_rule.minimum until 60 prior events accrue). The thresholds are design choices, not measured values.", - "item_rule": "If fewer than 2 development years remain, or 2020 is dropped, H1 and H3 are not tested and every H1 and H3 item scores p = 1 at every stage, permanently. The same year rule applies to D and I alike.", + "item_rule": "If fewer than 2 development years remain, or 2020 is dropped, no item of H1-H5 is tested: all 35 items score p = 1 at every stage, permanently, and that is the recorded result of this protocol version. The same year rule applies to D and I alike.", + "dropped_year_consequences": "(a) Breakpoints: a dropped year's sessions are skipped, as holdout_breakpoints skips its gap; the trailing window of tercile_rule is the 252 most recent kept sessions strictly before s, and a dropped year's events never enter it. (b) Segments: each edge of a dropped year is a segment boundary under chronology.boundaries, so a trade entered before a dropped year whose planned exit falls inside it is censored on the last kept session before it, no trade enters in the dropped year, and multi-session arms give up the first 6 kept sessions after it as entry sessions. (c) Bootstrap: the stage's kept sessions are concatenated in date order and the circular block bootstrap wraps over that kept list. Each has a synthetic test with a dropped middle year.", "limitation": "The coverage stamps sample the whole $1+ universe, not movers; mover quote coverage may be lower. Per-stage no-entry and excluded counts are reported so that this is visible." }, "tercile_rule": { - "breakpoints": "For a covariate X (MAX21 in H1, DTC in H5) and a population P, the breakpoints for an event decided in session s are the 1/3 and 2/3 empirical quantiles (numpy 'linear' method) of X over all P events whose decision session lies in the 252 sessions strictly before s. Every P event counts, whether it entered, had no entry or had no fill. Every input is known at the decision, including for I, whose later same-morning triggers are never used.", - "holdout_breakpoints": "For a holdout event the trailing window is the 252 most recent sessions before s that lie outside 2026-01-02 to session N0 - 1. That gap covers #162's reserved holdout (2026-01-02 to 2026-09-18, which v3 never reads) and every session from 2026-09-21 to N0 - 1, which neither snapshot holds. The window therefore draws only on (a) the 252 sessions ending 2025-12-31 (v1/v2 validation, already exposed), whose covariates and event membership come from the pre-freeze sealed snapshot (pre_freeze_access_path; daily bars, auctions and corporate actions from 2024-11-01, for the MAX21 and ref lookback, to 2025-12-31, asof the freeze date) and are computed only after the gate opens; and (b) earlier holdout sessions, from the sealed holdout snapshot's decision-time inputs, read only after the gate opens. It reads only covariates and event membership, never an outcome.", + "breakpoints": "For a covariate X (MAX21 in H1, DTC in H5) and a population P, the breakpoints for an event decided in session s are the 1/3 and 2/3 empirical quantiles (numpy 'linear' method) of X over all P events with a defined X whose decision session lies in the 252 kept sessions strictly before s (coverage_rule.dropped_year_consequences). Every P event counts, whether it entered, had no entry or had no fill. Every input is known at the decision, including for I, whose later same-morning triggers are never used.", + "holdout_breakpoints": "For a holdout event the trailing window is the 252 most recent sessions before s that lie outside 2026-01-02 to session N0 - 1. That gap covers #162's reserved holdout (2026-01-02 to 2026-09-18, which v3 never reads) and every session from 2026-09-21 to N0 - 1, whose events never enter a breakpoint window (the sessions from the freeze session to N0 - 1 are collected only as lookback inputs, holdout_gate.prospective_collection). The window therefore draws only on (a) the 252 sessions ending 2025-12-31 (v1/v2 validation, already exposed), whose covariates and event membership come from the pre-freeze sealed snapshot (pre_freeze_access_path.snapshot_contents part 2: daily bars, official prints, corporate actions, 1-minute bars from 04:00 ET to the close and, if H5 is tested, FINRA short interest, from 2024-11-01 for the MAX21, ref and 20-session lookbacks to 2025-12-31, asof its fetch date) and are computed only after the gate opens; and (b) earlier holdout sessions, from the sealed holdout snapshot's decision-time inputs, read only after the gate opens. It reads only covariates and event membership, never an outcome. A synthetic test computes the breakpoints for the first holdout session from part (a) alone.", "assignment": "low if X <= the 1/3 breakpoint, high if X > the 2/3 breakpoint, middle otherwise (a value equal to a breakpoint goes to the lower tercile)", "h5_event_set": "For H5, P is the set of D events with a defined DTC (a FINRA figure published before the decision). D events without DTC are counted and reported per stage but are not H5 events: they are outside the H5 breakpoint set, the 60-event minimum and every H5 cell.", "minimum": "if fewer than 60 prior P events lie in the trailing window, the event is unassigned: it is counted and reported, and it enters no tercile cell or difference test. The 2016 warm-up year supplies the first window.", @@ -82,27 +87,30 @@ "validation": ["2020-01-02", "2020-12-31"], "holdout": { "kind": "prospective", - "window": "252 sessions (session_calendar) starting at session N0 = the 40th session after the session of the freeze commit. N0 is fixed by the freeze commit alone. Development and validation run by committed automation started at the freeze, and the validation results file's sha256 must be committed and pushed before N0. If it is not, the holdout of this protocol version is void for every item, whatever the reason; a new protocol version is required. There is no deferral, so the start cannot be chosen after the validation results or the live market are seen.", - "extension": "if any carried item has fewer than its minimum holdout trades after 252 sessions, extend in 63-session blocks, at most 2. The holdout window is common to every carried item: once extended, every carried item is evaluated over the extended window. A carried item still below its minimum after the second block is labelled underpowered. Each extension decision uses one 'count' read of the sealed snapshot (holdout_gate.access_log): entries and tercile, group or halted-parent assignments per carried item, never an exit or outcome price", + "window": "252 sessions (session_calendar) starting at session N0 = the 40th session after the session of the freeze. N0 is fixed by the freeze commit alone. The freeze commit is the first commit reachable from origin/main (first-parent history) in which this file's status is frozen; this repository squash-merges pull requests, so a commit that exists only on a PR branch is never the freeze commit. Its committer timestamp, converted to America/New_York, is the freeze time, and the session of the freeze is the first XNYS session (session_calendar) whose scheduled open is at or after the freeze time. D decisions made on session N0 - 1 enter on N0 and belong to the holdout (chronology.boundaries assigns by entry session). Development and validation run by committed automation started at the freeze, and the commit that adds the validation results file (with its sha256 in the run log, run_discipline) must be reachable from origin/main before 09:30 ET on session N0, measured by the GitHub-recorded time it became reachable (the pull request's merged_at, or the push event for a direct push), which the run log records. If it is not, the holdout of this protocol version is void for every item, whatever the reason; a new protocol version is required. There is no deferral, so the start cannot be chosen after the validation results or the live market are seen.", + "extension": "if any carried item has fewer than its minimum holdout trades after 252 sessions, extend in 63-session blocks, at most 2. The holdout window is common to every carried item: once extended, every carried item is evaluated over the extended window. A carried item still below its minimum after the second block is labelled underpowered. Each extension decision uses one 'count' read (holdout_gate.access_log) whose unit is count_unit, never an exit or outcome price", + "count_unit": "For each carried item, the number of trades that would enter that item's statistic at the read: events that pass membership and every exclusion, have an entry fill quote inside the entry timeout, have filled notional >= $1,000, are not fetch-incomplete, hold no suspected unadjusted split, have an assigned tercile, catalyst group or halted-parent status where the item needs one, and have every exit quote or official print the item needs present (for H3-c, all 8 official legs). Only presence and exclusion flags are evaluated: no price, mid, spread, return or exit value is computed or emitted. The same unit applies to the minimum_sample checks. A synthetic test asserts that the count output carries only integers keyed by item_ids and that the count code path calls no price or return function.", "why_prospective": "No retrospective window free of prior mover outcomes remains: 2021-2025 is v1/v2 dev/val, 2026-01-02 to 2026-09-18 is #162's reserved holdout and is event-level exposed, 2017-2026-08-14 is broad-universe exposed at aggregate level, and 2016 is warm-up.", - "accrual_exposure": "The primary holdout statistic of every item uses every holdout trade; nothing is excluded for exposure. Paper exposure is recorded only for symbol-sessions in which a paper order was submitted, logged at order time (symbol, session, order id, strategy) in the collection batch's accrual log before any outcome of that trade exists. A holdout D trade is paper-exposed if any session in [decision t, planned exit] is such a symbol-session; an I trade if its event session is. The result without paper-exposed trades is reported only as a sensitivity. If the paper-exposed fraction of any carried item's holdout trades exceeds 5%, the holdout is labelled contaminated for that item, which can then not be labelled supported (confirmatory). An ad hoc read, dashboard or other computation on holdout-window mover prices outside holdout_gate never excludes a trade: it is a holdout read without a granted entry and voids the holdout for every item (holdout_gate.access_log.rule). The engineering protocol's positions follow its population rule and are logged the same way." + "accrual_exposure": "The primary holdout statistic of every item uses every holdout trade; nothing is excluded for exposure. Paper exposure is recorded only for symbol-sessions in which a paper order was submitted, logged at order time (symbol, session, order id, strategy) in the collection batch's accrual log before any outcome of that trade exists. A holdout D trade is paper-exposed if any session in [decision t, planned exit] is such a symbol-session; an I trade if its event session is. The result without paper-exposed trades is reported only as a sensitivity. If the paper-exposed fraction of any carried item's holdout trades exceeds 5%, the holdout is labelled contaminated for that item, which can then not be labelled supported (confirmatory). A holdout read never excludes a trade; a read without a granted entry voids the holdout for every item (holdout_gate.access_log.rule). The engineering protocol's positions follow its population rule and are logged the same way.", + "holdout_read": "A holdout read is exactly this: computing, from data of sessions N0 onward, any v3-defined outcome, meaning a price at a v3 exit stamp paired with a v3 entry, or a forward return, net return or leg return of a v3 D or I event (populations, hypotheses), or any statistic built from one. Only such a computation inside a granted 'read' entry is permitted; a 'count' entry permits count_unit only. Not a read: (1) collection under holdout_gate.prospective_collection, including computing the collected batch's sha256; (2) live paper decisions and orders of any strategy, including the NautilusTrader paper runs and their decision streams, which are exposure and are logged in the accrual log; (3) dashboards and timers that publish metadata only (counts, liveness, freshness), such as the grand-dashboard checkpoint; (4) computing D or I membership without any outcome, when done by committed code whose output is logged in the accrual log (for example a paper strategy's own screen). A synthetic test classifies one example of each case." }, "labels": { "development": "disjoint from v1/v2; exposed at aggregate level through broad-universe development (both lanes, 2017-2021); screening only; survivorship-limited before 2020 (names delisted in 2016-2019 without a retained corporate action cannot be discovered)", "validation": "disjoint from v1/v2; exposed at aggregate level through broad-universe development (both lanes include 2020) and the S2 headline the author read; screening only, never confirmatory; the 2020 COVID regime is reported separately and never dropped", - "holdout": "prospective; untouched only if the validation results sha256 was pushed before N0, no read preceded a committed granted access-log entry, every collection batch has its committed accrual log, and no carried item's paper-exposed fraction exceeds 5% (accrual_exposure)" + "holdout": "prospective; untouched only if the validation results commit was reachable from origin/main before 09:30 ET on N0, no holdout read (chronology.holdout.holdout_read) preceded a committed granted access-log entry, every collection batch has its committed accrual log, and no carried item's paper-exposed fraction exceeds 5% (accrual_exposure)" }, "boundaries": "Segments are assigned by entry session. A trade whose planned exit falls after its segment's last session is censored: it exits at the first eligible quote at or after 15:55 ET on that last session, is flagged, stays in the earlier segment's means and is reported as a count. H3 b_overnight makes no entry on a segment's last session (counted), and an H3-c trade whose legs cross the segment end is excluded from H3-c and counted. For multi-session arms, the later segment gives up its first 6 sessions as entry sessions (an embargo after the censored crossing trades); same-session arms need no gap." }, "outcome_reporting": { "labels": { "passes_stage": "the item passes its stage rule, with a stage-specific name: 'nominated' at development (the screen), 'screened' at validation (Holm in the 35-item family plus minimum sample and, for a tradable cell, the robustness means), 'supported (confirmatory)' at the holdout (the same at the holdout). Only the holdout label is a claim.", - "not_supported_mde_excluded": "the stage has at least its minimum sample, the item does not pass, and the one-sided bootstrap-percentile confidence bound (from the same B draws) at Bonferroni level 0.05/35 in the item's direction excludes an effect of the preregistered MDE for the realized n (minimum_detectable_effect). For H3-c and H4 at development, where no direction is locked yet, both one-sided bounds at 0.05/70 must exclude the MDE in their own directions. The fixed Bonferroni level is used because Holm's step-down level is not defined for items after the first retained hypothesis.", + "not_supported_mde_excluded": "the stage has at least its minimum sample, the item does not pass, and the one-sided bootstrap-percentile confidence bound (from the same B draws, numpy 'linear' quantile) at Bonferroni level a = 0.05/35 excludes an effect of the preregistered MDE for the realized n (minimum_detectable_effect, with the item's sigma from minimum_detectable_effect.sigma_by_item). For an alternative 'less than 0' the bound is the lower bound, the a-quantile of the bootstrap statistics, and the MDE is excluded when it is greater than -MDE. For an alternative 'greater than 0' (every tradable cell, and H3-c or H4 locked positive) the bound is the upper bound, the (1 - a)-quantile, and the MDE is excluded when it is less than +MDE. For H3-c and H4 at development, where no direction is locked yet, a = 0.05/70 and both hold: the lower bound is greater than -MDE and the upper bound is less than +MDE. The fixed Bonferroni level is used because Holm's step-down level is not defined for items after the first retained hypothesis.", "underpowered": "the stage is below its minimum sample, or the item neither passes nor excludes the MDE" }, "hypothesis_verdict": "A hypothesis-level verdict (each not_supported_if) is computed at validation, reported as a screening verdict, and, for a hypothesis with a carried item, at the holdout, reported as the confirmatory verdict. Each uses the labels of its own stage only: 'no tradable cell passes' means no tradable cell of that hypothesis passes that stage.", "validation_reporting": "Every item's validation estimate, interval and label are computed and reported descriptively, whether or not development nominated it; only nominated items receive a Holm p, and the rest score p = 1 in the Holm run.", - "rule": "Only these three labels are used. A miss is never reported as 'falsified' or as proof of no effect; 'not supported' always states the size it excludes. A result in the direction opposite to a one-sided alternative is reported descriptively with its estimate and interval and supports no claim, because it was not tested." + "qualifiers": "Contamination and lineage status are qualifiers attached to a label, never labels of their own. A holdout item that meets the holdout pass rule while its holdout is contaminated (chronology.holdout.accrual_exposure) is labelled 'screened (contaminated holdout)', the screening-level pass name, and never 'supported (confirmatory)'. An item labelled 'supported (confirmatory)' that is not lineage-confirmed (multiple_testing.lineage_sensitivity) carries the qualifier 'lineage-unconfirmed'. A stage decided by a post-deviation rerun (run_discipline.deviations) carries 'post-deviation'.", + "rule": "Only these three labels are used, with the qualifiers above. A miss is never reported as 'falsified' or as proof of no effect; 'not supported' always states the size it excludes. A result in the direction opposite to a one-sided alternative is reported descriptively with its estimate and interval and supports no claim, because it was not tested." }, "hypotheses": { "H1": { @@ -112,7 +120,7 @@ {"source": "catalyst-experiment/protocol.json ranks by relative_volume then price_return, a MAX-like selection", "status": "repository source read"} ], "populations": ["D_daily", "I_intraday"], - "covariate": "MAX21 = the largest split-consistent daily close-to-close return over the 21 sessions ending t-1 (D) or the previous session (I), from data known at the decision; terciles by tercile_rule (trailing 252-session breakpoints of the same population)", + "covariate": "MAX21 as populations.formulas defines it: the largest of the 21 split-consistent accepted-official-close returns of sessions t-21 .. t-1, for D and I alike, undefined (event unassigned, counted) if any of the 22 closes is missing or a suspected unadjusted split lies in the window; terciles by tercile_rule (trailing 252-session breakpoints of the same population)", "exit_arms": { "a_fast": "first eligible quote at or after min(entry + 30 min, 15:55 ET)", "b_lane": "D: first eligible quote at or after 15:55 ET on the 5th holding session (entry session = 1); I: at or after min(entry + 60 min, 15:55)", @@ -144,7 +152,7 @@ ], "tradable_cells": "2 populations x 2 groups = 4", "not_supported_if": "at the verdict stage (outcome_reporting.hypothesis_verdict), H2-D and H2-I are both labelled not_supported_mde_excluded and no H2 tradable cell passes: the catalyst filter adds nothing beyond the volume filter at the detectable size.", - "data_status": "gated: cannot be computed with the repository's code as it stands. catalyst.py sets available_at = max(acceptance, local first observation), so every 2016-2020 filing observed in 2026 fails historical eligibility; it has no Item 1.01 parser and no historical ticker-to-CIK map. Before the freeze H2 needs (1) a preregistered historical availability rule, EDGAR acceptance time taken as original availability with that limitation stated, which departs from the catalyst-experiment rule and must be recorded as such; (2) a committed Item parser (for example from the header's ITEM INFORMATION lines) with synthetic tests; (3) a dated as-known ticker-to-CIK source; (4) a metadata coverage check of identity and index completeness for D and I symbol-sessions in 2016-2020. If any is missing at the freeze, H2 is recorded as not tested (multiple_testing.data_status_at_freeze).", + "data_status": "gated: cannot be computed with the repository's code as it stands. catalyst.py sets available_at = max(acceptance, local first observation), so every 2016-2020 filing observed in 2026 fails historical eligibility; it has no Item 1.01 parser and no historical ticker-to-CIK map. Before the freeze H2 needs (1) a preregistered historical availability rule, EDGAR acceptance time taken as original availability with that limitation stated, which departs from the catalyst-experiment rule and must be recorded as such; (2) a committed Item parser (for example from the header's ITEM INFORMATION lines) with synthetic tests; (3) a dated as-known ticker-to-CIK source; (4) a metadata coverage check of identity and filing-index completeness over a population that uses no membership: the coverage-stamp sample of exposure_registry.pre_freeze_access_path.quote_coverage_stamps (1 in 500 symbol-sessions with a daily bar and raw close >= $1, 2016-2020), run by the count-only code, since D and I membership may not be computed before the freeze; mover completeness may be lower, and per-stage 'identity/item unknown' counts make that visible; (5) the prospective commitment of multiple_testing.prospective_data. If any is missing at the freeze, H2 is recorded as not tested (multiple_testing.data_status_at_freeze).", "chronology": "common, if H2 is tested" }, "H3": { @@ -194,7 +202,7 @@ "minimum_halted_trades": {"development": 60, "validation": 40, "holdout": 40}, "not_supported_if": "at the verdict stage (outcome_reporting.hypothesis_verdict), H4 is labelled not_supported_mde_excluded. A stage below its minimum halted trades is underpowered, not a miss; either way the halt state stays a safety rule only.", "data_status": "gated: Databento XNAS.ITCH status schema (paid, metered; covers halt and resume state for every security traded on the Nasdaq execution system, with the P pause state and J auction collar for Nasdaq-listed securities only; cost and coverage start unverified; *contested*), or Nasdaq Trader halt history (free, limited lookback, pilot only). Whether SIP conditions can rebuild halts is unverified.", - "chronology": "common, restricted to the halt source's covered sessions as established by metadata before any outcome. A Nasdaq Trader pilot is exploratory and cannot pass. If the covered development span is shorter than 1 year, or the data are not in hand at the freeze, H4 is not tested and scores p = 1 at every stage, permanently (multiple_testing.data_status_at_freeze). The halt source is used only for H4 and never changes the H1-H3 trade sets." + "chronology": "common, restricted to the halt source's covered sessions as established by metadata before any outcome. A Nasdaq Trader pilot is exploratory and cannot pass. If the covered development span is shorter than 1 year, or the data are not in hand at the freeze, or no prospective_data entry covers its holdout (multiple_testing.prospective_data), H4 is not tested and scores p = 1 at every stage, permanently (multiple_testing.data_status_at_freeze). The halt source is used only for H4 and never changes the H1-H3 trade sets." }, "H5": { "name": "Days-to-cover crowding overlay", @@ -214,7 +222,7 @@ "tradable_cells": "3 DTC terciles", "not_supported_if": "at the verdict stage (outcome_reporting.hypothesis_verdict), H5-p (or H5-s, reported separately) is labelled not_supported_mde_excluded and no H5 tradable cell passes.", "data_status": "gated on FINRA short-interest access and terms, which the user must establish; bi-monthly, an overlay only", - "chronology": "common, restricted to sessions whose decision has a published figure. Published history must cover the 2016 warm-up (tercile_rule needs 60 prior D events), at least 1 development year and the whole validation year. If it does not, or the data are not in hand at the freeze, H5 is not tested and both tests score p = 1 at every stage, permanently (multiple_testing.data_status_at_freeze)." + "chronology": "common, restricted to sessions whose decision has a published figure. Published history must cover the 2016 warm-up (tercile_rule needs 60 prior D events), at least 1 development year and the whole validation year. If it does not, or the data are not in hand at the freeze, or no prospective_data entry covers its holdout (multiple_testing.prospective_data), H5 is not tested and both tests score p = 1 at every stage, permanently (multiple_testing.data_status_at_freeze)." }, "H6": { "name": "Execution parity: broker-held exits versus client-side exits (engineering, not alpha)", @@ -229,10 +237,11 @@ "item_id_rule": "Tradable cell ids are hypothesis-population-arm-group (H3-a and H3-b are both primary tests and tradable cells). item_ids is the complete list, in bytewise ASCII order, which is the order used for stream spawning, for the final tie-break and for the validation results file.", "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout. An item not carried to a stage, or not tested for lack of data, scores p = 1 there. The validation results file, when committed, contains all 35 validation p-values keyed by item_ids (1 for items not carried or not tested); an incomplete file is refused by the evaluator.", "direction_lock": "H3-c and H4 are two-sided only at development: the development screen uses the two-sided p = 2 x min(one-sided p), and the sign of the development estimate is recorded in the development results file. Validation and the holdout then use the one-sided test in that recorded direction, in the same Holm slot. A significant result in the opposite direction at validation or the holdout is reported descriptively and cannot pass.", - "data_status_at_freeze": "At the freeze each of H1 and H3 (coverage_rule) and each of H2, H4 and H5 is recorded as either 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently'. Data acquired after the freeze can support only a new protocol version with its own family; it never enters this family's Holm run, so a late small p cannot loosen other items' thresholds.", + "data_status_at_freeze": "At the freeze each of H1 and H3 (coverage_rule) and each of H2, H4 and H5 is recorded as either 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently'. Data from a new source, or a new historical range, acquired after the freeze can support only a new protocol version with its own family; it never enters this family's Holm run, so a late small p cannot loosen other items' thresholds. The prospective holdout data of a tested item under its committed prospective_data entry are not new data in this sense.", + "prospective_data": "At the freeze, each tested item whose holdout needs data beyond the Alpaca SIP collection (H2: EDGAR filings and the ticker-to-CIK source; H4: the halt source and reopening-print source; H5: FINRA short interest) records a committed prospective source, licence and budget covering the full holdout plus both 63-session extension blocks; an item without one is recorded as not tested. The purchase or collection follows that record whatever validation shows. A carried item whose holdout data do not arrive, in full or in part, is labelled underpowered at the holdout with the reason stated, and is never dropped silently or replaced.", "strictest_level": 0.0014286, "development_screen": "unadjusted p <= 0.05 with the minimum sample: one-sided in the stated direction for directional items, two-sided for H3-c and H4 (direction_lock); development only nominates items for validation", - "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) and broad-universe-mover-v1's 60 predeclared signal variants (5 signals x 3 horizons x 2 selections x 2 instrument lanes) on overlapping or other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 60 + 35) = 4.52e-5. broad-universe's 2 controls and its descriptive lane are not counted: they are base-rate reports with no pass rule (its promotion 'never'); that exclusion is a stated limitation. Each p is reported beside a normal-tail p from the same bootstrap standard error. With B = 100,000 a bootstrap p at 4.52e-5 means at most 3 null-side draws, so the bootstrap p alone cannot resolve this level (its Monte Carlo error is about the size of the threshold). An item is lineage-confirmed only if both its bootstrap p and its normal-tail p are at or below 4.52e-5; an item that passes Holm but not both is labelled v3-family significant, lineage-unconfirmed.", + "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) and broad-universe-mover-v1's 60 predeclared signal variants (5 signals x 3 horizons x 2 selections x 2 instrument lanes) on overlapping or other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 60 + 35) = 4.52e-5. broad-universe's 2 controls and its descriptive lane are not counted: they are base-rate reports with no pass rule (its promotion 'never'); that exclusion is a stated limitation. Each p is reported beside a normal-tail p from the same bootstrap standard error. With B = 100,000 a bootstrap p at 4.52e-5 means at most 3 null-side draws, so the bootstrap p alone cannot resolve this level (its Monte Carlo error is about the size of the threshold). An item is lineage-confirmed only if both its bootstrap p and its normal-tail p are at or below 4.52e-5; an item that passes Holm but not both keeps its stage label with the qualifier 'lineage-unconfirmed' (outcome_reporting.qualifiers).", "tie_breaks": "Where items tie on a p (for example at the floor 1 / (B + 1)), in the holdout cap of 5 tradable cells or in Holm ordering, ties are broken by the normal-tail p, then by item id in bytewise ASCII order (item_ids).", "tests_not_in_family": "BH (q = 0.10) diagnostics across tercile, year, price-tier and halt-covariate splits are reported but can never open the holdout" }, @@ -240,7 +249,7 @@ "tradable_cell": {"development": 300, "validation": 150, "holdout": 150}, "difference_test_per_group": {"development": 150, "validation": 100, "holdout": 100}, "one_sample_paired": "H3-c uses the tradable_cell minima (trades with all 8 legs); H4 uses its minimum_halted_trades", - "rule": "A stage below its minimum reports the item as underpowered (outcome_reporting); it cannot pass that stage." + "rule": "A stage below its minimum reports the item as underpowered (outcome_reporting); it cannot pass that stage. Minima are counted in chronology.holdout.count_unit (trades that enter the item's statistic after every exclusion), at every stage." }, "minimum_detectable_effect": { "formula": "MDE = (z_alpha + z(0.80)) x sigma x sqrt(DEFF) x sqrt(1/n) for one cell or a one-sample paired statistic (H3-c, H4); x sqrt(1/n1 + 1/n2) in place of sqrt(1/n) for a two-group difference (so sqrt(2/n) for equal groups; realized trailing terciles are not exactly 1/3 each, and H5-s pools two terciles). z_alpha = z(1 - 0.05/35) = 2.983 for one-sided tests, including H3-c and H4 after development (direction_lock), and z(1 - 0.05/70) = 3.189 for H3-c and H4 at development. The MDE is computed from the planning sigma below and the realized n; it is never re-estimated from outcomes.", @@ -255,6 +264,20 @@ "leg_difference_H3_c": 0.10, "halt_difference_H4": 0.35 }, + "sigma_by_item": { + "rule": "Each of the 35 item_ids takes the sigma of its arm and the MDE form of its statistic: 'cell' uses sqrt(1/n), 'difference' uses sqrt(1/n1 + 1/n2), 'paired' is a one-sample paired statistic with sqrt(1/n). I b_lane (min(entry + 60 min, 15:55)) is a 30-to-60-minute arm; D b_lane is a 5-session arm; H3-a (09:35 to 15:55) is a same-session close arm.", + "H1-D": [0.35, "difference"], "H1-D-a_fast-high": [0.10, "cell"], "H1-D-a_fast-low": [0.10, "cell"], "H1-D-a_fast-middle": [0.10, "cell"], + "H1-D-b_lane-high": [0.35, "cell"], "H1-D-b_lane-low": [0.35, "cell"], "H1-D-b_lane-middle": [0.35, "cell"], + "H1-D-c_session-high": [0.25, "cell"], "H1-D-c_session-low": [0.25, "cell"], "H1-D-c_session-middle": [0.25, "cell"], + "H1-I": [0.10, "difference"], "H1-I-a_fast-high": [0.10, "cell"], "H1-I-a_fast-low": [0.10, "cell"], "H1-I-a_fast-middle": [0.10, "cell"], + "H1-I-b_lane-high": [0.10, "cell"], "H1-I-b_lane-low": [0.10, "cell"], "H1-I-b_lane-middle": [0.10, "cell"], + "H1-I-c_session-high": [0.25, "cell"], "H1-I-c_session-low": [0.25, "cell"], "H1-I-c_session-middle": [0.25, "cell"], + "H2-D": [0.35, "difference"], "H2-D-b_lane-attention": [0.35, "cell"], "H2-D-b_lane-catalyst": [0.35, "cell"], + "H2-I": [0.10, "difference"], "H2-I-b_lane-attention": [0.10, "cell"], "H2-I-b_lane-catalyst": [0.10, "cell"], + "H3-a": [0.25, "cell"], "H3-b": [0.15, "cell"], "H3-c": [0.10, "paired"], "H4": [0.35, "paired"], + "H5-D-b_lane-high": [0.35, "cell"], "H5-D-b_lane-low": [0.35, "cell"], "H5-D-b_lane-middle": [0.35, "cell"], + "H5-p": [0.35, "difference"], "H5-s": [0.35, "difference"] + }, "sigma_provenance": "0.10 and 0.25 are (P95 - P5) / 3.29 from #162 v1 development aggregates (07:00|G0.20|V1000000 X2 and X1: P5 -15.0% / P95 +18.0%, and P5 -42.1% / P95 +39.5%); normal approximation, which understates fat tails. Those rule-exits entered at 07:00 ET in the pre-market, so their spread for 09:35 entries is not measured. 0.35, 0.15 (one 15:55-to-09:30 leg), 0.10 (the difference of two 4-leg means of log returns) and 0.35 for H4 (dominated by the remaining b_ignore path) are assumptions with no measured source. DEFF 1.5 for session clustering is an assumption.", "mde_percent_single_cell_one_sided": { "n40": {"sigma_0.10": 7.41, "sigma_0.15": 11.11, "sigma_0.25": 18.51, "sigma_0.35": 25.92}, @@ -306,8 +329,8 @@ }, "fix_2_impact_and_capacity": { "finding": "#162 review item costs-F4: pass statistics use a fixed $20k notional with no impact term and assume full fills", - "notional": "filled notional = min($20,000; 1% of the 20-session median dollar volume ending before the decision; 10% of the entry bar's dollar volume, or the opening auction's). The excess is not filled. A trade with filled notional under $1,000 is a no-fill and is counted.", - "impact": "impact per side (return) = c x sigma_d x sqrt(q), where q = filled notional / 20-session median dollar volume and sigma_d = 20-session standard deviation of daily log returns, both known at the decision. Primary c = 1.0; sensitivities c = 0.5 and 2.0.", + "notional": "filled notional = min($20,000; 1% of med20; 10% of the entry bar's dollar volume, vwap x volume), with med20 and the entry bar (the last 1-minute bar complete at the fill-decision time, so it never includes volume after the fill) as populations.formulas defines them. No auction volume is used: every fill is at a quote mid (populations.fill_price). The excess is not filled. A trade with filled notional under $1,000, or one that cannot be sized, is a no-fill and is counted.", + "impact": "impact per side (return) = c x sigma_d x sqrt(q), where q = filled notional / med20 and sigma_d is populations.formulas' standard deviation of split-consistent official-close log returns over the sizing window W(t), both known at the decision. Primary c = 1.0; sensitivities c = 0.5 and 2.0.", "provenance": "The square-root functional form and the c grid are design choices bracketing the unknown; the sweep supplies no source for them." }, "out_of_period_spreads": { @@ -322,7 +345,7 @@ }, "statistics": { "estimator": "trade-level mean net return; also median, 1%-winsorised mean, mean without the top 5 sessions and mean of per-session means", - "bootstrap": "circular session block bootstrap, B = 100,000; block length 5 sessions for same-session arms and 10 sessions for multi-session arms and for H4 (whose parents mix both); difference tests resample sessions jointly for both groups; generator numpy.random.Generator(PCG64), seeded per stage: stage_index 0 development, 1 validation, 2 holdout; each stage uses numpy.random.SeedSequence(entropy = the first 8 bytes of sha256(frozen protocol id) read big-endian, spawn_key = (stage_index,)).spawn(35), and child i drives item_ids[i] (multiple_testing.item_ids order), so no stage reuses another stage's stream", + "bootstrap": "circular session block bootstrap, B = 100,000, over the stage's kept sessions concatenated in date order (coverage_rule.dropped_year_consequences); block length 5 sessions for same-session arms and 10 sessions for multi-session arms and for H4 (whose parents mix both); difference tests resample sessions jointly for both groups; generator numpy.random.Generator(PCG64), seeded per stage: stage_index 0 development, 1 validation, 2 holdout; each stage uses numpy.random.SeedSequence(entropy = the first 8 bytes of sha256(frozen protocol id) read big-endian, spawn_key = (stage_index,)).spawn(35), and child i drives item_ids[i] (multiple_testing.item_ids order), so no stage reuses another stage's stream", "p_value": "one-sided p = (1 + #{b : stat_b on the null side of 0}) / (B + 1); two-sided p = min(1, 2 x the smaller one-sided p)", "robustness_required_for_a_pass": "a tradable cell's validation and holdout passes also need a positive winsorised mean, mean without the top 5 sessions and mean of per-session means", "sensitivities": ["two-way (session, symbol) clustering", "without split-day rows (v3 has no fallback-price rows: populations.formulas and official_prints)", "2020 reported alone", "survivorship-limited label before 2020", "validation restricted to symbol-sessions that were not a broad-universe lane decision (exposure_registry.consequence)", "cost from the realized half-spread of each fill quote (populations.fill_price)", "censored segment-end trades removed (chronology.boundaries)", "holdout without paper-exposed trades (chronology.holdout.accrual_exposure)"] @@ -331,40 +354,52 @@ "opens_only_for": "items that passed validation: a primary test or tradable cell with Holm-adjusted validation p <= 0.05 in the 35-item family, the minimum validation sample and, for a tradable cell, the robustness means. At most 5 tradable cells, ranked by raw (unadjusted) validation bootstrap p with multiple_testing.tie_breaks, and every validated primary test.", "h4_scope": "a validated H4 authorizes computing, in the holdout, the a_reopen and b_ignore exits of halted parent trades only (H4 parent_trades), whether or not any parent cell validated. It authorizes no non-halted parent outcome and no tradable-cell statistic.", "no_pass": "If nothing validates, the holdout is never labelled or read, and that is the result.", - "prospective_collection": "Holdout-window data are collected forward into a sealed private snapshot whose sha256 is committed per collection batch. No outcome label, forward return or exit is computed from it before the gate opens.", + "prospective_collection": "Collected forward from the session of the freeze (chronology.holdout.window) by the frozen collector, so that the first holdout sessions have their 22-session MAX21, 20-session sizing and ref lookbacks and D decisions on N0 - 1 have their inputs. Each collection batch holds, for the whole exchange-listed universe and only these decision-time inputs: daily bars raw, adjustment=split and adjustment=all; 1-minute SIP bars from 04:00 ET to the scheduled close; official prints with their sessions_io.official_price labels; corporate-action records. Batches are sealed into a private snapshot whose sha256 is committed per batch with its accrual log. Quotes are not collected forward and nothing selects symbols by membership: the entry, exit and fill quotes of holdout trades, and any H2, H4 or H5 source a carried item needs (multiple_testing.prospective_data), are fetched only under a granted 'count' or 'read' access-log entry, by the frozen fetch code (run_discipline.fetch), with the fetch date as asof, and sealed before use. Sessions from the freeze to N0 - 1 serve only as lookback inputs. No outcome label, forward return or exit is computed from any batch before a granted 'read' entry. The collector and its synthetic tests (including a first-holdout-session test that needs the pre-N0 lookback) are freeze preconditions.", "evaluator_refusals": [ "protocol status is not frozen, or its sha256 differs from the frozen commit", - "the validation results file is missing, its sha256 differs from the one committed, or that commit is not earlier than the first holdout session", + "the validation results file is missing, its sha256 differs from the one committed, or its commit did not become reachable from origin/main before 09:30 ET on N0 (chronology.holdout.window)", + "the validation results file is not the governing validation run of run_discipline: its run-log entry is missing, or names a study-code revision or tree other than the frozen protocol's study_code pin (or a rerun that run_discipline.deviations does not let govern)", "the list of validated items is empty", "a requested item is not in that list, or a requested H4 read asks for more than h4_scope", "the access log has no committed 'granted' entry for this run", - "any collection batch lacks its committed accrual log (chronology.holdout.accrual_exposure), or a batch's log was committed after the batch's last session's outcomes could exist", + "any collection batch lacks its committed accrual log (chronology.holdout.accrual_exposure), or a batch's log became reachable from origin/main (GitHub-recorded time, as in chronology.holdout.window) at or after 09:30 ET of the session after the batch's last session", "a previous holdout run exists (the holdout is read once; failed or partial runs stay in the log and are never called untouched). A granted 'count' entry is not a holdout run", - "a 'count' request asks for anything beyond entry and assignment counts per carried item, or a 'count' read is requested other than once at the end of the 252 sessions and once at the end of each extension block" + "a 'count' request asks for anything beyond chronology.holdout.count_unit per carried item, or a 'count' read is requested other than once at the end of the 252 sessions and once at the end of each extension block" ], "access_log": { "path": "blueprints/us-equities/mover-v3/holdout-access-log.jsonl", "mode": "append-only JSON lines, committed and pushed before the read it authorizes", - "fields": ["utc", "actor_role", "protocol_id", "protocol_sha256", "code_revision", "validation_results_sha256", "requested_items", "purpose", "decision (granted or refused)", "snapshot_sha256", "rows_read", "results_sha256", "exposed_symbol_sessions"], - "purposes": {"collect": "a collection batch, rows_read 0, listing that batch's accrual exposure", "count": "one at the end of the 252 sessions and one at the end of each extension block: entry and tercile, catalyst-group or halted-parent assignment counts per carried item, never an exit or outcome price; used only for the extension decision", "read": "the single outcome read for carried items"}, + "fields": ["utc", "actor_role", "protocol_id", "protocol_sha256", "code_revision", "study_code_tree", "validation_results_sha256", "requested_items", "purpose", "decision (granted or refused)", "snapshot_sha256", "rows_read", "results_sha256", "exposed_symbol_sessions"], + "purposes": {"collect": "a collection batch, rows_read 0, listing that batch's accrual exposure", "count": "one at the end of the 252 sessions and one at the end of each extension block: chronology.holdout.count_unit per carried item, never an exit or outcome price; it may fetch the quotes and sources it needs under run_discipline.fetch; used only for the extension decision", "read": "the single outcome read for carried items"}, "rule": "Any holdout read without a preceding committed granted entry of the matching purpose voids the holdout for every item. Refusals are logged too. The 'count' path is tested in the synthetic suite, including a test that it computes no exit price." } }, + "run_discipline": { + "study_code": "The frozen protocol records study_code = {revision: the git commit of the study code, tree: the git tree hash of blueprints/us-equities/mover-v3/study/ at that commit}, plus the sha256 of the collector and fetch code it contains. Every run, fetch and evaluator call names the revision and tree it ran from, and the evaluator refuses to run from any other tree unless run_discipline.deviations allows it.", + "run_log": "blueprints/us-equities/mover-v3/run-log.jsonl, append-only JSON lines. Every run of the frozen automation (cost sample, each stage's fetch and re-fetch, development, validation, each holdout 'count' and 'read'), including failed and partial runs, gets one line with utc start and end, stage, purpose, study_code revision and tree, input snapshot sha256s, status (complete, failed or partial), the sha256 of every results file it wrote (partial files included) and, for the validation results commit, the GitHub-recorded time it became reachable from origin/main. Each line is committed and pushed before the next run of any stage starts.", + "once": "Development is computed by one logged run and validation by one logged run. The first run of a stage that writes any outcome is that stage's governing run, whether or not it completes: the governing development run nominates, and the governing validation run's results file governs the holdout gate.", + "deviations": "Any change to the study code, the fetch code or a stage's inputs after the first outcome of any stage is a numbered deviation in blueprints/us-equities/mover-v3/deviations.json (cause, diff reference, affected stages), committed and pushed before any rerun. A rerun under a deviation is reported beside the governing run and never replaces it, with one exception: if the governing run is incomplete because of an exception or an infrastructure failure, one rerun under a deviation that changes no rule may govern, labelled 'post-deviation', provided it reproduces byte for byte every outcome the incomplete run had written; otherwise that stage is decided by the incomplete run's written outcomes alone if it is development (unwritten items score p = 1), and the holdout of this protocol version is void if it is validation. A deviation committed after any validation outcome exists never changes which items the holdout gate opens for.", + "fetch": "Before any outcome of a stage (development, validation, the cost sample, and each holdout 'count' or 'read'), the frozen automation fetches every quote window, minute bar and official print that stage needs, deriving stamps only from decision-time inputs and quote timestamps, seals the result as a sha256-pinned private snapshot and commits that sha256 and the stage's fetch-incomplete rate (populations.fetch_failures) in the run log. At most one re-fetch is allowed, preregistered here: once, of only the fetch-incomplete stamps, before any outcome of that stage; its sealed result and new rate are committed the same way. After it the 1% void rule is final. Development and validation data fetched after the freeze are used only through these sealed snapshots, and no outcome is computed from unsealed data.", + "synthetic_tests": "the evaluator refuses a results file from an unpinned revision or tree, a stage with a second outcome-bearing run that no deviation covers, and an outcome computed before the stage's fetch rate is committed" + }, "leverage_and_exits": "Research runs use 1x equal notional. Paper use of any passing rule follows the README's exit and leverage policy: at most one broker-held closing order per share (a trailing_stop or an OCO per quantity tranche, as the Alpaca docs require), the leverage-schedule-v1-20260922 ceiling, and the leverage-ladder-1x/2x/4x gate rows in catalogs/us-equities/gates-20260922.json, one rung at a time with automatic step-down. The edge-shrink (fractional Kelly) term stays 0 until a holdout pass.", "freeze_preconditions": [ "An independent pre-outcome review of this draft from a different session and model family, with every finding resolved in a dated record", "Direct full-text reads of Lou-Polk-Skouras 2019, Akbas-Boehmer-Jiang-Koch 2022 and MacLean-Thorp-Ziemba 2010, or removal of every claim that depends on them (the two JFE DOIs resolve in Crossref; that is metadata only)", "The 2017-2019 development cost sample fixed by rule (not measured: it is the first step after the freeze); fees-v3.json committed with sourced 2017-2020 SEC and TAF rows and pinned (cost_model.fees); session-calendar.json generated from exchange_calendars 4.13.2 XNYS and pinned (populations.session_calendar)", - "Metadata-only coverage checks through exposure_registry.pre_freeze_access_path only, no outcome computed: Alpaca SIP eligible-quote rates at the coverage stamps and accepted official-print rates per label, for 2016-2020, with coverage_rule applied (H1-H3); forward-split, reverse-split and cash-dividend record counts per year for 2016-2020 and the count of suspected unadjusted splits (populations.suspected_unadjusted_split); the Databento get_cost quote, XNAS.ITCH coverage start and a reopening-cross print source (H4); FINRA short-interest history start, including the 2016 warm-up, and terms (H5); identity and filing-index completeness for H2's gates", - "The data status of H1 and H3 (coverage_rule) and of H2, H4 and H5 recorded as 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently' (multiple_testing.data_status_at_freeze)", - "The universe and daily-dataset snapshot pinned by sha256 with its asof date and asof policy", - "Study code implementing this protocol committed with synthetic tests before any outcome, including a split and ex-dividend crossing, a suspected unadjusted split, each official-print source label, the fill-quote cases, 2016-2020 early-close sessions from session_calendar, a 2017 fee lookup, segment-end censoring, the H4 edge cases, the direction lock and the evaluator's holdout refusals (including a missing accrual log) and 'count' path. The code imports none of rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return and rules.net_return_np, and a test asserts this", + "Metadata-only coverage checks through exposure_registry.pre_freeze_access_path only, no outcome computed: Alpaca SIP eligible-quote rates at the coverage stamps and accepted official-print rates per label, for 2016-2020, with coverage_rule applied to every D- and I-based item (H1-H5); forward-split, reverse-split and cash-dividend record counts per year for 2016-2020 and the count of suspected unadjusted splits (populations.suspected_unadjusted_split); the Databento get_cost quote, XNAS.ITCH coverage start and a reopening-cross print source (H4); FINRA short-interest history start, including the 2016 warm-up, and terms (H5); identity and filing-index completeness over the coverage-stamp sample for H2's gates (hypotheses.H2)", + "The data status of H1-H5 under coverage_rule, and of H2, H4 and H5 under their own gates and multiple_testing.prospective_data (a committed prospective source, licence and budget for the full holdout plus extensions), recorded as 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently' (multiple_testing.data_status_at_freeze)", + "The Alpaca market-data plan's documented historical-data rate limit recorded with its source, and an upper-bound fetch-time estimate for development and validation (symbol-sessions from the count-only outputs x the quote-window and minute-bar requests per symbol-session, at that limit) that leaves a margin of at least 50% under the 40 sessions before N0; if it does not, the protocol is not frozen as written. The 200/min figure elsewhere in this plan is the trading API, and quotes.py's 30 requests/s default is not an entitlement", + "The frozen collector (holdout_gate.prospective_collection) and fetch code (run_discipline.fetch) committed with synthetic tests, including the first holdout session's lookback", + "The pre-freeze sealed snapshot (exposure_registry.pre_freeze_access_path.snapshot_contents, parts 1 and 2) pinned by sha256 with its fetch date as asof and its asof policy", + "Study code implementing this protocol committed with synthetic tests before any outcome, including a split, ex-dividend and 5% special-dividend crossing, every populations.formulas case, the exclusion rules, a suspected unadjusted split, a dropped middle year (coverage_rule.dropped_year_consequences), the holdout-read classification, a calendar amendment, the run_discipline refusals, the count_unit output, each official-print source label, the fill-quote cases, 2016-2020 early-close sessions from session_calendar, a 2017 fee lookup, segment-end censoring, the H4 edge cases, the direction lock and the evaluator's holdout refusals (including a missing accrual log) and 'count' path. The code imports none of rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return and rules.net_return_np, and a test asserts this", "The exposure registry updated with everything the author saw since this draft", - "status set to frozen, frozen_before_outcomes true, and the protocol sha256 recorded in a pushed commit" + "status set to frozen, frozen_before_outcomes true, run_discipline.study_code filled in, and the protocol sha256 recorded in a commit that becomes reachable from origin/main (chronology.holdout.window defines the freeze commit)" ], "review_record": [ {"date": "2026-09-24", "round": 2, "input": "two independent pre-outcome rereviews of commit 0b924e7 (findings S1-S14 and F1-F13)", "resolution": "S1 direction_lock; S2 data_status_at_freeze and complete validation file; S3 and F12 cost sample moved after the freeze; S4 sigma entries, label bound, unequal-group formula, H3-c minimum and leg rule, two-sided table replaced; S5 holdout_breakpoints; S6 'count' purpose; S7 and F6 boundaries, session_times and H3-b timeout; S8 stage labels and hypothesis_verdict; S9 README H2/H5 rows; S10 fixed holdout start and accrual keys; S11 tie_breaks, H4 block length, PCG64 streams; S12 C0 as superset and lane-decision slice; S13 lineage 1,107; S14 official-close rule, I window, breakpoints over all events; F3 H2 gated; F4 corporate_actions and evaluate_v2.py pin; F5 fill_price and coverage preconditions; F9 accrual keys and disjoint H6 symbols; F10 reopen print source and halt cost; F11 README rate-limit text; F13 preconditions. F1, F2, F7 and F8 are resolved in the H6 draft (H6 gated, H6-c descriptive). No finding rejected."}, - {"date": "2026-09-24", "round": 3, "input": "rereview of commit 9f4b9f2 (findings S-1 to S-13 and X1 to X11), each checked against this draft and the named code at aa6fc79 or origin/main", "resolution": "S-1 accrual_exposure (every trade in the primary statistic, paper-order exposure logged at order time, 5% contamination cap, ad hoc reads void rather than exclude, missing-accrual-log refusal); S-2 exposure_registry.pre_freeze_access_path with count-only outputs and non-v3 coverage stamps; S-3 populations.official_prints (closing_print_other_exchange rejected); S-4 and X4 populations.session_calendar, v3-owned fees-v3.json, the pinned rules.py fee and close functions barred; S-5 N0 fixed, validation by committed automation, a late push voids the holdout; S-6 H4 population and edge_cases; S-7 holdout_breakpoints excludes 2026-01-02 to N0 - 1 and names the 2024-11 to 2025-12 covariate source; S-8 multiple_testing.item_ids and per-stage SeedSequence spawning; S-9 common extended window, underpowered after 2 blocks, cap by raw p; S-10 and X10 populations.fill_quote (one eligibility rule for fills and cost cells), fetch_failures and the H3-a timeout and overlap note; S-11 tercile_rule.h5_event_set; S-12 lineage label needs both bootstrap and normal-tail p; S-13 README precedence corrected to exits.py; X3 populations.suspected_unadjusted_split and the corporate-action coverage precondition; X5 coverage_rule, with H1 and H3 data status now fixed at the freeze; X6 populations.formulas restated and sessions_io.py pinned. X1, X2, X7, X8, X9 and X11 are resolved in the H6 draft. No finding rejected."} + {"date": "2026-09-24", "round": 3, "input": "rereview of commit 9f4b9f2 (findings S-1 to S-13 and X1 to X11), each checked against this draft and the named code at aa6fc79 or origin/main", "resolution": "S-1 accrual_exposure (every trade in the primary statistic, paper-order exposure logged at order time, 5% contamination cap, ad hoc reads void rather than exclude, missing-accrual-log refusal); S-2 exposure_registry.pre_freeze_access_path with count-only outputs and non-v3 coverage stamps; S-3 populations.official_prints (closing_print_other_exchange rejected); S-4 and X4 populations.session_calendar, v3-owned fees-v3.json, the pinned rules.py fee and close functions barred; S-5 N0 fixed, validation by committed automation, a late push voids the holdout; S-6 H4 population and edge_cases; S-7 holdout_breakpoints excludes 2026-01-02 to N0 - 1 and names the 2024-11 to 2025-12 covariate source; S-8 multiple_testing.item_ids and per-stage SeedSequence spawning; S-9 common extended window, underpowered after 2 blocks, cap by raw p; S-10 and X10 populations.fill_quote (one eligibility rule for fills and cost cells), fetch_failures and the H3-a timeout and overlap note; S-11 tercile_rule.h5_event_set; S-12 lineage label needs both bootstrap and normal-tail p; S-13 README precedence corrected to exits.py; X3 populations.suspected_unadjusted_split and the corporate-action coverage precondition; X5 coverage_rule, with H1 and H3 data status now fixed at the freeze; X6 populations.formulas restated and sessions_io.py pinned. X1, X2, X7, X8, X9 and X11 are resolved in the H6 draft. No finding rejected."}, + {"date": "2026-09-24", "round": 4, "input": "rereview of commit 3053b31 (findings S1-S13 and X-1 to X-16), each checked against this draft and the named code at aa6fc79 or origin/main (adaptive-paper transport.py normalize_intent, safety.py sell reservation, native_adapter.py order_report and _submit_order, config.json feed iex and max_order_quantity 1; rules.split_factor and evaluate_v2.py D7 share_mult; candidates.py and mover_scan.py exclusion regex; research-evaluation/plan.json; catalyst-dataset/native-receipt.json)", "resolution": "S1 run_discipline (study_code pin, run log, once rule, deviations, evaluator refusal); S2 and X-3 pre_freeze_access_path.snapshot_contents part 2 (minute bars, prints, split-adjusted bars, FINRA short interest if H5 is tested); S3 coverage_rule over H1-H5 with dropped_year_consequences; S4 and X-12 populations.formulas (DV_reg, MAX21, W(t), med20, sigma_d, entry bar) and cost_model notional and impact; S5 chronology.holdout.holdout_read; S6 run_discipline.fetch and fetch_failures; S7 freeze commit, freeze session and the reachability deadline; S8 count_unit; S9 the bound per alternative and sigma_by_item; S10 literal regex and as-known listing exclusion; S11 a pre-freeze stamp read is forbidden and voids development screening for its population; S12 outcome_reporting.qualifiers and the accrual-log deadline; S13 two registry entries; X-1 and X-2 prospective_collection from the freeze session, quotes fetched only under granted entries; X-4 H2 coverage over the coverage-stamp sample; X-5 multiple_testing.prospective_data; X-13 coverage-stamp window and one run of the count-only code; X-14 calendar amendment; X-15 rate-limit and fetch-time precondition; X-16 split factors from adjustment=split bars, a stated departure from D7. X-6 to X-11 are resolved in the H6 draft. No finding rejected; where the draft chose among a finding's suggested fixes, the choice is named in the section it cites."} ], "overturn": "Any headline result is overturned if a rerun on the same frozen inputs and runtime does not reproduce it byte for byte, if an independent quote sample puts the cost table off by more than 2x, or if the access log shows a holdout read that no committed granted entry preceded." } diff --git a/manifests/evidence.json b/manifests/evidence.json index 6d27a24a5..0081c75dd 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -8448,18 +8448,18 @@ }, { "path": "blueprints/us-equities/mover-v3/README.md", - "sha256": "3c9c7a7ded69c2c7a50d719e9eeeafbecd308e7630f0ce0913372ef5854c02d5", - "bytes": 41186 + "sha256": "e8f961c99148bc11506cf70d05bf0fbf73ac52a6a9b6611272186cc372d6a3c5", + "bytes": 43952 }, { "path": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json", - "sha256": "a884eaebb9cdec0f38f0eb250fe4d0262c2479117f5ca07f686982cc2e4a513d", - "bytes": 17723 + "sha256": "f56def6e032ed2cd076996e90d9dc242efdc005801e5e19718f4d57432c6cc85", + "bytes": 22300 }, { "path": "blueprints/us-equities/mover-v3/protocol-draft.json", - "sha256": "3483eb4bc3b26dcf7d7edca9a8b5b248feb11e171d19fe8d52a0270d6326424a", - "bytes": 74207 + "sha256": "e8d8b70798060ad6c6ab4fc13ca869afad95a3487b2248962659b5c3c97c7099", + "bytes": 99194 }, { "path": "blueprints/us-equities/nanosecond-replay/README.md", From 334da0bacb5af15c4eee7d79b37b4781a9b5e0b0 Mon Sep 17 00:00:00 2001 From: Scout Date: Thu, 24 Sep 2026 04:24:24 -0400 Subject: [PATCH 6/8] Mover v3: restructure into a core draft (H1 and H3, m = 5); archive the full draft Three review rounds of the full draft (75 fixes) did not converge (highs 4, 3, 4) across 35 items in H1-H6. Cut a small core that can converge. - protocol-core-draft.json (new): H1 on population D (H1-D difference test and the low-MAX b_lane cell) and H3 (H3-a, H3-b, H3-c), each kept item justified; exposure registry, prospective holdout, Holm over the 5 core items, MDEs with a sigma entry per item, H3-c direction lock, v1 cost table by sha with monotone tiers, impact and a realized half-spread floor, holdout gate and access log. Resolves the remaining findings that apply to the core: S1 (holdout count/read only from the governing validation run's study tree, evaluator refusal, calendar and fee amendments in separate data files), S2 (preregistered terminal exits for symbols that stop quoting), S3/X1 (asof = decision session with the as-known symbol, named enumeration source, identity dedup, empty responses counted), S4-S11, X5, X9, X10; X8 no longer applies. - protocol-draft.json: status superseded_by_core_draft_20260924, archived. - h6-execution-parity-draft.json: labelled not part of the alpha family. - README.md: core hypotheses section; H2, H4, H5, H6 in an appendix "Deferred to v3.1+" with reasons and open findings (S3, S4, S8, S10, S11, X1, X2-X4, X6-X8). - manifests/evidence.json: hashes for the changed and new files. Status stays draft_pending_independent_pre_outcome_review; nothing frozen. Co-Authored-By: Claude Opus 5.5 (1M context) --- blueprints/us-equities/mover-v3/README.md | 295 ++++++----- .../mover-v3/h6-execution-parity-draft.json | 8 +- .../mover-v3/protocol-core-draft.json | 488 ++++++++++++++++++ .../us-equities/mover-v3/protocol-draft.json | 3 +- manifests/evidence.json | 17 +- 5 files changed, 684 insertions(+), 127 deletions(-) create mode 100644 blueprints/us-equities/mover-v3/protocol-core-draft.json diff --git a/blueprints/us-equities/mover-v3/README.md b/blueprints/us-equities/mover-v3/README.md index 24e7617af..631dd8b6c 100644 --- a/blueprints/us-equities/mover-v3/README.md +++ b/blueprints/us-equities/mover-v3/README.md @@ -1,11 +1,21 @@ # Mover study v3: research plan (2026-09-24) -**Status: draft plan. Nothing here is frozen, and no outcome has been computed.** The preregistration draft is -[`protocol-draft.json`](protocol-draft.json), status `draft_pending_independent_pre_outcome_review`. The engineering -hypothesis H6 has its own draft, [`h6-execution-parity-draft.json`](h6-execution-parity-draft.json). The catalog +**Status: draft plan. Nothing here is frozen, and no outcome has been computed.** The governing preregistration +draft is the core, [`protocol-core-draft.json`](protocol-core-draft.json), with status +`draft_pending_independent_pre_outcome_review` and `frozen_before_outcomes: false`. It covers H1 and H3 only. The +earlier full draft, [`protocol-draft.json`](protocol-draft.json), is archived with status +`superseded_by_core_draft_20260924`. It was never frozen or run, and no rule in it governs. The execution study H6 +keeps its own draft, [`h6-execution-parity-draft.json`](h6-execution-parity-draft.json), which is **not part of the +alpha family**. H2, H4, H5 and H6 are deferred to v3.1+ (see [the appendix](#appendix-deferred-to-v31)). The catalog decisions from the same sweep are in [`catalogs/us-equities/mover-v3-sweep-20260924.json`](../../../catalogs/us-equities/mover-v3-sweep-20260924.json). +**Why a core.** Three review rounds of the full draft (75 fixes) did not converge: high-severity findings went 4, 3 +and then 4. The full draft covered 35 items across H1-H6 plus an execution study. The core keeps the two hypotheses +that can be computed from the Alpaca SIP data the repository already uses, with the fewest populations, arms and +cells that still test them: one population (D), three arms and five items (m = 5). It fixes every remaining review +finding that applies to them. The core's `review_record` maps each finding to the section that resolves it. + **Evidence class.** This plan rests on literature and metadata evidence, plus a few unpinned source reads, all from a read-only SOTA sweep on 2026-09-24. It is not native execution. The sweep had five angles (literature, repositories, data, exits and sizing, execution) and proposed 37 items. At least two independent verifiers checked each item: @@ -18,12 +28,12 @@ private because its verifier notes contain host paths. This README and the suppl early-entry v1 and v2 studies (`blueprints/us-equities/mover-early-entry/**`). **v1: 0 of 768 rule-exits pass development. v2: no pass in families E, F or P** (results sha256 `75a5004b...` after deviation D7). v3 does not modify those files. It reuses their pipeline only by reference, pinned to main at `aa6fc79` with git-blob and sha256 hashes -for `rules.py`, `quotes.py`, `sessions_io.py` (`official_price` only), the cost table and `evaluate_v2.py` (for D7's -split and ex-dividend accounting only; `protocol-draft.json`). The pin includes D7's stamp-window guard in `quotes.py`. -That code is valid for 2021-2026 only: its early-close list starts in 2021 and its fee file has no SEC rate before -2020. v3 therefore owns an XNYS session calendar (`exchange_calendars` 4.13.2) and a fee file with sourced 2017-2020 -rows, both pinned at the freeze, and never calls `rules.py`'s close-time or fee functions. The earlier pin `92062e8` survives only on the PR branch. v3 does not read #162's reserved holdout. The -2021 control segment of the catalyst experiment is permanently inspected and is not a fresh holdout. +for `rules.py`, `quotes.py` (record format and `valid_quote` only), `sessions_io.py` (`official_price` only), the cost +table and `evaluate_v2.py` (for D7's split and ex-dividend accounting only). That code is valid for 2021-2026 only: +its early-close list starts in 2021 and its fee file has no SEC rate before 2020. v3 therefore keeps an XNYS session +calendar (`exchange_calendars` 4.13.2) and a fee file with sourced 2017-2020 rows as data files outside the study +code, pinned at the freeze, and never calls `rules.py`'s close-time or fee functions. v3 does not read #162's reserved +holdout. The 2021 control segment of the catalyst experiment is permanently inspected and is not a fresh holdout. ## Why continuation is not supported @@ -49,116 +59,116 @@ Partners conference draft (EFMA 2025), not Hong et al., so v3 gives its squeeze The literature is monthly, cross-sectional and mostly large-cap. Whether it carries over to catalyst-conditioned movers priced at $1 to $50 is what v3 tests; it is not an assumed result. -## Hypotheses H1-H6 +## Core hypotheses: H1 and H3 + +v3 tests the effect's **sign and conditioning**, not continuation alone. `protocol-core-draft.json` gives the exact +arms, items, minimum samples and windows. It has one population: -v3 tests the effect's **sign and conditioning**, not continuation alone. `protocol-draft.json` gives the exact arms, -primary tests, minimum samples and windows. Two populations are common to H1-H5, and both reuse #162's definitions -from `rules.py` by reference: +- **D (daily lane):** a split-consistent official-close gain of at least +20%, an official close of at least $1 and a + regular-session dollar volume of at least $1M (summed from 1-minute bars from 09:30 ET to the scheduled close). The + decision is at 16:15 ET, and the entry is the first eligible quote at or after 09:35 ET on the next session. -- **D (daily lane):** a split-consistent close-to-close gain of at least +20%, a price of at least $1 and a - regular-session dollar volume of at least $1M (summed from 1-minute bars from 09:30 ET to the scheduled close). The decision is at 16:15 ET, and the entry is the first eligible - quote at or after 09:35 ET on the next session. -- **I (intraday lane):** the first minute from 09:35 to 10:30 ET at which `gain_at_t >= 0.20` and - `dollar_volume_at_t >= $1M`, with `price_at_t >= $1`. The entry is the first eligible quote at or after - decision + 60 s. +The intraday lane I is dropped from the core. Neither H1 nor H3 needs it, and it carried its own open finding (its +listing-exchange opening print can come after the decision minute). -The draft restates these formulas exactly, since `rules.py` does not define `ref` or the gain. Official opens and +The draft restates every formula exactly, since `rules.py` does not define `ref` or the gain. Official opens and closes count only from the listing exchange's auction print or official close. A close printed only on another exchange, or a missing previous close, means no event, with no bar fallback. A session whose raw close ratio sits within 1% of an integer split ratio with no adjustment is treated as an unadjusted split and is excluded before membership. Split factors come from split-adjusted bars, so a large cash dividend is booked as cash, not as a share -change (a stated departure from #162's D7 code, which used all-adjusted bars). MAX21, the sizing window, the -20-session median dollar volume, the volatility used for impact and the entry bar are each defined exactly. OTC -names are excluded by as-known session data (no listing-exchange opening print), never by today's asset flags. -Every such case is counted. - -H1-H3 use no halt data. As in the catalyst experiment, a halt is never inferred from missing data: fills need an -eligible quote, so a trade set never depends on a later halt-data purchase. Halt state enters only H4. - -The sweep's phrase "v1 lanes" mixes two protocols: the catalyst experiment's daily and intraday lanes, and #162's -+20% grid. v3 settles this with D and I above. G = 0.20 and V = $1M are v1's loosest gain and middle volume floor, -chosen for sample size. They were chosen after v1's dev/val results were seen, in which every cell is negative; the -exposure registry records this. - -| H | Question | Entry | Exit arms | Sizing | Not supported if | Data | -|---|---|---|---|---|---|---| -| H1 | Does MAX (lottery) conditioning flip the sign? | D and I, with MAX(21 sessions) terciles as a covariate. Breakpoints come from the same population's events in the prior 252 sessions, so they are known at the decision; no new thresholds | (a) exit at min(entry + 30 min, 15:55); (b) the lane horizon (D: 15:55 on the 5th session; I: entry + 60 min); (c) same-session 15:55 | 1x, equal notional, capacity-capped | not supported: at the verdict stage, both high-MAX minus low-MAX tests for arm (b) (D and I) exclude a negative effect of the MDE size, and no H1 arm-by-tercile cell passes. A high-MAX outperformance is outside the one-sided test and supports no claim | **expected available** (Alpaca SIP bars and quotes); 2016-2020 quote and auction coverage is unverified; H1 is tested only if the preregistered coverage rule keeps enough years, decided at the freeze | -| H2 | Do catalyst-verified moves beat pure attention spikes? | D and I, split by an 8-K Item 1.01 accepted before the decision vs a complete, parsed filing index with no qualifying filing; unknown identity or items are excluded from both groups | the lane horizon only | 1x | not supported: at the verdict stage, H2-D and H2-I (catalyst minus attention) are both labelled not supported with the MDE excluded, and no H2 cell passes | **gated**: `catalyst.py` makes every 2016-2020 filing ineligible (availability = max(acceptance, local observation)) and has no Item parser or historical ticker-to-CIK map. Needs a preregistered acceptance-time availability rule, an Item parser, an as-known CIK source and a coverage check before the freeze, else not tested | -| H3 | Do the overnight and intraday legs differ? | D | (a) intraday only: enter at 09:35, exit at 15:55 the same session; (b) overnight only: enter at 15:55 (300 s timeout), exit at the first eligible quote after the next 09:30 open; (c) the 5-session hold split ex post into 4 overnight legs (official close to official open) and 4 intraday legs (official open to official close), comparing mean per-leg returns (a diagnostic, not traded; a trade missing any leg is excluded). Two-sided at development; its direction is then locked | 1x. Arm (b) stays at 1x in any later paper use until the 2x rung and its overnight cap are clean | not supported: at the verdict stage, the per-leg difference excludes the MDE, and neither traded arm passes | **expected available**; 2016-2020 quote and auction coverage is unverified; H3 is tested only if the coverage rule keeps enough years, decided at the freeze | -| H4 | Does a halt-conditional exit beat ignoring the halt? | No new entry. Applies to every D and I lane-horizon (b_lane) trade, deduplicated by symbol, session and entry time, that meets a halt or, for a Nasdaq-listed name, an LULD pause (not only passing arms, so that a no-pass outcome still leaves a sample) | (a) exit at the reopening of the first halt that starts after the entry fill, at the reopening-cross print (source fixed before the freeze) or the first post-reopen quote; (b) hold to the lane horizon. A halt still in force at the planned exit gives both arms the same post-reopen price (difference 0); one that spans a segment end is excluded; a pause folded into the closing cross exits at the official close. No fills during a halt. Both sides costed at the wider of the table and the realized post-reopen spread. Pause length is logged as a covariate, not a signal. Two-sided at development; its direction is then locked | inherits the parent arm | not supported: at the verdict stage, the (a) minus (b) difference excludes the MDE. Below the minimum halted trades it is underpowered. Either way the halt state stays a safety rule only | **gated** (Databento XNAS.ITCH status schema, paid, *contested*: corrected from raw ITCH messages to the normalized schema; Nasdaq Trader history as a short pilot) | -| H5 | Does days-to-cover crowding filter movers? | D split by days-to-cover tercile (short interest / ADV, both in the decision session's share basis; trailing 252-session breakpoints), using the latest FINRA figure published before the decision, with its settlement lag | the lane horizon | 1x; a filter, never a size input | primary (H5-p): high minus low DTC is labelled not supported with a negative effect of the MDE size excluded. Secondary squeeze test (H5-s, no sourced prior): high DTC minus the pooled middle and low terciles, reported separately. Either way at the verdict stage and with no H5 cell passing | **gated** (FINRA short-interest access and terms; history must cover the 2016 warm-up) | -| H6 | Engineering, not alpha, in its own draft and one-test family: do broker-held exits realize exit prices equivalent to client-side exits? | a named mechanics-only strategy on a committed large-cap and ETF list, 2 shares per entry on the SIP feed, entering after 10:31 ET only in symbols that are flat, have no open order and had no absolute daily return of 10% or more in the previous 5 sessions or at entry (a screen that uses no v3 definition), assigned to an arm by a hash of symbol, session and entry count written to the ledger before the order is sent. Not yet defined, so H6 cannot accrue | (a) the current `exits.py` chain: client-side triggers, one exit limit, replace disabled (the shipped default); (b) the quantity split into two tranches, each with exactly one broker-held GTC closing order: an OCO (take-profit and a stop-market stop) on the take-profit tranche and a `trailing_stop` (trail_percent from `effective_trailing_bps` at entry) on the rest. Time, risk-off, force and halt exits stay client-side | the strategy's fixed 2-share entry | not supported: the median exit slippage of positions whose first exit was stop-type, taken intention to treat to their final close, is not shown equivalent within ±25 bps against each arm's preregistered trigger level and against the NBBO bid at trigger (TOST, 200 per arm per confirmatory stage, with stages cut by session so both arms cover the same period); or paper partial-fill or trigger behaviour departs from the docs. REST calls per position are reported, not tested: arm (a) needs about 2, arm (b) at least 3 | **gated**: no position source; needs the strategy, a recorded decision on the no-manufactured-trades rule, the adapter, transport, safety-reservation and reconciliation changes for non-limit orders, a paper smoke test and a paper pilot projecting at least 1.5x each stage minimum within 126 sessions (else dropped) | +change (a stated departure from #162's D7 code, which used all-adjusted bars). OTC names are excluded by as-known +session data (no listing-exchange opening print), never by today's asset flags. Every such case is counted. + +**Universe and identity.** The 2016-2020 symbol list is the Alpaca asset master (active and inactive, every current +exchange including OTC) plus every old and new symbol named by an Alpaca corporate-action record. This is +broad-universe's supplement method, which found that the asset list alone misses delisted names such as TWTR. Every +bars, auctions and quotes request carries Alpaca's `asof` set to a session date, never the fetch date. The screen for +session s uses `asof = s`. Every request for an event decided on session t uses the symbol as known on t, with +`asof = t`. The fetch date is stored separately as the vintage. Duplicate histories (FB and META both carrying Meta's +bars) are removed with broad-universe's pinned `coverage.dedupe_identity` before membership. Empty responses are +counted, never silently dropped. + +**Symbols that stop quoting.** An exit waits for an eligible quote for at most 5 sessions after the planned exit +session. A position still without one is terminal (a delisting, a move to OTC, a suspension, or a halt that does not +resume). It is booked at the last eligible bid of the hold when a retained merger record explains the delisting, +and at 0 otherwise. Terminal trades stay in every primary statistic and are counted per item. Booking every terminal +trade at the last eligible bid is only a sensitivity. + +No halt data are used. As in the catalyst experiment, a halt is never inferred from missing data. G = 0.20 and +V = $1M are v1's loosest gain and middle volume floor, chosen for sample size after v1's dev/val results were seen +(in which every cell is negative); the exposure registry records this. + +| Item | Hypothesis and role | Statistic | Alternative | Why it is kept | +|---|---|---|---|---| +| H1-D | H1, primary test | mean net(5-session hold, high MAX21 tercile) - mean net(same, low tercile) | less than 0 | the direct test of BCW's reversal prediction, on the D horizon closest to BCW's holding period | +| H1-D-b_lane-low | H1, tradable cell | mean net(5-session hold, low MAX21 tercile) | greater than 0 | in a long-only study, the one H1 cell for which the prior predicts the best relative return; the high and middle cells are reported descriptively only | +| H3-a | H3, tradable cell | mean net(enter 09:35, exit 15:55 on t+1) | greater than 0 | the intraday leg as a trade | +| H3-b | H3, tradable cell | mean net(enter 15:55 on t+1, exit at the first eligible quote from 09:30 on t+2) | greater than 0 | the overnight leg as a trade; neither leg has a sourced sign for movers, so both are kept | +| H3-c | H3, primary test | mean of (mean of 4 overnight official-print log legs - mean of 4 intraday legs) over t+1 .. t+5 | two-sided at development, then locked to the development sign | the direct test of "overnight versus intraday". It uses official prints only, so it needs no fill or cost model | + +MAX21 is the largest of the 21 split-consistent official-close returns before the decision session. Its terciles use +breakpoints from D events in the prior 252 sessions, so they are known at the decision. Research sizing is 1x equal +notional, capacity-capped. Costs per side are 1.25 x the larger of the v1 table cell (pinned by sha256, monotone in +dollar volume) and the fill quote's own half-spread, plus a square-root impact term. H1 is not supported if H1-D +excludes a negative effect of the MDE size and the low-MAX cell does not pass. H3 is not supported if H3-c excludes +the MDE and neither H3-a nor H3-b passes. A high-MAX outperformance is outside H1-D's one-sided test and supports no +claim. **Outcome labels.** Each test ends in one of three preregistered labels: a pass; *not supported*, with the excluded effect size stated; or *underpowered*. A pass is named by its stage: *nominated* at development, *screened* at -validation and *supported (confirmatory)* only at the holdout. Each hypothesis-level verdict names its stage too. At the -minimum samples the detectable effects are roughly 3-26% (the MDE tables in the protocol draft), far above the -literature's roughly 1% a month, so a miss on H1, H2 or H5 will most likely be labelled underpowered. v3 can detect +validation and *supported (confirmatory)* only at the holdout. Holm runs over the five core items only. At the +minimum samples the detectable effects are roughly 2-19% (the MDE tables in the core draft), far above the +literature's roughly 1% a month for MAX, so a miss on H1-D will most likely be labelled underpowered. v3 can detect only large mover-specific effects. -**Data status is fixed at the freeze.** H2, H4 and H5 are each recorded then as tested (data in hand) or not tested -(p = 1 at every stage, permanently). One coverage rule covers all of H1-H5, with one set of kept years: a 2016-2020 -year below 90% official-close coverage or 80% eligible-quote coverage is dropped for every item, and with fewer than 2 development years left, or without 2020, no item of H1-H5 is -tested, since every item uses the same D and I trades. A dropped year's edges are segment boundaries, its events -leave the tercile windows and the bootstrap wraps over the kept sessions. Data from a new source or a new historical -range obtained after the freeze can only start a new protocol version, so it cannot change this family's Holm -thresholds after results are seen. Each tested item that needs holdout data beyond the SIP collection (H2 filings, H4 -halts, H5 short interest) must have a prospective source, licence and budget committed at the freeze; if those data -later fail to arrive, the item is labelled underpowered, never dropped. +**Data status is fixed at the freeze.** One coverage rule covers all five items, with one set of kept years: a +2016-2020 year below 90% official-close coverage or 80% eligible-quote coverage is dropped for every item. With fewer +than 2 development years left, or without 2020, no item is tested. A dropped year's edges are segment boundaries, +its events leave the tercile windows and the bootstrap wraps over the kept sessions. Data from a new source or a new +historical range obtained after the freeze can only start a new protocol version (v3.1+), so it cannot change this +family's Holm thresholds after results are seen. **Exposure and chronology.** Development is 2017-2019 and validation 2020. The holdout is 252 sessions (early closes included) starting at the 40th session after the freeze. The freeze commit is the first commit on `main` whose protocol status is frozen (pull requests are squash-merged), timed by its committer timestamp in New York. If the validation results commit is not on `main` before 09:30 ET on that 40th session, the holdout of this protocol version -is void; there is no deferral, so its start cannot be chosen after the results are -seen. Every holdout trade enters the primary statistic. Paper orders in holdout symbol-sessions are logged at order time -and removed only in a sensitivity, and more than 5% exposed trades labels an item's holdout contaminated. A holdout read is defined narrowly: computing any v3 outcome from -holdout data outside a granted access-log entry. It voids the holdout rather than removing trades. Live paper -decisions and orders are exposure, logged in the accrual log, not reads; metadata-only dashboards are neither. -Development and validation each run once from a pinned study-code revision, every run is logged (failed runs -included), and a code change after the first outcome is a numbered deviation that never changes which items the -holdout opens for. The 2017-2020 windows are -disjoint from #162's v1 and v2, but not unseen. The broad-universe study computed forward returns for every eligible -symbol-session in 2017-2021 (its C0 control), including a descriptive lane covering names with a close of at least $1, -a median dollar volume of at least $2M, 60 prior contiguous bars and no gap. C0 on that lane is the superset exposure -for most of D and I; its S2 volume-shock signal overlaps D but is not a superset. This plan's author also read its S2 -headline for that period. Development and validation are therefore -**screening stages only**: validation decides which items the holdout opens for, and only the prospective holdout can -support a confirmatory claim. +is void. There is no deferral, so its start cannot be chosen after the results are seen. Every holdout trade enters +the primary statistic. Paper orders in holdout symbol-sessions are logged at order time and removed only in a +sensitivity, and more than 5% exposed trades labels an item's holdout contaminated. A holdout read is defined +narrowly: computing any v3 outcome from holdout data outside a granted access-log entry. It voids the holdout rather +than removing trades. + +Development, validation and the holdout count and read all run from one study-code tree pinned at the freeze, with a +pinned runtime lockfile. The evaluator refuses a holdout count or read from any other tree. A code change after the +freeze is a numbered deviation whose results are reported beside the governing ones with no label. The one exception +is a rerun after a crash that changes no rule and reproduces every written outcome byte for byte. Calendar and fee +amendments go in append-only data files outside the study tree, so appending one never changes the tree. + +The 2017-2020 windows are disjoint from #162's v1 and v2, but not unseen. The broad-universe study computed forward +returns for every eligible symbol-session in 2017-2021 (its C0 control), including a descriptive lane covering names +with a close of at least $1, a median dollar volume of at least $2M, 60 prior contiguous bars and no gap. C0 on that +lane is the superset exposure for most of D; its S2 volume-shock signal overlaps D but is not a superset. This plan's +author also read its S2 headline for that period. The pit-availability wave read filing indexes, sampled news and +split-window prices for 20 named symbols (including AAPL and TSLA around 2020-08-31) but computed no mover outcome. +Development and validation are therefore **screening stages only**: validation decides which items the holdout opens +for, and only the prospective holdout can support a confirmatory claim. H1 rests on BCW 2011 (**survives**) and the relative-volume/price-return ranking in -`catalyst-experiment/protocol.json`, which is structurally a MAX-like selection. H2 rests on the Barber/Odean line -(*contested*). H3 rests on LPS 2019 and ABJK 2022 (*contested*). A VWAP-loss or ladder arm, from MarĂ³y's SSRN 5095349 -(posted 2025-01-12, returned HTTP 403, never read), is **excluded from the confirmatory family**. It can be added only -by an amendment made before the freeze. - -H4's mechanics come from the SEC DERA LULD papers -([1](https://www.sec.gov/files/dera-luld-white-paper.pdf), -[2](https://www.sec.gov/files/marketstructure/research/dera_wp_luld_and_extraordinary_transitory_volatility.pdf); -*contested*). The execution-state idea survives. The claimed "pause duration predicts post-reopen volatility" is a -statement from DERA's literature review, not a DERA finding. The rest comes from Nasdaq's LULD material (a 15 s limit -state, a 5-minute pause, and pauses in the last 10 minutes folding into the close; opened by the reviewer, *contested*) -and the [Nasdaq Trader halt pages](https://www.nasdaqtrader.com/trader.aspx?id=TradeHalts) (**survives**; a web/RSS -interface with a limited lookback, not a bulk archive). - -H5 rests on Hong et al. (**survives**) for H5-p only. H6 rests on -[orders-at-alpaca](https://docs.alpaca.markets/docs/orders-at-alpaca). `trailing_stop` **survives**. The bracket -proposal was *contested* and corrected to OCO: a bracket's take-profit closes the whole position, so it cannot -express `take_profit_fraction` < 1. The same page limits how legs combine: "Trailing stop orders are currently -supported only with single orders", and "you would not be able to submit two conditional closing orders since -Alpaca's system would view one of the two orders as exceeding the available position quantity". So a share can carry -either a trailing stop or an OCO, never both. +`catalyst-experiment/protocol.json`, which is structurally a MAX-like selection. H3 rests on LPS 2019 and ABJK 2022 +(*contested*). Their full texts must be read before the freeze, and H3-c is two-sided at development, so no test +depends on their sign. A VWAP-loss or ladder arm, from MarĂ³y's SSRN 5095349 (posted 2025-01-12, returned HTTP 403, +never read), is **excluded from the confirmatory family**. ## Data plan | Need | Source | Status now | Gate | |---|---|---|---| -| PIT daily and minute SIP bars, quotes, official auction prices, corporate actions (H1-H3, H6) | Alpaca market data (SIP), already entitled; used by `broad-universe` and `extreme-gainer-audit` | bars **available** (daily from 2016-01-04, minute from 2016-01-01); quote and auction-print coverage for 2016-2020 **unverified** | a metadata coverage check before the freeze; private hashed snapshots pinned with their asof date. Before 2020, survivorship is limited (`broad-universe/README.md`), so results for 2017-2019 carry that label | -| 8-K Item 1.01 acceptance times (H2) | SEC EDGAR through the catalyst-provenance and catalyst-dataset recipes | **gated**: `catalyst.py` parses no Item and has no historical ticker-to-CIK map, and its availability rule fails every historical filing | a preregistered acceptance-time availability rule, an Item parser, an as-known CIK source and an index-completeness check, all before the freeze | +| PIT daily and minute SIP bars, quotes, official auction prices, corporate actions (core H1 and H3; H6 later) | Alpaca market data (SIP), already entitled; used by `broad-universe` and `extreme-gainer-audit` | bars **available** (daily from 2016-01-04, minute from 2016-01-01); quote and auction-print coverage for 2016-2020 **unverified** | a metadata coverage check before the freeze; private hashed snapshots pinned, each request with `asof` = its session and the fetch date kept as the vintage. Before 2020, survivorship is limited (`broad-universe/README.md`), so results for 2017-2019 carry that label | +| 8-K Item 1.01 acceptance times (H2, deferred to v3.1+) | SEC EDGAR through the catalyst-provenance and catalyst-dataset recipes | **gated**: `catalyst.py` parses no Item and has no historical ticker-to-CIK map, and its availability rule fails every historical filing | a preregistered acceptance-time availability rule, an Item parser, an as-known CIK source and an index-completeness check, all before the freeze | | Shares outstanding and public float (screen only) | [SEC XBRL frames](https://www.sec.gov/search-filings/edgar-application-programming-interfaces) | **available** as a screen (*contested*: one misattributed source corrected) | frames give one last-filed value per entity. Each row carries `accn` but no filed date, and superseded vintages are absent. A name that clears the screen must be confirmed from companyfacts (or submissions by `accn`) for the filed date and the original vintage before the float counts as point-in-time | -| Historical halt, LULD and status events (H4, halt fidelity in simulation) | Databento XNAS.ITCH normalized status schema (`databento/dbn`, Apache-2.0; *contested*: corrected from raw ITCH H/h/J messages to the normalized schema), cross-checked against the Nasdaq Trader halt history | **gated on paid data the user must buy** (Databento is metered). A free short-window pilot is possible | get a `Historical.metadata.get_cost` quote for one symbol-year; confirm the coverage start and the licence. Per the TotalView-ITCH 5.0 spec, halt and resume state (H, Q, T) covers every security traded on the Nasdaq execution system, including other exchanges' listings; the LULD pause state (P) and the auction collar (J) are for Nasdaq-listed names only, and a pause in another listing appears as a halt | +| Historical halt, LULD and status events (H4, deferred to v3.1+; halt fidelity in simulation) | Databento XNAS.ITCH normalized status schema (`databento/dbn`, Apache-2.0; *contested*: corrected from raw ITCH H/h/J messages to the normalized schema), cross-checked against the Nasdaq Trader halt history | **gated on paid data the user must buy** (Databento is metered). A free short-window pilot is possible | get a `Historical.metadata.get_cost` quote for one symbol-year; confirm the coverage start and the licence. Per the TotalView-ITCH 5.0 spec, halt and resume state (H, Q, T) covers every security traded on the Nasdaq execution system, including other exchanges' listings; the LULD pause state (P) and the auction collar (J) are for Nasdaq-listed names only, and a pause in another listing appears as a halt | | Survivorship-free delisted history before 2020 | Norgate Data US Platinum ([prices](https://norgatedata.com/prices.php); *contested*: facts confirmed, but not new, since it is already named in `catalogs/sota-convergence/manifest-20260923.json`) vs the in-repo SEC Form-25 proxy | **gated on paid data the user must buy** | read the EULA's derived-data terms and what happens when the subscription lapses. Compare only | -| Short interest (H5) | FINRA consolidated short interest (`blueprints/us-equities/acceptance-wave/factors-regimes.md`) | **gated** on access and terms the user must establish | model the publication and settlement lag; a bi-monthly overlay only | +| Short interest (H5, deferred to v3.1+) | FINRA consolidated short interest (`blueprints/us-equities/acceptance-wave/factors-regimes.md`) | **gated** on access and terms the user must establish | model the publication and settlement lag; a bi-monthly overlay only | | Order latency and fill agreement | Alpaca paper `trade_updates` timestamps plus `x-ratelimit-*` headers | **available** (paper) | probe orders come out of the 20-call management reserve and are labelled as measurement | | Earnings surprise (deferred lane) | Benzinga via Alpaca news, or a licensed calendar with revision history | gated on paid data; **not a v3 priority** | PEAD after costs is contested | @@ -411,32 +421,83 @@ into another: ## Before any freeze -`protocol-draft.json` lists the preconditions (`freeze_preconditions`). The main ones: +`protocol-core-draft.json` lists the preconditions (`freeze_preconditions`). The main ones: -- an independent pre-outcome review, from a different session and model family, with every finding resolved in a - dated record; -- the direct citation reads above, or removal of the claims that depend on them; -- a study-code revision and tree pinned in the frozen protocol, with an append-only run log and a deviation rule, - and post-freeze fetches sealed with their fetch-incomplete rate committed before any outcome; +- an independent pre-outcome review of the core, from a different session and model family, with every finding + resolved in a dated record; +- direct reads of the LPS 2019 and ABJK 2022 full texts, or removal of the claims that depend on them; +- the session calendar, the fee file and their empty append-only amendment files committed as data files outside the + study tree and pinned; +- the enumerated symbol list and the pre-freeze snapshot sealed and pinned, every request with `asof` = its session; +- metadata-only coverage checks, run only by committed count-only code that emits counts and rates and seals the raw + pages unopened: 2016-2020 official prints by source label, quotes at non-v3 coverage stamps, corporate-action + records, suspected unadjusted splits, symbols per enumeration source and empty responses; +- H1 and H3 recorded as tested or permanently not tested by the coverage rule; - the documented market-data rate limit and a fetch-time estimate with margin under the 40 sessions before the holdout; -- the 2017-2019 cost re-measure fixed by rule only; it runs as the first step after the freeze, because its quote - stamps coincide with outcome prices; fee rates dated for 2017-2020; -- metadata-only coverage checks, run only by committed count-only code that emits counts and rates and seals the raw - pages unopened: 2016-2020 official prints by source label, quotes at non-v3 coverage stamps, split and dividend - records and suspected unadjusted splits (H1-H3); H2's identity and filing-index gates, H4's halt and reopening-print - sources, H5's short-interest history including the 2016 warm-up; -- the v3 session calendar and fee file pinned; -- H1 and H3 (by the coverage rule) and H2, H4 and H5 each recorded as tested or permanently not tested; -- the pre-freeze snapshot pinned with its asof date, including the 2024-11 to 2025-12 minute bars, official prints - and (if H5 is tested) short interest that the holdout tercile breakpoints need; -- study code committed with synthetic tests before any outcome is computed, including split and ex-dividend - crossings, early-close sessions and segment-end censoring. - -H6 has its own preconditions in `h6-execution-parity-draft.json`, including a named mechanics-only strategy, a test -that one share never carries two closing orders and a paper smoke test of the tranche pair. - -The second, third and fourth review rounds' findings and their resolution are recorded in `protocol-draft.json` -(`review_record`). +- the study tree committed with its runtime lockfile and synthetic tests before any outcome is computed, including + terminal exits, renames and ticker reuse, split and ex-dividend crossings, early-close sessions, segment-end + censoring and the embargo, the direction lock and the evaluator's refusals. + +H6 has its own preconditions in `h6-execution-parity-draft.json`. Review rounds 2-4 of the full draft are recorded in +`protocol-draft.json` (`review_record`); round 5, the restructure into the core, is in `protocol-core-draft.json`. Until all of that is done, this is a plan, and no window it names may be read for outcomes. + +## Appendix: deferred to v3.1+ + +These items are out of the core. None of them is in the alpha family (m = 5), none is tested by this protocol +version, and none may be added to it after the freeze. Each needs its own later protocol version, with its own +family, preregistration and review. The rows below are the full draft's definitions, kept for that later work. The +findings named are the review findings still open when the core was cut. Several of them (S3, S4, X1) are resolved +in the core for H1 and H3, but they must be resolved again for each deferred item. + +| H | Question | Entry | Exit arms | Sizing | Not supported if | Data | +|---|---|---|---|---|---|---| +| H2 | Do catalyst-verified moves beat pure attention spikes? | D and I, split by an 8-K Item 1.01 accepted before the decision vs a complete, parsed filing index with no qualifying filing; unknown identity or items are excluded from both groups | the lane horizon only | 1x | not supported: at the verdict stage, H2-D and H2-I (catalyst minus attention) are both labelled not supported with the MDE excluded, and no H2 cell passes | **gated**: `catalyst.py` makes every 2016-2020 filing ineligible (availability = max(acceptance, local observation)) and has no Item parser or historical ticker-to-CIK map. Needs a preregistered acceptance-time availability rule, an Item parser, an as-known CIK source and a coverage check before the freeze, else not tested | +| H4 | Does a halt-conditional exit beat ignoring the halt? | No new entry. Applies to every D and I lane-horizon (b_lane) trade, deduplicated by symbol, session and entry time, that meets a halt or, for a Nasdaq-listed name, an LULD pause (not only passing arms, so that a no-pass outcome still leaves a sample) | (a) exit at the reopening of the first halt that starts after the entry fill, at the reopening-cross print (source fixed before the freeze) or the first post-reopen quote; (b) hold to the lane horizon. A halt still in force at the planned exit gives both arms the same post-reopen price (difference 0); one that spans a segment end is excluded; a pause folded into the closing cross exits at the official close. No fills during a halt. Both sides costed at the wider of the table and the realized post-reopen spread. Pause length is logged as a covariate, not a signal. Two-sided at development; its direction is then locked | inherits the parent arm | not supported: at the verdict stage, the (a) minus (b) difference excludes the MDE. Below the minimum halted trades it is underpowered. Either way the halt state stays a safety rule only | **gated** (Databento XNAS.ITCH status schema, paid, *contested*: corrected from raw ITCH messages to the normalized schema; Nasdaq Trader history as a short pilot) | +| H5 | Does days-to-cover crowding filter movers? | D split by days-to-cover tercile (short interest / ADV, both in the decision session's share basis; trailing 252-session breakpoints), using the latest FINRA figure published before the decision, with its settlement lag | the lane horizon | 1x; a filter, never a size input | primary (H5-p): high minus low DTC is labelled not supported with a negative effect of the MDE size excluded. Secondary squeeze test (H5-s, no sourced prior): high DTC minus the pooled middle and low terciles, reported separately. Either way at the verdict stage and with no H5 cell passing | **gated** (FINRA short-interest access and terms; history must cover the 2016 warm-up) | +| H6 | Engineering, not alpha, in its own draft and one-test family: do broker-held exits realize exit prices equivalent to client-side exits? | a named mechanics-only strategy on a committed large-cap and ETF list, 2 shares per entry on the SIP feed, entering after 10:31 ET only in symbols that are flat, have no open order and had no absolute daily return of 10% or more in the previous 5 sessions or at entry (a screen that uses no v3 definition), assigned to an arm by a hash of symbol, session and entry count written to the ledger before the order is sent. Not yet defined, so H6 cannot accrue | (a) the current `exits.py` chain: client-side triggers, one exit limit, replace disabled (the shipped default); (b) the quantity split into two tranches, each with exactly one broker-held GTC closing order: an OCO (take-profit and a stop-market stop) on the take-profit tranche and a `trailing_stop` (trail_percent from `effective_trailing_bps` at entry) on the rest. Time, risk-off, force and halt exits stay client-side | the strategy's fixed 2-share entry | not supported: the median exit slippage of positions whose first exit was stop-type, taken intention to treat to their final close, is not shown equivalent within ±25 bps against each arm's preregistered trigger level and against the NBBO bid at trigger (TOST, 200 per arm per confirmatory stage, with stages cut by session so both arms cover the same period); or paper partial-fill or trigger behaviour departs from the docs. REST calls per position are reported, not tested: arm (a) needs about 2, arm (b) at least 3 | **gated**: no position source; needs the strategy, a recorded decision on the no-manufactured-trades rule, the adapter, transport, safety-reservation and reconciliation changes for non-limit orders, a paper smoke test and a paper pilot projecting at least 1.5x each stage minimum within 126 sessions (else dropped) | + +- **H2, catalyst versus attention.** *Reason:* not computable from the data in hand. `catalyst.py` makes every + 2016-2020 filing ineligible and has no Item 1.01 parser and no historical ticker-to-CIK map, so the item needs a new + availability rule, a parser, an as-known CIK source and a coverage check first. *Open findings:* S4 (per-group + counts for the H2-D and H2-I differences), S11 (the pit-availability wave read filing indexes for 20 symbols and + found the XOM and DKNG ticker-to-CIK breaks, which bear on H2's identity gate), S3 and X1 (as-known identity and + empty responses; H2 also needs an as-known CIK join), and X8 for its I-lane half. +- **H4, halt-conditional exit.** *Reason:* needs a paid halt source (Databento XNAS.ITCH status schema, *contested*, + cost and coverage start unverified) and a reopening-print source. Whether SIP conditions can rebuild halts is + unverified. *Open findings:* S8 (the direction lock is undefined with 0 development halted parents or a zero + estimate), S10 (the two-sided normal-tail p at development), and S2's terminal rule, which H4's a_reopen arm must + reconcile with halts that never resume. +- **H5, days-to-cover.** *Reason:* FINRA short-interest access and terms are not established, and the history must + cover the 2016 warm-up. *Open findings:* X7 (the DTC volume denominator names no volume source) and S4 (per-group + counts for H5-p and H5-s). +- **H6, execution parity.** *Reason:* engineering, not alpha. It has no position source yet and needs a recorded + decision on the no-manufactured-trades rule. Its draft stays in + [`h6-execution-parity-draft.json`](h6-execution-parity-draft.json), labelled not part of the alpha family. *Open + findings:* X2 (no minimum stage length, so a one-session stage passes TOST trivially), X3 (arm b's trigger time is + in practice its fill time, which biases the common basis), X4 (H6-e eligibility treats the arms asymmetrically) and + X6 (labels are undefined for a TOST test with no MDE). +- **Population I and the H1 a_fast and c_session arms.** *Reason:* they multiplied cells without adding a test of + MAX conditioning. *Open finding:* X8 (listing eligibility used an opening print that can come after the decision + minute). + +Evidence behind the deferred items, kept from the full plan: + +H4's mechanics come from the SEC DERA LULD papers +([1](https://www.sec.gov/files/dera-luld-white-paper.pdf), +[2](https://www.sec.gov/files/marketstructure/research/dera_wp_luld_and_extraordinary_transitory_volatility.pdf); +*contested*). The execution-state idea survives. The claimed "pause duration predicts post-reopen volatility" is a +statement from DERA's literature review, not a DERA finding. The rest comes from Nasdaq's LULD material (a 15 s limit +state, a 5-minute pause, and pauses in the last 10 minutes folding into the close; opened by the reviewer, *contested*) +and the [Nasdaq Trader halt pages](https://www.nasdaqtrader.com/trader.aspx?id=TradeHalts) (**survives**; a web/RSS +interface with a limited lookback, not a bulk archive). + +H5 rests on Hong et al. (**survives**) for H5-p only. H6 rests on +[orders-at-alpaca](https://docs.alpaca.markets/docs/orders-at-alpaca). `trailing_stop` **survives**. The bracket +proposal was *contested* and corrected to OCO: a bracket's take-profit closes the whole position, so it cannot +express `take_profit_fraction` < 1. The same page limits how legs combine: "Trailing stop orders are currently +supported only with single orders", and "you would not be able to submit two conditional closing orders since +Alpaca's system would view one of the two orders as exceeding the available position quantity". So a share can carry +either a trailing stop or an OCO, never both. diff --git a/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json index 8fea3e763..02a0559ae 100644 --- a/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json +++ b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json @@ -4,6 +4,8 @@ "kind": "draft_engineering_preregistration", "status": "draft_pending_independent_pre_outcome_review", "frozen_before_outcomes": false, + "alpha_family_member": false, + "label": "Execution study, not part of the mover-v3 alpha family. The alpha protocol is protocol-core-draft.json (m = 5). This draft uses none of its slots, and its results never open that protocol's holdout. Deferred to v3.1+ with open review findings X2 (no minimum stage length), X3 (trigger-time basis), X4 (asymmetric H6-e eligibility) and X6 (labels undefined for a TOST test); see the README appendix 'Deferred to v3.1+'.", "drafted_at": "2026-09-24", "revised_at": "2026-09-24 (round 2: population named as a gated mechanics-only strategy, H6-c restated as descriptive because arm a needs fewer calls by construction, arm a configuration pinned, H6-e unit and trigger strata, GTC and tranche timing, calendar cap, paper smoke test); round 3 on the same day: trigger references per arm plus a common NBBO-bid basis, stop-market OCO leg, intention-to-treat H6-e unit, flat-symbol entry rule, entry cancel timeout and tranche-reject fallback, a pilot-based power precondition, and a disjoint, sealed population); round 4 on the same day: disjointness by a screen that uses no v3 definition, adapter preconditions for transport intents, safety reservations and reconciliation reports, session-based stages, a statistics section, the SIP feed pinned, and a 2-share minimum)", "base_commit": "aa6fc791bbd4af3499317673989d2e90a522c257", @@ -11,7 +13,7 @@ "split_from": "blueprints/us-equities/mover-v3/protocol-draft.json (H6), in the 2026-09-24 review fix round", "evidence_class": "Official broker documentation read on 2026-09-24 plus repository source reads. No native execution, no paper order and no outcome. Paper fills are simulated by Alpaca, so any result is sim-to-paper evidence, not market evidence.", "question": "Do broker-held exits (Alpaca trailing_stop and OCO) realize exit prices equivalent to client-side exits, relative to the same trigger reference? REST calls per closed position are reported for both arms as a description, not tested.", - "independence": "Engineering, not alpha. This protocol has its own one-test family and its own paper timeline. It uses no slot of the mover-v3 alpha family (m = 35), H1-H5 never wait for it, and no result here opens or informs the alpha holdout. Its positions are mechanics-only and follow population.alpha_disjointness; each H6 paper order is logged at order time in the alpha protocol's accrual log (protocol-draft.json chronology.holdout.accrual_exposure), where it counts toward that protocol's 5% contamination cap and its exposure sensitivity.", + "independence": "Engineering, not alpha. This protocol has its own one-test family and its own paper timeline. It uses no slot of the mover-v3 alpha family (protocol-core-draft.json, m = 5; the archived full draft had m = 35), H1-H5 never wait for it, and no result here opens or informs the alpha holdout. Its positions are mechanics-only and follow population.alpha_disjointness; each H6 paper order is logged at order time in the alpha protocol's accrual log (protocol-draft.json chronology.holdout.accrual_exposure), where it counts toward that protocol's 5% contamination cap and its exposure sensitivity.", "data_status": "gated. H6 has no source of positions today: v3 defines no paper candidate, the v3 edge-shrink term is 0 until a holdout pass (so every alpha-sized position is 0), and this draft creates no order for the test itself. It cannot accrue any closed position until the population below is committed and the preconditions hold.", "evidence": [ {"source": "https://docs.alpaca.markets/docs/orders-at-alpaca", "status": "read on 2026-09-24. trailing_stop (trail_price or trail_percent) and OCO for a held position ('the entry order is already filled, and you can submit the take-profit and stop-loss in one order submission') are documented. Constraints quoted verbatim: 'Trailing stop orders are currently supported only with single orders. However, we plan to support trailing stop as the stop loss leg of bracket/OCO orders in the future.' and 'you would not be able to submit two conditional closing orders since Alpaca's system would view one of the two orders as exceeding the available position quantity'."}, @@ -27,7 +29,7 @@ "sealed_ledger": "H6 fills and closed positions go to a sealed ledger (sha256 committed per session batch). No per-position exit price or P&L of H6 positions is displayed or computed during a stage; stage statistics are computed only through h6-access-log.jsonl entries.", "open_decision": "Whether such a strategy is compatible with adaptive-paper README's rule 'No strategy manufactures trades to hit a throughput target' needs an explicit, recorded decision before the freeze. If the decision is no, H6 is dropped.", "rate": "estimated before the freeze by a short paper pilot of the committed mechanics-only strategy and configuration (both arms, labelled as measurement, never counted in any stage). The pilot records the exit-type mix per arm and the H6-e-eligible positions per arm per session. If that rate x 126 is below 1.5 x the largest stage minimum (1.5 x 200 = 300 eligible positions per arm), H6 is dropped. Retained adaptive-paper trial ledgers of other strategies are not an estimate of this rate.", - "calendar_cap": "each stage has at most 126 sessions (protocol-draft.json populations.session_calendar, early closes included); a stage that has not reached its minimum by then is labelled underpowered and the protocol stops" + "calendar_cap": "each stage has at most 126 sessions (protocol-core-draft.json populations.session_calendar, early closes included); a stage that has not reached its minimum by then is labelled underpowered and the protocol stops" }, "arm_assignment": { "rule": "arm = a if the first byte of sha256('mover-v3-h6|' + symbol + '|' + entry session (YYYY-MM-DD) + '|' + k) is even, else b, where k is the 1-based count of this symbol's entries in this session in decision order", @@ -92,7 +94,7 @@ "tost_p": "with d_b the bootstrap differences in bps and margin 25 bps: p_lower = (1 + #{b : d_b <= -25}) / (B + 1) and p_upper = (1 + #{b : d_b >= +25}) / (B + 1); p = max(p_lower, p_upper), and equivalence passes when p <= 0.05, which is the percentile 90% interval lying inside +/-25 bps", "evaluator": "the H6 evaluator is committed with synthetic tests (a known equivalent pair, a known shifted pair, the seed derivation and the session-cut stages) before any stage read" }, - "outcome_reporting": "the three labels of protocol-draft.json outcome_reporting (a stage-specific pass label; not supported with the excluded size stated; underpowered)", + "outcome_reporting": "the three labels of protocol-core-draft.json outcome_reporting (a stage-specific pass label; not supported with the excluded size stated; underpowered)", "sizing": "the fixed entry quantity of 2 shares of the mechanics-only strategy (population.rule)", "freeze_preconditions": [ "An independent pre-outcome review from a different session and model family, findings resolved in a dated record", diff --git a/blueprints/us-equities/mover-v3/protocol-core-draft.json b/blueprints/us-equities/mover-v3/protocol-core-draft.json new file mode 100644 index 000000000..a4d53b822 --- /dev/null +++ b/blueprints/us-equities/mover-v3/protocol-core-draft.json @@ -0,0 +1,488 @@ +{ + "schema_version": 1, + "id": "mover-v3-core-draft-20260924", + "kind": "draft_preregistration", + "status": "draft_pending_independent_pre_outcome_review", + "frozen_before_outcomes": false, + "drafted_at": "2026-09-24", + "base_commit": "aa6fc791bbd4af3499317673989d2e90a522c257", + "plan": "blueprints/us-equities/mover-v3/README.md", + "archived_full_draft": "blueprints/us-equities/mover-v3/protocol-draft.json (status superseded_by_core_draft_20260924). It is kept unchanged apart from its status and a superseded_by pointer. It was never frozen or run, and computed no outcome. Its review_record holds rounds 2-4.", + "execution_study": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json: an engineering draft, not part of this alpha family. It uses no slot of this family, and its results never open this protocol's holdout.", + "catalog_decisions": "catalogs/us-equities/mover-v3-sweep-20260924.json", + "why_core": "Three review rounds of the full draft (75 fixes) did not converge: high-severity findings went 4, 3, 4. The full draft spans 35 items across H1-H6 plus an execution study. This core keeps only H1 (MAX/lottery conditioning) and H3 (overnight versus intraday legs), because both can be computed from the Alpaca SIP data the repository already uses (daily and minute bars, quotes, auction prints and corporate actions). It keeps one population (D), three arms and five items (item_set). H2, H4, H5 and H6 are deferred to v3.1+ (README appendix 'Deferred to v3.1+'), each with its reason and the open review findings that apply to it.", + "evidence_class": "Literature and metadata evidence, plus unpinned source reads, from a read-only sweep on 2026-09-24 (5 angles, 37 items; 5 survive, 24 contested, 8 killed). No native execution, no market data fetched and no outcome computed. This is not a frozen protocol: no window named here may be read for outcomes until status is frozen under freeze_preconditions.", + "relation_to_prior_protocols": { + "mover-early-entry-v1-20260924": "PR #162, merged to main on 2026-09-24 as squash commit aa6fc79. 0 of 768 rule-exits pass development, and the holdout was not read. It is reused by reference only, pinned to main at aa6fc79 (paths under blueprints/us-equities/mover-early-entry/). rules.py (git blob 9be164c9e1a49a451d3a2381205349ed5003a993, sha256 d66156b3e57c6299f10e41a3f25c921c8be52571bd84417040190d926bf49450) supplies Bars.state, Bars.cum_dv and split_factor's arithmetic. sessions_io.py (git blob 0f7791a87685c73635efe94c226031d9afe7bd7c, sha256 2a753cb8f9164f48c1dceb952eb42eeae17811f636fddf5fca082731c708e59a) supplies official_price only, with the accepted labels of populations.official_prints. quotes.py (git blob caebf6f6a72fee6bb7ae637ebb698f8c5f6b3850, sha256 4116bf2809e0952f842f0e0fe35fd8b5ab11ea295637734ffe0d719ca72ee85e) supplies valid_quote and the page and stamp record format; its fetch_one (PROTOCOL_ASOF = '2026-09-21', and 5 retries that raise instead of stamping) and cmd_sample stamping are never used (populations.fetch_failures, universe_and_identity.asof). evidence/cost-table-run-v1.json has its hashes under cost_model.base_table. From evaluate_v2.py (git blob 2248580f4c673da3fcde25d85c6d20799ffa994e, sha256 d2007077ac7367e16061e49a645a54802fbb58c41d79d3b2da6417b0275b2fe4), only deviation D7's split and ex-dividend accounting is used, restated in populations.corporate_actions. v3 never modifies blueprints/us-equities/mover-early-entry/**. The pinned code is valid for 2021-2026 only: rules.EARLY_CLOSES starts in 2021, and fees.json has no SEC Section 31 row before 2020-01-01. v3 study code therefore never calls rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return or rules.net_return_np. It uses the v3 data files instead (run_discipline.data_files).", + "mover-followup-v2-20260924": "PR #162 at aa6fc79: no pass in families E (216), F (16) or P (12), and no holdout read. Not reused.", + "broad-universe-mover-v1-20260921": "Pinned at aa6fc79 for identity handling only: coverage.py (git blob d08c5ec2f11a70717f6e7470d611ab307c6efd63, sha256 ea88f5d7a9fd4d5f957b559ee4afc035134c7d267e4a1afa52c45afe3f48ebbd; dedupe_identity), collect_daily.py (git blob 8df7d3485b848847a372d264768945e3a6d8b9c1, sha256 4557cd9b0fc6aa6781a31748bfc97d53b6c9234648480177daa2f78bca2bbfaf; DATA_SYMBOL and the supplement method) and corporate_actions.py (git blob 2cf689b615f9c7da1426bffab5eab53b6979baa7, sha256 0e39b3eeb84db83c64aa777ed46756ae495b1581dbc6a41193604bbf132ce81e). Its collector requested bars at the provider's default asof (the request date), which v3 does not do (universe_and_identity.asof). Its outcome exposure is in exposure_registry.", + "catalyst-experiment-v1-20260920": "Its D-lane timing (16:15 decision, 09:35 entry, +60 s minimum latency, 5-session horizon) and its rule 'Do not infer a halt from missing data' are reused by reference. Its 2021Q1 ETF control is permanently inspected and is never a holdout.", + "mover-v3-draft-20260924": "The archived full draft. This core carries over its H1 and H3 definitions, restricted as item_set states, and the rules it names. The core governs wherever the two differ." + }, + "question": "Among US-equity extreme daily movers (population D), does MAX lottery conditioning (H1), or the overnight versus intraday split of the hold (H3), change the sign or size of the cost-net forward return? Continuation is one tested outcome, not the premise.", + "direction": "long only (a short side for the MAX-reversal prior is an open question; borrow, hard-to-borrow fees and SEC Rule 201 were not examined)", + "item_set": { + "family": "m = 5: two primary tests that are not tradable (H1-D, H3-c) and three tradable cells (H1-D-b_lane-low, H3-a, H3-b). Holm runs over these five items only.", + "items": [ + { + "id": "H1-D", + "hypothesis": "H1", + "role": "primary test (not a tradable cell)", + "statistic": "mean net(b_lane | high MAX21 tercile) - mean net(b_lane | low MAX21 tercile), population D", + "alternative": "less than 0", + "why_kept": "The direct test of H1. BCW 2011 predicts that high-MAX names underperform low-MAX names. The high-minus-low difference on one arm is the smallest statistic that tests that prediction. The 5-session b_lane arm is kept because it is the D horizon closest to BCW's holding period." + }, + { + "id": "H1-D-b_lane-low", + "hypothesis": "H1", + "role": "tradable cell", + "statistic": "mean net(b_lane | low MAX21 tercile), population D", + "alternative": "greater than 0", + "why_kept": "The study is long-only and asks about cost-net returns. The low-MAX tercile is the only H1 cell for which the prior predicts the best relative return, so it is the one cell whose pass would make the conditioning tradable. The high and middle cells (high has a negative prior, middle none) are reported descriptively with no p-value." + }, + { + "id": "H3-a", + "hypothesis": "H3", + "role": "tradable cell", + "statistic": "mean net(a_intraday), population D", + "alternative": "greater than 0", + "why_kept": "The intraday leg as a trade: one regular session, from 09:35 to 15:55 ET on t+1. With H3-b it answers whether either leg is tradable after costs. Neither leg has a sourced direction for movers, so both are kept." + }, + { + "id": "H3-b", + "hypothesis": "H3", + "role": "tradable cell", + "statistic": "mean net(b_overnight), population D", + "alternative": "greater than 0", + "why_kept": "The overnight leg as a trade: from 15:55 ET on t+1 to the first eligible quote from 09:30 ET on t+2. It is the counterpart of H3-a." + }, + { + "id": "H3-c", + "hypothesis": "H3", + "role": "primary test (not a tradable cell)", + "statistic": "mean over events of (mean of the 4 overnight leg log returns - mean of the 4 intraday leg log returns), gross, legs as in arms.c_decomposition", + "alternative": "not equal to 0 at development; after development, one-sided in the direction of the development estimate (multiple_testing.direction_lock)", + "why_kept": "The direct test of H3's 'versus'. It uses official prints only, so it needs no fill and no cost model. The literature (LPS 2019, ABJK 2022) concerns large, liquid names and gives no sign for movers, so the test is two-sided until development locks the direction." + } + ], + "tradable_cells": "H1-D-b_lane-low, H3-a and H3-b are tradable cells and nothing else. H1-D and H3-c are primary tests and are never tradable cells. The robustness requirement (statistics.robustness_required_for_a_pass) applies to the three tradable cells only.", + "dropped_from_the_full_draft": "Population I and its minute-trigger formulas: it is a second population with its own open finding (the listing-exchange opening print can come after decision minute m), and neither H1 nor H3 needs it. The H1 arms a_fast and c_session and their 12 cells (6 per population): they multiplied cells without adding a test of the conditioning. H1-I and the 9 I-lane cells. The holdout cap of 5 tradable cells: with three tradable cells it has no work to do, so the gate opens for every validated item. The 2017-2019 cost re-measure: it is replaced by the per-fill realized half-spread floor (cost_model.per_side), which removes a pre-outcome stage whose quote stamps were outcome prices. H2, H4 and H5 (and their data gates) and H6: deferred (README appendix)." + }, + "exposure_registry": { + "already_looked_at": [ + { + "window": [ + "2021-01-04", + "2023-12-29" + ], + "what": "mover-early-entry v1 and v2 development outcomes (1,012 rule-exits) on the +20% candidate universe; the v1 cost table's quote sample was drawn from these development sessions" + }, + { + "window": [ + "2024-01-02", + "2025-12-31" + ], + "what": "mover-early-entry v1 and v2 validation outcomes" + }, + { + "window": [ + "2026-01-02", + "2026-09-18" + ], + "what": "the reserved holdout of mover-early-entry v1/v2, unread by #162 and owned by it. It is also exposed at event level: extreme-gainer-audit computed T+1/T+5/T+20 forward returns for 957 package events from 2021 to September 2026. v3 excludes this window." + }, + { + "window": [ + "2017-01-01", + "2021-12-31" + ], + "what": "broad-universe-mover-v1 development segment on two lanes: the main universe (close >= $5, median dollar volume >= $20M) and the descriptive lane on the close >= $1, median dollar volume >= $2M universe (a strict superset). Through the C0_all_eligible control it computed H1/H5/H20 forward returns for every eligible symbol-session, which is the superset exposure of most D symbol-sessions (raw close >= $1, med20 of raw close x volume >= $2M, at least 60 prior contiguous bars, no has_gap). Its S2 volume-shock signal overlaps D's arms but is not a superset of them. This covers v3 development (2017-2019) and validation (2020) at aggregate level." + }, + { + "window": [ + "2022-01-01", + "2026-08-14" + ], + "what": "broad-universe-mover-v1 validation 2022-2023 and reserved 2024-01-01 to 2026-08-14, both lanes" + }, + { + "window": [ + "2019-12-02", + "2020-04-30" + ], + "what": "historical-simulation: six LEAN margin-stress scenarios on bundled SPY history; no mover-population outcome" + }, + { + "window": [ + "2021-01-01", + "2021-03-31" + ], + "what": "catalyst-experiment 2021Q1 ETF control, permanently inspected" + }, + { + "window": "2020 and 2022 single-name samples", + "what": "LEAN/AAPL 2020, FB/META 2022, SPY parity and corporate-action diagnostics; engine and data checks, not mover outcomes" + }, + { + "window": [ + "2015-01-02", + "2020-12-31" + ], + "what": "research-evaluation/plan.json (daily-etf-chronological-control-v1) development window: five-session momentum and cash outcomes on SPY, QQQ and IWM only; no single-name or mover outcome" + }, + { + "window": "2020-03-02 (one source day)", + "what": "catalyst-dataset/native-receipt.json: EDGAR 8-K header metadata for one source day; filing metadata only, with no ticker join, no price and no return" + }, + { + "window": [ + "2016-01-01", + "2026-09-22" + ], + "what": "pit-availability (blueprints/us-equities/pit-availability/plan.json and evidence/receipts/pit-availability.json, merged to main in #75 before aa6fc79). It read SEC submissions filing indexes for 2016-01-01 to 2026-09-22 and Benzinga news (Alpaca /v1beta1/news) in one March week per year for 2016-2026, for 20 symbols: AAPL, MSFT, AMZN, GOOGL, META, JPM, XOM, JNJ, WMT, NVDA, GME, AMC, PLUG, RIOT, MARA, SOFI, UPST, DKNG, CVNA and FUBO. The 2016-2020 weeks are 2016-03-14..20, 2017-03-13..19, 2018-03-12..18, 2019-03-11..17 and 2020-03-09..15. It also read daily raw and all-adjusted SIP bars and corporate actions in 10-day windows around the forward splits of AAPL, AMZN, GOOGL, NVDA and TSLA, including AAPL 4:1 and TSLA 5:1 on 2020-08-31, which fall inside v3 validation. Metadata and data checks only: no mover membership and no mover outcome was computed. It found the XOM and DKNG ticker-to-CIK identity breaks." + } + ], + "draft_author_exposure": "Before the full draft, the author read #162's README results and v1 dev/val summary aggregates (per-rule means, and the P5/P95 of the 07:00|G0.20|V1000000 rule-exits, used only for sigma planning), the v1 cost-table cells, the #162 review findings, the broad-universe README headlines (including the development | validation | reserved table of date-clustered mean net excess over C0 for S1-S5, for example S2 H1 development -0.33% over 2017-2021 on the main universe) and the sweep. Review rounds 2-4 read the lane definitions, method texts and code paths listed in the archived draft's review_record, and opened no outcome row. The core restructure round (2026-09-24) read the round-4 remaining findings, the identity section of broad-universe/README.md (the TWTR under-enumeration and the FB/META duplicate history), the function headers of broad-universe collect_daily.py and coverage.py, the asof lines of extreme-gainer-audit/audit.py, and pit-availability plan.json and its README header. It opened no outcome row, return or results file. No v3-defined statistic (a D outcome, a MAX21 tercile or an H3 leg) has been computed or seen for any window.", + "consequence": "2017-2020 is exposed at aggregate level through broad-universe development (both lanes) and the S2 headline above. Development (2017-2019) and validation (2020) are therefore screening stages only. Validation decides which items the holdout gate opens for, but a validation pass is labelled 'screened', never confirmatory. Only the prospective holdout can support a confirmatory claim. Beside each validation result, the same statistic is reported on the least-exposed slice: symbol-sessions that were not a broad-universe lane decision (med20 of raw close x volume < $2M, fewer than 60 prior contiguous bars, or has_gap, as broad-universe protocol.json defines them). The slice is a sensitivity, not a separate test.", + "update_rule": "Every later read of data in any v3 window before the freeze is appended here with its date and purpose. No quote at a v3 entry, exit or terminal-search stamp is fetched before the freeze. Daily bars and official prints are fetched before the freeze only into the sealed pre_freeze_access_path snapshot, and are opened only by the count-only code. Any other pre-freeze read is forbidden, not tolerated. If one happens anyway, it is appended here and recorded as a numbered deviation before the freeze, and it voids development screening: no item can be nominated at development, so every item scores p = 1 in the validation Holm run. A read of a v3-defined outcome in the validation window before the freeze voids validation. A read in the holdout window before the gate opens voids the holdout.", + "pre_freeze_access_path": { + "rule": "Before the freeze, data are fetched only by committed count-only code in the study tree (run_discipline.study_code). Raw pages go straight into a private snapshot sealed by sha256. They are never opened by a person or by any other code. The count-only code emits only counts and rates: no price, spread, return, gain, covariate value, event list or symbol-level row. Its committed output is the input to the freeze preconditions. The sealed snapshot is the frozen evaluation's screen input (universe_and_identity.request_shapes). Every per-event request is fetched after the freeze under run_discipline.fetch.", + "snapshot_contents": "(0) The enumerated symbol list (universe_and_identity.enumeration), with its source responses. (1) Screen data for every XNYS session s from 2016-01-04 to 2020-12-31: the daily bars (adjustment raw, split and all) and official prints (auctions, with their sessions_io.official_price source labels) of sessions s-1 and s for the whole enumerated list, requested with asof = s. Also the corporate-action records from 2016-01-01 to the fetch date, and the coverage-stamp quotes of quote_coverage_stamps, requested with asof = the stamp's session. (2) The same screen data for every session from 2024-11-01 to 2025-12-31, for the holdout breakpoints only (tercile_rule.holdout_breakpoints). No minute bar and no other quote is fetched before the freeze. Every part is pinned by sha256. Each page record keeps its request asof and its fetch date (the vintage) as separate fields.", + "outputs": "Per calendar year and listing exchange: symbol-sessions with a daily bar; with a raw close >= $1; with each official_price source label for the open and the close (populations.official_prints); with split, reverse-split, cash-dividend and merger records; with a suspected unadjusted split (populations.suspected_unadjusted_split). Also coverage-stamp quote counts (quote_coverage_stamps), enumerated symbols per source, and screen requests answered with no row (universe_and_identity.empty_responses).", + "quote_coverage_stamps": "Eligible-quote rates are measured only at stamps that are never v3 entry or exit stamps: 12:17 ET and 14:43 ET (12:17 and 12:43 ET on a 13:00 early close). The symbol-sessions are selected by int.from_bytes(sha256('mover-v3-coverage|' + symbol + '|' + session)[:8], 'big') % 500 == 0 among all symbol-sessions with a daily bar and a raw close >= $1, with no reference to event membership. A stamp has an eligible quote if the NBBO prevailing at the stamp is eligible (populations.fill_quote) and at most 1000 ms old, or if the first eligible quote update after the stamp arrives within 60 s. Per year, the code reports the stamps with an eligible quote, those without one, and those that are fetch-incomplete (populations.fetch_failures), and nothing else.", + "runs": "The count-only code runs once, from the committed study tree named in its output. Every run, including a failed or partial one, is appended to this registry with its revision, tree, date and output sha256. A rerun is allowed only after a failed or incomplete run, and never after its rates were read. The first complete run's output governs coverage_rule.", + "not_allowed": "No pre-freeze fetch or read of a quote at a D entry, exit or terminal-search stamp. No opening of the sealed snapshot's bars or prints by a person or by code other than the count-only code. No computation of D membership, MAX21 or any outcome." + } + }, + "universe_and_identity": { + "enumeration": "The symbol list for 2016-2020 and for the 2024-11-01 to 2025-12-31 breakpoint sessions is the union of two sources. (a) The Alpaca asset master: GET /v2/assets with status=active and with status=inactive, class us_equity, every current exchange including OTC (today's OTC listing says nothing about 2016-2020 listing, which exclusions rule 1 decides from session data). Symbols that fail collect_daily.py DATA_SYMBOL at aa6fc79 (CUSIP-style placeholders the bars API rejects) are removed. (b) Every old and new symbol named by an Alpaca corporate-action record (GET /v1/corporate-actions, every action type, 2016-01-01 to the fetch date). This is broad-universe's supplement method (collect_daily.py and corporate_actions.py at aa6fc79), except that renamed-away tickers are kept, because per-session asof resolves them. The list is written once, sealed with the pre-freeze snapshot and pinned by sha256, and its per-source counts are count-only outputs. Survivorship limit: a name delisted in 2016-2019 that is absent from both sources cannot be discovered (broad-universe/README.md: 'names delisted in 2016-2019 without a retained action cannot be discovered from entitled sources'), so development keeps the survivorship-limited label. Holdout collection batches enumerate from the same two sources, refreshed at each batch, united with every earlier batch's list.", + "asof": "Every request for bars, auctions (official prints) or quotes carries Alpaca's asof parameter set to a session date, never to the fetch date and never to the provider default. A screen request for session s uses asof = s. Every request for a D event decided on session t uses the event's as-known symbol with asof = t. The as-known symbol is the one under which the event appeared in the asof = t screen. Such requests cover the event's lookback (t-22 .. t-1), its minute bars, its entry, exit and terminal-search quotes and its official prints through the end of its hold or search window. A rename inside a hold or lookback is therefore resolved to the same issuer. The fetch date is kept as a separate vintage field in each page record. quotes.py fetch_one, whose PROTOCOL_ASOF is fixed at '2026-09-21', is never called.", + "request_shapes": "The screen data are the sealed snapshot for 2016-2020 and for 2024-11 to 2025-12, and the collection batches for the holdout. The gain condition (close[t], ref(t), f(t)), the $1 floor, suspected unadjusted splits at t and exclusions 1 and 2 are evaluated on the screen data. Only the symbol-sessions that pass them are candidates, and only candidates have per-event data fetched, with asof = t. Per-event data are the minute bars for DV_reg (the last membership condition), med20, the entry bar and cum_dv; the lookback daily bars and official closes for MAX21 and exclusion 3; quotes; and hold-period prints. If a per-event official close of t or t-1 differs from the screen value, the screen value governs membership, and the difference is counted per stage.", + "dedup": "Before membership, coverage.dedupe_identity at aa6fc79 (DUPLICATE_SERIES_MIN_SESSIONS = 20, DUPLICATE_SERIES_MIN_SPAN_COVERAGE = 0.98, survivor ranking by retained rename record, then price continuity, then active status, then lexicographic order) is applied to the screen table of session-s rows (each from its asof = s request). There is also a same-session guard. Two candidate D events on one session whose raw daily OHLCV (volume > 0) is identical on both s-1 and s are one event, kept under the higher-ranked symbol. Because the rows are identical, the choice cannot change any price. Pairs removed by either rule are counted per stage.", + "empty_responses": "HTTP 200 with no row is an empty response, never silently dropped, and never fetch-incomplete (fetch-incomplete is reserved for HTTP errors and timeouts, populations.fetch_failures). Per stage, the counts reported are: enumerated symbols with no bar in the stage; screen requests with no row; and, per item, event requests with no row. Each empty event request has a fixed consequence. An empty entry-quote window is a no-entry. An empty exit or search window is a terminal exit (populations.terminal_exits), which stays in the statistic. An empty official-print response is a missing print: no event at t, or a missing H3-c leg. An empty lookback makes MAX21 undefined, so the event is unassigned.", + "tests": "Synthetic tests with a recorded fake client cover: FB/META (a renamed-away ticker served with its successor's history under both symbols gives one event, not two); a ticker reused by a new issuer after a delisting (asof = t resolves the old issuer); a rename inside a 5-session hold (the as-known symbol with asof = t returns the whole hold); an empty exit response (a terminal exit, counted); and a request built with the fetch date as asof (refused)." + }, + "populations": { + "D_daily": "Symbol-sessions t with split-consistent official-close gain close[t]/ref(t) - 1 >= 0.20 - 1e-9, where close[t] is the accepted official close of populations.official_prints and ref(t) is as populations.formulas states. They also need an official close >= $1.00 and a regular-session dollar volume DV_reg(t) >= $1,000,000. A session without an accepted official close is not an event and is counted; there is no bar fallback. The decision is at 16:15 ET on t. Entry is at the first eligible quote at or after 09:35 ET on t+1 (and at or after decision + 60 s), with an entry timeout of 300 s; otherwise there is no entry, and it is counted. The b_overnight entry is the exception (arms.b_overnight).", + "formulas": "Restated exactly, so membership does not depend on unpinned code. f(t) = (raw close / split-adjusted close on the previous XNYS session) / (the same on t), from daily bars with adjustment=raw and adjustment=split. It is set to 1 when |f - 1| <= 1e-2 or any input is missing. This is rules.split_factor's arithmetic at aa6fc79, with adjustment=split closes in place of adjustment=all closes, a deliberate departure: with adjustment=all, a cash dividend of about 1% or more would be booked as a share change. ref(t) = the accepted official close of the previous XNYS session / f(t). If that session has no accepted official close, ref(t) is undefined and t is not an event (counted). DV_reg(t) = the summed vwap x volume of the 1-minute SIP bars of session t that start at or after 09:30 ET and before the scheduled close (session_calendar); a session with no such bar has DV_reg 0. The split-consistent official-close return of session j is r(j) = C(j) x f(j) / C(j-1) - 1, where C is the accepted official close; r(j) is undefined if either close is missing. MAX21(t) = the maximum over k = 1..21 of r(t-k). These are the 21 returns of sessions t-21 .. t-1, from the 22 accepted official closes of sessions t-22 .. t-1 (the event's own session t is not in the window). If any of those closes is missing, or any session t-21 .. t-1 is a suspected unadjusted split, MAX21 is undefined and the event is unassigned (counted); there is no maximum over fewer returns. The sizing window W(t) is the 20 sessions ending at t (its close is known at the 16:15 decision). med20 = the median of DV_reg over W(t). sigma_d = the sample standard deviation (ddof = 1) of log(1 + r(j)) over the 20 sessions j of W(t). If any DV_reg or r(j) in W(t) is undefined, the trade cannot be sized and is a counted no-fill. The entry bar is the last 1-minute SIP bar of the entry session (from 04:00 ET) that is complete at the fill-decision time X, meaning it starts at or before X - 60 s. If there is none, the entry-bar cap is 0 and the trade is a counted no-fill. A synthetic test fixes each formula, including a split day, a missing previous close, a missing close inside the MAX21 window, a suspected unadjusted split inside it and an entry with no completed bar.", + "official_prints": "The accepted source labels of sessions_io.official_price at aa6fc79 are these. The listing exchange is the exchange of the largest condition-O opening print; a session with no O print has no listing exchange. Official open: opening_print_listing_exchange only. Official close: closing_print_listing_exchange or official_close_listing_exchange only. A closing_print_other_exchange, or a session with no O print, counts as no official close and is counted per year. The same rule applies to close[t], to the previous close in ref(t) and to every H3-c leg. For D, the O print of session t precedes the 16:15 ET decision by construction. The synthetic suite has one case per source label, including the rejected label and a session with a closing print but no opening print.", + "suspected_unadjusted_split": "Applied before membership. For session t, let r = raw daily close[t] / raw daily close of the previous XNYS session. Session t is a suspected unadjusted split if f(t) = 1 and |r - n| <= 0.01 x n or |r - 1/n| <= 0.01 / n for some integer n in 2..100. Such a session is not an event. Any trade whose hold spans t (from entry session to exit session) is excluded, and any event whose MAX21 window contains t is unassigned. All three are counted per stage. The rule also removes genuine moves within 1% of an integer multiple (for example +100%). That loss is preregistered and counted, not corrected.", + "exclusions": "(1) Not exchange-listed on the session: a symbol-session is eligible only if it has a listing exchange under populations.official_prints, so OTC-only trading is excluded by as-known session data. The current Alpaca asset exchange and tradable flags are never used. (2) Warrants, units and rights, by this literal rule (candidates.py at aa6fc79): exclude the symbol if (len(symbol) == 5 and re.search('[A-Z]{4}[WUR]$', symbol)) or re.search('\\.(WS|U|R|W)', symbol) (Python re, unanchored search), or if it contains '/'. (3) Symbols with fewer than 21 prior XNYS sessions with a daily bar. Each exclusion is counted per stage, with a synthetic test per rule. There is no halt exclusion (halts).", + "fetch_failures": "The frozen fetch code is v3-owned and lives in the study tree. It writes page and stamp records in quotes.py's format at aa6fc79. Every request is retried up to 3 times, with waits of 1 s, 4 s and 16 s. A request that still fails with an HTTP error, a URLError or a timeout makes its stamp fetch-incomplete (a stamp_incomplete record). The run continues and does not raise. The trade that needs the stamp is excluded, not counted as a no-entry or a terminal exit, and the count is reported per item. If more than 1% of the stamps a stage requires are fetch-incomplete after the single re-fetch of run_discipline.fetch, that stage is void: every item scores p = 1 and is labelled underpowered there, with the reason stated. The rate is committed before any outcome of the stage, and a void is final. Synthetic tests cover an exhausted URLError and a timeout (each becomes stamp_incomplete, and the pool run completes), a success on the third retry, and an empty response (not fetch-incomplete).", + "session_calendar": "XNYS from exchange_calendars 4.13.2 (the version rules.py used for 2021-2026), for 2016-01-01 through the end of the last possible holdout extension. The session list, with each session's scheduled open and close, is written to the v3 data file data/session-calendar.json and pinned by sha256 at the freeze (run_discipline.data_files). A 'session' anywhere in this protocol is every scheduled XNYS trading session, early closes included. The pinned file cannot know about unscheduled future closures, such as a national day of mourning. Such a closure is removed only by a line in data/session-calendar-amendments.jsonl that cites its primary source (an NYSE notice), is committed and pushed, and is logged in the access log (purpose 'amend') before the affected session. It may concern only a date on or after the freeze session. The freeze session and N0 keep the dates computed at the freeze, and if N0 itself closes, the holdout starts at the next session. The 252-session holdout and each 63-session block count actual sessions. Synthetic tests use the 2017-07-03, 2017-11-24, 2018-11-23, 2019-12-24 and 2020-11-27 early closes and a removed session inside the holdout.", + "session_times": "Every '15:55 ET' in this protocol means the scheduled close (session_calendar) minus 5 minutes (12:55 ET on a 13:00 early close). Eligible quotes are regular-session quotes, at or after 09:30 ET and before the scheduled close. D decisions stay at 16:15 ET on early-close sessions.", + "fill_quote": "For a fill at or after time X, the quote is the NBBO prevailing at X (the latest quote update stamped at or before X) if it is eligible and X minus its timestamp is at most 1000 ms. Otherwise it is the first eligible quote update stamped after X, with the fill time equal to its timestamp. Eligible means bid > 0 and ask > bid (locked and crossed quotes excluded, as quotes.py valid_quote at aa6fc79 does) and regular session (session_times). Timeouts run from X. Synthetic tests cover a fresh prevailing quote, a stale one, a locked one and no quote inside the timeout.", + "fill_price": "Every entry and non-terminal exit fills at the mid (bid + ask) / 2 of the eligible quote that fills it, so gross returns are mid to mid. cost_model.per_side adds the per-side cost on top.", + "terminal_exits": { + "search_window": "An exit fills at the first eligible quote at or after its planned stamp. If none arrives before the scheduled close, the search continues session by session through the scheduled close of the 5th XNYS session after the planned exit session E (E+5). It never goes past the last session of the trade's segment (chronology.boundaries). A fill inside the window is a normal exit, flagged delayed and counted.", + "terminal": "A held position with no eligible quote in its search window is terminal. This covers a delisting, a move to OTC-only trading (outside the SIP NBBO), an SEC trading suspension, a halt that does not resume inside the window, and an exit request answered empty (universe_and_identity.empty_responses).", + "booking": "Terminal-merger: if a retained corporate-action record shows a cash or stock merger of the symbol effective on a session from the entry session through the end of the search window, the exit is booked at the last eligible bid of the hold. That is the bid of the latest eligible quote stamped after the entry fill and at or before the planned exit stamp, and merger arbitrage keeps it near the consideration. Fees are charged on that raw bid, with no further spread cost and no impact. Terminal-zero: every other terminal trade is booked at an exit value of 0, a total loss of the position, with no exit fees. This is the stated choice. Booking at the last eligible bid for all terminal trades is a sensitivity only.", + "in_statistics": "Terminal trades stay in every primary statistic, in count_unit and in the minimum samples. For each item and stage, the counts of delayed, terminal-merger and terminal-zero exits are reported. H3-c has no position: an event whose hold loses a leg this way lacks its 8 legs, is excluded from H3-c, and is counted as terminal in H3-c's report.", + "segment_end": "A trade censored at a segment end (chronology.boundaries) searches from its censoring stamp through the scheduled close of the segment's last session. With no eligible quote by then, it is terminal under the same booking and flagged censored-terminal.", + "tests": "Synthetic tests: a delisting inside a 5-session hold (terminal-zero); a delisting on a segment's last session (censored-terminal); a halt that resumes on E+3 (delayed exit); a halt that has not resumed by E+5 (terminal-zero); a cash merger effective inside the hold (terminal-merger at the last eligible bid); an empty exit response; and a check that each case is counted and kept in the statistic." + }, + "corporate_actions": "For every multi-session arm (b_lane, b_overnight) and each H3-c leg, deviation D7's method is restated. The exit uses the raw exit mid (the raw official print for H3-c). Shares change by the product of the split factors f(j) (populations.formulas) over the sessions after the entry session through the exit session. The rest of the adjustment=all close-to-close return over the hold is booked as cash per original share, without spread or fees. Fees use the actual exit shares and the raw exit price. H3-c legs use log((raw end print x split factor + cash) / raw start print). Mergers follow populations.terminal_exits. Synthetic tests, passed before the freeze, cross a split, an ordinary ex-dividend date and a special cash dividend of 5% (booked as cash with f = 1, never as a share change, and not flagged as a suspected unadjusted split).", + "halts": "No halt source is used, historically or prospectively, so no trade set depends on a later data purchase. As in catalyst-experiment, a halt is never inferred from missing data. An entry fills only at an eligible quote, and an entry with none inside its timeout is a counted no-entry. An exit waits for an eligible quote and becomes terminal after its search window (populations.terminal_exits). Limitation: a trade that sits through a short unobserved halt is not flagged.", + "parameter_provenance": "G = 0.20 and V = $1M are v1's loosest gain and middle volume floor. They were fixed for sample size after v1's all-negative dev/val grid was seen (a disclosed exposure) and are not tuned on any v3 window." + }, + "arms": { + "b_lane": "H1: entry as D_daily; exit at the first eligible quote at or after 15:55 ET on the 5th holding session (the entry session t+1 is holding session 1), under populations.terminal_exits", + "a_intraday": "H3-a: entry as D_daily (09:35 ET on t+1, timeout 300 s); exit at the first eligible quote at or after 15:55 ET on the entry session. Its entries are the b_lane entries of all D events, assigned or unassigned to a tercile. The overlap is intended and reported.", + "b_overnight": "H3-b: entry at the first eligible quote at or after 15:55 ET on t+1, timeout 300 s; otherwise no entry (counted). Exit at the first eligible quote at or after 09:30 ET on t+2, under populations.terminal_exits", + "c_decomposition": "H3-c, diagnostic only, not traded, gross, from official prints only: over sessions t+1 .. t+5 of every D event (no entry fill needed), the overnight legs k = 1..4 run from the official close of t+k to the official open of t+k+1, and the intraday legs k = 2..5 run from the official open to the official close of t+k. The partial first-session leg is excluded, so each event has 4 legs of each kind, and together they decompose official close(t+1) to official close(t+5). An event missing any of the 8 legs is excluded from H3-c and counted; no bar or quote substitutes, and no event is averaged over fewer legs.", + "stops_and_sizing": "No stops. Research sizing is 1x equal notional, capacity-capped (cost_model.fix_2_impact_and_capacity)." + }, + "coverage_rule": { + "scope": "All five items, with one set of kept years per stage. It is decided at the freeze from the count-only outputs (exposure_registry.pre_freeze_access_path), before any outcome, and recorded beside multiple_testing.data_status_at_freeze.", + "no_fallback": "A session or symbol without an accepted official print or an eligible quote stays a counted non-event, no-entry, terminal or excluded trade under its own rule. No bar, quote or other-exchange substitute is ever used.", + "year_rule": "A calendar year of 2016-2020 is dropped from its stage entirely (every session of that year) if its accepted official-close rate is below 90% (symbol-sessions with an accepted official close / symbol-sessions with a daily bar and raw close >= $1), or if its eligible-quote rate at the coverage stamps is below 80%. If 2016, the warm-up year, is dropped, events of early 2017 stay unassigned under tercile_rule.minimum until 60 prior events accrue. The thresholds are design choices, not measured values.", + "item_rule": "If fewer than 2 development years remain, or 2020 is dropped, no item is tested: all 5 items score p = 1 at every stage, permanently, and that is the recorded result of this protocol version.", + "dropped_year_consequences": "(a) Breakpoints: a dropped year's sessions are skipped, and a dropped year's events never enter a trailing window. (b) Segments: each edge of a dropped year is a segment boundary under chronology.boundaries, with censoring and, after it, the embargo. (c) Bootstrap: the stage's kept sessions are concatenated in date order, and the circular block bootstrap wraps over that kept list. Each has a synthetic test with a dropped middle year.", + "limitation": "The coverage stamps sample the whole $1+ universe, not movers, so mover quote coverage may be lower. Per-stage no-entry, terminal and excluded counts are reported so that this is visible." + }, + "tercile_rule": { + "breakpoints": "For an event decided on session s, the breakpoints are the 1/3 and 2/3 empirical quantiles (numpy 'linear' method) of MAX21 over all D events with a defined MAX21 whose decision session lies in the 252 kept sessions strictly before s. Every D event counts, whether it entered, had no entry or had no fill. Every input is known at the decision.", + "holdout_breakpoints": "For a holdout event, the trailing window is the 252 most recent sessions before s that lie outside the gap from 2026-01-02 to N0 - 1. That gap covers #162's reserved holdout, which v3 never reads, and every session from 2026-09-21 to N0 - 1. The window therefore draws only on (a) the sessions ending 2025-12-31 (v1/v2 validation, already exposed), whose membership comes from pre-freeze snapshot part 2, and whose per-event MAX21 lookbacks and DV_reg minute bars (asof = t) are fetched under the first granted 'count' entry, and (b) earlier holdout sessions. It reads covariates and membership only, never an outcome. A synthetic test computes the breakpoints for the first holdout session from part (a) alone.", + "assignment": "Low if MAX21 <= the 1/3 breakpoint, high if MAX21 > the 2/3 breakpoint, middle otherwise. A value equal to a breakpoint goes to the lower tercile.", + "minimum": "If fewer than 60 prior D events lie in the trailing window, the event is unassigned: it is counted and reported, and it enters neither H1 item. The 2016 warm-up year supplies the first window." + }, + "chronology": { + "warmup": [ + "2016-01-04", + "2016-12-30" + ], + "development": [ + "2017-01-03", + "2019-12-31" + ], + "validation": [ + "2020-01-02", + "2020-12-31" + ], + "holdout": { + "kind": "prospective", + "window": "252 sessions (session_calendar) starting at session N0 = the 40th session after the session of the freeze. N0 is fixed by the freeze commit alone. The freeze commit is the first commit reachable from origin/main (first-parent history) in which this file's status is frozen. The repository squash-merges pull requests, so a commit that exists only on a PR branch is never the freeze commit. Its committer timestamp, converted to America/New_York, is the freeze time. The session of the freeze is the first XNYS session whose scheduled open is at or after the freeze time. D decisions made on session N0 - 1 enter on N0 and belong to the holdout. Development and validation run by committed automation started at the freeze. The commit that adds the validation results file (with its sha256 in the run log) must be reachable from origin/main before 09:30 ET on session N0, measured by the GitHub-recorded time it became reachable (the pull request's merged_at, or the push event for a direct push). If it is not, the holdout of this protocol version is void for every item; a new protocol version is required. There is no deferral.", + "extension": "If any carried item has fewer than its minimum holdout sample after 252 sessions (for H1-D, its smaller group), the holdout is extended in 63-session blocks, at most 2. The window is common to every carried item. A carried item still below its minimum after the second block is labelled underpowered. Each extension decision uses one 'count' read whose unit is count_unit, never an exit or outcome price.", + "count_unit": "Integers only, keyed as follows: 'H1-D:high' and 'H1-D:low' (one integer per group, since H1-D is a difference test), 'H1-D-b_lane-low', 'H3-a', 'H3-b' and 'H3-c'. For a tradable cell or an H1-D group, the count is the trades that pass membership and every exclusion, have an entry fill quote inside the entry timeout and filled notional >= $1,000, are not fetch-incomplete, hold no suspected unadjusted split and have the tercile the key needs. Their exit is irrelevant, because terminal exits stay in the statistic (populations.terminal_exits). For H3-c, the count is the events with all 8 official legs present. Only presence and exclusion flags, decision-time inputs (MAX21, med20, the entry bar) and entry-quote presence are evaluated. No mid, spread, return or exit value is computed or emitted. The same unit applies to every minimum_sample check at every stage, and the extension rule applies to the smallest key of each carried item. A synthetic test asserts that the count output carries exactly these keys with integer values, and that the count path calls no price or return function.", + "why_prospective": "No retrospective window free of prior mover outcomes remains: 2021-2025 is v1/v2 dev/val, 2026-01-02 to 2026-09-18 is #162's reserved holdout and is exposed at event level, 2017-2026-08-14 is broad-universe exposed at aggregate level, and 2016 is the warm-up.", + "accrual_exposure": "The primary holdout statistic of every item uses every holdout trade; nothing is excluded for exposure. Paper exposure is recorded only for symbol-sessions in which a paper order was submitted, logged at order time (symbol, session, order id, strategy) in the collection batch's accrual log before any outcome of that trade exists. A holdout trade is paper-exposed if any session from decision t through its exit is such a symbol-session. The result without paper-exposed trades is reported only as a sensitivity. If the paper-exposed fraction of any carried item's holdout trades exceeds 5%, the holdout is labelled contaminated for that item, and the item cannot then be labelled supported (confirmatory).", + "holdout_read": "A holdout read is exactly this: computing, from data of sessions N0 onward, any v3-defined outcome (an exit price paired with a v3 entry, a forward, net or leg return of a D event, a terminal booking) or any statistic built from one. Only such a computation inside a granted 'read' entry is permitted. A 'count' entry permits count_unit only. The following are not reads: (1) collection under holdout_gate.prospective_collection, including computing a batch's sha256; (2) live paper decisions and orders of any strategy, including the NautilusTrader paper runs and their decision streams, which are exposure and are logged in the accrual log; (3) dashboards and timers that publish metadata only (counts, liveness, freshness); (4) computing D membership without any outcome, by committed code whose output is logged in the accrual log. A synthetic test classifies one example of each case." + }, + "labels": { + "development": "disjoint from v1/v2; exposed at aggregate level through broad-universe development; screening only; survivorship-limited (names delisted in 2016-2019 without a retained corporate action cannot be discovered)", + "validation": "disjoint from v1/v2; exposed at aggregate level through broad-universe development and the S2 headline the author read; screening only, never confirmatory; the 2020 COVID regime is never dropped", + "holdout": "prospective; untouched only if the validation results commit was reachable from origin/main before 09:30 ET on N0, no holdout read preceded a committed granted access-log entry, every holdout count and read ran from the governing validation run's study tree (run_discipline.study_code), every collection batch has its committed accrual log, and no carried item's paper-exposed fraction exceeds 5%" + }, + "boundaries": "Segments are assigned by entry session (for H3-c, by session t+1). A trade whose planned exit falls after its segment's last session is censored: it exits at the first eligible quote at or after 15:55 ET on that last session (populations.terminal_exits.segment_end), is flagged, stays in its segment's statistics and is reported as a count. H3-b makes no entry on a segment's last session (counted). An H3-c event whose legs cross the segment end is excluded from H3-c and counted. Embargo: multi-session items (H1-D, H1-D-b_lane-low, H3-b, H3-c) give up the first 6 sessions of validation, and the first 6 kept sessions after a dropped year, as entry sessions. This keeps the censored trades of the earlier segment clear of later entries. Same-session H3-a needs no embargo. There is no embargo at N0: the holdout follows years after validation, no v3 trade is open in the sessions before N0 (those sessions are lookback inputs only), and D decisions on N0 - 1 enter on N0. A synthetic test covers the embargo after development, the embargo after a dropped year, and a 5-session trade entered on N0 that is kept." + }, + "outcome_reporting": { + "labels": { + "passes_stage": "The item passes its stage rule. The pass has a stage-specific name: 'nominated' at development (the screen), 'screened' at validation (Holm in the 5-item family, the minimum sample and, for a tradable cell, the robustness means), and 'supported (confirmatory)' at the holdout (the same rule). Only the holdout label is a claim.", + "not_supported_mde_excluded": "The stage has at least its minimum sample, the item does not pass, and the one-sided bootstrap-percentile confidence bound (from the same B draws, numpy 'linear' quantile) at Bonferroni level a = 0.05/5 = 0.01 excludes an effect of the preregistered MDE for the realized n (minimum_detectable_effect). For the alternative 'less than 0' (H1-D), the bound is the a-quantile of the bootstrap statistics, and the MDE is excluded when that bound is greater than -MDE. For the alternative 'greater than 0' (every tradable cell, and H3-c when locked positive), the bound is the (1 - a)-quantile, and the MDE is excluded when it is less than +MDE; H3-c locked negative mirrors the 'less than 0' rule. Two-sided cases use a = 0.05/10 and require both conditions: the lower bound is greater than -MDE and the upper bound is less than +MDE. They are H3-c at development, and H3-c at later stages when multiple_testing.direction_lock found no direction. The fixed Bonferroni level is used because Holm's step-down level is not defined for items after the first retained hypothesis.", + "underpowered": "The stage is below its minimum sample, or the stage is void (populations.fetch_failures), or the item neither passes nor excludes the MDE." + }, + "hypothesis_verdict": "H1 is not supported if H1-D is labelled not_supported_mde_excluded and H1-D-b_lane-low does not pass. H3 is not supported if H3-c is labelled not_supported_mde_excluded and neither H3-a nor H3-b passes. A verdict is computed at validation and reported as a screening verdict. For a hypothesis with a carried item, it is also computed at the holdout and reported as the confirmatory verdict. Each uses the labels of its own stage only.", + "validation_reporting": "Every item's validation estimate, interval and label are computed and reported descriptively, whether or not development nominated it. Only nominated items receive a Holm p; the rest score p = 1 in the Holm run. The H1 high and middle b_lane cells are reported descriptively at every stage, with no p-value.", + "qualifiers": "Contamination, lineage and deviation status are qualifiers attached to a label, never labels of their own. A holdout item that meets the pass rule while its holdout is contaminated is labelled 'screened (contaminated holdout)', never 'supported (confirmatory)'. An item labelled 'supported (confirmatory)' that is not lineage-confirmed (multiple_testing.lineage_sensitivity) carries 'lineage-unconfirmed'. A stage decided by the one permitted post-deviation rerun (run_discipline.deviations) carries 'post-deviation'.", + "rule": "Only these three labels are used, with the qualifiers above. A result from a deviated study tree other than the permitted rerun is reported beside the governing result with no label at all. A miss is never reported as 'falsified' or as proof of no effect, and 'not supported' always states the size it excludes. A result in the direction opposite to a one-sided alternative is reported descriptively with its estimate and interval and supports no claim." + }, + "multiple_testing": { + "family": "One family of m = 5 items (item_set): H1-D, H1-D-b_lane-low, H3-a, H3-b, H3-c. H6 and the deferred H2, H4 and H5 are not in it.", + "item_ids": [ + "H1-D", + "H1-D-b_lane-low", + "H3-a", + "H3-b", + "H3-c" + ], + "item_id_rule": "item_ids is the complete list, in bytewise ASCII order. That order is used for stream spawning, for the final tie-break and for the validation results file.", + "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout, over these 5 items only. An item not carried to a stage, or not tested for lack of data, scores p = 1 there. The validation results file contains all 5 validation p-values keyed by item_ids (1 for items not nominated or not tested); the evaluator refuses an incomplete file.", + "direction_lock": "H3-c is two-sided only at development, where the screen uses the two-sided p (statistics.p_value). The development results file records the sign of its development estimate. Validation and the holdout then use the one-sided test in that recorded direction, in the same Holm slot. Missing or zero estimate: if the governing development run wrote no H3-c estimate (an incomplete run, or no event with all 8 legs), or the estimate is exactly 0, no direction is recorded. H3-c then scores p = 1 at validation and at the holdout, and its label there uses the two-sided bound at a = 0.05/10 (outcome_reporting.labels). A significant result in the direction opposite to the locked one is reported descriptively and cannot pass. Synthetic tests cover a positive, a negative, a zero and a missing development estimate.", + "data_status_at_freeze": "At the freeze, H1 and H3 are each recorded as either 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently', by coverage_rule. Data from a new source, or a new historical range acquired after the freeze, can support only a new protocol version with its own family (v3.1+), never this family's Holm run. The prospective Alpaca SIP data collected under holdout_gate.prospective_collection are not new data in this sense.", + "development_screen": "Unadjusted p <= 0.05 with the minimum sample: one-sided in the stated direction for H1-D, H1-D-b_lane-low, H3-a and H3-b, and two-sided for H3-c. Development only nominates items for validation.", + "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) and broad-universe-mover-v1's 60 predeclared signal variants on overlapping or other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 60 + 5) = 4.64e-5. The archived 35-item draft computed nothing and is not counted. broad-universe's controls and descriptive lane have no pass rule and are not counted, which is a stated limitation. Each p is reported beside the normal-tail p of statistics.normal_tail_p. With B = 100,000, a bootstrap p at 4.64e-5 means at most 3 null-side draws, so the bootstrap p alone cannot resolve this level. An item is lineage-confirmed only if both its bootstrap p and its normal-tail p are at or below 4.64e-5.", + "tie_breaks": "Where items tie on a p (for example at the floor 1 / (B + 1)) in Holm ordering or in reporting, ties are broken by the normal-tail p, then by item id in bytewise ASCII order.", + "tests_not_in_family": "BH (q = 0.10) diagnostics across year, price-tier and tercile splits are reported but can never open the holdout." + }, + "minimum_sample": { + "tradable_cell": { + "development": 300, + "validation": 150, + "holdout": 150 + }, + "difference_test_per_group": { + "development": 150, + "validation": 100, + "holdout": 100 + }, + "H3-c": "the tradable_cell minima, counted as events with all 8 legs", + "rule": "A stage below its minimum reports the item as underpowered; it cannot pass that stage. Minima are counted in chronology.holdout.count_unit at every stage. For H1-D, each group (count_unit keys H1-D:high and H1-D:low) must reach the per-group minimum." + }, + "minimum_detectable_effect": { + "formula": "MDE = (z_alpha + z(0.80)) x sigma x sqrt(DEFF) x sqrt(1/n) for one cell or H3-c. For H1-D, sqrt(1/n1 + 1/n2) replaces sqrt(1/n), with n1 and n2 the realized high and low group sizes. z_alpha = z(1 - 0.05/5) = 2.326 for one-sided tests, including H3-c after development. It is z(1 - 0.05/10) = 2.576 for H3-c at development and whenever H3-c has no locked direction. The MDE is computed from the planning sigma below and the realized n, and it is never re-estimated from outcomes.", + "z_sum_one_sided": 3.168, + "z_sum_two_sided": 3.4175, + "design_effect_DEFF": 1.5, + "sigma_by_item": { + "rule": "Every item has an entry: [planning sigma, MDE form]. 'difference' uses sqrt(1/n1 + 1/n2), 'cell' uses sqrt(1/n), and 'paired' is a one-sample paired statistic with sqrt(1/n).", + "H1-D": [ + 0.35, + "difference" + ], + "H1-D-b_lane-low": [ + 0.35, + "cell" + ], + "H3-a": [ + 0.25, + "cell" + ], + "H3-b": [ + 0.15, + "cell" + ], + "H3-c": [ + 0.1, + "paired" + ] + }, + "sigma_provenance": "0.25 (the same-session close arm, H3-a) is (P95 - P5) / 3.29 from #162 v1 development aggregates for the 07:00|G0.20|V1000000 X1 exit (P5 -42.1%, P95 +39.5%), a normal approximation that understates fat tails. Those rule-exits entered at 07:00 ET in the pre-market, so the spread for 09:35 entries is not measured. The other values are assumptions with no measured source: 0.35 for the 5-session b_lane arm, 0.15 for one 15:55-to-09:30 overnight leg (H3-b), and 0.10 for the difference of two 4-leg means of log returns (H3-c). DEFF 1.5 for session clustering is also an assumption.", + "mde_percent_single_cell_one_sided": { + "n150": { + "sigma_0.15": 4.75, + "sigma_0.25": 7.92, + "sigma_0.35": 11.09 + }, + "n300": { + "sigma_0.15": 3.36, + "sigma_0.25": 5.6, + "sigma_0.35": 7.84 + }, + "n600": { + "sigma_0.15": 2.38, + "sigma_0.25": 3.96, + "sigma_0.35": 5.54 + } + }, + "mde_percent_H1-D_equal_groups_one_sided": { + "n100_per_group": 19.2, + "n150_per_group": 15.68, + "n300_per_group": 11.09 + }, + "mde_percent_H3-c": { + "one_sided_n150": 3.17, + "one_sided_n300": 2.24, + "two_sided_n300": 2.42, + "two_sided_n600": 1.71 + }, + "reading": "Only large effects can pass or be excluded. The literature's MAX effect (about 1% a month) is far below these MDEs, so a miss on H1-D will most likely be labelled underpowered. v3 is informative only about large mover-specific effects." + }, + "cost_model": { + "base_table": { + "path": "blueprints/us-equities/mover-early-entry/evidence/cost-table-run-v1.json", + "commit": "aa6fc791bbd4af3499317673989d2e90a522c257", + "git_blob": "197e71ada3c8249f2b49b168e4ce00a2cdf28628", + "sha256": "be50cbdfd75c5c4b0a66286bf9edb40c9ce49add719b6b5e1b0cdf93064eaa13", + "note": "The v1 table, unchanged from the PR branch commit 92062e8 to main at aa6fc79. D7 records that it reproduces byte for byte under the stamp-window check. v3 refuses a table with another hash." + }, + "lookup": { + "cell_key": "time_bucket|price_tier|dv_tier, per side, from that side's fill: the time bucket of the fill timestamp, the price tier of the fill price, and the dv tier of the session's cumulative dollar volume at the fill timestamp (rules.py Bars.cum_dv: the summed vwap x volume of the session's 1-minute bars from 04:00 ET that start before the timestamp).", + "buckets": "time buckets 04:00-08:00, 08:00-09:30, 09:30-10:00, 10:00-16:01 ET; price tiers < $2, $2-5, $5-20, >= $20; dv tiers < $1M, $1-5M, >= $5M" + }, + "fix_1_monotone_dollar_volume": { + "rule": "For each time x price bucket: hs'(>= $5M) = hs(>= $5M); hs'($1-5M) = max(hs($1-5M), hs'(>= $5M)); hs'(< $1M) = max(hs(< $1M), hs'($1-5M)). A less liquid tier is never cheaper than a more liquid one.", + "cells_changed_bps_before_after": { + "1|0|0": [ + 34.29, + 38.04 + ], + "1|1|0": [ + 27.97, + 44.16 + ], + "1|2|0": [ + 31.27, + 65.29 + ], + "2|2|0": [ + 31.62, + 61.83 + ], + "3|0|0": [ + 40.01, + 58.97 + ], + "3|1|0": [ + 27.04, + 57.09 + ], + "3|2|0": [ + 24.09, + 74.5 + ] + }, + "finding": "#162 review item costs-F1: cells under $1M dollar volume fell back to a time x price pool costing about half the $1-5M cell" + }, + "fix_2_impact_and_capacity": { + "notional": "Filled notional = min($20,000; 1% of med20; 10% of the entry bar's dollar volume, vwap x volume), with med20 and the entry bar as populations.formulas defines them. The excess is not filled. A trade with filled notional under $1,000, or one that cannot be sized, is a no-fill and is counted.", + "impact": "Impact per side (as a return) = c x sigma_d x sqrt(q), where q = filled notional / med20 and sigma_d is from populations.formulas, both known at the decision. The primary value is c = 1.0, with sensitivities at c = 0.5 and 2.0. The square-root form and the c grid are design choices bracketing the unknown." + }, + "per_side": "Cost per side = 1.25 x max(hs'(cell), h_fill) + impact, where h_fill = (ask - bid) / (ask + bid) is the realized half-spread of the fill quote as a fraction of the mid. The realized floor measures each fill's own period and quote, so the full draft's 2017-2019 re-measure of the table is not needed. The table-only cost (v1's convention) is a sensitivity. Terminal exits follow populations.terminal_exits.booking.", + "fees": "Fees come from data/fees-v3.json, pinned by sha256 at the freeze (run_discipline.data_files). It carries the SEC Section 31 and FINRA TAF rows in force from 2017-01-01 through the freeze, each with its primary source and quote. Rates announced after the freeze are appended only as lines of data/fees-v3-amendments.jsonl, from primary sources, committed and logged in the access log (purpose 'amend') before the first holdout count or read that uses their dates. They concern dates on or after the freeze session and never change an earlier line. #162 fees.json at aa6fc79 is not used. Alpaca commission is 0. A synthetic test looks up a 2017 and a 2019 sale and a post-freeze amended rate.", + "stress": "2 x (spread + slippage) with c = 2.0", + "break_even": "The cost multiple at which each tradable cell's mean net return reaches 0 is reported." + }, + "statistics": { + "estimator": "The trade-level mean net return (for H3-c, the event-level mean leg difference). The median and the three robustness statistics are also reported.", + "bootstrap": "A circular session block bootstrap, B = 100,000, over the stage's kept sessions concatenated in date order (sessions keyed by entry session; for H3-c, by t+1). Block length is 5 sessions for H3-a and 10 sessions for H1-D, H1-D-b_lane-low, H3-b and H3-c. H1-D resamples sessions jointly for both groups. The generator is numpy.random.Generator(PCG64), seeded per stage (stage_index 0 development, 1 validation, 2 holdout). Each stage uses numpy.random.SeedSequence(entropy = the first 8 bytes of sha256(frozen protocol id) read big-endian, spawn_key = (stage_index,)).spawn(5), and child i drives item_ids[i].", + "p_value": "One-sided p = (1 + #{b : stat_b on the null side of 0}) / (B + 1). Two-sided p = min(1, 2 x the smaller one-sided p).", + "normal_tail_p": "z = estimate / sd(the B bootstrap statistics, ddof = 1). For the alternative 'greater than 0', p_N = 1 - Phi(z); for 'less than 0', p_N = Phi(z); two-sided (H3-c at development) p_N = min(1, 2 x min(Phi(z), 1 - Phi(z))). Phi is the standard normal CDF, and p_N = 1 if the sd is 0. It is used only for lineage confirmation and tie-breaks.", + "robustness_required_for_a_pass": "A tradable cell's validation and holdout passes also need all three of these to be > 0, each computed from that stage's trades of that cell. (1) The winsorised mean: the trade-level mean after clipping each trade net return to [q01, q99], the numpy.quantile 'linear' 0.01 and 0.99 quantiles of those returns (1% in each tail). (2) The mean without the top 5 sessions: sum trade net returns by entry session, drop the 5 entry sessions with the largest sums (a tie is broken by dropping the earlier session first), and take the trade-level mean of the remaining trades. (3) The mean of per-session means: the equal-weighted mean, over entry sessions, of each session's trade-level mean. A synthetic test fixes each on a small table.", + "sensitivities": [ + "two-way (session, symbol) clustering", + "validation restricted to symbol-sessions that were not a broad-universe lane decision (exposure_registry.consequence)", + "table-only cost, and the stress cost", + "every terminal-zero trade rebooked at the last eligible bid of its hold (populations.terminal_exits)", + "censored segment-end trades removed (chronology.boundaries)", + "survivorship-limited label before 2020", + "holdout without paper-exposed trades (chronology.holdout.accrual_exposure)" + ] + }, + "holdout_gate": { + "opens_only_for": "Items that passed validation: Holm-adjusted validation p <= 0.05 in the 5-item family, the minimum validation sample (for H1-D, in each group) and, for a tradable cell (H1-D-b_lane-low, H3-a, H3-b), the three robustness statistics. There is no cap: every validated item is carried. A synthetic gate test carries three validated tradable cells including H3-a, and refuses one that fails a robustness statistic.", + "no_pass": "If nothing validates, the holdout is never labelled or read, and that is the result.", + "prospective_collection": "The frozen collector (in the study tree) collects forward from the session of the freeze. Each batch holds screen data only, for the whole enumerated list (universe_and_identity.enumeration, refreshed per batch): the daily bars (raw, split and all) and official prints of sessions s-1 and s for each collected session s, with asof = s, plus corporate-action records. Batches are sealed into a private snapshot whose sha256 is committed per batch with its accrual log. No quote and no minute bar is collected, and nothing selects symbols by membership. Everything else a holdout event needs (lookbacks, minute bars, entry, exit and terminal-search quotes, hold-period prints) is fetched only under a granted 'count' or 'read' entry, by the frozen fetch code with the event's asof = t (universe_and_identity.asof), and sealed before use. Sessions from the freeze to N0 - 1 serve only as lookback inputs. No outcome is computed from any batch before a granted 'read' entry.", + "evaluator_refusals": [ + "the protocol status is not frozen, or its sha256 differs from the frozen commit", + "the validation results file is missing, its sha256 differs from the committed one, or its commit did not become reachable from origin/main before 09:30 ET on N0", + "the validation results file is not from the governing validation run: its run-log entry is missing, or names a study tree other than the frozen study_code tree (the one post-deviation rerun that run_discipline.deviations lets govern excepted)", + "a holdout 'count' or 'read' runs from a study tree other than the tree of the governing validation run (which is the frozen study_code tree), except the single post-deviation rerun that run_discipline.deviations permits", + "the Python, numpy, pandas or exchange_calendars version differs from study/runtime.lock", + "data/session-calendar.json or data/fees-v3.json differs from its frozen sha256, or an amendment file is not a byte-prefix extension of the content logged at the previous run, or an amendment line concerns a date before the freeze session", + "the list of validated items is empty, or a requested item is not in it", + "the access log has no committed 'granted' entry of the matching purpose for this run", + "any collection batch lacks its committed accrual log, or a batch's log became reachable from origin/main at or after 09:30 ET of the session after the batch's last session", + "a previous holdout 'read' run exists (the holdout is read once; failed or partial runs stay in the log). A 'count' is not a read", + "a 'count' request asks for anything beyond chronology.holdout.count_unit, or comes other than once at the end of the 252 sessions and once at the end of each extension block" + ], + "access_log": { + "path": "blueprints/us-equities/mover-v3/holdout-access-log.jsonl", + "mode": "append-only JSON lines, committed and pushed before the action each authorizes", + "fields": [ + "utc", + "actor_role", + "protocol_id", + "protocol_sha256", + "code_revision", + "study_code_tree", + "runtime_lock_sha256", + "data_file_sha256s", + "validation_results_sha256", + "requested_items", + "purpose", + "decision (granted or refused)", + "snapshot_sha256", + "rows_read", + "results_sha256", + "exposed_symbol_sessions" + ], + "purposes": { + "collect": "a collection batch, rows_read 0, listing that batch's accrual exposure", + "amend": "one calendar or fee amendment line, with its primary source", + "count": "at the end of the 252 sessions and of each extension block: count_unit per carried item, used only for the extension decision; it may fetch the entry quotes, lookbacks, minute bars and prints the count needs, under run_discipline.fetch", + "read": "the single outcome read for carried items" + }, + "rule": "Any holdout read without a preceding committed granted entry of the matching purpose voids the holdout for every item. Refusals are logged too. The synthetic suite tests the 'count' path, including a test that it computes no exit price." + } + }, + "run_discipline": { + "study_code": "The frozen protocol records study_code = {revision: the git commit, tree: the git tree hash of blueprints/us-equities/mover-v3/study/ at that commit}. The tree holds the study code, the count-only code, the fetch code, the collector, the synthetic tests and study/runtime.lock. runtime.lock pins the Python, numpy and pandas versions and exchange_calendars 4.13.2. The tree holds no data file (data_files). Every run, fetch and evaluator call names the revision, tree and runtime it ran from. Development, validation and every holdout 'count' and 'read' run from this one tree. The holdout count and read must name the same tree as the governing validation run, and the evaluator refuses any other tree (holdout_gate.evaluator_refusals). The only exception is the single rerun that run_discipline.deviations permits.", + "data_files": "Data the study reads but may need to append to after the freeze live outside the study tree, in blueprints/us-equities/mover-v3/data/: session-calendar.json and fees-v3.json (the base files, pinned by sha256 in the frozen protocol), and session-calendar-amendments.jsonl and fees-v3-amendments.jsonl (append-only, possibly empty at the freeze). Appending a line never changes the study tree. The run log records each amendment file's sha256 and byte length at every run, and the evaluator refuses a file whose earlier logged content is not a byte prefix of its current content.", + "run_log": "blueprints/us-equities/mover-v3/run-log.jsonl, append-only JSON lines. Every run of the frozen automation gets one line: each stage's fetch and re-fetch, development, validation, and each holdout 'count' and 'read', including failed and partial runs. Each line records the utc start and end, stage, purpose, study_code revision and tree, runtime lock sha256, data-file sha256s, input snapshot sha256s, status (complete, failed or partial) and the sha256 of every results file written (partial files included). For the validation results commit, it also records the GitHub-recorded time it became reachable from origin/main. Each line is committed and pushed before the next run of any stage starts.", + "once": "Development is computed by one logged run and validation by one logged run. The first run of a stage that writes any outcome is that stage's governing run, whether or not it completes. The governing development run nominates, and the governing validation run's results file governs the holdout gate.", + "deviations": "Any change to the study tree after the freeze is a numbered deviation in blueprints/us-equities/mover-v3/deviations.json (cause, diff reference, new tree hash, affected stages), committed and pushed before any run from the new tree. A run from a deviated tree is reported beside the governing result, carries no label, and never governs a stage, the holdout gate or the extension decision. One exception: if a governing run from the frozen tree is incomplete because of an exception or an infrastructure failure, one rerun may govern, labelled 'post-deviation'. It must run under a deviation that changes no rule, and it must reproduce byte for byte every outcome the incomplete run had written. Otherwise: an incomplete development run decides development by its written outcomes alone (unwritten items score p = 1); an incomplete validation run voids the holdout of this protocol version; and an incomplete holdout read labels every carried item without a written outcome underpowered, with the reason stated.", + "fetch": "Before any outcome of a stage (development, validation, and each holdout 'count' or 'read'), the frozen fetch code fetches every quote window, minute bar and official print that stage needs, under universe_and_identity.asof. It derives stamps only from decision-time inputs and quote timestamps, including the terminal-search windows. It seals the result as a sha256-pinned private snapshot, and commits that sha256 and the stage's fetch-incomplete rate in the run log. At most one re-fetch is allowed: once, of only the fetch-incomplete stamps, before any outcome of that stage, committed the same way. After it, the 1% void rule is final. No outcome is computed from unsealed data.", + "synthetic_tests": "The evaluator refuses a results file from an unpinned revision or tree, a holdout count or read from a tree other than the governing validation run's, a runtime that differs from runtime.lock, an edited (not appended) amendment file, a stage with a second outcome-bearing run that no deviation covers, and an outcome computed before the stage's fetch rate is committed." + }, + "leverage_and_exits": "Research runs use 1x equal notional. Paper use of any passing rule follows the README's exit and leverage policy (at most one broker-held closing order per share, the leverage-schedule-v1-20260922 ceiling, and the leverage-ladder gate rows in catalogs/us-equities/gates-20260922.json, one rung at a time). H3-b stays at 1x in any later paper use until the 2x rung and its overnight cap are clean. The edge-shrink term stays 0 until a holdout pass.", + "freeze_preconditions": [ + "An independent pre-outcome review of this core draft from a different session and model family, with every finding resolved in a dated review_record entry", + "Direct full-text reads of Lou-Polk-Skouras 2019 and Akbas-Boehmer-Jiang-Koch 2022, or removal of every claim that depends on them (the two JFE DOIs resolve in Crossref, which is metadata only). H3-c is two-sided at development, so no test depends on their sign.", + "data/session-calendar.json generated from exchange_calendars 4.13.2 XNYS and data/fees-v3.json with sourced 2017-2020 (through the freeze) SEC Section 31 and FINRA TAF rows, both committed and pinned by sha256, plus both amendment files committed (empty is allowed)", + "The enumerated symbol list (universe_and_identity.enumeration) and pre-freeze snapshot parts 0-2, fetched under universe_and_identity.asof, sealed and pinned by sha256, with each page's asof and fetch date recorded", + "Count-only coverage checks through exposure_registry.pre_freeze_access_path only, with no outcome computed: accepted official-print rates per label, eligible-quote rates at the coverage stamps, corporate-action record counts, suspected unadjusted splits, enumerated symbols per source and empty screen responses, all for 2016-2020, with coverage_rule applied and H1 and H3 recorded as tested or not tested (multiple_testing.data_status_at_freeze)", + "The Alpaca market-data plan's documented historical-data rate limit recorded with its source, and an upper-bound fetch-time estimate for development and validation (event candidates from the count-only outputs x the per-event requests, at that limit). The estimate must leave a margin of at least 50% under the 40 sessions before N0. If it does not, the protocol is not frozen as written.", + "The study tree committed with runtime.lock and synthetic tests that pass before any outcome. The tests cover: every populations.formulas case, each official-print source label, the exclusion rules, a suspected unadjusted split, the fill-quote cases, the terminal-exit cases, the universe_and_identity cases (FB/META, ticker reuse, rename inside a hold, empty responses, fetch-date asof refused), split, ex-dividend and 5% special-dividend crossings, the 2016-2020 early closes, a calendar and a fee amendment, a 2017 fee lookup, the fetch-failure cases, a dropped middle year, segment-end censoring and the embargo (including none at N0), the direction lock (positive, negative, zero and missing estimate), the robustness statistics, the gate test, the count_unit keys, the holdout-read classification, the run_discipline refusals and the evaluator's holdout refusals. The code imports none of rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return, rules.net_return_np and quotes.fetch_one, and a test asserts this.", + "The exposure registry updated with everything the author saw since this draft", + "status set to frozen, frozen_before_outcomes true, run_discipline.study_code filled in, and the protocol sha256 recorded in a commit that becomes reachable from origin/main (chronology.holdout.window defines the freeze commit)" + ], + "review_record": [ + { + "date": "2026-09-24", + "round": 5, + "input": "The remaining findings of the round-4 rereview of the full draft (S1-S11 and X1-X10), after three fix rounds (75 fixes) in which high-severity findings went 4, 3, 4", + "resolution": "The draft was restructured into this core: H1 on population D (one difference test, one tradable cell) and H3 (two tradable arms and the leg decomposition), m = 5. Resolved in the core: S1 (run_discipline.study_code, data_files and deviations, holdout_gate.evaluator_refusals: the holdout count and read run only from the governing validation run's tree, and a deviated tree's result carries no label); S2 (populations.terminal_exits); S3 and X1 (universe_and_identity: per-request asof = the decision session with the as-known symbol, the enumeration source, identity dedup and empty-response counts); S4 (count_unit keys per H1-D group); S5 (no cap; the tradable cells are named); S6 (statistics.robustness_required_for_a_pass); S7 (chronology.boundaries: no embargo at N0); S8 (multiple_testing.direction_lock for a missing or zero estimate); S9 (run_discipline.data_files and the amendment files); S10 (statistics.normal_tail_p); S11 (the pit-availability registry entry); X5 (populations.fetch_failures: v3-owned fetch code); X9 (study/runtime.lock); X10 (exposure_registry.update_rule reworded). No longer applicable: X8 (population I is not in the core; for D the opening print precedes the decision). Deferred with their items (README appendix): X7 (H5), and X2, X3, X4 and X6 (H6). No finding rejected. Rounds 2-4 are recorded in the archived draft's review_record." + } + ], + "overturn": "Any headline result is overturned if a rerun on the same frozen inputs, study tree and runtime.lock does not reproduce it byte for byte, if an independent quote sample puts the cost table off by more than 2x, or if the access log shows a holdout read that no committed granted entry preceded." +} diff --git a/blueprints/us-equities/mover-v3/protocol-draft.json b/blueprints/us-equities/mover-v3/protocol-draft.json index 757b193a3..c579339d0 100644 --- a/blueprints/us-equities/mover-v3/protocol-draft.json +++ b/blueprints/us-equities/mover-v3/protocol-draft.json @@ -2,7 +2,8 @@ "schema_version": 1, "id": "mover-v3-draft-20260924", "kind": "draft_preregistration", - "status": "draft_pending_independent_pre_outcome_review", + "status": "superseded_by_core_draft_20260924", + "superseded_by": "blueprints/us-equities/mover-v3/protocol-core-draft.json (H1 on population D and H3 only, m = 5). This full draft is archived: it was never frozen or run, and no rule in it governs. H2, H4, H5 and H6 are deferred to v3.1+ (README appendix).", "frozen_before_outcomes": false, "drafted_at": "2026-09-24", "revised_at": "2026-09-24 (review fix round: exposure registry, H6 moved to its own engineering draft, trailing breakpoints, three-outcome reporting, leg-matched H3-c, halt-free H1-H3, H4 parent set, cost lookup pins, repin to merged #162); round 2 on the same day (review_record): directions locked after development for two-sided items, data status of H2, H4 and H5 fixed at the freeze, freeze before any quote is sampled, defined MDE and label bound, holdout breakpoint, start and count-read rules, segment-end and early-close rules, mid-quote fills, split and dividend accounting, H2 moved to gated, lineage count 1,107; round 3 on the same day (review_record): every holdout trade in the primary statistic with a contamination cap, a count-only pre-freeze access path, accepted official-print labels, a v3-owned XNYS calendar and fees file, a fixed N0 with no push discretion, H4 halt edge cases, preregistered item ids and stream spawning, extension scope, quote eligibility and fetch failures, H5 DTC scope, the lineage label rule, unadjusted-split exclusion, coverage consequences for H1 and H3, and restated membership formulas; round 4 on the same day (review_record): study-code pin, a run log and a deviation rule (run_discipline), sealed post-freeze fetches with one re-fetch, the full pre-freeze covariate snapshot, the coverage rule over every D and I item with kept-year rules, exact MAX21, sigma_d, med20 and dollar-volume formulas, split factors from split-adjusted bars, a narrow holdout-read definition, a mechanical freeze commit and N0, the holdout collection scope from the freeze session, prospective data commitments for H2, H4 and H5, count units, the MDE bound and sigma map, the literal exclusion regex, label qualifiers, calendar amendments and a fetch-time bound", diff --git a/manifests/evidence.json b/manifests/evidence.json index 0081c75dd..148d3b566 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -8448,18 +8448,23 @@ }, { "path": "blueprints/us-equities/mover-v3/README.md", - "sha256": "e8f961c99148bc11506cf70d05bf0fbf73ac52a6a9b6611272186cc372d6a3c5", - "bytes": 43952 + "sha256": "91e481ae33f1c29e5f6eecd0e6e89f2cf149782e0d96c4c2d0b422e56c676bfb", + "bytes": 48328 }, { "path": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json", - "sha256": "f56def6e032ed2cd076996e90d9dc242efdc005801e5e19718f4d57432c6cc85", - "bytes": 22300 + "sha256": "890ca29b1347fc63a6f9a9c7d13dcdb179c1cfcb0792027150d9fdc5db257499", + "bytes": 22836 + }, + { + "path": "blueprints/us-equities/mover-v3/protocol-core-draft.json", + "sha256": "bf53802185a34ba6acce8fd533692523cbd6ea71ca030d429700ea5113c00fb7", + "bytes": 81149 }, { "path": "blueprints/us-equities/mover-v3/protocol-draft.json", - "sha256": "e8d8b70798060ad6c6ab4fc13ca869afad95a3487b2248962659b5c3c97c7099", - "bytes": 99194 + "sha256": "a239aa326cb0f17509559a6ca8f4072ef4677ee767a0d258353a2ed9325f732a", + "bytes": 99446 }, { "path": "blueprints/us-equities/nanosecond-replay/README.md", From 89ff41d4bb4ec38d630a97f97906ce03fd58a212 Mon Sep 17 00:00:00 2001 From: Scout Date: Thu, 24 Sep 2026 04:29:43 -0400 Subject: [PATCH 7/8] Mover v3 review round 6: PR #167 threads (survivorship gate, access-log records) Applied to protocol-core-draft.json: - Survivorship-limited development (2017-2019) no longer gates anything: every item enters validation, H3-c is two-sided at every stage with no development direction lock, validation tercile breakpoints use validation sessions only, and the validation label cites the broad-universe 2020 survivorship count (563 names with last bar in 2020, inside 2021-2025's 512-756). - The holdout access log separates a pre-action authorization record from a post-action completion record (status, rows read, result hash, exposure), with failed and partial reads appended as completions. Already resolved in the core: paired H3-c MDE form, stage-own verdicts, entry bar complete before the fill decision. Deferred with their items (README appendix): H2's Item 1.01 corpus gate and three H6 findings. manifests/evidence.json: hashes for the changed files. Co-Authored-By: Claude Opus 5.5 (1M context) --- blueprints/us-equities/mover-v3/README.md | 24 +++-- .../mover-v3/h6-execution-parity-draft.json | 2 +- .../mover-v3/protocol-core-draft.json | 101 ++++++++++-------- manifests/evidence.json | 18 ++-- 4 files changed, 84 insertions(+), 61 deletions(-) diff --git a/blueprints/us-equities/mover-v3/README.md b/blueprints/us-equities/mover-v3/README.md index 631dd8b6c..d36e6e688 100644 --- a/blueprints/us-equities/mover-v3/README.md +++ b/blueprints/us-equities/mover-v3/README.md @@ -104,7 +104,7 @@ V = $1M are v1's loosest gain and middle volume floor, chosen for sample size af | H1-D-b_lane-low | H1, tradable cell | mean net(5-session hold, low MAX21 tercile) | greater than 0 | in a long-only study, the one H1 cell for which the prior predicts the best relative return; the high and middle cells are reported descriptively only | | H3-a | H3, tradable cell | mean net(enter 09:35, exit 15:55 on t+1) | greater than 0 | the intraday leg as a trade | | H3-b | H3, tradable cell | mean net(enter 15:55 on t+1, exit at the first eligible quote from 09:30 on t+2) | greater than 0 | the overnight leg as a trade; neither leg has a sourced sign for movers, so both are kept | -| H3-c | H3, primary test | mean of (mean of 4 overnight official-print log legs - mean of 4 intraday legs) over t+1 .. t+5 | two-sided at development, then locked to the development sign | the direct test of "overnight versus intraday". It uses official prints only, so it needs no fill or cost model | +| H3-c | H3, primary test | mean of (mean of 4 overnight official-print log legs - mean of 4 intraday legs) over t+1 .. t+5 | two-sided at every stage, never locked to the survivorship-limited development sign | the direct test of "overnight versus intraday". It uses official prints only, so it needs no fill or cost model | MAX21 is the largest of the 21 split-consistent official-close returns before the decision session. Its terciles use breakpoints from D events in the prior 252 sessions, so they are known at the decision. Research sizing is 1x equal @@ -115,8 +115,10 @@ the MDE and neither H3-a nor H3-b passes. A high-MAX outperformance is outside H claim. **Outcome labels.** Each test ends in one of three preregistered labels: a pass; *not supported*, with the excluded -effect size stated; or *underpowered*. A pass is named by its stage: *nominated* at development, *screened* at -validation and *supported (confirmatory)* only at the holdout. Holm runs over the five core items only. At the +effect size stated; or *underpowered*. A pass is named by its stage: a descriptive *development pass* that gates +nothing, *screened* at validation and *supported (confirmatory)* only at the holdout. Development (2017-2019) is +survivorship-limited, so every item enters validation whatever its development result, no development output feeds +validation or the holdout, and validation's tercile breakpoints use validation sessions only. Holm runs over the five core items only. At the minimum samples the detectable effects are roughly 2-19% (the MDE tables in the core draft), far above the literature's roughly 1% a month for MAX, so a miss on H1-D will most likely be labelled underpowered. v3 can detect only large mover-specific effects. @@ -136,7 +138,8 @@ is void. There is no deferral, so its start cannot be chosen after the results a the primary statistic. Paper orders in holdout symbol-sessions are logged at order time and removed only in a sensitivity, and more than 5% exposed trades labels an item's holdout contaminated. A holdout read is defined narrowly: computing any v3 outcome from holdout data outside a granted access-log entry. It voids the holdout rather -than removing trades. +than removing trades. The access log has two record kinds: an authorization committed before the action, and a +completion (status, rows read, result hash, exposure) appended after it, including for failed and partial runs. Development, validation and the holdout count and read all run from one study-code tree pinned at the freeze, with a pinned runtime lockfile. The evaluator refuses a holdout count or read from any other tree. A code change after the @@ -155,7 +158,7 @@ for, and only the prospective holdout can support a confirmatory claim. H1 rests on BCW 2011 (**survives**) and the relative-volume/price-return ranking in `catalyst-experiment/protocol.json`, which is structurally a MAX-like selection. H3 rests on LPS 2019 and ABJK 2022 -(*contested*). Their full texts must be read before the freeze, and H3-c is two-sided at development, so no test +(*contested*). Their full texts must be read before the freeze, and H3-c is two-sided at every stage, so no test depends on their sign. A VWAP-loss or ladder arm, from MarĂ³y's SSRN 5095349 (posted 2025-01-12, returned HTTP 403, never read), is **excluded from the confirmatory family**. @@ -163,7 +166,7 @@ never read), is **excluded from the confirmatory family**. | Need | Source | Status now | Gate | |---|---|---|---| -| PIT daily and minute SIP bars, quotes, official auction prices, corporate actions (core H1 and H3; H6 later) | Alpaca market data (SIP), already entitled; used by `broad-universe` and `extreme-gainer-audit` | bars **available** (daily from 2016-01-04, minute from 2016-01-01); quote and auction-print coverage for 2016-2020 **unverified** | a metadata coverage check before the freeze; private hashed snapshots pinned, each request with `asof` = its session and the fetch date kept as the vintage. Before 2020, survivorship is limited (`broad-universe/README.md`), so results for 2017-2019 carry that label | +| PIT daily and minute SIP bars, quotes, official auction prices, corporate actions (core H1 and H3; H6 later) | Alpaca market data (SIP), already entitled; used by `broad-universe` and `extreme-gainer-audit` | bars **available** (daily from 2016-01-04, minute from 2016-01-01); quote and auction-print coverage for 2016-2020 **unverified** | a metadata coverage check before the freeze; private hashed snapshots pinned, each request with `asof` = its session and the fetch date kept as the vintage. Before 2020, survivorship is limited (`broad-universe/README.md`), so results for 2017-2019 carry that label and gate nothing. For 2020 (validation), the broad-universe measure counts 563 names whose last bar falls in 2020, inside 2021-2025's 512-756 (`broad-universe/receipt.json`) | | 8-K Item 1.01 acceptance times (H2, deferred to v3.1+) | SEC EDGAR through the catalyst-provenance and catalyst-dataset recipes | **gated**: `catalyst.py` parses no Item and has no historical ticker-to-CIK map, and its availability rule fails every historical filing | a preregistered acceptance-time availability rule, an Item parser, an as-known CIK source and an index-completeness check, all before the freeze | | Shares outstanding and public float (screen only) | [SEC XBRL frames](https://www.sec.gov/search-filings/edgar-application-programming-interfaces) | **available** as a screen (*contested*: one misattributed source corrected) | frames give one last-filed value per entity. Each row carries `accn` but no filed date, and superseded vintages are absent. A name that clears the screen must be confirmed from companyfacts (or submissions by `accn`) for the filed date and the original vintage before the float counts as point-in-time | | Historical halt, LULD and status events (H4, deferred to v3.1+; halt fidelity in simulation) | Databento XNAS.ITCH normalized status schema (`databento/dbn`, Apache-2.0; *contested*: corrected from raw ITCH H/h/J messages to the normalized schema), cross-checked against the Nasdaq Trader halt history | **gated on paid data the user must buy** (Databento is metered). A free short-window pilot is possible | get a `Historical.metadata.get_cost` quote for one symbol-year; confirm the coverage start and the licence. Per the TotalView-ITCH 5.0 spec, halt and resume state (H, Q, T) covers every security traded on the Nasdaq execution system, including other exchanges' listings; the LULD pause state (P) and the auction collar (J) are for Nasdaq-listed names only, and a pause in another listing appears as a halt | @@ -437,7 +440,8 @@ into another: holdout; - the study tree committed with its runtime lockfile and synthetic tests before any outcome is computed, including terminal exits, renames and ticker reuse, split and ex-dividend crossings, early-close sessions, segment-end - censoring and the embargo, the direction lock and the evaluator's refusals. + censoring and the embargo, validation's independence from development outputs, the access-log authorization and + completion records and the evaluator's refusals. H6 has its own preconditions in `h6-execution-parity-draft.json`. Review rounds 2-4 of the full draft are recorded in `protocol-draft.json` (`review_record`); round 5, the restructure into the core, is in `protocol-core-draft.json`. @@ -478,7 +482,11 @@ in the core for H1 and H3, but they must be resolved again for each deferred ite [`h6-execution-parity-draft.json`](h6-execution-parity-draft.json), labelled not part of the alpha family. *Open findings:* X2 (no minimum stage length, so a one-session stage passes TOST trivially), X3 (arm b's trigger time is in practice its fill time, which biases the common basis), X4 (H6-e eligibility treats the arms asymmetrically) and - X6 (labels are undefined for a TOST test with no MDE). + X6 (labels are undefined for a TOST test with no MDE). From the PR #167 review: stage membership still follows the + closing session, so slow exits move to later stages (freeze entry cohorts and wait for each position's terminal or + censoring state); each arm is normalized to its own trigger, so a common frozen shadow trigger policy should be the + primary comparison; and the descriptive H6-c call count needs an inclusion list and an allocation rule for shared + reconciliation reads, batches and retries. - **Population I and the H1 a_fast and c_session arms.** *Reason:* they multiplied cells without adding a test of MAX conditioning. *Open finding:* X8 (listing eligibility used an opening print that can come after the decision minute). diff --git a/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json index 02a0559ae..b3afb59e5 100644 --- a/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json +++ b/blueprints/us-equities/mover-v3/h6-execution-parity-draft.json @@ -5,7 +5,7 @@ "status": "draft_pending_independent_pre_outcome_review", "frozen_before_outcomes": false, "alpha_family_member": false, - "label": "Execution study, not part of the mover-v3 alpha family. The alpha protocol is protocol-core-draft.json (m = 5). This draft uses none of its slots, and its results never open that protocol's holdout. Deferred to v3.1+ with open review findings X2 (no minimum stage length), X3 (trigger-time basis), X4 (asymmetric H6-e eligibility) and X6 (labels undefined for a TOST test); see the README appendix 'Deferred to v3.1+'.", + "label": "Execution study, not part of the mover-v3 alpha family. The alpha protocol is protocol-core-draft.json (m = 5). This draft uses none of its slots, and its results never open that protocol's holdout. Deferred to v3.1+ with open review findings X2 (no minimum stage length), X3 (trigger-time basis), X4 (asymmetric H6-e eligibility) and X6 (labels undefined for a TOST test), plus three from the PR #167 review (entry-cohort stages, a common frozen trigger policy, H6-c call attribution); see the README appendix 'Deferred to v3.1+'.", "drafted_at": "2026-09-24", "revised_at": "2026-09-24 (round 2: population named as a gated mechanics-only strategy, H6-c restated as descriptive because arm a needs fewer calls by construction, arm a configuration pinned, H6-e unit and trigger strata, GTC and tranche timing, calendar cap, paper smoke test); round 3 on the same day: trigger references per arm plus a common NBBO-bid basis, stop-market OCO leg, intention-to-treat H6-e unit, flat-symbol entry rule, entry cancel timeout and tranche-reject fallback, a pilot-based power precondition, and a disjoint, sealed population); round 4 on the same day: disjointness by a screen that uses no v3 definition, adapter preconditions for transport intents, safety reservations and reconciliation reports, session-based stages, a statistics section, the SIP feed pinned, and a 2-share minimum)", "base_commit": "aa6fc791bbd4af3499317673989d2e90a522c257", diff --git a/blueprints/us-equities/mover-v3/protocol-core-draft.json b/blueprints/us-equities/mover-v3/protocol-core-draft.json index a4d53b822..fe176dd04 100644 --- a/blueprints/us-equities/mover-v3/protocol-core-draft.json +++ b/blueprints/us-equities/mover-v3/protocol-core-draft.json @@ -61,8 +61,8 @@ "hypothesis": "H3", "role": "primary test (not a tradable cell)", "statistic": "mean over events of (mean of the 4 overnight leg log returns - mean of the 4 intraday leg log returns), gross, legs as in arms.c_decomposition", - "alternative": "not equal to 0 at development; after development, one-sided in the direction of the development estimate (multiple_testing.direction_lock)", - "why_kept": "The direct test of H3's 'versus'. It uses official prints only, so it needs no fill and no cost model. The literature (LPS 2019, ABJK 2022) concerns large, liquid names and gives no sign for movers, so the test is two-sided until development locks the direction." + "alternative": "not equal to 0 at every stage (multiple_testing.no_direction_lock)", + "why_kept": "The direct test of H3's 'versus'. It uses official prints only, so it needs no fill and no cost model. The literature (LPS 2019, ABJK 2022) concerns large, liquid names and gives no sign for movers, so the test is two-sided at every stage. It is not locked to the development sign, because development is survivorship-limited (chronology.labels.development) and gates nothing." } ], "tradable_cells": "H1-D-b_lane-low, H3-a and H3-b are tradable cells and nothing else. H1-D and H3-c are primary tests and are never tradable cells. The robustness requirement (statistics.robustness_required_for_a_pass) applies to the three tradable cells only.", @@ -144,7 +144,7 @@ ], "draft_author_exposure": "Before the full draft, the author read #162's README results and v1 dev/val summary aggregates (per-rule means, and the P5/P95 of the 07:00|G0.20|V1000000 rule-exits, used only for sigma planning), the v1 cost-table cells, the #162 review findings, the broad-universe README headlines (including the development | validation | reserved table of date-clustered mean net excess over C0 for S1-S5, for example S2 H1 development -0.33% over 2017-2021 on the main universe) and the sweep. Review rounds 2-4 read the lane definitions, method texts and code paths listed in the archived draft's review_record, and opened no outcome row. The core restructure round (2026-09-24) read the round-4 remaining findings, the identity section of broad-universe/README.md (the TWTR under-enumeration and the FB/META duplicate history), the function headers of broad-universe collect_daily.py and coverage.py, the asof lines of extreme-gainer-audit/audit.py, and pit-availability plan.json and its README header. It opened no outcome row, return or results file. No v3-defined statistic (a D outcome, a MAX21 tercile or an H3 leg) has been computed or seen for any window.", "consequence": "2017-2020 is exposed at aggregate level through broad-universe development (both lanes) and the S2 headline above. Development (2017-2019) and validation (2020) are therefore screening stages only. Validation decides which items the holdout gate opens for, but a validation pass is labelled 'screened', never confirmatory. Only the prospective holdout can support a confirmatory claim. Beside each validation result, the same statistic is reported on the least-exposed slice: symbol-sessions that were not a broad-universe lane decision (med20 of raw close x volume < $2M, fewer than 60 prior contiguous bars, or has_gap, as broad-universe protocol.json defines them). The slice is a sensitivity, not a separate test.", - "update_rule": "Every later read of data in any v3 window before the freeze is appended here with its date and purpose. No quote at a v3 entry, exit or terminal-search stamp is fetched before the freeze. Daily bars and official prints are fetched before the freeze only into the sealed pre_freeze_access_path snapshot, and are opened only by the count-only code. Any other pre-freeze read is forbidden, not tolerated. If one happens anyway, it is appended here and recorded as a numbered deviation before the freeze, and it voids development screening: no item can be nominated at development, so every item scores p = 1 in the validation Holm run. A read of a v3-defined outcome in the validation window before the freeze voids validation. A read in the holdout window before the gate opens voids the holdout.", + "update_rule": "Every later read of data in any v3 window before the freeze is appended here with its date and purpose. No quote at a v3 entry, exit or terminal-search stamp is fetched before the freeze. Daily bars and official prints are fetched before the freeze only into the sealed pre_freeze_access_path snapshot, and are opened only by the count-only code. Any other pre-freeze read is forbidden, not tolerated. If one happens anyway, it is appended here and recorded as a numbered deviation before the freeze, and it voids this protocol version's tests: every item scores p = 1 in the validation Holm run, so the holdout never opens. A read of a v3-defined outcome in the validation window before the freeze voids validation. A read in the holdout window before the gate opens voids the holdout.", "pre_freeze_access_path": { "rule": "Before the freeze, data are fetched only by committed count-only code in the study tree (run_discipline.study_code). Raw pages go straight into a private snapshot sealed by sha256. They are never opened by a person or by any other code. The count-only code emits only counts and rates: no price, spread, return, gain, covariate value, event list or symbol-level row. Its committed output is the input to the freeze preconditions. The sealed snapshot is the frozen evaluation's screen input (universe_and_identity.request_shapes). Every per-event request is fetched after the freeze under run_discipline.fetch.", "snapshot_contents": "(0) The enumerated symbol list (universe_and_identity.enumeration), with its source responses. (1) Screen data for every XNYS session s from 2016-01-04 to 2020-12-31: the daily bars (adjustment raw, split and all) and official prints (auctions, with their sessions_io.official_price source labels) of sessions s-1 and s for the whole enumerated list, requested with asof = s. Also the corporate-action records from 2016-01-01 to the fetch date, and the coverage-stamp quotes of quote_coverage_stamps, requested with asof = the stamp's session. (2) The same screen data for every session from 2024-11-01 to 2025-12-31, for the holdout breakpoints only (tercile_rule.holdout_breakpoints). No minute bar and no other quote is fetched before the freeze. Every part is pinned by sha256. Each page record keeps its request asof and its fetch date (the vintage) as separate fields.", @@ -201,10 +201,10 @@ "limitation": "The coverage stamps sample the whole $1+ universe, not movers, so mover quote coverage may be lower. Per-stage no-entry, terminal and excluded counts are reported so that this is visible." }, "tercile_rule": { - "breakpoints": "For an event decided on session s, the breakpoints are the 1/3 and 2/3 empirical quantiles (numpy 'linear' method) of MAX21 over all D events with a defined MAX21 whose decision session lies in the 252 kept sessions strictly before s. Every D event counts, whether it entered, had no entry or had no fill. Every input is known at the decision.", + "breakpoints": "For an event decided on session s, the breakpoints are the 1/3 and 2/3 empirical quantiles (numpy 'linear' method) of MAX21 over all D events with a defined MAX21 whose decision session lies in the 252 kept sessions strictly before s. Every D event counts, whether it entered, had no entry or had no fill. Every input is known at the decision. For an event of the validation stage, the window is restricted to validation sessions strictly before s, so no survivorship-limited development or warm-up event sets a validation breakpoint.", "holdout_breakpoints": "For a holdout event, the trailing window is the 252 most recent sessions before s that lie outside the gap from 2026-01-02 to N0 - 1. That gap covers #162's reserved holdout, which v3 never reads, and every session from 2026-09-21 to N0 - 1. The window therefore draws only on (a) the sessions ending 2025-12-31 (v1/v2 validation, already exposed), whose membership comes from pre-freeze snapshot part 2, and whose per-event MAX21 lookbacks and DV_reg minute bars (asof = t) are fetched under the first granted 'count' entry, and (b) earlier holdout sessions. It reads covariates and membership only, never an outcome. A synthetic test computes the breakpoints for the first holdout session from part (a) alone.", "assignment": "Low if MAX21 <= the 1/3 breakpoint, high if MAX21 > the 2/3 breakpoint, middle otherwise. A value equal to a breakpoint goes to the lower tercile.", - "minimum": "If fewer than 60 prior D events lie in the trailing window, the event is unassigned: it is counted and reported, and it enters neither H1 item. The 2016 warm-up year supplies the first window." + "minimum": "If fewer than 60 prior D events lie in the trailing window, the event is unassigned: it is counted and reported, and it enters neither H1 item. The 2016 warm-up year supplies the first development window. Validation events of early 2020 stay unassigned until 60 prior validation events accrue; they are counted." }, "chronology": { "warmup": [ @@ -229,20 +229,20 @@ "holdout_read": "A holdout read is exactly this: computing, from data of sessions N0 onward, any v3-defined outcome (an exit price paired with a v3 entry, a forward, net or leg return of a D event, a terminal booking) or any statistic built from one. Only such a computation inside a granted 'read' entry is permitted. A 'count' entry permits count_unit only. The following are not reads: (1) collection under holdout_gate.prospective_collection, including computing a batch's sha256; (2) live paper decisions and orders of any strategy, including the NautilusTrader paper runs and their decision streams, which are exposure and are logged in the accrual log; (3) dashboards and timers that publish metadata only (counts, liveness, freshness); (4) computing D membership without any outcome, by committed code whose output is logged in the accrual log. A synthetic test classifies one example of each case." }, "labels": { - "development": "disjoint from v1/v2; exposed at aggregate level through broad-universe development; screening only; survivorship-limited (names delisted in 2016-2019 without a retained corporate action cannot be discovered)", - "validation": "disjoint from v1/v2; exposed at aggregate level through broad-universe development and the S2 headline the author read; screening only, never confirmatory; the 2020 COVID regime is never dropped", + "development": "disjoint from v1/v2; exposed at aggregate level through broad-universe development; screening only; survivorship-limited (names delisted in 2016-2019 without a retained corporate action cannot be discovered); descriptive only, so it gates nothing (multiple_testing.development_screen)", + "validation": "disjoint from v1/v2; exposed at aggregate level through broad-universe development and the S2 headline the author read; screening only, never confirmatory; the 2020 COVID regime is never dropped; not labelled survivorship-limited: the broad-universe survivorship measure (known_inactive_or_supplement_last_bar_year_histogram in blueprints/us-equities/broad-universe/receipt.json, from the same asset-master and corporate-action sources as universe_and_identity.enumeration) counts 563 names whose last bar falls in 2020, inside the 2021-2025 range of 512-756, against 371 in 2019 and 25-167 in 2016-2018. That is a measured count on entitled sources, not proof of completeness. Validation uses no development data: its tercile breakpoints come from validation sessions only (tercile_rule.breakpoints), and no development output is an input (multiple_testing.development_screen)", "holdout": "prospective; untouched only if the validation results commit was reachable from origin/main before 09:30 ET on N0, no holdout read preceded a committed granted access-log entry, every holdout count and read ran from the governing validation run's study tree (run_discipline.study_code), every collection batch has its committed accrual log, and no carried item's paper-exposed fraction exceeds 5%" }, "boundaries": "Segments are assigned by entry session (for H3-c, by session t+1). A trade whose planned exit falls after its segment's last session is censored: it exits at the first eligible quote at or after 15:55 ET on that last session (populations.terminal_exits.segment_end), is flagged, stays in its segment's statistics and is reported as a count. H3-b makes no entry on a segment's last session (counted). An H3-c event whose legs cross the segment end is excluded from H3-c and counted. Embargo: multi-session items (H1-D, H1-D-b_lane-low, H3-b, H3-c) give up the first 6 sessions of validation, and the first 6 kept sessions after a dropped year, as entry sessions. This keeps the censored trades of the earlier segment clear of later entries. Same-session H3-a needs no embargo. There is no embargo at N0: the holdout follows years after validation, no v3 trade is open in the sessions before N0 (those sessions are lookback inputs only), and D decisions on N0 - 1 enter on N0. A synthetic test covers the embargo after development, the embargo after a dropped year, and a 5-session trade entered on N0 that is kept." }, "outcome_reporting": { "labels": { - "passes_stage": "The item passes its stage rule. The pass has a stage-specific name: 'nominated' at development (the screen), 'screened' at validation (Holm in the 5-item family, the minimum sample and, for a tradable cell, the robustness means), and 'supported (confirmatory)' at the holdout (the same rule). Only the holdout label is a claim.", - "not_supported_mde_excluded": "The stage has at least its minimum sample, the item does not pass, and the one-sided bootstrap-percentile confidence bound (from the same B draws, numpy 'linear' quantile) at Bonferroni level a = 0.05/5 = 0.01 excludes an effect of the preregistered MDE for the realized n (minimum_detectable_effect). For the alternative 'less than 0' (H1-D), the bound is the a-quantile of the bootstrap statistics, and the MDE is excluded when that bound is greater than -MDE. For the alternative 'greater than 0' (every tradable cell, and H3-c when locked positive), the bound is the (1 - a)-quantile, and the MDE is excluded when it is less than +MDE; H3-c locked negative mirrors the 'less than 0' rule. Two-sided cases use a = 0.05/10 and require both conditions: the lower bound is greater than -MDE and the upper bound is less than +MDE. They are H3-c at development, and H3-c at later stages when multiple_testing.direction_lock found no direction. The fixed Bonferroni level is used because Holm's step-down level is not defined for items after the first retained hypothesis.", + "passes_stage": "The item passes its stage rule. The pass has a stage-specific name: 'development pass' at development (descriptive only; it gates nothing, multiple_testing.development_screen), 'screened' at validation (Holm in the 5-item family, the minimum sample and, for a tradable cell, the robustness means), and 'supported (confirmatory)' at the holdout (the same rule). Only the holdout label is a claim.", + "not_supported_mde_excluded": "The stage has at least its minimum sample, the item does not pass, and the one-sided bootstrap-percentile confidence bound (from the same B draws, numpy 'linear' quantile) at Bonferroni level a = 0.05/5 = 0.01 excludes an effect of the preregistered MDE for the realized n (minimum_detectable_effect). For the alternative 'less than 0' (H1-D), the bound is the a-quantile of the bootstrap statistics, and the MDE is excluded when that bound is greater than -MDE. For the alternative 'greater than 0' (every tradable cell), the bound is the (1 - a)-quantile, and the MDE is excluded when it is less than +MDE. H3-c is two-sided at every stage: it uses a = 0.05/10 and requires both conditions, the lower bound (the a-quantile) greater than -MDE and the upper bound (the (1 - a)-quantile) less than +MDE. The fixed Bonferroni level is used because Holm's step-down level is not defined for items after the first retained hypothesis.", "underpowered": "The stage is below its minimum sample, or the stage is void (populations.fetch_failures), or the item neither passes nor excludes the MDE." }, "hypothesis_verdict": "H1 is not supported if H1-D is labelled not_supported_mde_excluded and H1-D-b_lane-low does not pass. H3 is not supported if H3-c is labelled not_supported_mde_excluded and neither H3-a nor H3-b passes. A verdict is computed at validation and reported as a screening verdict. For a hypothesis with a carried item, it is also computed at the holdout and reported as the confirmatory verdict. Each uses the labels of its own stage only.", - "validation_reporting": "Every item's validation estimate, interval and label are computed and reported descriptively, whether or not development nominated it. Only nominated items receive a Holm p; the rest score p = 1 in the Holm run. The H1 high and middle b_lane cells are reported descriptively at every stage, with no p-value.", + "validation_reporting": "Every item receives its validation p and enters the validation Holm run, whatever its development result. Development is survivorship-limited, so it nominates nothing and never removes an item from validation or the holdout. An item scores p = 1 at validation only when it is not tested (coverage_rule) or its validation outcome was not written (run_discipline.deviations). The H1 high and middle b_lane cells are reported descriptively at every stage, with no p-value.", "qualifiers": "Contamination, lineage and deviation status are qualifiers attached to a label, never labels of their own. A holdout item that meets the pass rule while its holdout is contaminated is labelled 'screened (contaminated holdout)', never 'supported (confirmatory)'. An item labelled 'supported (confirmatory)' that is not lineage-confirmed (multiple_testing.lineage_sensitivity) carries 'lineage-unconfirmed'. A stage decided by the one permitted post-deviation rerun (run_discipline.deviations) carries 'post-deviation'.", "rule": "Only these three labels are used, with the qualifiers above. A result from a deviated study tree other than the permitted rerun is reported beside the governing result with no label at all. A miss is never reported as 'falsified' or as proof of no effect, and 'not supported' always states the size it excludes. A result in the direction opposite to a one-sided alternative is reported descriptively with its estimate and interval and supports no claim." }, @@ -256,10 +256,10 @@ "H3-c" ], "item_id_rule": "item_ids is the complete list, in bytewise ASCII order. That order is used for stream spawning, for the final tie-break and for the validation results file.", - "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout, over these 5 items only. An item not carried to a stage, or not tested for lack of data, scores p = 1 there. The validation results file contains all 5 validation p-values keyed by item_ids (1 for items not nominated or not tested); the evaluator refuses an incomplete file.", - "direction_lock": "H3-c is two-sided only at development, where the screen uses the two-sided p (statistics.p_value). The development results file records the sign of its development estimate. Validation and the holdout then use the one-sided test in that recorded direction, in the same Holm slot. Missing or zero estimate: if the governing development run wrote no H3-c estimate (an incomplete run, or no event with all 8 legs), or the estimate is exactly 0, no direction is recorded. H3-c then scores p = 1 at validation and at the holdout, and its label there uses the two-sided bound at a = 0.05/10 (outcome_reporting.labels). A significant result in the direction opposite to the locked one is reported descriptively and cannot pass. Synthetic tests cover a positive, a negative, a zero and a missing development estimate.", + "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout, over these 5 items only. An item not carried to a stage, or not tested for lack of data, scores p = 1 there. The validation results file contains all 5 validation p-values keyed by item_ids (1 only for items not tested or without a written outcome); the evaluator refuses an incomplete file.", + "no_direction_lock": "H3-c is two-sided at every stage, with the two-sided p of statistics.p_value in its Holm slot. The full draft and the first core draft locked H3-c after development to the sign of its development estimate. That lock is removed (review round 6): development is survivorship-limited, and a lock set by a survivorship-biased estimate could stop H3-c from ever validating or reaching the holdout. A significant H3-c result of either sign can pass. A synthetic test asserts that validation and holdout p-values do not change when every development output is altered.", "data_status_at_freeze": "At the freeze, H1 and H3 are each recorded as either 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently', by coverage_rule. Data from a new source, or a new historical range acquired after the freeze, can support only a new protocol version with its own family (v3.1+), never this family's Holm run. The prospective Alpaca SIP data collected under holdout_gate.prospective_collection are not new data in this sense.", - "development_screen": "Unadjusted p <= 0.05 with the minimum sample: one-sided in the stated direction for H1-D, H1-D-b_lane-low, H3-a and H3-b, and two-sided for H3-c. Development only nominates items for validation.", + "development_screen": "Unadjusted p <= 0.05 with the minimum sample: one-sided in the stated direction for H1-D, H1-D-b_lane-low, H3-a and H3-b, and two-sided for H3-c. Development is descriptive: its pass ('development pass') is reported for every item and gates nothing. Every item goes to validation (outcome_reporting.validation_reporting). No development output (an estimate, a sign, a pass or an event's tercile breakpoint) is an input to validation, the holdout gate or any holdout statistic, because 2017-2019 is survivorship-limited (chronology.labels.development).", "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) and broad-universe-mover-v1's 60 predeclared signal variants on overlapping or other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 60 + 5) = 4.64e-5. The archived 35-item draft computed nothing and is not counted. broad-universe's controls and descriptive lane have no pass rule and are not counted, which is a stated limitation. Each p is reported beside the normal-tail p of statistics.normal_tail_p. With B = 100,000, a bootstrap p at 4.64e-5 means at most 3 null-side draws, so the bootstrap p alone cannot resolve this level. An item is lineage-confirmed only if both its bootstrap p and its normal-tail p are at or below 4.64e-5.", "tie_breaks": "Where items tie on a p (for example at the floor 1 / (B + 1)) in Holm ordering or in reporting, ties are broken by the normal-tail p, then by item id in bytewise ASCII order.", "tests_not_in_family": "BH (q = 0.10) diagnostics across year, price-tier and tercile splits are reported but can never open the holdout." @@ -279,7 +279,7 @@ "rule": "A stage below its minimum reports the item as underpowered; it cannot pass that stage. Minima are counted in chronology.holdout.count_unit at every stage. For H1-D, each group (count_unit keys H1-D:high and H1-D:low) must reach the per-group minimum." }, "minimum_detectable_effect": { - "formula": "MDE = (z_alpha + z(0.80)) x sigma x sqrt(DEFF) x sqrt(1/n) for one cell or H3-c. For H1-D, sqrt(1/n1 + 1/n2) replaces sqrt(1/n), with n1 and n2 the realized high and low group sizes. z_alpha = z(1 - 0.05/5) = 2.326 for one-sided tests, including H3-c after development. It is z(1 - 0.05/10) = 2.576 for H3-c at development and whenever H3-c has no locked direction. The MDE is computed from the planning sigma below and the realized n, and it is never re-estimated from outcomes.", + "formula": "MDE = (z_alpha + z(0.80)) x sigma x sqrt(DEFF) x sqrt(1/n) for one cell or H3-c. For H1-D, sqrt(1/n1 + 1/n2) replaces sqrt(1/n), with n1 and n2 the realized high and low group sizes. z_alpha = z(1 - 0.05/5) = 2.326 for the one-sided tests (H1-D and the three tradable cells), and z(1 - 0.05/10) = 2.576 for H3-c, which is two-sided at every stage. The MDE is computed from the planning sigma below and the realized n, and it is never re-estimated from outcomes.", "z_sum_one_sided": 3.168, "z_sum_two_sided": 3.4175, "design_effect_DEFF": 1.5, @@ -330,8 +330,7 @@ "n300_per_group": 11.09 }, "mde_percent_H3-c": { - "one_sided_n150": 3.17, - "one_sided_n300": 2.24, + "two_sided_n150": 3.42, "two_sided_n300": 2.42, "two_sided_n600": 1.71 }, @@ -396,7 +395,7 @@ "estimator": "The trade-level mean net return (for H3-c, the event-level mean leg difference). The median and the three robustness statistics are also reported.", "bootstrap": "A circular session block bootstrap, B = 100,000, over the stage's kept sessions concatenated in date order (sessions keyed by entry session; for H3-c, by t+1). Block length is 5 sessions for H3-a and 10 sessions for H1-D, H1-D-b_lane-low, H3-b and H3-c. H1-D resamples sessions jointly for both groups. The generator is numpy.random.Generator(PCG64), seeded per stage (stage_index 0 development, 1 validation, 2 holdout). Each stage uses numpy.random.SeedSequence(entropy = the first 8 bytes of sha256(frozen protocol id) read big-endian, spawn_key = (stage_index,)).spawn(5), and child i drives item_ids[i].", "p_value": "One-sided p = (1 + #{b : stat_b on the null side of 0}) / (B + 1). Two-sided p = min(1, 2 x the smaller one-sided p).", - "normal_tail_p": "z = estimate / sd(the B bootstrap statistics, ddof = 1). For the alternative 'greater than 0', p_N = 1 - Phi(z); for 'less than 0', p_N = Phi(z); two-sided (H3-c at development) p_N = min(1, 2 x min(Phi(z), 1 - Phi(z))). Phi is the standard normal CDF, and p_N = 1 if the sd is 0. It is used only for lineage confirmation and tie-breaks.", + "normal_tail_p": "z = estimate / sd(the B bootstrap statistics, ddof = 1). For the alternative 'greater than 0', p_N = 1 - Phi(z); for 'less than 0', p_N = Phi(z); two-sided (H3-c) p_N = min(1, 2 x min(Phi(z), 1 - Phi(z))). Phi is the standard normal CDF, and p_N = 1 if the sd is 0. It is used only for lineage confirmation and tie-breaks.", "robustness_required_for_a_pass": "A tradable cell's validation and holdout passes also need all three of these to be > 0, each computed from that stage's trades of that cell. (1) The winsorised mean: the trade-level mean after clipping each trade net return to [q01, q99], the numpy.quantile 'linear' 0.01 and 0.99 quantiles of those returns (1% in each tail). (2) The mean without the top 5 sessions: sum trade net returns by entry session, drop the 5 entry sessions with the largest sums (a tie is broken by dropping the earlier session first), and take the trade-level mean of the remaining trades. (3) The mean of per-session means: the equal-weighted mean, over entry sessions, of each session's trade-level mean. A synthetic test fixes each on a small table.", "sensitivities": [ "two-way (session, symbol) clustering", @@ -420,46 +419,56 @@ "the Python, numpy, pandas or exchange_calendars version differs from study/runtime.lock", "data/session-calendar.json or data/fees-v3.json differs from its frozen sha256, or an amendment file is not a byte-prefix extension of the content logged at the previous run, or an amendment line concerns a date before the freeze session", "the list of validated items is empty, or a requested item is not in it", - "the access log has no committed 'granted' entry of the matching purpose for this run", + "the access log has no committed granted authorization record of the matching purpose for this run, or an earlier granted authorization has no committed completion record", "any collection batch lacks its committed accrual log, or a batch's log became reachable from origin/main at or after 09:30 ET of the session after the batch's last session", - "a previous holdout 'read' run exists (the holdout is read once; failed or partial runs stay in the log). A 'count' is not a read", + "a previous granted 'read' authorization exists, with or without a completion (the holdout is read once; failed or partial reads stay in the log as completions). A 'count' is not a read", "a 'count' request asks for anything beyond chronology.holdout.count_unit, or comes other than once at the end of the 252 sessions and once at the end of each extension block" ], "access_log": { "path": "blueprints/us-equities/mover-v3/holdout-access-log.jsonl", - "mode": "append-only JSON lines, committed and pushed before the action each authorizes", - "fields": [ - "utc", - "actor_role", - "protocol_id", - "protocol_sha256", - "code_revision", - "study_code_tree", - "runtime_lock_sha256", - "data_file_sha256s", - "validation_results_sha256", - "requested_items", - "purpose", - "decision (granted or refused)", - "snapshot_sha256", - "rows_read", - "results_sha256", - "exposed_symbol_sessions" - ], + "mode": "append-only JSON lines of two record kinds. An 'authorization' record is committed and pushed before the action it authorizes and carries no value that exists only after that action. A 'completion' record is appended, committed and pushed after the action ends (complete, failed or partial), before any later authorization; it never edits its authorization.", + "fields": { + "authorization": [ + "utc", + "record_kind ('authorization')", + "authorization_id", + "actor_role", + "protocol_id", + "protocol_sha256", + "code_revision", + "study_code_tree", + "runtime_lock_sha256", + "data_file_sha256s", + "validation_results_sha256", + "requested_items", + "purpose", + "decision (granted or refused)" + ], + "completion": [ + "utc", + "record_kind ('completion')", + "authorization_id (the authorization it completes)", + "status (complete, failed or partial)", + "snapshot_sha256 (null if nothing was sealed)", + "rows_read", + "results_sha256 (null if no results file was written)", + "exposed_symbol_sessions" + ] + }, "purposes": { - "collect": "a collection batch, rows_read 0, listing that batch's accrual exposure", + "collect": "a collection batch; its completion has rows_read 0 and lists that batch's accrual exposure", "amend": "one calendar or fee amendment line, with its primary source", "count": "at the end of the 252 sessions and of each extension block: count_unit per carried item, used only for the extension decision; it may fetch the entry quotes, lookbacks, minute bars and prints the count needs, under run_discipline.fetch", "read": "the single outcome read for carried items" }, - "rule": "Any holdout read without a preceding committed granted entry of the matching purpose voids the holdout for every item. Refusals are logged too. The synthetic suite tests the 'count' path, including a test that it computes no exit price." + "rule": "Any holdout read without a preceding committed granted entry of the matching purpose voids the holdout for every item. Refusals are logged too, as authorization records with decision 'refused' and no completion. A granted authorization without its completion blocks every later authorization, and a granted 'read' authorization counts as the one holdout read whether or not its completion reports complete. The synthetic suite tests the 'count' path, including a test that it computes no exit price, and the log for a complete, a failed and a partial read, each an authorization followed by its completion." } }, "run_discipline": { "study_code": "The frozen protocol records study_code = {revision: the git commit, tree: the git tree hash of blueprints/us-equities/mover-v3/study/ at that commit}. The tree holds the study code, the count-only code, the fetch code, the collector, the synthetic tests and study/runtime.lock. runtime.lock pins the Python, numpy and pandas versions and exchange_calendars 4.13.2. The tree holds no data file (data_files). Every run, fetch and evaluator call names the revision, tree and runtime it ran from. Development, validation and every holdout 'count' and 'read' run from this one tree. The holdout count and read must name the same tree as the governing validation run, and the evaluator refuses any other tree (holdout_gate.evaluator_refusals). The only exception is the single rerun that run_discipline.deviations permits.", "data_files": "Data the study reads but may need to append to after the freeze live outside the study tree, in blueprints/us-equities/mover-v3/data/: session-calendar.json and fees-v3.json (the base files, pinned by sha256 in the frozen protocol), and session-calendar-amendments.jsonl and fees-v3-amendments.jsonl (append-only, possibly empty at the freeze). Appending a line never changes the study tree. The run log records each amendment file's sha256 and byte length at every run, and the evaluator refuses a file whose earlier logged content is not a byte prefix of its current content.", "run_log": "blueprints/us-equities/mover-v3/run-log.jsonl, append-only JSON lines. Every run of the frozen automation gets one line: each stage's fetch and re-fetch, development, validation, and each holdout 'count' and 'read', including failed and partial runs. Each line records the utc start and end, stage, purpose, study_code revision and tree, runtime lock sha256, data-file sha256s, input snapshot sha256s, status (complete, failed or partial) and the sha256 of every results file written (partial files included). For the validation results commit, it also records the GitHub-recorded time it became reachable from origin/main. Each line is committed and pushed before the next run of any stage starts.", - "once": "Development is computed by one logged run and validation by one logged run. The first run of a stage that writes any outcome is that stage's governing run, whether or not it completes. The governing development run nominates, and the governing validation run's results file governs the holdout gate.", + "once": "Development is computed by one logged run and validation by one logged run. The first run of a stage that writes any outcome is that stage's governing run, whether or not it completes. The governing development run's results are descriptive (multiple_testing.development_screen), and the governing validation run's results file governs the holdout gate.", "deviations": "Any change to the study tree after the freeze is a numbered deviation in blueprints/us-equities/mover-v3/deviations.json (cause, diff reference, new tree hash, affected stages), committed and pushed before any run from the new tree. A run from a deviated tree is reported beside the governing result, carries no label, and never governs a stage, the holdout gate or the extension decision. One exception: if a governing run from the frozen tree is incomplete because of an exception or an infrastructure failure, one rerun may govern, labelled 'post-deviation'. It must run under a deviation that changes no rule, and it must reproduce byte for byte every outcome the incomplete run had written. Otherwise: an incomplete development run decides development by its written outcomes alone (unwritten items score p = 1); an incomplete validation run voids the holdout of this protocol version; and an incomplete holdout read labels every carried item without a written outcome underpowered, with the reason stated.", "fetch": "Before any outcome of a stage (development, validation, and each holdout 'count' or 'read'), the frozen fetch code fetches every quote window, minute bar and official print that stage needs, under universe_and_identity.asof. It derives stamps only from decision-time inputs and quote timestamps, including the terminal-search windows. It seals the result as a sha256-pinned private snapshot, and commits that sha256 and the stage's fetch-incomplete rate in the run log. At most one re-fetch is allowed: once, of only the fetch-incomplete stamps, before any outcome of that stage, committed the same way. After it, the 1% void rule is final. No outcome is computed from unsealed data.", "synthetic_tests": "The evaluator refuses a results file from an unpinned revision or tree, a holdout count or read from a tree other than the governing validation run's, a runtime that differs from runtime.lock, an edited (not appended) amendment file, a stage with a second outcome-bearing run that no deviation covers, and an outcome computed before the stage's fetch rate is committed." @@ -467,12 +476,12 @@ "leverage_and_exits": "Research runs use 1x equal notional. Paper use of any passing rule follows the README's exit and leverage policy (at most one broker-held closing order per share, the leverage-schedule-v1-20260922 ceiling, and the leverage-ladder gate rows in catalogs/us-equities/gates-20260922.json, one rung at a time). H3-b stays at 1x in any later paper use until the 2x rung and its overnight cap are clean. The edge-shrink term stays 0 until a holdout pass.", "freeze_preconditions": [ "An independent pre-outcome review of this core draft from a different session and model family, with every finding resolved in a dated review_record entry", - "Direct full-text reads of Lou-Polk-Skouras 2019 and Akbas-Boehmer-Jiang-Koch 2022, or removal of every claim that depends on them (the two JFE DOIs resolve in Crossref, which is metadata only). H3-c is two-sided at development, so no test depends on their sign.", + "Direct full-text reads of Lou-Polk-Skouras 2019 and Akbas-Boehmer-Jiang-Koch 2022, or removal of every claim that depends on them (the two JFE DOIs resolve in Crossref, which is metadata only). H3-c is two-sided at every stage, so no test depends on their sign.", "data/session-calendar.json generated from exchange_calendars 4.13.2 XNYS and data/fees-v3.json with sourced 2017-2020 (through the freeze) SEC Section 31 and FINRA TAF rows, both committed and pinned by sha256, plus both amendment files committed (empty is allowed)", "The enumerated symbol list (universe_and_identity.enumeration) and pre-freeze snapshot parts 0-2, fetched under universe_and_identity.asof, sealed and pinned by sha256, with each page's asof and fetch date recorded", "Count-only coverage checks through exposure_registry.pre_freeze_access_path only, with no outcome computed: accepted official-print rates per label, eligible-quote rates at the coverage stamps, corporate-action record counts, suspected unadjusted splits, enumerated symbols per source and empty screen responses, all for 2016-2020, with coverage_rule applied and H1 and H3 recorded as tested or not tested (multiple_testing.data_status_at_freeze)", "The Alpaca market-data plan's documented historical-data rate limit recorded with its source, and an upper-bound fetch-time estimate for development and validation (event candidates from the count-only outputs x the per-event requests, at that limit). The estimate must leave a margin of at least 50% under the 40 sessions before N0. If it does not, the protocol is not frozen as written.", - "The study tree committed with runtime.lock and synthetic tests that pass before any outcome. The tests cover: every populations.formulas case, each official-print source label, the exclusion rules, a suspected unadjusted split, the fill-quote cases, the terminal-exit cases, the universe_and_identity cases (FB/META, ticker reuse, rename inside a hold, empty responses, fetch-date asof refused), split, ex-dividend and 5% special-dividend crossings, the 2016-2020 early closes, a calendar and a fee amendment, a 2017 fee lookup, the fetch-failure cases, a dropped middle year, segment-end censoring and the embargo (including none at N0), the direction lock (positive, negative, zero and missing estimate), the robustness statistics, the gate test, the count_unit keys, the holdout-read classification, the run_discipline refusals and the evaluator's holdout refusals. The code imports none of rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return, rules.net_return_np and quotes.fetch_one, and a test asserts this.", + "The study tree committed with runtime.lock and synthetic tests that pass before any outcome. The tests cover: every populations.formulas case, each official-print source label, the exclusion rules, a suspected unadjusted split, the fill-quote cases, the terminal-exit cases, the universe_and_identity cases (FB/META, ticker reuse, rename inside a hold, empty responses, fetch-date asof refused), split, ex-dividend and 5% special-dividend crossings, the 2016-2020 early closes, a calendar and a fee amendment, a 2017 fee lookup, the fetch-failure cases, a dropped middle year, segment-end censoring and the embargo (including none at N0), the independence of validation and the holdout from every development output (H3-c two-sided with no direction lock, validation breakpoints from validation sessions only), the robustness statistics, the gate test, the count_unit keys, the holdout-read classification, the run_discipline refusals and the evaluator's holdout refusals, and the access log's authorization and completion records. The code imports none of rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return, rules.net_return_np and quotes.fetch_one, and a test asserts this.", "The exposure registry updated with everything the author saw since this draft", "status set to frozen, frozen_before_outcomes true, run_discipline.study_code filled in, and the protocol sha256 recorded in a commit that becomes reachable from origin/main (chronology.holdout.window defines the freeze commit)" ], @@ -482,6 +491,12 @@ "round": 5, "input": "The remaining findings of the round-4 rereview of the full draft (S1-S11 and X1-X10), after three fix rounds (75 fixes) in which high-severity findings went 4, 3, 4", "resolution": "The draft was restructured into this core: H1 on population D (one difference test, one tradable cell) and H3 (two tradable arms and the leg decomposition), m = 5. Resolved in the core: S1 (run_discipline.study_code, data_files and deviations, holdout_gate.evaluator_refusals: the holdout count and read run only from the governing validation run's tree, and a deviated tree's result carries no label); S2 (populations.terminal_exits); S3 and X1 (universe_and_identity: per-request asof = the decision session with the as-known symbol, the enumeration source, identity dedup and empty-response counts); S4 (count_unit keys per H1-D group); S5 (no cap; the tradable cells are named); S6 (statistics.robustness_required_for_a_pass); S7 (chronology.boundaries: no embargo at N0); S8 (multiple_testing.direction_lock for a missing or zero estimate); S9 (run_discipline.data_files and the amendment files); S10 (statistics.normal_tail_p); S11 (the pit-availability registry entry); X5 (populations.fetch_failures: v3-owned fetch code); X9 (study/runtime.lock); X10 (exposure_registry.update_rule reworded). No longer applicable: X8 (population I is not in the core; for D the opening print precedes the decision). Deferred with their items (README appendix): X7 (H5), and X2, X3, X4 and X6 (H6). No finding rejected. Rounds 2-4 are recorded in the archived draft's review_record." + }, + { + "date": "2026-09-24", + "round": 6, + "input": "The chatgpt-codex-connector review of PR #167 (nine threads, raised against the full draft at 0b924e7)", + "resolution": "Applied to the core: (1) the survivorship-limited development stage no longer gates anything. Every item enters validation (outcome_reporting.validation_reporting), H3-c is two-sided at every stage with no development direction lock (multiple_testing.no_direction_lock), validation breakpoints use validation sessions only (tercile_rule.breakpoints), and the validation label records the broad-universe 2020 survivorship count (chronology.labels.validation); (2) the holdout access log separates a pre-action authorization record from a post-action completion record (holdout_gate.access_log). Already resolved in the core: the paired H3-c MDE form (minimum_detectable_effect.sigma_by_item), stage-own verdicts (outcome_reporting.hypothesis_verdict) and the entry bar complete before the fill decision (populations.formulas). Deferred with their items (README appendix): H2's Item 1.01 corpus gate, and the three H6 findings (stage membership by closing session, a common frozen trigger policy, REST-call attribution for the now-descriptive H6-c). No finding rejected." } ], "overturn": "Any headline result is overturned if a rerun on the same frozen inputs, study tree and runtime.lock does not reproduce it byte for byte, if an independent quote sample puts the cost table off by more than 2x, or if the access log shows a holdout read that no committed granted entry preceded." diff --git a/manifests/evidence.json b/manifests/evidence.json index 148d3b566..ea54c7904 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -918,7 +918,7 @@ "prometheus", "loki" ], - "claim": "After native sign-in restored readiness, the freshly reproduced SDK completed a real LEAN backtest→Dagu→DuckDB→GPT-6 Astra→Claude Opus 5 prepared-packet research workflow; both native reports passed structural citation/value/unit/scope checks and the accepted Astra report was bound into Claude's prompt by verified hashes.", + "claim": "After native sign-in restored readiness, the freshly reproduced SDK completed a real LEAN backtest\u2192Dagu\u2192DuckDB\u2192GPT-6 Astra\u2192Claude Opus 5 prepared-packet research workflow; both native reports passed structural citation/value/unit/scope checks and the accepted Astra report was bound into Claude's prompt by verified hashes.", "limitations": [ "This validates a bounded historical simulation research workflow on the authoring host, not a profitable strategy, current market source, broker execution or unattended production service.", "The two reported model tasks used no model tools. Their native hooks and telemetry remain separate evidence; this does not repeat every MCP, memory or GPU-RAG behavior.", @@ -1900,7 +1900,7 @@ "component_ids": [ "ai-memory" ], - "claim": "Mac arm64 synthetic2.3.1→2.3.2 migration, scoped exact reads/FTS, reserved and Unicode paths, restart and no-capture checks passed.", + "claim": "Mac arm64 synthetic2.3.1\u21922.3.2 migration, scoped exact reads/FTS, reserved and Unicode paths, restart and no-capture checks passed.", "limitations": [ "Separate synthetic store and explicit no-model policy only; no production migration claimed by this receipt.", "OS filesystem/network confinement was not established; failed restricted-sandbox attempt retained. Native execution succeeded without security overrides; Gatekeeper assessment rejected the ad-hoc distribution.", @@ -5392,7 +5392,7 @@ "bytes": 126 }, { - "path": "blueprints/convergence-practice/wsl-retrieval/seed/corpus/primary/notes/aéé.md", + "path": "blueprints/convergence-practice/wsl-retrieval/seed/corpus/primary/notes/a\u00e9\u00e9.md", "sha256": "cb36cd06858449475efbfaddaedfd78d20a2b7b4f30993bca02cd1cb360e0cb3", "bytes": 111 }, @@ -8448,18 +8448,18 @@ }, { "path": "blueprints/us-equities/mover-v3/README.md", - "sha256": "91e481ae33f1c29e5f6eecd0e6e89f2cf149782e0d96c4c2d0b422e56c676bfb", - "bytes": 48328 + "sha256": "8a736ba481187b93132e47a088dbcf7c8c2d40ed3de23b61a247a96d1bf54c58", + "bytes": 49529 }, { "path": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json", - "sha256": "890ca29b1347fc63a6f9a9c7d13dcdb179c1cfcb0792027150d9fdc5db257499", - "bytes": 22836 + "sha256": "09d91ded5109e67c824ea27a3735419654142ac42c1fe87fe4e65cdc6e546e7f", + "bytes": 22949 }, { "path": "blueprints/us-equities/mover-v3/protocol-core-draft.json", - "sha256": "bf53802185a34ba6acce8fd533692523cbd6ea71ca030d429700ea5113c00fb7", - "bytes": 81149 + "sha256": "14c576fc6c5594bd807f09f79ce7ae6dfddeae4f177491039d88c2a1fd255ef7", + "bytes": 85121 }, { "path": "blueprints/us-equities/mover-v3/protocol-draft.json", From 7d67df208e0ea4ebf535224d5c0f310b3ef8104d Mon Sep 17 00:00:00 2001 From: Scout Date: Thu, 24 Sep 2026 04:48:59 -0400 Subject: [PATCH 8/8] Mover v3 review round 7: fix C1-C14 and F1-F10 in the core draft - Atomic per-stage results and retries only from the unchanged frozen tree; no post-deviation rerun; a fetch-only transport deviation class with a byte-for-byte reproduction check (C1, C2) - Hashed 1-in-20 pre-freeze sample instead of the full 2016-2020 screen; full screen fetched after the freeze; sampled candidate count and bound (C4, C8, F3) - In-hold split exclusion only on retained records; ratio rule a sensitivity; telescoping share factor with undefined-on-missing inputs; per-event hold daily bars under asof = t (C3, F1, F2) - Tree-hash-only study_code pin, pinned copies of external definitions, mandatory automated holdout read, H3-c holdout sign match, exhaustive verdicts, code-decided coverage rule with defined rates, identity diagnostic and native probe, net-return formula with the D7 clamp, censored search past segment ends, acquiree-only merger booking, late-collected batches (C5-C7, C9-C14, F4-F10) Co-Authored-By: Claude Opus 5.5 (1M context) --- blueprints/us-equities/mover-v3/README.md | 56 ++++--- .../mover-v3/protocol-core-draft.json | 149 ++++++++++-------- manifests/evidence.json | 8 +- 3 files changed, 125 insertions(+), 88 deletions(-) diff --git a/blueprints/us-equities/mover-v3/README.md b/blueprints/us-equities/mover-v3/README.md index d36e6e688..deff7b804 100644 --- a/blueprints/us-equities/mover-v3/README.md +++ b/blueprints/us-equities/mover-v3/README.md @@ -12,8 +12,9 @@ decisions from the same sweep are in **Why a core.** Three review rounds of the full draft (75 fixes) did not converge: high-severity findings went 4, 3 and then 4. The full draft covered 35 items across H1-H6 plus an execution study. The core keeps the two hypotheses -that can be computed from the Alpaca SIP data the repository already uses, with the fewest populations, arms and -cells that still test them: one population (D), three arms and five items (m = 5). It fixes every remaining review +that can be computed from Alpaca SIP data types the repository is already entitled to, with the fewest populations, +arms and cells that still test them (quote and auction-print coverage for 2016-2020 is unverified and is decided by the +coverage rule at the freeze): one population (D), three arms and five items (m = 5). It fixes every remaining review finding that applies to them. The core's `review_record` maps each finding to the section that resolves it. **Evidence class.** This plan rests on literature and metadata evidence, plus a few unpinned source reads, all from a @@ -75,8 +76,13 @@ The draft restates every formula exactly, since `rules.py` does not define `ref` closes count only from the listing exchange's auction print or official close. A close printed only on another exchange, or a missing previous close, means no event, with no bar fallback. A session whose raw close ratio sits within 1% of an integer split ratio with no adjustment is treated as an unadjusted split and is excluded before -membership. Split factors come from split-adjusted bars, so a large cash dividend is booked as cash, not as a share -change (a stated departure from #162's D7 code, which used all-adjusted bars). OTC names are excluded by as-known +membership. Inside a hold that ratio rule is only a sensitivity, because hold-period ratios are outcomes: a trade is +excluded only when a retained split record falls in the hold and the adjusted bars do not carry it. Split factors come +from split-adjusted bars, so a large cash dividend is booked as cash, not as a share change (a stated departure from +#162's D7 code, which used all-adjusted bars). They telescope across sessions with bars, so a split on a session with +no bar is still booked, and a missing input makes a factor undefined rather than 1. Hold-period daily bars are fetched +per event under the as-known symbol with `asof` = the decision session, and the share factor and dividend cash use only +those responses. The net-return formula and D7's cash clamp are stated exactly in the core draft. OTC names are excluded by as-known session data (no listing-exchange opening print), never by today's asset flags. Every such case is counted. **Universe and identity.** The 2016-2020 symbol list is the Alpaca asset master (active and inactive, every current @@ -90,8 +96,9 @@ counted, never silently dropped. **Symbols that stop quoting.** An exit waits for an eligible quote for at most 5 sessions after the planned exit session. A position still without one is terminal (a delisting, a move to OTC, a suspension, or a halt that does not -resume). It is booked at the last eligible bid of the hold when a retained merger record explains the delisting, -and at 0 otherwise. Terminal trades stay in every primary statistic and are counted per item. Booking every terminal +resume). It is booked at the last eligible bid of the hold when a retained cash, stock or stock-and-cash merger record +names the held symbol as the acquiree, and at 0 otherwise. A trade censored at a segment end searches past the end for +its exit quote under the same 5-session rule, so a segment boundary alone never books a total loss. Terminal trades stay in every primary statistic and are counted per item. Booking every terminal trade at the last eligible bid is only a sensitivity. No halt data are used. As in the catalyst experiment, a halt is never inferred from missing data. G = 0.20 and @@ -104,7 +111,7 @@ V = $1M are v1's loosest gain and middle volume floor, chosen for sample size af | H1-D-b_lane-low | H1, tradable cell | mean net(5-session hold, low MAX21 tercile) | greater than 0 | in a long-only study, the one H1 cell for which the prior predicts the best relative return; the high and middle cells are reported descriptively only | | H3-a | H3, tradable cell | mean net(enter 09:35, exit 15:55 on t+1) | greater than 0 | the intraday leg as a trade | | H3-b | H3, tradable cell | mean net(enter 15:55 on t+1, exit at the first eligible quote from 09:30 on t+2) | greater than 0 | the overnight leg as a trade; neither leg has a sourced sign for movers, so both are kept | -| H3-c | H3, primary test | mean of (mean of 4 overnight official-print log legs - mean of 4 intraday legs) over t+1 .. t+5 | two-sided at every stage, never locked to the survivorship-limited development sign | the direct test of "overnight versus intraday". It uses official prints only, so it needs no fill or cost model | +| H3-c | H3, primary test | mean of (mean of 4 overnight official-print log legs - mean of 4 intraday legs) over t+1 .. t+5 | two-sided at every stage, never locked to the survivorship-limited development sign; a holdout pass also needs validation's sign | the direct test of "overnight versus intraday". It uses official prints only, so it needs no fill or cost model | MAX21 is the largest of the 21 split-consistent official-close returns before the decision session. Its terciles use breakpoints from D events in the prior 252 sessions, so they are known at the decision. Research sizing is 1x equal @@ -124,8 +131,10 @@ literature's roughly 1% a month for MAX, so a miss on H1-D will most likely be l only large mover-specific effects. **Data status is fixed at the freeze.** One coverage rule covers all five items, with one set of kept years: a -2016-2020 year below 90% official-close coverage or 80% eligible-quote coverage is dropped for every item. With fewer -than 2 development years left, or without 2020, no item is tested. A dropped year's edges are segment boundaries, +2016-2020 year below 90% official-close coverage or 80% eligible-quote coverage (fetch-incomplete stamps count against +it), or with more than 10% of sampled histories unreachable by `asof` = session screening, is dropped for every item. +The count-only code applies the rule itself, and the rule text is pinned by hash. Without 2020, no item is tested; +development years condition nothing. A dropped year's edges are segment boundaries, its events leave the tercile windows and the bootstrap wraps over the kept sessions. Data from a new source or a new historical range obtained after the freeze can only start a new protocol version (v3.1+), so it cannot change this family's Holm thresholds after results are seen. @@ -140,11 +149,17 @@ sensitivity, and more than 5% exposed trades labels an item's holdout contaminat narrowly: computing any v3 outcome from holdout data outside a granted access-log entry. It voids the holdout rather than removing trades. The access log has two record kinds: an authorization committed before the action, and a completion (status, rows read, result hash, exposure) appended after it, including for failed and partial runs. - -Development, validation and the holdout count and read all run from one study-code tree pinned at the freeze, with a -pinned runtime lockfile. The evaluator refuses a holdout count or read from any other tree. A code change after the -freeze is a numbered deviation whose results are reported beside the governing ones with no label. The one exception -is a rerun after a crash that changes no rule and reproduces every written outcome byte for byte. Calendar and fee +The read is mandatory: committed automation performs it within 15 sessions after the last holdout session, and a +holdout that is not read, refused or void labels every carried item *not supported (holdout not read)*. + +Development, validation and the holdout count and read all run from one study-code tree pinned at the freeze by its +tree hash (not a commit, since pull requests are squash-merged), with a pinned runtime lockfile. The tree imports +nothing from earlier studies; it holds byte-for-byte copies of the pinned definitions it needs, tested against their +blobs. The evaluator refuses a holdout count or read from any other tree. Each stage writes its results atomically in +one file, so a crashed run writes nothing and may be retried from the unchanged tree. A code change after the freeze is +a numbered deviation whose results are reported beside the governing ones with no label, and a stage that cannot +complete without one is void. The only exception is a fetch-transport fix confined to `study/fetch/`, accepted only if +it reproduces already-sealed pages byte for byte. Calendar and fee amendments go in append-only data files outside the study tree, so appending one never changes the tree. The 2017-2020 windows are disjoint from #162's v1 and v2, but not unseen. The broad-universe study computed forward @@ -432,19 +447,24 @@ into another: - the session calendar, the fee file and their empty append-only amendment files committed as data files outside the study tree and pinned; - the enumerated symbol list and the pre-freeze snapshot sealed and pinned, every request with `asof` = its session; + its 2016-2020 part is a hashed 1-in-20 sample of symbol-sessions, and the full screen is fetched only after the + freeze; - metadata-only coverage checks, run only by committed count-only code that emits counts and rates and seals the raw pages unopened: 2016-2020 official prints by source label, quotes at non-v3 coverage stamps, corporate-action - records, suspected unadjusted splits, symbols per enumeration source and empty responses; + records, suspected unadjusted splits, a sampled candidate count, symbols per enumeration source, empty responses and + the share of histories that `asof` = session screening cannot reach; +- a native count-only probe of `asof` identity on 2016-2020 renames and ticker reuses, with match thresholds; - H1 and H3 recorded as tested or permanently not tested by the coverage rule; -- the documented market-data rate limit and a fetch-time estimate with margin under the 40 sessions before the - holdout; +- the documented market-data rate limit and a fetch-time estimate, from the sampled candidate count, with margin under + the 40 sessions before the holdout; +- the holdout collector committed and scheduled so that its first batch covers the freeze session; - the study tree committed with its runtime lockfile and synthetic tests before any outcome is computed, including terminal exits, renames and ticker reuse, split and ex-dividend crossings, early-close sessions, segment-end censoring and the embargo, validation's independence from development outputs, the access-log authorization and completion records and the evaluator's refusals. H6 has its own preconditions in `h6-execution-parity-draft.json`. Review rounds 2-4 of the full draft are recorded in -`protocol-draft.json` (`review_record`); round 5, the restructure into the core, is in `protocol-core-draft.json`. +`protocol-draft.json` (`review_record`); rounds 5-7 (the restructure into the core and its two review rounds) are in `protocol-core-draft.json`. Until all of that is done, this is a plan, and no window it names may be read for outcomes. diff --git a/blueprints/us-equities/mover-v3/protocol-core-draft.json b/blueprints/us-equities/mover-v3/protocol-core-draft.json index fe176dd04..60e57268e 100644 --- a/blueprints/us-equities/mover-v3/protocol-core-draft.json +++ b/blueprints/us-equities/mover-v3/protocol-core-draft.json @@ -10,12 +10,12 @@ "archived_full_draft": "blueprints/us-equities/mover-v3/protocol-draft.json (status superseded_by_core_draft_20260924). It is kept unchanged apart from its status and a superseded_by pointer. It was never frozen or run, and computed no outcome. Its review_record holds rounds 2-4.", "execution_study": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json: an engineering draft, not part of this alpha family. It uses no slot of this family, and its results never open this protocol's holdout.", "catalog_decisions": "catalogs/us-equities/mover-v3-sweep-20260924.json", - "why_core": "Three review rounds of the full draft (75 fixes) did not converge: high-severity findings went 4, 3, 4. The full draft spans 35 items across H1-H6 plus an execution study. This core keeps only H1 (MAX/lottery conditioning) and H3 (overnight versus intraday legs), because both can be computed from the Alpaca SIP data the repository already uses (daily and minute bars, quotes, auction prints and corporate actions). It keeps one population (D), three arms and five items (item_set). H2, H4, H5 and H6 are deferred to v3.1+ (README appendix 'Deferred to v3.1+'), each with its reason and the open review findings that apply to it.", + "why_core": "Three review rounds of the full draft (75 fixes) did not converge: high-severity findings went 4, 3, 4. The full draft spans 35 items across H1-H6 plus an execution study. This core keeps only H1 (MAX/lottery conditioning) and H3 (overnight versus intraday legs), because both can be computed from Alpaca SIP data types the repository is already entitled to (daily and minute bars, quotes, auction prints and corporate actions). Quote and auction-print coverage for 2016-2020 is unverified: no repository run has fetched quotes or auctions before 2021 (broad-universe/README.md: minute bars and quotes 'are not collected market-wide here'), and coverage_rule decides it at the freeze from count-only outputs. It keeps one population (D), three arms and five items (item_set). H2, H4, H5 and H6 are deferred to v3.1+ (README appendix 'Deferred to v3.1+'), each with its reason and the open review findings that apply to it.", "evidence_class": "Literature and metadata evidence, plus unpinned source reads, from a read-only sweep on 2026-09-24 (5 angles, 37 items; 5 survive, 24 contested, 8 killed). No native execution, no market data fetched and no outcome computed. This is not a frozen protocol: no window named here may be read for outcomes until status is frozen under freeze_preconditions.", "relation_to_prior_protocols": { - "mover-early-entry-v1-20260924": "PR #162, merged to main on 2026-09-24 as squash commit aa6fc79. 0 of 768 rule-exits pass development, and the holdout was not read. It is reused by reference only, pinned to main at aa6fc79 (paths under blueprints/us-equities/mover-early-entry/). rules.py (git blob 9be164c9e1a49a451d3a2381205349ed5003a993, sha256 d66156b3e57c6299f10e41a3f25c921c8be52571bd84417040190d926bf49450) supplies Bars.state, Bars.cum_dv and split_factor's arithmetic. sessions_io.py (git blob 0f7791a87685c73635efe94c226031d9afe7bd7c, sha256 2a753cb8f9164f48c1dceb952eb42eeae17811f636fddf5fca082731c708e59a) supplies official_price only, with the accepted labels of populations.official_prints. quotes.py (git blob caebf6f6a72fee6bb7ae637ebb698f8c5f6b3850, sha256 4116bf2809e0952f842f0e0fe35fd8b5ab11ea295637734ffe0d719ca72ee85e) supplies valid_quote and the page and stamp record format; its fetch_one (PROTOCOL_ASOF = '2026-09-21', and 5 retries that raise instead of stamping) and cmd_sample stamping are never used (populations.fetch_failures, universe_and_identity.asof). evidence/cost-table-run-v1.json has its hashes under cost_model.base_table. From evaluate_v2.py (git blob 2248580f4c673da3fcde25d85c6d20799ffa994e, sha256 d2007077ac7367e16061e49a645a54802fbb58c41d79d3b2da6417b0275b2fe4), only deviation D7's split and ex-dividend accounting is used, restated in populations.corporate_actions. v3 never modifies blueprints/us-equities/mover-early-entry/**. The pinned code is valid for 2021-2026 only: rules.EARLY_CLOSES starts in 2021, and fees.json has no SEC Section 31 row before 2020-01-01. v3 study code therefore never calls rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return or rules.net_return_np. It uses the v3 data files instead (run_discipline.data_files).", + "mover-early-entry-v1-20260924": "PR #162, merged to main on 2026-09-24 as squash commit aa6fc79. 0 of 768 rule-exits pass development, and the holdout was not read. It is reused by reference only, pinned to main at aa6fc79 (paths under blueprints/us-equities/mover-early-entry/). The study tree imports none of these files: it holds byte-for-byte copies of the few definitions it needs, checked against these blobs (run_discipline.pinned_copies). rules.py (git blob 9be164c9e1a49a451d3a2381205349ed5003a993, sha256 d66156b3e57c6299f10e41a3f25c921c8be52571bd84417040190d926bf49450) supplies the Bars class (Bars.state and Bars.cum_dv); split_factor's arithmetic is restated in populations.formulas. sessions_io.py (git blob 0f7791a87685c73635efe94c226031d9afe7bd7c, sha256 2a753cb8f9164f48c1dceb952eb42eeae17811f636fddf5fca082731c708e59a) supplies official_price only, with the accepted labels of populations.official_prints. quotes.py (git blob caebf6f6a72fee6bb7ae637ebb698f8c5f6b3850, sha256 4116bf2809e0952f842f0e0fe35fd8b5ab11ea295637734ffe0d719ca72ee85e) supplies valid_quote, applied only as a predicate to one quote (populations.fill_quote), and the page and stamp record format; its fetch_one (PROTOCOL_ASOF = '2026-09-21', and 5 retries that raise instead of stamping) and cmd_sample stamping are never used (populations.fetch_failures, universe_and_identity.asof). evidence/cost-table-run-v1.json has its hashes under cost_model.base_table. From evaluate_v2.py (git blob 2248580f4c673da3fcde25d85c6d20799ffa994e, sha256 d2007077ac7367e16061e49a645a54802fbb58c41d79d3b2da6417b0275b2fe4), only deviation D7's split and ex-dividend accounting is used (lines 416-420, including the cash clamp), restated in populations.corporate_actions. v3 never modifies blueprints/us-equities/mover-early-entry/**. The pinned code is valid for 2021-2026 only: rules.EARLY_CLOSES starts in 2021, and fees.json has no SEC Section 31 row before 2020-01-01. v3 study code therefore never copies or calls rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return or rules.net_return_np. It uses the v3 data files instead (run_discipline.data_files).", "mover-followup-v2-20260924": "PR #162 at aa6fc79: no pass in families E (216), F (16) or P (12), and no holdout read. Not reused.", - "broad-universe-mover-v1-20260921": "Pinned at aa6fc79 for identity handling only: coverage.py (git blob d08c5ec2f11a70717f6e7470d611ab307c6efd63, sha256 ea88f5d7a9fd4d5f957b559ee4afc035134c7d267e4a1afa52c45afe3f48ebbd; dedupe_identity), collect_daily.py (git blob 8df7d3485b848847a372d264768945e3a6d8b9c1, sha256 4557cd9b0fc6aa6781a31748bfc97d53b6c9234648480177daa2f78bca2bbfaf; DATA_SYMBOL and the supplement method) and corporate_actions.py (git blob 2cf689b615f9c7da1426bffab5eab53b6979baa7, sha256 0e39b3eeb84db83c64aa777ed46756ae495b1581dbc6a41193604bbf132ce81e). Its collector requested bars at the provider's default asof (the request date), which v3 does not do (universe_and_identity.asof). Its outcome exposure is in exposure_registry.", + "broad-universe-mover-v1-20260921": "Pinned at aa6fc79 for identity handling only: coverage.py (git blob d08c5ec2f11a70717f6e7470d611ab307c6efd63, sha256 ea88f5d7a9fd4d5f957b559ee4afc035134c7d267e4a1afa52c45afe3f48ebbd; dedupe_identity), collect_daily.py (git blob 8df7d3485b848847a372d264768945e3a6d8b9c1, sha256 4557cd9b0fc6aa6781a31748bfc97d53b6c9234648480177daa2f78bca2bbfaf; DATA_SYMBOL and the supplement method) and corporate_actions.py (git blob 2cf689b615f9c7da1426bffab5eab53b6979baa7, sha256 0e39b3eeb84db83c64aa777ed46756ae495b1581dbc6a41193604bbf132ce81e). dedupe_identity (with the module-level definitions it references) and DATA_SYMBOL are copied into the study tree under run_discipline.pinned_copies. Its collector requested bars at the provider's default asof (the request date), which v3 does not do (universe_and_identity.asof). Its outcome exposure is in exposure_registry.", "catalyst-experiment-v1-20260920": "Its D-lane timing (16:15 decision, 09:35 entry, +60 s minimum latency, 5-session horizon) and its rule 'Do not infer a halt from missing data' are reused by reference. Its 2021Q1 ETF control is permanently inspected and is never a holdout.", "mover-v3-draft-20260924": "The archived full draft. This core carries over its H1 and H3 definitions, restricted as item_set states, and the rules it names. The core governs wherever the two differ." }, @@ -61,8 +61,8 @@ "hypothesis": "H3", "role": "primary test (not a tradable cell)", "statistic": "mean over events of (mean of the 4 overnight leg log returns - mean of the 4 intraday leg log returns), gross, legs as in arms.c_decomposition", - "alternative": "not equal to 0 at every stage (multiple_testing.no_direction_lock)", - "why_kept": "The direct test of H3's 'versus'. It uses official prints only, so it needs no fill and no cost model. The literature (LPS 2019, ABJK 2022) concerns large, liquid names and gives no sign for movers, so the test is two-sided at every stage. It is not locked to the development sign, because development is survivorship-limited (chronology.labels.development) and gates nothing." + "alternative": "not equal to 0 at every stage; at the holdout a pass also needs the sign of the validation estimate (multiple_testing.no_direction_lock)", + "why_kept": "The direct test of H3's 'versus'. It uses official prints only, so it needs no fill and no cost model. The literature (LPS 2019, ABJK 2022) concerns large, liquid names and gives no sign for movers, so the test is two-sided at every stage. It is not locked to the development sign, because development is survivorship-limited (chronology.labels.development) and gates nothing. At the holdout, a confirmatory pass also needs the holdout estimate to have the sign of the governing validation estimate, so a pass replicates the screened direction." } ], "tradable_cells": "H1-D-b_lane-low, H3-a and H3-b are tradable cells and nothing else. H1-D and H3-c are primary tests and are never tradable cells. The robustness requirement (statistics.robustness_required_for_a_pass) applies to the three tradable cells only.", @@ -142,46 +142,48 @@ "what": "pit-availability (blueprints/us-equities/pit-availability/plan.json and evidence/receipts/pit-availability.json, merged to main in #75 before aa6fc79). It read SEC submissions filing indexes for 2016-01-01 to 2026-09-22 and Benzinga news (Alpaca /v1beta1/news) in one March week per year for 2016-2026, for 20 symbols: AAPL, MSFT, AMZN, GOOGL, META, JPM, XOM, JNJ, WMT, NVDA, GME, AMC, PLUG, RIOT, MARA, SOFI, UPST, DKNG, CVNA and FUBO. The 2016-2020 weeks are 2016-03-14..20, 2017-03-13..19, 2018-03-12..18, 2019-03-11..17 and 2020-03-09..15. It also read daily raw and all-adjusted SIP bars and corporate actions in 10-day windows around the forward splits of AAPL, AMZN, GOOGL, NVDA and TSLA, including AAPL 4:1 and TSLA 5:1 on 2020-08-31, which fall inside v3 validation. Metadata and data checks only: no mover membership and no mover outcome was computed. It found the XOM and DKNG ticker-to-CIK identity breaks." } ], - "draft_author_exposure": "Before the full draft, the author read #162's README results and v1 dev/val summary aggregates (per-rule means, and the P5/P95 of the 07:00|G0.20|V1000000 rule-exits, used only for sigma planning), the v1 cost-table cells, the #162 review findings, the broad-universe README headlines (including the development | validation | reserved table of date-clustered mean net excess over C0 for S1-S5, for example S2 H1 development -0.33% over 2017-2021 on the main universe) and the sweep. Review rounds 2-4 read the lane definitions, method texts and code paths listed in the archived draft's review_record, and opened no outcome row. The core restructure round (2026-09-24) read the round-4 remaining findings, the identity section of broad-universe/README.md (the TWTR under-enumeration and the FB/META duplicate history), the function headers of broad-universe collect_daily.py and coverage.py, the asof lines of extreme-gainer-audit/audit.py, and pit-availability plan.json and its README header. It opened no outcome row, return or results file. No v3-defined statistic (a D outcome, a MAX21 tercile or an H3 leg) has been computed or seen for any window.", + "draft_author_exposure": "Before the full draft, the author read #162's README results and v1 dev/val summary aggregates (per-rule means, and the P5/P95 of the 07:00|G0.20|V1000000 rule-exits, used only for sigma planning), the v1 cost-table cells, the #162 review findings, the broad-universe README headlines (including the development | validation | reserved table of date-clustered mean net excess over C0 for S1-S5, for example S2 H1 development -0.33% over 2017-2021 on the main universe) and the sweep. Review rounds 2-4 read the lane definitions, method texts and code paths listed in the archived draft's review_record, and opened no outcome row. The core restructure round (2026-09-24) read the round-4 remaining findings, the identity section of broad-universe/README.md (the TWTR under-enumeration and the FB/META duplicate history), the function headers of broad-universe collect_daily.py and coverage.py, the asof lines of extreme-gainer-audit/audit.py, and pit-availability plan.json and its README header. It opened no outcome row, return or results file. No v3-defined statistic (a D outcome, a MAX21 tercile or an H3 leg) has been computed or seen for any window. Review round 6 (2026-09-24) read the nine chatgpt-codex-connector review threads of PR #167 and the known_inactive_or_supplement_last_bar_year_histogram in blueprints/us-equities/broad-universe/receipt.json (counts of last-bar years, no price or return). Review round 7 (2026-09-24) read the round-7 findings, the identity and coverage-gate paragraphs of broad-universe/README.md (duplicate-pair and name-change counts), the exclusion regex of mover-early-entry/candidates.py, lines 405-420 of evaluate_v2.py (the D7 cash clamp) and the module header and valid_quote of quotes.py, all at aa6fc79. Neither round opened an outcome row, return or results file.", "consequence": "2017-2020 is exposed at aggregate level through broad-universe development (both lanes) and the S2 headline above. Development (2017-2019) and validation (2020) are therefore screening stages only. Validation decides which items the holdout gate opens for, but a validation pass is labelled 'screened', never confirmatory. Only the prospective holdout can support a confirmatory claim. Beside each validation result, the same statistic is reported on the least-exposed slice: symbol-sessions that were not a broad-universe lane decision (med20 of raw close x volume < $2M, fewer than 60 prior contiguous bars, or has_gap, as broad-universe protocol.json defines them). The slice is a sensitivity, not a separate test.", - "update_rule": "Every later read of data in any v3 window before the freeze is appended here with its date and purpose. No quote at a v3 entry, exit or terminal-search stamp is fetched before the freeze. Daily bars and official prints are fetched before the freeze only into the sealed pre_freeze_access_path snapshot, and are opened only by the count-only code. Any other pre-freeze read is forbidden, not tolerated. If one happens anyway, it is appended here and recorded as a numbered deviation before the freeze, and it voids this protocol version's tests: every item scores p = 1 in the validation Holm run, so the holdout never opens. A read of a v3-defined outcome in the validation window before the freeze voids validation. A read in the holdout window before the gate opens voids the holdout.", + "update_rule": "Every later read of data in any v3 window before the freeze is appended here with its date and purpose. No quote at a planned v3 entry or exit stamp is fetched before the freeze. The coverage-stamp quotes (pre_freeze_access_path.quote_coverage_stamps) are chosen with no reference to event membership and may fall inside a later D trade's hold or terminal-search window; only the count-only code opens them. Daily bars, official prints and quotes are fetched before the freeze only into the sealed pre_freeze_access_path snapshot, whose 2016-2020 screen part is a hashed sample, never the full screen, and are opened only by the count-only code. Any other pre-freeze read is forbidden, not tolerated. If one happens anyway, it is appended here and recorded as a numbered deviation before the freeze, and it voids this protocol version's tests: every item scores p = 1 in the validation Holm run, so the holdout never opens. A read of a v3-defined outcome in the validation window before the freeze voids validation. A read in the holdout window before the gate opens voids the holdout.", "pre_freeze_access_path": { - "rule": "Before the freeze, data are fetched only by committed count-only code in the study tree (run_discipline.study_code). Raw pages go straight into a private snapshot sealed by sha256. They are never opened by a person or by any other code. The count-only code emits only counts and rates: no price, spread, return, gain, covariate value, event list or symbol-level row. Its committed output is the input to the freeze preconditions. The sealed snapshot is the frozen evaluation's screen input (universe_and_identity.request_shapes). Every per-event request is fetched after the freeze under run_discipline.fetch.", - "snapshot_contents": "(0) The enumerated symbol list (universe_and_identity.enumeration), with its source responses. (1) Screen data for every XNYS session s from 2016-01-04 to 2020-12-31: the daily bars (adjustment raw, split and all) and official prints (auctions, with their sessions_io.official_price source labels) of sessions s-1 and s for the whole enumerated list, requested with asof = s. Also the corporate-action records from 2016-01-01 to the fetch date, and the coverage-stamp quotes of quote_coverage_stamps, requested with asof = the stamp's session. (2) The same screen data for every session from 2024-11-01 to 2025-12-31, for the holdout breakpoints only (tercile_rule.holdout_breakpoints). No minute bar and no other quote is fetched before the freeze. Every part is pinned by sha256. Each page record keeps its request asof and its fetch date (the vintage) as separate fields.", - "outputs": "Per calendar year and listing exchange: symbol-sessions with a daily bar; with a raw close >= $1; with each official_price source label for the open and the close (populations.official_prints); with split, reverse-split, cash-dividend and merger records; with a suspected unadjusted split (populations.suspected_unadjusted_split). Also coverage-stamp quote counts (quote_coverage_stamps), enumerated symbols per source, and screen requests answered with no row (universe_and_identity.empty_responses).", - "quote_coverage_stamps": "Eligible-quote rates are measured only at stamps that are never v3 entry or exit stamps: 12:17 ET and 14:43 ET (12:17 and 12:43 ET on a 13:00 early close). The symbol-sessions are selected by int.from_bytes(sha256('mover-v3-coverage|' + symbol + '|' + session)[:8], 'big') % 500 == 0 among all symbol-sessions with a daily bar and a raw close >= $1, with no reference to event membership. A stamp has an eligible quote if the NBBO prevailing at the stamp is eligible (populations.fill_quote) and at most 1000 ms old, or if the first eligible quote update after the stamp arrives within 60 s. Per year, the code reports the stamps with an eligible quote, those without one, and those that are fetch-incomplete (populations.fetch_failures), and nothing else.", - "runs": "The count-only code runs once, from the committed study tree named in its output. Every run, including a failed or partial one, is appended to this registry with its revision, tree, date and output sha256. A rerun is allowed only after a failed or incomplete run, and never after its rates were read. The first complete run's output governs coverage_rule.", - "not_allowed": "No pre-freeze fetch or read of a quote at a D entry, exit or terminal-search stamp. No opening of the sealed snapshot's bars or prints by a person or by code other than the count-only code. No computation of D membership, MAX21 or any outcome." + "rule": "Before the freeze, data are fetched only by committed count-only code in the study tree (run_discipline.study_code). Raw pages go straight into a private snapshot sealed by sha256. They are never opened by a person or by any other code. The count-only code emits only counts, rates and the coverage_rule decisions: no price, spread, return, gain, covariate value, event list or symbol-level row. Its committed output is the input to the freeze preconditions. The pre-freeze snapshot is never an input of the frozen evaluation. Its 2016-2020 screen part is a hashed sample of symbol-sessions, enough for coverage rates and a candidate count, so before the freeze no complete set of inputs to any development or validation outcome (including H3-c's official prints and close-to-close proxies of b_lane) exists on disk while the draft is still editable. The full 2016-2020 screen data and every per-event request are fetched only after the freeze under run_discipline.fetch (review round 7, C4).", + "snapshot_contents": "(0) The enumerated symbol list (universe_and_identity.enumeration), with its source responses, and the corporate-action records from 2016-01-01 to the fetch date. (1) A hashed sample of 2016-2020 screen data. The pair (symbol, s) is sampled if int.from_bytes(sha256('mover-v3-sample|' + symbol + '|' + s)[:8], 'big') % 20 == 0, for every enumerated symbol and every XNYS session s from 2016-01-04 to 2020-12-31, with no reference to event membership. For each sampled pair: the daily bars (adjustment raw, split and all) and official prints (auctions, with their sessions_io.official_price source labels) of sessions s-1 and s, requested with asof = s; the same daily bars requested at the provider's default asof, for identity_diagnostic only; and, for the pairs that quote_coverage_stamps selects, the coverage-stamp quotes requested with asof = s. No 2016-2020 screen row outside the sample is fetched before the freeze. (2) The full screen data (as in part 1, for the whole enumerated list, without the default-asof copy) for every session from 2024-11-01 to 2025-12-31, sessions of v1/v2 validation that are already exposed, for the holdout breakpoints only (tercile_rule.holdout_breakpoints). (3) The native identity probe of identity_probe. No minute bar and no other quote is fetched before the freeze. Every part is pinned by sha256. Each page record keeps its request asof and its fetch date (the vintage) as separate fields.", + "outputs": "From the part-1 sample, per calendar year and listing exchange (sampled counts, and rates where coverage_rule names them): symbol-sessions with a daily bar; with a raw close >= $1; with each official_price source label for the open and the close (populations.official_prints); with a suspected unadjusted split at s (populations.suspected_unadjusted_split); and candidates, the sampled symbol-sessions that pass the gain condition, the $1 floor, the suspected-split rule and exclusions 1 and 2 (a count only: no DV_reg, no price and no symbol; review round 7, C8). From part 0, per year: split, reverse-split, cash-dividend and merger records, and enumerated symbols per source. Also the coverage-stamp quote counts (quote_coverage_stamps), screen requests answered with no row (universe_and_identity.empty_responses), the counts of identity_diagnostic and identity_probe, and each year's coverage_rule rates with the kept or dropped decision and the item_rule result, computed by the count-only code itself (coverage_rule.decided_by_code).", + "quote_coverage_stamps": "Eligible-quote rates are measured only at stamps that are never planned v3 entry or exit stamps: 12:17 ET and 14:43 ET (12:17 and 12:43 ET on a 13:00 early close). They may fall inside a later D trade's hold or terminal-search window; only the count-only code opens them. The symbol-sessions are selected among the part-1 sampled symbol-sessions with a daily bar and a raw close >= $1 by int.from_bytes(sha256('mover-v3-coverage|' + symbol + '|' + session)[:8], 'big') % 25 == 0 (about 1 in 500 overall), with no reference to event membership. A stamp has an eligible quote if the NBBO prevailing at the stamp is eligible (populations.fill_quote) and at most 1000 ms old, or if the first eligible quote update after the stamp arrives within 60 s. Per year, the code reports the stamps with an eligible quote, those without one, and those that are fetch-incomplete (populations.fetch_failures), and nothing else.", + "runs": "The count-only code runs once, from the committed study tree named in its output. Every run, including a failed or partial one, is appended to this registry with its revision, tree, date and output sha256. A rerun is allowed only after a failed or incomplete run, and never after its rates were read. The first complete run's output governs coverage_rule. The count-only code is the only code that opens parts 1 and 3.", + "not_allowed": "No pre-freeze fetch or read of a quote at a planned D entry or exit stamp. No pre-freeze fetch of 2016-2020 screen rows outside the part-1 sample. No opening of the sealed snapshot's bars, prints or quotes by a person or by code other than the count-only code. No computation of D membership (the candidate count stops before DV_reg), MAX21 or any outcome.", + "identity_diagnostic": "A count-only measure of what asof = s screening cannot reach (review round 7, F4). A part-1 sampled pair (X, s) is identity-unreached if X's default-asof response has a daily bar for s with a raw close >= $1 whose raw OHLCV differs from X's asof = s row, or X has no asof = s row, and no retained name_change record names X as its new symbol. Such a history belongs to an issuer that carried another ticker on s, and asof = s screening reaches it only through that old ticker, which neither enumeration source holds when no rename record was retained. broad-universe, which requested the default asof, received such histories under the successor ticker. Per year, the code reports the sampled pairs with a default-asof bar and raw close >= $1, the identity-unreached pairs and their rate, and the distinct unreached symbols. The rate is an upper bound (an old ticker can still be in the asset master). It enters coverage_rule.year_rule and chronology.labels.validation.", + "identity_probe": "A count-only native check of the asof = t behaviour that universe_and_identity.asof relies on, for 2016-2020 (review round 7, F5). The list is fixed by rule: every name_change record of part 0 effective on a session d from 2016-01-04 to 2020-12-31 (old symbol O, new symbol N), ordered by sha256('mover-v3-probe|' + O + '|' + N + '|' + d) and capped at the first 100. For each, the code requests O with asof = d-3 and N with asof = d+3 (sessions), each over sessions d-3 .. d+3, for daily bars (raw), auctions and quotes (the 60 s ending 12:17 ET on d-1 and on d+1). A rename probe matches on an endpoint if the raw daily OHLCV, or the auction prints, are identical on every common session, and for quotes if both sides are non-empty. Ticker reuse is probed on every symbol that is the old symbol of one name_change record and the new symbol of a later one inside 2016-2020 (same order, capped at 50): requested for a session before the first record, once with asof before the first record and once with asof after the second, the two responses must differ in raw OHLCV. Only match and mismatch counts per endpoint are emitted. The freeze needs at least 20 rename probes, at least 95% matches for bars and for auctions and at least 90% for quotes; otherwise the protocol is not frozen as written. If no reuse case exists, the ticker-reuse claim of universe_and_identity.asof is recorded as unverified for 2016-2020." } }, "universe_and_identity": { - "enumeration": "The symbol list for 2016-2020 and for the 2024-11-01 to 2025-12-31 breakpoint sessions is the union of two sources. (a) The Alpaca asset master: GET /v2/assets with status=active and with status=inactive, class us_equity, every current exchange including OTC (today's OTC listing says nothing about 2016-2020 listing, which exclusions rule 1 decides from session data). Symbols that fail collect_daily.py DATA_SYMBOL at aa6fc79 (CUSIP-style placeholders the bars API rejects) are removed. (b) Every old and new symbol named by an Alpaca corporate-action record (GET /v1/corporate-actions, every action type, 2016-01-01 to the fetch date). This is broad-universe's supplement method (collect_daily.py and corporate_actions.py at aa6fc79), except that renamed-away tickers are kept, because per-session asof resolves them. The list is written once, sealed with the pre-freeze snapshot and pinned by sha256, and its per-source counts are count-only outputs. Survivorship limit: a name delisted in 2016-2019 that is absent from both sources cannot be discovered (broad-universe/README.md: 'names delisted in 2016-2019 without a retained action cannot be discovered from entitled sources'), so development keeps the survivorship-limited label. Holdout collection batches enumerate from the same two sources, refreshed at each batch, united with every earlier batch's list.", - "asof": "Every request for bars, auctions (official prints) or quotes carries Alpaca's asof parameter set to a session date, never to the fetch date and never to the provider default. A screen request for session s uses asof = s. Every request for a D event decided on session t uses the event's as-known symbol with asof = t. The as-known symbol is the one under which the event appeared in the asof = t screen. Such requests cover the event's lookback (t-22 .. t-1), its minute bars, its entry, exit and terminal-search quotes and its official prints through the end of its hold or search window. A rename inside a hold or lookback is therefore resolved to the same issuer. The fetch date is kept as a separate vintage field in each page record. quotes.py fetch_one, whose PROTOCOL_ASOF is fixed at '2026-09-21', is never called.", - "request_shapes": "The screen data are the sealed snapshot for 2016-2020 and for 2024-11 to 2025-12, and the collection batches for the holdout. The gain condition (close[t], ref(t), f(t)), the $1 floor, suspected unadjusted splits at t and exclusions 1 and 2 are evaluated on the screen data. Only the symbol-sessions that pass them are candidates, and only candidates have per-event data fetched, with asof = t. Per-event data are the minute bars for DV_reg (the last membership condition), med20, the entry bar and cum_dv; the lookback daily bars and official closes for MAX21 and exclusion 3; quotes; and hold-period prints. If a per-event official close of t or t-1 differs from the screen value, the screen value governs membership, and the difference is counted per stage.", + "enumeration": "The symbol list for 2016-2020 and for the 2024-11-01 to 2025-12-31 breakpoint sessions is the union of two sources. (a) The Alpaca asset master: GET /v2/assets with status=active and with status=inactive, class us_equity, every current exchange including OTC (today's OTC listing says nothing about 2016-2020 listing, which exclusions rule 1 decides from session data). Symbols that fail collect_daily.py DATA_SYMBOL at aa6fc79 (CUSIP-style placeholders the bars API rejects) are removed. (b) Every old and new symbol named by an Alpaca corporate-action record (GET /v1/corporate-actions, every action type, 2016-01-01 to the fetch date). This is broad-universe's supplement method (collect_daily.py and corporate_actions.py at aa6fc79), except that renamed-away tickers are kept, because per-session asof resolves them. The list is written once, sealed with the pre-freeze snapshot and pinned by sha256, and its per-source counts are count-only outputs. Survivorship limit: a name delisted in 2016-2019 that is absent from both sources cannot be discovered (broad-universe/README.md: 'names delisted in 2016-2019 without a retained action cannot be discovered from entitled sources'), so development keeps the survivorship-limited label. A second limit concerns identity: an issuer renamed without a retained name_change record, whose old ticker is in neither source, is not reached for its pre-rename sessions by asof = s screening, although broad-universe, which requested the provider's default asof, received that history under the successor ticker (113 of its 155 duplicate pairs were resolved without a rename record, and name_change records number 57 for 2017-2019; broad-universe/README.md). exposure_registry.pre_freeze_access_path.identity_diagnostic measures it per year, and coverage_rule.year_rule and chronology.labels.validation use it. Holdout collection batches enumerate from the same two sources, refreshed at each batch, united with every earlier batch's list.", + "asof": "Every request for bars, auctions (official prints) or quotes carries Alpaca's asof parameter set to a session date, never to the fetch date and never to the provider default. A screen request for session s uses asof = s. Every request for a D event decided on session t uses the event's as-known symbol with asof = t. The as-known symbol is the one under which the event appeared in the asof = t screen. Such requests cover the event's daily bars (raw, split and all) from t-22 through the end of its terminal-search window, its minute bars, its entry, exit and terminal-search quotes and its official prints through the end of its hold or search window. A rename inside a hold or lookback is therefore resolved to the same issuer. The fetch date is kept as a separate vintage field in each page record. quotes.py fetch_one, whose PROTOCOL_ASOF is fixed at '2026-09-21', is never called.", + "request_shapes": "The screen data are: for 2016-2020, the full screen fetch made after the freeze (run_discipline.fetch: daily bars raw, split and all, and official prints of sessions s-1 and s for the whole enumerated list, with asof = s, sealed and pinned before any outcome); for 2024-11 to 2025-12, pre-freeze snapshot part 2; and for the holdout, the collection batches. The pre-freeze sample is never a screen input. The gain condition (close[t], ref(t), f(t)), the $1 floor, suspected unadjusted splits at t and exclusions 1 and 2 are evaluated on the screen data. Only the symbol-sessions that pass them are candidates, and only candidates have per-event data fetched, with the as-known symbol and asof = t. Per-event data are: the minute bars for DV_reg (the last membership condition), med20, the entry bar and cum_dv; the event's daily bars with adjustment raw, split and all, one request per adjustment, from t-22 through the scheduled close of the 5th session after its latest planned exit session (for a censored trade, after the segment's last session; populations.terminal_exits); the official prints over the same sessions; and quotes. The per-event daily bars supply MAX21, exclusion 3, every in-hold share factor and the D7 cash term (populations.corporate_actions), each computed within that event's responses only; screen rows, fetched under other symbols, asof values and vintages, are never joined into a per-event computation (review round 7, F1). If a per-event official close of t or t-1 differs from the screen value, the screen value governs membership, and the difference is counted per stage.", "dedup": "Before membership, coverage.dedupe_identity at aa6fc79 (DUPLICATE_SERIES_MIN_SESSIONS = 20, DUPLICATE_SERIES_MIN_SPAN_COVERAGE = 0.98, survivor ranking by retained rename record, then price continuity, then active status, then lexicographic order) is applied to the screen table of session-s rows (each from its asof = s request). There is also a same-session guard. Two candidate D events on one session whose raw daily OHLCV (volume > 0) is identical on both s-1 and s are one event, kept under the higher-ranked symbol. Because the rows are identical, the choice cannot change any price. Pairs removed by either rule are counted per stage.", "empty_responses": "HTTP 200 with no row is an empty response, never silently dropped, and never fetch-incomplete (fetch-incomplete is reserved for HTTP errors and timeouts, populations.fetch_failures). Per stage, the counts reported are: enumerated symbols with no bar in the stage; screen requests with no row; and, per item, event requests with no row. Each empty event request has a fixed consequence. An empty entry-quote window is a no-entry. An empty exit or search window is a terminal exit (populations.terminal_exits), which stays in the statistic. An empty official-print response is a missing print: no event at t, or a missing H3-c leg. An empty lookback makes MAX21 undefined, so the event is unassigned.", - "tests": "Synthetic tests with a recorded fake client cover: FB/META (a renamed-away ticker served with its successor's history under both symbols gives one event, not two); a ticker reused by a new issuer after a delisting (asof = t resolves the old issuer); a rename inside a 5-session hold (the as-known symbol with asof = t returns the whole hold); an empty exit response (a terminal exit, counted); and a request built with the fetch date as asof (refused)." + "tests": "Synthetic tests with a recorded fake client cover: FB/META (a renamed-away ticker served with its successor's history under both symbols gives one event, not two); a ticker reused by a new issuer after a delisting (asof = t resolves the old issuer); a rename inside a 5-session hold (the as-known symbol with asof = t returns the whole hold); a split and a rename inside the same hold (the share factor and the cash term come from the per-event responses under the as-known symbol, never from screen rows); an empty exit response (a terminal exit, counted); and a request built with the fetch date as asof (refused)." }, "populations": { "D_daily": "Symbol-sessions t with split-consistent official-close gain close[t]/ref(t) - 1 >= 0.20 - 1e-9, where close[t] is the accepted official close of populations.official_prints and ref(t) is as populations.formulas states. They also need an official close >= $1.00 and a regular-session dollar volume DV_reg(t) >= $1,000,000. A session without an accepted official close is not an event and is counted; there is no bar fallback. The decision is at 16:15 ET on t. Entry is at the first eligible quote at or after 09:35 ET on t+1 (and at or after decision + 60 s), with an entry timeout of 300 s; otherwise there is no entry, and it is counted. The b_overnight entry is the exception (arms.b_overnight).", - "formulas": "Restated exactly, so membership does not depend on unpinned code. f(t) = (raw close / split-adjusted close on the previous XNYS session) / (the same on t), from daily bars with adjustment=raw and adjustment=split. It is set to 1 when |f - 1| <= 1e-2 or any input is missing. This is rules.split_factor's arithmetic at aa6fc79, with adjustment=split closes in place of adjustment=all closes, a deliberate departure: with adjustment=all, a cash dividend of about 1% or more would be booked as a share change. ref(t) = the accepted official close of the previous XNYS session / f(t). If that session has no accepted official close, ref(t) is undefined and t is not an event (counted). DV_reg(t) = the summed vwap x volume of the 1-minute SIP bars of session t that start at or after 09:30 ET and before the scheduled close (session_calendar); a session with no such bar has DV_reg 0. The split-consistent official-close return of session j is r(j) = C(j) x f(j) / C(j-1) - 1, where C is the accepted official close; r(j) is undefined if either close is missing. MAX21(t) = the maximum over k = 1..21 of r(t-k). These are the 21 returns of sessions t-21 .. t-1, from the 22 accepted official closes of sessions t-22 .. t-1 (the event's own session t is not in the window). If any of those closes is missing, or any session t-21 .. t-1 is a suspected unadjusted split, MAX21 is undefined and the event is unassigned (counted); there is no maximum over fewer returns. The sizing window W(t) is the 20 sessions ending at t (its close is known at the 16:15 decision). med20 = the median of DV_reg over W(t). sigma_d = the sample standard deviation (ddof = 1) of log(1 + r(j)) over the 20 sessions j of W(t). If any DV_reg or r(j) in W(t) is undefined, the trade cannot be sized and is a counted no-fill. The entry bar is the last 1-minute SIP bar of the entry session (from 04:00 ET) that is complete at the fill-decision time X, meaning it starts at or before X - 60 s. If there is none, the entry-bar cap is 0 and the trade is a counted no-fill. A synthetic test fixes each formula, including a split day, a missing previous close, a missing close inside the MAX21 window, a suspected unadjusted split inside it and an entry with no completed bar.", + "formulas": "Restated exactly, so membership does not depend on unpinned code. Let a(s) = raw daily close(s) / split-adjusted daily close(s), the cumulative split factor of session s, from the daily bars with adjustment=raw and adjustment=split. For sessions u < v, the share factor F(u, v) = a(u) / a(v), set to 1 when |F - 1| <= 1e-2. F(u, v) is undefined if u or v has no daily bar in either adjustment, and every quantity that uses an undefined F is undefined (counted). F telescopes, so a split that takes effect on a session with no bar (for example, halted all day) is still carried between the surrounding sessions with bars. f(j) = F(j-1, j) for consecutive XNYS sessions. This is rules.split_factor's arithmetic at aa6fc79 with two deliberate departures: adjustment=split closes replace adjustment=all closes (with adjustment=all, a cash dividend of about 1% or more would be booked as a share change), and a missing input makes F undefined instead of 1 (a default of 1 left a split on a session without a bar unbooked, review round 7 F2). ref(t) = the accepted official close of the previous XNYS session / f(t). If that session has no accepted official close, or f(t) is undefined, ref(t) is undefined and t is not an event (counted). DV_reg(t) = the summed vwap x volume of the 1-minute SIP bars of session t that start at or after 09:30 ET and before the scheduled close (session_calendar); a session with no such bar has DV_reg 0. The split-consistent official-close return of session j is r(j) = C(j) x f(j) / C(j-1) - 1, where C is the accepted official close; r(j) is undefined if either close is missing or f(j) is undefined. MAX21(t) = the maximum over k = 1..21 of r(t-k). These are the 21 returns of sessions t-21 .. t-1, from the 22 accepted official closes of sessions t-22 .. t-1 (the event's own session t is not in the window). If any of those closes is missing, or any session t-21 .. t-1 is a suspected unadjusted split, MAX21 is undefined and the event is unassigned (counted); there is no maximum over fewer returns. The sizing window W(t) is the 20 sessions ending at t (its close is known at the 16:15 decision). med20 = the median of DV_reg over W(t). sigma_d = the sample standard deviation (ddof = 1) of log(1 + r(j)) over the 20 sessions j of W(t). If any DV_reg or r(j) in W(t) is undefined, the trade cannot be sized and is a counted no-fill. The entry bar is the last 1-minute SIP bar of the entry session (from 04:00 ET) that is complete at the fill-decision time X, meaning it starts at or before X - 60 s. If there is none, the entry-bar cap is 0 and the trade is a counted no-fill. A synthetic test fixes each formula, including a split day, a missing previous close, a missing close inside the MAX21 window, a suspected unadjusted split inside it, a reverse split on a session with no daily bar inside a lookback (MAX21 undefined) and inside a hold (carried by F between the surrounding bars), and an entry with no completed bar.", "official_prints": "The accepted source labels of sessions_io.official_price at aa6fc79 are these. The listing exchange is the exchange of the largest condition-O opening print; a session with no O print has no listing exchange. Official open: opening_print_listing_exchange only. Official close: closing_print_listing_exchange or official_close_listing_exchange only. A closing_print_other_exchange, or a session with no O print, counts as no official close and is counted per year. The same rule applies to close[t], to the previous close in ref(t) and to every H3-c leg. For D, the O print of session t precedes the 16:15 ET decision by construction. The synthetic suite has one case per source label, including the rejected label and a session with a closing print but no opening print.", - "suspected_unadjusted_split": "Applied before membership. For session t, let r = raw daily close[t] / raw daily close of the previous XNYS session. Session t is a suspected unadjusted split if f(t) = 1 and |r - n| <= 0.01 x n or |r - 1/n| <= 0.01 / n for some integer n in 2..100. Such a session is not an event. Any trade whose hold spans t (from entry session to exit session) is excluded, and any event whose MAX21 window contains t is unassigned. All three are counted per stage. The rule also removes genuine moves within 1% of an integer multiple (for example +100%). That loss is preregistered and counted, not corrected.", - "exclusions": "(1) Not exchange-listed on the session: a symbol-session is eligible only if it has a listing exchange under populations.official_prints, so OTC-only trading is excluded by as-known session data. The current Alpaca asset exchange and tradable flags are never used. (2) Warrants, units and rights, by this literal rule (candidates.py at aa6fc79): exclude the symbol if (len(symbol) == 5 and re.search('[A-Z]{4}[WUR]$', symbol)) or re.search('\\.(WS|U|R|W)', symbol) (Python re, unanchored search), or if it contains '/'. (3) Symbols with fewer than 21 prior XNYS sessions with a daily bar. Each exclusion is counted per stage, with a synthetic test per rule. There is no halt exclusion (halts).", + "suspected_unadjusted_split": "Applied at t before membership, on the screen data. For session t, let r = raw daily close[t] / raw daily close of the previous XNYS session. Session t is a suspected unadjusted split if f(t) = 1 and |r - n| <= 0.01 x n or |r - 1/n| <= 0.01 / n for some integer n in 2..100. Such a session is not an event, and any event whose MAX21 window (t-21 .. t-1) contains a suspected session is unassigned; both are counted per stage. The rule also removes genuine moves at t within 1% of an integer multiple (for example +100%); that loss is preregistered and counted, not corrected. Inside a hold the ratio rule is never applied to a primary statistic: hold-period close ratios are outcomes, and the rule would drop real tail moves of low-priced movers (+100%, +200%, -50%, -67%). A trade is excluded from the primary statistics only on price-independent evidence: a retained forward_split or reverse_split record effective on a session j with entry session < j <= planned exit session while F(entry session, planned exit session) = 1, so the adjusted bars do not carry the recorded split. That exclusion uses only records and the cumulative factors a(s) (ratios of two closes of one session, never a return), so count_unit applies it too; it is counted per item and stage. Removing trades whose hold spans a session flagged by the ratio rule is a sensitivity only (statistics.sensitivities; review round 7, C3).", + "exclusions": "(1) Not exchange-listed on the session: a symbol-session is eligible only if it has a listing exchange under populations.official_prints, so OTC-only trading is excluded by as-known session data. The current Alpaca asset exchange and tradable flags are never used. (2) Warrants, units and rights: exclude the symbol if (len(symbol) == 5 and re.search('[A-Z]{4}[WUR]$', symbol)) or re.search('\\.(WS|U|R|W)', symbol) (Python re, unanchored search), which is candidates.py's rule at aa6fc79 (its SQL regexp_matches, line 47), or if the symbol contains '/', a v3 addition that candidates.py does not have. (3) Symbols with fewer than 21 prior XNYS sessions with a daily bar. Each exclusion is counted per stage, with a synthetic test per rule. There is no halt exclusion (halts).", "fetch_failures": "The frozen fetch code is v3-owned and lives in the study tree. It writes page and stamp records in quotes.py's format at aa6fc79. Every request is retried up to 3 times, with waits of 1 s, 4 s and 16 s. A request that still fails with an HTTP error, a URLError or a timeout makes its stamp fetch-incomplete (a stamp_incomplete record). The run continues and does not raise. The trade that needs the stamp is excluded, not counted as a no-entry or a terminal exit, and the count is reported per item. If more than 1% of the stamps a stage requires are fetch-incomplete after the single re-fetch of run_discipline.fetch, that stage is void: every item scores p = 1 and is labelled underpowered there, with the reason stated. The rate is committed before any outcome of the stage, and a void is final. Synthetic tests cover an exhausted URLError and a timeout (each becomes stamp_incomplete, and the pool run completes), a success on the third retry, and an empty response (not fetch-incomplete).", "session_calendar": "XNYS from exchange_calendars 4.13.2 (the version rules.py used for 2021-2026), for 2016-01-01 through the end of the last possible holdout extension. The session list, with each session's scheduled open and close, is written to the v3 data file data/session-calendar.json and pinned by sha256 at the freeze (run_discipline.data_files). A 'session' anywhere in this protocol is every scheduled XNYS trading session, early closes included. The pinned file cannot know about unscheduled future closures, such as a national day of mourning. Such a closure is removed only by a line in data/session-calendar-amendments.jsonl that cites its primary source (an NYSE notice), is committed and pushed, and is logged in the access log (purpose 'amend') before the affected session. It may concern only a date on or after the freeze session. The freeze session and N0 keep the dates computed at the freeze, and if N0 itself closes, the holdout starts at the next session. The 252-session holdout and each 63-session block count actual sessions. Synthetic tests use the 2017-07-03, 2017-11-24, 2018-11-23, 2019-12-24 and 2020-11-27 early closes and a removed session inside the holdout.", "session_times": "Every '15:55 ET' in this protocol means the scheduled close (session_calendar) minus 5 minutes (12:55 ET on a 13:00 early close). Eligible quotes are regular-session quotes, at or after 09:30 ET and before the scheduled close. D decisions stay at 16:15 ET on early-close sessions.", - "fill_quote": "For a fill at or after time X, the quote is the NBBO prevailing at X (the latest quote update stamped at or before X) if it is eligible and X minus its timestamp is at most 1000 ms. Otherwise it is the first eligible quote update stamped after X, with the fill time equal to its timestamp. Eligible means bid > 0 and ask > bid (locked and crossed quotes excluded, as quotes.py valid_quote at aa6fc79 does) and regular session (session_times). Timeouts run from X. Synthetic tests cover a fresh prevailing quote, a stale one, a locked one and no quote inside the timeout.", + "fill_quote": "For a fill at or after time X, the quote is the NBBO prevailing at X (the latest quote update stamped at or before X) if it is eligible and X minus its timestamp is at most 1000 ms. Otherwise it is the first eligible quote update stamped after X, with the fill time equal to its timestamp. Eligible means bid > 0 and ask > bid (locked and crossed quotes excluded, as quotes.py valid_quote at aa6fc79 does; its pinned copy is applied only as a predicate to one quote at a time, never to a quote list, where it would skip newer locked or crossed quotes and return an older one) and regular session (session_times). Timeouts run from X. Synthetic tests cover a fresh prevailing quote, a stale one, a locked one and no quote inside the timeout.", "fill_price": "Every entry and non-terminal exit fills at the mid (bid + ask) / 2 of the eligible quote that fills it, so gross returns are mid to mid. cost_model.per_side adds the per-side cost on top.", "terminal_exits": { - "search_window": "An exit fills at the first eligible quote at or after its planned stamp. If none arrives before the scheduled close, the search continues session by session through the scheduled close of the 5th XNYS session after the planned exit session E (E+5). It never goes past the last session of the trade's segment (chronology.boundaries). A fill inside the window is a normal exit, flagged delayed and counted.", + "search_window": "An exit fills at the first eligible quote at or after its planned stamp. If none arrives before the scheduled close, the search continues session by session through the scheduled close of the 5th XNYS session after the planned exit session E (E+5). For a trade censored at a segment end, E is the segment's last session (segment_end). A fill inside the window is a normal exit, flagged delayed and counted.", "terminal": "A held position with no eligible quote in its search window is terminal. This covers a delisting, a move to OTC-only trading (outside the SIP NBBO), an SEC trading suspension, a halt that does not resume inside the window, and an exit request answered empty (universe_and_identity.empty_responses).", - "booking": "Terminal-merger: if a retained corporate-action record shows a cash or stock merger of the symbol effective on a session from the entry session through the end of the search window, the exit is booked at the last eligible bid of the hold. That is the bid of the latest eligible quote stamped after the entry fill and at or before the planned exit stamp, and merger arbitrage keeps it near the consideration. Fees are charged on that raw bid, with no further spread cost and no impact. Terminal-zero: every other terminal trade is booked at an exit value of 0, a total loss of the position, with no exit fees. This is the stated choice. Booking at the last eligible bid for all terminal trades is a sensitivity only.", + "booking": "Terminal-merger: if a retained corporate-action record of type cash_merger, stock_merger or stock_and_cash_merger names the held as-known symbol as the acquiree (its acquiree symbol field) and is effective on a session from the entry session through the end of the search window, the exit is booked at the last eligible bid of the hold. That is the bid of the latest eligible quote stamped after the entry fill and at or before the planned exit stamp, and merger arbitrage keeps it near the consideration. Fees are charged on that raw bid, with no further spread cost and no impact (populations.corporate_actions gives the formula). A record in which the held symbol is only the acquirer does not qualify. Terminal-zero: every other terminal trade is booked at an exit value of 0, a total loss of the position, with no exit fees. This is the stated choice. Booking at the last eligible bid for all terminal trades is a sensitivity only. Corporate-action records are thin before 2020 (broad-universe/README.md), so a merger without a retained record books terminal-zero; per stage and year, terminal-zero trades with no merger record of any kind for the symbol are reported beside the merger-record counts.", "in_statistics": "Terminal trades stay in every primary statistic, in count_unit and in the minimum samples. For each item and stage, the counts of delayed, terminal-merger and terminal-zero exits are reported. H3-c has no position: an event whose hold loses a leg this way lacks its 8 legs, is excluded from H3-c, and is counted as terminal in H3-c's report.", - "segment_end": "A trade censored at a segment end (chronology.boundaries) searches from its censoring stamp through the scheduled close of the segment's last session. With no eligible quote by then, it is terminal under the same booking and flagged censored-terminal.", - "tests": "Synthetic tests: a delisting inside a 5-session hold (terminal-zero); a delisting on a segment's last session (censored-terminal); a halt that resumes on E+3 (delayed exit); a halt that has not resumed by E+5 (terminal-zero); a cash merger effective inside the hold (terminal-merger at the last eligible bid); an empty exit response; and a check that each case is counted and kept in the statistic." + "segment_end": "A trade censored at a segment end (chronology.boundaries) exits at the first eligible quote at or after its censoring stamp, 15:55 ET on the segment's last session. If none arrives before that session's scheduled close, the search continues past the segment end under the normal rule, through the scheduled close of the 5th XNYS session after the segment's last session, with the event's as-known symbol and asof = t. Quotes after the segment end are this trade's exit data only; they create no entry and no event. A fill there is flagged censored-delayed. With no eligible quote by then, the trade is terminal under the same booking and flagged censored-terminal. Where a segment ends therefore never by itself books a -100% (review round 7, C11).", + "tests": "Synthetic tests: a delisting inside a 5-session hold (terminal-zero); a delisting on a segment's last session (censored-terminal); a censored trade whose first eligible quote comes on the session after the segment end (censored-delayed); a halt that resumes on E+3 (delayed exit); a halt that has not resumed by E+5 (terminal-zero); a cash merger effective inside the hold (terminal-merger at the last eligible bid); an empty exit response; and a check that each case is counted and kept in the statistic." }, - "corporate_actions": "For every multi-session arm (b_lane, b_overnight) and each H3-c leg, deviation D7's method is restated. The exit uses the raw exit mid (the raw official print for H3-c). Shares change by the product of the split factors f(j) (populations.formulas) over the sessions after the entry session through the exit session. The rest of the adjustment=all close-to-close return over the hold is booked as cash per original share, without spread or fees. Fees use the actual exit shares and the raw exit price. H3-c legs use log((raw end print x split factor + cash) / raw start print). Mergers follow populations.terminal_exits. Synthetic tests, passed before the freeze, cross a split, an ordinary ex-dividend date and a special cash dividend of 5% (booked as cash with f = 1, never as a share change, and not flagged as a suspected unadjusted split).", + "corporate_actions": "For every arm and each H3-c leg, deviation D7's method (evaluate_v2.py lines 416-420 at aa6fc79) is restated with its cash clamp. Let e be the entry session and x the exit session (the session of the exit fill, or of the terminal-merger bid), N = filled notional / entry mid the entry shares, and raw_c and all_c the adjustment=raw and adjustment=all daily closes. Every input comes from the event's per-event daily-bar responses (universe_and_identity.request_shapes), never from screen rows. The share factor is F = F(e, x) (populations.formulas), and exit shares are N x F. The cash term is anchored at the daily closes of e and x for every arm, including b_overnight (e = t+1, x = t+2) and each overnight H3-c leg (e = t+k, x = t+k+1): cash = raw_c(e) x all_c(x) / all_c(e) - F x raw_c(x) per original share, then cash = cash if cash > 1e-4 x raw_c(e) else 0.0. For a same-session trade or leg (a_intraday, the intraday H3-c legs) e = x, so F = 1 and cash = 0. The trade net return is net = (N x F x exit_mid x (1 - c_out) - sell_fees + N x cash) / (N x entry_mid x (1 + c_in)) - 1, where c_in and c_out are the entry and exit costs of cost_model.per_side (fractions of the mid, impact included) and sell_fees is the dollar SEC Section 31 fee and FINRA TAF on selling N x F shares at the raw exit mid (cost_model.fees; no fee on the buy, and commission 0). A terminal-merger exit uses exit_mid = the last eligible bid and c_out = 0, with fees on that bid. A terminal-zero trade has net = -1 exactly (no exit value, fees or cash). H3-c legs are gross: an overnight leg is log((raw official open(x) x F + cash) / raw official close(e)), and an intraday leg is log(raw official close / raw official open) of one session. An undefined F excludes the trade, or the H3-c event, and is counted. Mergers follow populations.terminal_exits. Synthetic tests, passed before the freeze, fix the net-return formula on a small table and cross a split, an ordinary ex-dividend date, a special cash dividend of 5% (booked as cash with F = 1, never as a share change, and not flagged as a suspected unadjusted split), a cash term below the clamp and a split effective on the exit session of b_overnight.", "halts": "No halt source is used, historically or prospectively, so no trade set depends on a later data purchase. As in catalyst-experiment, a halt is never inferred from missing data. An entry fills only at an eligible quote, and an entry with none inside its timeout is a counted no-entry. An exit waits for an eligible quote and becomes terminal after its search window (populations.terminal_exits). Limitation: a trade that sits through a short unobserved halt is not flagged.", "parameter_provenance": "G = 0.20 and V = $1M are v1's loosest gain and middle volume floor. They were fixed for sample size after v1's all-negative dev/val grid was seen (a disclosed exposure) and are not tuned on any v3 window." }, @@ -195,10 +197,11 @@ "coverage_rule": { "scope": "All five items, with one set of kept years per stage. It is decided at the freeze from the count-only outputs (exposure_registry.pre_freeze_access_path), before any outcome, and recorded beside multiple_testing.data_status_at_freeze.", "no_fallback": "A session or symbol without an accepted official print or an eligible quote stays a counted non-event, no-entry, terminal or excluded trade under its own rule. No bar, quote or other-exchange substitute is ever used.", - "year_rule": "A calendar year of 2016-2020 is dropped from its stage entirely (every session of that year) if its accepted official-close rate is below 90% (symbol-sessions with an accepted official close / symbol-sessions with a daily bar and raw close >= $1), or if its eligible-quote rate at the coverage stamps is below 80%. If 2016, the warm-up year, is dropped, events of early 2017 stay unassigned under tercile_rule.minimum until 60 prior events accrue. The thresholds are design choices, not measured values.", - "item_rule": "If fewer than 2 development years remain, or 2020 is dropped, no item is tested: all 5 items score p = 1 at every stage, permanently, and that is the recorded result of this protocol version.", + "year_rule": "A calendar year of 2016-2020 is dropped from its stage entirely (every session of that year) if any of these holds, each from the part-1 sample (exposure_registry.pre_freeze_access_path): (a) its accepted official-close rate is below 90%, where the rate = sampled symbol-sessions with an accepted official close / sampled symbol-sessions with a daily bar and raw close >= $1; (b) its eligible-quote rate is below 80%, where the rate = coverage stamps with an eligible quote / (stamps with an eligible quote + stamps without one + fetch-incomplete stamps), so a fetch-incomplete stamp counts against coverage; (c) its identity-unreached rate (identity_diagnostic) is above 10%. If 2016, the warm-up year, is dropped, events of early 2017 stay unassigned under tercile_rule.minimum until 60 prior events accrue. The thresholds are design choices, not measured values.", + "item_rule": "If 2020 is dropped, no item is tested: all 5 items score p = 1 at validation and the holdout, permanently, and that is the recorded result of this protocol version. Development years do not condition testing, because development gates nothing (multiple_testing.development_screen) and validation breakpoints use validation sessions only (tercile_rule.breakpoints). If every development year is dropped, development is reported as not computed and every item is underpowered there. (The former condition of at least 2 development years was left over from the removed development gate; review round 7, C13.)", "dropped_year_consequences": "(a) Breakpoints: a dropped year's sessions are skipped, and a dropped year's events never enter a trailing window. (b) Segments: each edge of a dropped year is a segment boundary under chronology.boundaries, with censoring and, after it, the embargo. (c) Bootstrap: the stage's kept sessions are concatenated in date order, and the circular block bootstrap wraps over that kept list. Each has a synthetic test with a dropped middle year.", - "limitation": "The coverage stamps sample the whole $1+ universe, not movers, so mover quote coverage may be lower. Per-stage no-entry, terminal and excluded counts are reported so that this is visible." + "limitation": "The coverage stamps sample the whole $1+ universe, not movers, so mover quote coverage may be lower. Per-stage no-entry, terminal and excluded counts are reported so that this is visible.", + "decided_by_code": "Nobody applies year_rule or item_rule by hand. The count-only code computes each year's three rates and emits the kept or dropped decision per year and the item_rule result. The commit that adds the count-only code records the sha256 of this coverage_rule object, as UTF-8 of json.dumps(obj, sort_keys=True, separators=(',', ':'), ensure_ascii=False), and the code refuses to run if the protocol's coverage_rule hashes differently. The freeze is refused if the frozen coverage_rule hashes differently from the value in the governing count-only run's log entry (review round 7, C9)." }, "tercile_rule": { "breakpoints": "For an event decided on session s, the breakpoints are the 1/3 and 2/3 empirical quantiles (numpy 'linear' method) of MAX21 over all D events with a defined MAX21 whose decision session lies in the 252 kept sessions strictly before s. Every D event counts, whether it entered, had no entry or had no fill. Every input is known at the decision. For an event of the validation stage, the window is restricted to validation sessions strictly before s, so no survivorship-limited development or warm-up event sets a validation breakpoint.", @@ -221,30 +224,31 @@ ], "holdout": { "kind": "prospective", - "window": "252 sessions (session_calendar) starting at session N0 = the 40th session after the session of the freeze. N0 is fixed by the freeze commit alone. The freeze commit is the first commit reachable from origin/main (first-parent history) in which this file's status is frozen. The repository squash-merges pull requests, so a commit that exists only on a PR branch is never the freeze commit. Its committer timestamp, converted to America/New_York, is the freeze time. The session of the freeze is the first XNYS session whose scheduled open is at or after the freeze time. D decisions made on session N0 - 1 enter on N0 and belong to the holdout. Development and validation run by committed automation started at the freeze. The commit that adds the validation results file (with its sha256 in the run log) must be reachable from origin/main before 09:30 ET on session N0, measured by the GitHub-recorded time it became reachable (the pull request's merged_at, or the push event for a direct push). If it is not, the holdout of this protocol version is void for every item; a new protocol version is required. There is no deferral.", - "extension": "If any carried item has fewer than its minimum holdout sample after 252 sessions (for H1-D, its smaller group), the holdout is extended in 63-session blocks, at most 2. The window is common to every carried item. A carried item still below its minimum after the second block is labelled underpowered. Each extension decision uses one 'count' read whose unit is count_unit, never an exit or outcome price.", - "count_unit": "Integers only, keyed as follows: 'H1-D:high' and 'H1-D:low' (one integer per group, since H1-D is a difference test), 'H1-D-b_lane-low', 'H3-a', 'H3-b' and 'H3-c'. For a tradable cell or an H1-D group, the count is the trades that pass membership and every exclusion, have an entry fill quote inside the entry timeout and filled notional >= $1,000, are not fetch-incomplete, hold no suspected unadjusted split and have the tercile the key needs. Their exit is irrelevant, because terminal exits stay in the statistic (populations.terminal_exits). For H3-c, the count is the events with all 8 official legs present. Only presence and exclusion flags, decision-time inputs (MAX21, med20, the entry bar) and entry-quote presence are evaluated. No mid, spread, return or exit value is computed or emitted. The same unit applies to every minimum_sample check at every stage, and the extension rule applies to the smallest key of each carried item. A synthetic test asserts that the count output carries exactly these keys with integer values, and that the count path calls no price or return function.", + "window": "252 sessions (session_calendar) starting at session N0 = the 40th session after the session of the freeze. N0 is fixed by the freeze commit alone. The freeze commit is the first commit reachable from origin/main (first-parent history) in which blueprints/us-equities/mover-v3/protocol-core-draft.json has status frozen; the file keeps this path at and after the freeze and is never renamed or moved. The repository squash-merges pull requests, so a commit that exists only on a PR branch is never the freeze commit. Its committer timestamp, converted to America/New_York, is the freeze time. The session of the freeze is the first XNYS session whose scheduled open is at or after the freeze time. D decisions made on session N0 - 1 enter on N0 and belong to the holdout. Development and validation run by committed automation started at the freeze. The commit that adds the validation results file (with its sha256 in the run log) must be reachable from origin/main before 09:30 ET on session N0, measured by the GitHub-recorded time it became reachable (the pull request's merged_at, or the push event for a direct push). If it is not, the holdout of this protocol version is void for every item; a new protocol version is required. There is no deferral.", + "extension": "If any carried item has fewer than its minimum holdout sample after 252 sessions (for H1-D, its smaller group), the holdout is extended in 63-session blocks, at most 2. The window is common to every carried item. A carried item still below its minimum after the second block is labelled underpowered. Each extension decision is made by the committed automation, from one 'count' whose unit is count_unit, never an exit or outcome price.", + "count_unit": "Integers only, keyed as follows: 'H1-D:high' and 'H1-D:low' (one integer per group, since H1-D is a difference test), 'H1-D-b_lane-low', 'H3-a', 'H3-b' and 'H3-c'. For a tradable cell or an H1-D group, the count is the trades that pass membership and every exclusion, have an entry fill quote inside the entry timeout and filled notional >= $1,000, are not fetch-incomplete, are not excluded by the in-hold split-record rule (populations.suspected_unadjusted_split) and have the tercile the key needs. Their exit is irrelevant, because terminal exits stay in the statistic (populations.terminal_exits). For H3-c, the count is the events with all 8 official legs present. Only presence and exclusion flags, decision-time inputs (MAX21, med20, the entry bar), corporate-action records, the cumulative split factors a(s) of populations.formulas (a ratio of two closes of one session, never a return) and entry-quote presence are evaluated. No mid, spread, return or exit value is computed or emitted. The same unit applies to every minimum_sample check at every stage, and the extension rule applies to the smallest key of each carried item. A synthetic test asserts that the count output carries exactly these keys with integer values, and that the count path calls no price or return function.", "why_prospective": "No retrospective window free of prior mover outcomes remains: 2021-2025 is v1/v2 dev/val, 2026-01-02 to 2026-09-18 is #162's reserved holdout and is exposed at event level, 2017-2026-08-14 is broad-universe exposed at aggregate level, and 2016 is the warm-up.", - "accrual_exposure": "The primary holdout statistic of every item uses every holdout trade; nothing is excluded for exposure. Paper exposure is recorded only for symbol-sessions in which a paper order was submitted, logged at order time (symbol, session, order id, strategy) in the collection batch's accrual log before any outcome of that trade exists. A holdout trade is paper-exposed if any session from decision t through its exit is such a symbol-session. The result without paper-exposed trades is reported only as a sensitivity. If the paper-exposed fraction of any carried item's holdout trades exceeds 5%, the holdout is labelled contaminated for that item, and the item cannot then be labelled supported (confirmatory).", - "holdout_read": "A holdout read is exactly this: computing, from data of sessions N0 onward, any v3-defined outcome (an exit price paired with a v3 entry, a forward, net or leg return of a D event, a terminal booking) or any statistic built from one. Only such a computation inside a granted 'read' entry is permitted. A 'count' entry permits count_unit only. The following are not reads: (1) collection under holdout_gate.prospective_collection, including computing a batch's sha256; (2) live paper decisions and orders of any strategy, including the NautilusTrader paper runs and their decision streams, which are exposure and are logged in the accrual log; (3) dashboards and timers that publish metadata only (counts, liveness, freshness); (4) computing D membership without any outcome, by committed code whose output is logged in the accrual log. A synthetic test classifies one example of each case." + "accrual_exposure": "The primary holdout statistic of every item uses every holdout trade; nothing is excluded for exposure. Paper exposure is recorded only for symbol-sessions in which a paper order was submitted, logged at order time (symbol, session, order id, strategy) in the collection batch's accrual log before any outcome of that trade exists. A holdout trade is paper-exposed if any session from decision t through its exit is such a symbol-session. A holdout trade whose span from decision t through exit includes a late-collected session (holdout_gate.prospective_collection) is also paper-exposed, because that session's accrual log was not committed in time. The result without paper-exposed trades is reported only as a sensitivity. If the paper-exposed fraction of any carried item's holdout trades exceeds 5%, the holdout is labelled contaminated for that item, and the item cannot then be labelled supported (confirmatory).", + "holdout_read": "A holdout read is exactly this: computing, from data of sessions N0 onward, any v3-defined outcome (an exit price paired with a v3 entry, a forward, net or leg return of a D event, a terminal booking) or any statistic built from one. Only such a computation inside a granted 'read' entry is permitted. A 'count' entry permits count_unit only. The following are not reads: (1) collection under holdout_gate.prospective_collection, including computing a batch's sha256; (2) live paper decisions and orders of any strategy, including the NautilusTrader paper runs and their decision streams, which are exposure and are logged in the accrual log; (3) dashboards and timers that publish metadata only (counts, liveness, freshness); (4) computing D membership without any outcome, by committed code whose output is logged in the accrual log. A synthetic test classifies one example of each case.", + "read_timing": "The holdout read is mandatory (review round 7, C6). The committed automation requests the 'read' for every carried item within 15 sessions after the last session of the final block (the terminal-search windows of censored trades end at most 5 sessions after it). No person decides it: the authorization is granted by rule unless an evaluator refusal applies, and then it is recorded 'refused' with that refusal. A read whose completion reports failed with no results file may be retried under a new authorization before the deadline, from the same study tree and, if one was sealed, the same input snapshot (run_discipline.once). A read that is not completed with a results file by the deadline, is refused, or is void, and a holdout voided for any reason after the gate opened, are numbered deviations, and every carried item is labelled 'not supported (holdout not read)' with the reason (outcome_reporting.rule). No person and no paper result can delay, skip or cancel the read." }, "labels": { "development": "disjoint from v1/v2; exposed at aggregate level through broad-universe development; screening only; survivorship-limited (names delisted in 2016-2019 without a retained corporate action cannot be discovered); descriptive only, so it gates nothing (multiple_testing.development_screen)", - "validation": "disjoint from v1/v2; exposed at aggregate level through broad-universe development and the S2 headline the author read; screening only, never confirmatory; the 2020 COVID regime is never dropped; not labelled survivorship-limited: the broad-universe survivorship measure (known_inactive_or_supplement_last_bar_year_histogram in blueprints/us-equities/broad-universe/receipt.json, from the same asset-master and corporate-action sources as universe_and_identity.enumeration) counts 563 names whose last bar falls in 2020, inside the 2021-2025 range of 512-756, against 371 in 2019 and 25-167 in 2016-2018. That is a measured count on entitled sources, not proof of completeness. Validation uses no development data: its tercile breakpoints come from validation sessions only (tercile_rule.breakpoints), and no development output is an input (multiple_testing.development_screen)", - "holdout": "prospective; untouched only if the validation results commit was reachable from origin/main before 09:30 ET on N0, no holdout read preceded a committed granted access-log entry, every holdout count and read ran from the governing validation run's study tree (run_discipline.study_code), every collection batch has its committed accrual log, and no carried item's paper-exposed fraction exceeds 5%" + "validation": "disjoint from v1/v2; exposed at aggregate level through broad-universe development and the S2 headline the author read; screening only, never confirmatory; the 2020 COVID regime is never dropped; not labelled survivorship-limited on the delisting measure: the broad-universe survivorship measure (known_inactive_or_supplement_last_bar_year_histogram in blueprints/us-equities/broad-universe/receipt.json, from the same asset-master and corporate-action sources as universe_and_identity.enumeration) counts 563 names whose last bar falls in 2020, inside the 2021-2025 range of 512-756, against 371 in 2019 and 25-167 in 2016-2018. That is a measured count on entitled sources, not proof of completeness, and it was measured under broad-universe's default-asof requests, so it does not cover what asof = s screening misses. The identity diagnostic decides that part (exposure_registry.pre_freeze_access_path.identity_diagnostic): if 2020's identity-unreached rate exceeds 2%, every validation result is labelled identity-limited, with the rate stated. Validation uses no development data: its tercile breakpoints come from validation sessions only (tercile_rule.breakpoints), and no development output is an input (multiple_testing.development_screen)", + "holdout": "prospective; untouched only if the validation results commit was reachable from origin/main before 09:30 ET on N0, no holdout read preceded a committed granted access-log entry, every holdout count and read ran from the frozen study_code tree or a passing transport deviation of it (run_discipline.study_code, run_discipline.transport_deviations), every collection batch has its committed accrual log, and no carried item's paper-exposed fraction exceeds 5%" }, - "boundaries": "Segments are assigned by entry session (for H3-c, by session t+1). A trade whose planned exit falls after its segment's last session is censored: it exits at the first eligible quote at or after 15:55 ET on that last session (populations.terminal_exits.segment_end), is flagged, stays in its segment's statistics and is reported as a count. H3-b makes no entry on a segment's last session (counted). An H3-c event whose legs cross the segment end is excluded from H3-c and counted. Embargo: multi-session items (H1-D, H1-D-b_lane-low, H3-b, H3-c) give up the first 6 sessions of validation, and the first 6 kept sessions after a dropped year, as entry sessions. This keeps the censored trades of the earlier segment clear of later entries. Same-session H3-a needs no embargo. There is no embargo at N0: the holdout follows years after validation, no v3 trade is open in the sessions before N0 (those sessions are lookback inputs only), and D decisions on N0 - 1 enter on N0. A synthetic test covers the embargo after development, the embargo after a dropped year, and a 5-session trade entered on N0 that is kept." + "boundaries": "Segments are assigned by entry session (for H3-c, by session t+1). A D decision whose entry session (for H3-c, session t+1) lies in no segment, such as a decision on 2020-12-31 (entry 2021-01-04) or on the last kept session before a dropped year, is dropped and counted per stage. A trade whose planned exit falls after its segment's last session is censored: it exits at the first eligible quote at or after 15:55 ET on that last session, searching past the segment end if needed (populations.terminal_exits.segment_end), is flagged, stays in its segment's statistics and is reported as a count. H3-b makes no entry on a segment's last session (counted). An H3-c event whose legs cross the segment end is excluded from H3-c and counted. Embargo: multi-session items (H1-D, H1-D-b_lane-low, H3-b, H3-c) give up the first 6 sessions of validation, and the first 6 kept sessions after a dropped year, as entry sessions. This keeps the censored trades of the earlier segment clear of later entries. Same-session H3-a needs no embargo. There is no embargo at N0: the holdout follows years after validation, no v3 trade is open in the sessions before N0 (those sessions are lookback inputs only), and D decisions on N0 - 1 enter on N0. A synthetic test covers the embargo after development, the embargo after a dropped year, and a 5-session trade entered on N0 that is kept." }, "outcome_reporting": { "labels": { - "passes_stage": "The item passes its stage rule. The pass has a stage-specific name: 'development pass' at development (descriptive only; it gates nothing, multiple_testing.development_screen), 'screened' at validation (Holm in the 5-item family, the minimum sample and, for a tradable cell, the robustness means), and 'supported (confirmatory)' at the holdout (the same rule). Only the holdout label is a claim.", + "passes_stage": "The item passes its stage rule. The pass has a stage-specific name: 'development pass' at development (descriptive only; it gates nothing, multiple_testing.development_screen), 'screened' at validation (Holm in the 5-item family, the minimum sample and, for a tradable cell, the robustness means), and 'supported (confirmatory)' at the holdout (the same rule, and for H3-c the validation sign of multiple_testing.no_direction_lock). Only the holdout label is a claim.", "not_supported_mde_excluded": "The stage has at least its minimum sample, the item does not pass, and the one-sided bootstrap-percentile confidence bound (from the same B draws, numpy 'linear' quantile) at Bonferroni level a = 0.05/5 = 0.01 excludes an effect of the preregistered MDE for the realized n (minimum_detectable_effect). For the alternative 'less than 0' (H1-D), the bound is the a-quantile of the bootstrap statistics, and the MDE is excluded when that bound is greater than -MDE. For the alternative 'greater than 0' (every tradable cell), the bound is the (1 - a)-quantile, and the MDE is excluded when it is less than +MDE. H3-c is two-sided at every stage: it uses a = 0.05/10 and requires both conditions, the lower bound (the a-quantile) greater than -MDE and the upper bound (the (1 - a)-quantile) less than +MDE. The fixed Bonferroni level is used because Holm's step-down level is not defined for items after the first retained hypothesis.", "underpowered": "The stage is below its minimum sample, or the stage is void (populations.fetch_failures), or the item neither passes nor excludes the MDE." }, - "hypothesis_verdict": "H1 is not supported if H1-D is labelled not_supported_mde_excluded and H1-D-b_lane-low does not pass. H3 is not supported if H3-c is labelled not_supported_mde_excluded and neither H3-a nor H3-b passes. A verdict is computed at validation and reported as a screening verdict. For a hypothesis with a carried item, it is also computed at the holdout and reported as the confirmatory verdict. Each uses the labels of its own stage only.", - "validation_reporting": "Every item receives its validation p and enters the validation Holm run, whatever its development result. Development is survivorship-limited, so it nominates nothing and never removes an item from validation or the holdout. An item scores p = 1 at validation only when it is not tested (coverage_rule) or its validation outcome was not written (run_discipline.deviations). The H1 high and middle b_lane cells are reported descriptively at every stage, with no p-value.", - "qualifiers": "Contamination, lineage and deviation status are qualifiers attached to a label, never labels of their own. A holdout item that meets the pass rule while its holdout is contaminated is labelled 'screened (contaminated holdout)', never 'supported (confirmatory)'. An item labelled 'supported (confirmatory)' that is not lineage-confirmed (multiple_testing.lineage_sensitivity) carries 'lineage-unconfirmed'. A stage decided by the one permitted post-deviation rerun (run_discipline.deviations) carries 'post-deviation'.", - "rule": "Only these three labels are used, with the qualifiers above. A result from a deviated study tree other than the permitted rerun is reported beside the governing result with no label at all. A miss is never reported as 'falsified' or as proof of no effect, and 'not supported' always states the size it excludes. A result in the direction opposite to a one-sided alternative is reported descriptively with its estimate and interval and supports no claim." + "hypothesis_verdict": "Each hypothesis gets exactly one verdict per stage, from the labels of that stage's items only, by the first rule that applies (review round 7, C12). (1) Pass: an item of the hypothesis passes the stage; the verdict takes that stage's pass name ('development pass', 'screened' or 'supported (confirmatory)') with the qualifiers of the passing items. (2) Not supported: H1 if H1-D is labelled not_supported_mde_excluded and H1-D-b_lane-low does not pass; H3 if H3-c is labelled not_supported_mde_excluded and neither H3-a nor H3-b passes; and at the holdout, 'not supported (holdout not read)' if every carried item of the hypothesis has that label. (3) Inconclusive: every other case, including a mix of underpowered and not-supported items and a pass labelled 'screened (contaminated holdout)'. A verdict is computed at development (descriptive), at validation (the screening verdict) and, for a hypothesis with a carried item, at the holdout (the confirmatory verdict). At the holdout, an item that was not carried counts as neither passing nor not_supported_mde_excluded.", + "validation_reporting": "Every item receives its validation p and enters the validation Holm run, whatever its development result. Development is survivorship-limited, so it nominates nothing and never removes an item from validation or the holdout. An item scores p = 1 at validation only when it is not tested (coverage_rule) or validation is void (populations.fetch_failures, run_discipline.deviations). The H1 high and middle b_lane cells are reported descriptively at every stage, with no p-value.", + "qualifiers": "Contamination, lineage and deviation status are qualifiers attached to a label, never labels of their own. A holdout item that meets the pass rule while its holdout is contaminated is labelled 'screened (contaminated holdout)', never 'supported (confirmatory)'. An item labelled 'supported (confirmatory)' that is not lineage-confirmed (multiple_testing.lineage_sensitivity) carries 'lineage-unconfirmed'. A stage whose data were fetched or collected under a transport deviation (run_discipline.transport_deviations) carries 'transport-deviation'.", + "rule": "Only these three labels are used, with the qualifiers above, plus one fixed label: a carried item whose holdout is not read by the deadline, is refused or is void is labelled 'not supported (holdout not read)' with the reason and no excluded size (chronology.holdout.read_timing). A result from a deviated study tree (other than a passing transport deviation) is reported beside the governing result with no label at all. A miss is never reported as 'falsified' or as proof of no effect, and 'not supported' otherwise always states the size it excludes. A result in the direction opposite to a one-sided alternative, or for H3-c at the holdout opposite to the validation sign, is reported descriptively with its estimate and interval and supports no claim." }, "multiple_testing": { "family": "One family of m = 5 items (item_set): H1-D, H1-D-b_lane-low, H3-a, H3-b, H3-c. H6 and the deferred H2, H4 and H5 are not in it.", @@ -256,8 +260,8 @@ "H3-c" ], "item_id_rule": "item_ids is the complete list, in bytewise ASCII order. That order is used for stream spawning, for the final tie-break and for the validation results file.", - "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout, over these 5 items only. An item not carried to a stage, or not tested for lack of data, scores p = 1 there. The validation results file contains all 5 validation p-values keyed by item_ids (1 only for items not tested or without a written outcome); the evaluator refuses an incomplete file.", - "no_direction_lock": "H3-c is two-sided at every stage, with the two-sided p of statistics.p_value in its Holm slot. The full draft and the first core draft locked H3-c after development to the sign of its development estimate. That lock is removed (review round 6): development is survivorship-limited, and a lock set by a survivorship-biased estimate could stop H3-c from ever validating or reaching the holdout. A significant H3-c result of either sign can pass. A synthetic test asserts that validation and holdout p-values do not change when every development output is altered.", + "procedure": "Holm step-down at family-wise alpha 0.05, applied separately at validation and at the holdout, over these 5 items only. An item not carried to a stage, or not tested for lack of data, scores p = 1 there. The validation results file contains all 5 validation p-values keyed by item_ids (1 only for items not tested); the evaluator refuses an incomplete file.", + "no_direction_lock": "H3-c's p is two-sided at every stage, with the two-sided p of statistics.p_value in its Holm slot, and no development output sets its direction. The full draft and the first core draft locked H3-c after development to the sign of its development estimate. That lock is removed (review round 6): development is survivorship-limited, and a lock set by a survivorship-biased estimate could stop H3-c from ever validating or reaching the holdout. At the holdout, a confirmatory pass of H3-c also needs the holdout estimate to have the sign of the governing validation estimate (review round 7, C7). Validation is the governing screen and is not survivorship-limited on the delisting measure, and H3-c is carried only if it validated, so that estimate is nonzero. A significant holdout estimate of the other sign is reported descriptively with its estimate and interval and supports no claim. A synthetic test asserts that validation and holdout p-values do not change when every development output is altered, and that a significant holdout H3-c opposite to the validation sign does not pass.", "data_status_at_freeze": "At the freeze, H1 and H3 are each recorded as either 'data in hand, tested' or 'not tested, p = 1 at every stage, permanently', by coverage_rule. Data from a new source, or a new historical range acquired after the freeze, can support only a new protocol version with its own family (v3.1+), never this family's Holm run. The prospective Alpaca SIP data collected under holdout_gate.prospective_collection are not new data in this sense.", "development_screen": "Unadjusted p <= 0.05 with the minimum sample: one-sided in the stated direction for H1-D, H1-D-b_lane-low, H3-a and H3-b, and two-sided for H3-c. Development is descriptive: its pass ('development pass') is reported for every item and gates nothing. Every item goes to validation (outcome_reporting.validation_reporting). No development output (an estimate, a sign, a pass or an event's tercile breakpoint) is an input to validation, the holdout gate or any holdout statistic, because 2017-2019 is survivorship-limited (chronology.labels.development).", "lineage_sensitivity": "The mover question has already tried 1,012 rule-exits (v1 768, v2 244) and broad-universe-mover-v1's 60 predeclared signal variants on overlapping or other windows. Every validation and holdout p is also reported against Bonferroni 0.05 / (1012 + 60 + 5) = 4.64e-5. The archived 35-item draft computed nothing and is not counted. broad-universe's controls and descriptive lane have no pass rule and are not counted, which is a stated limitation. Each p is reported beside the normal-tail p of statistics.normal_tail_p. With B = 100,000, a bootstrap p at 4.64e-5 means at most 3 null-side draws, so the bootstrap p alone cannot resolve this level. An item is lineage-confirmed only if both its bootstrap p and its normal-tail p are at or below 4.64e-5.", @@ -402,6 +406,7 @@ "validation restricted to symbol-sessions that were not a broad-universe lane decision (exposure_registry.consequence)", "table-only cost, and the stress cost", "every terminal-zero trade rebooked at the last eligible bid of its hold (populations.terminal_exits)", + "trades whose hold spans a session flagged by the ratio rule of populations.suspected_unadjusted_split removed", "censored segment-end trades removed (chronology.boundaries)", "survivorship-limited label before 2020", "holdout without paper-exposed trades (chronology.holdout.accrual_exposure)" @@ -410,18 +415,18 @@ "holdout_gate": { "opens_only_for": "Items that passed validation: Holm-adjusted validation p <= 0.05 in the 5-item family, the minimum validation sample (for H1-D, in each group) and, for a tradable cell (H1-D-b_lane-low, H3-a, H3-b), the three robustness statistics. There is no cap: every validated item is carried. A synthetic gate test carries three validated tradable cells including H3-a, and refuses one that fails a robustness statistic.", "no_pass": "If nothing validates, the holdout is never labelled or read, and that is the result.", - "prospective_collection": "The frozen collector (in the study tree) collects forward from the session of the freeze. Each batch holds screen data only, for the whole enumerated list (universe_and_identity.enumeration, refreshed per batch): the daily bars (raw, split and all) and official prints of sessions s-1 and s for each collected session s, with asof = s, plus corporate-action records. Batches are sealed into a private snapshot whose sha256 is committed per batch with its accrual log. No quote and no minute bar is collected, and nothing selects symbols by membership. Everything else a holdout event needs (lookbacks, minute bars, entry, exit and terminal-search quotes, hold-period prints) is fetched only under a granted 'count' or 'read' entry, by the frozen fetch code with the event's asof = t (universe_and_identity.asof), and sealed before use. Sessions from the freeze to N0 - 1 serve only as lookback inputs. No outcome is computed from any batch before a granted 'read' entry.", + "prospective_collection": "The frozen collector (in the study tree) collects forward from the session of the freeze. Each batch holds screen data only, for the whole enumerated list (universe_and_identity.enumeration, refreshed per batch): the daily bars (raw, split and all) and official prints of sessions s-1 and s for each collected session s, with asof = s, plus corporate-action records. Batches are sealed into a private snapshot whose sha256 is committed per batch with its accrual log. No quote and no minute bar is collected, and nothing selects symbols by membership. Everything else a holdout event needs (lookbacks, minute bars, entry, exit and terminal-search quotes, hold-period prints) is fetched only under a granted 'count' or 'read' entry, by the frozen fetch code with the event's asof = t (universe_and_identity.asof), and sealed before use. Sessions from the freeze to N0 - 1 serve only as lookback inputs. No outcome is computed from any batch before a granted 'read' entry. A batch is timely if its accrual log is reachable from origin/main before 09:30 ET of the session after its last session. A session with no timely batch is collected by the next batch, flagged late-collected and counted; it stays in the holdout (collection is not a read), and chronology.holdout.accrual_exposure treats trades that span it as paper-exposed. Because the asof mapping of a renamed symbol may become available only after the rename day, each batch also re-fetches once, logged, the rows of sessions s-1 and s under the new symbol with asof = s for every name_change record effective on a session s of the previous batch, and those rows join session s's screen (counted per batch; review round 7, F9).", "evaluator_refusals": [ "the protocol status is not frozen, or its sha256 differs from the frozen commit", "the validation results file is missing, its sha256 differs from the committed one, or its commit did not become reachable from origin/main before 09:30 ET on N0", - "the validation results file is not from the governing validation run: its run-log entry is missing, or names a study tree other than the frozen study_code tree (the one post-deviation rerun that run_discipline.deviations lets govern excepted)", - "a holdout 'count' or 'read' runs from a study tree other than the tree of the governing validation run (which is the frozen study_code tree), except the single post-deviation rerun that run_discipline.deviations permits", + "the validation results file is not from the governing validation run: its run-log entry is missing, or names a study tree other than the frozen study_code tree", + "a holdout 'count' or 'read' runs from a study tree other than the frozen study_code tree, except a tree that differs from it only under study/fetch/ by transport deviations that passed their reproduction check (run_discipline.transport_deviations)", "the Python, numpy, pandas or exchange_calendars version differs from study/runtime.lock", "data/session-calendar.json or data/fees-v3.json differs from its frozen sha256, or an amendment file is not a byte-prefix extension of the content logged at the previous run, or an amendment line concerns a date before the freeze session", "the list of validated items is empty, or a requested item is not in it", "the access log has no committed granted authorization record of the matching purpose for this run, or an earlier granted authorization has no committed completion record", - "any collection batch lacks its committed accrual log, or a batch's log became reachable from origin/main at or after 09:30 ET of the session after the batch's last session", - "a previous granted 'read' authorization exists, with or without a completion (the holdout is read once; failed or partial reads stay in the log as completions). A 'count' is not a read", + "any collection batch lacks its committed accrual log (a late log is not refused: its sessions are late-collected, holdout_gate.prospective_collection)", + "a previous granted 'read' authorization exists, unless its completion reports status failed with results_sha256 null and the new authorization is a retry under chronology.holdout.read_timing from the same tree and, if one was sealed, the same input snapshot. A read that wrote a results file is final, so the holdout is read once. A 'count' is not a read", "a 'count' request asks for anything beyond chronology.holdout.count_unit, or comes other than once at the end of the 252 sessions and once at the end of each extension block" ], "access_log": { @@ -432,17 +437,19 @@ "utc", "record_kind ('authorization')", "authorization_id", - "actor_role", + "actor_role (the committed automation)", "protocol_id", "protocol_sha256", - "code_revision", + "code_revision (the commit run at, provenance only)", "study_code_tree", "runtime_lock_sha256", "data_file_sha256s", "validation_results_sha256", "requested_items", "purpose", - "decision (granted or refused)" + "decision (granted by rule, or refused)", + "refusal (the evaluator refusal, when refused)", + "retry_of (the failed read's authorization_id, or null)" ], "completion": [ "utc", @@ -459,44 +466,54 @@ "collect": "a collection batch; its completion has rows_read 0 and lists that batch's accrual exposure", "amend": "one calendar or fee amendment line, with its primary source", "count": "at the end of the 252 sessions and of each extension block: count_unit per carried item, used only for the extension decision; it may fetch the entry quotes, lookbacks, minute bars and prints the count needs, under run_discipline.fetch", - "read": "the single outcome read for carried items" + "read": "the single mandatory outcome read for carried items (chronology.holdout.read_timing)" }, - "rule": "Any holdout read without a preceding committed granted entry of the matching purpose voids the holdout for every item. Refusals are logged too, as authorization records with decision 'refused' and no completion. A granted authorization without its completion blocks every later authorization, and a granted 'read' authorization counts as the one holdout read whether or not its completion reports complete. The synthetic suite tests the 'count' path, including a test that it computes no exit price, and the log for a complete, a failed and a partial read, each an authorization followed by its completion." + "rule": "Any holdout read without a preceding committed granted entry of the matching purpose voids the holdout for every item. Refusals are logged too, as authorization records with decision 'refused' and no completion. A granted authorization without its completion blocks every later authorization, and a granted 'read' authorization counts as the one holdout read unless its completion reports failed with no results file, in which case only a retry under chronology.holdout.read_timing may follow. The synthetic suite tests the 'count' path, including a test that it computes no exit price, and the log for a complete, a failed and a partial read, each an authorization followed by its completion." } }, "run_discipline": { - "study_code": "The frozen protocol records study_code = {revision: the git commit, tree: the git tree hash of blueprints/us-equities/mover-v3/study/ at that commit}. The tree holds the study code, the count-only code, the fetch code, the collector, the synthetic tests and study/runtime.lock. runtime.lock pins the Python, numpy and pandas versions and exchange_calendars 4.13.2. The tree holds no data file (data_files). Every run, fetch and evaluator call names the revision, tree and runtime it ran from. Development, validation and every holdout 'count' and 'read' run from this one tree. The holdout count and read must name the same tree as the governing validation run, and the evaluator refuses any other tree (holdout_gate.evaluator_refusals). The only exception is the single rerun that run_discipline.deviations permits.", + "study_code": "The frozen protocol records study_code = {tree: the git tree hash of blueprints/us-equities/mover-v3/study/, fetch_tree: the git tree hash of blueprints/us-equities/mover-v3/study/fetch/}. It pins trees only, never a commit: the repository squash-merges, so a PR-branch commit becomes unreachable, and every run appends run-log, access-log and amendment lines outside the tree, which moves HEAD (review round 7, C5). The study tree is merged to main before the freeze pull request, which does not change it. Every run checks out a commit reachable from origin/main whose blueprints/us-equities/mover-v3/study tree equals the pinned tree (or a passing transport deviation of it), executes that tree, and reads the logs and data files of that same commit; the commit is recorded as provenance only. The tree holds the study code, the count-only code, the fetch code and the collector (under study/fetch/), the pinned copies (pinned_copies), the synthetic tests and study/runtime.lock. runtime.lock pins the Python, numpy and pandas versions and exchange_calendars 4.13.2. The tree holds no data file (data_files). Every run, fetch and evaluator call names the tree and runtime it ran from. Development, validation and every holdout 'count' and 'read' run from this one tree, and the evaluator refuses any other (holdout_gate.evaluator_refusals). The only tree changes that keep a stage are transport deviations (transport_deviations).", "data_files": "Data the study reads but may need to append to after the freeze live outside the study tree, in blueprints/us-equities/mover-v3/data/: session-calendar.json and fees-v3.json (the base files, pinned by sha256 in the frozen protocol), and session-calendar-amendments.jsonl and fees-v3-amendments.jsonl (append-only, possibly empty at the freeze). Appending a line never changes the study tree. The run log records each amendment file's sha256 and byte length at every run, and the evaluator refuses a file whose earlier logged content is not a byte prefix of its current content.", - "run_log": "blueprints/us-equities/mover-v3/run-log.jsonl, append-only JSON lines. Every run of the frozen automation gets one line: each stage's fetch and re-fetch, development, validation, and each holdout 'count' and 'read', including failed and partial runs. Each line records the utc start and end, stage, purpose, study_code revision and tree, runtime lock sha256, data-file sha256s, input snapshot sha256s, status (complete, failed or partial) and the sha256 of every results file written (partial files included). For the validation results commit, it also records the GitHub-recorded time it became reachable from origin/main. Each line is committed and pushed before the next run of any stage starts.", - "once": "Development is computed by one logged run and validation by one logged run. The first run of a stage that writes any outcome is that stage's governing run, whether or not it completes. The governing development run's results are descriptive (multiple_testing.development_screen), and the governing validation run's results file governs the holdout gate.", - "deviations": "Any change to the study tree after the freeze is a numbered deviation in blueprints/us-equities/mover-v3/deviations.json (cause, diff reference, new tree hash, affected stages), committed and pushed before any run from the new tree. A run from a deviated tree is reported beside the governing result, carries no label, and never governs a stage, the holdout gate or the extension decision. One exception: if a governing run from the frozen tree is incomplete because of an exception or an infrastructure failure, one rerun may govern, labelled 'post-deviation'. It must run under a deviation that changes no rule, and it must reproduce byte for byte every outcome the incomplete run had written. Otherwise: an incomplete development run decides development by its written outcomes alone (unwritten items score p = 1); an incomplete validation run voids the holdout of this protocol version; and an incomplete holdout read labels every carried item without a written outcome underpowered, with the reason stated.", - "fetch": "Before any outcome of a stage (development, validation, and each holdout 'count' or 'read'), the frozen fetch code fetches every quote window, minute bar and official print that stage needs, under universe_and_identity.asof. It derives stamps only from decision-time inputs and quote timestamps, including the terminal-search windows. It seals the result as a sha256-pinned private snapshot, and commits that sha256 and the stage's fetch-incomplete rate in the run log. At most one re-fetch is allowed: once, of only the fetch-incomplete stamps, before any outcome of that stage, committed the same way. After it, the 1% void rule is final. No outcome is computed from unsealed data.", - "synthetic_tests": "The evaluator refuses a results file from an unpinned revision or tree, a holdout count or read from a tree other than the governing validation run's, a runtime that differs from runtime.lock, an edited (not appended) amendment file, a stage with a second outcome-bearing run that no deviation covers, and an outcome computed before the stage's fetch rate is committed." + "run_log": "blueprints/us-equities/mover-v3/run-log.jsonl, append-only JSON lines. Every run of the frozen automation gets one line: each stage's fetch and re-fetch, development, validation, and each holdout 'count' and 'read', including failed and partial runs. Each line records the utc start and end, stage, purpose, commit run at (provenance) and study tree, runtime lock sha256, data-file sha256s, input snapshot sha256s, status (complete, failed or partial) and the sha256 of the results file, if one was written (at most one per run, written atomically; run_discipline.once). For the validation results commit, it also records the GitHub-recorded time it became reachable from origin/main. Each line is committed and pushed before the next run of any stage starts.", + "once": "Development is computed by one logged run and validation by one logged run. A run holds every outcome in memory and writes the stage's results as one results file, atomically (temporary file, fsync, rename), only after every item of the stage is computed; no outcome is written before (review round 7, C1). A run that ends before that write has written no outcome and is not governing. It is logged as failed, and the stage may run again from the unchanged frozen tree on the same sealed inputs, each attempt logged; a retry cannot differ from an uninterrupted run, because the computation is deterministic (seeded streams, pinned runtime, sealed inputs). The first run of a stage that writes its results file is the stage's governing run. The governing development run's results are descriptive (multiple_testing.development_screen), and the governing validation run's results file governs the holdout gate. Each holdout 'count' and 'read' writes its results the same way.", + "deviations": "Any change to the study tree after the freeze is a numbered deviation in blueprints/us-equities/mover-v3/deviations.json (cause, diff reference, new tree hash, affected stages), committed and pushed before any run from the new tree. A run from a deviated tree is reported beside the governing result, carries no label, and never governs a stage, the holdout gate or the extension decision. There is no exception for a rerun after a code change, whatever the cause and whatever it reproduces (review round 7, C1 and C2): a stage that cannot complete from the unchanged frozen tree is void. A void development stage reports every item as underpowered there (development gates nothing). A void validation stage voids the holdout of this protocol version (every item scores p = 1 at validation). A void holdout 'count' or 'read' labels every carried item 'not supported (holdout not read)' with the reason (chronology.holdout.read_timing). Transport deviations (transport_deviations) are the only tree changes that keep a stage, and they change no evaluation code.", + "fetch": "Before any outcome of a stage (development, validation, and each holdout 'count' or 'read'), the frozen fetch code fetches every quote window, minute bar, per-event daily bar and official print that stage needs, and for development and validation first the full 2016-2020 screen data (universe_and_identity.request_shapes), under universe_and_identity.asof. Terminal-search windows of censored trades extend past the segment end (populations.terminal_exits.segment_end). It derives stamps only from decision-time inputs and quote timestamps, including the terminal-search windows. It seals the result as a sha256-pinned private snapshot, and commits that sha256 and the stage's fetch-incomplete rate in the run log. At most one re-fetch is allowed: once, of only the fetch-incomplete stamps, before any outcome of that stage, committed the same way. After it, the 1% void rule is final. No outcome is computed from unsealed data.", + "synthetic_tests": "The evaluator refuses a results file whose run-log entry names a tree other than the pinned one, a holdout count or read from a tree other than the pinned one or a passing transport deviation of it, a transport deviation without a passing reproduction check, a runtime that differs from runtime.lock, an edited (not appended) amendment file, a second results file for a stage, a read retry that chronology.holdout.read_timing does not permit, and an outcome computed before the stage's fetch rate is committed. It accepts a run from a commit reachable from origin/main whose study tree equals the pinned tree, with run-log, access-log and amendment lines committed after the freeze. A test kills a run before its results write and asserts that nothing was written and that the retry reproduces an uninterrupted run byte for byte.", + "pinned_copies": "The study tree imports no module outside itself except the Python standard library, numpy, pandas and exchange_calendars (review round 7, F6). It holds byte-for-byte copies of the external definitions it needs, listed in study/pinned_copies.json as (source path, commit aa6fc79, git blob, definition names): the Bars class from mover-early-entry rules.py; official_price from sessions_io.py; valid_quote from quotes.py; dedupe_identity from broad-universe coverage.py; DATA_SYMBOL from collect_daily.py; each with the module-level definitions it references. A synthetic test reads each source with git cat-file at the listed blob, checks the blob hash, extracts each named definition with ast.get_source_segment and asserts that the copy is identical. Because nothing outside the tree is imported, a later edit to those paths on main cannot reach a run, and no module-level code of rules.py or quotes.py (their fees.json and protocol.json loads) ever runs.", + "transport_deviations": "The frozen fetch code and collector must work against a live API for 12-18 months (review round 7, C2). A deviation that changes only files under study/fetch/ and only transport (host, endpoint path, authentication header, pagination and retry transport, response parsing into the same record format) is a transport deviation. Before any run from the new tree, it passes a committed reproduction check: the new fetch code re-requests the first 200 requests, in sha256 order of their request records, of the most recently sealed snapshot of each request kind that exists (screen and per-event), and its normalized page records, every field except the fetch date and vintage, must equal the sealed records byte for byte. The result is committed in deviations.json with the deviation. A passing transport deviation may govern every later fetch, collection batch, count and read, whose results carry the qualifier 'transport-deviation'. A deviation that touches any file outside study/fetch/, or fails the check, is an ordinary deviation (deviations). Every evaluation module lives outside study/fetch/, so a transport deviation cannot change a rule. If no passing transport deviation restores collection or fetching in time, the affected sessions are late-collected (holdout_gate.prospective_collection) or the read misses its deadline, and chronology.holdout.read_timing applies." }, "leverage_and_exits": "Research runs use 1x equal notional. Paper use of any passing rule follows the README's exit and leverage policy (at most one broker-held closing order per share, the leverage-schedule-v1-20260922 ceiling, and the leverage-ladder gate rows in catalogs/us-equities/gates-20260922.json, one rung at a time). H3-b stays at 1x in any later paper use until the 2x rung and its overnight cap are clean. The edge-shrink term stays 0 until a holdout pass.", "freeze_preconditions": [ "An independent pre-outcome review of this core draft from a different session and model family, with every finding resolved in a dated review_record entry", "Direct full-text reads of Lou-Polk-Skouras 2019 and Akbas-Boehmer-Jiang-Koch 2022, or removal of every claim that depends on them (the two JFE DOIs resolve in Crossref, which is metadata only). H3-c is two-sided at every stage, so no test depends on their sign.", "data/session-calendar.json generated from exchange_calendars 4.13.2 XNYS and data/fees-v3.json with sourced 2017-2020 (through the freeze) SEC Section 31 and FINRA TAF rows, both committed and pinned by sha256, plus both amendment files committed (empty is allowed)", - "The enumerated symbol list (universe_and_identity.enumeration) and pre-freeze snapshot parts 0-2, fetched under universe_and_identity.asof, sealed and pinned by sha256, with each page's asof and fetch date recorded", - "Count-only coverage checks through exposure_registry.pre_freeze_access_path only, with no outcome computed: accepted official-print rates per label, eligible-quote rates at the coverage stamps, corporate-action record counts, suspected unadjusted splits, enumerated symbols per source and empty screen responses, all for 2016-2020, with coverage_rule applied and H1 and H3 recorded as tested or not tested (multiple_testing.data_status_at_freeze)", - "The Alpaca market-data plan's documented historical-data rate limit recorded with its source, and an upper-bound fetch-time estimate for development and validation (event candidates from the count-only outputs x the per-event requests, at that limit). The estimate must leave a margin of at least 50% under the 40 sessions before N0. If it does not, the protocol is not frozen as written.", - "The study tree committed with runtime.lock and synthetic tests that pass before any outcome. The tests cover: every populations.formulas case, each official-print source label, the exclusion rules, a suspected unadjusted split, the fill-quote cases, the terminal-exit cases, the universe_and_identity cases (FB/META, ticker reuse, rename inside a hold, empty responses, fetch-date asof refused), split, ex-dividend and 5% special-dividend crossings, the 2016-2020 early closes, a calendar and a fee amendment, a 2017 fee lookup, the fetch-failure cases, a dropped middle year, segment-end censoring and the embargo (including none at N0), the independence of validation and the holdout from every development output (H3-c two-sided with no direction lock, validation breakpoints from validation sessions only), the robustness statistics, the gate test, the count_unit keys, the holdout-read classification, the run_discipline refusals and the evaluator's holdout refusals, and the access log's authorization and completion records. The code imports none of rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return, rules.net_return_np and quotes.fetch_one, and a test asserts this.", + "The enumerated symbol list (universe_and_identity.enumeration) and pre-freeze snapshot parts 0-3 (part 1 a hashed sample of 2016-2020 screen data, never the full screen), fetched under universe_and_identity.asof, sealed and pinned by sha256, with each page's asof and fetch date recorded", + "Count-only coverage checks through exposure_registry.pre_freeze_access_path only, with no outcome computed: accepted official-print rates per label, eligible-quote rates at the coverage stamps, corporate-action record counts, suspected unadjusted splits, the sampled candidate count, enumerated symbols per source, empty screen responses and the identity-unreached rate, all for 2016-2020, with coverage_rule applied by the count-only code itself (coverage_rule.decided_by_code, its sha256 unchanged since that run) and H1 and H3 recorded as tested or not tested (multiple_testing.data_status_at_freeze)", + "The Alpaca market-data plan's documented historical-data rate limit recorded with its source, and an upper-bound fetch-time estimate for development and validation at that limit: the full 2016-2020 screen fetch (sessions x requests per session for the enumerated list) plus the per-event requests for at most 20 x (k + 3 sqrt(k) + 3) candidates per year, where k is that year's sampled candidate count (pre_freeze_access_path.outputs). This candidate bound governs the margin test. The estimate must leave a margin of at least 50% under the 40 sessions before N0. If it does not, the protocol is not frozen as written.", + "The identity probe (exposure_registry.pre_freeze_access_path.identity_probe) run by committed count-only code, with at least 20 rename probes, at least 95% matches for bars and for auctions and at least 90% for quotes, and the ticker-reuse result recorded (verified, or unverified for 2016-2020 if no case exists)", + "The study tree committed with runtime.lock and synthetic tests that pass before any outcome. The tests cover: every populations.formulas case, each official-print source label, the exclusion rules, a suspected unadjusted split, the fill-quote cases, the terminal-exit cases, the universe_and_identity cases (FB/META, ticker reuse, rename inside a hold, empty responses, fetch-date asof refused), split, ex-dividend and 5% special-dividend crossings, the 2016-2020 early closes, a calendar and a fee amendment, a 2017 fee lookup, the fetch-failure cases, a dropped middle year, segment-end censoring and the embargo (including none at N0), the independence of validation and the holdout from every development output (H3-c two-sided with no direction lock, validation breakpoints from validation sessions only), the robustness statistics, the gate test, the count_unit keys, the holdout-read classification, the run_discipline refusals and the evaluator's holdout refusals, and the access log's authorization and completion records, the net-return formula and D7 cash clamp, the in-hold split-record rule, a split on a session with no bar, censored exits searched past a segment end, the pinned copies against their blobs, the atomic results write and the read retry, and the transport-deviation reproduction check. The code imports no module outside the study tree except the Python standard library, numpy, pandas and exchange_calendars, so none of rules.close_hhmm, rules.fee_rates, rules.sell_fees, rules.net_return, rules.net_return_np and quotes.fetch_one can be called, and a test asserts both.", + "The frozen collector committed in the study tree, with its schedule committed and enabled so that the first collection batch covers the session of the freeze (holdout_gate.prospective_collection)", "The exposure registry updated with everything the author saw since this draft", - "status set to frozen, frozen_before_outcomes true, run_discipline.study_code filled in, and the protocol sha256 recorded in a commit that becomes reachable from origin/main (chronology.holdout.window defines the freeze commit)" + "status set to frozen, frozen_before_outcomes true, run_discipline.study_code (tree hashes only) filled in, and the protocol sha256 recorded in a commit that becomes reachable from origin/main (chronology.holdout.window defines the freeze commit); the study tree was merged to main before the freeze pull request, which does not change it" ], "review_record": [ { "date": "2026-09-24", "round": 5, "input": "The remaining findings of the round-4 rereview of the full draft (S1-S11 and X1-X10), after three fix rounds (75 fixes) in which high-severity findings went 4, 3, 4", - "resolution": "The draft was restructured into this core: H1 on population D (one difference test, one tradable cell) and H3 (two tradable arms and the leg decomposition), m = 5. Resolved in the core: S1 (run_discipline.study_code, data_files and deviations, holdout_gate.evaluator_refusals: the holdout count and read run only from the governing validation run's tree, and a deviated tree's result carries no label); S2 (populations.terminal_exits); S3 and X1 (universe_and_identity: per-request asof = the decision session with the as-known symbol, the enumeration source, identity dedup and empty-response counts); S4 (count_unit keys per H1-D group); S5 (no cap; the tradable cells are named); S6 (statistics.robustness_required_for_a_pass); S7 (chronology.boundaries: no embargo at N0); S8 (multiple_testing.direction_lock for a missing or zero estimate); S9 (run_discipline.data_files and the amendment files); S10 (statistics.normal_tail_p); S11 (the pit-availability registry entry); X5 (populations.fetch_failures: v3-owned fetch code); X9 (study/runtime.lock); X10 (exposure_registry.update_rule reworded). No longer applicable: X8 (population I is not in the core; for D the opening print precedes the decision). Deferred with their items (README appendix): X7 (H5), and X2, X3, X4 and X6 (H6). No finding rejected. Rounds 2-4 are recorded in the archived draft's review_record." + "resolution": "The draft was restructured into this core: H1 on population D (one difference test, one tradable cell) and H3 (two tradable arms and the leg decomposition), m = 5. Resolved in the core: S1 (run_discipline.study_code, data_files and deviations, holdout_gate.evaluator_refusals: the holdout count and read run only from the governing validation run's tree, and a deviated tree's result carries no label); S2 (populations.terminal_exits); S3 and X1 (universe_and_identity: per-request asof = the decision session with the as-known symbol, the enumeration source, identity dedup and empty-response counts); S4 (count_unit keys per H1-D group); S5 (no cap; the tradable cells are named); S6 (statistics.robustness_required_for_a_pass); S7 (chronology.boundaries: no embargo at N0); S8 (then multiple_testing.direction_lock for a missing or zero estimate; the lock was removed in round 6 and the section is now multiple_testing.no_direction_lock); S9 (run_discipline.data_files and the amendment files); S10 (statistics.normal_tail_p); S11 (the pit-availability registry entry); X5 (populations.fetch_failures: v3-owned fetch code); X9 (study/runtime.lock); X10 (exposure_registry.update_rule reworded). No longer applicable: X8 (population I is not in the core; for D the opening print precedes the decision). Deferred with their items (README appendix): X7 (H5), and X2, X3, X4 and X6 (H6). No finding rejected. Rounds 2-4 are recorded in the archived draft's review_record." }, { "date": "2026-09-24", "round": 6, "input": "The chatgpt-codex-connector review of PR #167 (nine threads, raised against the full draft at 0b924e7)", "resolution": "Applied to the core: (1) the survivorship-limited development stage no longer gates anything. Every item enters validation (outcome_reporting.validation_reporting), H3-c is two-sided at every stage with no development direction lock (multiple_testing.no_direction_lock), validation breakpoints use validation sessions only (tercile_rule.breakpoints), and the validation label records the broad-universe 2020 survivorship count (chronology.labels.validation); (2) the holdout access log separates a pre-action authorization record from a post-action completion record (holdout_gate.access_log). Already resolved in the core: the paired H3-c MDE form (minimum_detectable_effect.sigma_by_item), stage-own verdicts (outcome_reporting.hypothesis_verdict) and the entry bar complete before the fill decision (populations.formulas). Deferred with their items (README appendix): H2's Item 1.01 corpus gate, and the three H6 findings (stage membership by closing session, a common frozen trigger policy, REST-call attribution for the now-descriptive H6-c). No finding rejected." + }, + { + "date": "2026-09-24", + "round": 7, + "input": "A pre-outcome review of the core draft at 89ff41d: 14 findings C1-C14 and 10 findings F1-F10 (2 high, 12 medium, 10 low)", + "resolution": "All 24 verified against this file, the branch README and the pinned sources at aa6fc79, and fixed in the core. C1 (high): run_discipline.once writes each stage's results atomically after every item is computed, and a retry runs only from the unchanged frozen tree; run_discipline.deviations removes the post-deviation rerun, so a code change voids the stage. C2: the rerun exception is gone from evaluator_refusals and study_code, and run_discipline.transport_deviations adds a fetch-only deviation class with a byte-for-byte reproduction check. C3: populations.suspected_unadjusted_split applies the ratio rule only at t and in MAX21 windows; in a hold only a retained split record that the adjusted bars do not carry excludes a trade (also in count_unit), and the ratio rule is a sensitivity. C4: pre_freeze_access_path fetches a hashed 1-in-20 sample of 2016-2020 screen data; the full screen is fetched after the freeze (request_shapes, run_discipline.fetch). C5: study_code pins tree hashes only, the study tree is merged before the freeze PR and runs read HEAD's logs; chronology.holdout.window names the frozen file path. C6: chronology.holdout.read_timing makes the read mandatory and automated, and an unread holdout labels carried items 'not supported (holdout not read)'. C7: a holdout pass of H3-c needs the validation sign (multiple_testing.no_direction_lock). C8 and F3: a sampled candidate count is a count-only output, and freeze_preconditions bounds the candidates by 20 x (k + 3 sqrt(k) + 3). C9: coverage_rule.year_rule defines both rates (fetch-incomplete stamps count against coverage), and coverage_rule.decided_by_code has the count-only code decide, pinned by sha256. C10: populations.corporate_actions states the net-return formula and D7 with its clamp and close anchors, including the overnight legs. C11: terminal_exits.segment_end searches past the segment end. C12: outcome_reporting.hypothesis_verdict is exhaustive. C13: coverage_rule.item_rule depends on 2020 only. C14: the round-5 cross-reference is annotated, the '/' clause is credited to v3, rounds 6 and 7 are in draft_author_exposure, and chronology.boundaries drops decisions whose entry lies in no segment. F1 (high): request_shapes fetches per-event daily bars (raw, split and all) through the search window with asof = t, and the share factor and cash use only those responses. F2: populations.formulas defines F(u, v) = a(u) / a(v) over sessions with bars and makes a missing input undefined instead of 1. F4: universe_and_identity.enumeration states the rename limit, and pre_freeze_access_path.identity_diagnostic feeds coverage_rule.year_rule and chronology.labels.validation. F5: pre_freeze_access_path.identity_probe with a freeze precondition. F6: run_discipline.pinned_copies copies the pinned definitions into the study tree with a blob test, and valid_quote is a one-quote predicate. F7: why_core states that 2016-2020 quote and auction coverage is unverified. F8: terminal-merger booking needs the held symbol as acquiree of a named merger type, with unrecorded terminal-zero counts. F9: holdout_gate.prospective_collection adds late-collected sessions, a rename-day re-fetch and a collector precondition. F10: update_rule and not_allowed forbid only planned entry and exit stamps. Alternatives not taken: C3's option of excluding every trade that spans a recorded split (it would drop trades that F already books correctly); F2's option of excluding any hold with a missing bar (F telescopes across it); F4's option of a third, default-asof enumeration source (it would mix request asof values in one screen). No finding rejected." } ], "overturn": "Any headline result is overturned if a rerun on the same frozen inputs, study tree and runtime.lock does not reproduce it byte for byte, if an independent quote sample puts the cost table off by more than 2x, or if the access log shows a holdout read that no committed granted entry preceded." diff --git a/manifests/evidence.json b/manifests/evidence.json index ea54c7904..748b1ce33 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -8448,8 +8448,8 @@ }, { "path": "blueprints/us-equities/mover-v3/README.md", - "sha256": "8a736ba481187b93132e47a088dbcf7c8c2d40ed3de23b61a247a96d1bf54c58", - "bytes": 49529 + "sha256": "5728609b55cf20969342f7bccbc5dd14bf41af0dbcb6a540eaba9d400891b6d2", + "bytes": 51853 }, { "path": "blueprints/us-equities/mover-v3/h6-execution-parity-draft.json", @@ -8458,8 +8458,8 @@ }, { "path": "blueprints/us-equities/mover-v3/protocol-core-draft.json", - "sha256": "14c576fc6c5594bd807f09f79ce7ae6dfddeae4f177491039d88c2a1fd255ef7", - "bytes": 85121 + "sha256": "7274c29ed1f902cbf9605e049bfafd1856f2fe7d76d5fbb6962c4ba38a757af4", + "bytes": 113618 }, { "path": "blueprints/us-equities/mover-v3/protocol-draft.json",