Resolve the real-path pip from the data-server geometry sidecar
(instrument_geometry) and remove the redundant authored floor
(InstrumentSpec / instrument_spec / VETTED_SYMBOLS) plus its table-only
tests. Consumption grep: only pip_size is read in production;
InstrumentSpec.instrument_id has no consumer (the position-event table's
id is a decoupled caller parameter). Speculative geometry fields are
deferred to the Stage-2 broker, read from the sidecar when needed.
Signed via grounding-check PASS (all assumptions tied to currently-green
tests, executed with the archive present). Decisions recorded on #124;
#140 closed as obsolete.
refs #124
Cycle-close audit: drift-clean on all load-bearing invariants (C7/C8/C15 hold; geometry stays non-scalar metadata, the Source seam still carries only (Timestamp, Scalar); scope boundary held — no resolver, quote_currency still &'static str, both #124's).
Tidy: completed the VETTED_SYMBOLS consolidation the feat commit over-claimed — migrated the two remaining inline vetted-symbol arrays in the crate-internal unit tests (instrument_spec_pip_value_matches_contract_times_pip, instrument_spec_ids_are_distinct) to the const, so #140 now extends one list.
Ledger: lifted this cycle's durable rationale into the C15 realization note (docs/design/INDEX.md) — the floor now carries tick_size/digits, and the recorded-metadata tier-2 source (instrument_geometry over symbol_meta -> neutral InstrumentGeometry, cross-checked against the authored floor) now exists at the ingestion edge; the tier-composing resolver remains #124.
Drop the ephemeral cycle spec + plan (docs/specs, docs/plans) per the lifecycle convention; durable rationale now in the ledger + the #143 thread.
refs #143
Make data-server's neutral InstrumentGeometry a first-class, broker-agnostic input at the aura-ingest edge: re-export InstrumentGeometry + a thin instrument_geometry(server, symbol) accessor over symbol_meta (the recorded dataset-metadata tier 2 of #124's hierarchy; the tier-composing resolver stays #124). Add the two deferred deploy-grade InstrumentSpec fields tick_size + digits (additive, struct stays Copy; C15), authored for the 5 vetted symbols from the verified sidecars. A new real-data cross-check asserts the authored floor agrees with provider geometry (pip_size, contract_size==lot_size, the C10 pip_value invariant, tick_size, digits, quote_currency), iterating the vetted set so #140's additions are covered for free; skip-if-archive-absent.
Scope (derived, recorded on #143): no runtime resolver and no quote_currency type change — both remain #124's. 'quote_currency beyond FX' is satisfied at the geometry tier (InstrumentGeometry.quote: String), not by widening the &'static str floor.
Beyond the plan (implementer additions, reviewed and accepted): hoisted the vetted-symbol list to one pub const VETTED_SYMBOLS, collapsing the three drift-prone copies plan-recon flagged (#140 now extends one constant); a tick_size == 10^(-digits) consistency guard (verified to hold across all 30 provider sidecars, so it will not false-trip on #140); a re-export-nameable seam test guarding the InstrumentGeometry re-export from silent removal.
Verification: cargo test --workspace all green; cargo clippy --workspace --all-targets -D warnings clean. The cross-check ran against the live Pepperstone archive (not skipped) — all 5 vetted sidecars match the authored table.
refs #143
Three tasks at the aura-ingest edge: (1) re-export InstrumentGeometry + a thin instrument_geometry accessor over symbol_meta; (2) two additive InstrumentSpec fields tick_size/digits (compile-gated literal update); (3) a real-data cross-check of the authored floor vs provider geometry, iterating the vetted set.
refs #143
data-server #3 (closed) shipped DataServer::symbol_meta -> InstrumentGeometry (the per-symbol .meta.json reader). Bump 0f5e665 -> 694f96f2 so aura-ingest can consume the neutral geometry for the #143 cross-check. The bump alone changes no behaviour (the new API is unused until the #143 code lands).
refs #143
Consume data-server's neutral InstrumentGeometry at the aura-ingest edge to cross-check the hand-authored vetted InstrumentSpec floor (#113) against provider truth, and add the two deferred deploy-grade fields (tick_size, digits) the sidecars now supply.
Scope fork derived and recorded on #143: this cycle does NOT build #124's runtime resolver and does NOT change quote_currency's &'static str type (the geometry tier carries non-FX currency as String). Auto-signed autonomously under /boss on a grounding-check PASS (6/6 assumptions ratified); reply to the run to veto.
refs #143
The EWMA Bollinger-band mean-reversion fade is refuted: no cross-index
generalization (FRA40 leans positive, GER40 negative — two correlated indices
disagreeing in sign), no OOS-significant cell on either index, and its lone
IS-significant cell is consistent with multiple-testing noise (full verdict on
#137). The machinery (--strategy stage1-meanrev, the composed EWMA Bollinger
band, zero new nodes) is permanent and kept; only the per-cycle spec/plan are
dropped per the ephemeral-artifact lifecycle convention.
refs #137
Third price-only Stage-1 R strategy: aura sweep --strategy stage1-meanrev fades
deviation from a rolling mean (price above mean+k*sigma -> short, below -> long,
latched +-1), feeding the unchanged bias -> RiskExecutor (vol-stop = R) -> flat-1R
seam. sigma = Sqrt(Ema((price-Ema(price))^2)) reuses the vol_stop deviation-
squared-then-smoothed shape, so the band is built from existing aura-std nodes
(Ema/Sub/Mul/Sqrt/LinComb/Add/Gt/Latch) -> ZERO new nodes. No Delay: the current
bar legitimately belongs to its own band (causal, C2). Swept knobs: --window (one
Ema length ganged across mean+variance) and --band-k; downstream vol-stop R knobs
unchanged.
An in-file test reads the SHIPPED graph's exposure tap to pin the fade polarity
(short above the band, long below) — a latch-set/reset copy-paste from breakout
would invert the signal yet leave the fold-vs-raw and window-grid CLI tests
green. All 588 workspace tests pass (existing R/sma/momentum/breakout goldens
byte-identical); clippy clean.
refs #137
Hand-wired EWMA Bollinger-band fade subgraph (price -> Ema mean + dev -> Mul
square -> Ema var -> Sqrt sigma -> LinComb k-band -> Add/Sub bands -> Gt/Gt ->
Latch/Latch -> Sub bias). Pins the fade direction (above mean+k*sigma -> short,
below -> long), the latch hold, and flat-series -> no fade. Green on arrival
(composes only existing aura-std nodes; no new node), so it is a characterization
pin rather than a RED->GREEN unit.
refs #137
A third price-only Stage-1 R candidate: an EWMA Bollinger-band mean-reversion
fade (price vs rolling mean +/- k*sigma, latched +-1, sign inverted vs breakout).
Composed entirely from existing aura-std primitives (the vol_stop sigma pattern)
-> zero new nodes. Screens the mean-reversion hypothesis under the same #137
lie-detector bar after the two trend candidates (momentum, breakout) were
refuted. Grounding-check PASS (9/9 load-bearing assumptions ratified).
refs #137
The Donchian channel-breakout Stage-1 R candidate is refuted: no cross-index
generalization, violent IS->OOS sign flips at long channels, no significant
positive cell (full verdict recorded on #137). The breakout machinery
(RollingMax / RollingMin nodes, --strategy stage1-breakout) is permanent and
kept; only the per-cycle spec/plan are dropped per the ephemeral-artifact
lifecycle convention.
refs #137
A second Stage-1 R strategy candidate for the edge-research milestone, a structurally
different trend mechanic than the refuted SMA-momentum: a Donchian channel breakout.
Swaps only the signal leg of the stage1-r graph —
close -> Delay(1) -> {RollingMax,RollingMin}(N) -> {Gt,Gt} -> {Latch,Latch} ->
Sub = bias in {-1,0,+1} — keeping the vol-stop R definition unchanged, so it screens
under the identical R yardstick (clean A/B vs momentum).
- aura-std: new RollingMax / RollingMin nodes (sliding-window extremum via a monotonic
deque, O(1) amortized; mirror the Sma/Delay ring-buffer shape, warm-up skip-emit,
C7/C8). The +/-1 direction latch composes from two existing Latch nodes + Sub (no new
latch type).
- aura-cli: stage1_breakout_graph + a manual-grid stage1_breakout_sweep_family
(fully-bound graph per point, sidestepping parameter-ganging since one channel length
drives both rolling nodes); --strategy stage1-breakout + --channel grid flag; usage
strings updated.
- Causality (C2): one Delay(1) on close feeds both rolling nodes, so each channel covers
close[t-N..t-1] — the current bar is excluded. Pinned by a contrastive e2e test (a
strictly rising series up-breaks every warmed bar; a window including the current bar
never could) plus a +/-1-latch-hold test.
All existing goldens byte-identical (stage1-r/sma/momentum untouched); folded-no-trace
== raw-trace metrics; workspace tests + clippy green.
refs #137
Implementation plan for the Donchian breakout candidate (parent spec
docs/specs/0071-stage1-breakout.md). Four dependency-ordered tasks: RollingMax and
RollingMin (monotonic-deque sliding extrema, mirroring the Sma/Delay node shape), a
C2-causality + ±1-latch composition test, and the CLI breakout strategy
(stage1_breakout_graph + manual-grid sweep family + --strategy/--channel wiring).
refs #137
Donchian channel-breakout as the next Stage-1 R research candidate, after the
SMA-momentum family was refuted (cross-index non-generalization + no within-symbol
significance; #137/#141). Swaps only the signal leg of stage1_r_graph:
close -> Delay(1) -> {RollingMax,RollingMin}(N) -> {Gt,Gt} -> {Latch,Latch} ->
Sub = bias in {-1,0,+1}; the vol-stop defines R unchanged (clean A/B vs momentum).
Two new aura-std nodes (RollingMax/RollingMin, monotonic-deque sliding extremum);
the +/-1 direction latch composes from two existing Latch nodes + Sub. New
--strategy stage1-breakout + --channel grid flag; the family iterates the grid
with fully-bound graphs (compile_with_params(&[]) + Harness::bootstrap), sidestepping
parameter-ganging. Delay(1) is the C2 causality guard (channel excludes current bar).
Grounding-check PASS: all 10 load-bearing assumptions ratified by named green tests.
Auto-signed under /boss (grounding-check PASS = the signature).
refs #137
Cycle 0070 (streaming sink reductions, #138) close. Durable rationale lifted to
the ledger: C8 now records the end-of-stream `finalize` lifecycle (the second node
phase beside `eval`), its `Harness::run` topo-order epilogue, and its C1/C7/C8
reconciliation — with the open `Recorder`->`Probe` / per-cycle-allocation question
(#77) cross-referenced, since `finalize` is the "non-channel exit" that issue's
accumulate-then-read option named as missing. The per-cycle spec and plan are
git-rm'd (ephemeral, valid only for their cycle).
Audit (architect) found no invariant breakage; noted debt, left as-is: the
two-mode `stage1_r_graph` (reduce vs raw sink wiring) rests on the CLI-seam
equivalence test rather than structural unification, and `SeriesReducer.last_ts`
carries the last *warm* cycle's ts (latent only if `--trace` ever folds).
refs #138
Completes the M3 fix for BLOCKER #138. The no-trace stage1-r sweep now folds its
reductions online: `stage1_r_graph(reduce=true)` wires `SeriesReducer` (equity,
exposure -> one summary row each) and `GatedRecorder` (gated on
`closed_this_cycle` -> only the trade rows + the final row), and the folded
consumer rebuilds `RunMetrics` from those compact outputs. Per-member memory drops
from O(cycles) (~2 GiB over ~5.5M one-minute bars) to O(trades), so full-history
multi-member sweeps no longer exhaust RAM+swap. The `r_equity` tap is omitted when
reducing (it is consumed only by the `--trace` persistence path).
`reduce = trace.is_none()`: the `--trace` path and the single `run_stage1_r` keep
the raw recorders + `summarize`/`summarize_r` over full rows — the byte-for-byte
reference. A new CLI-seam test
(`sweep_strategy_stage1_r_folded_no_trace_metrics_equal_raw_trace_metrics`) proves
the folded no-trace sweep reports byte-identical per-member metrics to the raw
`--trace` path.
Verified: cargo test --workspace green (42 suites); cargo clippy --workspace
--all-targets -- -D warnings clean; stage1-r goldens byte-identical.
closes#138
M3 machinery for BLOCKER #138: sink reductions can now fold online instead of
buffering every per-cycle row to an unbounded channel.
- aura-core: additive `Node::finalize` end-of-stream hook (default no-op) +
`SeriesFold` online accumulator (last / max_drawdown / sign_flips).
- aura-engine: `Harness::run` calls `finalize` once per node in topo order after
the source loop; `summarize` refactored to drive `SeriesFold` (byte-identical),
the now-dead `sign0` removed.
- aura-std: `GatedRecorder` (emits only gated rows + the final row on finalize)
and `SeriesReducer` (folds one f64 series, emits one summary row on finalize),
both emitting the `Recorder` channel type — no aura-engine dependency.
- Integration tests: folded reductions are byte-for-byte equal to the
raw-recorder path; the R-reduction's peak memory is O(trades), independent of
cycle count.
Ratified two off-plan fixes the implementer made in the tests (both plan defects,
not code defects): the GatedRecorder correctly flushes the final non-gated row on
finalize, so the retained count is K_TRADES + 1; and the heterogeneous PM record
needs kind-correct zero scalars (a blanket f64 zero is rejected by the kind-typed
AnyColumn::push).
Wiring these reducers into the stage1-r sweep (the actual O(cycles)->O(trades)
win) follows next. refs #138
M3 fix for BLOCKER #138: an end-of-stream `finalize` lifecycle on sinks
lets reductions fold online into O(trades)+O(1) state, so full-history
stage1-r sweeps run in bounded memory per member instead of OOM-killing.
Grounding-check PASS; signed autonomously under /boss.
refs #138
Enabling change for the strategy-research run: `aura sweep --strategy
stage1-r` gains four optional comma-separated grid flags `--fast`,
`--slow`, `--stop-length`, `--stop-k` that become the sweep's grid axes
over the four stage1_r_graph knobs (sma fast/slow length, vol-stop
length, vol-stop k). The family is their cartesian product. This lets a
slow time-series-momentum edge be screened across timeframes — Phase 0
showed fast (2/4-bar) signals are chop-dominated noise on M1, so any
momentum edge lives at slower timescales, and the stop timescale must be
tunable alongside the signal.
Mechanism:
- aura-composites: factor vol_stop and risk_executor into a single
shared topology body each (vol_stop_inner / risk_executor_inner), with
the vol-stop knobs either BOUND (the original constant-bind form,
byte-identical) or left OPEN and named `stop_length` / `stop_k` so
their slots surface in param_space as sweepable axes. New
risk_executor_vol_open(risk_budget) is the gridding sibling of
risk_executor(StopRule::Vol{..}, ..). One body, two arms → topology
cannot drift; a member at any (length, k) matches the bound form
byte-for-byte.
- aura-cli: parse_csv_list (generic FromStr, rejects non-numeric/empty),
pure unit-testable parse_sweep_args, all four knobs gridded via
.axis(); absent flags fall back to today's exact defaults (fast {2,3},
slow {6,12}, stop_length {3}, stop_k {2.0}).
Default behaviour byte-identical: all existing goldens/sweep tests stay
green (541 passed, 0 failed; clippy -D warnings clean). Frictionless
Stage-1 R unchanged.
refs #137
Executable-spec for the strategy-research enabling change: expose the
stage1-r sweep's hardcoded timescales as CLI grids so a slow
time-series-momentum edge can be screened across timeframes.
- sweep_strategy_stage1_r_grids_all_four_knobs_from_csv_flags (RED):
`aura sweep --strategy stage1-r --fast 240 --slow 960
--stop-length 240 --stop-k 2.0` must yield a 1-member family whose
member params are fast=240, slow=960, stop_length=240, stop_k=2.0.
Fails today: the four flags are rejected (exit 2), gridding absent.
- sweep_strategy_stage1_r_no_flags_keeps_the_default_grid (guard,
green today): a no-flags sweep still yields the {2,3}×{6,12} 4-member
family with the stop pinned at 3 / 2.0, so existing goldens stay
byte-green once the feature lands.
GREEN side follows via implement mini-mode.
refs #137
Architect audit clean (no drift): the derive is the faithful C10 book
first-difference — Close uses the stored book volume (the actual book), never an
exposure delta (the exact inversion of the abandoned 0064 exposure-integral);
post-run reduction, no in-graph node (C14), positional type-erased Scalar reads
(C7), each event's event_ts is its own cycle (C2), the >1-event-per-instant
reversal case is why it is derived not per-eval (C8/C10); aura-engine -> aura-core
only (instrument_id a caller scalar, no InstrumentSpec import).
- Ledger C10: add a "Realization (cycle 0068, #115)" note — derive_position_events
as the first difference of the executed book, the post-reframe replacement for
the rolled-back 0064 derive; fixed-fractional / currency / equity-feedback
sizing and the realistic brokers stay #116.
- Remove the ephemeral spec + plan (cycle closed; rationale in the ledger + git
history).
Accepted low debt (architect [low]): the stop-then-same-cycle-reopen producer path
emits the same closed&&open shape as a bias-flip reversal and the derive handles it
correctly (it keys on closed/open/direction/size, not exit_reason; unit-covered via
the reversal test), but is not separately asserted end-to-end. Cosmetic; the derive
is producer-agnostic.
refs #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
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
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
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
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#130closes#135
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
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).
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 499121e.
- Ledger: a C10 realization note records the durable rationale -- SQN as the single-number sweep objective, the signal-only grid (the stop defines R, so varying it breaks cross-member SQN comparability -- the SQN100 #130 motivation), the R-metric rank vocabulary, and the manifest-completeness fix.
- Glossary: new 'R metrics' and 'SQN' entries.
- The ephemeral spec + plan are removed; the ledger + git history are the durable record.
Follow-ups filed: #135 (per-member --trace for the stage1-r sweep). refs #133
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
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
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
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
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
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
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
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.
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).
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
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
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
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 (2ee0d74).
Lifted to the ledger (docs/design/INDEX.md):
- A C10 realization note for cycle 0065 recording the built Stage-1 R layer (the
chain, the public RiskExecutor + StopRule axis, the CLI surface, the scale-robust
guard, the aura-engine->aura-std normal dep) and that Stage 2
(currency/compounding/realistic brokers) stays the deferred downstream layer.
- Amended the C10 contract's "pending Stage-1 implementation" line (it landed).
Fixed stale prose introduced by iter-3's dependency-tier change: report.rs and
position_management.rs comments cited "aura-std is only a dev-dependency" as the
rationale for the by-convention dense-record layout contract; iter-3 made it a
normal dependency, so that rationale was false. Reworded to the real,
dep-tier-independent reason - the record crosses the crate boundary as a
type-erased Scalar vector (C4 SoA), read positionally, guarded by stage1_r_e2e.rs.
Carry-on filed as follow-ons (not actioned): the exposure->bias rename tail (the
serde-stable exposure_sign_flips key + slot/param names - commented on #126, the
recommended next iteration); CLI UX friction (#131); stage1-r harness cosmetic
smells (#132); R-sweep families + rank-by-sqn (#133).
This is a drift-clean cycle-close audit, NOT a milestone-container close: the
ephemeral spec/plans stay (spec 0065's change (a) still scopes the deferred rename
tail) and the milestone close stays a deliberate act for the user.
cargo build/test --workspace green; clippy --workspace --all-targets -D warnings clean.
refs #117#126
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
`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
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
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
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
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