spec(0051): strategy as a World-consumable Composite blueprint
Milestone spec: ship the GER40 session-breakout as a Composite blueprint so the same strategy runs across backtest / sweep / walk_forward / compare without re-authoring (the #94 fieldtest friction). Records the design decision that the bar period is a structural axis (a construction arg binding both Resample and Session, so a sweep cannot desync them — #96) and that the canonical shippable strategy form is a Composite (FlatGraph is its compiled substrate). Authoring- side; no engine-core change (the existing bind() + GraphBuilder + param_space() layer already suffices). refs #94, #96, #97
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
# 0051 — Strategy as a World-consumable Composite blueprint (GER40 breakout)
|
||||
|
||||
Status: draft (milestone spec)
|
||||
Driver: the GER40 research deep-dive fieldtest
|
||||
(`docs/specs/fieldtest-research-breakout-deepdive.md`) found that the shipped
|
||||
real-data session-breakout (commit `5b5f034`) ships as a hand-wired raw
|
||||
`FlatGraph`, which the World families (`sweep` / `walk_forward` / `optimize` /
|
||||
compare) **cannot consume** — they need a `Composite` with `param_space()` +
|
||||
`bootstrap_with_cells`. Every World task forced a researcher to re-author the
|
||||
13-node graph by hand. This milestone makes the flagship strategy World-ready.
|
||||
|
||||
Tracker: closes #94, #96, #97 (milestone "GER40 breakout as a World-consumable
|
||||
Composite blueprint").
|
||||
|
||||
## 1. Goal & non-goal
|
||||
|
||||
**Goal.** Ship the GER40 session-breakout as a reusable **`Composite` blueprint**
|
||||
so the *same* strategy runs across single backtest, parameter sweep,
|
||||
walk-forward and multi-instrument/timeframe comparison **without re-authoring**.
|
||||
Establish (and record in the ledger) that the canonical *shippable* form of a
|
||||
strategy is a `Composite` blueprint; the `FlatGraph` is its compiled substrate
|
||||
(C11/C23), not the authoring or shipping form.
|
||||
|
||||
**Authoring-side only — no engine-core change.** The recon
|
||||
(`crates/aura-engine/src/blueprint.rs`, `builder.rs`, `node.rs`) confirms the
|
||||
authoring + binding layer already supports everything this needs:
|
||||
`GraphBuilder` / `Composite::new`, `param_space()` derivation, and
|
||||
`PrimitiveBuilder::bind(slot, value)` (removes a param from the space). No new
|
||||
engine mechanism is built here.
|
||||
|
||||
**Non-goal.**
|
||||
- The **generic param fan-out** feature (one blueprint param → multiple node
|
||||
slots; recon §3 verdict: does not exist) is **explicitly not built**. The
|
||||
bar-period is a structural axis (see §2), so fan-out is unnecessary for this
|
||||
strategy. The capability stays an open idea for a future, genuinely-coupled
|
||||
*sweep* param.
|
||||
- The **process-residency bug** (#95) is a separate debug thread (likely
|
||||
data-server chunk retention), not this milestone.
|
||||
- **Per-instrument pip registry** (#22) and the **ns→ms inverse** (#80) stay as
|
||||
filed; the compare demo hand-supplies `pip_size = 1.0` for both index
|
||||
instruments (honest by construction — both quote in index points).
|
||||
- No `aura new` / cdylib / deploy (C9 Part-B still deferred). No new nodes.
|
||||
|
||||
## 2. The design decision: bar-period is a structural axis, not a sweep param
|
||||
|
||||
The bar period lives in two nodes — `Resample` exposes `period_minutes` as a
|
||||
tunable param; `Session` bakes its period (`session.rs:66-76`, `params: vec![]`).
|
||||
Sweeping one while the other stays baked silently desyncs to 0.0 pips (#96).
|
||||
|
||||
**Decision:** the blueprint takes `bar_period_minutes` as a **construction
|
||||
argument** that binds *both* nodes at authoring time —
|
||||
`Resample::builder().bind("period_minutes", bar_period)` **and**
|
||||
`Session::new(open_hour, open_minute, tz, bar_period)` — so the two periods are
|
||||
**locked equal by construction** and a desync is structurally impossible.
|
||||
|
||||
Rationale (substance, not effort):
|
||||
- **C34 (deform vs tune).** A 15m-breakout and a 30m-breakout are *different
|
||||
strategies*, not one strategy tuned — the bar period deforms, it does not
|
||||
tune. Deformers do not belong in `param_space()`.
|
||||
- **C11 (structural axes = experiment matrix; tuning params = sweep).** The
|
||||
timeframe is a matrix axis (instantiate the blueprint per period and compare),
|
||||
the entry/exit bar indices are the sweep *within* each.
|
||||
|
||||
That this also avoids the net-new fan-out feature is a tiebreaker, not the
|
||||
reason.
|
||||
|
||||
`delay.lag` (the "previous bar's high" register — a structural constant, the
|
||||
exact C34 case) is likewise **bound out**: `Delay::builder().bind("lag", 1)`,
|
||||
removing it from the space.
|
||||
|
||||
**Result:** `param_space() == { entry_bar.target, exit_bar.target }` — exactly
|
||||
the two genuine tuning knobs (the `EqConst` targets, named `entry_bar` /
|
||||
`exit_bar`). This resolves #96 (no desync, no `delay.lag` leak) and makes #97's
|
||||
`walk_forward` non-degenerate (a real, non-empty space to optimize per window).
|
||||
|
||||
## 3. The blueprint
|
||||
|
||||
A factory in the example's shared module (C9: pure composition of shipped
|
||||
`aura-std` nodes → lives in `aura/examples`, not a separate crate):
|
||||
|
||||
```
|
||||
ger40_breakout_blueprint(bar_period_minutes, open_hour, open_minute, tz)
|
||||
-> Composite
|
||||
```
|
||||
|
||||
- Internally the 13-node breakout (the proven wiring), authored via
|
||||
`GraphBuilder` (the fluent, name-addressed form; canonical template
|
||||
`crates/aura-engine/src/test_fixtures.rs` SMA-cross).
|
||||
- `bar_period_minutes` binds Resample's period and constructs Session (§2).
|
||||
- `delay.lag` bound to 1 (§2).
|
||||
- Four OHLC **source roles** (open, high, low, close) in the C4 merge order,
|
||||
fanning into Resample's `Barrier(0)` slots.
|
||||
- `param_space() = { entry_bar.target, exit_bar.target }`.
|
||||
- One exposed output (the SimBroker equity) + the recorder taps as today.
|
||||
|
||||
## 4. Deliverables
|
||||
|
||||
1. The blueprint factory (above), replacing the hand-wired `FlatGraph` as the
|
||||
authoring form in `crates/aura-ingest/examples/shared/`.
|
||||
2. **Single backtest** — the existing `ger40_breakout_real` example bootstraps
|
||||
the blueprint (`blueprint.with("entry_bar.target", 3).with("exit_bar.target",
|
||||
5).bootstrap()`), reproducing the shipped FlatGraph's numbers **exactly**
|
||||
(C23 behaviour-preserving: 2024-09 → −45.0; 2024 → +203.4 / 62 sessions).
|
||||
3. **World-family demos on real GER40** (new runnable examples):
|
||||
- `sweep` over the `{entry_bar, exit_bar}` grid, ranked by `total_pips`.
|
||||
- `walk_forward` (IS/OOS roll) — non-degenerate, optimizing entry/exit per
|
||||
window.
|
||||
- `compare` across instruments (GER40 vs FRA40) **and** across the bar-period
|
||||
structural axis (a 15m blueprint vs a 30m blueprint).
|
||||
4. **Gated test** (mirrors `real_bars.rs` skip-guard): asserts
|
||||
(a) C23 — Composite-bootstrap == FlatGraph-bootstrap on the same window,
|
||||
bit-identical; (b) `param_space()` is exactly `{entry_bar.target,
|
||||
exit_bar.target}` (no `delay.lag`, no period); (c) determinism.
|
||||
5. A short **ledger note**: the canonical shippable strategy form is a
|
||||
`Composite` blueprint; `FlatGraph` is its compiled substrate.
|
||||
|
||||
## 5. Acceptance criteria
|
||||
|
||||
- The breakout ships as a `Composite`; `sweep` / `walk_forward` / compare all run
|
||||
on it with **no re-authoring** (the #94 friction is gone).
|
||||
- `param_space()` is exactly `{ entry_bar.target, exit_bar.target }` (#96).
|
||||
- The period **cannot** be swept into a desync (it is not in the space) (#96).
|
||||
- `walk_forward` optimizes a non-empty space per window (#97).
|
||||
- C23: the blueprint reproduces the shipped FlatGraph numbers exactly.
|
||||
- `cargo test --workspace` green; `cargo clippy --workspace --all-targets -D
|
||||
warnings` clean. No change under `crates/*/src` except the optional ledger
|
||||
note (authoring-side milestone).
|
||||
|
||||
## 6. Issue resolution
|
||||
|
||||
| Issue | Resolution |
|
||||
|---|---|
|
||||
| #94 form (FlatGraph not World-consumable) | ship the Composite blueprint (§3, §4.1) |
|
||||
| #96 period desync + `delay.lag` leak | bar-period structural-bound, `delay.lag` bound out (§2) |
|
||||
| #97 walk_forward empty-space | non-degenerate via the `{entry,exit}` space (§2, §4.3) |
|
||||
| #3 param fan-out | **not needed** — stays filed (non-goal §1) |
|
||||
| #95 residency | separate debug thread (non-goal §1) |
|
||||
| #22 pip / #80 ns→ms | adjacent, not in scope (non-goal §1) |
|
||||
Reference in New Issue
Block a user