diff --git a/docs/specs/fieldtest-milestone-the-world-orchestration-families.md b/docs/specs/fieldtest-milestone-the-world-orchestration-families.md new file mode 100644 index 0000000..6c4a1dd --- /dev/null +++ b/docs/specs/fieldtest-milestone-the-world-orchestration-families.md @@ -0,0 +1,197 @@ +# Fieldtest — milestone "The World, part II — orchestration families" — 2026-06-17 + +**Status:** Draft — awaiting orchestrator triage +**Author:** fieldtester (dispatched by fieldtest skill, milestone-close gate) + +## Scope + +Milestone-18 promised the four C12 orchestration-family axes — parameter-sweep +(grid + random), optimize/argmax, walk-forward, Monte-Carlo — delivered +end-to-end through aura's public Rust API and CLI, resting on three substrate +guarantees: a lazy `Source` ingestion seam (synthetic + real M1), seed-as-input +reproducibility, and registry lineage that persists each family as a queryable +unit (`aura runs families` / `aura runs family [rank ]`). This +field test composes those axes top-down from the promise into four realistic +downstream tasks, run from a standalone consumer crate (path-deps only; public +interface = design ledger + glossary + specs 0041–0049 + `cargo doc` rustdoc; no +`crates/*/src` was read) plus the `aura` CLI driven as a researcher would. Every +binary was rebuilt from HEAD (`8b22fa6`) and run; the real-data scenario streamed +the local EURUSD M1 archive. + +## Examples + +### fieldtests/milestone-the-world-orchestration-families/mw_3_walkforward_real.rs — real-data walk-forward (THE HEADLINE) +- Walk-forward over real EURUSD M1 close bars: roll 4-month IS / 1-month OOS over + a 9-month span; per window, grid-sweep + `optimize` the in-sample slice by + total_pips, run the chosen params out-of-sample, stitch the OOS months into one + continuous pip curve, then persist the per-window OOS reports as a WalkForward + lineage family. +- Fits the milestone: composes axes 1 (in-sample grid) + 2 (optimize) + 3 + (walk-forward) over the **real-data** Source seam (`M1FieldSource`) plus + registry lineage — exactly the promise's headline scenario. +- Outcome: built ✓, ran ✓, matched expected ✓. 5 windows; per-window optimize + reacted to the data (window 4 chose slow=50, the others slow=20); stitched curve + 145 200 points, final cumulative −2.2884 pips; C2 no-look-ahead held on every + window; lineage round-tripped 5 members as `eurusd-wfo-0`. + +### fieldtests/milestone-the-world-orchestration-families/mw_2_monte_carlo_robustness.rs — Monte-Carlo robustness over a seed set +- `monte_carlo` over 128 seeds with a fixed strategy and `SyntheticSpec::source(seed)`; + read the aggregate metric distribution (mean + p5/p50/p95), confirm same seed + set reproduces the whole `McFamily`, persist + round-trip the MC family. +- Fits the milestone: axis 4 + seed-as-input + lineage. +- Outcome: built ✓, ran ✓, matched expected ✓. 128 distinct draws (seed truly + perturbs), ordered quantiles, byte-identical re-run (`family == family2`, C1), + 128-member MonteCarlo lineage family. + +### fieldtests/milestone-the-world-orchestration-families/mw_1_random_tune_lineage.rs — random tune → optimize → lineage +- Draw 64 seeded random points over continuous `ParamRange`s, run the SAME `sweep` + the grid uses (the "one unified sweep path"), `optimize` the best by total_pips, + persist as a named Sweep family, round-trip, re-run mints a fresh id. +- Fits the milestone: axis 1 (random) + axis 2 + lineage over the synthetic seam. +- Outcome: built ✓, ran ✓, matched expected ✓ (after a fixture cleanup fix — see + finding F8). 33 distinct total_pips over 64 points (family spreads); `optimize` + == manual argmax; 64-member round-trip; re-run → `eurusd-tune-1`. + +### fieldtests/milestone-the-world-orchestration-families/out_mw_4_cli_lineage.txt — family-lineage CLI surface (CLI transcript) +- The family-lineage CLI driven end-to-end against the repo `runs/` store: + `aura sweep --name`, `aura mc --name`, `aura runs families`, `aura runs family + `, `aura runs family rank total_pips`, unknown-id, and `aura runs list`. +- Fits the milestone: the registry-lineage CLI surface the promise names verbatim. +- Outcome: ran ✓; the family surface (sweep/mc persist, `runs families`, + `runs family`, within-family `rank`) all worked; **`runs list` / `runs rank` + surfaced a bug** (finding F5). + +## Findings + +### [working] Real-data walk-forward composes all four axes end-to-end +- Example: mw_3. +- What happened: `walk_forward(roller, space, |bounds| { sweep+optimize in-sample; + run OOS })` over `M1FieldSource`-streamed EURUSD M1 produced 5 windows, a + 145 200-point stitched OOS curve, per-window chosen params that vary with the + data, and a 5-member WalkForward lineage family — correct on first run. +- Why working: this is the milestone's central promise (the differentiator over a + single backtest). The composition a researcher reaches for — roller + in-sample + optimize + held-out OOS + stitch + persist — reads as ordinary code and runs + deterministically over real data with no look-ahead (C2 structural). +- Recommended action: carry-on. Protect with a milestone-level integration test if + one does not already exist. + +### [working] Monte-Carlo over a seed set is reproducible and yields a real distribution +- Example: mw_2. +- What happened: 128 seeds → 128 distinct draws, ordered aggregate quantiles, and + `monte_carlo(...) == monte_carlo(...)` byte-for-byte (C1). Each draw's + `manifest.seed` equals its input seed. +- Why working: seed-as-input (Fork B) plus the MC family delivers the robustness + question directly; reproducibility is checkable in one `assert_eq!`. +- Recommended action: carry-on. + +### [working] Random sweep + optimize + lineage round-trip +- Example: mw_1. +- What happened: the random half of the sweep path drives the same `sweep` core, + `optimize` returns the exact argmax, and the family persists + round-trips as a + 64-member unit; a same-name re-run mints `-1`. +- Why working: confirms the "one unified sweep path" (grid and random both feed + one enumeration-agnostic core) and the lineage round-trip empirically. +- Recommended action: carry-on. + +### [working] The family-lineage CLI surface lists and ranks families as units +- Example: mw_4 transcript. +- What happened: `aura sweep --name mw-grid` and `aura mc --name mw-robust` persist + families; `aura runs families` lists `{mw-grid-0 Sweep 4}` + `{mw-robust-0 + MonteCarlo 3}`; `aura runs family mw-grid-0 rank total_pips` ranks the members + best-first; an unknown id prints nothing and exits 0 (per spec 0045). +- Why working: the CLI half of the lineage promise behaves exactly as the spec's + worked console session shows. +- Recommended action: carry-on. + +### [bug] `aura runs list` / `aura runs rank` cannot read the existing runs.jsonl (typed-params schema break, unmigrated store) +- Example: mw_4 transcript (last two invocations). +- What happened, verbatim: + ``` + $ aura runs list + aura: registry parse error at line 1: expected value at line 1 column 94 + (exit 0) + $ aura runs rank total_pips + aura: registry parse error at line 1: expected value at line 1 column 94 + ``` + `runs/runs.jsonl` line 1 column 94 is the bare float `2.0` in + `["sma_cross.fast",2.0]`. `RunManifest.params` was migrated to typed `Scalar` + (`{"I64":2}` / `{"F64":2.0}`) in cycle 0047 (commit 86746e3, inside the milestone + range), but the persisted flat store was not migrated, so `Registry::load` + fails to deserialize any pre-0047 line. The lineage path is unaffected + (`families.jsonl` is written fresh in the new format — `runs families` / + `runs family` succeeded). +- Why bug: `aura runs list` is documented to list the stored runs; on the + repo's own checked-in store it instead errors out. The store is a real artefact + (`runs/runs.jsonl` is in the tree, albeit under a gitignored dir), so a + researcher who ran `aura sweep`/`aura run` before the 0047 upgrade loses read + access to their run history. Secondary: a parse error prints to stderr but the + process exits **0**, so a script cannot detect the failure by status. +- Repro: `cargo run -p aura-cli -- runs list` against the current `runs/runs.jsonl`. +- Recommended action: debug. RED test: `Registry::load` over a pre-0047 + `Vec<(String, f64)>`-params line. The fix is a migration/back-compat read (accept + both the bare-float and the typed forms) or a one-shot store migration; separately + decide whether a parse error should exit non-zero. + +### [friction] Real-data walk-forward must hand-invert the ms↔ns time convention +- Example: mw_3. +- What happened: `WindowRoller` / `WindowBounds` / `RunManifest.window` are in + epoch-ns, but `M1FieldSource::open(from_ms, to_ms)` wants Unix-ms. aura-ingest + exports `unix_ms_to_epoch_ns` (ms→ns) but **no inverse**, so the closure + hand-writes `ts.0 / 1_000_000` to feed the roller's ns bounds back into the + ms-typed source — re-deriving a normalization the seam already owns. +- Why friction: the task completes, but a real-data walk-forward (the headline + composition) forces the consumer to re-implement the ns→ms half of a convention + the public surface only provides in one direction. A transposed divisor is a + silent wrong-window bug class the seam was built to prevent. +- Recommended action: plan (tidy iteration) — export an `epoch_ns_to_unix_ms` + inverse beside `unix_ms_to_epoch_ns`, or have `M1FieldSource::open` accept a + `Timestamp` window directly so the roller's bounds feed straight in. + +### [friction] DataServer / DEFAULT_DATA_PATH are not re-exported by aura-ingest +- Example: mw_3 (Cargo.toml + `use data_server::{DataServer, DEFAULT_DATA_PATH}`). +- What happened: `M1FieldSource::open` takes `&Arc`, but aura-ingest + re-exports neither `DataServer` nor the data path constant. To construct the + required server, the consumer crate must add the external + `data-server` git dependency directly and import from it — discovered only by + reading the `--real` commit body (`data_server::DEFAULT_DATA_PATH`), not from any + aura-ingest rustdoc or example. +- Why friction: the headline real-data task is reachable only if the consumer + knows to pull and name a transitive external crate whose construction is + undocumented in aura's public surface. The data path `/mnt/tickdata/Pepperstone` + appears only in plan/spec prose, never in the API docs a consumer reads. +- Recommended action: plan — re-export `data_server::DataServer` (and a data-path + accessor) from aura-ingest, or add an aura-ingest constructor + (`M1FieldSource::open_default_archive(symbol, from, to, field)`) so a real-data + source is buildable from aura-ingest alone. + +### [friction] The family store is keyed by the runs DIRECTORY, not the runs path; Registry::open rustdoc is silent +- Example: mw_1 (the round-trip assert first found 2 families instead of 1). +- What happened: `append_family` writes to `path.with_file_name("families.jsonl")` + — a fixed filename in the runs path's directory. Two `Registry::open` calls with + different runs filenames in the same directory therefore share **one** family + store. A first fixture attempt isolated runs by a per-process *filename* + (`mw1-runs-.jsonl`) and got a shared `/tmp/families.jsonl`, so the round-trip + recovered 2 families. The sibling path is documented on `append_family`, but + `Registry::open`'s own rustdoc ("Bind to a JSONL path") does not mention that a + second, directory-keyed store rides along. +- Why friction: the natural "isolate a temp store by unique filename" reflex + silently fails for families; the consumer must know to isolate by directory. The + fixture had to switch to a per-process temp *directory*. +- Recommended action: ratify or tighten the design ledger — note on + `Registry::open` (and/or the C18 ledger entry) that a `Registry` owns two + directory-co-located stores (`runs.jsonl` + `families.jsonl`), so isolation is + per-directory. (Behaviour itself is a reasonable design; the silence is the gap.) + +## Recommendation summary + +| Finding | Class | Action | +|---|---|---| +| F1 real-data walk-forward composes all axes | working | carry-on | +| F2 Monte-Carlo reproducible + distribution | working | carry-on | +| F3 random sweep + optimize + lineage | working | carry-on | +| F4 family-lineage CLI lists/ranks units | working | carry-on | +| F5 `runs list`/`rank` break on pre-0047 store | bug | debug | +| F6 no ns→ms inverse for real-data WFO | friction | plan | +| F7 DataServer not re-exported by aura-ingest | friction | plan | +| F8 family store keyed by directory, undocumented on `open` | friction | ratify / tighten ledger | diff --git a/fieldtests/milestone-the-world-orchestration-families/Cargo.lock b/fieldtests/milestone-the-world-orchestration-families/Cargo.lock new file mode 100644 index 0000000..29c7ed7 --- /dev/null +++ b/fieldtests/milestone-the-world-orchestration-families/Cargo.lock @@ -0,0 +1,985 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "aura-core" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "aura-engine" +version = "0.1.0" +dependencies = [ + "aura-core", + "serde", + "serde_json", +] + +[[package]] +name = "aura-ingest" +version = "0.1.0" +dependencies = [ + "aura-core", + "aura-engine", + "data-server", +] + +[[package]] +name = "aura-registry" +version = "0.1.0" +dependencies = [ + "aura-engine", + "serde", + "serde_json", +] + +[[package]] +name = "aura-std" +version = "0.1.0" +dependencies = [ + "aura-core", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-server" +version = "0.1.0" +source = "git+http://192.168.178.103:3000/Brummel/data-server.git?branch=main#0f5e6655b90eeab0f43a8b0f330b8d1632e80ed4" +dependencies = [ + "chrono", + "regex", + "zip", +] + +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mw18-fieldtest" +version = "0.0.0" +dependencies = [ + "aura-core", + "aura-engine", + "aura-ingest", + "aura-registry", + "aura-std", + "data-server", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "getrandom", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "sha1", + "thiserror", + "time", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/fieldtests/milestone-the-world-orchestration-families/Cargo.toml b/fieldtests/milestone-the-world-orchestration-families/Cargo.toml new file mode 100644 index 0000000..36228bc --- /dev/null +++ b/fieldtests/milestone-the-world-orchestration-families/Cargo.toml @@ -0,0 +1,56 @@ +# Standalone downstream-consumer crate for the milestone-18 fieldtest +# ("The World, part II — orchestration families"). +# +# This is the MILESTONE-close fieldtest: it composes the four orchestration-family +# axes (sweep / optimize / walk-forward / Monte-Carlo) end to end, the way a +# downstream researcher would, exercising the three substrate guarantees (Source +# ingestion seam, seed-as-input, registry lineage) underneath them. +# +# Like the per-cycle fixtures, this is NOT a member of the aura workspace — it +# path-deps the engine crates exactly as a real C16 research project would, then +# drives the public surface ONLY (design ledger + glossary + specs 0041-0049 + +# `cargo doc` rustdoc; no crates/*/src was read). The real-data scenario depends +# on `data-server` directly (the git dep aura-ingest itself uses), because +# `M1FieldSource::open` takes `&Arc` and aura-ingest does not +# re-export DataServer — a downstream consumer must reach for the external crate. +# +# Built/run via, e.g.: +# cargo run --manifest-path fieldtests/milestone-the-world-orchestration-families/Cargo.toml --bin mw_3_walkforward_real +# so HEAD source is always what runs. +# +# Empty [workspace] table: marks this fixture crate as its OWN workspace root. +[workspace] + +[package] +name = "mw18-fieldtest" +version = "0.0.0" +edition = "2024" +publish = false + +[dependencies] +aura-core = { path = "../../crates/aura-core" } +aura-engine = { path = "../../crates/aura-engine" } +aura-registry = { path = "../../crates/aura-registry" } +aura-ingest = { path = "../../crates/aura-ingest" } +aura-std = { path = "../../crates/aura-std" } +# Reached for directly: M1FieldSource::open wants &Arc, and aura-ingest +# re-exports neither DataServer nor DEFAULT_DATA_PATH. A real-data consumer must +# pull the same external git dep aura-ingest uses. +data-server = { git = "http://192.168.178.103:3000/Brummel/data-server.git", branch = "main" } + +[[bin]] +name = "mw_1_random_tune_lineage" +path = "mw_1_random_tune_lineage.rs" + +[[bin]] +name = "mw_2_monte_carlo_robustness" +path = "mw_2_monte_carlo_robustness.rs" + +[[bin]] +name = "mw_3_walkforward_real" +path = "mw_3_walkforward_real.rs" + +# Scenario 4 (the family-lineage CLI surface) is exercised directly through the +# `aura` binary, since the canonical consumer form for a CLI tool is the CLI +# invocation itself, not a Rust program. Its transcript lives in +# out_mw_4_cli_lineage.txt; there is no [[bin]] for it. diff --git a/fieldtests/milestone-the-world-orchestration-families/mw_1_random_tune_lineage.rs b/fieldtests/milestone-the-world-orchestration-families/mw_1_random_tune_lineage.rs new file mode 100644 index 0000000..72172d1 --- /dev/null +++ b/fieldtests/milestone-the-world-orchestration-families/mw_1_random_tune_lineage.rs @@ -0,0 +1,198 @@ +// Milestone-18 fieldtest — scenario 1: RANDOM PARAM-SWEEP, optimized, persisted +// as a lineage family, round-tripped. +// +// Composes: axis 1 (random sample, the "one unified sweep path"), axis 2 +// (optimize / argmax by a named metric), the Source ingestion seam (synthetic +// VecSource), and registry lineage (append_family + group_families round-trip). +// +// The task a downstream researcher does: "tune a 2-SMA-cross strategy by drawing +// 64 seeded random points over continuous ranges, pick the best by total_pips, +// then persist the whole sweep as ONE named family I can re-list and rank later." +// +// PUBLIC INTERFACE ONLY: API discovered from the design ledger (C12), glossary, +// specs 0028/0045/0046/0048/0049, and `cargo doc --workspace --no-deps` rustdoc. +// No crates/*/src was read. + +use std::sync::mpsc; + +use aura_core::{Cell, Firing, Scalar, ScalarKind, Timestamp}; +use aura_engine::{ + f64_field, summarize, sweep, BlueprintNode, Composite, Edge, OutField, ParamRange, RandomSpace, + Role, RunManifest, RunReport, SweepFamily, Target, VecSource, +}; +use aura_registry::{group_families, optimize, sweep_member_reports, FamilyKind, Registry}; +use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub}; + +fn sma_cross() -> Composite { + Composite::new( + "sma_cross", + vec![ + Sma::builder().named("fast").into(), + Sma::builder().named("slow").into(), + Sub::builder().into(), + ], + vec![ + Edge { from: 0, to: 2, slot: 0, from_field: 0 }, + Edge { from: 1, to: 2, slot: 1, from_field: 0 }, + ], + vec![Role { + name: "price".into(), + targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }], + source: None, + }], + vec![OutField { node: 2, field: 0, name: "out".into() }], + ) +} + +fn harness_with_sinks() -> ( + Composite, + mpsc::Receiver<(Timestamp, Vec)>, + mpsc::Receiver<(Timestamp, Vec)>, +) { + let (tx_eq, rx_eq) = mpsc::channel(); + let (tx_ex, rx_ex) = mpsc::channel(); + let bp = Composite::new( + "harness", + vec![ + BlueprintNode::Composite(sma_cross()), + Exposure::builder().into(), + SimBroker::builder(1e-4).into(), + Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq).into(), + Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex).into(), + ], + vec![ + Edge { from: 0, to: 1, slot: 0, from_field: 0 }, + Edge { from: 1, to: 2, slot: 0, from_field: 0 }, + Edge { from: 2, to: 3, slot: 0, from_field: 0 }, + Edge { from: 1, to: 4, slot: 0, from_field: 0 }, + ], + vec![Role { + name: "price".into(), + targets: vec![Target { node: 0, slot: 0 }, Target { node: 2, slot: 1 }], + source: Some(ScalarKind::F64), + }], + vec![], + ); + (bp, rx_eq, rx_ex) +} + +fn synthetic_prices() -> Vec<(Timestamp, Scalar)> { + let prices = [ + 1.00, 1.01, 1.02, 1.03, 1.05, 1.08, 1.11, 1.10, 1.06, 1.04, 1.02, 1.01, 1.03, 1.07, 1.12, + 1.15, 1.13, 1.09, 1.05, 1.02, + ]; + prices + .iter() + .enumerate() + .map(|(i, &p)| (Timestamp(60_000_000_000 * i as i64), Scalar::f64(p))) + .collect() +} + +fn run_one(point: &[Cell], prices: &[(Timestamp, Scalar)]) -> RunReport { + let (bp, rx_eq, rx_ex) = harness_with_sinks(); + let mut h = bp + .bootstrap_with_cells(point) + .expect("random point is kind-checked against the param-space"); + // The Source ingestion seam: run() consumes Vec>, not a Vec. + h.run(vec![Box::new(VecSource::new(prices.to_vec()))]); + drop(h); + let equity = f64_field(&rx_eq.try_iter().collect::>(), 0); + let exposure = f64_field(&rx_ex.try_iter().collect::>(), 0); + RunReport { + manifest: RunManifest { + commit: "fieldtest".into(), + params: vec![], + window: (Timestamp(0), Timestamp(60_000_000_000 * 20)), + seed: 0, + broker: "sim-optimal".into(), + }, + metrics: summarize(&equity, &exposure), + } +} + +fn main() { + let prices = synthetic_prices(); + let space = harness_with_sinks().0.param_space(); + + // axis 1 (random): draw 64 seeded points over continuous ranges (positional, + // parallel to param_space()). RandomBinder would let me name them; here the + // grid-style positional ParamRange is what the headline sweep uses. + let ranges = vec![ + ParamRange::i64(2, 8), // sma_cross.fast.length + ParamRange::i64(10, 30), // sma_cross.slow.length + ParamRange::f64(0.5, 4.0), // exposure.scale + ]; + let seed = 0xA11CE; + let rand_space = + RandomSpace::new(&space, ranges, 64, seed).expect("ranges well-formed for the space"); + println!("axis 1 (random): {} points, seed {:#x}", rand_space.len(), seed); + + // The SAME sweep core the grid uses — `sweep` is generic over impl Space. + let family: SweepFamily = sweep(&rand_space, |point: &[Cell]| run_one(point, &prices)); + assert_eq!(family.points.len(), 64); + + // Did the random sweep actually spread, or collapse? (the "one unified sweep + // path" must produce a real family over the synthetic source). + let distinct: std::collections::BTreeSet = family + .points + .iter() + .map(|p| format!("{:.6}", p.report.metrics.total_pips)) + .collect(); + println!("distinct total_pips across 64 points: {}", distinct.len()); + + // axis 2 (optimize): pick the best point by total_pips — argmax over the family. + let best = optimize(&family, "total_pips").expect("total_pips is a known metric"); + println!( + "axis 2 (optimize): best total_pips = {:.4}", + best.report.metrics.total_pips + ); + // Confirm optimize really returned the argmax of the family. + let manual_max = family + .points + .iter() + .map(|p| p.report.metrics.total_pips) + .fold(f64::NEG_INFINITY, f64::max); + assert_eq!(best.report.metrics.total_pips, manual_max, "optimize == argmax"); + + // Lineage: persist the whole sweep as ONE named family to a fresh temp store. + // NOTE: the family store lives at `/families.jsonl` — keyed by the runs + // path's DIRECTORY, not its filename (spec 0045: `path.with_file_name( + // "families.jsonl")`). So a fresh, per-process *directory* is needed to isolate + // the family store, not just a per-process runs *filename*. (A first attempt + // that varied only the filename shared one /tmp/families.jsonl across runs and + // round-tripped 2 families instead of 1 — recorded as a finding.) + let dir = std::env::temp_dir().join(format!("mw1-{}", std::process::id())); + let _ = std::fs::create_dir_all(&dir); + let tmp = dir.join("runs.jsonl"); + let reg = Registry::open(&tmp); + let id = reg + .append_family("eurusd-tune", FamilyKind::Sweep, &sweep_member_reports(&family)) + .expect("family append"); + println!("lineage: persisted family id = {id}"); + assert_eq!(id, "eurusd-tune-0", "first family of this name is -0"); + + // Round-trip: the stored links re-derive the family as a unit. + let families = group_families(reg.load_family_members().expect("load members")); + assert_eq!(families.len(), 1, "one family round-tripped"); + let f = &families[0]; + println!( + "round-trip: family {} kind={:?} members={}", + f.id, + f.kind, + f.members.len() + ); + assert_eq!(f.id, "eurusd-tune-0"); + assert_eq!(f.kind, FamilyKind::Sweep); + assert_eq!(f.members.len(), 64, "all 64 sweep points are members"); + + // A second run of the same name mints a fresh family id (a new experiment run). + let id2 = reg + .append_family("eurusd-tune", FamilyKind::Sweep, &sweep_member_reports(&family)) + .expect("second family append"); + println!("re-run mints fresh id = {id2}"); + assert_eq!(id2, "eurusd-tune-1"); + + let _ = std::fs::remove_dir_all(&dir); // removes runs.jsonl + the sibling families.jsonl + + println!("\nOK: random tune -> optimize -> persisted+round-tripped lineage family."); +} diff --git a/fieldtests/milestone-the-world-orchestration-families/mw_2_monte_carlo_robustness.rs b/fieldtests/milestone-the-world-orchestration-families/mw_2_monte_carlo_robustness.rs new file mode 100644 index 0000000..feae938 --- /dev/null +++ b/fieldtests/milestone-the-world-orchestration-families/mw_2_monte_carlo_robustness.rs @@ -0,0 +1,163 @@ +// Milestone-18 fieldtest — scenario 2: MONTE-CARLO robustness over a seed set. +// +// Composes: axis 4 (Monte-Carlo = sweep over seeds), seed-as-input (SyntheticSpec +// ::source(seed) — the stream is fully seed-determined), the aggregate metric +// distribution (mean + quantiles), reproducibility (same seed set -> same family), +// and registry lineage (persist the MC family + round-trip). +// +// The task a downstream researcher does: "how robust is this fixed strategy's +// tail across 128 stochastic market realizations? give me the median and p95 of +// the metrics, and persist the run so I can re-list it later." +// +// PUBLIC INTERFACE ONLY: API from the design ledger (C12 axis 4), glossary, specs +// 0042/0043/0045, and `cargo doc` rustdoc. No crates/*/src was read. + +use std::sync::mpsc; + +use aura_core::{Firing, Scalar, ScalarKind, Timestamp}; +use aura_engine::{ + f64_field, monte_carlo, summarize, BlueprintNode, Composite, Edge, McFamily, OutField, Role, + RunManifest, RunReport, SyntheticSpec, Target, +}; +use aura_registry::{group_families, mc_member_reports, FamilyKind, Registry}; +use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub}; + +fn sma_cross() -> Composite { + Composite::new( + "sma_cross", + vec![ + Sma::builder().named("fast").into(), + Sma::builder().named("slow").into(), + Sub::builder().into(), + ], + vec![ + Edge { from: 0, to: 2, slot: 0, from_field: 0 }, + Edge { from: 1, to: 2, slot: 1, from_field: 0 }, + ], + vec![Role { + name: "price".into(), + targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }], + source: None, + }], + vec![OutField { node: 2, field: 0, name: "out".into() }], + ) +} + +fn harness_with_sinks() -> ( + Composite, + mpsc::Receiver<(Timestamp, Vec)>, + mpsc::Receiver<(Timestamp, Vec)>, +) { + let (tx_eq, rx_eq) = mpsc::channel(); + let (tx_ex, rx_ex) = mpsc::channel(); + let bp = Composite::new( + "harness", + vec![ + BlueprintNode::Composite(sma_cross()), + Exposure::builder().into(), + SimBroker::builder(1e-4).into(), + Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq).into(), + Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex).into(), + ], + vec![ + Edge { from: 0, to: 1, slot: 0, from_field: 0 }, + Edge { from: 1, to: 2, slot: 0, from_field: 0 }, + Edge { from: 2, to: 3, slot: 0, from_field: 0 }, + Edge { from: 1, to: 4, slot: 0, from_field: 0 }, + ], + vec![Role { + name: "price".into(), + targets: vec![Target { node: 0, slot: 0 }, Target { node: 2, slot: 1 }], + source: Some(ScalarKind::F64), + }], + vec![], + ); + (bp, rx_eq, rx_ex) +} + +// The per-draw closure: the SEED perturbs the synthetic market (seed-as-input, +// Fork B — the seed lives at the data-generation edge, never inside the engine). +// The strategy (base_point) is fixed across draws. +fn run_draw(seed: u64, point: &[Scalar]) -> RunReport { + let (bp, rx_eq, rx_ex) = harness_with_sinks(); + let mut h = bp + .bootstrap_with_params(point.to_vec()) + .expect("base point kind-checked against param_space"); + let spec = SyntheticSpec { start: 1.0, len: 256, step: 60_000 }; + let window = (Timestamp(1), Timestamp((spec.len as i64 - 1) * spec.step + 1)); + // The seed -> Source construction lives inside the closure body (#71 firewall): + // a seeded *Source*, never a materialized Vec crossing the monte_carlo boundary. + h.run(vec![Box::new(spec.source(seed))]); + drop(h); + let equity = f64_field(&rx_eq.try_iter().collect::>(), 0); + let exposure = f64_field(&rx_ex.try_iter().collect::>(), 0); + RunReport { + manifest: RunManifest { + commit: "fieldtest".into(), + params: vec![], + window, + seed, // <- the seed that drove this draw is recorded (C12) + broker: "sim-optimal(pip_size=0.0001)".into(), + }, + metrics: summarize(&equity, &exposure), + } +} + +fn main() { + // The strategy is fixed; only the market realization (the seed) varies. + let base_point = vec![Scalar::I64(3), Scalar::I64(8), Scalar::F64(1.0)]; + let seeds: Vec = (0..128).collect(); + + // axis 4: monte_carlo varies the SEED across the seed set. + let family: McFamily = monte_carlo(&base_point, &seeds, run_draw); + assert_eq!(family.draws.len(), 128, "one draw per seed"); + + // Each draw recorded the seed that drove it, in seed-INPUT order. + for (i, d) in family.draws.iter().enumerate() { + assert_eq!(d.seed, i as u64, "draw order == seed input order"); + assert_eq!(d.report.manifest.seed, i as u64, "seed captured in manifest"); + } + println!("axis 4: {} draws, seeds {}..{}", family.draws.len(), 0, 127); + + // The aggregate metric distribution — the robustness answer (mean + quantiles). + let tp = &family.aggregate.total_pips; + let dd = &family.aggregate.max_drawdown; + println!("total_pips: mean {:.4} p5 {:.4} p50 {:.4} p95 {:.4}", tp.mean, tp.p5, tp.p50, tp.p95); + println!("max_drawdown: mean {:.4} p50 {:.4} p95 (tail) {:.4}", dd.mean, dd.p50, dd.p95); + // The aggregate is a pure reduction over the retained draws (recomputable): + assert!(tp.p5 <= tp.p50 && tp.p50 <= tp.p95, "quantiles ordered"); + // Custom statistics always read the raw draws directly: + let worst_dd = family.draws.iter().map(|d| d.report.metrics.max_drawdown).fold(0.0_f64, f64::max); + println!("worst single-draw drawdown (raw draws): {worst_dd:.4}"); + + // The family must not collapse: distinct seeds perturb the run. + let distinct: std::collections::BTreeSet = family + .draws + .iter() + .map(|d| format!("{:.6}", d.report.metrics.total_pips)) + .collect(); + println!("distinct total_pips across 128 draws: {}", distinct.len()); + assert!(distinct.len() > 1, "a Monte-Carlo over distinct seeds must spread"); + + // Reproducibility (C1): same seed set -> same McFamily, byte-for-byte. + let family2: McFamily = monte_carlo(&base_point, &seeds, run_draw); + assert_eq!(family, family2, "same seed set reproduces the whole family (C1)"); + println!("reproducibility: re-run produced an identical McFamily (C1) ✓"); + + // Lineage: persist the MC family + round-trip. + let tmp = std::env::temp_dir().join(format!("mw2-runs-{}.jsonl", std::process::id())); + let _ = std::fs::remove_file(&tmp); + let _ = std::fs::remove_file(tmp.with_file_name("families.jsonl")); + let reg = Registry::open(&tmp); + let id = reg + .append_family("eurusd-robustness", FamilyKind::MonteCarlo, &mc_member_reports(&family)) + .expect("mc family append"); + let families = group_families(reg.load_family_members().expect("load")); + let f = families.iter().find(|f| f.id == id).expect("our family is present"); + println!("lineage: family {} kind={:?} members={}", f.id, f.kind, f.members.len()); + assert_eq!(f.kind, FamilyKind::MonteCarlo); + assert_eq!(f.members.len(), 128); + let _ = std::fs::remove_file(&tmp); + + println!("\nOK: monte-carlo robustness -> aggregate distribution -> reproducible -> persisted lineage."); +} diff --git a/fieldtests/milestone-the-world-orchestration-families/mw_3_walkforward_real.rs b/fieldtests/milestone-the-world-orchestration-families/mw_3_walkforward_real.rs new file mode 100644 index 0000000..563602c --- /dev/null +++ b/fieldtests/milestone-the-world-orchestration-families/mw_3_walkforward_real.rs @@ -0,0 +1,245 @@ +// Milestone-18 fieldtest — scenario 3 (THE HEADLINE): walk-forward over a real +// M1 data window, with per-window grid-optimize and a stitched OOS equity curve. +// +// Composes: axis 3 (walk-forward = vary the data window), axis 1 (grid sweep +// in-sample), axis 2 (optimize the in-sample grid by total_pips), the Source +// ingestion seam over REAL M1 close bars (M1FieldSource, lazy chunk streaming), +// and registry lineage (persist the per-window OOS reports as a WalkForward +// family). This is the milestone's headline promise end to end. +// +// The task a downstream researcher does: "validate my SMA-cross strategy +// out-of-sample on real EURUSD M1 — roll 6-month in-sample / 1-month OOS windows, +// re-optimize the params in each in-sample slice, run the chosen params on the +// held-out month, and stitch the OOS months into one continuous pip curve." +// +// PUBLIC INTERFACE ONLY: API from the design ledger (C12 axis 3, source seam), +// glossary, specs 0041/0044/0045/0048, and `cargo doc` rustdoc. No crates/*/src +// was read. DataServer is reached for from the external `data-server` crate +// directly, because M1FieldSource::open takes &Arc and aura-ingest +// re-exports neither DataServer nor DEFAULT_DATA_PATH. + +use std::sync::mpsc; +use std::sync::Arc; + +use aura_core::{Cell, Firing, Scalar, ScalarKind, Timestamp}; +use aura_engine::{ + f64_field, summarize, sweep, walk_forward, BlueprintNode, Composite, Edge, GridSpace, OutField, + RollMode, Role, RunManifest, RunReport, Source, SweepFamily, Target, WindowBounds, WindowRoller, + WindowRun, +}; +use aura_ingest::{M1Field, M1FieldSource}; +use aura_registry::{group_families, optimize, walkforward_member_reports, FamilyKind, Registry}; +use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub}; +use data_server::{DataServer, DEFAULT_DATA_PATH}; + +const SYMBOL: &str = "EURUSD"; + +fn sma_cross() -> Composite { + Composite::new( + "sma_cross", + vec![ + Sma::builder().named("fast").into(), + Sma::builder().named("slow").into(), + Sub::builder().into(), + ], + vec![ + Edge { from: 0, to: 2, slot: 0, from_field: 0 }, + Edge { from: 1, to: 2, slot: 1, from_field: 0 }, + ], + vec![Role { + name: "price".into(), + targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }], + source: None, + }], + vec![OutField { node: 2, field: 0, name: "out".into() }], + ) +} + +fn harness_with_sinks() -> ( + Composite, + mpsc::Receiver<(Timestamp, Vec)>, + mpsc::Receiver<(Timestamp, Vec)>, +) { + let (tx_eq, rx_eq) = mpsc::channel(); + let (tx_ex, rx_ex) = mpsc::channel(); + let bp = Composite::new( + "harness", + vec![ + BlueprintNode::Composite(sma_cross()), + Exposure::builder().into(), + SimBroker::builder(1e-4).into(), + Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq).into(), + Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex).into(), + ], + vec![ + Edge { from: 0, to: 1, slot: 0, from_field: 0 }, + Edge { from: 1, to: 2, slot: 0, from_field: 0 }, + Edge { from: 2, to: 3, slot: 0, from_field: 0 }, + Edge { from: 1, to: 4, slot: 0, from_field: 0 }, + ], + vec![Role { + name: "price".into(), + targets: vec![Target { node: 0, slot: 0 }, Target { node: 2, slot: 1 }], + source: Some(ScalarKind::F64), + }], + vec![], + ); + (bp, rx_eq, rx_ex) +} + +// epoch-ns (the roller / WindowBounds unit, == RunManifest.window) back to the +// Unix-ms M1FieldSource::open wants. aura-ingest exports unix_ms_to_epoch_ns but +// NOT its inverse, so a real-data walk-forward must hand-divide here. +fn ns_to_ms(ts: Timestamp) -> i64 { + ts.0 / 1_000_000 +} + +// One run of the harness over a real [from, to] M1 close window. Returns the OOS +// equity stream and the folded RunReport. The bounds-keyed producer the #71 +// firewall wants lives right here in the closure body. +fn run_window_real( + server: &Arc, + params: &[Cell], + from: Timestamp, + to: Timestamp, +) -> (Vec<(Timestamp, f64)>, RunReport) { + let (bp, rx_eq, rx_ex) = harness_with_sinks(); + let mut h = bp + .bootstrap_with_cells(params) + .expect("chosen params kind-checked against param_space"); + let src = M1FieldSource::open(server, SYMBOL, Some(ns_to_ms(from)), Some(ns_to_ms(to)), M1Field::Close) + .expect("real EURUSD window overlaps archived data"); + // Producer-supplied window (C18): the source knows its bounds without + // materializing the stream. + let window = src.bounds().unwrap_or((from, to)); + h.run(vec![Box::new(src)]); + drop(h); + let eq_rows: Vec<_> = rx_eq.try_iter().collect(); + let ex_rows: Vec<_> = rx_ex.try_iter().collect(); + let equity = f64_field(&eq_rows, 0); + let exposure = f64_field(&ex_rows, 0); + let oos_equity: Vec<(Timestamp, f64)> = eq_rows.iter().map(|(t, v)| (*t, v[0].as_f64())).collect(); + let report = RunReport { + manifest: RunManifest { + commit: "fieldtest".into(), + params: vec![], + window, + seed: 0, + broker: "sim-optimal(pip_size=0.0001)".into(), + }, + metrics: summarize(&equity, &exposure), + }; + (oos_equity, report) +} + +fn main() { + let server = Arc::new(DataServer::new(DEFAULT_DATA_PATH)); + if !server.has_symbol(SYMBOL) { + eprintln!("no local {SYMBOL} archive under {DEFAULT_DATA_PATH}; skipping real-data scenario"); + std::process::exit(0); + } + + // A 9-month span of EURUSD starting 2015-01-01. is=4 months, oos=1 month, + // step=1 month, rolling — yields several windows. Units are epoch-ns + // (RunManifest.window / WindowBounds unit). 1 month ~= 30 days of ns. + let day_ns: i64 = 86_400_000 * 1_000_000; + let month_ns: i64 = 30 * day_ns; + let origin = aura_ingest::unix_ms_to_epoch_ns(1_420_070_400_000); // 2015-01-01 UTC + let span = (origin, Timestamp(origin.0 + 9 * month_ns)); + let roller = WindowRoller::new(span, 4 * month_ns, month_ns, month_ns, RollMode::Rolling) + .expect("span fits at least one window"); + + // The in-sample grid we re-optimize per window. + let space = harness_with_sinks().0.param_space(); + println!("param-space slots:"); + for ps in &space { + println!(" {} : {:?}", ps.name, ps.kind); + } + let grid: GridSpace = GridSpace::new( + &space, + vec![ + vec![Scalar::I64(3), Scalar::I64(5)], // sma_cross.fast.length + vec![Scalar::I64(20), Scalar::I64(50)], // sma_cross.slow.length + vec![Scalar::F64(1.0)], // exposure.scale + ], + ) + .expect("grid well-formed for the space"); + + let server_for_closure = Arc::clone(&server); + let space_for_result = space.clone(); + + // axis 3: walk_forward varies the DATA WINDOW. Inside the closure: axis 1 + // (grid sweep on the in-sample slice) + axis 2 (optimize by total_pips) + + // an OOS run on the held-out window. All over REAL M1 close bars. + let result = walk_forward(roller, space_for_result, move |w: WindowBounds| -> WindowRun { + // in-sample: sweep the grid over the in-sample window, pick the best params. + let is_family: SweepFamily = sweep(&grid, |pt: &[Cell]| { + let (_eq, report) = run_window_real(&server_for_closure, pt, w.is.0, w.is.1); + report + }); + let best = optimize(&is_family, "total_pips").expect("total_pips known"); + // out-of-sample: run the chosen params on the held-out window, record equity. + let (oos_equity, oos_report) = + run_window_real(&server_for_closure, &best.params, w.oos.0, w.oos.1); + WindowRun { chosen_params: best.params, oos_equity, oos_report } + }); + + println!("\naxis 3: walk-forward produced {} windows", result.windows.len()); + assert!(!result.windows.is_empty(), "the roll yields at least one window"); + + // C2: no look-ahead — every OOS window starts strictly after its IS window ends. + for (k, w) in result.windows.iter().enumerate() { + let b = w.bounds; + assert!(b.oos.0 > b.is.1, "window {k}: oos.0 > is.1 (no look-ahead, C2)"); + let named = result.named_params(k); + let chosen: Vec = named.iter().map(|(n, v)| format!("{n}={}", scalar_str(v))).collect(); + println!( + " window {k}: is=[{}..{}] oos=[{}..{}] chosen {{ {} }} oos_pips={:.4}", + ns_to_ms(b.is.0), ns_to_ms(b.is.1), ns_to_ms(b.oos.0), ns_to_ms(b.oos.1), + chosen.join(", "), + w.run.oos_report.metrics.total_pips, + ); + } + + // The stitched OOS curve — one continuous pip equity across all OOS months. + let stitched = &result.stitched_oos_equity; + println!( + "\nstitched OOS curve: {} points, final cumulative = {:.4}", + stitched.len(), + stitched.last().map(|(_, v)| *v).unwrap_or(0.0), + ); + assert!(!stitched.is_empty(), "real OOS windows produced equity points"); + // Stitched curve is monotone in timestamp (carried forward across windows). + for w in stitched.windows(2) { + assert!(w[1].0 .0 >= w[0].0 .0, "stitched timestamps are non-decreasing"); + } + + // Lineage: persist the per-window OOS reports as a WalkForward family. + let tmp = std::env::temp_dir().join(format!("mw3-runs-{}.jsonl", std::process::id())); + let _ = std::fs::remove_file(&tmp); + let _ = std::fs::remove_file(tmp.with_file_name("families.jsonl")); + let reg = Registry::open(&tmp); + let id = reg + .append_family("eurusd-wfo", FamilyKind::WalkForward, &walkforward_member_reports(&result)) + .expect("wfo family append"); + let families = group_families(reg.load_family_members().expect("load")); + let f = families.iter().find(|f| f.id == id).expect("our family present"); + println!( + "\nlineage: family {} kind={:?} members={} (one OOS report per window)", + f.id, f.kind, f.members.len() + ); + assert_eq!(f.kind, FamilyKind::WalkForward); + assert_eq!(f.members.len(), result.windows.len()); + let _ = std::fs::remove_file(&tmp); + + println!("\nOK: real-data walk-forward -> per-window grid-optimize -> stitched OOS -> lineage."); +} + +fn scalar_str(s: &Scalar) -> String { + match s { + Scalar::I64(v) => v.to_string(), + Scalar::F64(v) => format!("{v:.2}"), + Scalar::Bool(v) => v.to_string(), + Scalar::Timestamp(t) => t.0.to_string(), + } +} diff --git a/fieldtests/milestone-the-world-orchestration-families/out_mw_1_random_tune_lineage.txt b/fieldtests/milestone-the-world-orchestration-families/out_mw_1_random_tune_lineage.txt new file mode 100644 index 0000000..dc73947 --- /dev/null +++ b/fieldtests/milestone-the-world-orchestration-families/out_mw_1_random_tune_lineage.txt @@ -0,0 +1,8 @@ +axis 1 (random): 64 points, seed 0xa11ce +distinct total_pips across 64 points: 33 +axis 2 (optimize): best total_pips = 0.0000 +lineage: persisted family id = eurusd-tune-0 +round-trip: family eurusd-tune-0 kind=Sweep members=64 +re-run mints fresh id = eurusd-tune-1 + +OK: random tune -> optimize -> persisted+round-tripped lineage family. diff --git a/fieldtests/milestone-the-world-orchestration-families/out_mw_2_monte_carlo_robustness.txt b/fieldtests/milestone-the-world-orchestration-families/out_mw_2_monte_carlo_robustness.txt new file mode 100644 index 0000000..3b08aef --- /dev/null +++ b/fieldtests/milestone-the-world-orchestration-families/out_mw_2_monte_carlo_robustness.txt @@ -0,0 +1,9 @@ +axis 4: 128 draws, seeds 0..127 +total_pips: mean 0.0046 p5 -0.3840 p50 -0.0320 p95 0.4423 +max_drawdown: mean 0.2471 p50 0.2213 p95 (tail) 0.4691 +worst single-draw drawdown (raw draws): 0.7044 +distinct total_pips across 128 draws: 128 +reproducibility: re-run produced an identical McFamily (C1) ✓ +lineage: family eurusd-robustness-0 kind=MonteCarlo members=128 + +OK: monte-carlo robustness -> aggregate distribution -> reproducible -> persisted lineage. diff --git a/fieldtests/milestone-the-world-orchestration-families/out_mw_3_walkforward_real.txt b/fieldtests/milestone-the-world-orchestration-families/out_mw_3_walkforward_real.txt new file mode 100644 index 0000000..ffdbe9e --- /dev/null +++ b/fieldtests/milestone-the-world-orchestration-families/out_mw_3_walkforward_real.txt @@ -0,0 +1,17 @@ +param-space slots: + sma_cross.fast.length : I64 + sma_cross.slow.length : I64 + exposure.scale : F64 + +axis 3: walk-forward produced 5 windows + window 0: is=[1420070400000..1430438399999] oos=[1430438400000..1433030399999] chosen { sma_cross.fast.length=5, sma_cross.slow.length=20, exposure.scale=1.00 } oos_pips=-0.7245 + window 1: is=[1422662400000..1433030399999] oos=[1433030400000..1435622399999] chosen { sma_cross.fast.length=5, sma_cross.slow.length=20, exposure.scale=1.00 } oos_pips=-0.5109 + window 2: is=[1425254400000..1435622399999] oos=[1435622400000..1438214399999] chosen { sma_cross.fast.length=5, sma_cross.slow.length=20, exposure.scale=1.00 } oos_pips=-0.6869 + window 3: is=[1427846400000..1438214399999] oos=[1438214400000..1440806399999] chosen { sma_cross.fast.length=5, sma_cross.slow.length=20, exposure.scale=1.00 } oos_pips=-0.2823 + window 4: is=[1430438400000..1440806399999] oos=[1440806400000..1443398399999] chosen { sma_cross.fast.length=5, sma_cross.slow.length=50, exposure.scale=1.00 } oos_pips=-0.0838 + +stitched OOS curve: 145200 points, final cumulative = -2.2884 + +lineage: family eurusd-wfo-0 kind=WalkForward members=5 (one OOS report per window) + +OK: real-data walk-forward -> per-window grid-optimize -> stitched OOS -> lineage. diff --git a/fieldtests/milestone-the-world-orchestration-families/out_mw_4_cli_lineage.txt b/fieldtests/milestone-the-world-orchestration-families/out_mw_4_cli_lineage.txt new file mode 100644 index 0000000..e6eb781 --- /dev/null +++ b/fieldtests/milestone-the-world-orchestration-families/out_mw_4_cli_lineage.txt @@ -0,0 +1,45 @@ +=== Milestone-18 fieldtest scenario 4 — family-lineage CLI surface, end to end === + (driven against the repo runs/ store; sweep family mw-grid-0 already persisted above) + +$ cargo run -q -p aura-cli -- mc --name mw-robust 2>&1 | tail -4 +{"family_id":"mw-robust-0","report":{"manifest":{"broker":"sim-optimal(pip_size=0.0001)","commit":"8b22fa6c7307e266bc31a2d997d23d19720c8971","params":[["sma_fast",{"I64":2}],["sma_slow",{"I64":4}],["exposure_scale",{"F64":0.5}]],"seed":1,"window":[1,32]},"metrics":{"exposure_sign_flips":6,"max_drawdown":0.05492042518617829,"total_pips":0.033219795137322934}},"seed":1} +{"family_id":"mw-robust-0","report":{"manifest":{"broker":"sim-optimal(pip_size=0.0001)","commit":"8b22fa6c7307e266bc31a2d997d23d19720c8971","params":[["sma_fast",{"I64":2}],["sma_slow",{"I64":4}],["exposure_scale",{"F64":0.5}]],"seed":2,"window":[1,32]},"metrics":{"exposure_sign_flips":10,"max_drawdown":0.050384043775853884,"total_pips":-0.025017958181714973}},"seed":2} +{"family_id":"mw-robust-0","report":{"manifest":{"broker":"sim-optimal(pip_size=0.0001)","commit":"8b22fa6c7307e266bc31a2d997d23d19720c8971","params":[["sma_fast",{"I64":2}],["sma_slow",{"I64":4}],["exposure_scale",{"F64":0.5}]],"seed":3,"window":[1,32]},"metrics":{"exposure_sign_flips":9,"max_drawdown":0.13931444672911716,"total_pips":-0.07233362336360569}},"seed":3} +{"mc_aggregate":{"exposure_sign_flips":{"mean":8.333333333333334,"p25":7.5,"p5":6.3,"p50":9.0,"p75":9.5,"p95":9.9},"max_drawdown":{"mean":0.08153963856371645,"p25":0.05265223448101609,"p5":0.050837681916886326,"p50":0.05492042518617829,"p75":0.09711743595764773,"p95":0.13087504457482327},"total_pips":{"mean":-0.021377262135999237,"p25":-0.04867579077266033,"p5":-0.06760205684541662,"p50":-0.025017958181714973,"p75":0.004100918477803982,"p95":0.027396019805419138}}} +(exit 0) + +$ cargo run -q -p aura-cli -- runs families +{"family_id":"mw-grid-0","kind":"Sweep","members":4} +{"family_id":"mw-robust-0","kind":"MonteCarlo","members":3} +(exit 0) + +$ cargo run -q -p aura-cli -- runs family mw-grid-0 2>&1 | head -2 +{"manifest":{"commit":"8b22fa6c7307e266bc31a2d997d23d19720c8971","params":[["signals.trend.fast.length",{"I64":2}],["signals.trend.slow.length",{"I64":4}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["exposure.scale",{"F64":0.5}]],"window":[1,18],"seed":0,"broker":"sim-optimal(pip_size=0.0001)"},"metrics":{"total_pips":0.26428361277333234,"max_drawdown":0.15423679012344482,"exposure_sign_flips":2}} +{"manifest":{"commit":"8b22fa6c7307e266bc31a2d997d23d19720c8971","params":[["signals.trend.fast.length",{"I64":2}],["signals.trend.slow.length",{"I64":5}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["exposure.scale",{"F64":0.5}]],"window":[1,18],"seed":0,"broker":"sim-optimal(pip_size=0.0001)"},"metrics":{"total_pips":0.1605136127733241,"max_drawdown":0.22647145679012357,"exposure_sign_flips":2}} +(exit 0) + +$ cargo run -q -p aura-cli -- runs family mw-grid-0 rank total_pips 2>&1 | head -4 +{"manifest":{"commit":"8b22fa6c7307e266bc31a2d997d23d19720c8971","params":[["signals.trend.fast.length",{"I64":2}],["signals.trend.slow.length",{"I64":4}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["exposure.scale",{"F64":0.5}]],"window":[1,18],"seed":0,"broker":"sim-optimal(pip_size=0.0001)"},"metrics":{"total_pips":0.26428361277333234,"max_drawdown":0.15423679012344482,"exposure_sign_flips":2}} +{"manifest":{"commit":"8b22fa6c7307e266bc31a2d997d23d19720c8971","params":[["signals.trend.fast.length",{"I64":2}],["signals.trend.slow.length",{"I64":5}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["exposure.scale",{"F64":0.5}]],"window":[1,18],"seed":0,"broker":"sim-optimal(pip_size=0.0001)"},"metrics":{"total_pips":0.1605136127733241,"max_drawdown":0.22647145679012357,"exposure_sign_flips":2}} +{"manifest":{"commit":"8b22fa6c7307e266bc31a2d997d23d19720c8971","params":[["signals.trend.fast.length",{"I64":3}],["signals.trend.slow.length",{"I64":4}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["exposure.scale",{"F64":0.5}]],"window":[1,18],"seed":0,"broker":"sim-optimal(pip_size=0.0001)"},"metrics":{"total_pips":0.10228361277332589,"max_drawdown":0.11347145679010996,"exposure_sign_flips":2}} +{"manifest":{"commit":"8b22fa6c7307e266bc31a2d997d23d19720c8971","params":[["signals.trend.fast.length",{"I64":3}],["signals.trend.slow.length",{"I64":5}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["exposure.scale",{"F64":0.5}]],"window":[1,18],"seed":0,"broker":"sim-optimal(pip_size=0.0001)"},"metrics":{"total_pips":-0.0014863872266822969,"max_drawdown":0.20567145679011695,"exposure_sign_flips":2}} +(exit 0) + +$ cargo run -q -p aura-cli -- runs family no-such-family-99 +(exit 0) + +$ cargo run -q -p aura-cli -- runs list 2>&1 | head -2 +aura: registry parse error at line 1: expected value at line 1 column 94 +(exit 0) + +$ cargo run -q -p aura-cli -- runs rank total_pips # also load()s runs.jsonl +aura: registry parse error at line 1: expected value at line 1 column 94 +(exit 0) + +=== FINDING (bug): aura runs list / runs rank cannot read the pre-existing runs/runs.jsonl === +runs.jsonl line 1 col 94 is the bare-float 2.0 in ["sma_cross.fast",2.0]; the manifest +params field was migrated to typed Scalar ({"I64":2}/{"F64":2.0}) in cycle 0047 (commit +86746e3, inside the milestone range), but the persisted store was not migrated, so load() +now fails to deserialize any pre-0047 line. The lineage path (families.jsonl, written fresh +in the new format) is unaffected — runs families / runs family both succeeded above. +Also: the parse error prints to stderr but the process exits 0.