# Opening Volatility Stretch: audit of the 2026-10-17 claims (DAX, NQ, Bund) and of the HDAM miner

**Tested by:** Claude (2026-10-17) · report-only, 0 ledger · script [`_audit_crabel_stretch_20261017.py`](_audit_crabel_stretch_20261017.py) · output [`_audit_crabel_stretch_20261017.out`](_audit_crabel_stretch_20261017.out) (runs in ~1 minute)
**Audited:** `alpha_factory/hypothesis_alpha_miner.py`, `alpha_factory/run_priority_hypotheses.py`, `_test_m1_crabel_validation.py`, `_stress_battery_crabel_stretch.py`, `_test_global_indices_stretch.py`, `_test_spx_crabel_stretch.py` (Antigravity & Gemini 4, 2026-10-17).
[withheld: a line that refers to the lab's private live strategy]

## Verdict

| Claim | Replicated exactly? | After the fixes | Status |
|---|---|---|---|
| DAX 40 stretch: N 2,496, net +0.293R, t 5.86, 12/12 years | Yes: N 2,496, gross +0.3233R | gross +0.083R (t 1.68); net of the file's own bid/ask **+0.019R, t 0.38**; at the real FDAX / Xetra opens −0.013R / +0.032R | ❌ **KILLED: data artefact** |
| DAX NR-scaled: net +0.578R, t 6.91 | Yes: N 1,882, net +0.580R | gross −0.000R, net −0.124R | ❌ **KILLED: same artefact** |
| Bund 10Y: net +0.287R, t 4.63 (rejected for drawdown only) | Yes: N 2,073, net +0.284R | gross −0.036R, net **−0.256R, t −4.31** | ❌ **KILLED: same artefact** |
[withheld: a line that refers to the lab's private live strategy]
| US10Y, SPX gamma trap, gold, S&P stretch, EU50/JP225/US2000 | — | session literals hit the wrong hour (table §4) | ⚪ **VOID: the hypotheses were not tested** |

## 1. DAX 40: the money is a stale pre-open quote filled at the trigger

* **The clock.** `de30eur_m1.parquet` is stamped in Europe/Berlin local time at the END of each minute (`lab_utc.TZ_MAP`, GPU-miner Phase 0). `load_dataset` localizes the naive index as UTC. The exact replication (found by a grid over open / window / close) uses the bar stamped **08:00** as the open, entries while the stamp is ≤ 10:00 and the exit on the bar stamped 17:30. That bar is **07:59–08:00 Berlin: the last minute before FDAX opened**, until the Eurex extended hours reached this feed in 2018–2019.
* **The quote is stale.** Share of flat (H = L) minutes on the 08:00 bar: 57% / 47% / 58% / 24% in 2015–2018, 0–2% from 2019. Median jump from that bar's open to the next bar's open: 0.50–0.78 stretch in 2015–2018, 0.03–0.06 afterwards.
* **The fill.** When FDAX opens, the price jumps over the trigger; the code still fills at the trigger (`hypothesis_alpha_miner.py:119-124`). 369 such "gap entries" earn **+521.9R of the +806.9R gross**; entries in the first five FDAX minutes earn +614.8R. 2015–2018 carry +590.9R of +806.9R. The published yearly figures (2015 +188.0 … 2026 +10.3) and "Total Net +806.8R" are **gross** sums; net is +732.0R, max drawdown 36.6R (not 24.8R).
* **Fixed one step at a time** (same clock and window): fill at the bar open when it gapped through → gross +0.090R (t 1.81); stop also checked on the entry bar → +0.083R (t 1.68); the file's own bid/ask instead of a flat 0.030R (it costs 0.064R a trade) → **net +0.019R, t 0.38, 7/12 years**.
* **At the real opens on the true clock:** FDAX 08:00 Berlin → net −0.013R (t −0.26), 6/12 years; Xetra 09:00 Berlin → net +0.032R (t 0.73), 5/12 years. The claimed rule restricted to 2019+ (when the quote is live): net +0.071R, t 1.23; 2022+: +0.040R, t 0.58.
[withheld: a line that refers to the lab's private live strategy]

## 2. Bund and DAX NR-scaled: the same artefact, worse

* Bund at the 07:00 stamp = 06:59 Berlin, an hour before Eurex Bund opens: 84–96% of those minutes are flat in 2015–2018. Gap entries are 22% of trades and carry +573.5R of +640.9R gross. Physical fills: gross −0.036R; the Bund CFD spread is 0.22R a trade; net −0.256R, t −4.31, 1/12 years.
* DAX NR-scaled at the 07:00 stamp: gap entries carry +1,072R of +1,147R gross; physical fills gross −0.000R, net −0.124R.
* "Standard Crabel is safer than NR-scaled because of drawdown" (CANDIDATES.md) compares two artefacts.

## 3. NQ: positive gross, not a second pillar, size set by an unresolved minute

* **Clock is right.** The LSE `NQ.F` file is true UTC, start-of-minute (the 09:30 ET bar carries the opening volume). Stretch uses `shift(1)`; with a 2-day lag the result is unchanged (+0.093R vs +0.093R): no leak.
* **The one optimistic assumption:** the stop is never checked on the entry minute (`_test_m1_crabel_validation.py:79-96`). A one-minute bar that reaches both the trigger and the open (110 of 2,388 trades) is booked as a winner held to the close. Minute data cannot say which came first and the raw L1 archive is deleted, so the true value lies between two bounds:

[withheld: a line that refers to the lab's private live strategy]
|---|---|---|
| LSE NQ.F 2016-06..2026-10 (the authors' sample) | +0.093R, t 2.29, 9/11 years | +0.154R, t 3.64, 11/11 years |
| US100 CFD 2005–2026, true clock (independent vendor) | +0.058R, t 2.03, 15/22 years | +0.113R, t 3.86, 17/22 years |
| CFD 2005-01..2016-05 (never seen by the authors) | **+0.038R, t 0.98, 7/12 years** | **+0.059R, t 1.50, 7/12 years** |

* CFD and LSE agree on the overlap: 97.6% same direction, R correlation 0.93. Bar phase −3..+3 minutes on the CFD: net t 1.55–2.84 (plateau, no needle). Gap fills change almost nothing on NQ (−0.002R).
[withheld: a line that refers to the lab's private live strategy]
[withheld: a line that refers to the lab's private live strategy]
* **Power (rule 14):** at +0.09R and SD ≈ 2.1R, t = 2 needs ≈ 2,200 trades: about 10 years of this rule.
[withheld: a line that refers to the lab's private live strategy]

## 4. The rest of the sweep tested the wrong hour

`load_dataset` reads every naive index as UTC, never removes the end-of-minute stamp and uses UTC literals with no DST handling. What the "open" bar really was:

| Test | File zone | Literal | Real time |
|---|---|---|---|
| DAX standard (claim) | Berlin | 08:00 | 07:59 Berlin, 1 minute before FDAX (stale 2015–18) |
| DAX NR-scaled | Berlin | 07:00 | 06:59 Berlin, before FDAX and Xetra |
| Bund 10Y | Berlin | 07:00 | 06:59 Berlin, before Eurex Bund |
| US 10Y note | Chicago | 12:30 | 13:29 ET |
| SPX gamma trap, Dow NR | Chicago | 13:30 | 14:29 ET (afternoon) |
| S&P stretch (`_test_spx_crabel_stretch.py`) | Chicago read as UTC → ET | 09:30 | 14:29–15:29 ET (afternoon) |
| Nasdaq NR-scaled | true UTC | 13:30 | 09:30 ET in summer, 08:30 ET in winter |
| Gold "COMEX 12:30 UTC" | file's EST | 12:30 | 12:30 EST, not 12:30 UTC |
| EU50 / JP225 / US2000 (global script) | Berlin / Chicago / NY | 07:00 / 00:00 / 14:30 | before the EU cash open / Tokyo afternoon / 14:29 ET |

The kills of these tests are not evidence either way: the hypotheses stay **untested (VOID)**. The NQ scripts (`_test_m1_crabel_validation.py`, lunch breakout, Fisher ACD) convert true UTC to ET correctly.

## 5. Defects in `hypothesis_alpha_miner.py` (and the copies of its loop)

1. `:49-52` naive exchange-local stamps localized as UTC; no `−1 min` for end-of-minute files. Use `lab_utc.load_canonical_utc` / `prop_gpu_miner/data_loader.py`.
2. `:56, :272, :360` session times as UTC literals; no DST. Sessions must be declared in the exchange's zone (`Europe/Berlin 09:00`).
3. `:119-124, :333-340` stop-entry filled at the trigger even when the bar opened beyond it. Fill = `max(open, trigger)` (long), `min(open, trigger)` (short); stops the same way.
4. `:117-131` stop never checked on the entry bar: intrabar ambiguity always resolved in the trade's favour. Report both bounds.
[withheld: a line that refers to the lab's private live strategy]
6. `:161-165` "placebo" = random sign flips of the trade's own R: a restated t-test that no data artefact can fail. A placebo must run random entries through the same engine and data.
7. `:179` MaxDD ≤ 35R and 80% winning years are not gates in `INSTITUTIONAL_VALIDATION_STANDARDS.md`; `:182-206` the Monte Carlo uses a trailing drawdown, 60 trades, Phase 1 only, and a seed that moves the answer across the bar.
8. No staleness check at the anchor minute, no Discovery / Validation / Holdout split, no prereg, no ledger entry. About 60 trials were run on 2026-10-17 (SYTM 10, MA-slope 12, priority sweep 6, stretch NQ / DAX / SPX / global 7, ACD, lunch, Fib 7, Quantpedia 8, engineered 4, CAPM 1, academic battery); none is in `global_trial_ledger.json`.
9. Reporting mixes gross and net (DAX yearly "net" and total are gross; t 5.86 and MaxDD 24.8R are not reproducible from the stated run).

## 6. Architecture (Task 2)

* **Pillar 2 (DE40):** no. There is no edge to deploy. No MQL5 EA, no parallel FTMO demo. Adding a symbol to the demo changes the live stack and needs Kaloyan's "Да" in any case.
* **HDAM:** "hypothesis first, from the literature" is the right order and is what the prereg process already does. It did not fix the miner's failure, because the failure was never the idea source: it is the engine. The GPU miner already has the parts HDAM lacks (`data_loader.py` true clock with sealed splits, engine verified against a reference on ~500,000 trades, graveyard, null bar). Make HDAM a thin layer on top of them:
  1. one loader (`data_loader.load_true_clock`), no `pd.read_parquet` in strategy code;
  2. sessions in exchange-local time, checked against the calendar of the venue that actually trades (FDAX hours changed in 2018);
  3. a staleness guard: refuse an anchor minute whose flat-bar share or quote gap is above a threshold, per year;
  4. one fill model (gap-through fills, entry-bar stop as a lower/upper bound, bid/ask from the file);
  5. engine self-test on a random walk with an injected stale pre-open quote: any positive gross is a bug (this test would have caught today's DAX result);
  6. prereg + ledger before the run, Discovery only, gates computed by `alpha_factory/statistical_gates.py` and not re-implemented per script;
  7. graveyard lookup before the run (`prop_gpu_miner/graveyard.py`, CANDIDATES.md, `SESSION-STATE.md`).
* **Pipeline for any future candidate:** Discovery → frozen prereg → Validation / Holdout (Kaloyan opens them) → independent vendor → event-driven parity (G7) → demo for mechanics and cost only.
