Task-by-task plan for spec 0082: the VolSlippageCost node (Task 1) and the
CostSum aggregator (Task 2) in aura-std; the run-path wiring as one compile-gate
task (Task 3) — stage1_r_graph cost-param widening + vol-proxy hoist + the
CostSum-aggregated cost block + the --slip-vol-mult thread-through across all
call sites, with a CLI composition test and the no-cost golden floor; and the
node-level exact-sum composition tests (Task 4). RED-first per task. The
&'static str CostSum port names are interned (COST_SUM_PORTS, mirroring
COL_PORTS); summarize_r is unchanged (it folds the CostSum aggregate).
refs #148
Cycle 2 of the "Cost-model graph (in R)" milestone (#148): the milestone's
real architectural claim — the cost graph composes. Two pieces:
- VolSlippageCost (aura-std): a second, state-dependent cost node whose
per-trade charge scales with a measured volatility input (k * vol /
|entry - stop|), distinct from ConstantCost's flat charge. The vol is an
independent short-horizon realized range, deliberately NOT the stop's own
vol — scaling slippage by the vol the stop already normalizes would collapse
cost-in-R to a constant (indistinguishable from ConstantCost).
- CostSum (aura-std): the cost-graph output node — sums N cost nodes' 3-field
cost-in-R records per-field into one aggregate record. So summarize_r and the
net_r_equity LinComb(4) read the AGGREGATE and stay structurally unchanged
(one home for cost; n=1 is the identity, keeping the cost path uniform).
Run-path wiring: --cost-per-trade and --slip-vol-mult combine, their costs
summing into the net-R curve; a hoisted vol proxy keeps the single-feed pattern.
A no-cost run stays byte-identical (C18 golden floor). Scope is the run path;
sweep/walkforward/mc still pass None (sweep-path cost remains deferred), and the
general CostNode trait stays deferred (designed against two shipped nodes later).
Auto-signed under /boss on the grounding-check PASS: all 8 load-bearing
existing-behaviour assumptions tie to named green tests. Fork decisions recorded
on #148.
refs #148
Cycle 0081 (cost-model graph, cycle 1) closes drift-clean against C10: the
architect review confirmed ConstantCost + the net-R seam realize the contract
at its floor (C9 cost node; net_r_equity a C8/C18 sink; R-pure; one home for
cost, subsuming the retired scalar round_trip_cost; pure feed-forward C1), and
the no-cost baseline is byte-identical (C18 golden held). Regression gate green
and unchanged: full workspace suite 0-fail, clippy --workspace --all-targets
-D warnings clean. No baseline moved -> no ratify.
Ledger sync (docs/design/INDEX.md): added the C10 cycle-0081 realization note
(ConstantCost node + net_r_equity seam shipped; scalar round_trip_cost retired;
run-path-scoped; later-cycle deferrals named).
Drift swept (cycle-adjacent + cheap one-liners):
- crates/aura-analysis/src/lib.rs net_expectancy_r doc: the removed in-fold
mechanic (mean(R - round_trip_cost/latched_dist)) -> the cost-stream fold.
- crates/aura-std/src/lib.rs module doc: "realistic broker profiles" (retired
by #116) -> the legacy SimBroker pip yardstick + cost-model nodes.
Backlogged (pre-existing, broader, predates this cycle): the
docs/project-layout.md backtest worked example is stale on #117 (exposure->bias)
and #116 (realistic broker); a proper rewrite is filed as #151, not folded here.
Architect-accepted: the #[allow(dead_code)] on r_col::ENTRY_PRICE/STOP_PRICE
(their production reader legitimately removed when summarize_r stopped recovering
latched_dist; still the layout contract + exercised by test fixtures).
Spec + plan ephemera removed (git rm docs/specs/0081, docs/plans/0081).
refs #148
Lands the first cycle of the "Cost-model graph (in R)" milestone: cost is
now a composable in-graph node, not a post-run scalar (C10).
- ConstantCost (aura-std): a flat round-trip cost per trade, emitted in R
as a 3-field record {cost_in_r, cum_cost_in_r, open_cost_in_r} isomorphic
to PositionManagement's R-triple. R-pure: cost_in_r = cost_per_trade /
|entry-stop|; notional cancels, no equity held.
- summarize_r folds the cost stream into net_expectancy_r, replacing its
scalar round_trip_cost (one home for cost, no double-count). Positional
co-temporal join; an empty stream is the gross-R baseline.
- net_r_equity tap (stage1_r_graph): LinComb(4) over [cum_realized_r,
unrealized_r, -cum_cost_in_r, -open_cost_in_r] -> Recorder, a sibling of
r_equity; --cost-per-trade on the run path wires the node + tap + fold.
- A no-cost run is byte-identical: no net_r_equity tap, net == gross, the
C18 golden unchanged.
Tests: exact-subsumption (the node-stream net == mean(r_i - cost_i) over
ALL trades incl. the window-end open one), the in-graph net_r_equity tap +
net < gross E2E, the no-cost golden held. Full suite green; clippy clean.
Implement-time decisions ratified (recorded on #148): the cost stream is
recorded 3-wide (the node's full output; summarize_r reads col 0/2, col 1
feeds the in-graph net curve) — resolving a 2-wide/3-wide inconsistency in
the plan's Step 3; Trade drops its `latched` field (cost now arrives in R),
so r_col ENTRY_PRICE/STOP_PRICE become #[allow(dead_code)] layout contract.
Scoped to the run path (non-reduce); sweep/walkforward/mc pass None — cost
on the reduce-mode sweep path and OOS-pooling cost are a deferred follow-up.
Known pre-existing concern (not this cycle): aura-std lib.rs module doc
still names "realistic broker profiles", retired by the C10 rework.
refs #148
Task-by-task plan for spec 0081: the ConstantCost node (aura-std), the
summarize_r scalar->cost-stream change (the workspace-wide call-site
migration as one compile-gate task), and the run-path wiring (net_r_equity
tap + --cost-per-trade) — RED-first per task. Scoped to the run path
(non-reduce); sweep/walkforward/mc pass None this cycle.
refs #148
Cycle 1 of the "Cost-model graph (in R)" milestone (#148): a single
ConstantCost node (flat round-trip cost per trade) plus the net-R seam —
an in-graph cost_in_r stream, a net_r_equity sink (sibling of r_equity),
and summarize_r folding that one stream into net_expectancy_r (replacing
the scalar round_trip_cost). A run with no cost stays a byte-identical
gross-R baseline. Defers the general CostNode trait, the cost-graph
composite-builder, data-grounded factors, and the conviction axis.
Auto-signed under /boss on the grounding-check PASS: all load-bearing
existing-behaviour assumptions are ratified by green tests. Fork
decisions recorded on #148.
refs #148
Follows the C10 rework (29cdc8c). C15: the deploy-grade geometry fields now defer
to the cost model's currency→R normalization / the live deploy edge, not the
retired Stage-2 realistic broker. Glossary: "realistic broker" and "Stage 1 /
Stage 2" marked superseded (kept as history); bias/broker/equity-stream/position-
table/R/R-metrics/R-evaluator/RiskExecutor/Sizer/sim-optimal-broker/veto reworked
to the cost-model-in-R model; added cost model, gross R, net R, net-R equity
curve, conviction-based risk allocation.
Reworks the C10 contract per a ratified in-context design discussion (#116). The
historical "realistic broker" with authored frictions is retired as
horseshoe-throwing: real friction (slippage, swaps, spread) is not historically
knowable; the only reliable friction is measured forward against a real broker.
In its place:
- Cost is a composable C9 graph of cost nodes, in R, that approximates (never
claims) realism — generalizing/subsuming the scalar round_trip_cost. Output is
a net-R equity curve (gross R = signal, net R = after cost).
- Research is pure feed-forward: the Sizer and compounding leave the loop; the
z^-1 fill-edge register and the flat-1R-vs-compounding structural axis are gone.
Compounding/Kelly/drawdown are post-hoc transforms at the deploy/account layer.
- Executor = stop-rule + position-management in R. size/volume is a deploy
concept; the position-event table is demoted to a deploy/reconciliation artifact.
- Conviction-based risk allocation survives as an R-aggregation axis (flat vs
|bias|-weighted), distinct from the removed money-Sizer.
- Money, a real broker, and cTrader Open API are a separate, later live/deploy
edge (C11 record-then-replay; C13 frozen deploy) — the only ground truth.
Stage-1 R realizations preserved as history; the #117 reframe's R spine survives,
its Stage-2/currency/register/compounding portions superseded.
Cycle 0080 was a 44-line behaviour-preserving re-export relocation along the
exact engine↔domain seam the 0079 architect review just blessed, so the drift
review is carried from 0079 rather than re-dispatched; the only drift introduced
is the ledger note, synced here. Regression gate green and unchanged: full suite
665/0 (incl. the C18 goldens), clippy --workspace --all-targets -D warnings
clean.
Ledger sync (docs/design/INDEX.md): added a cycle-0080 realization note —
FamilySelection/SelectionMode are now in aura-analysis (engine-side extraction
complete); recorded the user-ratified decision that the aura-registry
Metric/metric_cmp/deflation vocabulary stays in the registry by design (it is
the trading selection layer, its deflation statistics branch irreducibly on
metric identity, and genericity would be a one-implementor abstraction); and
that making RunReport generic / a domain-agnostic orchestration substrate is
deferred to the World/C21 layer, explicitly out of #136.
No baseline moved (the suite is the regression gate; green-unchanged), so no
ratify statement. No spec/plan ephemera to drop.
The last trading-domain types still defined in aura-engine's report module —
the selection-provenance types FamilySelection and SelectionMode — move to
aura-analysis, finishing the #136 goal of getting the trading types out of the
engine. aura-analysis is the only non-cyclic home that removes them from the
engine: RunManifest.selection: Option<FamilySelection> needs the type and the
engine already depends on aura-analysis; aura-registry and aura-cli reach them
unchanged via aura-engine's re-export (report.rs pub-re-exports them, lib.rs
untouched). Their tests stay in report.rs (one round-trips through RunManifest,
which stays in the engine) and reach the types via the re-export.
Behaviour-preserving (C1): full suite 665/0 unchanged incl. the C18 goldens,
clippy clean, only report.rs and aura-analysis/src/lib.rs change.
Staying in the engine (trace-coupled, deferred to the World/C21 layer, not
#136): RunManifest, RunReport, summarize. The aura-registry Metric/metric_cmp
vocabulary stays in the registry by design — the registry is the trading
selection layer, not the domain-free engine (the deflation statistics branch on
metric identity, irreducibly domain). See the #136 thread for the rationale.
Architect drift review (3e8fd73..HEAD): the extraction is faithful and
behaviour-preserving — every moved symbol's definition left report.rs, 35 unit
tests relocated with their subject, aura-engine/src/lib.rs byte-unchanged, the
four downstream crates byte-unchanged. No code defect. Regression gate green:
cargo build/test --workspace = 665 passed / 0 failed (incl. the C18 cli_run
goldens and the registry legacy-line loads); clippy --workspace --all-targets
-D warnings clean.
Ledger sync (docs/design/INDEX.md) — pre-existing crate-topology drift the
extraction made visible:
- C10 notes (cycles 0063/0068) placed PositionAction / PositionEvent /
derive_position_events "in aura-engine beside RunMetrics / summarize_r";
cycle 0079 moved all of them to aura-analysis — corrected, with a forward
pointer to the new C16 note.
- C10 cycle-0065 note claimed "RiskExecutor ships as a public aura-engine
composite-builder" and "Composites live in aura-engine ... aura-engine ->
aura-std a normal dependency"; both stale — composites live in
aura-composites and aura-std is now an aura-engine [dev-dependencies] only.
Corrected.
- Added a C16 realization note recording the aura-analysis extraction as an
architecture fact: the engine<->domain seam, the current five non-node
workspace crates, and the deferred #136 generic-over-M tail.
serde_json moved from [dependencies] to [dev-dependencies] in aura-analysis
(architect debt item): the production types derive serde, but JSON rendering
lives in aura-engine's RunReport::to_json, so serde_json is test-only here and
must not enter the frozen artifact's library link (C16 frozen-artifact
scrutiny). Build + the 35 crate tests stay green after the move.
No baseline moved (the suite is the regression gate; it stayed green), so no
ratify statement. No spec/plan ephemera to drop (compiler-driven / agent-driven
cycle — no docs/specs or docs/plans files were created).
aura-engine is documented (C16) and named as the reusable, domain-agnostic
reactive SoA substrate, yet `report.rs` concentrated the trading-domain
analysis layer inside it. This cycle relocates the pure-domain leaf into a new
`aura-analysis` crate (deps: aura-core + serde only), erecting the
engine/domain seam before the Stage-2 broker milestone deposits more
currency/position-event code into the same module. Behaviour-preserving (C1):
the full workspace suite stays green unchanged (665 passed), including the C18
on-disk byte-identity goldens (cli_run, the registry legacy-line loads).
Moved to aura-analysis (bodies verbatim — no float expression re-associated):
RunMetrics, RMetrics (+ its hand-written PartialEq), the private r_col column
indices + SQN_CAP, summarize_r, r_metrics_from_rs, derive_position_events,
PositionAction (+ From/TryFrom<i64>), PositionEvent, and the deflation stats
helpers inv_norm_cdf / expected_max_of_normals. Their unit tests and private
helpers (r_row / r_row_full / pm_row) relocate with them — tests live with
their subject (the r_col helpers reach the private module directly, which a
re-export cannot bridge).
Stays in aura-engine for this cycle: the generic trace plumbing (ColumnarTrace,
join_on_ts, JoinedRow, f64_field), RunManifest, RunReport, and summarize (it
bridges recorded trace columns into RunMetrics, so it is trace-coupled). The
mis-placed orchestration types FamilySelection / SelectionMode also stay
(their relocation is entangled with the registry-vocabulary fork, deferred).
report.rs pub-re-exports the moved symbols, so aura-engine's lib.rs re-export
block and every `crate::report::X` / `aura_engine::X` consumer
(aura-registry, aura-cli, aura-composites, aura-ingest) compile byte-unchanged;
only the new crate, the workspace member list, aura-engine's Cargo.toml dep,
and report.rs itself change.
Deferred to later cycles (genuine design forks, route through specify when
reached): making RunReport generic over a metric type M and re-bounding
sweep/mc/walkforward; relocating the aura-registry Metric / metric_cmp / rank
vocabulary; relocating FamilySelection / SelectionMode. Fork decisions logged
on #136.
Resolves the four non-bug findings from the inferential-validation milestone
fieldtest (the bug — the broken-pipe panic — is fixed in 1088320):
- [friction] generalize refusals now carry a reason instead of a bare usage dump:
<2 instruments, a multi-value candidate flag, a missing knob, a duplicate
symbol, and a non-stage1-r strategy each get a one-line why (mirroring the
R-only metric refusal). Two parser tests strengthened to pin the message.
- [friction] --select <argmax|plateau:mean|plateau:worst> added to the
walkforward branch of the global USAGE (aura --help) — the plateau objective
was undiscoverable from the only help the CLI prints (the per-subcommand usage
already carried it).
- [spec_gap] C1 gains a scope note: bit-identity is per-run; a derived metric
recomputed by two different command paths may differ by floating-point
reassociation (<=1 ULP), which is not a C1 violation — ratifies the
fieldtester's reading of the sweep-vs-generalize sqn difference.
- [spec_gap] the milestone vocabulary is added to docs/glossary.md:
cross-instrument generalization, deflated score, overfit probability,
neighbourhood score, plateau selection, sign-agreement, worst-case floor.
The fieldtest triage spec (docs/specs/fieldtest-*) is removed now its findings
are resolved; the tracked fieldtests/ scenario corpus stays.
Verified: cargo test --workspace green (0 failed), clippy --workspace
--all-targets -D warnings clean; `aura --help` shows --select; the refusals
print their reason.
refs #146
GREEN for the broken-pipe RED test (c750b9f). Rust's runtime sets SIGPIPE to
SIG_IGN at startup, so a println! to a closed stdout pipe returned EPIPE and
panicked ("failed printing to stdout: Broken pipe", exit 101). Restore the
default SIGPIPE disposition once at the top of main() (cfg(unix), via
libc::signal) so a closed pipe terminates the process cleanly on SIGPIPE
(signal 13 / exit 141 — the conventional Unix CLI outcome, e.g. `yes | head`)
instead of panicking. One process-level reset covers every family-emitting
subcommand (sweep / mc / walkforward / runs family / generalize); the print
sites are untouched (minimal fix).
libc added as a direct aura-cli dependency — already transitive in Cargo.lock,
the standard vetted crate for signal disposition, admitted under the per-case
dependency policy.
Verified by the orchestrator: the broken-pipe test passes 6/6; `aura sweep | head`
exits 141 with no panic in stderr; cargo test --workspace green (0 failed);
clippy --workspace --all-targets -D warnings clean.
refs #146
Captures the pre-existing broken-pipe panic surfaced by the milestone fieldtest
([bug] finding): every multi-line subcommand (sweep / mc / walkforward /
runs family / generalize) panics "failed printing to stdout: Broken pipe
(os error 32)" and exits 101 when the stdout reader closes early (| head,
| less, a closed UI pane), instead of terminating cleanly. Root cause: Rust's
runtime resets SIGPIPE to SIG_IGN at startup, so a write to a closed pipe
returns EPIPE and println! panics rather than the process dying quietly.
crates/aura-cli/tests/cli_broken_pipe.rs spawns `aura sweep`, closes the stdout
read end mid-stream, and asserts no panic in stderr + exit != 101. Verified RED
(reproduces 6/6). The GREEN fix follows in the next commit.
refs #146
Milestone-close gate for "Inferential validation (defend against false
discovery at sweep scale)". Downstream-consumer exercise from the public
interface only (CLI --help, ledger, glossary, examples — never the source).
All four end-to-end scenarios PASS — the milestone delivers its promise:
trials-deflation (#144), plateau-over-peak (#145), and cross-instrument
generalization (#146) all reach the output and read sensibly, and the
composed "is this edge real or overfit?" verdict is honest (scenario 4: a
GER40 in-sample winner sqn 0.994 does NOT generalize — worst_case -0.149 on
EURUSD, sign_agreement 2/3 → instrument-specific / likely overfit).
Milestone status roll-up: bugs_found — NOT green, so the milestone is NOT yet
closeable (the green roll-up is the functional leg of the close gate). Findings:
- [bug] a family-emitting command piped into an early-closing reader
(`aura sweep|walkforward|mc|runs family … | head`) panics "Broken pipe
(os error 32)" on stdout (exit 101 under pipefail) instead of exiting
cleanly on EPIPE. Pre-existing (not a #146 regression — reproduced on
`aura sweep | head -1`); surfaced by the milestone commands. -> debug (RED-first).
- [friction] two generalize refusals (single --real symbol; multi-value
candidate flag) dump bare usage with no reason. -> tidy iteration.
- [friction] the --select plateau objective is absent from `aura --help`. -> tidy.
- [spec_gap] sweep vs generalize recompute the same member's sqn with a stable
1-ULP difference; the ledger's C1 / "recomputable" is silent on cross-command
metric agreement. -> ratify (ledger note).
- [spec_gap] the milestone vocabulary (deflated_score / overfit_probability /
worst_case / sign_agreement) is absent from docs/glossary.md. -> docwriter.
Artefacts: docs/specs/fieldtest-milestone-inferential-validation.md +
fieldtests/milestone-inferential-validation/ (README + 4 scenario scripts).
The existing fieldtests/ tree was untouched.
refs #146
Architect drift review (d50a482..feat): drift_found — documentation only,
NO contract violation. C1/C9/C10/C23 verified preserved against the diff:
`generalization` is a pure deterministic fold (registry-side, C9; R-only,
C10); the sole `instrument = Some` production site is `run_generalize`, every
other manifest constructor threads `None` + `skip_serializing_if`, so
sweep/mc/walkforward/standalone manifest bytes are byte-identical (C23); the
compat read-mirror was threaded in lockstep.
Drift fixed in this commit (ledger sync, docs/design/INDEX.md):
- The inferential-honesty thread read "now two of three pieces built" / "#146
remains the last open piece" — false now that #146 landed. Updated to record
the inferential half as structurally complete (all three built, cycle 0078)
+ the #146 realization, including the aggregator/validator-vs-selector
distinction (#146 is a recomputable aggregate, not a FamilySelection
annotation).
- C18 realization block: added the cycle-0078 note — the comparison axis (C12)
realized as FamilyKind::CrossInstrument + the first-class RunManifest.instrument
lineage field.
Regression: no regression scripts configured (project facts name only
Build/Test/Lint/Doc); architect is the gate. cargo test --workspace green
(0 failed) and cargo clippy --workspace --all-targets -D warnings clean,
orchestrator-verified.
Ephemera removed per the project lifecycle convention: docs/specs/0078-* and
docs/plans/0078-* (git rm). Durable rationale lifted to the ledger above.
refs #146
Last piece of the Inferential-validation milestone (#146): a new
`aura generalize` subcommand grades how consistently a single candidate
holds across a set of instruments — the across-instrument axis of the
same anti-false-discovery concern as trials-deflation (#144, within one
family) and plateau-over-peak (#145, within one instrument's surface).
Aggregator/validator, not a third selector (user-ratified): it runs a
brought candidate (a single-cell stage1-r grid — --fast/--slow/--stop-
length/--stop-k pinned to single values) across an instrument list
(--real SYM1,SYM2,...), collects per-instrument R-metrics, and reduces
them to a worst-case floor (min over instruments) + a sign-agreement
count + the per-instrument breakdown. R-only (C10 — R is the only
account/instrument-agnostic unit); the worst-case min is the cross-
instrument sibling of PlateauMode::Worst and satisfies the non-
domination constraint by construction (a strong instrument cannot lift
a min). Rejected: a cross-instrument t-stat mean/std*sqrt(M), which
reintroduces the pooled mean the milestone warns against.
- RunManifest gains a first-class `instrument` lineage field (serde-
widened, C14/C18 — legacy lines load as None, existing manifest bytes
unchanged); the compat read-mirror threads it in lockstep.
- FamilyKind::CrossInstrument (C12 comparison axis); the M per-instrument
runs persist as a CrossInstrument family, each member self-identifying
via its stamped instrument. The aggregate is recomputable from the
members (McAggregate precedent), printed live.
- The generalization reduction lives registry-side beside optimize_
deflated/optimize_plateau (C9), reusing resolve_metric/metric_value/
is_r_metric; check_r_metric is the data-free pre-check the CLI runs
before evaluating any instrument.
- Additive: existing sweep/walkforward/mc/standalone paths stay byte-
identical (C23 — instrument omitted when None via skip_serializing_if).
Verified by the orchestrator: cargo test --workspace green (0 failed —
the new generalization_*, check_r_metric, parse_generalize_*, and the
gated cross-instrument E2E all pass; the E2E ran the real GER40/USDJPY
path on this host, exit 0); cargo clippy --workspace --all-targets
-D warnings clean.
closes#146
Three tasks for #146: (1) RunManifest.instrument serde-widened field +
compat-mirror lockstep + thread all in-workspace literals + back-compat tests;
(2) aura-registry FamilyKind::CrossInstrument + the R-only generalization
reduction (worst-case floor + sign-agreement + breakdown) + check_r_metric +
two RegistryError variants; (3) aura-cli generalize subcommand (single-cell
stage1-r candidate x instrument list), data-free arity/metric refusals, a
gated-skip cross-instrument E2E.
fieldtests/ confirmed outside the workspace compile set; FamilyKind has no
exhaustive match (serde-derive); only RegistryError's Display match is a gate.
refs #146
Last piece of the Inferential-validation milestone (#146): grade how
consistently a brought candidate holds across a set of instruments.
Aggregator/validator, not a third selector (user-ratified "mach es so",
decision + derivations minuted on #146): a new `aura generalize` subcommand
runs a single candidate (single-cell stage1-r grid) across an instrument list,
collects per-instrument R-metrics, and reduces them to a worst-case floor
(min over instruments) + a sign-agreement count + the per-instrument breakdown.
R-only (C10 — R is the only cross-instrument-comparable unit); the worst-case
min is the cross-instrument sibling of PlateauMode::Worst and satisfies the
non-domination constraint by construction. New FamilyKind::CrossInstrument (C12)
+ a first-class RunManifest.instrument lineage field (serde-widened, C14/C18);
the reduction lives registry-side beside optimize_deflated/optimize_plateau (C9).
Additive — existing sweep/walkforward/mc paths stay byte-identical (C23).
grounding-check PASS: all 9 current-behaviour assumptions ratified by green tests.
refs #146
Architect drift review over 9c6b9c7..cb32658 returned drift_found (3 items);
all resolved here. What holds (architect): C23 default-argmax byte-identical,
C9 separation (selector in aura-registry, walk_forward selection-agnostic),
C1/C2 (plateau pure/deterministic/in-sample), one higher_is_better direction
source.
Resolutions:
- [medium] Backfilled the pre-0077 FamilySelection legacy-load fixture
(report.rs family_selection_loads_a_pre_0077_legacy_line). The feat commit
and the struct doc-comment claimed "a legacy line still loads (C14/C18)" as
landed, but no test pinned the field-level mapping: bare deflation scalars ->
Some(...), absent plateau keys -> None. families.jsonl/runs.jsonl are
append-only, so a winner stamped pre-0077 must read back; now pinned.
- [low] Synced the ledger's inferential-validation open thread
(docs/design/INDEX.md): #145 plateau-over-peak landed (cycle 0077), selection
is now a pluggable objective (bare argmax -> trials-deflated -> plateau) on
the manifest carrier; only #146 (cross-instrument generalization) remains of
the milestone's three pieces.
- [medium-high] Removed the cycle ephemera (git rm docs/specs/0077-*,
docs/plans/0077-*) per the cycle-close convention.
Regression: full workspace suite green and clippy --all-targets -D warnings
clean (verified at feat-review time). No baseline scripts configured; the test
+ clippy gates are the regression gate. Cycle 0077 tidy (clean) — drift-clean,
not a milestone close (the milestone fieldtest is a separate deliberate gate).
Add an opt-in plateau selection objective for walk-forward, recorded on the
same RunManifest.selection carrier #144 introduced. Default argmax is
byte-identical (C23); plateau is reached only via a new --select flag.
What landed:
- FamilySelection / SelectionMode reshape (report.rs): the selection RULE
(mode) and its ANNOTATION are orthogonal — deflation fields become Option
(present iff Argmax), plus PlateauMean/PlateauWorst variants and
neighbourhood_score/n_neighbours (present iff Plateau*). Legacy lines still
load: the deflation scalars deserialize from bare values via serde default
(C14/C18). compat.rs embeds FamilySelection by value — reshape flows through
untouched.
- GridSpace::axis_lens() + SweepBinder::sweep_with_lattice (engine): the grid
radixes in param_space()/odometer order. sweep() delegates to
sweep_with_lattice with the lattice dropped, so every existing .sweep()
caller is byte-unchanged.
- optimize_plateau + PlateauMode + closed_neighbourhood (aura-registry, C9):
each member scores as the mean/worst of its closed mixed-radix grid
neighbourhood's metric_value; the winner is the smoothed argmax by the
metric's direction (earliest-odometer tie, as optimize). Pure, no RNG (C1);
in-sample only (C2). A higher_is_better helper now sources the per-metric
direction once, shared by metric_cmp and optimize_plateau.
- CLI --select <argmax|plateau:mean|plateau:worst> (default argmax; unknown
token exits 2). walkforward_family dispatches via a select_winner helper;
sweep_over/stage1_r_sweep_over carry the lattice as Option<Vec<usize>>.
runs-family display gains a plateau(<mode>)=<score> over <n> cells line.
RandomSpace-refuse: walkforward has no --random producer, so the
plateau-without-lattice guard is structural — select_winner returns the
refuse on a None lattice (the caller prints the message and exits 2),
unit-tested at the helper, not via a --random E2E (decided + recorded on #145
comment 1974, with the lattice-seam fork).
Tests: plateau-picks-plateau-not-spike, mean-vs-worst, lower-is-better
direction-flip, closed-neighbourhood golden, single-member degeneracy, C1
determinism; CLI select-parse, select_winner refuse; E2E argmax-byte-identical
(C23), plateau:mean and plateau:worst provenance. Full workspace suite green;
clippy --all-targets -D warnings clean.
closes#145
Executable plan for cycle 0077 (#145), decomposed into 5 tasks with the
compile-gates sequenced:
1. Reshape FamilySelection/SelectionMode across 3 crates (mechanical,
byte-preserving): deflation fields -> Option, add PlateauMean/PlateauWorst
+ neighbourhood_score/n_neighbours; thread optimize_deflated + all tests +
the runs_family display (argmax bytes unchanged).
2. GridSpace::axis_lens() + SweepBinder::sweep_with_lattice (sweep delegates,
every existing .sweep() caller byte-unchanged).
3. optimize_plateau + PlateauMode + closed_neighbourhood (mixed-radix grid
neighbourhood, argmax of the smoothed surface) + a higher_is_better refactor
so direction lives in one place.
4. CLI --select parse + Selection threading + sweep-entry Option<lattice>
tuples + select_winner (the RandomSpace-refuse guard) + walkforward_family
dispatch; all signature-change callers threaded before the build gate.
5. runs-family plateau E2E golden.
Two plan-time forks were derived and recorded on #145 (comment 1974): the
lattice surfaces at the engine terminal (only the post-resolve_axes GridSpace
holds param_space order), and the RandomSpace-refuse is a structural guard
unit-tested at select_winner (walkforward has no --random producer to E2E).
refs #145
Second cycle of the inferential-validation milestone. Adds an opt-in plateau
selection objective: optimize_plateau beside optimize/optimize_deflated scores
each grid member by the mean/worst of its grid-neighbourhood metric and argmaxes
the smoothed surface (mixed-radix neighbours over GridSpace axis_lens, passed as
an argument not a SweepFamily field). Strictly opt-in via --select
<argmax|plateau:mean|plateau:worst>, default argmax (C23 byte-preserving).
Reconciles the shared RunManifest.selection carrier with #144's landed reality:
the selection RULE (argmax vs plateau) and the deflation ANNOTATION are orthogonal,
so FamilySelection is extended — deflation fields become Option, SelectionMode gains
PlateauMean/PlateauWorst (the reserved slot), and neighbourhood_score/n_neighbours
are added. A small wire change to the one-cycle-old type; legacy lines still load.
RandomSpace plateau refused (exit 2 — no lattice); kNN deferred.
Grounding-check PASS (12 assumptions ratified). refs #145
Architect drift review (c192dfd..a295905): drift_found, medium/low only — no
ledger or hot-path contract crossed. C23-additive, C1-deterministic, C14/C18
back-compat all verified green (full workspace suite + clippy). No regression
scripts configured (test+clippy are the gate).
Resolution:
- [medium] three spec-promised optimize_deflated tests were missing (the
centring control, the all-noise high-p companion, the C2 IS-only read) — the
centring was unprotected ("removing it would pass every test"). Plus an
unguarded dispersion-floor sign assumption. FIXED in 0076.tidy (ecc9541).
- [low, RATIFY] the ledger's "Open architectural threads" inferential entry was
stale on one fact (it described optimize as having no trials penalty). Synced:
it now records that #144's trials-deflation landed this cycle (additive
optimize_deflated, winner unchanged — C23; optimize/rank_by stay bare argmax),
that #139's per-candidate OOS bootstrap landed in 0075, and that #145/#146
remain open. This ratifies the cycle-0076 intent; the gap description is
preserved for the still-open default-ranker path.
Deferred (spec 0076 Out of scope, not drift): n_eff effective-trials advisory,
reload-time recompute of the R-arm statistic, deflated-score re-ranking, CLI
flags for the resampling config.
Ephemeral spec + plan (docs/specs/0076-*, docs/plans/0076-*) removed per the
cycle-close convention. refs #144
Audit (architect, cycle 0076) found three tests the spec's testing strategy
promised but the implement-loop did not land, plus an unguarded sign assumption:
- optimize_deflated_centres_the_null_so_a_uniform_edge_is_not_called_overfit:
the load-bearing protection for the mean-subtraction. A family where every
member shares the same +R edge reads as NOT overfit only because the null is
centred; an uncentred null would give ≈0.5. Removing the centring now flips a
test (it previously passed every test — the architect's headline finding).
- optimize_deflated_all_noise_family_has_high_overfit_probability: the high-p
companion to the existing clear-edge low-p test.
- optimize_deflated_provenance_reflects_only_the_passed_family: C2 at the
registry boundary (n_trials == family size, raw == the family's own argmax),
pinning that the record encodes only in-sample information.
- A debug_assert in the dispersion-floor arm: it subtracts inflation, valid only
for higher-is-better metrics; lower-is-better (max_drawdown/bias_sign_flips)
would deflate wrong-signed. Unreached from the two shipped call sites, but
optimize_deflated is public — guard the assumption rather than leave it implicit.
cargo test -p aura-registry (33) + clippy green. refs #144
Adds optimize_deflated beside optimize in aura-registry: the in-sample
walk-forward selection now records, on each OOS winner's manifest, how much
the winner's metric is inflated by the size of the search it won — a deflated
score and (R arm) an overfit probability — without changing which member wins
(additive, C23; a regression test pins optimize_deflated's winner == optimize's).
- R arm (sqn_normalized/expectancy_r/...): a centred moving-block reality-check.
Each member's per-trade R series is mean-subtracted to impose the no-edge null,
resampled via the r_bootstrap kernel (extracted to a shared resample_block,
byte-identical), and the best-of-K null-max gives
overfit_probability = (count(null >= raw)+1)/(n+1) and
deflated_score = raw - p95(null). Deterministic given the seed (C1).
- total_pips arm: a closed-form expected-max-of-K dispersion floor
(inv_norm_cdf + expected_max_of_normals), no probability.
The record (FamilySelection on RunManifest) rides the proven
serde(default, skip_serializing_if) widening + a compat.rs mirror, so legacy
runs.jsonl/families.jsonl lines load unchanged and unstamped sweep/mc/standalone
manifests stay byte-identical (C14/C18). The SelectionMode enum reserves a
Plateau slot for #145. CLI: walk-forward stamps both arms; runs family <id> rank
surfaces a human-readable deflated/overfit line.
Quality review (re-dispatched after the implement-loop's quality gate exhausted)
caught a real defect: on the R arm with positive resamples but no member carrying
trade_rs (a family loaded from disk — trade_rs is serde-skipped), the null was
all-NEG_INFINITY, turning deflated_score into +inf. Fixed by collapsing "no usable
null" (zero resamples OR no trade_rs) into one degenerate floor (deflated == raw,
overfit == 1.0); pinned by optimize_deflated_no_trade_rs_floors_instead_of_infinity.
Verified: cargo test --workspace and cargo clippy --workspace --all-targets
-D warnings both green. Frictionless Stage-1 R; n_eff advisory and reload-time
recompute deferred (spec 0076 Out of scope).
closes#144
First cycle of the inferential-validation milestone. Adds optimize_deflated
beside optimize: an additive selection-provenance record (deflated score +
overfit probability) stamped on the OOS winner's manifest, never re-ranking
(C23). R arm = centred moving-block reality-check reusing the r_bootstrap
kernel; total_pips arm = expected-max-of-K dispersion floor. Shared
RunManifest.selection carrier with a SelectionMode::Plateau slot reserved
for #145.
refs #144
Architect drift review (c99e7b8..2e77ab1) against the design ledger +
CLAUDE.md invariants: drift-clean, debt only — no contract or spec
violation. Verified intact: C18 wire shape (trade_rs is #[serde(skip)] +
excluded from the hand-written PartialEq; runs.jsonl/families.jsonl bytes
and digests unmoved; mc_r_bootstrap is a printed line, never a record),
C1 determinism (seeded r_bootstrap + disjoint walkforward windows, both
pinned by byte-identical-rerun e2e tests), C7 frictionless Stage-1
(round_trip_cost stays 0.0), C8/C12 streaming reduction (IS sweep folds
O(trades)/member, OOS run non-reduce). The verbatim summarize_r /
r_metrics_from_rs float duplication is guarded by the cross-reducer
equality test — sound.
Tidy applied this close:
- doc-link drift: the new E[R] notation in r_bootstrap / RBootstrap /
run_mc_r_bootstrap doc-comments was parsed by rustdoc as an intra-doc
link `[R]` (4 warnings). Wrapped in backticks; doc build now clean.
- coverage micro-gap [low, architect]: added a tripwire asserting a
populated trade_rs is absent from the serialized JSON (the serde(skip)
C18-preservation property had no direct test — only Vec::new() was
serialized before).
Carry-on (benign debt, not fixed): the mc usage literal carries a `usage:`
prefix the sibling sweep/walkforward usages omit — required because the
new `["mc", rest @ ..]` arm now handles the `--real` rejection and the
preserved golden asserts stderr.contains("usage"); churning the golden to
drop the prefix is not worth it.
Removed the cycle's ephemeral spec + plans (docs/specs/0075,
docs/plans/0075, docs/plans/0076) per the lifecycle rule.
Verified: cargo test --workspace 610 passed / 0 failed, clippy -D warnings
clean, cargo doc --no-deps clean.
closes#139
Family-selection across a sweep has no false-discovery control (no
trials-deflation, plateau-over-peak, or cross-instrument generalization);
recorded as the World's third unbuilt half. refs #144#145#146#139
Add `aura mc --strategy stage1-r [--real <SYM>]`: run the stage1-r
walk-forward, pool its OOS per-trade R series, and report a moving-block
bootstrap E[R] distribution / CI (one `mc_r_bootstrap` line with an `e_r`
quantile block + P(E[R]<=0)). Completes spec 0075's Monte-Carlo half on top
of iter 1's walk-forward R-reporting.
Engine: `r_bootstrap(rs, n_resamples, block_len, seed) -> RBootstrap` — a
deterministic moving-block bootstrap (non-circular, final block truncated;
block_len=1 = i.i.d. trade-shuffle; clamped to [1,n]; empty -> all-zero),
reusing the existing MetricStats/quantile and SplitMix64.
CLI: parse_mc_args/McArgs split — bare/`--name`/`--trace` route to the
unchanged synthetic seed-resweep `run_mc` (byte-for-byte preserved); the R
path accepts ONLY `--strategy stage1-r`, so a bare `--real` stays a usage
error (mc_rejects_real_flag_with_usage_exit_2 preserved, doc-comment
refreshed). run_mc_r_bootstrap reuses walkforward_family + RMetrics.trade_rs.
Corrective fixes over the plan snippets, both verified: the mc usage string
carries a `usage:` token (the new `["mc", rest @ ..]` arm now handles the
`--real` rejection, and the preserved golden asserts `stderr.contains
("usage")`); `#[allow(clippy::large_enum_variant)]` on McArgs keeps the
unboxed Stage1RGrid field layout.
Scope: single `--real <SYM>` per invocation; cross-symbol pooling is a
deferred follow-on (needs a multi-symbol grammar). Verified: cargo build
clean, full `cargo test --workspace` green (609 passed), clippy -D warnings
clean; bare `aura mc`, SMA walkforward, stage1-r single-run, and C18
round-trip goldens all preserved.
refs #139
Iteration 2 of cycle 0075: the engine primitive r_bootstrap (moving-block
bootstrap over a flat R series, reusing MetricStats/SplitMix64); the CLI
parse_mc_args/McArgs split (synthetic seed-resweep preserved byte-for-byte;
the --strategy stage1-r real-R path reuses iter-1's walkforward_family +
RMetrics.trade_rs); run_mc_r_bootstrap prints one mc_r_bootstrap line. The
mc real-R path accepts only --strategy stage1-r, so a bare --real stays a
usage error (mc_rejects_real_flag_with_usage_exit_2 preserved).
refs #139
Make `aura walkforward --strategy stage1-r [--real <SYM>]` roll IS->OOS
windows, sweep the stage1-r grid in-sample, pick the winner by an R metric
(sqn_normalized), run it out-of-sample, and report per-window + pooled OOS
R-metrics. The bare SMA walkforward path is byte-identical (its dispatch arm
is verbatim today's body; the pooled `oos_r` block is emitted only when a
window carries an `r` block).
Engine:
- RMetrics gains an in-memory `trade_rs: Vec<f64>` (realised R per closed
trade), excluded from serde (`#[serde(skip)]`) and from a hand-written
PartialEq, so the C18 wire shape and every existing equality assertion /
round-trip stay unchanged. summarize_r now retains the per-trade R vector
it used to drop.
- New `r_metrics_from_rs(&[f64])` reduces a flat (pooled across-window) R
series to RMetrics. Its R-distribution arithmetic is copied verbatim from
summarize_r (the byte-pinned floats must not be algebraically refactored);
the two copies are guarded in lockstep by a cross-reducer equality test.
net_expectancy_r = expectancy_r (exact at the Stage-1 cost=0 invariant);
conviction_terciles_r = [0,0,0] (per-trade conviction is not pooled).
CLI:
- walkforward gains --strategy + the four stage1-r grid flags (reusing
parse_csv_list / Stage1RGrid); walkforward_family is strategy-dispatched.
- Windowed stage1-r helpers: stage1_r_sweep_over (reduce-mode folded IS
sweep, O(trades)/member) and run_oos_r (non-reduce OOS run, for the
stitched pip-equity curve), plus stage1_r_space.
Frictionless Stage-1 R (costs are Stage-2). Verified: cargo build clean,
full `cargo test --workspace` green (SMA walkforward, synthetic mc,
stage1_r_single_run_output_golden, and the C18 round-trips all preserved),
clippy -D warnings clean. Monte-Carlo R-bootstrap is iter 2.
refs #139
Make the World layer's two OOS validation verbs candidate-aware and
R-reporting instead of locked to the SMA sample blueprint:
- aura walkforward --strategy stage1-r --real <SYM>: roll IS->OOS, sweep
the stage1-r grid in-sample, pick by an R metric, run OOS, report
per-window + across-window-pooled OOS R-metrics.
- aura mc --strategy stage1-r --real <SYM>: moving-block bootstrap of the
pooled OOS per-trade R series -> E[R] distribution / CI.
Frictionless Stage-1 R (costs are Stage-2). Bare walkforward/mc paths and
their goldens preserved byte-for-byte. Grounding-check PASS; signed under
/boss (design forks recorded on the reference issue).
refs #139
Architect audit: drift-clean. The ledger and source now point at the recorded
geometry sidecar; no live reference to the removed authored floor survives in
code or any live contract. Refuse-don't-guess preserved.
Recorded debt (non-blocking, accepted):
- fieldtests/milestone-runway references the removed instrument_spec; a frozen
non-workspace snapshot, left untouched per the tracked-fieldtests convention.
- cli_run.rs test fn names retain unspecced/vetted vocabulary (plan prescribed
substring-only edits); cosmetic.
#124 stays open for the deferred override/floor tiers the Stage-2 broker needs.
Drops the ephemeral cycle spec + plan (rationale lifted to the ledger).
The data-server now ships per-symbol geometry sidecars, so aura no longer
hard-codes instrument geometry. A consumption grep showed production reads
exactly one `InstrumentSpec` field — `pip_size` — at the real-data path; the
other eight (incl. `instrument_id`) were constructed but never read.
- aura-cli resolves the real-path pip from the recorded sidecar
(`instrument_geometry` -> `InstrumentGeometry.pip_size`), refusing (exit 2)
on absent geometry. The authored table no longer gates: any symbol with a
sidecar + bars now runs (new test: USDJPY, never vetted, resolves its JPY
pip 0.01 from the sidecar).
- Removed the redundant authored floor: `InstrumentSpec`, `instrument_spec`,
`VETTED_SYMBOLS`, and the table-only tests. The `InstrumentGeometry` seam
(the surviving pip source) and its nameability guard are kept.
- `instrument_id` (the "find out"): no production consumer. The position-event
table's `instrument_id` is a decoupled caller parameter; the engine never
imports an instrument spec. Dropped with the struct.
- The example `pip_size_of` is re-sourced to the sidecar; the pure-structural
blueprint tests use a literal pip to stay archive-independent.
Speculative deploy-grade fields and the #124 override/floor tiers are deferred
to the Stage-2 broker that will consume them, read from the sidecar geometry
then. Ledger (C8/C15/#22/#106) updated; #124 collapses to "recorded sidecar
geometry -> refuse" in the meantime.
Verified: cargo test --workspace (all green), clippy -D warnings clean, cargo
doc clean, with the local archive present so the sidecar paths run.
refs #124
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