764dc2adbf
The FOURTH and LAST verb dissolution. `aura mc --strategy r-sma --real`
dissolves into thin sugar over the one campaign executor — a generated
[std::sweep(argmax), std::walk_forward, std::monte_carlo] campaign
document whose terminal monte_carlo stage reproduces the current
`mc_r_bootstrap` grade byte-for-byte (the committed anchor 3fc491a gates
it). Pure sugar, no executor change; mirrors the walkforward dissolution.
The one new load-bearing decision is the seed mapping: the campaign
carries the mc `--seed` (the wf winners are argmax hence
deflation-seed-independent, proven by the shipped walkforward anchor).
Fork C retention as walkforward: the built-in arm splits, run_mc_r_bootstrap
/ run_mc stay inline for the fenced synthetic/non-real paths (until #159).
Spec grounding-check PASS, auto-signed on #210 (2026-07-06).
refs #210
248 lines
13 KiB
Markdown
248 lines
13 KiB
Markdown
# mc R-bootstrap dissolution — Design Spec
|
|
|
|
**Date:** 2026-07-06
|
|
**Status:** Draft — awaiting user spec review
|
|
**Authors:** orchestrator + Claude
|
|
|
|
> Milestone #210, the FOURTH and LAST verb dissolution. Reference issue: Gitea
|
|
> Brummel/Aura #210. Mirrors the just-shipped walkforward dissolution; the only new
|
|
> load-bearing decision is the seed mapping (recorded as a #210 comment,
|
|
> 2026-07-06). Scope reading: decision 2 / Reading A (Fork C retention).
|
|
|
|
## Goal
|
|
|
|
Dissolve `aura mc --strategy r-sma --real`'s inline execution into thin sugar over
|
|
the one campaign executor (`aura_campaign::execute`), completing the verb set
|
|
(sweep, generalize, walkforward, mc). The `dispatch_mc` built-in arm gains an
|
|
r-sma-real branch that runs through a generated, content-addressed campaign
|
|
document with a `[std::sweep(argmax), std::walk_forward, std::monte_carlo]` process,
|
|
reproducing the `{"mc_r_bootstrap":{…}}` line byte-for-byte. The committed anchor
|
|
`mc_r_bootstrap_real_e2e_pins_the_exact_current_grade` (3fc491a) is the acceptance
|
|
gate.
|
|
|
|
`run_mc_r_bootstrap`, `run_mc`, and their helpers are NOT deleted (Fork C, as
|
|
walkforward): the built-in arm splits, and everything the dissolution predicate
|
|
does not match stays inline, fenced until #159 — the synthetic-r-sma path
|
|
(`--strategy r-sma` without `--real`), the synthetic seed-resweep (`aura mc`), and
|
|
the blueprint mc path.
|
|
|
|
## Architecture
|
|
|
|
Pure sugar — NO executor change (`aura-campaign`/`aura-engine`/`aura-research`
|
|
diff-free). The campaign monte_carlo stage already does the identical work:
|
|
`crates/aura-campaign/src/exec.rs:400-446`, `StageBlock::MonteCarlo` after a
|
|
`std::walk_forward` stage produces `StageBootstrap::PooledOos(r_bootstrap(
|
|
&pooled_trade_rs(&fam.reports), resamples, block_len, seed))`, seeded from the
|
|
campaign seed, landing in `StageRealization.bootstrap`
|
|
(`crates/aura-registry/src/lineage.rs:130`). It is reachable in-memory at
|
|
`run.outcome.record.cells[0].stages[].bootstrap`. So the sugar reads that
|
|
`PooledOos(RBootstrap)` and renders the existing `mc_r_bootstrap_json(&RBootstrap)`.
|
|
mc prints ONLY the one summary line (no per-window member lines, unlike walkforward).
|
|
|
|
The inline mc R-bootstrap is `run_mc_r_bootstrap` → `mc_r_bootstrap_report` →
|
|
`walkforward_family` → `pooled_oos_trade_rs` → `r_bootstrap` → `mc_r_bootstrap_json`
|
|
(main.rs ~2106-2133). The dissolution reroutes the same rolling walk-forward +
|
|
pooled-OOS bootstrap through the campaign path.
|
|
|
|
**The seed mapping (the one new load-bearing detail).** The inline path uses TWO
|
|
seeds: a fixed `DEFLATION_SEED` for the per-window wf winner selection, and the mc
|
|
`--seed` for the terminal `r_bootstrap`. The campaign path has ONE `campaign.seed`,
|
|
used for both. This reconciles because the wf winners are **argmax** — the deflation
|
|
seed only annotates the recorded selection provenance, never changes the pick — so
|
|
the winners, and the pooled OOS series, are **seed-independent**. The shipped
|
|
walkforward anchor already proved this (the campaign path at `campaign.seed = 0`
|
|
reproduced the inline `DEFLATION_SEED` winners byte-for-byte). So `translate_mc`
|
|
sets `campaign.seed = the mc --seed`: the pooled series is unchanged (n_trades =
|
|
20681, matching the walkforward multi-grid anchor — mc pools the same series) and
|
|
the `r_bootstrap` at that seed reproduces the inline bootstrap. `block_len` /
|
|
`resamples` map to `StageBlock::MonteCarlo{resamples, block_len}`.
|
|
|
|
Fork A (stop): a single `RiskRegime::Vol{stop_length, stop_k}` (multi-value stop
|
|
refused); fast/slow are the multi-value IS-refit grid.
|
|
|
|
## Concrete code shapes
|
|
|
|
### The user-facing program (unchanged surface, dissolved execution)
|
|
|
|
```console
|
|
$ aura mc --strategy r-sma --real GER40 \
|
|
--from 1735689600000 --to 1767225599000 \
|
|
--fast 3,5 --slow 12,20 --stop-length 14 --stop-k 2.0 \
|
|
--block-len 5 --resamples 1000 --seed 42
|
|
{"mc_r_bootstrap":{"block_len":5,"e_r":{"mean":-0.0025753095301594307,…},
|
|
"n_resamples":1000,"n_trades":20681,"prob_le_zero":0.558}}
|
|
```
|
|
|
|
Byte-identical to today's inline output (the acceptance gate). The audience — a
|
|
researcher stress-testing a candidate's E[R] distribution — runs the identical
|
|
command; the execution becomes a durable, content-addressed campaign document (C18,
|
|
reproducible-from-manifest). It reintroduces no look-ahead or determinism failure:
|
|
the same deterministic engine `r_bootstrap` over the same pooled OOS series (C1),
|
|
no new streamed non-scalar (invariant 4). All mc-specific logic stays in the CLI
|
|
translator/reader (invariant 10: no verb-special engine logic); the World runs it
|
|
through the one campaign path (invariant 12).
|
|
|
|
### Sugar translator — mirror of `translate_walkforward` (verb_sugar.rs), + the monte_carlo stage
|
|
|
|
```rust
|
|
// crates/aura-cli/src/verb_sugar.rs — new, mirror of translate_walkforward
|
|
pub(crate) struct GeneratedMc { pub process: ProcessDoc, pub campaign: CampaignDoc }
|
|
|
|
#[allow(clippy::too_many_arguments)] // #214: bundle deferred until after this last verb
|
|
pub(crate) fn translate_mc(
|
|
fast: &[i64], slow: &[i64],
|
|
stop_length: i64, stop_k: f64,
|
|
metric: &str, // "sqn_normalized" (the wf objective)
|
|
in_sample_ms: u64, out_of_sample_ms: u64, step_ms: u64,
|
|
resamples: u32, block_len: u32, seed: u64, // seed = the mc --seed
|
|
name: &str, symbol: &str, from_ms: i64, to_ms: i64,
|
|
blueprint_canonical: &str,
|
|
) -> Result<GeneratedMc, String> {
|
|
let process = ProcessDoc {
|
|
format_version: FORMAT_VERSION, kind: DocKind::Process,
|
|
name: "mc".to_string(), description: None,
|
|
pipeline: vec![
|
|
StageBlock::Sweep { selection: Some(SweepSelection {
|
|
metric: metric.to_string(), select: SelectRule::Argmax, deflate: false }) },
|
|
StageBlock::WalkForward {
|
|
in_sample_ms, out_of_sample_ms, step_ms,
|
|
mode: WfMode::Rolling, metric: metric.to_string(), select: SelectRule::Argmax },
|
|
StageBlock::MonteCarlo { resamples, block_len },
|
|
],
|
|
};
|
|
// …doc_axes for fast.length / slow.length exactly as translate_walkforward…
|
|
let campaign = CampaignDoc {
|
|
// …instruments: [symbol], windows: [{from_ms,to_ms}], one StrategyEntry with the axes…
|
|
risk: vec![RiskRegime::Vol { length: stop_length, k: stop_k }],
|
|
seed, // <-- the mc --seed (the wf winners are seed-independent)
|
|
// …presentation: persist_taps [], emit ["family_table"]…
|
|
// (process/data/strategies fields identical in shape to translate_walkforward)
|
|
};
|
|
Ok(GeneratedMc { process, campaign })
|
|
}
|
|
```
|
|
|
|
`register_generated_mc` is the verbatim `register_generated_wf` shape (put_process +
|
|
put_campaign).
|
|
|
|
### Sugar runner — read the bootstrap from the record, print one line
|
|
|
|
```rust
|
|
// crates/aura-cli/src/verb_sugar.rs — new, mirror of run_walkforward_sugar
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub(crate) fn run_mc_sugar(/* translate_mc args + env */) -> Result<(), String> {
|
|
let generated = translate_mc(/* … */)?;
|
|
let probe_params = vec![
|
|
("fast.length".to_string(), Scalar::i64(fast[0])),
|
|
("slow.length".to_string(), Scalar::i64(slow[0])),
|
|
];
|
|
validate_before_register(&generated.process, &generated.campaign, blueprint_canonical, &probe_params, env)?;
|
|
let reg = env.registry();
|
|
let (_p, campaign_id) = register_generated_mc(®, &generated)?;
|
|
let run = crate::campaign_run::run_campaign_returning(&campaign_id, env)?;
|
|
|
|
// The single cell's monte_carlo StageRealization carries the pooled-OOS bootstrap.
|
|
let boot = run.outcome.record.cells.iter()
|
|
.flat_map(|c| c.stages.iter())
|
|
.find_map(|s| match &s.bootstrap {
|
|
Some(aura_registry::StageBootstrap::PooledOos(b)) => Some(b),
|
|
_ => None,
|
|
})
|
|
.ok_or("mc produced no pooled-OOS bootstrap")?;
|
|
println!("{}", crate::mc_r_bootstrap_json(boot));
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### Dispatch split — mirror of the walkforward split; `run_mc_r_bootstrap`/`run_mc` STAY
|
|
|
|
```rust
|
|
// crates/aura-cli/src/main.rs — dispatch_mc, the None (built-in) arm
|
|
None => {
|
|
// …existing usage / blueprint+seeds guards, unchanged…
|
|
if a.strategy.as_deref() == Some("r-sma") && a.real.is_some() {
|
|
// r-sma --real: the dissolved sugar path.
|
|
let (name, symbol, fast, slow, stop_length, stop_k, block_len, resamples, seed, from, to) =
|
|
mc_args_from(&a) /* unwrap_or_else: eprintln + exit 2 */;
|
|
let signal = sma_signal(None, None);
|
|
let canonical = blueprint_to_json(&signal).expect("a bare sma_signal serializes");
|
|
let reg = env.registry();
|
|
reg.put_blueprint(&topology_hash(&signal), &canonical) /* exit 1 on err */;
|
|
let (from_ms, to_ms) = resolve_window(&symbol, from, to, env); // full_window unconditionally
|
|
let (is_ms, oos_ms, step_ms) = wf_ms_sizes();
|
|
verb_sugar::run_mc_sugar(&fast, &slow, stop_length, stop_k, "sqn_normalized",
|
|
is_ms, oos_ms, step_ms, resamples, block_len, seed, &name, &symbol, from_ms, to_ms, &canonical, env)
|
|
/* unwrap_or_else: eprintln + exit 1 */;
|
|
return;
|
|
}
|
|
// …EXISTING fenced body, verbatim: the r_path / McArgs::RealR / McArgs::Synthetic
|
|
// logic → run_mc_r_bootstrap / run_mc…
|
|
}
|
|
```
|
|
|
|
`mc_args_from` mirrors `walkforward_args_from` plus `block_len` (default 1),
|
|
`resamples` (default 1000), `seed` (default 1) — single-value stop (multi-value
|
|
refused, Fork A), fast/slow CSV grids, `--real` required. `run_mc_r_bootstrap`,
|
|
`run_mc`, `mc_r_bootstrap_report`, `mc_r_bootstrap_json`, `pooled_oos_trade_rs` are
|
|
all RETAINED (the fenced paths + `mc_r_bootstrap_json` is reused by the sugar).
|
|
|
|
## Components
|
|
|
|
- **`crates/aura-cli/src/verb_sugar.rs`** — `GeneratedMc`, `translate_mc`,
|
|
`register_generated_mc`, `run_mc_sugar`. Reuses `axis_from_values`,
|
|
`validate_before_register`, `run_campaign_returning`.
|
|
- **`crates/aura-cli/src/main.rs`** — `mc_args_from`, the `dispatch_mc` built-in-arm
|
|
split (add the r-sma-real sugar branch; retain the existing body). Reuses
|
|
`wf_ms_sizes`, `resolve_window` (the walkforward helper if it was extracted, else
|
|
the same inline match), `sma_signal`, `mc_r_bootstrap_json`.
|
|
- **No executor change.**
|
|
|
|
## Data flow
|
|
|
|
argv → `mc_args_from` → `dispatch_mc` synthesizes + stores the `sma_signal`
|
|
blueprint, resolves the window, ms roller sizes → `run_mc_sugar` → `translate_mc`
|
|
builds `[sweep(argmax), walk_forward, monte_carlo]` + a campaign (fast/slow axes,
|
|
one instrument, one Vol regime, seed = mc_seed) → `validate_before_register` →
|
|
`register_generated_mc` → `run_campaign_returning` (sweep → wf refit per window →
|
|
monte_carlo pools the OOS R + bootstraps at the campaign seed) → the sugar reads
|
|
`StageBootstrap::PooledOos(RBootstrap)` from the record's stages and prints
|
|
`mc_r_bootstrap_json`.
|
|
|
|
## Error handling
|
|
|
|
- Non-`r-sma`, missing knobs, empty `--real`: the fenced arm's / `mc_args_from`'s
|
|
refusals (exit 2), reusing message strings where shared.
|
|
- Multi-value `--stop-length`/`--stop-k`: exit 2 (Fork A), same message shape as
|
|
walkforward.
|
|
- Referential faults: `validate_before_register` refuses before any store write.
|
|
- Data refusal (no archive): the member runner's `no_real_data` path (exit 1).
|
|
|
|
## Testing strategy
|
|
|
|
- **The committed anchor** `mc_r_bootstrap_real_e2e_pins_the_exact_current_grade`
|
|
(3fc491a) is the equivalence gate: it must stay green through the dissolution
|
|
(block_len, n_resamples, n_trades, e_r.mean, e_r.p50, prob_le_zero all exact).
|
|
- **`translate_mc` unit tests** (mirror the walkforward translator tests):
|
|
deterministic content ids; the pipeline is `[sweep(argmax), walk_forward,
|
|
monte_carlo]` with the right roller/mc fields + seed; the regime carries the stop;
|
|
diverging seeds/stops do not collide content ids; `register_generated_mc` resolves.
|
|
- **Dissolution-proof e2e** (mirror `walkforward_dissolves_through_the_campaign_path`):
|
|
a successful real run auto-registers one process + one campaign + one campaign-run,
|
|
and the output is the single `mc_r_bootstrap` line.
|
|
- **Front-end refusal tests**: multi-value stop (exit 2), non-r-sma real handled by
|
|
the fenced path (still works).
|
|
|
|
## Acceptance criteria
|
|
|
|
1. `aura mc --strategy r-sma --real <SYM> …` runs through the campaign path; the
|
|
anchor stays green (the `mc_r_bootstrap` line byte-identical).
|
|
2. `--strategy r-sma --real` no longer flows through `run_mc_r_bootstrap`; it runs
|
|
the sugar path. `run_mc_r_bootstrap` / `run_mc` are RETAINED for the fenced
|
|
branches.
|
|
3. The fenced branches are behaviour-unchanged (Reading A): synthetic-r-sma
|
|
(`--strategy r-sma` no `--real`), the synthetic seed-resweep (`aura mc`), and the
|
|
blueprint mc path all run inline exactly as before.
|
|
4. Multi-value `--stop-length`/`--stop-k` is refused (Fork A).
|
|
5. No executor change (`aura-campaign`/`aura-engine`/`aura-research` diff-free).
|
|
6. Full workspace suite green; `clippy --all-targets -D warnings` clean.
|