EXP-011 design — Turtle + Bollinger squeeze, 30-symbol generalization
- Designed and run: 2026-09-10
- Execution model:
claude-opus-5 / high - Week: work week 5 (
2026-W37) - Status:
pre-registered
The question this experiment answers
EXP-008 recorded that on 7 symbols the squeeze filter raised the locked OOS median CAGR from 2.05% → 9.33% and cut mean MDD from -30.54% → -16.30%. But with about 4 OOS trades per symbol, that table cannot tell edge from luck.
So this experiment searches no further parameters. With the contract fixed, it only increases the number of symbols to see whether the EXP-008 result reproduces. If it does not, the EXP-008 figures are an artefact of 7 symbols.
Hypotheses fixed in advance
- The squeeze filter's MDD improvement holds as symbols are added.
- The squeeze filter's CAGR improvement holds as symbols are added.
- If only one of the two effects holds, only the one that holds is accepted as a reusable component.
- If no contract beats buy-and-hold over the same period, none is promoted regardless of the sign of its return.
The case where hypothesis 2 breaks and only hypothesis 1 remains is defined in advance as a normal verdict outcome, not a failure.
Basket preregistration
Before seeing results, the basket was fixed with select_exp011_universe.py (original supporting file) and saved in EXP-011-universe.json (original supporting file).
| Gate | Rule | Result |
|---|---|---|
| Candidate pool | All symbols in data/raw/creon/bulk/all_research | 3,552 |
| A · Coverage | First bar ≤ 2009-06-30, last bar ≥ 2026-06-30 | Excluded: 1,221 with no train-period bars · 980 short of coverage |
| B · Quality | 0 duplicate dates, 0 OHLC violations, train trading days ≥ 2,100 | 309 excluded |
| C · Rank | Median daily trading value over the train period (2010–2018), descending | Eligible 1,042 |
| Selected | Top 30 | 30 |
Leakage prevention: the rank calculation reads only train-period bars. Validation and locked OOS are not used for basket selection at all, so symbols cannot be picked to produce good OOS results.
Limit: all_research is a listing snapshot as of 2026-08-26, so symbols delisted before then are absent. Survivorship bias remains, so even if this basket's results pass the criteria, it is not final adoption. All 7 EXP-008 symbols are in the top 30. So the real subject of the generalization test is the 23 new symbols, and the existing 7 symbols are aggregated separately as the control.
Execution contract
The EXP-008 contract is inherited as is. Only the three items below are accounting fixes; no strategy rule changes.
| Item | Contract |
|---|---|
| Data | CREON daily bars, 30 preregistered symbols |
| Indicator warm-up | Signals generated from 2005-01-01 (250-day squeeze and 55-day breakout mature by 2010-01-01) |
| Bands | SMA(20) ± 2σ, BandWidth=(upper-lower)/middle |
| squeeze | Prior bar's BandWidth in the bottom 20% of its distribution over the previous 60/125/250 days |
| Entry | Close above the prior 55-day high → next trading day's open |
| Exit | Close below the prior 20-day low → next trading day's open, or ATR stop |
| Stop | Entry price - ATR(14) × 1.0/1.5/2.0 |
| Accounting | Initial 3,000,000 KRW, 95% of cash just before entry, integer quantities, no leverage |
| Costs | 0bp / CREON 1.5bp one-way provisional proxy / 2x / proxy + 5bp one-way slippage |
| Tax | Per product. A069500 and A102110 are ETFs, so no securities transaction tax |
| Risk | research_unhalted and account_halt run separately |
Fix 1 — per-period capital isolation
EXP-008 ran the full time series once and cut it by date. So drawdowns in the train period reduced order sizes in locked OOS. This time each period is an independent run, and at the start of each period capital is reset to 3,000,000 KRW. Signals are still generated from the full time series, so warm-up is preserved.
The engine contract is PortfolioConfig.trading_window; open positions at the end of a period are closed at the close as window_end. The regression test is in test_segment_trading_window.py (original supporting file).
Fix 2 — per-period buy-and-hold benchmark
Buy-and-hold is computed for the same period, same bars and same one-way commission and kept in a separate ledger. Excess CAGR, strategy CAGR minus benchmark CAGR, is used for the verdict. Since 2023 the domestic index has risen sharply, so a positive CAGR by itself is no evidence of an edge.
Fix 3 — flagging capital-starved cells
Entry signals discarded because cash could not buy even 1 share are counted as starved. Cells with starved > 0 are not strategy performance but cells contaminated by accounting starvation, so they are excluded from the verdict population and reported separately.
Matrix
- 4 contracts:
turtle_only(control),turtle_squeeze_60,turtle_squeeze_125,turtle_squeeze_250 - In EXP-008, bulge exclusion did not actually remove signals and standalone
bb_breakout_*provided no grounds. This is a generalization test, not an expanded search, so those two diagnostic groups are not carried over. - 4 contracts × 3 ATR × 4 costs × 2 risk × 30 symbols × 4 periods = 11,520 rows
- Periods:
train(2010–2018),validation(2019–2022),locked_oos(2023–2026-08-26),full(2010–2026-08-26)
1st verdict cell and criteria
A single verdict cell is fixed before checking results: locked_oos × normalized_actual_proxy × ATR 1.5 × research_unhalted.
To pass to advance, all of the following must be met.
- Median per-symbol CAGR on the 23 new symbols ≥ 15%
- Median per-symbol MDD ≥ -25%
- Median excess CAGR > 0 (did it beat buy-and-hold?)
- Total locked OOS trades ≥ 100
- Direction held at 2x costs and 5bp one-way slippage
- Direction held at neighbouring ATR 1.0 and 2.0
- Consistent direction across train, validation and locked_oos
starved= 0 in the verdict cell
If any is unmet, record hold; if the effect disappears on the new symbols relative to the control, record rejected. The best parameters after the fact are not picked for promotion.
Reproduction command
cd [original local path withheld]
python -X utf8 scripts\build\select_exp011_universe.py
python -X utf8 scripts\backtest\run_turtle_squeeze_basket_exp011.py