cf2ad73343
Walk-forward "second opinion" stake multiplier (meta-labelling on 65 decision-time features, 3yr Oanda books @ $0.65) FAILED the pre-registered rule on M5 (clean null) and M15 (lift inside the label-shuffle null, explained by BUY-vs-SELL). Nothing that changes numbers ships; the 12-month M5 book re-run is byte-identical to the pinned one. Behaviour-neutral plumbing: - position_sizing.build_features (65 features; multiplier path returns 1.0, no artifact) - SignalReport.penalty_tags / penalty_total / momentum_fading (record-keeping) - TradeSetup.stake_multiplier = None (reserved) - gpu_backtest --export-features (default export stays the pinned 63 columns; extras forced to the tail even when the first trade lacks an indicator) Research tools (backtest/research): book_report, burst_map, burst_precursors, open_trade_bursts, calendar_impact, burst_indicator_map, sizing_model, watch_rule_eval. Findings recorded in backtest/research/sizing_campaign_2026-09.md: M5 stretch measures are positive in both halves; every open-trade protection against violent bars loses on both TFs; high-impact USD releases cause 20-31% of violent bars but trades held through them do better; violent bars come when price is already at an extreme, not from calm. Module 29 tests (13). Suite: 435 passed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RWV2yLt3YXHHGVYHSQU2EE
349 lines
26 KiB
Markdown
349 lines
26 KiB
Markdown
# Stake-sizing campaign — 2026-09-05 (3-year Oanda window, $0.65)
|
||
|
||
## Purpose and owner decisions
|
||
Owner asked (2026-09-05): predict direction and choose order size better; protect against
|
||
trend exhaustion at signal time; map hours/days where institutions move gold violently;
|
||
backtests ≤ 3 years. Decisions taken in planning:
|
||
- Order size = a per-signal RELATIVE stake ("Stake: 0.7× normal"), not account-based units.
|
||
- Exhaustion protection = RISK LESS, DON'T SKIP (every hard exhaustion block tested since
|
||
2026-07 removed net-positive trades — the winners are the stretched runs).
|
||
- Model only M5 and M15; H1/H4 measured descriptively.
|
||
- scikit-learn / lightgbm allowed in ./venv for research only; the shipped artefact is
|
||
numpy-only JSON. Neither library enters requirements.txt.
|
||
- No tuned threshold, gate or exit changes (rule 1). Sizing is a post-decision overlay computed
|
||
by `position_sizing.build_features` / `stake_multiplier` INSIDE `analyze_from_dataframes`, so
|
||
live and backtest use the same call on the same objects (rule 4).
|
||
|
||
## Harness
|
||
`backtest/gpu_backtest.py --pair XAUUSD --start 2023-09-05 --end 2026-09-05 --spread 0.65
|
||
--workers 12`, pair-factory config (no --set/--quality/--strategies/--confirm-tfs), NumPy CPU
|
||
backend (CuPy cannot locate CUDA_PATH on this box; the 2026-09-03 pinned books were also CPU).
|
||
Cache: `oanda_XAU_USD_{5min,15min,1h,4h}_20230705_20260905.parquet`, `…_1d_20221110_20260905`.
|
||
Feature books add `--export-features` (65 `f_*` columns + `stake_multiplier` + `r_sized`; the
|
||
default export stays the pinned 63-column shape). Outputs in `backtest/research/sizing_2026-09/`
|
||
(gitignored).
|
||
|
||
## Baseline (control books, unmodified backtester, 2026-09-05 23:36–23:41 UTC)
|
||
| TF | trades | total R | WR | PF | max DD |
|
||
|---|---|---|---|---|---|
|
||
| M5 | 857 | +161.32 | 53.7% | 1.53 | 9.85 |
|
||
| M15 | 1086 | +148.69 | 59.1% | 1.37 | 15.47 |
|
||
| H1 | 255 | +70.52 | 64.3% | 1.98 | 4.43 |
|
||
| H4 | 113 | +22.68 | 58.4% | 1.63 | 5.39 |
|
||
|
||
### Behaviour-neutrality checks (2026-09-05 23:57 UTC)
|
||
- 12-month M5 canonical window (2025-08-14 → 2026-08-14, $0.65) re-run on the modified tree:
|
||
`book_diff` vs `backtest/oanda_m5_12mo_065_nopb_20260903.csv` = ΔR +0.0, Δtrades +0, and the
|
||
two CSVs have the SAME MD5 (`a28fed4becd056466c26d6a947e1a0e5`). Record-keeping fields,
|
||
`SizingConfig`, and the `--export-features` hook change nothing when the flag is off.
|
||
- Feature books: the 63 shared columns are value-identical to the control books on every TF
|
||
(`assert_frame_equal`). A column-ORDER quirk was found and fixed the same day: pandas orders
|
||
export columns by first appearance, so when the first trade of a book lacks an indicator
|
||
(the 3-year M5 book's first trade had no candlestick pattern) that indicator's columns were
|
||
appended after the extras. `trades_to_dataframe` now forces the extras to the tail; test
|
||
extended with that exact case (435 tests green).
|
||
|
||
## Pre-registered acceptance rule (written 2026-09-05, BEFORE any model fit)
|
||
A per-timeframe stake-multiplier model may ship only if, evaluated on OUT-OF-SAMPLE quarters
|
||
only (all calendar quarters after a 12-month burn-in, each predicted by a model trained
|
||
exclusively on trades whose entry time is ≥ 300 primary bars before that quarter's start,
|
||
expanding window, standardisation fit on train only), ALL of the following hold:
|
||
1. Sized total R > flat total R in BOTH halves of the OOS period (split at the median OOS
|
||
entry time).
|
||
2. Sized total R > flat total R over the full OOS period.
|
||
3. Sized max drawdown (0-prepended cumulative sized-R curve, as `compute_metrics`) ≤ flat max
|
||
drawdown × 1.10.
|
||
4. Sized OOS book has PF ≥ 1.3 and positive total R in both halves (the rule-1 floor).
|
||
5. Real OOS lift > the 95th percentile of the lift distribution from 50 label-shuffle refits
|
||
(R permuted within training windows only; test labels and walk-forward geometry unchanged).
|
||
6. Mean multiplier over OOS trades within 1.00 ± 0.05; every multiplier in [0.5, 1.5].
|
||
Plus the mechanical check: the `--sizing` full-window replay reproduces the flat book's
|
||
entry/exit/`r_multiple` columns exactly (sizing changes no entries).
|
||
Model family and size map (fixed): primary = ridge regression on winsorised R (clip to
|
||
[−1.5, +4]); secondary = L2 logistic on R>0 (ships only if the primary fails and it passes);
|
||
simplicity baseline = one-feature model on `f_is_buy` (the shipped model must beat its OOS
|
||
lift, else the simpler one ships if it passes); upper bound = lightgbm on all `f_*` (never
|
||
ships; if its OOS lift is inside the shuffle null, stop); ablation = exhaustion-block-only model.
|
||
Map: `m = 0.5 + 1.0 × clip((z − q10)/(q90 − q10), 0, 1)` with q10/q90 from TRAIN predictions,
|
||
times a normaliser making the mean train multiplier 1.0, clipped to [0.5, 1.5], rounded to 0.05.
|
||
`SHIPPED_FEATURES` (≤ 12 per TF, ≥ 2 from the exhaustion block) are chosen from the Stage-B
|
||
sign-stable screen on the 3-year feature books and recorded below BEFORE the first fit; any
|
||
later change restarts the shuffle control. If any clause fails on a timeframe, that timeframe
|
||
ships nothing. No widening the clip, changing the split, dropping the purge, re-picking
|
||
features or adding quarters to rescue a result.
|
||
|
||
## Pre-registered feature lists
|
||
Derived MECHANICALLY (script in the session log, saved as `sizing_2026-09/preregistered_features.json`):
|
||
sign-stable `f_*` features from the Stage-B screen (top-minus-bottom quintile mean-R gap has the
|
||
same sign in both halves, both halves ≥ 10 trades per bucket), zero-variance columns removed,
|
||
ranked by min(|gap_h1|, |gap_h2|), top 12; the ≥ 2-from-the-exhaustion-block clause was already
|
||
satisfied on both TFs. Written 2026-09-06 00:05 UTC, before any Stage-D fit.
|
||
- **M5 (29 sign-stable candidates):** f_sl_atr, f_confidence, f_rsi, f_mtf_c1, f_swing_dist_atr,
|
||
f_ret_48_atr, f_tag_no_pullback, f_rsi_dir, f_first_friday, f_mtf_agreement,
|
||
f_exhaustion_count, f_ret_24_atr. Exhaustion-block members: f_swing_dist_atr,
|
||
f_tag_no_pullback, f_exhaustion_count (all three run POSITIVE in both halves).
|
||
- **M15 (37 sign-stable candidates):** f_rsi, f_bb_pos, f_day_of_month, f_tag_ema200_near,
|
||
f_atr_pct, f_is_buy, f_confidence, f_close_pos_in_bar, f_tag_weak_pullback, f_tag_exhaustion,
|
||
f_penalty_total, f_ret_6_atr. Exhaustion-block members: f_tag_exhaustion, f_penalty_total
|
||
(both NEGATIVE in both halves; f_tag_weak_pullback and f_tag_ema200_near also negative).
|
||
Exhaustion-only ablation list (fixed by the plan): f_swing_dist_atr, f_ema21_dist_atr,
|
||
f_ema200_dist_atr, f_exhaustion_count, f_penalty_total, f_tag_bb_overextended,
|
||
f_tag_rsi_extended, f_tag_no_pullback, f_tag_exhaustion.
|
||
|
||
## Stage B — book breakdown (3-year feature books)
|
||
Tool: `backtest/research/book_report.py` (outputs in `sizing_2026-09/report_{5min,15min,1h,4h}/`).
|
||
Halves = median TRADE split (M5 cut 2025-12-12 because trade density doubled in the last year;
|
||
M15 cut 2025-04-25; H1 2025-04-09).
|
||
|
||
**M5 (857 trades, +161.3R, PF 1.53, DD 9.85; halves +51.0R PF 1.33 / +110.3R PF 1.74; max 11
|
||
wins / 8 losses in a row; longest flat stretch 88 trades).** Mix: GENERIC 469, XAU_CP 388, BUY
|
||
765 / SELL 92 (SELLs +11.8R, positive both halves). Grade: A +0.26R/trade (227) > B +0.17 (608)
|
||
> C −0.01 (21). Session (decision hour): NY 17–22 best (+0.31R/trade, PF 1.88), overlap 13–16
|
||
weakest (+0.08, PF 1.22) — both stable. Weekday: Wed and Fri best and stable; Mon/Tue flip sign.
|
||
Feature screen: 29 sign-stable `f_*` features. **Every stretch measure is POSITIVE**: wider
|
||
stop in ATR (+0.22/+0.23 gap), higher RSI (+0.20/+0.19), farther from the last swing
|
||
(+0.18/+0.27), 48-bar run-up (+0.18/+0.20), the `no_pullback` tag (+0.17/+0.28), more exhaustion
|
||
tags (+0.12/+0.24). Only ATR-relative-to-500-bar-mean is negative (−0.11/−0.12: entries during
|
||
a volatility spike do worse). "Risk less when stretched" has NO support on M5 — a model that
|
||
honours the data will size stretched M5 trades UP, not down.
|
||
|
||
**M15 (1086 trades, +148.7R, PF 1.37, DD 15.47; halves +65.8R / +82.9R; max 12 wins / 8 losses;
|
||
flat stretch 131 trades).** All GENERIC; BUY 739 (+158.4R, PF 1.62) vs SELL 347 (−9.7R, PF 0.93;
|
||
−19.2R in half 1, +9.4R in half 2). Grade NOT monotone: C +0.45R/trade (82, PF 2.41) > A +0.14
|
||
(481) > B +0.08 (516) — stable in both halves (matches the 2026-08-04 quant review). Session:
|
||
Asian and NY ≈ +0.18–0.20R/trade, London and overlap ≈ +0.09–0.10 (all stable). Weekday:
|
||
Wednesday flat/negative in both halves (229 trades, −0.6R); Thu/Fri flip sign between halves.
|
||
Feature screen: 37 sign-stable features. Here the exhaustion block runs NEGATIVE: `exhaustion`
|
||
tag (−0.19/−0.10), `weak_pullback` tag (−0.24/−0.11), `ema200_near` tag (−0.17/−0.32), penalty
|
||
total (−0.16/−0.10), ATR percentile (−0.20/−0.15); while raw RSI (+0.31/+0.24) and Bollinger
|
||
position (+0.36/+0.22) are positive. So on M15 "stretched by the aggregator's own penalty
|
||
logic" is bad while "strong momentum reading" is good — the sizing model gets both.
|
||
|
||
**H1 (255 trades, +70.5R, PF 1.98, DD 4.43; halves +28.7R PF 1.79 / +41.9R PF 2.17)** and
|
||
**H4 (113 trades, +22.7R, PF 1.63, DD 5.39)** are descriptive only. H1 is all-BUY by
|
||
construction (shorts blocked 2026-09-03); Mon/Thu strongest, Friday the only losing weekday
|
||
(−1.4R/50, flips between halves); 03:00 UTC decision hour is the best single hour (+20.0R/40,
|
||
stable) and 04:00–05:00 negative.
|
||
|
||
**Labelling quirk (pre-existing, not fixed):** 33 M5 trades carry the backtester `strategy`
|
||
tag GENERIC while their setup strategy is XAU_ATR (`f_strat_atr = 1`); the CSV tag and the
|
||
setup's own string disagree for the ATR path. Labels only — R is unaffected.
|
||
|
||
**Stop-trigger check: PASSED** (both TFs have many sign-stable features) → proceed to Stage D.
|
||
|
||
## Stage C — institutional burst / time map
|
||
Tool: `backtest/research/burst_map.py` on the 3-year raw bars (`burst_5min/`, `burst_15min/`
|
||
under `sizing_2026-09/`). Burst = bar range ≥ K × prior Wilder ATR(14). Base rate at K=3:
|
||
0.86 % of M5 bars (1840), 1.19 % of M15 bars (846).
|
||
|
||
**Where the violent bars cluster (bar-CLOSE hour, UTC, K=3 burst % of bars):**
|
||
- 12:00–13:00 (US data window): M5 2.4 % / 2.3 %, M15 3.7 % / 5.0 % — 3–4× the base rate;
|
||
Friday 12:00–13:00 is the densest weekday cell on M5 (3.4 % / 3.1 %). Largest mean 1-hour
|
||
dollar moves are at close-hours 13–15 (M5: $7.8 / $9.4 / $8.5 vs ≈$5 elsewhere).
|
||
- 01:00 (Asia open): M5 2.9 %, M15 3.3 %. Also 05:00 (Tokyo/London hand-over) ≈1.2 % on M5.
|
||
- 22:00–23:00 (post-settlement thin book): ≈1.3–1.4 % on M5; Sunday reopen 22–23 h is the most
|
||
burst-prone slot of all (2.4–3.0 % of bars) but the calendar already blocks Sunday entries
|
||
before 18:00 NY.
|
||
- Quietest hours: 03:00–04:00 and 16:00–17:00 (0.14–0.3 %).
|
||
|
||
**Post-burst drift (in the burst's own direction, ATR units):** ≈ 0 at every horizon on both
|
||
TFs (M5 6/12/24/48-bar means −0.01/−0.01/+0.09/+0.05; M15 −0.08/+0.14/+0.18/+0.17; % positive
|
||
46–51 %). London-session bursts on M5 show a mild short-horizon reversal (42 % positive at 6
|
||
bars). No continuation or reversal edge worth a rule.
|
||
|
||
**Do bursts hurt the system's entries? No.** Trades DECIDED within 12 bars after a K=3 burst:
|
||
M5 149 trades, mean +0.199R, PF 1.62 vs +0.186R / PF 1.52 for the rest; M15 242 trades,
|
||
+0.160R / PF 1.47 vs +0.130R / PF 1.34. At K=4/5 the after-burst group is better still
|
||
(M5 +0.276R PF 1.98; M15 +0.259R PF 1.80). This re-confirms the 2026-07-30 big-candle-gate
|
||
rejection with 3 years of data: expansion bars are part of the entry signal, not a danger.
|
||
(Group (b) "burst while open" is survivorship-shaped — winners live longer — and is not evidence.)
|
||
|
||
**The 40 largest 1-hour moves** are almost all one episode (2026-01-28 → 2026-02-06, incl. a
|
||
−$394 hour at 15:45 UTC on 2026-01-29, and 2026-03-23), not recurring clock slots; only 3 of 40
|
||
fall in the 12:30 data slot and 1 in the Wednesday 18:00 central-bank slot (slot labels only —
|
||
no economic calendar file exists in the repo). Violent moves are episodic regime events; the
|
||
recurring, predictable clustering is the 12:00–15:00 UTC US-data window and the 01:00 Asia open.
|
||
|
||
**Use in this campaign:** `f_bars_since_burst` and the hour/session features are already in the
|
||
feature set, so Stage D can weigh them as a stake input; no entry gate is proposed (rule 1).
|
||
|
||
## Stage D — walk-forward results (2026-09-06 00:20 UTC)
|
||
Driver: `backtest/research/sizing_model.py` (numpy closed-form ridge + Newton logistic; lightgbm
|
||
4.7 + scikit-learn 1.9 installed in ./venv for the upper bound only; NOT in requirements.txt).
|
||
Outputs: `sizing_2026-09/sm_{m5,m15}_{ridge,logistic}/` (summary.txt, oos_trades.csv,
|
||
quarters.csv, coefs_by_quarter.csv, shuffle_lifts.csv). Validation of the driver: planted
|
||
synthetic effect recovered (lift +116.7R, shuffle rank 100 %, all clauses PASS); pure-noise
|
||
book correctly FAILS (rank 32 %); ridge matches `lstsq` to 2e-16; logistic gradient < 3e-15.
|
||
|
||
**M5 (5 OOS quarters 2025Q3→2026Q3, 619 trades — 2024Q4–2025Q2 skipped by the pre-registered
|
||
min-train rule because only 96/119/142 trades precede them; 2026Q1 alone is +92R of the +125R
|
||
flat OOS):**
|
||
| model | flat R | sized R | lift | PF | DD | mean m | shuffle p95 | rank | verdict |
|
||
|---|---|---|---|---|---|---|---|---|---|
|
||
| ridge (primary) | +125.41 | +121.98 | −3.43 | 1.63 | 10.69 | 0.891 | +13.69 | 48 % | FAIL (1,2,5,6) |
|
||
| logistic | +125.41 | +128.40 | +2.99 | 1.66 | 8.70 | 0.900 | +14.39 | 62 % | FAIL |
|
||
| isbuy baseline | +125.41 | +123.22 | −2.20 | 1.55 | 11.92 | 1.000 | +7.05 | 4 % | FAIL |
|
||
| exhaustion-only | +125.41 | +118.03 | −7.38 | 1.59 | 10.88 | 0.918 | +14.36 | 28 % | FAIL |
|
||
| lightgbm (all 65) | +125.41 | +116.05 | −9.36 | 1.51 | 11.64 | 1.007 | +7.60 | 10 % | FAIL |
|
||
Ridge weights (standardised, 5 quarters): `f_tag_no_pullback` +0.018 sign-stable 5/5 — the
|
||
"no pullback" flag INCREASES the stake; `f_swing_dist_atr` and `f_exhaustion_count` ≈ 0 and
|
||
sign-unstable. Exhaustion-only sizing LOSES 7.4R. **A clean null: nothing in the 65 features
|
||
predicts M5 trade outcomes beyond noise, linear or non-linear.**
|
||
|
||
**M15 (8 OOS quarters 2024Q4→2026Q3, 762 trades; flat +115.93R PF 1.42 DD 13.73):**
|
||
| model | sized R | lift | PF | DD | mean m | shuffle p95 | rank | clauses |
|
||
|---|---|---|---|---|---|---|---|---|
|
||
| ridge (primary) | +142.04 | +26.11 | 1.53 | 14.46 | 0.980 | +28.50 | 88 % | 1,2,3,4,6 PASS · **5 FAIL** |
|
||
| logistic | +144.78 | +28.84 | 1.53 | 13.97 | 1.002 | +31.89 | 92 % | 1,2,3,4,6 PASS · **5 FAIL** |
|
||
| isbuy baseline | +139.38 | +23.45 | 1.50 | 16.44 | 1.007 | +24.47 | 58 % | FAIL (3, 5) |
|
||
| exhaustion-only | +123.83 | +7.90 | 1.45 | 14.85 | 1.010 | +19.77 | 70 % | FAIL |
|
||
| lightgbm (all 65) | +123.87 | +7.94 | 1.44 | 14.74 | 0.996 | +17.66 | 60 % | FAIL |
|
||
Ridge weights sign-stable in 8/8 quarters: `f_tag_exhaustion` −0.022, `f_tag_ema200_near`
|
||
−0.033 (both SHRINK the stake — the "risk less when stretched" direction), `f_rsi` +0.028,
|
||
`f_bb_pos` +0.030, `f_is_buy` +0.029, `f_close_pos_in_bar` +0.022, `f_ret_6_atr` +0.022;
|
||
λ = 1000 every quarter (heavy shrinkage — the score is weak and the map is essentially a rank
|
||
transform). Sized R beat flat in 6 of 8 quarters and in both halves, BUT the label-shuffle
|
||
null has p95 = +28.5R: a random re-weighting of these 12 pre-screened features produces the
|
||
same lift 12 % of the time, and the one-feature BUY/SELL baseline already delivers +23.5R of
|
||
the +26.1R. **The model's information beyond "buys are better than sells" (≈ +2.7R over 762
|
||
trades) is indistinguishable from noise.**
|
||
|
||
## Verdict (pre-registered rule applied, no rescue attempted)
|
||
**FAIL on both timeframes → no stake multiplier ships.** No artifact, no `sizing.enabled`, no
|
||
alert line, no `--sizing` backtester flag. What remains in the tree is behaviour-neutral
|
||
research plumbing: `position_sizing.build_features` (65 decision-time features; the multiplier
|
||
path returns 1.0 and no artifact exists), `SignalReport.penalty_tags/penalty_total/
|
||
momentum_fading` (record-keeping), `TradeSetup.stake_multiplier` (None; reserved), the
|
||
`--export-features` flag, `book_report.py`, `burst_map.py`, `sizing_model.py`, and Module 29
|
||
tests. `SizingConfig` was REMOVED again before merge (rule 8: nothing reads it).
|
||
|
||
**What the owner should take from this (plain language):**
|
||
1. On the 5-minute chart there is nothing to size on. Stretched trades are the good ones; the
|
||
system's exhaustion flags carry no usable information about the outcome; a flexible model
|
||
given all 65 facts does worse than flat sizing out of sample.
|
||
2. On the 15-minute chart the only robust sizing signal is BUY vs SELL, which the gates already
|
||
act on. The exhaustion flag does point the "risk less" way in every one of 8 unseen
|
||
quarters, but the money it would make (≈ +3R per year) is inside noise at 1,086 trades.
|
||
3. "Predict direction better" with a learned second opinion is closed at this sample size:
|
||
the strongest non-linear model (all features, gradient boosting) fails on both charts.
|
||
|
||
**Open avenues (not rescues — each needs a NEW pre-registration):**
|
||
- Re-run the M15 exhaustion-tag sizing when the book has ≈ 2× the trades (≈ 2028) or on a
|
||
sealed forward sample; the sign is stable, the magnitude is not proven.
|
||
- The shuffle null is wide because the size map rank-stretches a weak score to 0.5–1.5×; a
|
||
narrower map (e.g. 0.75–1.25×) would shrink both lift and null — pre-register before testing.
|
||
- Session and weekday structure (M5: NY best / overlap weakest; M15: Wednesday flat) is stable
|
||
across halves but was rejected as a gate repeatedly; as a *size* input it was inside the
|
||
12-feature model and did not survive the null.
|
||
|
||
## Addendum 2026-09-06 — violent bars: signs before them, and protecting OPEN trades
|
||
Owner follow-up: "we might have an order running for hours before a violent move; we want
|
||
protection." Tools: `backtest/research/burst_precursors.py` (signs before a burst) and
|
||
`backtest/research/open_trade_bursts.py` (exact replay of every book trade on raw bars with the
|
||
backtester's exit model — reproduces `r_multiple` on 100 % of trades — plus protection variants).
|
||
Outputs: `sizing_2026-09/burst_precursors_{m5,m15}.txt`, `open_trade_bursts_{m5,m15}.txt`.
|
||
|
||
**Signs before a burst (both TFs, both halves stable):** the clock (close-hours 01, 12, 13 UTC
|
||
hold ~40 % of bursts in 13 % of bars); tick activity in the last 3 bars vs 50-bar mean (top
|
||
quintile ≈ 2× rate); the last bars already wider than normal (top quintile ≈ 2.2×; the
|
||
tightest quintile ≈ 0.3–0.5× — NOT calm-before-storm at bar scale); a move already under way
|
||
over the past hour (≈ 1.6×); a burst minutes ago (≈ 1.4×). Out of sample (fit half 1, score
|
||
half 2) a logistic model reaches AUC 0.75–0.77 for the next bar; the top 5 % of moments carry a
|
||
3.4–5.5 % burst chance vs 0.8–1.1 % base, and 24 % vs 9 % (M5) for "within the next hour".
|
||
Burst direction continues the prior hour's drift 61 % of the time. Signs say "ripe", not "now".
|
||
|
||
**Damage to open trades (base exits):** M5 — 164 trades (19 %) meet an opposing burst while
|
||
open: mean open R +0.42 just before → −0.06 just after → final +0.01; 43 % are stopped on that
|
||
bar; 54 % end as losers. M15 — 246 trades (23 %): +0.27 → −0.20 → final −0.12; 39 % stopped on
|
||
the bar. Trades already in profit before the burst still finish positive on average (M5 +0.53,
|
||
M15 +0.27). Bursts WITH the trade (n 176 / 246) finish at +1.11 / +0.83R — they are where the
|
||
winners come from.
|
||
|
||
**Protection variants — ALL negative on both TFs, both halves:**
|
||
| variant | M5 ΔR (h1/h2) | M15 ΔR (h1/h2) |
|
||
|---|---|---|
|
||
| flatten before US-data hours (12–13 UTC) | −31.9 (−13.6/−18.3) | −103.0 (−67.4/−35.6) |
|
||
| flatten before Asian open (00–01 UTC) | −38.7 (−22.0/−16.7) | −73.0 (−42.0/−30.9) |
|
||
| protective 0.5R stop through the data hours | −29.6 (−12.3/−17.3) | −82.3 (−55.5/−26.8) |
|
||
| protective 0.5R stop through the Asian open | −19.2 (−9.5/−9.7) | −35.1 (−17.3/−17.8) |
|
||
| exit next open after an opposing burst | −19.1 (−6.5/−12.5) | −6.9 (−11.9/+5.0) |
|
||
| 0.5R protective stop after an opposing burst | −9.0 (−3.0/−6.0) | −8.3 (−15.5/+7.3) |
|
||
The existing stop/trail already contains the opposing-burst damage (≈ 0.4–0.5R per hit); every
|
||
pre-emptive rule removes far more from the with-trade bursts than it saves. **Verdict: no
|
||
open-trade protection rule ships; do not re-propose flatten/tighten-before-window or
|
||
react-to-burst exits without a new mechanism.** Display-only "violent-move conditions" note on
|
||
alerts is the only harmless use of the precursor signs.
|
||
|
||
## Addendum 2026-09-06 — calendar events, indicator states and time slots before violent bars
|
||
Tools: `backtest/research/calendar_impact.py` (needs `backtest/cache/calendar.parquet`, scraped
|
||
2023-09-01→2026-09-10 with `ff_calendar_scraper.py`: 15,124 events, 1,055 high-impact USD in
|
||
window) and `backtest/research/burst_indicator_map.py` (states from the backtester's own
|
||
`precompute_indicators`, taken at t−1). Outputs `sizing_2026-09/calendar_impact_{m5,m15}.txt`,
|
||
`burst_indicator_map_{m5,m15}.txt`. Burst = range ≥ 3× prior ATR14; lifts quoted are stable in
|
||
BOTH halves on both TFs unless noted.
|
||
|
||
**Calendar.** The M5 bar containing a high-impact USD release averages 4.4× normal range; 54 %
|
||
of such releases produce a burst within the hour (base 9 %). By event: Non-Farm Payrolls (with
|
||
Unemployment Rate / Avg Hourly Earnings) 97 %, 9.3× bar, $11.9 mean 1-h move; CPI 91 %, 8.3×,
|
||
$9.0; FOMC decision/statement 87.5 %, 7.1×, $18.2 (press conference a further $20 hour); FOMC
|
||
projections 92 %; Retail Sales 64 %; PPI 54 %; Core PCE 50 %; Unemployment Claims 44 %; GDP
|
||
45–58 %; ISM PMIs 26 %; Powell speaks 33 %; Trump speaks 21 %; UoM sentiment ≈ 5 %. 20 % (M5)
|
||
/ 31 % (M15) of ALL bursts fall within ±30 min of a high-impact USD release (3.7–4.2 % of time);
|
||
in the 30 min AFTER one, next-bar burst odds are 8–17 % (7–16× base); 15 min before, 2–3 %.
|
||
Open trades that held through a high-impact release did BETTER (M5 +0.41R vs +0.16R; M15 +0.27
|
||
vs +0.07), even when the first 15-min move went against them (M5 +0.09, M15 +0.22). Flattening
|
||
5 min before a release: −5.4R (M5) / −59.3R (M15); 0.5R protective stop through the hour:
|
||
−2.4R / −43.1R. **Events are the biggest single cause of violent bars and still not a reason
|
||
to touch an open trade.**
|
||
|
||
**Indicator states before a burst (next-bar lift, M5 / M15).** Price OUTSIDE the Bollinger
|
||
bands: below 2.9× / 1.8–2.9×, above 2.1× / 2.2×, middle 0.2–0.8 band 0.7×. RSI < 30: 2.6–2.9× /
|
||
1.8–2.2×; RSI ≥ 70: 1.9–2.1× / 1.6–1.9×; RSI 40–60: 0.7× (calmest). Close outside the Keltner
|
||
channel: 1.4–2.2×; inside 0.76×. ADX RISING > 3: 1.4–1.5× / 1.2×; ADX FALLING < −3: 0.5–0.6×
|
||
(safest state); the ADX level itself carries nothing. MACD histogram negative and still falling
|
||
1.3× / 1.2–1.4×; histogram fading (either sign) 0.8×. Fully bearish 4-EMA stack 1.3–1.4× /
|
||
1.3×; "mostly bull" 0.8× on M5. Regime: transitioning 1.4–1.6× / 1.5–1.6×, ranging 0.88×.
|
||
Volatility regime: extreme 1.5–1.6× / 1.4–1.9×, low 0.6–0.8×. ROC beyond ±0.3 %: 1.2–1.9×
|
||
(>1 %: 1.3–10×, n small). Bollinger-width squeeze: 0.59× on M5 (LESS likely next bar — narrow
|
||
bands are not a warning); TTM squeeze (bands inside Keltner) 1.6× on M15 only. EMA200 distance,
|
||
day of month, first-Friday-as-a-day: nothing stable (first Friday matters only through its
|
||
12:30 slot).
|
||
|
||
**Time slots (M5 next-bar burst %, base 0.86 %):** Fri 01:xx 3.4 %, Fri 12:xx 3.4 %, Tue/Wed
|
||
01:xx 3.2 %, Thu 12:xx 3.1 %, Fri 13:xx 3.1 %, Sun 22:xx reopen 3.0 %, Thu 01:xx 2.7 %, Wed
|
||
12:xx 2.6 %, Sun 23:xx 2.4 %, Tue 00:xx 2.3 % (all 2.5–3.9× base). Quietest: 03–04 and 16–17.
|
||
|
||
**Direction of the burst (share going UP):** fully-bull stack 62 % (M5) / 50 % (M15); fully-bear
|
||
stack 26 % / 29 %; RSI < 30 → 22 % / 30 % up (i.e. 70–78 % DOWN); RSI ≥ 70 → 64 % / 52 % up;
|
||
MACD neg & falling → 30 % / 34 % up; trending_down regime → 26 % / 26 % up; trending_up → 60 % /
|
||
46 % up. Bearish states foretell a DOWN burst reliably on both TFs; bullish states foretell an
|
||
UP burst on M5 only.
|
||
|
||
**Reading:** violent bars arrive when price is already at an extreme with momentum accelerating
|
||
(outside the bands, RSI extreme, ADX rising, MACD still falling, transitioning regime, extreme
|
||
volatility) and/or a release is minutes away — "the storm is already brewing", not "calm before
|
||
the storm". Calm mid-band bars with fading momentum and falling ADX are the safest. None of it
|
||
changes trade handling (protection tests above); it is display material for a warning line.
|
||
|
||
## Addendum 2026-09-06 — violent-move WATCH rule (display-only feature, `violent_watch.py`)
|
||
Owner asked for a warning. Delivery: a line on signal alerts + a standalone heads-up to the owner
|
||
(once per hour per trigger set, daily cap), dark by default (`violent_watch` config block).
|
||
The first draft rule ("hot" = calendar | ≥3 indicator readings | clock & ≥2 readings) was
|
||
evaluated bar-by-bar on 3 years (`backtest/research/watch_rule_eval.py`, scratch variant
|
||
sweep): it flagged 27 % of bars hot at only ≈2× odds and would have sent ≈13 heads-ups/day on
|
||
M5 — rejected. Sweep of 11 variants (M5 / M15, both halves):
|
||
| hot definition | bars hot | next-bar lift h1/h2 | next-hour rate | hot hours/day |
|
||
|---|---|---|---|---|
|
||
| calendar only (hi-impact USD ±30 min) | 3.4 % | 6.7×/4.4× · 9.1×/6.4× | 31 % · 28 % | 2.6 |
|
||
| clock slot only | 18.8 % | 3.1×/2.0× · 3.6×/2.6× | 16 % · 13 % | 5.3 |
|
||
| ≥2 price-extreme readings | 14.6 % | 2.3×/2.2× · 1.8×/2.0× | 11 % · 6 % | 10.7 |
|
||
| **calendar ∨ (clock ∧ ≥1 price-extreme reading)** | **8.6 %** | **4.9×/3.1× · 5.9×/4.2×** | **21 % · 17 %** | **4.1 · 3.5** |
|
||
| draft (cal ∨ readings≥3 ∨ clock∧readings≥2) | 26.9 % | 2.4×/2.0× · 2.4×/2.1× | 13 % · 8 % | 13.0 |
|
||
**Shipped rule:** price-extreme readings = outside the Bollinger bands, RSI < 30 or ≥ 70, close
|
||
outside the Keltner channel (the neutral readings — ADX rising, extreme volatility, transitioning
|
||
— are dropped from the level logic; they are common and only ≈1.5×). `hot` = high-impact USD
|
||
release within 30 min or 30 min after, OR a hot clock slot with ≥ 1 price-extreme reading; `watch`
|
||
= clock slot alone or ≥ 2 price-extreme readings (≈2× odds). Direction sentence from the bear/bull
|
||
polarity of the readings plus MACD acceleration and trend regime ("DOWN" on both TFs, "UP" on M5
|
||
only). Heads-up only at `hot`. Expected ≈4 hot hours/day → ≤4 heads-ups/day after per-hour dedup.
|