7aaa3e5d5ce76e54965775bb2858c6360930af92
415 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7aaa3e5d5c |
feat(0068): position-event derive — book first-difference table (#115)
Add `derive_position_events(record, instrument_id) -> Vec<PositionEvent>` to aura-engine (report.rs), the broker-independent Stage-2 audit layer of C10: the first difference of the executed book derived from a PositionManagement dense record. A pure post-run reduction, sibling of summarize_r — same positional, type-erased Scalar read of the 14-column record, no in-graph node (the hot path stays domain-free, C14). A reversal emits Close then the opposite open at one event_ts (close first); a window-end open position emits its open with no synthetic Close (the table records actual executed events — unlike summarize_r, which force-closes for the R metric). instrument_id is a caller-supplied scalar (aura-engine depends only on aura-core; it cannot import InstrumentSpec). Two r_col indices added (DIRECTION=4, SIZE=10), the lockstep guard in stage1_r_e2e.rs extended to name-pin `direction`, and an agreement E2E folds both summarize_r and derive_position_events over one recorded ledger (one Close per closed round-trip; every open closed-or-open-at-end; referential integrity). Scope: the derive only. Fixed-fractional / currency / equity-feedback sizing is #116 (it owns the equity->Sizer z^-1 register per C10) — deferring it here avoids a circular dependency. Supersedes the rolled-back 0064 exposure-integral derive (abandoned per #117): this derives from the executed book, never exposure deltas. Verified by the orchestrator: cargo build + clippy --all-targets -D warnings clean; full workspace suite 0 failed; aura-engine lib 214 pass (incl. the 6 derive_* unit tests); stage1_r_e2e 11 pass (incl. the new E2E + extended guard). Fork decisions recorded on #115. closes #115 |
||
|
|
1647737baa |
plan: 0068 position-event-derive
Three tasks for spec 0068: (1) derive_position_events + r_col DIRECTION/SIZE consts + crate-root re-export + report.rs unit tests (reversal, normal lifecycle, short, window-end open, empty, monotonic position_id); (2) stage1_r_e2e.rs lockstep-guard direction pin + an agreement E2E folding both summarize_r and derive_position_events over one recorded ledger; (3) full workspace build/test/clippy verification. Purely additive — no existing output touched. refs #115 |
||
|
|
ef8537c44f |
spec: 0068 position-event-derive — book first-difference table (#115) (boss-signed)
Derive the broker-independent position-event table (#114's PositionEvent rows) from a completed PositionManagement run, as the first difference of the executed book (deal = target - book - in_flight; in_flight = 0 for instant backtest fills). A pure post-run reduction `derive_position_events(record, instrument_id)` in aura-engine, sibling of summarize_r, reading the 14-column PM record positionally; a reversal emits Close then the opposite open at one event_ts (close first); a window-end open position emits its open with no synthetic Close. Supersedes the rolled-back 0064 exposure-integral derive (abandoned per #117): this derives from the executed book (the PM record), never from exposure deltas. Stage-2 fixed-fractional / currency / equity-feedback sizing is out of scope (#116 owns the equity->Sizer z^-1 register per C10). Boss-signed: specify Step-5 grounding-check PASS (8/8 load-bearing assumptions ratified by named green tests). Fork decisions recorded on #115. refs #115 |
||
|
|
458b2ae74b |
audit(0067): cycle close — lift SQN100 / stage1-r-trace rationale to ledger, drop ephemera
Architect audit clean (no drift, no debt): raw sqn verified bit-identical, C18 serde back-compat + C4 SoA seam + C1 determinism + Part-B symmetry all hold. - Ledger C10: add a "Realization (cycle 0067, #130 + #135)" note — SQN100 as the turnover-robust opt-in rank key (raw sqn / default ranker byte-unchanged, serde(default) for back-compat), and per-member --trace now symmetric across all three sweep strategies via the shared persist_traces_r. - Glossary: R-metrics list gains sqn_normalized; the SQN entry documents the SQN100 variant (was "deferred #130"). - Remove the ephemeral spec + plan (cycle closed; rationale lives in the ledger and git history). refs #130, refs #135 |
||
|
|
89d967a940 |
feat(0067): SQN100 metric + stage1-r sweep --trace
Two additive follow-ups to the cycle-0065/0066 R-sweep surface. Part A (#130) — n-normalized SQN (SQN100): RMetrics gains sqn_normalized = (mean_R/stdev_R)·√(min(n,100)), a turnover-robust ranking objective comparable across sweep members with different trade counts. The raw sqn is kept byte-identical (computed sqrt(n)*mean/sd, NOT via a factored shared ratio, so the existing metric does not drift even by 1 ULP). The field carries #[serde(default)] for C18 back-compat (a pre-0067 r: block deserialises with sqn_normalized = 0.0). The registry learns Metric::SqnNormalized (opt-in; the default ranker and raw sqn are unchanged), so `runs family rank sqn_normalized` works. The 0066 single-run golden is re-baselined (the r: block gains the field; for n=3 ≤ 100 the cap is inactive so sqn_normalized == sqn). Part B (#135) — wire --trace for the stage1-r sweep: stage1_r_sweep_family now honours --trace, persisting each member's equity/exposure/r_equity under runs/traces/<n>/<member_key>/ via the existing persist_traces_r (mirroring momentum_sweep_family); the interim run_sweep refusal guard is dropped. --trace is now symmetric across all three sweep strategies and a swept member is chartable (chart <n>/<member> --tap r_equity). Tests: SQN100 cap / below-cap / empty + serde back-compat (engine); rank-by sqn_normalized + unknown-metric message (registry); an E2E guard that both SQN values fold from recorded records and agree below the cap (the cross-crate column seam); inverted stage1-r --trace persistence + a CLI rank-by- sqn_normalized integration. Full workspace suite green (527 passed, 0 failed); clippy --all-targets -D warnings clean. Derived fork decisions logged on #130 and #135 (cap = fixed const 100; opt-in metric, default ranker unchanged; serde(default); Part B mirrors momentum_sweep_family + reuses persist_traces_r). closes #130 closes #135 |
||
|
|
a726974310 |
plan: 0067 r-sweep-followups
5-task plan for the boss-signed spec docs/specs/0067-r-sweep-followups.md. Task 1 adds RMetrics.sqn_normalized (+ SQN_CAP + summarize_r compute) and threads all four RMetrics struct literals (compile-gate). Task 2 adds the Metric::SqnNormalized rank key. Task 3 re-baselines the 0066 single-run golden (the r: block gains the field). Task 4 wires --trace for the stage1-r sweep (invert the refusal test → persist member dirs via persist_traces_r, drop the run_sweep guard). Task 5 is the full-workspace gate. Sequencing pins the golden re-pin and the unfiltered suite after Task 1. refs #130, refs #135 |
||
|
|
e5c87a5173 |
spec: 0067 r-sweep-followups (boss-signed)
Two settled, additive follow-ups to the cycle-0065/0066 R-sweep surface, named together by the user under /boss. Signed on the grounding-check PASS (9 load-bearing current-behaviour assumptions, each ratified by a named green test). Part A (refs #130) — n-normalized SQN (SQN100): a new opt-in ranking metric sqn_normalized = (mean_R/stdev_R)·√(min(n,100)), turnover-robust across sweep members with different trade counts. Additive RMetrics field (serde(default) for C18 back-compat) + a new Metric::SqnNormalized rank key mirroring Sqn. Raw sqn and the default ranker are unchanged; the 0066 single-run golden is re-baselined (the r: block gains one field — intended, not drift). Part B (refs #135) — wire --trace for the stage1-r sweep: stage1_r_sweep_family honours --trace by persisting each member's equity/exposure/r_equity under runs/traces/<n>/<member_key>/ via the existing persist_traces_r, mirroring momentum_sweep_family; the interim refusal guard in run_sweep is dropped. Makes --trace symmetric across all three sweep strategies. Derived fork decisions logged on #130 and #135 (reconciliation comments). |
||
|
|
986a9fd5c7 |
audit(0066): cycle close — lift R-sweep/SQN rationale to ledger, drop ephemera
Cycle 0066 (#133) closed. The close audit (architect) found the family ratified against C12 axis-2 (rank-by-metric over the C18 family store) and the C10 R framing, with one [high] gap (swept-member manifest omitted the fixed R-defining params) fixed forward in
|
||
|
|
499121e5c8 |
fix(stage1-r): record fixed R-defining params in swept member manifests
Cycle-0066 close audit found a C18/C10 gap: a stage1-r sweep member's manifest recorded only the floated knobs (fast.length, slow.length) via zip_params, omitting the fixed R-defining params (stop_length, stop_k, bias_scale) that the single run records. A family member must be reproducible from its own manifest (C18), and the constant stop -- which defines 1R -- must be visible so cross-member SQN comparability (C10) is auditable from the family record, not just the swept fast/slow knobs. The sweep run_one now appends the three fixed params, matching the single run; the integration test pins their presence per member. Verified: full workspace suite 521 passed / 0 failed; clippy -D warnings clean. refs #133 |
||
|
|
b8ed6f4858 |
feat(stage1-r): R sweep families + rank-by-SQN
Realizes #133 (cycle 0066): SQN becomes the single-number objective for ranking a Stage-1 sweep family by R signal quality. aura-registry — metric_cmp learns three higher-is-better R metrics (sqn, expectancy_r, net_expectancy_r) that reach into RunReport.metrics.r; a member with r:None sorts to the worst position (treated as -inf via total_cmp), so a pip-only family ranked by an R metric degrades to ordinal order without error. The UnknownMetric "known:" list is extended. No persistence change — the family store already carries the r block. aura-cli — `aura sweep --strategy stage1-r` sweeps the stage1-r harness over a fast×slow SIGNAL grid (4 members), each folding the dense R-record via summarize_r so its RunReport carries r:Some(..). The grid varies only the signal: the stop and sizing are held fixed because risk_budget is R-invariant (#128) and bias.scale is sign-only under flat-1R (both degenerate axes for the ranked metric), and because the stop defines the R unit — varying it would break cross-member SQN comparability (the motivation for the deferred SQN100, #130). The stage1-r topology is extracted into a shared stage1_r_graph(.., fast_len, slow_len) helper: the single run binds Some(2)/Some(4) (output byte-unchanged), only the sweep floats the knobs. Per-member --trace tracing is deferred (a follow-up): rather than accept the flag and silently write nothing (a quality-review catch), `aura sweep --strategy stage1-r --trace` is refused explicitly (exit 2); --name records the rankable family. Verification — a golden characterization test (stage1_r_single_run_output_golden, added against HEAD before the refactor) verifies the helper extraction is byte-preserving; full workspace suite 521 passed / 0 failed; clippy --all-targets -D warnings clean. The grounding-check that signed the spec first BLOCKed a false "already byte-pinned" claim, which is why the golden is added rather than assumed. All load-bearing forks (signal-only grid, None-sorts-worst, --trace refusal, helper extraction, golden shape) are recorded on the #133 decision log. closes #133 refs #117 #130 |
||
|
|
d430dbb885 |
plan: 0066 r-sweep-rank — R sweep families + rank-by-SQN
Parent spec docs/specs/0066-r-sweep-rank.md (#133). Four tasks: golden characterization of the stage1-r single run (byte-preservation guard); extract stage1_r_graph helper (float fast/slow for the sweep, single run binds Some(2)/Some(4)); metric_cmp learns sqn/expectancy_r/net_expectancy_r (None sorts worst); stage1_r_sweep_family + --strategy stage1-r + rank-by-sqn integration test. refs #133 |
||
|
|
1c8678a6ad |
spec: 0066 r-sweep-rank — R sweep families + rank-by-SQN (boss-signed)
Cycle 0066, reference issue #133. Settled design (deferred from 0065): make SQN the single-number objective for ranking a Stage-1 sweep family by R signal quality. Two halves: - aura-registry: metric_cmp learns sqn / expectancy_r / net_expectancy_r (all higher-is-better, reaching into RunReport.metrics.r); a member with r:None sorts to the worst position. No persistence change (the family store already carries r). - aura-cli: --strategy stage1-r sweeps the stage1-r harness over a fast×slow signal grid (stop & sizing fixed — risk_budget is R-invariant, bias.scale is sign-only for flat-1R, both degenerate axes; the stop defines the R unit so varying it breaks cross-member SQN comparability — see the #133 decision log). The stage1-r topology is extracted into a shared helper so the single run keeps its identical binding (output unchanged by construction) and only the sweep floats fast/slow. Signed under /boss via the grounding-check PASS (the autonomous signature). The first grounding pass BLOCKed a false claim that the stage1-r single-run output was already byte-pinned; the spec was revised so a golden characterization test is added against current HEAD before the refactor (verifying byte-preservation rather than assuming it), and re-grounded to PASS. refs #133 #117 |
||
|
|
3d3baaeb24 |
refactor(stage1-r): dedicated broker label + intern col-port names (no per-build leak)
Two cosmetic smells the cycle-0065 close audit flagged (#132), neither a correctness issue: 1. Broker label. run_stage1_r reused sim_optimal_manifest, recording broker: "sim-optimal(pip_size=...)" for a harness that also runs a RiskExecutor branch. It now overrides the label to "sim-optimal+risk-executor(pip_size=...)" so the dual-tap harness reads honestly. A new assertion in run_stage1_r_synthetic_folds_an_r_block pins it. 2. col[i] leak. stage1_r_blueprint did format!("col[{i}]").leak() per PositionManagement field (14 strings) to satisfy the &'static str port-name API -- harmless for a CLI one-shot, but a leak-per-build smell once the harness is reused in a sweep / Monte-Carlo loop (the #133 direction). Replaced with a process-wide LazyLock<Vec<String>> (COL_PORTS), interned once: any number of builds now reuse the same 14 strings. (#132 deferred this to #133, but the intern is robust to any reuse pattern, so it lands here cleanly.) Behaviour-preserving (the port names and the recorded R-record are unchanged); the broker label is the only observable change, and it is pinned. Verified: clippy --all-targets -D warnings clean; full suite 514 passed / 0 failed; live `aura run --harness stage1-r` reports "broker":"sim-optimal+risk-executor(pip_size=0.0001)". closes #132 |
||
|
|
68605b7d88 |
refactor(stage1-r): rename the strategy-output param namespace exposure -> bias
Complete the deferred rename tail from cycle 0065's #126 (which renamed only the typed metric, keeping the param namespace uniformly "exposure" so the single-run rename would not smuggle in a ~30-site sweep-pinned change). Now renamed together as one consistent unit, all driven by the Bias node's instance name: - Bias::builder().named("exposure") -> .named("bias") (the harness builders + the engine fixtures in test_fixtures.rs / blueprint.rs); - the derived knob path exposure.scale -> bias.scale (Composite::param_space derives the knob from the node name) -- the sweep .axis/.range bindings and .with() points (blueprint.rs, main.rs), the walkforward ParamSpecs (walkforward.rs), the member_key dir names, and the JSON byte-pins in cli_run.rs / report.rs / main.rs tests; - the exposure_scale manifest param label -> bias_scale (the single-run manifest builders + the streaming_seam / real_bars fixtures). The rename is surgical: the three targets (named("exposure"), exposure.scale, exposure_scale) are syntactically distinct from the deliberately-kept "exposure" forms, which are untouched -- SimBroker's pre-reframe exposure input slot + prev_exposure + the exposure-integral; the LongOnly / gate / latch ports named "exposure"; and the on-disk "exposure" tap label (the recorded bias series that feeds `chart --tap exposure`, decoupled from the node name via ColumnarTrace::from_rows("exposure", ...)). No on-disk back-compat break: the knob path is a runtime param address, and recorded params in old runs.jsonl / families.jsonl are inert data strings (a pre-existing recorded "exposure.scale" still loads, it is just data). INDEX.md cycle-0065 realization note amended: the param namespace is recorded as the now-completed tail; the SimBroker slot + the on-disk tap label remain the deliberate permanent keeps (#117), and exposure_sign_flips stays a serde alias. Verified: cargo build --workspace --all-targets clean; clippy --all-targets -D warnings clean; full suite 514 passed / 0 failed (source + byte-pins renamed together); live `aura run` emits "bias_scale", `aura sweep` emits "bias.scale". closes #134 refs #126 #117 |
||
|
|
5c6de45b6e |
fix(cli): list valid taps on bad --tap, make --help uniform across subcommands
Two CLI-UX defects the Stage-1 R fieldtest surfaced (#131): 1. Tap discovery. `aura chart <name> --tap <bad>` rejected an unknown tap but the error omitted the valid names. filter_to_tap now enumerates the run's available taps in the message ("... (available: equity, exposure)"), so the user can correct a typo instead of guessing -- refuse, but help. 2. Uniform --help. `aura chart --help` errored with "unexpected chart argument '--help'" (exit 2) and `aura run --help` leaked usage to stderr via the error path, while bare `aura --help` printed to stdout (exit 0). main() now intercepts `--help`/`-h` anywhere in the invocation, prints usage to stdout, and exits 0 -- uniform for every subcommand. The now-dead bare-flag match arm is removed (the early intercept covers it). RED-first: the existing chart_tap_nonexistent test gains an available-taps assertion (was: message had no names), and a new per_subcommand_help test pins `aura <sub> --help` -> stdout usage + exit 0 for run/chart/sweep/walkforward/ mc/graph/runs (was: chart --help exited 2). Unknown-subcommand stays exit 2. Verified: clippy --all-targets -D warnings clean; full suite 514 passed / 0 failed (one new test); live `aura chart --help` prints usage to stdout, exit 0. closes #131 |
||
|
|
0288878ad2 |
refactor(cli): remove the redundant --macd flag, superseded by --harness macd
The --macd flag was a back-compat alias for --harness macd, added in cycle 0065 iter-3 while the uniform `--harness <name>` selector was introduced. With that selector in place the alias is pure redundancy, so drop it: --harness <name> is now the sole way to pick a built-in harness. Removed from parse_run_args: the macd_flag accumulator, its match arm, and the (harness, macd_flag) conflict resolution -- harness selection collapses to `harness.unwrap_or(HarnessKind::Sma)`. Both usage strings drop `[--macd]`; the parse_run_args doc and the parse_real_args note no longer cite the flag. The MACD harness itself is untouched: run_macd, the macd() composite, and `--harness macd` all stay. Tests updated -- the alias test becomes a positive `--harness macd -> Macd` test plus an assertion that `--macd` is now rejected as an unknown token; the "repeated --macd" edge is dropped (the flag is gone, and repeated-flag strictness is still covered by --harness/--trace). INDEX.md: the two realization notes that documented `--macd` as a live run form updated (`aura run [--real ...]`; `run --harness macd`). The historical graph-viewer retirement note and the frozen fieldtest capture are left as the record of their time. Verified: clippy --all-targets -D warnings clean; full suite 513 passed / 0 failed (assertions rewritten in place, no test count change); live smoke -- `aura run --harness macd` emits a RunReport, `aura run --macd` exits with the updated usage. |
||
|
|
1a6eafa056 |
refactor(composites): extract Stage-1 composite-builders into their own crate
Dissolve the aura-engine -> aura-std dependency by moving the vol_stop /
risk_executor composite-builders out of aura-engine into a new aura-composites
crate. This restores the engine's domain-free layering, which the
composites-in-engine placement (cycle 0065) had inverted.
Why: the engine routes type-erased Scalar records and never names a concrete
node -- summarize_r reads the PositionManagement record positionally, by column
index, without linking aura-std (which is exactly why aura-std was a dev-only
dependency before 0065). Parking the node-naming composite-builders inside
aura-engine forced the engine's production code to reference the node library
for the first time (aura-engine -> aura-std), an upside-down layering: the
engine is the foundation the node library builds upon, not the reverse.
The clean home is a dedicated crate that depends on BOTH the engine's
GraphBuilder and the std nodes, leaving each lower crate untouched:
aura-composites -> { aura-engine, aura-std } -> aura-core (acyclic)
aura-engine -> aura-core only (domain-free again)
aura-std -> aura-core only (lean node library)
aura-std reverts to a [dev-dependency] of aura-engine (the engine's own E2E
tests -- stage1_r_e2e, random_sweep_e2e, ger40_breakout -- still drive real
std nodes through a bootstrapped Harness; that test-only edge creates no cycle).
Moved with history (git mv):
- crates/aura-engine/src/composites.rs -> crates/aura-composites/src/lib.rs
- crates/aura-engine/tests/risk_executor.rs -> crates/aura-composites/tests/
- crates/aura-engine/tests/vol_stop_composite.rs -> crates/aura-composites/tests/
Consumers rewired: aura-cli imports risk_executor / StopRule from aura_composites;
the moved tests import the builders from the crate under test. The intra-doc link
to RollMode and the module rationale doc are updated for the new home.
Behaviour-preserving: workspace build clean; clippy --all-targets -D warnings
clean; full suite 513 passed / 0 failed, identical to baseline (the two moved
test files run unchanged under aura-composites); cargo doc --no-deps clean (no
broken intra-doc links).
|
||
|
|
c406b969f5 |
chore(stage1-r): finalize cycle 0065 — milestone closed, ephemeral spec/plans removed
The 'R-based signal quality (Stage 1)' milestone is complete and ratified (iter-1/2/3 + the corrective primitives mini, the tiny-pip fix, the metric rename; end-to-end fieldtest green). Closing the cycle: - Milestone container + issues #119/#126/#127/#128/#129 closed on the tracker. - The ephemeral per-cycle spec (0065) and plans (0065-0069) are git-rm'd per the cycle-close convention; the durable rationale lives in the design ledger (the C10 cycle-0065 realization note) plus git history. Follow-ons tracked: #130 (n-normalized SQN), #131 (CLI UX), #132 (harness polish), #133 (R-sweep + rank-by-sqn), #134 (strategy-output param-namespace rename). refs #117 |
||
|
|
c6c1d3bb10 |
feat(stage1-r): rename the exposure_sign_flips metric to bias_sign_flips (#126)
Complete the typed-field half of the exposure->bias rename - the one the cycle-0065
close audit flagged as aging worst (a serde-stable on-disk key).
- RunMetrics.exposure_sign_flips -> bias_sign_flips with
#[serde(alias = "exposure_sign_flips")], so legacy runs.jsonl still deserialises
(pinned by the legacy-read tests, which keep the old key); new output serialises
bias_sign_flips (the byte-pin tests updated to the new key).
- The registry rank metric: Metric::ExposureSignFlips -> BiasSignFlips, the rank
string accepts BOTH "bias_sign_flips" and "exposure_sign_flips" (CLI back-compat),
the error/listing updated; the rank tests exercise both names.
- The MC aggregate field renamed too (McAggregate is hand-rendered to JSON, not
serde-derived - no alias needed).
Scope held to the typed metric. The strategy-output PARAM NAMESPACE - the
.named("exposure") Bias instance, its exposure.scale knob path (Composite::param_space
derives the knob from the node name), and the exposure_scale manifest label - is a
coherent ~30-site, sweep-pinned surface (the implement loop correctly flagged renaming
the knob as an unscoped expansion touching walkforward + the cli_run byte-pins). Kept
uniformly as "exposure" and deferred to a follow-on so the cluster renames as one
consistent unit. SimBroker's pre-reframe exposure concept and the on-disk "exposure"
tap label stay by the #117 fork decision.
cargo test --workspace green; clippy --workspace --all-targets -D warnings clean; a
live `aura run --harness stage1-r` emits "bias_sign_flips".
closes #126
refs #117
|
||
|
|
34e8bbc803 |
plan: 0069 exposure->bias rename tail (#126)
The deferred rename of the strategy-output residue (the typed exposure_sign_flips
metric with serde back-compat, the registry rank vocab, the exposure_scale manifest
label, the .named("exposure") Bias instances), keeping SimBroker's pre-reframe
exposure concept and the on-disk exposure tap label (scope fork on #117).
refs #126 #117
|
||
|
|
4e6b1d9b53 |
audit(stage1-r): cycle-0065 close — ledger records the Stage-1 R signal-quality layer
Architect drift review of cycle 0065 (Stage-1 R: the exposure->bias rename, the
stop-rule + vol_stop, PositionManagement's dense R-record, the Sizer, summarize_r,
the public RiskExecutor, and the CLI surface): code is drift-clean - C10 fidelity
holds (bias->stop->Sizer->PositionManagement, flat-1R feed-forward, R stop-defined
and size-invariant, guarded by the cross-crate layout lockstep test), C9/C23
acyclic, the --harness selector is a compile-time match (no runtime registry/DSL,
C17). The milestone fieldtest is green after the tiny-pip tolerance fix (
|
||
|
|
2ee0d74a8c |
fix(stage1-r): scale-robust tolerance for the R-record redundancy guard
The PositionManagement redundancy `debug_assert` (position_management.rs:229) re-derives realized_r from the dense-record columns as dir*(exit-entry)/|entry-stop| and compared it to the stored value with an ABSOLUTE < 1e-9 tolerance. At tiny-pip price scale (EURUSD/GBPUSD entry ~1.1, vol-stop distance ~1e-6) the denominator reconstruction `entry - stop` is a catastrophic-cancellation subtraction that loses ~6 significant digits; with a deep gap-through-stop realising a large |R|, the recomputed R drifts from the (correct) stored realized_r by more than 1e-9 - so a debug build PANICS (exit 101) while a release build (assert compiled out) returns the correct R. GER40 (large pip) never trips it. Found by the Stage-1 R milestone fieldtest: `aura run --harness stage1-r --real EURUSD` crashed the headline "score real FX in R" use case on the default (debug) build, and the debug/release disagreement made the R numbers' validity unknowable from the public surface. Root cause (via debug): the stored realized_r uses the cancellation-free FROZEN latched_dist and is exact - only the guard's tolerance was wrong for small price scales. Fix: a scale-robust relative tolerance `< 1e-9 * realized.abs().max(1.0)` (floored at the historical 1e-9 for |R| <= 1, so no change for normal-scale runs). RED-first: the hermetic synthetic-tiny-pip test `tiny_pip_gap_stop_does_not_trip_the_redundancy_guard` reproduces the panic and is pinned here. Verified end-to-end: `aura run --harness stage1-r --real EURUSD` now returns the R block (exit 0) on a debug build. cargo test --workspace green; clippy --workspace --all-targets -D warnings clean. refs #117 |
||
|
|
a6fc48daa7 |
feat(stage1-r): operate the R layer from the CLI (iter 3)
`aura run --harness stage1-r [--real <SYM>] [--trace <n>]` now runs a Stage-1 R
backtest and prints its signal-quality metrics (the R block) in the RunReport
JSON - one shell call, the research loop's primary verb, ergonomic for Claude
to drive.
What shipped:
- vol_stop + risk_executor promoted from test fixtures to public aura-engine
composite-builders, with a StopRule{Fixed,Vol} axis (C11). aura-std becomes a
normal dependency of aura-engine (the composites are the engine's convenience
layer over the standard nodes - the sibling tier of summarize_r; the graph
stays acyclic: aura-engine -> aura-std -> aura-core). Both fixtures call the
public fns; a Vol-backed RED test pins the new match arm.
- stage1_r_blueprint fans one bias into BOTH a SimBroker (pip) and the
RiskExecutor (R), so one report carries both yardsticks honestly. run_stage1_r
folds summarize (pip) + summarize_r (R, round_trip_cost 0.0 - Stage-1 is
frictionless signal quality) and sets RunMetrics.r = Some(..). An r_equity tap
(cum_realized_r + unrealized_r via LinComb) persists through a separate 3-tap
persist_traces_r and renders via the existing `aura chart --tap r_equity`.
- `aura run --harness <sma|macd|stage1-r>`: a parse_run_args tokenizer replaces
the five `run` literal-slice arms so --harness/--real/--from/--to/--trace
compose in any order; --macd kept as a back-compat alias; --harness sma the
default. A compile-time match over Rust-authored built-ins - not a runtime node
registry, not a DSL (C9/C17): the CLI runs an authored harness, it does not
wire one.
Back-compat: --harness sma/macd leave RunMetrics.r = None, so skip_serializing_if
keeps pip-only and legacy runs.jsonl JSON byte-unchanged; the plain-run tap list
stays ["equity","exposure"].
Refactor beyond the plan (verified behavior-equivalent): the old standalone
parse_real_args was orphaned by the new tokenizer, so it and its now-redundant
unit test were removed and run's --real parsing inlined into parse_run_args. The
--real strictness (empty-symbol, non-i64 ms, repeated-flag, window-requires-real
-> exit 2) is preserved and now pinned by
parse_run_args_rejects_malformed_real_and_repeated_flags (added to restore the
deleted coverage). All five run_real integration tests stay green.
Verification: cargo build/test --workspace green (every crate, 0 failed); clippy
--workspace --all-targets -D warnings clean. The full diff was adversarially
reviewed (run-arg refactor equivalence against the removed function, harness
wiring, C1/C2/C9/C17, the serde back-compat pin) - verdict SOUND; the one flagged
coverage gap is closed here.
Follow-on (noted, not done): the stage1-r manifest reuses the
"sim-optimal(pip_size=...)" broker label; a dedicated "risk-executor" label would
read more honestly for a dual-tap harness that runs a RiskExecutor branch.
closes #129
refs #117 #128
|
||
|
|
13086b8590 |
plan: 0068 stage1-r CLI surface (iter-3)
Executable plan for iteration 3 of cycle 0065 - make the Stage-1 R layer operable from the command line. Three tasks: - Task 1: promote vol_stop + risk_executor + a StopRule axis from test fixtures to public aura-engine composite-builders (aura-std moves to a normal dep; the two fixtures rewired to call them; a Vol-backed RED test added). - Task 2: the stage1_r_blueprint dual-tap harness (one bias fanned into both SimBroker and the RiskExecutor) + run_stage1_r folding summarize_r into RunMetrics.r + persist_traces_r (a separate 3-tap helper that leaves the seven pip callers byte-unchanged). - Task 3: a parse_run_args tokenizer + --harness selector + run_dispatch, replacing the run literal-slice arms; binary-driven selector / flag- composition / r_equity-round-trip tests. Recon: two plan-recon agents (aura-cli surface, aura-engine promotion). Verified before handoff by a 3-critic adversarial pass (API-fidelity, plan-discipline, behavioral-correctness): discipline and behavior SOUND, one API fix applied (tempdir -> the existing temp_cwd helper, no tempfile dep). refs #129 #117 |
||
|
|
08d09df97d |
spec: 0065 stage1-r iter-3 CLI surface (boss-signed)
Amend the cycle-0065 spec with the "Iteration 3 - the CLI surface" design: make the Stage-1 R layer operable from the command line (the research loop's primary verb, ergonomic for Claude to drive), splitting the CLI/recording surface out of the originally-lumped iter-2. Design (forks derived under /boss, user-delegated "du entscheidest das Design", recorded on #117): - Promote vol_stop + risk_executor from test fixtures to public aura-engine composite-builders; the embedded stop generalizes to a StopRule{Fixed,Vol} enum (a C11 structural axis), vol_stop the volatility-defined default. - aura run --harness <name>: a compile-time selector (sma|macd|stage1-r) over Rust-authored built-ins - running, not wiring (C9/C17 intact: no runtime registry, no DSL). Replaces the run literal-slice arms with a parse_run_args tokenizer so --harness/--real/--trace compose orthogonally; --macd kept as an alias. - The stage1-r harness fans one bias into both SimBroker (pip) and the RiskExecutor (R), so one RunReport carries both yardsticks honestly; the run path folds summarize_r -> RunMetrics.r (additive, skip_serializing_if keeps pip-only/legacy JSON byte-unchanged), round_trip_cost=0.0 (Stage-1 is frictionless; a --cost knob is a follow-on). - An r_equity tap (LinComb of cum_realized_r+unrealized_r) persists via an extended persist_traces and renders through the existing aura chart --tap - no new viewer code. Scope line: "operable from the CLI" = run+inspect a Rust-authored harness (the #109 (a) reading the contracts settle), not authoring topology in the CLI; the broad #109 meta-level stays a separate future milestone. Deferred follow-ons: R-sweep families + rank-by-sqn, rich R-record charting, the bias_sign_flips metric rename (never carried out in iter-0; field is still exposure_sign_flips). Verified before signing: grounding-check PASS (all substrate + post-patch current-code claims ratified by green tests) - the autonomous /boss signature; plus an advisory 5-lens spec-skeptic panel (criterion/invariants/scope SOUND; ambiguity + plan-readiness BLOCKs resolved inline: the argv tokenizer, the 3-tap persist change, the round_trip_cost value, the dual-tap arg provenance), re-grounded after the patch. refs #129 #117 |
||
|
|
92591e5dc0 |
refactor(stage1-r): name the conviction column conviction_at_entry
Rename the dense PositionManagement record's column 9 from `bias_at_entry_abs` to `conviction_at_entry` (FIELD_NAMES, the `r_col`/test index constants, the cross-crate layout guard, and the doc comments). Why: the column holds |bias| at entry, which C10 defines AS conviction (bias = direction + conviction magnitude; ledger INDEX.md ~458). The record already names the other half of that decomposition by its domain concept (`direction` = sign, not `bias_sign`), so naming the magnitude half mechanically (`bias_at_entry_abs`) was an inconsistency; `conviction_at_entry` speaks the ledger's ubiquitous language and parallels `direction`, while `_at_entry` preserves the frozen-snapshot precision. Considered and rejected: collapsing `(direction, bias_at_entry_abs)` into one signed `bias_at_entry` and dropping `direction` as redundant — it is not redundant. On a reversal row col 4 (`direction`) tracks the reopened leg while col 9 tracks the closed trade's entry conviction, so the two describe different trades and cannot be losslessly merged; they are also distinct semantic axes (current-position direction vs per-trade entry conviction). Scope: domain name at the data boundary, mechanical name internally — the internal `bias.abs()` variables (`Open.bias_abs`, the fold's `Trade.bias_abs`) keep their computation-faithful names, exactly the raw-internal / domain-external split the record uses elsewhere. Behaviour-preserving: cargo build --workspace clean; cargo test --workspace 500 passed, 0 failed (unchanged); clippy --all-targets -D warnings clean. The layout guard (`r_col_indices_match_producer_field_layout`) confirms the renamed string. refs #117 |
||
|
|
b4e84335c4 |
feat(stage1-r): Sizer + RiskExecutor + R-metric enrichment (iter 2)
Iteration 2 of cycle 0065 (Stage-1 R signal quality) — the node + metric layer. Builds on iter-1's PositionManagement dense R-record + core summarize_r. What ships: - PositionManagement gains a 4th `size` input (slot 3 -> col 10), fed by the Sizer. R is computed size-INVARIANTLY (pure stop-distance ratio), so size never touches realized_r — pinned by a RED test (scaling size leaves every realized_r unchanged) at the node and again end-to-end through the executor. - Sizer (aura-std): `size = risk_budget / stop_distance` — flat-1R (risk_budget 1.0 => one risk unit per trade, size inversely proportional to the stop, not a constant). Reads bias (firing/presence) + stop_distance; the Stage-2 fixed-fractional sizer slots in unchanged (swap risk_budget for risk_fraction*equity, same node shape). - summarize_r enrichment: SQN (sqrt(n)*mean_R/sample-stdev_R; n<2 or zero-variance -> 0), conviction_terciles_r (E[R] by |bias_at_entry| tercile), net_expectancy_r (gross minus one round-trip cost per trade, charged in R via the latched_dist recovered from the entry_price/stop_price columns). summarize_r gains a `round_trip_cost` param (price units). The net-of-cost recovery is tested through the real producer->consumer seam, not just hand-built rows. - RunMetrics.r: Option<RMetrics> with #[serde(default, skip_serializing_if = "Option::is_none")] — legacy runs.jsonl (no `r` key) deserialise to None and a pip-only run's on-disk shape stays byte-unchanged (C14/C18 back-compat). Every RunMetrics literal threaded (report.rs, aura-registry, aura-engine/mc). - RiskExecutor (aura-engine integration-test fixture, sibling of vol_stop_composite): FixedStop -> Sizer -> PositionManagement behind open bias+price input roles, price fanned to both the stop and PM, the Sizer's size into PM. Bias is produced in-graph from the single price source (a second bias *source* would k-way-merge into separate cycles and mark stale prices). The Veto is a DOCUMENTED SEAM, not a runtime node (a pass-through identity is what C19/C23 DCE deletes). Tests: the composite bootstraps + runs + folds to the documented hand value; R invariant under risk_budget while the size column scales; a live-folded RMetrics survives the RunMetrics serde round-trip. The dense-record size column (10) is brought under the cross-crate layout guard (stage1_r_e2e r_col_indices_match_producer_field_layout) so the executor fixture's size-invariance read is drift-protected like the others. Scope: the CLI/recording surface (#129) is sub-split into a separate iteration 3 and is NOT in this commit. Verified: cargo build --workspace clean; cargo test --workspace 500 passed, 0 failed; cargo clippy --workspace --all-targets -D warnings clean. refs #117 #127 #128 #129 |
||
|
|
6e214957d1 |
plan: 0067 stage1-r iter-2 — Sizer + RiskExecutor + metric enrichment
Iteration-2 plan for cycle 0065 (Stage-1 R signal quality). Sub-splits the spec's iteration 2: this plan is the node + metric layer; the CLI/recording surface (#129) becomes a separate iteration 3. Five tasks, sequenced so each signature change threads all its callers before its build gate, and the two signature changes land before the composite that consumes both: 1. PositionManagement gains a 4th `size` input (col 10, from the Sizer); R stays size-INVARIANT (RED test pins it). Threads the PM unit tests + the stage1_r_e2e direct-eval column setups. 2. Sizer node (aura-std): `size = risk_budget / stop_distance`, flat-1R; the Stage-2 fixed-fractional seam slots in unchanged. 3. summarize_r enrichment: SQN, conviction terciles, net-of-cost; gains a `round_trip_cost` param (reads dense cols 6/7/9). Threads report.rs + the E2E callers and extends the cross-crate layout guard. 4. RunMetrics.r: Option<RMetrics> with serde(default, skip_serializing_if) for legacy runs.jsonl back-compat. Threads every RunMetrics literal (report.rs, aura-registry, aura-engine/mc). 5. RiskExecutor composite (aura-engine integration-test fixture, sibling of vol_stop_composite): FixedStop -> Sizer -> PositionManagement behind open bias+price roles, price fanned to both; bias produced in-graph (single source, no stale-price two-cycle merge). Veto is a documented seam, not a node. E2E equivalence + R-invariance under risk_budget. refs #117 |
||
|
|
0998f9aabf |
refactor(stage1-r): the vol stop is a composition, not a fused node
Applies the principle the user named: a node is a primitive only if it is NOT DAG-expressible from other primitives; a function that needs a missing primitive gets the primitive added, not fused away. - Remove the fused VolStop node (it was pure feed-forward arithmetic). - The volatility stop is now a composition of primitives, vol_stop(length, k): k * Sqrt(Ema(Mul(d,d), length)), d = Sub(price, Delay(price,1)) -- a rolling EWMA standard deviation. Built with GraphBuilder; proven end-to-end (bootstraps + runs + emits k*sigma) in tests/vol_stop_composite.rs. - Migrate the one VolStop caller (stage1_r_e2e.rs) off it: the R-is-stop-defined test now contrasts a tight vs a wide FixedStop (two constant distances still fold to different R, the property under test). Uses the Mul + Sqrt primitives added in the prior commit. FixedStop stays the only stop-rule primitive (a triggered constant). Full suite + clippy green. refs #117 #119 |
||
|
|
831092841e |
feat(aura-std): add Mul + Sqrt primitives
The two genuinely-missing arithmetic primitives (Mul = two-stream f64 product, Sqrt = one-input f64 root, negatives clamped to 0). They are the building blocks for the volatility stop as a composition (rolling EWMA stddev), replacing the fused VolStop node. Also corrects plan 0066 Task 3 (the VolStop removal must migrate its stage1_r_e2e.rs caller — a false premise the implementer caught). refs #117 #119 |
||
|
|
5de8576fd0 |
plan: 0066 stage1-r primitives + spec correction (VolStop -> composition)
VolStop was a design error (a fused node justified by a missing primitive). A node is a primitive only if not DAG-expressible from other primitives; a missing primitive is added, not fused away. Adds Mul + Sqrt primitives, removes the fused VolStop, expresses the vol stop as a composition (rolling EWMA stddev = k * Sqrt(Ema(Mul(d,d), length))). Spec 0065 b gets a correction note; full record on #117. refs #117 #119 |
||
|
|
2c43296c2c |
feat(stage1-r): bias rename + stop-rule + position-management + summarize_r (iter 1)
Iteration 1 of the Stage-1 "R-based signal quality" cycle (spec 0065): a strategy's signal quality, measured in R on its unsized bias stream, feed-forward. New discrete-trade machinery, NOT a SimBroker extension. What lands: - exposure -> bias (refs #126): the Exposure node/type/file/output-field renamed to Bias (computation unchanged: clamp(signal/scale,-1,+1); the SEMANTICS change — the output is the unsized strategy bias, sizing leaves the strategy). Scoped to the semantic core; behaviour-preserving cosmetics are deferred (see below). - stop-rule nodes (refs #119): VolStop = k*EMA(|price - prev_price|) (one fused node; the volatility that defines 1R, close-to-close — true-range ATR deferred, needs OHLC) + FixedStop (constant, the test fixture / fixed-vs-vol structural-axis sibling). - PositionManagement (refs #127): the stateful heart. Latches the entry-cycle stop distance as the FROZEN R-denominator (never re-read), marks/exits no-look-ahead like SimBroker (a position earns from the next cycle; an exit realises against the cycle's close), and emits ONE dense per-cycle R-record (C8). Stop-outs are NOT capped at -1R (a gap through the stop realises R < -1 — the honest loss tail); R is computed size-invariantly (size = (exit-entry)*dir / latched_dist cancels). The trade ledger is the rows where closed_this_cycle; the R-equity is cum_realized + unrealized; a position open at window end is the last row (open=true) — explicit, never silent MtM. - summarize_r + RMetrics (refs #129): the post-run fold (NOT an in-graph node — recorders drain post-run). E[R], win-rate, profit-factor, avg win/loss R, by-trade max R-drawdown, n_open_at_end (window-end force-close). SQN / conviction terciles / net-of-cost / RunMetrics.r are iteration 2. Design adversarially hardened before implementation (12-juror refute panel; decisions on #117). Ratified implementer deviations from the plan snippet, verified by hand: - col-4 `direction` tracks the OPEN position at cycle end (window-end synthesis; names the reopened leg on a reversal), falling back to the closed trade's dir only when flat. summarize_r does not read col 4; the R-metrics are unaffected. - .named("exposure") kept on the 4 previously-unnamed Bias nodes so the auto-derived param-path stays exposure.scale (no manifest/dir-name drift) — the unnamed nodes would otherwise flip to bias.scale. - intra-doc links [`Exposure`] -> [`Bias`] in sim_broker/latch + the sample-model test pin (cosmetic, behaviour-neutral; broken intra-doc links fail cargo doc). - the E2E drives a full bootstrapped Harness (stronger than the plan's direct-node drive — exercises the real cross-crate producer->consumer seam). Deferred (behaviour-preserving, separate cosmetic pass — NOT this iteration): RunMetrics.exposure_sign_flips / Metric::ExposureSignFlips, SimBroker/LongOnly "exposure" input ports, the "exposure" tap/trace names, and the .named("exposure") instance labels. Iteration 2: the Sizer seam, the RiskExecutor composite (+ the Veto documented-seam), summarize_r enrichment, RunMetrics.r, and the CLI/recording surface. Verification (orchestrator-run, not agent-claimed): - cargo build --workspace: clean. - cargo test --workspace: all green, 0 failed (incl. the new bias/stop_rule/ position_management unit tests, the summarize_r arithmetic tests, and the stage1_r_e2e capstone + layout guard). - cargo clippy --workspace --all-targets -- -D warnings: clean (exit 0). - Keystone RED tests pass: no_lookahead_bias_exit_realises_the_held_move, stop_out_is_not_capped_at_minus_one_r, no_gap_stop_is_exactly_minus_one_r, reversal_closes_one_leg_and_reopens, open_at_window_end_is_carried_on_the_last_row. refs #117 #119 #126 #127 #129 |
||
|
|
c5f5e17297 | plan: 0065 stage1-r-signal-quality | ||
|
|
51c87e5a91 |
spec: 0065 stage1-r-signal-quality (boss-signed)
Stage-1 "R-based signal quality" cycle spec (milestone #119/#126/#127/#128/#129, contract C10). Signal quality measured in R on a strategy's unsized bias stream, feed-forward (no equity feedback): bias -> stop-rule -> position-management -> per-trade R-outcomes -> summarize_r fold -> E[R]/SQN. Design adversarially hardened (12-juror refute panel; decisions logged on #117): - R is stop-defined and LATCHED at entry (frozen R-denominator, never re-read). - Stop-outs are NOT capped at -1R (honest loss tail; gap-through -> R < -1). - Fills one-cycle-lagged (SimBroker no-look-ahead discipline; RED test mandatory). - First stop is volatility-scaled (VolStop = k * EMA(|d price|)), not constant (a constant makes R just rescaled pips). - position-management emits a dense per-cycle R-record (trade ledger + R-equity + window-end handling), tapped by a Recorder, folded post-run by summarize_r (a post-run fold, NOT an in-graph node). - Sizer = load-bearing flat-1R seam; R computed size-invariantly. Veto = doc seam, not a runtime node (DCE deletes an identity). - exposure -> bias rename lands first (compiler-driven). Grounding-check PASS (11 assumptions ratified by green tests; auto-signed under /boss). refs #117 |
||
|
|
f040b66f30 |
design(c10): reframe to a bias stream + R-unit signal quality
Adopt the R-reframe ratified in #117: the strategy's native unit is risk (R), not pips/dollars, and its primary output is a directional bias. - exposure -> bias: the primary output is an UNSIZED direction + conviction `f64 in [-1,+1]`; sizing and the protective stop leave the strategy. - Signal quality is measured in R (Stage 1): an R-evaluator integrates the per-trade R-outcomes of a flat-1R RiskExecutor into R-expectancy. R is defined by the stop, account-/instrument-agnostic; pips retired as the unit. Flat-1R needs no equity, so Stage 1 is feed-forward (no cycle). - Risk-based execution is a decoupled layer: a RiskExecutor composite (stop-rule -> Sizer -> Veto -> position-management) per symbol, nested in a Broker/Account composite; account mode (netting/hedging) = composition constraint. "Sizer" is not "risk-manager" (that names the Veto layer). - Currency P&L is Stage 2 (fixed-fractional, compounding). The only feedback (equity -> Sizer) is cut by a z^-1 register on the fill edge (mark-to-market stays a same-cycle price read), encapsulated in the executor; flat-1R vs compounding is a structural axis (the explicit register is mandatory because C23 reorders the flat graph). - Position-event table stays the decoupled Stage-2 audit layer = first difference of the book (deal = target - book - in_flight); the flawed 0064 exposure-integral derive is abandoned. Touches: ledger C10 + CLAUDE.md domain invariant #7 + glossary (adds bias, R, R-evaluator, RiskExecutor, Sizer, veto, Stage 1/2; revises broker, exposure stream, position table, sim-optimal broker, strategy, ...). Also fixes cross-file drift surfaced by adversarial review (C20 guarantee + front-matter provenance still claimed "exposure stream"; cross-refs corrected to C9/C19/C20/C23). Industry-grounded against LEAN / nautilus_trader / backtrader / QSTrader / vectorbt / zipline. refs #117 |
||
|
|
2e0500dedd |
docs(claude): permit the /boss rollback sandbox in the main-sacrosanct rule
The autonomous orchestrator may now git reset --hard its OWN unpushed commits made this run (above the session anchor) when a line of work dead-ends; pushed and user-ratified history stays strictly forward-only (git revert, never reset). This codifies the /boss rollback sandbox the skill assumed but the project rule forbade, resolving the conflict the auto-mode classifier surfaced. User-authorized. |
||
|
|
7d7d1f72d9 |
audit(broker-foundation): cycle close 0063 — ledger records the C10 A-side foundation (#113 + #114)
Cycle-close tidy for broker-foundation (InstrumentSpec deploy metadata #113 + PositionEvent schema #114). Architect drift review (3638d48..HEAD): code is drift-clean. PositionEvent matches the C10 column spec field-for-field (no open_ts, unsigned volume, direction-is-action, bare-i64 action); the engine run-loop is untouched (C14); InstrumentSpec stays Copy metadata at the ingestion edge (C7/C15); no instrument identity reaches the hot path. The only drift was stale ledger prose, fixed here: - C10 realization note: added a cycle-0063 entry recording that the position-event *schema* landed (PositionAction/PositionEvent), while derivation (#115) and the realistic broker nodes (#116) remain the deferred decoupled layer. - C15 realization note: replaced "minimal today (pip only)" with the six-field deploy-grade floor and the #124 currency-type forward note (quote_currency is &'static str; the #124 resolver chooses how its runtime tier carries currency). Regression (verbatim): cargo test --workspace = 458 passed / 0 failed (exit 0); cargo clippy --workspace --all-targets -- -D warnings = clean (exit 0). Carry-on (not actioned, justified): the inline reversal test is a shape-pin only — the behaviour test arrives with #115's derive, and the E2E twin already exercises the reversal wire shape. The quote_currency &'static str / #124 resolver concern is logged on #124 as a deliberate deferred design choice. This is drift-clean, NOT a milestone close — the realistic-broker milestone needs its end-to-end fieldtest green, which depends on #115/#116/#120-123 still to land. Removed the ephemeral cycle artifacts (spec + plan) per the lifecycle convention. |
||
|
|
b91c89f964 |
feat(broker-foundation): InstrumentSpec deploy metadata + PositionEvent schema
The realistic-broker milestone's (C10 A-side) two foundation value types — no broker, no derivation yet, fully tested. #113 — InstrumentSpec gains six deploy-grade fields (instrument_id, contract_size, pip_value_per_lot, min_lot, lot_step, quote_currency) and the vetted table is populated for GER40/FRA40/EURUSD/GBPUSD/USDCAD. The struct stays Copy (quote_currency is &'static str); the _ => None refuse-don't-guess arm is the permanent floor. This is the milestone's permanent authored floor (recorded-metadata tier is forward work, #124). #114 — PositionAction { Buy, Sell, Close } (closed enum, serde-encoded as a bare i64 0/1/2 via From/TryFrom) + PositionEvent in aura-engine beside RunMetrics, faithful to the C10 column spec: event_ts, action, position_id, instrument_id, unsigned volume; no open_ts; direction is the action (no signed-volume trick). Re-exported from the crate root. Derived fork decisions (recorded on #113): quote_currency &'static str; action as ordinal i64; stable instrument_id + overridable vetted values. Verified by the orchestrator: cargo test --workspace = 458 passed / 0 failed; clippy --all-targets -D warnings clean. An adversarial 4-lens review (ledger faithfulness, downstream soundness, serde/wire robustness, test honesty) returned 3 SOUND + 1 CONCERN: quote_currency &'static str cannot hold a runtime-sourced currency at the #124 resolver — not a blocker (#115/#116 read only the authored floor), logged on #124 as a deliberate deferred design choice. The implementer's deviation from the plan snippet (vec![..] -> [..]) avoids clippy::useless_vec; semantics identical. Two tester-authored consumer-boundary E2E tests added beyond the inline units: instrument_spec_deploy_metadata.rs (public resolution seam) and position_event_table.rs (persisted bare-int wire shape + reversal table). closes #113 #114 |
||
|
|
099aac87e1 |
plan: 0063 broker-foundation — InstrumentSpec deploy metadata + PositionEvent schema
Three tasks. Task 1 (#113, compile-atomic): RED-rewrite the lookup test to the six-field shape + invariant/uniqueness tests, then extend InstrumentSpec and populate the vetted table for the five instruments (refuse arm stays). Task 2 (#114, greenfield): RED the round-trip / try_from-Err / serde-bare-int / reversal tests, then add PositionAction (serde-as-i64) + PositionEvent in report.rs and re-export from the crate root. Task 3: workspace test + clippy gate. Recon-grounded line anchors; the redundant std::convert::TryFrom import dropped (2021 prelude). INDEX.md realization-note drift (C10/C15 "minimal today") deferred to the cycle-close audit, not folded here (code-only scope). refs #113 #114 |
||
|
|
cc0dce19a3 |
spec: 0063 broker-foundation — InstrumentSpec deploy metadata + PositionEvent schema (boss-signed)
The broker milestone's (C10 A-side) foundation: two pure value-type deliverables, no broker and no derivation yet. - #113: extend InstrumentSpec with deploy-grade fields (instrument_id, contract_size, pip_value_per_lot, min_lot, lot_step, quote_currency) + populate the vetted table for the five instruments; refuse-don't-guess arm stays. Permanent authored floor of the override hierarchy (#124). - #114: PositionAction { Buy, Sell, Close } (closed enum, serde-encoded as i64) + PositionEvent row in aura-engine, faithful to the C10 column spec (no open_ts, unsigned volume, direction is the action). Signed under /boss on a grounding-check PASS (all current-state assumptions ratified by green tests; the two new types are greenfield, expected). Derived fork decisions (quote_currency &'static str; action enum encoded as i64 0/1/2; stable instrument_id + overridable vetted values) recorded on #113. refs #113 #114 |
||
|
|
3638d4822e |
docs(ledger): record the tap-aware decimation refinement (#111) in the cut-2 note
The cut-2 amendment described decimate as a pure min/max transform; #111 made the per-bucket reduction tap-aware (mean for the bounded exposure, min/max for equity). Keep the always-loaded design memory accurate; note #112 (bars/OHLC rendering) and #110 as the remaining deferred refinements. refs #111 #112 |
||
|
|
2957561c30 |
fix(chart): mean-decimate the bounded exposure series instead of a min/max band
The serve-time decimation (#108) reduced every series by per-bucket min/max. That is the right envelope for a cumulative equity curve but wrong for the bounded exposure stream (C10, f64 ∈ [-1,+1]): over a multi-year window each ~hundreds-of-bars bucket straddles sign flips, so min/max is ±1 in nearly every bucket and the exposure collapses to a solid -1..+1 band that reads as per-point oscillation — even for a calm strategy (a 50/200 member flips only 0.81% of bars yet still bands out). Make decimation tap-aware (RED-first, #111): a new `ReduceKind {MinMax, Mean}` on each Series, set by the chart builders via `reduce_for_tap` (exposure -> Mean, else MinMax). `decimate` honours it — a Mean series emits the per-bucket mean (its net/duty-cycle level) in both spine slots, a MinMax series keeps min/max. The shared spine, the equity rendering, and the page payload are unchanged (reduce is server-side only, `#[serde(skip)]`). Verified on real GER40 1y M1: the served exposure series goes from a ±1 band to a smooth net-level line in [-0.38, +0.34]. Rendering the min/max envelope honestly (range bars / OHLC, vs today's polyline) is the deferred other half — filed as #112. Verified: cargo test --workspace = 447 passed / 0 failed (incl. the RED-then-GREEN decimate_mean_reduces_a_bipolar_series_to_its_bucket_level); clippy -D warnings clean. closes #111 refs #112 |
||
|
|
e01bdc3d5e |
audit(visual-world-cut-2): cycle close 0062 — ledger records cut 2 (#102 + #108)
Cycle-close tidy for visual-world-cut-2 (decimation #108 + run-context header #102).
Architect drift review (53eeba5..HEAD): the only drift was a STALE LEDGER — the C22
section still listed #102 (run-context header) and #108 (decimation) as deferred/open
after they shipped in
|
||
|
|
476342d7b1 |
feat(chart): decimate served pages + run-context header (visual World cut 2)
Hardens the families-comparison chart page (#107) so it is openable and legible on real multi-year data. Two halves, both confined to the CLI render path (engine + registry untouched, C14): #108 — serve-time min-max decimation. A pure `decimate(ChartData, buckets)` transform partitions the shared union-ts spine into <=~2000 buckets and emits each series' per-bucket min+max (nulls preserved), bounding the served page to a few thousand points regardless of the underlying M1 point count. Applied in emit_chart on both the single-run and family paths; a no-op under budget (every existing fixture). Full recorded data stays on disk; only the page is thinned. For a walk-forward family the null-fill blow-up collapses for free (an all-null bucket stays null). Deterministic (C1). #102 — a run-context header. A `ChartMeta` (name/commit/window/broker/seed/taps, + member count for a family, + bound params for a single run) is built from the RunManifest in build_chart_data / build_comparison_chart_data (which gain a `name` arg), injected into window.AURA_TRACES.meta, and rendered by a new pure buildHeader in chart-viewer.js (headless .mjs-guarded). The family window is the SPAN across members (min from, max to), not the first member's window — the only reading that labels a disjoint walk-forward family's true OOS coverage (it collapses to the shared window for sweep/MC). Reuses the existing render_value for param display (no new Scalar Display, keeping C7's tag-only param plane). The earlier "manifest is NOT carried into the page" render pin is flipped to a positive one. Verified: cargo test --workspace = 446 passed / 0 failed; cargo clippy --workspace --all-targets -D warnings clean. New coverage: 5 decimate unit tests, single-run + family meta wiring (incl. the span-window invariant), a buildHeader headless guard, and two end-to-end cli_run tests pinning the served-page meta at the binary boundary. closes #108 closes #102 |
||
|
|
12bfa50a4f |
plan: 0062 visual-world-cut-2 — decimation + run-context header
Three tasks on the aura-cli render path + a workspace gate: - Task 1 (#102, compile-atomic): ChartMeta + ChartData.meta, built from RunManifest in build_chart_data/build_comparison_chart_data (gain a `name` param), every call site threaded (emit_chart + 3 comparison unit tests + the render test helper), the manifest re-injected into window.AURA_TRACES, the negative render pin flipped. - Task 2 (#102): #ctx CHART_HEAD slot + a pure buildHeader in chart-viewer.js with a node-driven headless guard pair (chart_viewer_header.{mjs,rs}). - Task 3 (#108): a pure decimate(ChartData, buckets) min-max transform applied in emit_chart on both paths; engine + registry untouched (C14). - Task 4: cargo test --workspace + clippy -D warnings. refs #108 refs #102 |
||
|
|
a8e11e7733 |
spec: 0062 visual-world-cut-2 — decimation + run-context header (boss-signed)
Served-page hardening for the families-comparison chart, bundling two issues that share one code locus (the aura-cli render path) and one purpose (C22/C14: make the #107 view openable + legible on real multi-year families): - #108 — serve-time min-max decimation as a pure ChartData->ChartData transform in emit_chart (both single-run and family paths), bounding the served page to a few thousand points regardless of the underlying M1 point count; full data stays on disk; the WFO null-fill page collapses for free. Engine/registry untouched. - #102 — a ChartMeta threaded from RunManifest into window.AURA_TRACES, rendered by a new pure buildHeader in chart-viewer.js (headless .mjs guarded), showing name/window/broker/commit/taps (+ member count for a family). Signed under /boss on the Step-5 grounding-check PASS (10/10 load-bearing assumptions ratified by green tests). refs #108 refs #102 |
||
|
|
f51d54a1c1 |
docs(cell): name Scalar as the param-authoring surface; no tagged Cell ctors by design
Cell is the kind-erased hot-path carrier (C7): the value carries no tag, so there are deliberately no tuple-style/tagged constructors (`Cell::I64(..)`). Document on the struct that Scalar is the value to reach for when authoring a param / bind / sweep point / RunManifest field, and that the from_* ctors are the carrier-side entry. Resolves the discoverability papercut without softening the carrier split. closes #100 |
||
|
|
5a01179fd5 |
docs(ledger): record the analysis meta-level as an open thread pointing to #109
Add an "Open architectural threads" entry that ties together aura's differentiator (C21): the composable-orchestration rebuild (sweep/MC/walk-forward as composable tools, not hard-wired CLI verbs) + the project-as-crate authoring layer (aura new / experiment-builder API / Aura.toml / cdylib loading, C16/C17). Records the load-bearing open fork — "driven via the CLI" = running a Rust-authored experiment (compatible with C16/C17/C22) vs wiring nodes through the CLI itself (a deliberate amendment to C17/C22/C9) — to be resolved in the brainstorm before any cycle. Keeps the meta-level in focus in the always-loaded design memory; #109 is its tracker home. No design decided here (the fork is recorded, not resolved). refs #109 |
||
|
|
53eeba5e16 |
audit(families-view): cycle close 0061 — drift-clean, ledger records the comparison view
Architect drift review over 99fd32b..HEAD (the housekeeping commits #99/#98 plus the 0061 cycle): code drift-clean. Engine untouched (aura-engine + aura-core diff empty — C9/C14); C21 realized faithfully (the comparison view consumes a set of runs, a family is the first producer, no orchestration-axis rebuild); C18/C10 refuse-don't-guess honoured (every new error path exits 2, never panics; all six tracing entry points guarded before persist, so name resolution is total). The only finding was a ledger-realization gap, lifted in this commit. Ledger (docs/design/INDEX.md): added a C22 amendment recording the families-comparison view as realized (#107) — aura chart <family> overlays one tap across a family's members on a shared y-scale, union-ts spine giving one mechanism / three correct readings, the write-guard totalising name resolution; reconciled the two stale "families-comparison ... remain open" lines (the #104 note and the open-threads list) and named the deferred follow-ons (#102 run-context header, #47 multi-column tap selection, the orchestration-composability rebuild). Regression: cargo test --workspace green (no failures), clippy --workspace --all-targets -D warnings exit 0, cargo build --workspace clean — verified by the orchestrator before close. No baseline to update (aura's regression gate is the gated determinism suite, which is part of the workspace tests). Retire the cycle's ephemeral spec + plan (0061). refs #107 |
||
|
|
4c64feb9ed |
feat(chart): aura chart <family> overlays a family's members; --tap + write-guard
`aura chart <name>` now branches on the name's on-disk kind: a single run charts
its taps as before; a FAMILY overlays one tap (default equity) across all its
members in one self-contained uPlot page, consistent across sweep / MC /
walk-forward. Members share ONE y-scale (they measure one quantity — that is what
makes the overlay comparable, unlike the single-run overlay of different taps),
and align on the union-ts spine via the existing join_on_ts: sweep/MC members
share the window (true overlay), walk-forward members are disjoint OOS windows
(null-complementary -> the stitched curve). One mechanism, three correct readings.
- build_comparison_chart_data (one labelled Series per member, shared y_scale_id);
filter_to_tap (single-run --tap restricts to one tap; no --tap = all taps,
unchanged); parse_chart_args (`<name> [--tap <t>] [--panels]`, any order);
emit_chart name-kind branch; USAGE updated.
- Write-guard: ensure_name_free is now called once per tracing command (run /
run --macd / run --real -> Run; sweep / mc / walkforward -> Family) so a name
cannot be reused across kinds — name resolution stays a total function.
Refuse-don't-guess throughout (exit 2, never panic). Engine untouched (C9/C14).
Tasks 2-3 of plan 0061; Task 1 (registry) landed in
|