backtest: full-GPU research twin (cudaBasedBacktesting.py + cuda_signal_kernels.py); books byte-identical to gpu_backtest.py (2026-09-18)
Owner decisions: research only (live scans stay on the CPU code), gpu_backtest.py stays the base reference, every research flag supported, the one exemption from instructions item 6 recorded in backtest/instructions_for_backtest.txt. - backtest/cuda_signal_kernels.py: the CUDA decision stage. K1 = engine.py + the GPUTechnicalEngine overrides (one thread per bar), K2 = analyze_from_dataframes end to end incl. gates 6b-6i, the four setup generators and the regime filter (one thread per primary bar), K3 = position_sizing.build_features, K4 cooldown, K5 prefix median, K6 recorder bar rows. Config packer with generated #defines; leaf module; --fmad=false; Python evaluation order kept (ternary min/max, pairwise sums, Neumaier sum(), exact round(x, 2)). - backtest/cudaBasedBacktesting.py: same CLI, cache files and writers as the reference; the host turns kernel codes into the exact strings; no CPU path. - test_all.py Module 37 (16 tests): every stage pinned against the live code; suite 552 / 552 on the GPU, without CUDA and with FB2_BACKEND=cpu. - Proof: five reference MD5s, four 6-month acceptance books (M5/M15, $0.65/$0.30), M15 OOS --export-features and --record --record-bars tables, ten research-flag variants, and the owner's five-year M5 and M15 books, all byte-identical to gpu_backtest.py. Wall time 0.8-6.6 s per book against 13-353 s. - CLAUDE.md sections 1, 3, 6, 7, 9, 10, 11 and the instructions addendum: any signal change is now a parity event for the twin.
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
# Full-GPU research twin of the backtester — 2026-09-18
|
||||
|
||||
Behaviour-neutral by construction and by proof. `backtest/cudaBasedBacktesting.py` runs every stage
|
||||
of a backtest on the GPU, including the per-bar decision that `gpu_backtest.py` still runs through
|
||||
the live Python signal code. Nothing in the signal logic, the weights, the gates or the exit rules
|
||||
moved; the proof is that both scripts export byte-identical books (same CSV MD5) on every window
|
||||
and every research flag tried, listed in section 3.
|
||||
|
||||
## 1. Owner decisions
|
||||
|
||||
- Research and backtesting only. Live scans stay on the CPU signal code; the Telegram bot, the CLI
|
||||
and the dashboard never import the new files (Module 37 sweeps the live roots).
|
||||
- `gpu_backtest.py` stays in the tree as the **base reference**. The twin must reproduce its
|
||||
exported book byte for byte for the same arguments; the MD5 of the CSV is the acceptance gate.
|
||||
- `--record`, `--record-bars`, `--export-features` and `--fixed-rr-tp` all work on the GPU path.
|
||||
- Acceptance: 6-month M5 and M15 books, window 2026-03-18 → 2026-09-18, both scripts, identical
|
||||
CSV MD5 at $0.65 (headline) and $0.30 (comparability). That window was not cached; the first
|
||||
control run fetched M5/M15/H1 from Oanda (padded 62 days, `oanda_XAU_USD_<tf>_20260115_20260918`)
|
||||
and both scripts read the same files afterwards.
|
||||
- `backtest/instructions_for_backtest.txt` item 6 ("replays go through the real signal code, never
|
||||
a re-implementation") is waived for this ONE research script; the dated addendum in that file
|
||||
records the exemption and the new obligation: any signal change is a parity event, re-prove the
|
||||
MD5 with both scripts before a number from the twin counts as evidence.
|
||||
|
||||
## 2. What shipped
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `backtest/cudaBasedBacktesting.py` (1,028 lines) | Same CLI as the reference (it reuses `_parse_args`), same cache files, same report and CSV writers. Reuses by import everything that is not the replay: the loader and its cleaning, `precompute_indicators` (GPU), the trade resolver (GPU), `_reanchor_entries_to_raw`, `_apply_fixed_rr_tp`, `compute_metrics`, `print_report`, `trades_to_dataframe`, `replay_record.write_run`. Replaces `_replay_span` + the worker pool + `_apply_cooldown` with kernel launches and host builders that turn the kernels' numeric codes into the exact strings (indicator descriptions, warnings, reasons) with the same f-string templates as `engine.py` / `signal_aggregator.py`, then call the real `SignalAggregator._clean_accepted_cp_warnings` and `_drop_stale_spread_gate_vetoes`. No CPU path: `--backend cpu`, `FB2_BACKEND=cpu` or a failed probe exit with a message that points to `gpu_backtest.py`; configurations beyond the compile-time limits (more than 4 confirmation frames, divergence lookback ≥ 64, swing lookback ≥ 256, `cp_lookback` ≥ 128, a big-candle window over 500 bars) are refused the same way. |
|
||||
| `backtest/cuda_signal_kernels.py` (2,914 lines, most of it CUDA C in strings) | K1 `fb2_analyze_tf` (one thread per bar of one timeframe; the nine `GPUTechnicalEngine` overrides plus the live engine code they do not cover: divergence, structure S/R, candlestick, regime, regime-aware RSI, composite, the `len < 30` empty analysis, `ema_mode="2ema"`). K2 `fb2_aggregate` (one thread per primary bar; `analyze_from_dataframes` end to end: the multi-timeframe merge, bonuses, penalties in the post-sort decay order with the 30 cap, exhaustion block, signal, grade, gates 6b–6i, the XAU_ATR / GENERIC / XAU_PB / XAU_CP generators, the NEUTRAL-at-grade-C direction, `_apply_regime_filter_live`, plus the replay's quality / strategy filters, next-open entry and distance-preserving re-anchor). K3 `fb2_features` (the 65 `position_sizing.build_features` columns per accepted trade). K4 cooldown (single thread, the only cross-bar rule). K5 prefix median (the snapshot's `spread_estimate`). K6 recorder bar rows (the 160-column float32 row). The config packer (`PARAMS_F` / `PARAMS_I` name lists → generated `#define`s, so a name cannot drift), the classification table of every config field the kernels do not read, and thin test launches of the device helpers. Compiled with `--fmad=false`; imports only numpy, `gpu_kernels`, `config`, `chart_patterns` (pattern priority order) and `position_sizing` constants. |
|
||||
| `test_all.py` Module 37 (16 tests) | Section 4. |
|
||||
| `backtest/instructions_for_backtest.txt` | The 2026-09-18 CUDA-twin addendum. |
|
||||
|
||||
What stays on the host, on purpose: loading and cleaning the parquet cache; the calendar and
|
||||
session flags (pandas time-zone arithmetic over the whole index: NY weekend, UTC weekday, blocked
|
||||
close hours, and the three clocks the gates use: bar label for gate 6b, bar close for gate 6g3 and
|
||||
XAU_CP, decision time for the replay skip); the text; the writers.
|
||||
|
||||
Rules that keep the twins bit-identical (they cost most of the debugging time and are now pinned
|
||||
by tests): doubles everywhere; Python `min()` / `max()` as argument-order ternaries, never
|
||||
`fmin` / `fmax`; every sum in Python's iteration order; numpy's pairwise summation (8 accumulators,
|
||||
128-blocks) for `np.mean` / `np.nanmean`; Python 3.12's Neumaier-compensated builtin `sum()` for
|
||||
the multi-timeframe score mean; pandas' online rolling mean and EWM recurrences copied from
|
||||
`gpu_kernels.py` for the restarted tails the features use; `round(x, 2)` reproduced exactly with an
|
||||
error-free product (the host additionally re-checks Python's `round()` on the device doubles of
|
||||
every accepted trade); `atr_percentile = count / 50 * 100` (not `count * 2`: 15/50·100 =
|
||||
30.000000000000004 flips "low" to "normal"); the as-of alignment compares int64 nanoseconds.
|
||||
|
||||
## 3. Evidence
|
||||
|
||||
Environment: NVIDIA GeForce RTX 4070 SUPER, CuPy 14.0.1 (`cupy-cuda12x` with the
|
||||
`nvidia-cuda-runtime-cu12` header wheel), NumPy 2.2.6, pandas 3.0.2, Python 3.12, 16 CPUs. Code:
|
||||
this working tree on branch `sql-integration` on top of `bac11d8`. `Backend:` lines verbatim:
|
||||
reference `CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)`, twin
|
||||
`CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)`; both log `GPU cache fallbacks: 0` (the
|
||||
twin's line adds "no per-bar recompute path in cudaBasedBacktesting").
|
||||
|
||||
### 3.1 Acceptance: 6 months, both scripts, both costs
|
||||
|
||||
Harness: `venv/bin/python backtest/gpu_backtest.py --pair XAUUSD --primary-tf <tf> --start
|
||||
2026-03-18 --end 2026-09-18 --spread <sp> --workers 12 --export-csv <out>` for the control and
|
||||
`venv/bin/python backtest/cudaBasedBacktesting.py` with the same arguments (no `--workers`) for the
|
||||
twin; factory config, no overrides.
|
||||
|
||||
| Primary | Cost | Control MD5 | Twin MD5 | Equal | Control wall | Twin wall |
|
||||
|---|---|---|---|---|---|---|
|
||||
| M5 | $0.65 | `86111c36c1a07e543a65f0cd09d56ebf` | `86111c36c1a07e543a65f0cd09d56ebf` | ✓ | 51.8 s (replay 43.8 s) | 1.27 s |
|
||||
| M5 | $0.30 | `e70d025205844ecedc2b9e9c08f38f77` | `e70d025205844ecedc2b9e9c08f38f77` | ✓ | 47.1 s (replay 45.9 s) | 1.65 s |
|
||||
| M15 | $0.65 | `b8c980833f1a32bd59930bd037c75b89` | `b8c980833f1a32bd59930bd037c75b89` | ✓ | 20.4 s (replay 18.7 s) | 1.24 s |
|
||||
| M15 | $0.30 | `e66b5fbdaace1d3fda4a76a0e2d47d69` | `e66b5fbdaace1d3fda4a76a0e2d47d69` | ✓ | 14.8 s (replay 13.5 s) | 1.50 s |
|
||||
|
||||
Because the CSVs are identical the figures below are the same from either script (`NO_DATA` rows
|
||||
excluded; halves split at 2026-06-18). They are reported because the instructions require them,
|
||||
not as a validation: nothing changed, and the window is 6 months (rule 1 needs 12).
|
||||
|
||||
| Primary | Cost | Segment | Trades | Total R | Win rate | PF | Max DD (R) |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| M5 | $0.65 | whole | 283 | +41.67 | 56.5% | 1.42 | 10.1 |
|
||||
| M5 | $0.65 | first half | 146 | +29.58 | 58.9% | 1.64 | 6.6 |
|
||||
| M5 | $0.65 | second half | 137 | +12.09 | 54.0% | 1.23 | 8.3 |
|
||||
| M5 | $0.30 | whole | 283 | +46.38 | 57.2% | 1.48 | 8.4 |
|
||||
| M5 | $0.30 | first half | 146 | +32.07 | 60.3% | 1.71 | 6.4 |
|
||||
| M5 | $0.30 | second half | 137 | +14.30 | 54.0% | 1.28 | 7.4 |
|
||||
| M15 | $0.65 | whole | 238 | +16.35 | 60.1% | 1.19 | 14.9 |
|
||||
| M15 | $0.65 | first half | 120 | +14.09 | 63.3% | 1.37 | 7.6 |
|
||||
| M15 | $0.65 | second half | 118 | +2.26 | 56.8% | 1.05 | 14.4 |
|
||||
| M15 | $0.30 | whole | 238 | +19.20 | 60.9% | 1.23 | 13.8 |
|
||||
| M15 | $0.30 | first half | 120 | +15.37 | 65.0% | 1.41 | 7.1 |
|
||||
| M15 | $0.30 | second half | 118 | +3.82 | 56.8% | 1.08 | 13.8 |
|
||||
|
||||
By generator at $0.65: M5 GENERIC 163 trades +42.4R, XAU_CP 104 −2.7R, XAU_ATR 16 +2.0R (BUY 244
|
||||
+25.7R, SELL 39 +16.0R); M15 GENERIC 149 +26.3R, XAU_ATR 89 −10.0R (BUY 144 +9.7R, SELL 94
|
||||
+6.7R). The M15 second half is the same weak stretch the 2026-09-17 live review saw; no decision
|
||||
follows from a 6-month window.
|
||||
|
||||
### 3.2 The five reference books (kept cache, `--spread 0.65`, factory config)
|
||||
|
||||
| Book | Window | Twin MD5 | Pinned (CLAUDE.md §9) | Twin wall |
|
||||
|---|---|---|---|---|
|
||||
| M5 W2 | 2024-09-05 → 2026-09-05 | `49f8e602fc6ae769713c94ef7b257810` | ✓ | 2.4 s (reference 114.8 s) |
|
||||
| M15 W2 | 2024-09-05 → 2026-09-05 | `b4aa07e33e17e23774c486a6900d673b` | ✓ | 2.3 s (37.2 s) |
|
||||
| M15 OOS | 2024-06-01 → 2025-03-30 | `9fd624fb387c36052122bd1830cf9c32` | ✓ | 1.5 s (15.6 s) |
|
||||
| H1 W3 | 2023-09-05 → 2026-09-05 | `7b848c917dcb5c371ecbb9f8f7292278` | ✓ | 1.3 s (13.0 s) |
|
||||
| H4 W3 | 2023-09-05 → 2026-09-05 | `186e677fbbd53cca735c94094c23ce05` | ✓ | 0.8 s (21.2 s) |
|
||||
|
||||
Reference wall times are the GPU-backend rows of `gpu_backend_2026-09-18.md`. On the twin the
|
||||
decision stage itself is milliseconds (M5 W2: K1 218,088 bars of three timeframes in 0.020 s, K2
|
||||
141,819 primary bars in 0.002 s); what remains is loading and cleaning the parquet files, the
|
||||
indicator precompute (1.3 s on the two-year M5 window), the host formatting of the accepted trades
|
||||
and the CSV write.
|
||||
|
||||
### 3.3 Research flags and knobs (M15 OOS window unless stated; both scripts, `--spread 0.65`)
|
||||
|
||||
| Variant | Rows | Twin MD5 (prefix) | Equal |
|
||||
|---|---|---|---|
|
||||
| `--export-features` (130 columns) | 355 | `6239d197` | ✓ |
|
||||
| `--record DIR --record-bars` | 355 | book `9fd624fb`; `trades` (355 × 138), `indicators` (8,120 × 15), `events` (6,902 × 14), `bars` (12,315 × 172) parquet tables equal via `DataFrame.equals`; manifests differ only in `cli`, `created_at`, `replay_seconds`, `workers` | ✓ |
|
||||
| `--fixed-rr-tp 2.0` | 355 | `95a7d2ab` | ✓ |
|
||||
| `--quality A+,A` | 249 | `1883c346` | ✓ |
|
||||
| `--strategies XAU` | 3 | `c07a4025` | ✓ |
|
||||
| `--cp` (all six chart patterns on M15) | 396 | `7ff51617` | ✓ |
|
||||
| `--big-candle 3.0` | 104 | `76f76f3b` | ✓ |
|
||||
| `--set backtest.regime_filter=true` (H4 ADX gate, a fourth frame) | 179 | `0a65af6b` | ✓ |
|
||||
| `--set technical.ema_mode=2ema` | 368 | `faaa7e86` | ✓ |
|
||||
| M5, `--set xau_strategy.m5_pullback_enabled=true --quality A+,A,B,C,D` | 279 | `1811117a` | ✓ |
|
||||
| `--trail-arm 0.5 --trail-dist 0.9 --cooldown-bars 3 --max-hold-bars 150` | 414 | `9665d753` | ✓ |
|
||||
| H1, `--swing-sr-lookback 80 --warmup-bars 300` | 75 | `f8ef2b9f` | ✓ |
|
||||
|
||||
One variant could not run on either script: H1 with `--confirm-tfs 15min,4h,1d` on the OOS
|
||||
window needs a D1 file padded 1,400 days (2020-08-01) that is not cached, and Oanda returned an
|
||||
empty page for that start; both scripts fail identically in `fetch_range`. Not a parity issue.
|
||||
|
||||
### 3.4 Test suite, three ways (final tree)
|
||||
|
||||
| Run | Result | Wall | Module 37 note |
|
||||
|---|---|---|---|
|
||||
| default (GPU) | `RESULTS: 552 passed, 0 failed, 552 total` | 116 s | every device test exercised |
|
||||
| `CUDA_VISIBLE_DEVICES=""` | `RESULTS: 552 passed, 0 failed, 552 total` | 30 s | `CUDA unavailable here: NumPy CPU fallback (CUDARuntimeError: cudaErrorNoDevice: no CUDA-capable device is detected)` printed; device tests print a notice |
|
||||
| `FB2_BACKEND=cpu` | `RESULTS: 552 passed, 0 failed, 552 total` | 31 s | `NumPy CPU fallback (forced: --backend cpu / FB2_BACKEND=cpu)` printed; the twin's refusal of a cpu-forced backend is exercised for real |
|
||||
|
||||
536 tests before this change, 16 added by Module 37.
|
||||
|
||||
## 4. Module 37 (what each test pins)
|
||||
|
||||
1. `fb2_round2` / `round1` against Python `round(x, 2)` / `round(x, 1)` on 400k doubles including
|
||||
every `.xx5` half in ±300 and signed zero; without CUDA the rule is checked in exact arithmetic.
|
||||
2. Parameter packing round-trips every decision leaf of the four factories (M5 blocked hours with
|
||||
9 and without 21, M15 SELL hours 15/17 and the RSI × 0.5 table, H1 empty SELL quality, M15
|
||||
`tp_floor_rr` 1.0, the six-regime × nine-indicator table, `FB2_EMA_LO` read once with the same
|
||||
fallback); every field of the four config dataclasses is either packed or classified host-only,
|
||||
so a new field fails the suite until it is placed; the `#define`s are generated from the lists.
|
||||
3. K1 against `GPUTechnicalEngine.analyze` on every bar of a 600-bar fixture (doji, hammer,
|
||||
shooting star, engulfing, marubozu, a flat stretch, plateaus) for 5min/15min/1h/4h under their
|
||||
factories, partial-EMA off, `2ema`, classic RSI, disabled indicators and a 150-bar frame: valid
|
||||
flag, composite, regime, volatility, every indicator's value / signal / confidence / description
|
||||
/ effective weight / weighted score, key levels, pattern signals.
|
||||
4. The arithmetic twins: numpy pairwise sums and `Series.mean` for n = 1…600, `nanmean` with NaNs,
|
||||
EWM and rolling means restarted on tails, Wilder ATR on tails, `_ext_vs_sma`, the prefix median
|
||||
with ties, CPython float floor division; and the rolling helper text is the prefix of
|
||||
`gpu_kernels._ROLL_SRC`.
|
||||
5. `align_cut` against the replay's `searchsorted` rule on gapped, late-starting M15/H1/H4/D1
|
||||
frames for M5 and H1 primaries (`cut == 0` included).
|
||||
6. `host_flags` against `_is_xau_blocked_by_calendar` plus the session block bar by bar across
|
||||
both 2026 DST shifts, an H4 primary (label hour ≠ close hour), the day filter, and each filter
|
||||
switched off.
|
||||
7. K2 against `analyze_from_dataframes` on every analysed bar: the M5 factory over a 21-day
|
||||
synthetic fixture (4,046 bars: all six grades, GENERIC and XAU_CP setups, both directions, the
|
||||
mixed `trending` regime with both score signs, 23 distinct warnings); the M5 research knobs
|
||||
(all six chart patterns, big-candle 3.0, the H4 regime filter, `FB2_EMA_LO=0.9`, grades to D;
|
||||
every second bar); the M15 factory (1,182 bars, 62 candidates, XAU_ATR and GENERIC, the SELL,
|
||||
exhaustion-block and spread gates); the H1 factory (shorts blocked) and the H4 factory with a
|
||||
D1 confirmation shorter than 30 bars on a 30-day fixture. Compared per bar: score, confidence,
|
||||
signal, grade, agreement, exhaustion count, momentum fading, penalty total and tags, the full
|
||||
warning and cleaned-warning lists, both reason lists, price, spread estimate, regime,
|
||||
volatility, the setup (strategy, direction, entry, stop, targets, rounded RR, pattern) and the
|
||||
replay's candidate fields.
|
||||
8. The device chart-pattern detector against `detect_chart_pattern` with all six patterns on the
|
||||
fixture and on its mirror image (bear patterns), zero ATR included; the device big-candle
|
||||
detector against `_detect_big_candle` (index and ratio) with injected abnormal bars.
|
||||
9. K4 against `_apply_cooldown` (cooldown 0 / 1 / 5 / 17, empty, a dense run).
|
||||
10. `Trade` rows, the 65 features, the recorder snapshots and the per-bar float32 matrix against a
|
||||
real `_replay_span` over the whole M5 fixture with the pullback path re-enabled (XAU_PB, XAU_CP
|
||||
and GENERIC trades; `dataclasses.asdict` field by field, exact floats, tz-aware entry times, the
|
||||
CSV frame, dtypes and bytes with the research columns).
|
||||
11. End to end: `cudaBasedBacktesting.main` against `gpu_backtest.main` on the fixture written to a
|
||||
temporary cache directory (M15, two weeks): plain book MD5 equal; `--fixed-rr-tp 2.0
|
||||
--export-features --record DIR --record-bars` MD5 equal and the four parquet tables equal.
|
||||
12. Refusals: `--backend cpu`, a cpu-forced backend and a failed probe exit before the cache is
|
||||
touched; each compile-time limit is refused with a message naming the knob and pointing to
|
||||
`gpu_backtest.py`, the largest supported values pass; the parser is the reference script's;
|
||||
`require_gpu_or_exit("gpu")` and the `Backend:` text on a working box.
|
||||
13. Source guards: the script never calls `analyze_from_dataframes` or `_replay_span`, never
|
||||
imports cupy or a live writer; the kernel module stays a leaf (no `engine`,
|
||||
`signal_aggregator`, `gpu_backtest`, `replay_record`, live writer or ML import at module level),
|
||||
pins `options=KERNEL_OPTIONS` (`--fmad=false`) and contains no `fmin(` / `fmax(`; the shared
|
||||
code tables equal the recorder's; the live roots import neither new module.
|
||||
|
||||
On the GPU the module takes about 85 s, almost all of it the CPU references (about 5 ms per bar
|
||||
through the live signal code). Without CUDA the device tests print a notice and the module takes
|
||||
about a second.
|
||||
|
||||
## 5. Honest notes
|
||||
|
||||
- The twin is a second implementation of the decision stage. The MD5 gate proves equality on the
|
||||
windows and flags tried, not in general; a future signal change that is not mirrored in the
|
||||
kernels will make the twin drift silently. That is why the instructions addendum and CLAUDE.md
|
||||
section 7 make every signal change a parity event, and why the reference script stays.
|
||||
- Speed comes from the batch, not from the per-bar work: the decision kernels run one thread per
|
||||
bar over the whole window at once. The remaining wall time (about a second per book) is parquet
|
||||
loading, cleaning, the precompute and the host formatting of the accepted trades.
|
||||
- `--workers` is accepted and ignored (logged); the twin never forks.
|
||||
- Nothing in `engine.py`, `signal_aggregator.py`, `chart_patterns.py`, `position_sizing.py`,
|
||||
`replay_record.py`, `gpu_backtest.py` or `gpu_kernels.py` changed. `git diff --stat` touches
|
||||
`test_all.py`, `CLAUDE.md` and `backtest/instructions_for_backtest.txt` and adds the two new
|
||||
modules and this note.
|
||||
@@ -0,0 +1,149 @@
|
||||
# M15 five-year book on the CUDA research twin — 2026-09-18
|
||||
|
||||
Owner request: a five-year M15 backtest with `backtest/cudaBasedBacktesting.py`. This window goes
|
||||
past the three-year cap the owner set on 2026-09-05 (CLAUDE.md rule 1); it was run as an explicit
|
||||
owner-requested exception. It is a read-out of the shipped configuration on a longer window, not
|
||||
a candidate change: nothing was tuned, nothing ships from it.
|
||||
|
||||
## 1. Runs
|
||||
|
||||
Window 2021-08-01 → 2026-08-04 (the kept 5-year cache, padded files
|
||||
`oanda_XAU_USD_{5min,15min,1h}_20210531_20260804.parquet`; 0 cache fallbacks; 122,489 M15 bars,
|
||||
118,389 replayed; skipped 43,245 `SESSION_WINDOW`, 247 `WEEKEND_FILTER`). Factory config, no
|
||||
overrides.
|
||||
|
||||
```
|
||||
venv/bin/python backtest/cudaBasedBacktesting.py --pair XAUUSD --primary-tf 15min --start 2021-08-01 --end 2026-08-04 --spread 0.65 --export-csv cuda_m15_5yr_0.65.csv
|
||||
venv/bin/python backtest/cudaBasedBacktesting.py --pair XAUUSD --primary-tf 15min --start 2021-08-01 --end 2026-08-04 --spread 0.30 --export-csv cuda_m15_5yr_0.30.csv
|
||||
venv/bin/python backtest/gpu_backtest.py --pair XAUUSD --primary-tf 15min --start 2021-08-01 --end 2026-08-04 --spread 0.65 --workers 12 --export-csv ctrl_m15_5yr_0.65.csv # paired reference
|
||||
```
|
||||
|
||||
| Run | Backend line (verbatim) | Wall | Book MD5 |
|
||||
|---|---|---|---|
|
||||
| twin $0.65 | `CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)` | 6.6 s (K1 520,523 bars 0.046 s, K2 118,389 bars 0.001 s, trade sim 0.80 s) | `d5e432bdb3dc9923e00594125b4867dd` |
|
||||
| twin $0.30 | `CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)` | 5.3 s | `4ac5c0307123557c0740e57f5b91ff72` |
|
||||
| reference $0.65 | `CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)`, `GPU cache fallbacks: 0` | 93.7 s (replay 89.2 s, 12 workers) | `d5e432bdb3dc9923e00594125b4867dd` — identical to the twin's (`cmp` equal) |
|
||||
|
||||
The paired reference run is the parity evidence the 2026-09-18 addendum in
|
||||
`backtest/instructions_for_backtest.txt` requires before a number from the twin is quoted.
|
||||
|
||||
## 2. Results
|
||||
|
||||
1,822 trades at both costs (1,087 `TRAIL`, 666 `SL`, 69 `TIMEOUT`, no `NO_DATA`). Halves split at
|
||||
2024-02-02.
|
||||
|
||||
| Cost | Segment | Trades | Total R | Win rate | PF | Max DD (R) |
|
||||
|---|---|---|---|---|---|---|
|
||||
| $0.65 | whole 2021-08-01 → 2026-08-04 | 1822 | +157.43 | 56.8% | 1.22 | 43.6 |
|
||||
| $0.65 | first half 2021-08-01 → 2024-02-01 | 704 | −9.61 | 52.4% | 0.97 | 43.6 |
|
||||
| $0.65 | second half 2024-02-02 → 2026-08-04 | 1118 | +167.04 | 59.5% | 1.40 | 17.1 |
|
||||
| $0.30 | whole | 1822 | +196.77 | 57.7% | 1.28 | 36.3 |
|
||||
| $0.30 | first half | 704 | +9.38 | 54.0% | 1.03 | 36.3 |
|
||||
| $0.30 | second half | 1118 | +187.39 | 60.1% | 1.46 | 14.9 |
|
||||
|
||||
Read against rule 1 for the record: the five-year window does **not** pass it (the first half is
|
||||
negative at $0.65 and the whole-window PF is 1.22). The shipped M15 configuration was validated on
|
||||
2024-09-05 → 2026-09-05 (+146.1R, PF 1.43) and the 2024-06 → 2025-03 out-of-sample window
|
||||
(+60.7R, PF 1.49); the extra 2021–2023 years are where the R is missing, which is the same picture
|
||||
the Oanda re-pin of 2026-08-04 gave ("all timeframes earn only 2024+") and the reason for the
|
||||
three-year cap.
|
||||
|
||||
### Per year
|
||||
|
||||
| Year | Trades | $0.65 R | WR | PF | Max DD | $0.30 R | PF |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 2021 (Aug–Dec) | 97 | +1.17 | 47.4% | 1.03 | 15.5 | +3.85 | 1.10 |
|
||||
| 2022 | 318 | −15.37 | 51.9% | 0.89 | 39.1 | −6.73 | 0.95 |
|
||||
| 2023 | 264 | +10.91 | 55.7% | 1.10 | 15.7 | +17.86 | 1.17 |
|
||||
| 2024 | 397 | +52.86 | 58.7% | 1.36 | 17.1 | +62.53 | 1.44 |
|
||||
| 2025 | 480 | +77.35 | 60.0% | 1.43 | 14.8 | +85.69 | 1.48 |
|
||||
| 2026 (Jan–Aug 4) | 266 | +30.51 | 58.3% | 1.30 | 13.2 | +33.57 | 1.34 |
|
||||
|
||||
The 43.6R drawdown is entirely 2021-Q4 → 2022-Q3 (quarterly R: −13.5, +13.5, −7.6, −22.6); from
|
||||
2024 on the worst quarter is −3.4R (2024-Q2) and the worst since is −8.3R for the five weeks of
|
||||
2026-Q3 in the window.
|
||||
|
||||
### Breakdowns ($0.65)
|
||||
|
||||
| Grade | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| A+ | 36 | +5.48 | 63.9% | 1.41 | 3.3 |
|
||||
| A | 970 | +92.30 | 56.5% | 1.24 | 21.3 |
|
||||
| B | 728 | +41.16 | 56.5% | 1.14 | 31.3 |
|
||||
| C | 88 | +18.49 | 59.1% | 1.55 | 4.8 |
|
||||
|
||||
| Generator | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| GENERIC | 1658 | +153.28 | 57.0% | 1.24 | 43.6 |
|
||||
| XAU_ATR | 164 | +4.15 | 54.3% | 1.06 | 17.0 |
|
||||
|
||||
XAU_ATR produces no trade before 2024 (5 in 2024, 48 in 2025, 111 in 2026). The gold ATR setup
|
||||
is gated by dollar-denominated floors (`min_atr_threshold`, the $8 stop floor, the 30 × $0.30
|
||||
target floor); at 2021–2023 gold prices and volatility they blocked the generator and the GENERIC
|
||||
swing setup took every A-grade signal. The pre-2024 book therefore runs under an effectively
|
||||
different setup mix. Observation only; no proposal.
|
||||
|
||||
| Direction | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| BUY | 1212 | +147.91 | 58.3% | 1.32 | 48.3 |
|
||||
| SELL | 610 | +9.52 | 53.8% | 1.04 | 36.2 |
|
||||
|
||||
Direction by year (trades / R): BUY 2021 58 / −9.8, 2022 215 / −28.9, 2023 166 / +10.9, 2024
|
||||
273 / +56.8, 2025 344 / +95.2, 2026 156 / +23.7; SELL 2021 39 / +10.9, 2022 103 / +13.6, 2023
|
||||
98 / 0.0, 2024 124 / −3.9, 2025 136 / −17.9, 2026 110 / +6.8. Shorts carried the book in the
|
||||
2021–2022 bear phase and lost in the 2024–2025 rally; the parked cycle-2 candidate (block M15
|
||||
grade-A SELLs, `cycle_2026-09-10.md`) is about the recent years only and this table does not
|
||||
change its status.
|
||||
|
||||
| Regime | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| ranging | 168 | +45.30 | 61.3% | 1.73 | 7.7 |
|
||||
| transitioning | 400 | +51.25 | 58.2% | 1.33 | 7.0 |
|
||||
| trending_up | 788 | +55.43 | 56.9% | 1.18 | 37.3 |
|
||||
| trending_down | 466 | +5.44 | 53.6% | 1.03 | 26.0 |
|
||||
|
||||
(The mixed `trending` regime is blocked on M15, so it has no row.)
|
||||
|
||||
| Volatility | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| low | 193 | +34.75 | 60.1% | 1.48 | 11.2 |
|
||||
| normal | 251 | +41.65 | 61.0% | 1.50 | 20.6 |
|
||||
| high | 393 | +2.77 | 53.7% | 1.02 | 19.2 |
|
||||
| extreme | 985 | +78.27 | 56.2% | 1.20 | 32.0 |
|
||||
|
||||
| Entry hour UTC | Trades | Total R | WR | PF | | Entry hour UTC | Trades | Total R | WR | PF |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 03 | 98 | +8.18 | 60.2% | 1.24 | | 14 | 381 | +24.60 | 55.4% | 1.16 |
|
||||
| 04 | 38 | +4.62 | 55.3% | 1.29 | | 15 | 146 | +17.60 | 57.5% | 1.30 |
|
||||
| 05 | 77 | +16.50 | 61.0% | 1.58 | | 17 | 79 | +27.77 | 68.4% | 2.20 |
|
||||
| 06 | 117 | +27.74 | 66.7% | 1.85 | | 18 | 112 | −6.65 | 50.9% | 0.87 |
|
||||
| 07 | 116 | −9.29 | 50.0% | 0.83 | | 19 | 80 | +10.95 | 62.5% | 1.40 |
|
||||
| 08 | 158 | +1.62 | 53.2% | 1.03 | | 20 | 55 | +11.68 | 61.8% | 1.66 |
|
||||
| 09 | 145 | −7.31 | 49.7% | 0.89 | | 22 | 40 | +12.48 | 67.5% | 2.01 |
|
||||
| 12 | 180 | +16.94 | 54.4% | 1.22 | | | | | | |
|
||||
|
||||
(Entry hour is the open of the bar after the signal bar; the blocked close hours 0, 1, 2, 10, 11,
|
||||
13, 16, 21 leave the gaps.)
|
||||
|
||||
| Weekday | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| Mon | 309 | +25.97 | 57.3% | 1.21 | 15.8 |
|
||||
| Tue | 394 | +40.58 | 60.2% | 1.29 | 17.4 |
|
||||
| Wed | 386 | −18.07 | 50.5% | 0.90 | 25.4 |
|
||||
| Thu | 356 | +50.99 | 56.5% | 1.37 | 22.3 |
|
||||
| Fri | 358 | +47.13 | 58.4% | 1.35 | 19.3 |
|
||||
| Sun | 19 | +10.82 | 78.9% | 3.64 | 2.1 |
|
||||
|
||||
Tail: the top 5% of trades (91) carry +249.8R of the +157.4R total; median R +0.113; largest win
|
||||
+9.07R, largest loss −2.04R; mean hold 70.5 bars. Same fat-right-tail shape as the 3-year cycle-1
|
||||
reading (94–96% of R in the top 5% there).
|
||||
|
||||
## 3. What to do with this
|
||||
|
||||
Nothing. The numbers describe the shipped configuration on a window the owner does not use for
|
||||
decisions; the per-hour, per-weekday and per-direction tables are in-book statistics on a
|
||||
five-year mix of setup gates and must not be turned into filters (rule 1 needs a control-vs-
|
||||
candidate run on the validated windows, and the 2026-08-04 age/stretch and 2026-09-06 sizing
|
||||
verdicts already refute the obvious ones). If the owner wants the pre-2024 weakness explained,
|
||||
the honest next step is a `--record --record-bars` run on this window with the twin (seconds) and
|
||||
a scorecard on the dollar-floor gates by year, not a parameter change.
|
||||
@@ -0,0 +1,156 @@
|
||||
# M5 five-year book on the CUDA research twin — 2026-09-18
|
||||
|
||||
Owner request, the companion of `m15_5yr_cuda_2026-09-18.md`: a five-year M5 backtest with
|
||||
`backtest/cudaBasedBacktesting.py`. Same owner-requested exception to the three-year cap
|
||||
(CLAUDE.md rule 1), same status: a read-out of the shipped configuration on a longer window,
|
||||
nothing tuned, nothing ships.
|
||||
|
||||
## 1. Runs
|
||||
|
||||
Window 2021-08-01 → 2026-08-04 (kept 5-year cache, padded files
|
||||
`oanda_XAU_USD_{5min,15min,1h}_20210531_20260804.parquet`; 0 cache fallbacks; 367,394 M5 bars,
|
||||
355,096 replayed; skipped 139,064 `SESSION_WINDOW`, 247 `WEEKEND_FILTER`). Factory config (M5:
|
||||
hour 9 blocked and 21 open, chart-pattern entries on, pullbacks off), no overrides.
|
||||
|
||||
```
|
||||
venv/bin/python backtest/cudaBasedBacktesting.py --pair XAUUSD --primary-tf 5min --start 2021-08-01 --end 2026-08-04 --spread 0.65 --export-csv cuda_m5_5yr_0.65.csv
|
||||
venv/bin/python backtest/cudaBasedBacktesting.py --pair XAUUSD --primary-tf 5min --start 2021-08-01 --end 2026-08-04 --spread 0.30 --export-csv cuda_m5_5yr_0.30.csv
|
||||
venv/bin/python backtest/gpu_backtest.py --pair XAUUSD --primary-tf 5min --start 2021-08-01 --end 2026-08-04 --spread 0.65 --workers 12 --export-csv ctrl_m5_5yr_0.65.csv # paired reference
|
||||
```
|
||||
|
||||
| Run | Backend line (verbatim) | Wall | Book MD5 |
|
||||
|---|---|---|---|
|
||||
| twin $0.65 | `CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)` | 5.2 s (K1 520,523 bars 0.045 s, K2 355,096 bars 0.004 s, trade sim 0.38 s) | `0429885713ca34653a39754c013db501` |
|
||||
| twin $0.30 | `CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)` | 5.6 s | `87bfdba14090459f715e1ee1f751a6cb` |
|
||||
| reference $0.65 | `CuPy GPU (NVIDIA GeForce RTX 4070 SUPER, cupy 14.0.1)`, `GPU cache fallbacks: 0` | 353.2 s (replay 348.7 s, 12 workers, 1,018 bars/s) | `0429885713ca34653a39754c013db501` — identical to the twin's (`cmp` equal) |
|
||||
|
||||
## 2. Results
|
||||
|
||||
905 trades at both costs (561 `TRAIL`, 290 `SL`, 54 `TIMEOUT`, no `NO_DATA`). Halves split at
|
||||
2024-02-02.
|
||||
|
||||
| Cost | Segment | Trades | Total R | Win rate | PF | Max DD (R) |
|
||||
|---|---|---|---|---|---|---|
|
||||
| $0.65 | whole 2021-08-01 → 2026-08-04 | 905 | +127.08 | 51.4% | 1.39 | 13.5 |
|
||||
| $0.65 | first half 2021-08-01 → 2024-02-01 | 129 | −8.98 | 43.4% | 0.82 | 12.4 |
|
||||
| $0.65 | second half 2024-02-02 → 2026-08-04 | 776 | +136.06 | 52.7% | 1.49 | 9.6 |
|
||||
| $0.30 | whole | 905 | +147.83 | 52.5% | 1.46 | 10.6 |
|
||||
| $0.30 | first half | 129 | −5.20 | 45.0% | 0.89 | 10.6 |
|
||||
| $0.30 | second half | 776 | +153.03 | 53.7% | 1.57 | 8.9 |
|
||||
|
||||
Against rule 1 for the record: the five-year window fails it (first half negative), on a first
|
||||
half of only 129 trades. The shipped M5 configuration was validated on 2024-09-05 → 2026-09-05
|
||||
(+146.1R, PF 1.52, 781 trades) and the 12-month window (+108.8R, PF 1.54).
|
||||
|
||||
### Per year
|
||||
|
||||
| Year | Trades | $0.65 R | WR | PF | Max DD | $0.30 R | PF |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 2021 (Aug–Dec) | 12 | −1.99 | 41.7% | 0.59 | 2.5 | −1.60 | 0.65 |
|
||||
| 2022 | 50 | −2.18 | 44.0% | 0.90 | 11.1 | −0.71 | 0.97 |
|
||||
| 2023 | 61 | −2.75 | 42.6% | 0.87 | 10.3 | −1.00 | 0.95 |
|
||||
| 2024 | 102 | +0.97 | 49.0% | 1.02 | 8.2 | +4.30 | 1.11 |
|
||||
| 2025 | 332 | +40.54 | 50.6% | 1.34 | 9.6 | +48.63 | 1.42 |
|
||||
| 2026 (Jan–Aug 4) | 348 | +92.49 | 55.7% | 1.77 | 9.2 | +98.21 | 1.83 |
|
||||
|
||||
Two things the M15 window did not show as sharply:
|
||||
|
||||
1. **M5 barely trades before 2025.** 225 trades in the first 3.4 years against 680 in the last
|
||||
1.6. Trade generation on M5 is governed by dollar-denominated floors: target 1 must clear
|
||||
30 × $0.30 = $9 (`SPREAD GATE`), the generic and chart-pattern stops are floored at $8, and a
|
||||
chart pattern needs a $9 height. At 2021–2023 prices and volatility an M5 ATR of $1.5–3 puts a
|
||||
1.5 × ATR target far below the floor, so the spread gate vetoed almost every setup and only
|
||||
high-volatility bursts survived. The pre-2025 M5 book is therefore a burst-only sample, not the
|
||||
shipped strategy under normal conditions.
|
||||
2. **One quarter carries the window.** 2026-Q1 made +90.2R of the +127.1R total (71%); 2025 Q1–Q2
|
||||
another +32.3R. Quarterly R outside those three quarters ranges −6.5 … +5.8. This is the
|
||||
fat-right-tail shape of every M5 reading (cycle 1: the top 5% of trades = 94–96% of R; here the
|
||||
top 5%, 45 trades, carry +145.8R of +127.1R) compounded by a single very favourable quarter.
|
||||
|
||||
### Breakdowns ($0.65)
|
||||
|
||||
| Grade | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| A+ | 1 | −0.03 | 0.0% | 0.00 | 0.0 |
|
||||
| A | 247 | +53.72 | 55.1% | 1.69 | 7.2 |
|
||||
| B | 637 | +73.66 | 50.2% | 1.30 | 16.4 |
|
||||
| C | 20 | −0.26 | 45.0% | 0.97 | 3.2 |
|
||||
|
||||
| Generator | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| GENERIC | 447 | +70.36 | 52.8% | 1.46 | 8.1 |
|
||||
| XAU_CP | 427 | +48.52 | 49.9% | 1.29 | 14.4 |
|
||||
| XAU_ATR | 31 | +8.20 | 51.6% | 1.89 | 3.8 |
|
||||
|
||||
Generator by year (trades / R): GENERIC 2021 4 / +0.4, 2022 23 / +4.4, 2023 32 / −2.9, 2024
|
||||
37 / +6.6, 2025 170 / +18.1, 2026 181 / +43.8; XAU_CP 2021 8 / −2.4, 2022 27 / −6.5, 2023
|
||||
29 / +0.2, 2024 65 / −5.7, 2025 159 / +21.6, 2026 139 / +41.3; XAU_ATR appears only in 2025 (3)
|
||||
and 2026 (28), the same dollar-floor effect as on M15. The chart-pattern path is negative through
|
||||
2024 (−14.4R over 129 trades) and positive since (+62.9R over 298); its validation
|
||||
(2026-08-04, +38.9R of the 12-month book) covered the recent regime only.
|
||||
|
||||
| Direction | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| BUY | 808 | +119.68 | 51.4% | 1.41 | 15.2 |
|
||||
| SELL | 97 | +7.40 | 51.5% | 1.21 | 11.4 |
|
||||
|
||||
Shorts are nearly absent before 2025 (9 trades in 2021–2024) under the M5 SELL gates; 2025 32 /
|
||||
−2.8R, 2026 56 / +12.0R.
|
||||
|
||||
| Regime | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| trending_up | 481 | +75.44 | 53.4% | 1.44 | 11.9 |
|
||||
| ranging | 174 | +24.78 | 50.0% | 1.36 | 8.3 |
|
||||
| trending_down | 94 | +6.61 | 51.1% | 1.19 | 12.4 |
|
||||
| transitioning | 84 | +19.59 | 46.4% | 1.69 | 6.9 |
|
||||
| trending (mixed) | 72 | +0.67 | 47.2% | 1.02 | 10.5 |
|
||||
|
||||
| Volatility | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| low | 250 | +62.92 | 51.6% | 1.70 | 12.4 |
|
||||
| normal | 169 | +0.69 | 47.9% | 1.01 | 12.5 |
|
||||
| extreme | 486 | +63.47 | 52.5% | 1.38 | 13.3 |
|
||||
|
||||
(`high` has no row: the M5 chop-transition gate blocks it.)
|
||||
|
||||
| Entry hour UTC | Trades | Total R | WR | PF | | Entry hour UTC | Trades | Total R | WR | PF |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 03 | 52 | +8.49 | 50.0% | 1.41 | | 15 | 121 | +12.81 | 53.7% | 1.30 |
|
||||
| 04 | 31 | +3.22 | 61.3% | 1.27 | | 17 | 47 | −2.61 | 48.9% | 0.86 |
|
||||
| 05 | 50 | +7.15 | 52.0% | 1.32 | | 18 | 51 | +13.17 | 58.8% | 1.76 |
|
||||
| 06 | 56 | +24.31 | 58.9% | 2.47 | | 19 | 51 | +2.96 | 49.0% | 1.18 |
|
||||
| 07 | 52 | +2.50 | 53.8% | 1.14 | | 20 | 43 | +31.45 | 46.5% | 2.80 |
|
||||
| 08 | 63 | −4.88 | 41.3% | 0.81 | | 21 | 12 | +16.02 | 75.0% | 6.16 |
|
||||
| 12 | 69 | +7.01 | 53.6% | 1.28 | | 22 | 37 | +4.14 | 45.9% | 1.32 |
|
||||
| 14 | 170 | +1.37 | 47.6% | 1.02 | | | | | | |
|
||||
|
||||
(Entry hour is the open of the bar after the signal bar; M5 blocks close hours 0, 1, 2, 9, 10,
|
||||
11, 13, 16 and leaves 21 open.)
|
||||
|
||||
| Weekday | Trades | Total R | WR | PF | Max DD |
|
||||
|---|---|---|---|---|---|
|
||||
| Mon | 172 | +20.80 | 55.8% | 1.36 | 12.3 |
|
||||
| Tue | 180 | +24.22 | 52.8% | 1.36 | 13.2 |
|
||||
| Wed | 200 | +30.84 | 48.5% | 1.43 | 11.4 |
|
||||
| Thu | 167 | +5.27 | 48.5% | 1.08 | 13.3 |
|
||||
| Fri | 177 | +41.41 | 50.3% | 1.62 | 9.2 |
|
||||
| Sun | 9 | +4.55 | 77.8% | 3.22 | 1.0 |
|
||||
|
||||
Tail: median R +0.031; largest win +5.53R, largest loss −1.84R; mean hold 76.6 bars.
|
||||
|
||||
## 3. What to do with this
|
||||
|
||||
Nothing ships. Two honest readings for the owner:
|
||||
|
||||
- The five-year M5 figure (+127R) is not "five years of edge"; it is the validated 2024-09 →
|
||||
2026-09 book plus three and a half years in which the dollar floors kept M5 almost out of the
|
||||
market and the few trades that got through lost a little. The three-year cap already keeps
|
||||
decisions inside the period where the gates behave as designed.
|
||||
- The concentration in 2026-Q1 (71% of the window's R) is a fact about the shipped M5 edge, not
|
||||
about the window length. It is consistent with every earlier M5 reading and is the reason the
|
||||
advisory outcomes over a quiet fortnight can look nothing like the book.
|
||||
|
||||
If the owner wants the floor effect measured rather than inferred, a `--record --record-bars`
|
||||
run of this window on the twin (seconds) with a by-year count of `SPREAD GATE` and `LOW ATR
|
||||
FILTER` warnings per analysed bar would settle it. Changing the floors is a rule-1 event in its
|
||||
own right (`round_trip_cost` drives the live target floor, CLAUDE.md rule 1) and is not proposed.
|
||||
Reference in New Issue
Block a user