# Fieldtest — research deep-dive on the GER40 session-breakout demo — 2026-06-17 **Status:** Draft — awaiting orchestrator triage **Author:** fieldtester (downstream-quant-researcher dispatch) ## Scope The just-shipped runnable demo `crates/aura-ingest/examples/ger40_breakout_real.rs` (+ `examples/shared/breakout_real.rs`, commit `5b5f034`) backtests the 15m Frankfurt session-breakout over real GER40 M1 bars for one month. This dispatch pushes that strategy through the escalating sequence a serious researcher attempts next: (1) scale to a full year and a 7-year span; (2) walk-forward across real history; (3) sweep the structural params (entry/exit bar, bar period); (4) compare signal quality across instruments (GER40 vs FRA40). Worked ONLY from the public surface (design ledger, glossary, the example corpus, `cargo doc`); no engine `crates/*/src` was read. All runs are against the real `/mnt/tickdata/Pepperstone` archive. Binaries path-dep the engine crates (HEAD source), built `--release`. The central, recurring tension: **the demo ships the breakout as a hand-wired raw `FlatGraph`, but every World orchestration family (`sweep` / `walk_forward` / `optimize`) consumes a `Composite` blueprint with `param_space()` / `bootstrap_with_cells`.** A `FlatGraph` has no param space, so to take *this exact strategy* into the World a researcher must first re-author it as a `Composite` — re-typing the 13-node wiring by hand. The demo provides no such blueprint. ## Examples All fixtures live under `fieldtests/research-breakout-deepdive/` (a standalone C16-style consumer crate). Output transcripts are the sibling `out_*.txt`. ### t1_scale.rs — scale the breakout over long horizons - Reuses the shipped `shared/breakout_real.rs` wiring verbatim (via `#[path]`), changing only the window: month → year → 7 years; adds an RSS probe + timer. - Fits scope: the first thing a researcher does with a one-month demo is run it over real history; the docs make a load-bearing O(one-chunk) residency claim. - Outcome: built, ran. 2024-09 −45.0 pips / 5 sessions (matches the shipped demo). 2024 full year +203.4 / 62 sessions. 2018..2024 (7y) +520.2 / 401 sessions in 1.12s. **Determinism held** (rerun report JSON bit-identical). **But RSS grew 8→127 MiB on the 7y run.** ### t1b_residency.rs / t1c_residency_scaling.rs — residency attribution - Isolates where the memory goes: drives 4 raw `M1FieldSource`s with no harness/ sinks, then the full harness; and sweeps one source over 1mo→10yr windows. - Fits scope: pin down whether the O(one-chunk) claim holds at the process level. - Outcome: built, ran. `resident_records ≤ 1024` (the docs' asserted predicate) holds at every window length, **yet VmHWM scales linearly with window**: 6 → 15 → 24 → 78 → 173 MiB across 1mo → 6mo → 1y → 4y → 10y of one symbol's Close. ### t2_walkforward.rs — walk-forward the shipped breakout (2018..2024) - Rolls the breakout with `walk_forward` (12mo IS / 3mo OOS / 3mo step, Rolling), stitching the OOS pip curve. - Fits scope: the headline World promise on the headline strategy. - Outcome: built, ran. 24 windows; stitched OOS curve 131508 points ending +618.8 pips. **But the walk-forward is degenerate** — see finding below: the breakout being a raw `FlatGraph` means `space = vec![]`, `chosen_params = vec![]`, and there is NO in-sample optimization (the whole point of WFO). ### t3_sweep.rs / t3b_period_desync.rs — sweep the structural params - Re-authors the breakout as a `Composite`, prints `param_space()`, runs a 3×3 entry_bar×exit_bar grid, and demonstrates the period-coupling failure. - Fits scope: the mission's explicit coupling question (Resample period == Session period; entry/exit as `EqConst` targets). - Outcome: built, ran. Grid works; `entry_bar=3,exit_bar=5` reproduces the shipped −45.0 exactly (re-authoring fidelity). The coupling is **not** expressible, and violating it **fails silently** (period-desync run → 0.0 pips, no error). ### t4_compare.rs — GER40 vs FRA40 signal quality - Reuses the `Composite`, loops over `(symbol, pip_size)`, compares 2024. - Fits scope: the structural "which instrument" axis + the per-asset pip question. - Outcome: built, ran. GER40 +203.4 / 62 sessions vs FRA40 +83.7 / 63 sessions (both pip=1.0 index points). Comparison is honest *only because the researcher hand-supplied matching pip sizes* — there is no instrument registry. ## Findings ### [spec_gap] The shipped breakout is a raw FlatGraph, unreachable by the World API - Example(s): t2_walkforward, t3_sweep, t4_compare (all three had to re-author it). - What happened: `sweep` / `walk_forward` / `optimize` operate on a `Composite` (they need `param_space()` + `bootstrap_with_cells`). The demo's breakout is a hand-wired `FlatGraph` built by `Harness::bootstrap`, which has no param space. To do *any* sweep/optimize/WFO of the shipped strategy I re-typed all 13 nodes + 14 edges + 4 roles as a `Composite` (t3_sweep.rs `breakout_composite`). - Why spec_gap: C20/C21 promise the strategy blueprint is "reused across backtest, sweep, visual workspaces"; the milestone fixtures demonstrate this for SMA-cross. But the *just-shipped, real-data flagship* strategy ships in a form the World cannot consume, and the design ledger does not say which form a shippable strategy should take. A researcher cannot tell from the public surface whether the FlatGraph form is intentional or a gap. The re-reading I picked (re-author as Composite) is plausible; "the demo should have shipped a Composite" is equally plausible — the ledger is silent. - Recommended action: ship the breakout as a reusable `Composite` blueprint (alongside or instead of the FlatGraph), so the demo's strategy is World-ready; or document in the example that the FlatGraph is a substrate-level fixture and a blueprint is the consumer form. ### [bug] Process residency scales with window length despite the O(one-chunk) claim - Example(s): t1_scale, t1b_residency, t1c_residency_scaling. - What happened: the docs (C12 cycle-0041 realization; `tests/streaming_seam.rs` `residency_is_bounded_by_one_chunk_independent_of_window_length`) claim "a 20-year window streams in the same memory as a one-day one". Empirically the aura-level predicate `resident_records ≤ CHUNK_SIZE (1024)` holds at every window length, but VmHWM (OS peak RSS) grows ~linearly: 6→15→24→78→173 MiB for 1mo→6mo→1y→4y→10y of ONE symbol's Close field; the 7-year 4-field breakout hits 127 MiB streaming, 210 MiB after `drain_trace`. The test's predicate measures only the aura `M1FieldSource` ring, not the underlying `data-server` chunk/file retention, so it passes while the process-level promise is false. - Why bug: a load-bearing, documented, test-asserted guarantee ("streaming residency is O(one chunk), independent of window length") is contradicted by the process's own VmHWM. A researcher who budgets RAM on the doc's promise (one chunk ≈ tens of KiB) will be off by 100-1000×; a real multi-symbol sweep over decades would OOM where the docs say it streams flat. - Repro: `cargo run --release --manifest-path fieldtests/research-breakout-deepdive/Cargo.toml --bin t1c_residency_scaling` → VmHWM column grows monotonically with the record count while the `aura-resident<=1024?` column stays `true`. - Recommended action: `debug` — either the `data-server` chunks/decoded buffers are retained across pulls (release them), or the residency claim must be narrowed in the docs/test to "aura-level ring residency" (which is all the predicate proves) and the true per-window process cost documented. ### [spec_gap] walk_forward demands a param-space even with nothing to optimize - Example(s): t2_walkforward. - What happened: `walk_forward(roller, space: Vec, F)` with `WindowRun { chosen_params: Vec, .. }`. For a fixed-param OOS roll of a param-free strategy I passed `space = vec![]` and `chosen_params = vec![]`. It ran, but the result is a walk-forward with no in-sample optimization — the OOS verdict of a strategy that was never tuned per window. The signature gives no hint whether the empty-space idiom is the intended way to express "OOS-only, no optimize", or whether WFO without per-window optimize is even a supported use. - Why spec_gap: I guessed the empty-space reading; "WFO requires a non-empty space and per-window optimize" is an equally plausible reading the ledger does not settle. Combined with the FlatGraph finding, the headline WFO demo on the headline strategy is unreachable in its *optimizing* form without re-authoring. - Recommended action: `ratify` the param-free OOS-roll idiom (document that empty space + empty chosen_params is a valid fixed-strategy walk-forward), or tighten `walk_forward` to make the optimize-per-window contract explicit. ### [bug] Structural deformers (Delay.lag, Resample.period_minutes) leak into param_space - Example(s): t3_sweep (the grid panicked on the un-enumerated `delay.lag` slot). - What happened: the breakout `Composite`'s `param_space()` is `{ resample.period_minutes, delay.lag, entry_bar.target, exit_bar.target }`. `delay.lag` is the breakout's "previous bar's high" — a STRUCTURAL constant (varying it deforms the strategy, the exact C34 deform-vs-tune discriminator), yet `Delay::builder()` ships `lag` as a tunable param. So `GridSpace::new` REQUIRES the researcher to enumerate `delay.lag` (or it errors), and a naive random sweep would silently deform the strategy. C34's `bind()` can remove it, but the node pollutes the space by default. - Why bug: this is the precise failure C34 (cycle 0034) exists to prevent — a structural deformer presented as a tuning knob. The strategy author did not opt into it; the node's default `builder()` forces the pollution onto every consumer. - Repro: `cargo run --release --manifest-path fieldtests/research-breakout-deepdive/Cargo.toml --bin t3_sweep` → `param_space()` lists `delay.lag`; removing its grid axis makes `GridSpace::new` reject the grid. - Recommended action: `debug` / `planner` — decide whether `Delay.lag` (and `Resample.period_minutes`) are tuning params or structural constants; if structural, they should not appear in a default `param_space()` (or the demo should `bind()` them out and the example should model that for consumers). ### [bug] The Resample/Session period coupling is unenforceable and fails silently - Example(s): t3_sweep, t3b_period_desync. - What happened: the breakout's bar period lives in TWO places — `Resample::builder()` exposes `period_minutes` as a tunable param, but `Session::builder(open, tz, period)` BAKES its period (its rustdoc: "NOT scalar params, the declared param list is empty"). So `param_space()` has `resample.period_minutes` but NO session period. Sweeping `resample.period_minutes = 30` while Session stays baked at 15 bootstraps and runs with NO error, but the two clocks desync: `entry_bar.target == 3` no longer means "3rd 30m bar". The desynced run produced **0.0 pips / no error**; a coherent 30m strategy (+63.0 pips) was reachable ONLY by rebuilding the `Composite` with `session_period = 30` (a different blueprint — topology, not a sweep). - Why bug: the public sweep API silently admits a parameter combination that is structurally meaningless, and the failure mode is a quiet 0.0-pip result rather than a diagnostic. The two periods are one logical knob split across a tunable param and a baked factory arg with no coupling mechanism on the public surface. - Repro: `cargo run --release --manifest-path fieldtests/research-breakout-deepdive/Cargo.toml --bin t3b_period_desync` → the resample=30/session=15 row prints `total_pips=0.0` with no error. - Recommended action: `debug` / `planner` — either make Session's period a tunable param so a single swept value feeds both (the C8/C12 way), or provide a coupling/ derived-param mechanism, or have the demo `bind` the period as a structural constant so it cannot be swept into a desync. A silent 0.0 is the worst outcome. ### [spec_gap] No per-instrument pip/metadata registry; cross-asset honesty is manual - Example(s): t4_compare. - What happened: `SimBroker::new(pip_size)` takes a bare `f64`. There is no instrument/pip-metadata registry on the public surface — `aura-ingest` exposes only M1 streaming, and `Aura.toml` (which the glossary says carries "instrument/pip metadata") does not exist (open architectural thread). So the researcher hard-codes pip_size per symbol from outside knowledge (GER40=1.0, FRA40=1.0, EURUSD=0.0001). The GER40-vs-FRA40 comparison is honest only because both happen to be index points and the researcher knew to pass 1.0 for each; nothing flags or prevents comparing GER40 index-pips to EURUSD fx-pips. - Why spec_gap: C10/C20 lean on "per-instrument pip metadata (reference data beside the hot path)" for the sim-optimal broker's currency-free yardstick, but the public surface offers no place to register or look up that metadata, so the yardstick's cross-asset honesty is entirely on the researcher. The ledger names the metadata; the surface does not realize it. (Acknowledged as an open thread, so this is a confirmation that the gap bites at the first real comparison.) - Recommended action: `ratify` / `planner` — sequence the `Aura.toml` instrument/pip schema (or a minimal pip-metadata source) so cross-asset comparison can be honest by construction, not by researcher discipline. ### [working] Determinism, the streaming source, and the sweep/WFO cores are solid - Example(s): t1_scale, t1c, t2, t3, t4. - What happened: every long-horizon run was bit-identical on rerun (C1 held over 7 years / 148k bars). The lazy `M1FieldSource` streamed a 7-year, 4-field, 8.6M- record window in 1.12s with no errors. The `Composite` re-authoring reproduced the shipped FlatGraph's numbers EXACTLY (`entry=3,exit=5` → −45.0; 2024 full year → +203.4 / 62 sessions, identical in t1 and t4) — strong evidence the bootstrap/compile and `bootstrap_with_cells` paths are behaviour-preserving (C23). `sweep` parallelized the 9-point grid and `walk_forward` produced 24 windows + a correctly stitched, timestamp-monotone OOS curve. The named-param surface (`param_space`, `named_params`, `GridSpace::new` validating axes against the space) is clean and self-describing where the strategy IS a Composite. - Why working: these are the load-bearing guarantees the whole World rests on, and they held under real multi-year load on the first or second try. Worth recording so they are protected from drift. - Recommended action: `carry-on`. ### [friction] ns↔ms conversion is one-directional; residency probe not on the trait - Example(s): t2_walkforward, t1b_residency. - What happened: (a) `WindowBounds`/`RunManifest.window` are epoch-ns but `M1FieldSource::open` wants Unix-ms; `aura-ingest` exports `unix_ms_to_epoch_ns` but NOT its inverse, so every real-data walk-forward hand-divides `ts.0 / 1_000_000` (the milestone `mw_3` fixture hit the identical seam). (b) `resident_records()` is an inherent method on the concrete `M1FieldSource`, not on the public `Source` trait, so a consumer driving `Box` (as `run` does) cannot read residency — I had to drop to the concrete type to probe it. - Why friction: both tasks completed, but the surface forced a verbose dance the ledger's "producer-supplied window" / "residency is O(one chunk)" framing implied would be cleaner. - Recommended action: `plan` — add `epoch_ns_to_unix_ms` to `aura-ingest` (mirror of the existing forward fn), and consider surfacing residency on the `Source` trait (or document that it is concrete-only). ## Recommendation summary | Finding | Action | |---|---| | spec_gap — breakout ships as FlatGraph, not a World-consumable Composite | `planner` (ship a Composite blueprint) | | bug — process residency scales with window despite O(one-chunk) claim | `debug` | | spec_gap — walk_forward empty-space idiom undefined for param-free strategy | `ratify` | | bug — Delay.lag / Resample.period_minutes leak into default param_space | `debug` / `planner` | | bug — Resample/Session period coupling unenforceable, desyncs silently to 0 pips | `debug` / `planner` | | spec_gap — no per-instrument pip/metadata registry; cross-asset honesty manual | `ratify` / `planner` | | working — C1 determinism, streaming source, sweep/WFO cores, re-author fidelity | `carry-on` | | friction — one-directional ns/ms conversion; residency not on Source trait | `plan` |