rename: retire the stage1-* family for the r-family (r-sma / r-breakout / r-meanrev)
The stage1 ordinal has been dead vocabulary since the C10 reframe dropped the two-stage research model: a 1 structurally implies a 2 that no longer exists. The family is renamed by its live discriminator - the R yardstick - with members named by their signal, uniform with their signal-named siblings (sma, macd, momentum): - selectors: stage1-r -> r-sma, stage1-breakout -> r-breakout, stage1-meanrev -> r-meanrev (old tokens are usage errors, exit 2 - no silent alias) - identifiers: Strategy::RSma/RBreakout/RMeanRev, HarnessKind::RSma, r_sma_*/r_breakout_*/r_meanrev_*, wrap_r, run_signal_r, RGrid, R_SMA_* - persisted identity: the sma_signal composite (param prefix sma_signal.fast.length / .slow.length; fixtures regenerated; content-ids shift - no test pins a literal hash, the registry parses no record names) - e2e test files git-mv'd to r_sma_e2e / r_breakout_e2e / r_meanrev_e2e - dead Stage-1/Stage-2 prose reworded to post-C10 vocabulary across rustdoc, Cargo.tomls, ledger live lines, and the glossary (historical entries stay; fieldtests corpus untouched) - CLAUDE.md invariant 7 rewritten to record the ratified C10 reframe faithfully (the token-swap alone would have laundered the retired gated-currency/realistic-broker design into unmarked live prose); the unbacked account-mode clause dropped Verification: cargo build/test --workspace green (51 targets), clippy -D warnings clean, doc build clean, acceptance grep gate leaves exactly the one resampling-stage false positive (harness.rs), smoke: --harness r-sma runs, --harness stage1-r exits 2 with the new usage line. Decision log: forks and rationale recorded on the issue (reconciliation + implementation-phase comments). closes #174
This commit is contained in:
@@ -84,31 +84,30 @@ design decision, not a refactor, and belongs in the ledger.
|
||||
makes a live external call mid-replay. (See `~/.claude/CLAUDE.md` for the
|
||||
IONOS consent rule: external LLM calls happen at the recording/live-source
|
||||
edge, with explicit per-session consent, never inside a backtest.)
|
||||
7. **Strategy output is a directional bias stream; risk-based execution and
|
||||
realistic brokers are decoupled downstream layers; signal quality is measured
|
||||
in R.** The DAG expresses one state at t (C8: ≤1 record per `eval`), so a
|
||||
strategy's primary, backtestable output is a signed, bounded **bias** `f64 ∈
|
||||
[-1,+1]` per cycle — direction (sign) + conviction (magnitude, optional),
|
||||
**unsized** (not an equity curve, not a position, not a size). Sizing leaves the
|
||||
strategy: a decoupled **risk-based execution** layer `bias → stop-rule → Sizer →
|
||||
Veto → position-management` (the **RiskExecutor** composite, per symbol, nested
|
||||
in a Broker/Account composite) turns bias + a protective **stop** into a sized
|
||||
intent. The **stop defines the risk unit R** (1R = the loss if stopped).
|
||||
**Signal quality is measured in R** (R-multiples / expectancy), the account- and
|
||||
instrument-agnostic yardstick — **Stage 1**: flat-1R sizing, feed-forward, no
|
||||
equity feedback, the primary research loop. **Currency P&L is Stage 2** (deploy
|
||||
viability): fixed-fractional sizing reads equity (compounding) → realistic
|
||||
brokers apply real frictions → currency equity, entered only after `E[R] > 0`.
|
||||
The single feedback (equity → Sizer) is cut by a `z⁻¹` register on the **fill
|
||||
edge** (mark-to-market stays a same-cycle price read), encapsulated in the
|
||||
executor composite; flat-1R vs compounding is a **structural axis** (C11). The
|
||||
broker-independent **position-event table** (`event_ts, action[buy/sell/close],
|
||||
position_id, instrument_id, volume`) remains the **decoupled, derived** Stage-2
|
||||
audit layer — the first difference of the *book* (`deal = target − book −
|
||||
in_flight`), a computed table (not a per-`eval` output, since one decision
|
||||
instant may yield >1 event) — feeding realistic brokers. Brokers/executors are
|
||||
ordinary downstream nodes, never part of the strategy; account mode
|
||||
(netting/hedging) is a composition constraint.
|
||||
7. **Strategy output is a directional bias stream; risk-based execution is a
|
||||
decoupled downstream layer; signal quality is measured in R.** The DAG
|
||||
expresses one state at t (C8: ≤1 record per `eval`), so a strategy's primary,
|
||||
backtestable output is a signed, bounded **bias** `f64 ∈ [-1,+1]` per cycle —
|
||||
direction (sign) + conviction (magnitude, optional), **unsized** (not an
|
||||
equity curve, not a position, not a size). Sizing leaves the strategy: a
|
||||
decoupled **risk-based execution** layer `bias → stop-rule →
|
||||
position-management` (the **RiskExecutor** composite, per symbol; the **Veto**
|
||||
an optional documented pre-trade-gate seam) turns bias + a protective **stop**
|
||||
into a managed position, in R. The **stop defines the risk unit R** (1R = the
|
||||
loss if stopped). **Signal quality is measured in R** (R-multiples /
|
||||
expectancy), the account- and instrument-agnostic yardstick — the research
|
||||
loop is **pure feed-forward**: flat-1R, gross R → net R via the composable
|
||||
cost-model graph (C10), no Sizer, no equity feedback, no `z⁻¹` register.
|
||||
**Money is a live/deploy-edge concern**: currency P&L, fixed-fractional
|
||||
sizing, and compounding are post-hoc money-management transforms of the net-R
|
||||
sequence at the deploy/account layer, and the live broker is an **I/O
|
||||
adapter** at the recording/deploy edge (C11/C13), never part of the strategy.
|
||||
The broker-independent **position-event table** (`event_ts,
|
||||
action[buy/sell/close], position_id, instrument_id, volume`) survives as the
|
||||
**decoupled, derived** deploy/reconciliation audit artifact — the first
|
||||
difference of the *book* (`deal = target − book − in_flight`), a computed
|
||||
table (not a per-`eval` output, since one decision instant may yield >1
|
||||
event).
|
||||
8. **Deploy artifacts are frozen.** Hot-reload (cdylib) is an authoring-loop
|
||||
tool only. The live bot is a statically-linked, versioned, frozen artifact —
|
||||
never hot-swapped (audit trail: this bot = this commit).
|
||||
|
||||
@@ -71,7 +71,7 @@ pub struct RunMetrics {
|
||||
/// `runs.jsonl` lines still deserialise (C14/C18 back-compat).
|
||||
#[serde(alias = "exposure_sign_flips")]
|
||||
pub bias_sign_flips: u64,
|
||||
/// Optional Stage-1 R metrics block. `None` for a pip-only run (and for legacy
|
||||
/// Optional R metrics block. `None` for a pip-only run (and for legacy
|
||||
/// `runs.jsonl` written before this field existed — `serde(default)`); omitted from
|
||||
/// the JSON entirely when absent (`skip_serializing_if`), so the pip-only on-disk
|
||||
/// shape stays byte-unchanged (C14/C18 back-compat).
|
||||
@@ -79,7 +79,7 @@ pub struct RunMetrics {
|
||||
pub r: Option<RMetrics>,
|
||||
}
|
||||
|
||||
/// R-based signal-quality metrics (Stage-1), reduced from a `PositionManagement` dense
|
||||
/// R-based signal-quality metrics, reduced from a `PositionManagement` dense
|
||||
/// record stream by [`summarize_r`]. Account- and instrument-agnostic (pure R). Carries
|
||||
/// the enriched dispersion/churn fields (SQN, conviction terciles, net-of-cost).
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
@@ -129,7 +129,7 @@ impl PartialEq for RMetrics {
|
||||
// Dense `PositionManagement` record column indices — the lockstep contract with
|
||||
// aura-std's FIELD_NAMES/RECORD_KINDS. The record crosses the crate boundary as a
|
||||
// type-erased `Scalar` vector (C4 SoA), so it is read positionally, not by importing
|
||||
// the producer's types; the layout is shared by convention and `stage1_r_e2e.rs`
|
||||
// the producer's types; the layout is shared by convention and `r_sma_e2e.rs`
|
||||
// guards that it matches.
|
||||
mod r_col {
|
||||
pub const CLOSED: usize = 0;
|
||||
@@ -324,7 +324,7 @@ pub fn summarize_r(
|
||||
/// `summarize_r_includes_open_trade_and_matches_r_metrics_from_rs` — touch one copy
|
||||
/// without the other and that test is the sole tripwire. The
|
||||
/// fields a flat R series cannot carry are set honestly: `n_open_at_end = 0`,
|
||||
/// `net_expectancy_r = expectancy_r` (exact under the Stage-1 cost = 0 invariant),
|
||||
/// `net_expectancy_r = expectancy_r` (exact under the cost = 0 invariant),
|
||||
/// `conviction_terciles_r = [0,0,0]` (per-trade conviction is not pooled). Empty
|
||||
/// input -> a well-defined all-zero `RMetrics`.
|
||||
pub fn r_metrics_from_rs(rs: &[f64]) -> RMetrics {
|
||||
@@ -375,7 +375,7 @@ pub fn r_metrics_from_rs(rs: &[f64]) -> RMetrics {
|
||||
n_open_at_end: 0,
|
||||
sqn,
|
||||
sqn_normalized,
|
||||
net_expectancy_r: mean, // cost = 0 -> net == gross (Stage-1 frictionless)
|
||||
net_expectancy_r: mean, // cost = 0 -> net == gross (frictionless)
|
||||
conviction_terciles_r: [0.0; 3],
|
||||
trade_rs: Vec::new(),
|
||||
}
|
||||
@@ -1108,7 +1108,7 @@ mod tests {
|
||||
#[test]
|
||||
fn r_metrics_from_rs_folds_a_flat_series() {
|
||||
// pooled across-window R series [2.0, -1.0, 1.0]: expectancy = 2/3, 2 wins of 3,
|
||||
// profit_factor = (2+1)/1 = 3. At cost 0 (frictionless Stage-1) net == gross.
|
||||
// profit_factor = (2+1)/1 = 3. At cost 0 (frictionless) net == gross.
|
||||
// conviction terciles are not pooled -> [0,0,0]; n_open_at_end is not a pooled
|
||||
// concept -> 0.
|
||||
let m = r_metrics_from_rs(&[2.0, -1.0, 1.0]);
|
||||
|
||||
@@ -12,8 +12,8 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
aura-core = { path = "../aura-core" }
|
||||
aura-engine = { path = "../aura-engine" }
|
||||
# aura-composites: the Stage-1 RiskExecutor / vol_stop composite-builders the
|
||||
# stage1-r harness wires (kept out of aura-engine so the engine stays domain-free).
|
||||
# aura-composites: the RiskExecutor / vol_stop composite-builders the
|
||||
# r-sma harness wires (kept out of aura-engine so the engine stays domain-free).
|
||||
aura-composites = { path = "../aura-composites" }
|
||||
aura-registry = { path = "../aura-registry" }
|
||||
aura-std = { path = "../aura-std" }
|
||||
|
||||
+276
-276
File diff suppressed because it is too large
Load Diff
+227
-227
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
{"format_version":1,"blueprint":{"name":"sma_signal","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}
|
||||
@@ -0,0 +1 @@
|
||||
{"format_version":1,"blueprint":{"name":"sma_signal","nodes":[{"primitive":{"type":"SMA","name":"fast"}},{"primitive":{"type":"SMA","name":"slow"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}
|
||||
@@ -1 +0,0 @@
|
||||
{"format_version":1,"blueprint":{"name":"stage1_signal","nodes":[{"primitive":{"type":"SMA","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}
|
||||
@@ -1 +0,0 @@
|
||||
{"format_version":1,"blueprint":{"name":"stage1_signal","nodes":[{"primitive":{"type":"SMA","name":"fast"}},{"primitive":{"type":"SMA","name":"slow"}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"format_version":1,"blueprint":{"name":"stage1_signal","nodes":[{"primitive":{"type":"Nope","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}
|
||||
{"format_version":1,"blueprint":{"name":"sma_signal","nodes":[{"primitive":{"type":"Nope","name":"fast","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":2}}]}},{"primitive":{"type":"SMA","name":"slow","bound":[{"pos":0,"name":"length","kind":"I64","value":{"I64":4}}]}},{"primitive":{"type":"Sub"}},{"primitive":{"type":"Bias","name":"bias","bound":[{"pos":0,"name":"scale","kind":"F64","value":{"F64":0.5}}]}}],"edges":[{"from":0,"to":2,"slot":0,"from_field":0},{"from":1,"to":2,"slot":1,"from_field":0},{"from":2,"to":3,"slot":0,"from_field":0}],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0},{"node":1,"slot":0}],"source":"F64"}],"output":[{"node":3,"field":0,"name":"bias"}]}}
|
||||
@@ -234,7 +234,7 @@ fn graph_build_renders_role_kind_mismatch_at_finalize_by_identifier() {
|
||||
/// runs and distinct for a structurally different topology. It is the same hash `aura run`
|
||||
/// stamps as `topology_hash` (both via the one `content_id` primitive over the canonical
|
||||
/// `blueprint_to_json` bytes), so a data-authored topology has a stable, comparable content
|
||||
/// id. (Note: an op-script and the Rust `stage1_signal` builder produce *different*
|
||||
/// id. (Note: an op-script and the Rust `sma_signal` builder produce *different*
|
||||
/// canonical forms — composite debug-name, an unbound `Bias.scale` — so they are different
|
||||
/// topologies by the byte definition; content-addressing keys on the canonical form.)
|
||||
#[test]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! `aura-composites` — reusable Stage-1 composite-builders: the volatility stop and
|
||||
//! `aura-composites` — reusable composite-builders for the feed-forward R loop: the volatility stop and
|
||||
//! the per-symbol RiskExecutor. Both are `GraphBuilder` compositions (from
|
||||
//! `aura-engine`) of `aura-std` primitives, so this crate sits *above* both and is
|
||||
//! the single place where the engine's builder and the standard nodes are wired
|
||||
@@ -35,7 +35,7 @@ pub fn vol_stop(length: i64, k: f64) -> Composite {
|
||||
/// are `named` `stop_length` / `stop_k` so their slots surface in `param_space` under
|
||||
/// the `.stop_length.length` / `.stop_k.weights[0]` suffixes). One body, so the
|
||||
/// topology — every node, edge, and exposed role — cannot drift between the two arms;
|
||||
/// only the two knobs differ (the `Option<i64>`-knob idiom `stage1_r_graph` uses).
|
||||
/// only the two knobs differ (the `Option<i64>`-knob idiom `r_sma_graph` uses).
|
||||
fn vol_stop_inner(knobs: Option<(i64, f64)>) -> Composite {
|
||||
let mut g = GraphBuilder::new("vol_stop");
|
||||
let price = g.input_role("price");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! The `RiskExecutor` composite (Stage-1, #128): a per-symbol risk-based executor over a
|
||||
//! The `RiskExecutor` composite (#128): a per-symbol risk-based executor over a
|
||||
//! bias stream — `stop-rule -> Sizer -> position-management`, exposing the dense R-record.
|
||||
//! Proves the composite bootstraps, runs end-to-end, and folds to the SAME R-outcomes as
|
||||
//! the hand-wired iter-1 chain, and that R is invariant under the Sizer's `risk_budget`
|
||||
//! (Stage-1 feed-forward). The Veto is a DOCUMENTED SEAM, not a runtime node (a
|
||||
//! (feed-forward). The Veto is a DOCUMENTED SEAM, not a runtime node (a
|
||||
//! pass-through identity is exactly what C19/C23 DCE deletes), so it appears nowhere here.
|
||||
use aura_core::{
|
||||
Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, Scalar,
|
||||
@@ -14,7 +14,7 @@ use aura_std::{Recorder, PM_FIELD_NAMES, PM_RECORD_KINDS};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
// The dense-record columns this fixture reads, named in lockstep with the sibling
|
||||
// `stage1_r_e2e.rs` (which names `REALIZED_R = 1` the same way) so the cross-crate
|
||||
// `r_sma_e2e.rs` (which names `REALIZED_R = 1` the same way) so the cross-crate
|
||||
// `PM_FIELD_NAMES` layout is never referenced by a bare literal.
|
||||
const REALIZED_R: usize = 1;
|
||||
const SIZE: usize = 10;
|
||||
@@ -100,7 +100,7 @@ fn risk_executor_bootstraps_and_folds_to_expected_rmetric() {
|
||||
assert!((m.expectancy_r - 0.5).abs() < 1e-9, "window-end R = (105-100)/10; got {}", m.expectancy_r);
|
||||
}
|
||||
|
||||
/// Property: R is invariant under the Sizer's `risk_budget` (Stage-1 feed-forward). The
|
||||
/// Property: R is invariant under the Sizer's `risk_budget` (feed-forward). The
|
||||
/// same price path at two budgets yields a bit-identical realised-R ledger, while the
|
||||
/// `size` column scales with the budget — proving size flows through the Sizer into PM yet
|
||||
/// never touches R.
|
||||
@@ -127,7 +127,7 @@ fn risk_executor_r_invariant_under_risk_budget() {
|
||||
}
|
||||
|
||||
/// Property: **a real folded `RMetrics` survives the `RunMetrics.r` serde round-trip
|
||||
/// byte-for-byte (the Stage-1 on-disk back-compat contract), driven from an actual run —
|
||||
/// byte-for-byte (the on-disk back-compat contract), driven from an actual run —
|
||||
/// not a hand-built literal.** A bootstrapped RiskExecutor run is folded by `summarize_r`
|
||||
/// and the result is attached as `RunMetrics.r = Some(..)`; serializing then
|
||||
/// deserializing must reproduce an equal value, and the `r` key must be present. The
|
||||
@@ -148,7 +148,7 @@ fn folded_rmetrics_survives_runmetrics_serde_round_trip() {
|
||||
}
|
||||
|
||||
/// Property: the RiskExecutor embeds the `vol_stop` composite (the new `StopRule::Vol`
|
||||
/// arm) and folds to a finite RMetric — the volatility-defined default the `stage1-r`
|
||||
/// arm) and folds to a finite RMetric — the volatility-defined default the `r-sma`
|
||||
/// harness uses. A short k·σ stop over a rising-then-falling path opens a trade and
|
||||
/// (stop or window-end) closes at least one, so the fold is non-empty and sane.
|
||||
#[test]
|
||||
@@ -246,7 +246,7 @@ fn risk_executor_vol_open_exposes_the_two_stop_knobs_as_free_axes() {
|
||||
|
||||
/// Property (#137): the OPEN vol-stop arm, bootstrapped at a given `(length, k)`, folds to
|
||||
/// the SAME R-outcome as the BOUND arm at the same values — the byte-equivalence the
|
||||
/// no-flags stage1-r sweep relies on to stay golden (the gridding variant only frees the
|
||||
/// no-flags r-sma sweep relies on to stay golden (the gridding variant only frees the
|
||||
/// two knobs; topology and every other constant are unchanged). The open arm's positional
|
||||
/// point is `[length, k]` in `param_space` slot order (length first, then k).
|
||||
#[test]
|
||||
|
||||
@@ -22,7 +22,7 @@ serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# aura-std is a TEST-ONLY dependency: the engine's own integration tests
|
||||
# (stage1_r_e2e, random_sweep_e2e, ger40_breakout) drive real standard nodes through
|
||||
# (r_sma_e2e, random_sweep_e2e, ger40_breakout) drive real standard nodes through
|
||||
# a bootstrapped Harness. The engine's library code never names a concrete node — it
|
||||
# routes type-erased Scalar records (summarize_r reads the PositionManagement record
|
||||
# positionally, by index), so aura-std stays out of [dependencies] and the engine is
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
//! {Gt, Gt} -> {Latch, Latch} -> Sub = bias in {-1,0,+1}. Tests the FADE
|
||||
//! direction (price above mean+k*sigma -> short -1; below mean-k*sigma -> long
|
||||
//! +1), the latched hold, and that no fade fires on a flat series. Built
|
||||
//! straight from aura-std nodes (no CLI dependency); mirrors stage1_breakout_e2e.
|
||||
//! straight from aura-std nodes (no CLI dependency); mirrors r_breakout_e2e.
|
||||
|
||||
use aura_core::{Scalar, Timestamp};
|
||||
use aura_engine::{GraphBuilder, Harness, Source, VecSource};
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Stage-1 R E2E: a synthetic bias+price chain through stop-rule + position-management,
|
||||
//! R E2E: a synthetic bias+price chain through stop-rule + position-management,
|
||||
//! recorded and folded by `summarize_r`. Also guards the dense-record layout contract
|
||||
//! (aura-std's `PM_FIELD_NAMES`/`PM_RECORD_KINDS` vs the column indices `summarize_r`
|
||||
//! reads).
|
||||
@@ -94,7 +94,7 @@ fn synthetic_long_then_stop_produces_a_sane_rmetric() {
|
||||
|
||||
/// Drive the real cross-crate chain `stop -> PositionManagement` over a `(bias, price)`
|
||||
/// path, collecting the producer's dense records (decoded by the producer's own
|
||||
/// `PM_RECORD_KINDS`, the wire contract) into a ledger — the recorded stream a Stage-1
|
||||
/// `PM_RECORD_KINDS`, the wire contract) into a ledger — the recorded stream an R-yardstick
|
||||
/// run would persist. Per-cycle bias (not constant) so a fixture can drop bias to 0 to
|
||||
/// forbid the immediate re-entry after a stop. The fold is left to the caller so the
|
||||
/// same recorded ledger can be summarized at several `round_trip_cost` values.
|
||||
@@ -124,7 +124,7 @@ fn run_chain_ledger(stop: &mut dyn Node, path: &[(f64, f64)]) -> Vec<(Timestamp,
|
||||
|
||||
/// Drive the real cross-crate chain `stop -> PositionManagement -> summarize_r`
|
||||
/// over a `(bias, price)` path. This is the actual producer->consumer seam,
|
||||
/// node-by-node; the resulting `RMetrics` is what a recorded Stage-1 run would report.
|
||||
/// node-by-node; the resulting `RMetrics` is what a recorded R-yardstick run would report.
|
||||
fn run_chain(stop: &mut dyn Node, path: &[(f64, f64)]) -> RMetrics {
|
||||
summarize_r(&run_chain_ledger(stop, path), &[])
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Folded sink reductions are byte-for-byte equal to the raw-recorder path.
|
||||
//! Drives the folding sinks node-by-node (the `stage1_r_e2e.rs` direct-node
|
||||
//! Drives the folding sinks node-by-node (the `r_sma_e2e.rs` direct-node
|
||||
//! style) over a synthetic dense R-record + equity/exposure series, and asserts
|
||||
//! `summarize_r` over `GatedRecorder`'s emitted rows equals `summarize_r` over
|
||||
//! the full record, and a `SeriesReducer` summary equals `summarize`'s fields.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! `PositionManagement` — the stateful heart of Stage-1 risk-based execution. Turns a
|
||||
//! `PositionManagement` — the stateful heart of feed-forward risk-based execution. Turns a
|
||||
//! bias + a protective-stop distance into a stream of realised per-trade R-outcomes.
|
||||
//! Emits ONE dense record per cycle (C8, like `SimBroker`; multi-field output on the
|
||||
//! `Resample` precedent): the trade ledger is the rows where `closed_this_cycle`, the
|
||||
@@ -146,7 +146,7 @@ impl Node for PositionManagement {
|
||||
} else {
|
||||
price.max(p.stop_level)
|
||||
};
|
||||
// size-invariant: R is a pure stop-distance ratio (Stage-1 feed-forward).
|
||||
// size-invariant: R is a pure stop-distance ratio (feed-forward).
|
||||
realized = p.dir as f64 * (fill - p.entry) / p.latched_dist;
|
||||
was_stopped = true;
|
||||
reason = ExitReason::Stop as i64;
|
||||
@@ -157,7 +157,7 @@ impl Node for PositionManagement {
|
||||
closed = true;
|
||||
self.pos = None;
|
||||
} else if bias_exit {
|
||||
// size-invariant: R is a pure stop-distance ratio (Stage-1 feed-forward).
|
||||
// size-invariant: R is a pure stop-distance ratio (feed-forward).
|
||||
realized = p.dir as f64 * (price - p.entry) / p.latched_dist;
|
||||
reason = if sign0(bias) != 0.0 {
|
||||
ExitReason::ReversalLeg as i64
|
||||
@@ -275,7 +275,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// (3) R-invariance under size: the Sizer's `size` flows into col 10 but never into R.
|
||||
// Scaling size leaves every realized_r identical (the property that keeps Stage 1
|
||||
// Scaling size leaves every realized_r identical (the property that keeps the research loop
|
||||
// feed-forward); col 10 reflects the size so we know it actually flowed end-to-end.
|
||||
#[test]
|
||||
fn realized_r_is_invariant_under_size() {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! `Sizer` — the flat-1R sizing seam (C10 Stage-1). Turns a protective-stop distance
|
||||
//! `Sizer` — the flat-1R sizing seam (C10). Turns a protective-stop distance
|
||||
//! into a position `size = risk_budget / stop_distance`: with `risk_budget = 1.0` this is
|
||||
//! true flat-1R (one risk unit per trade) and `size` is inversely proportional to the
|
||||
//! stop — NOT a constant. The `bias` input gates firing (a size is only meaningful when a
|
||||
//! strategy is taking a position) and is the Stage-2 seam: fixed-fractional sizing swaps
|
||||
//! `risk_budget` for `risk_fraction · equity` (an added equity input), same node shape.
|
||||
//! R is computed SIZE-INVARIANTLY downstream, so the Sizer never contaminates signal
|
||||
//! quality — it only scales the (Stage-2) currency exposure.
|
||||
//! strategy is taking a position) and is the live/deploy-edge seam: fixed-fractional
|
||||
//! sizing swaps `risk_budget` for `risk_fraction · equity` (an added equity input), same
|
||||
//! node shape. R is computed SIZE-INVARIANTLY downstream, so the Sizer never contaminates
|
||||
//! signal quality — it only scales the (deploy-edge) currency exposure.
|
||||
use aura_core::{
|
||||
Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder,
|
||||
ScalarKind,
|
||||
|
||||
+17
-17
@@ -614,7 +614,7 @@ the ground truth**. The cost model *approximates*; it **never claims realism**.
|
||||
`SimBroker` (the pip-equity, unsized-exposure node) is the **pre-reframe pip
|
||||
ancestor** — with the net-R cost model it is **redundant as a quality measure** (R
|
||||
supersedes pips). It is retained as a **legacy / simple optional pip yardstick**
|
||||
(still wired in the `stage1-r` harness for an honest dual readout), **not part of
|
||||
(still wired in the `r-sma` harness for an honest dual readout), **not part of
|
||||
the new model and not to be expanded**.
|
||||
|
||||
**Forbids.** Putting **sizing or the stop in the strategy** (bias is unsized; the
|
||||
@@ -737,7 +737,7 @@ co-temporal cost stream (positional 1:1 join — `cost[i]` is `record[i]`'s cycl
|
||||
cost = 0 baseline on an empty stream. The headline sink is **`net_r_equity`** =
|
||||
`LinComb(4)[cum_realized_r, unrealized_r, −cum_cost_in_r, −open_cost_in_r]` → Recorder
|
||||
(C8/C18), a sibling of `r_equity`, emitted only when a cost is authored. Wired on the
|
||||
**run path** via `--cost-per-trade` (`stage1_r_graph`); sweep / walk-forward / mc pass
|
||||
**run path** via `--cost-per-trade` (`r_sma_graph`); sweep / walk-forward / mc pass
|
||||
`None` (cost on the reduce-mode sweep path and cost in OOS pooling deferred — the
|
||||
positional join holds only while one cost node fires in lockstep with PM). Deferred to
|
||||
later milestone cycles: the general `CostNode` trait + multi-node cost-graph
|
||||
@@ -928,8 +928,8 @@ composite-builder (`risk_executor(StopRule, risk_budget)`) with a
|
||||
`StopRule{Fixed,Vol}` **structural axis** (C20); per the 2026-06-28 reframe its
|
||||
Sizer interior and `risk_budget` arg are dropped / vestigial, the **Veto** stays a
|
||||
documented seam, not a runtime node. The layer is operable from the CLI: `aura run
|
||||
--harness <sma|macd|stage1-r>` — a compile-time selector over Rust-authored
|
||||
harnesses (C9/C17) — folds `summarize_r` into `RunMetrics.r`, the `stage1-r`
|
||||
--harness <sma|macd|r-sma>` — a compile-time selector over Rust-authored
|
||||
harnesses (C9/C17) — folds `summarize_r` into `RunMetrics.r`, the `r-sma`
|
||||
harness fanning one bias into both `SimBroker` (legacy pip) and the RiskExecutor
|
||||
(R); an `r_equity` tap charts the by-trade R-equity. Composites live in the
|
||||
dedicated `aura-composites` crate, so `aura-engine`'s runtime dependency stays
|
||||
@@ -937,10 +937,10 @@ dedicated `aura-composites` crate, so `aura-engine`'s runtime dependency stays
|
||||
stays acyclic).
|
||||
|
||||
**Realization (cycle 0066).** SQN is the operational single-number objective for
|
||||
ranking a Stage-1 sweep family by signal quality — C12 **axis-2 (argmax-metric)**
|
||||
ranking an r-sma sweep family by signal quality — C12 **axis-2 (argmax-metric)**
|
||||
over the C18 family store. `metric_cmp` (`aura-registry`) learns the
|
||||
higher-is-better R metrics `sqn`, `expectancy_r`, `net_expectancy_r`; a member
|
||||
with no `r` block sorts last (`NEG_INFINITY`). `aura sweep --strategy stage1-r`
|
||||
with no `r` block sorts last (`NEG_INFINITY`). `aura sweep --strategy r-sma`
|
||||
produces the rankable R family, each member folding `summarize_r` into
|
||||
`RunMetrics.r`. The default grid varies **only the signal** (`fast` / `slow` SMA
|
||||
lengths), holding the stop and sizing fixed: `risk_budget` is R-invariant and
|
||||
@@ -954,8 +954,8 @@ beside the floated knobs (reproducible from its own manifest, C18).
|
||||
(`SQN_CAP = 100`), turnover-robust where raw `sqn` rewards trade count. It is an
|
||||
**opt-in** rank key (`Metric::SqnNormalized`); raw `sqn` and the default ranker
|
||||
stay byte-unchanged, and the field carries `#[serde(default)]` (C18). Below the cap
|
||||
(`n ≤ 100`) it equals raw `sqn` exactly. **#135 (stage1-r `--trace`):**
|
||||
`stage1_r_sweep_family` persists each member's equity / exposure / r_equity under
|
||||
(`n ≤ 100`) it equals raw `sqn` exactly. **#135 (r-sma `--trace`):**
|
||||
`r_sma_sweep_family` persists each member's equity / exposure / r_equity under
|
||||
`runs/traces/<n>/<member_key>/` via the same `persist_traces_r` the single run uses;
|
||||
per-member `--trace` is symmetric across all three sweep strategies.
|
||||
|
||||
@@ -1327,7 +1327,7 @@ one `content_id` primitive `topology_hash` also uses (acc 1); a Tier-1 optional
|
||||
blueprint does not use leaves the id byte-stable (acc 3, composing #156/#164).
|
||||
`serde_json/float_roundtrip` is enabled so stored f64 metrics round-trip exactly through
|
||||
`families.jsonl` — the precondition for a bit-identical compare (C1). **Signal-only this
|
||||
cycle**: the id covers the signal blueprint; the fixed Stage-1-R scaffolding stays
|
||||
cycle**: the id covers the signal blueprint; the fixed r-sma scaffolding stays
|
||||
commit-identified (it is not yet blueprint-data, C24). Whole-harness / structural-axis
|
||||
content-addressing, and whether the id should exclude the composite **debug-name** (a
|
||||
non-load-bearing symbol, invariant 11 — an op-script and the Rust builder produce
|
||||
@@ -1887,7 +1887,7 @@ Tier-2 section to validate it; recorded on #156).
|
||||
**Realization (2026-06-30, cycle 0092 — runs are built FROM blueprint-data, #165).**
|
||||
`aura run <blueprint.json>` now loads a serialized **signal** blueprint
|
||||
(`blueprint_from_json` → the closed `std_vocabulary`; an unknown type fails clean as
|
||||
`UnknownNodeType`, the data-plane face of invariant 9), wraps it in the Stage-1-R run
|
||||
`UnknownNodeType`, the data-plane face of invariant 9), wraps it in the r-sma run
|
||||
scaffolding (sinks / broker / data supplied **at run**, not serialized — C24's deferred
|
||||
set), runs it, and emits a `RunReport` **bit-identical** (C1) to its Rust-built twin —
|
||||
proven by `loaded_signal_runs_bit_identical_to_rust_built`. The `RunManifest` now carries
|
||||
@@ -1895,8 +1895,8 @@ a **`topology_hash`**: SHA256 of the canonical (#164) `blueprint_to_json`, the #
|
||||
reproducibility anchor, a Tier-1 optional field (#156). The hash + helper live
|
||||
research-side (`aura-cli` + `sha2`), off the frozen engine (invariant 8). This is the
|
||||
**keystone of the World/C21 milestone**: topology-as-data is now *runnable*, not only
|
||||
serializable. The harness wrap was made shareable — `stage1_r_graph()` =
|
||||
`wrap_stage1r(stage1_signal(...))`, so the Rust path and the data path are the same seam
|
||||
serializable. The harness wrap was made shareable — `r_sma_graph()` =
|
||||
`wrap_r(sma_signal(...))`, so the Rust path and the data path are the same seam
|
||||
over the same signal (a behaviour-preserving C19/C23 restructure). Scope note: the hash
|
||||
covers the **signal** only (the fixed scaffolding is identified by `commit`); a content-id
|
||||
distinguishing harness-structural variants is a #158/#166 concern once scaffolding varies.
|
||||
@@ -1904,7 +1904,7 @@ distinguishing harness-structural variants is a #158/#166 concern once scaffoldi
|
||||
**Realization (2026-07-01, cycle 0093 — the World orchestrates FAMILIES from blueprint-data,
|
||||
#166).** `aura sweep <blueprint.json> --axis <name>=<csv>` loads an **open** signal blueprint,
|
||||
grids the by-name axes against its `param_space()`, builds each member through cycle-1's
|
||||
`wrap_stage1r` seam, and aggregates to a `FamilyKind::Sweep` family — byte-identical in shape
|
||||
`wrap_r` seam, and aggregates to a `FamilyKind::Sweep` family — byte-identical in shape
|
||||
to the hard-wired sweep (`append_family` / `sweep_member_reports` reused verbatim). This is the
|
||||
C21 step beyond a single run: the World now constructs and orchestrates **families** of
|
||||
harnesses from topology-data, not just one. Each member manifest carries the **shared**
|
||||
@@ -1928,7 +1928,7 @@ the loaded blueprint's params over the user `--axis` grid (the #169 prefixed nam
|
||||
`select_winner` reused verbatim), runs it out-of-sample, and aggregates to a
|
||||
`FamilyKind::WalkForward` family — persisted + content-addressed + `aura reproduce` bit-identical
|
||||
(the read-side WalkForward branch rebuilds each OOS window from `manifest.window`). One
|
||||
substitution deep from the hard-wired stage1-r WF arm (the loaded blueprint via
|
||||
substitution deep from the hard-wired r-sma WF arm (the loaded blueprint via
|
||||
`blueprint_axis_probe` replaces the built-in strategy); a bad `--axis` is a clean in-closure
|
||||
exit 2, never a panic. This is Arm A (the settled direction): the loaded IS-optimizing form
|
||||
honestly carries the `walkforward` name. Reduce-mode members are R-measured (`oos_r` the
|
||||
@@ -1942,11 +1942,11 @@ prints are exactly what `--axis` binds. Every listed name is **mandatory** on a
|
||||
`walkforward`: the blueprint must be fully bound before it runs, so a subset grid is refused with
|
||||
the missing knob named (`BindError::MissingKnob`) and there is no default — pin a knob you do not
|
||||
want to vary with a single-value axis (`--axis <name>=<one-value>`). A single `blueprint_axis_probe` helper now single-sources
|
||||
the wrapped probe (`wrap_stage1r(loaded_signal).param_space()`) for the sweep terminal, the MC
|
||||
the wrapped probe (`wrap_r(loaded_signal).param_space()`) for the sweep terminal, the MC
|
||||
closed-check, AND the listing (three former inline copies → one), so **listed == swept by
|
||||
construction** (and stays so across #159's harness retirement — the listing tracks whatever the
|
||||
sweep actually resolves, never a second source of truth). The names are prefixed by the current
|
||||
stage1-r wrapping (`stage1_signal.fast.length`, the nested-composite prefix), not the raw
|
||||
r-sma wrapping (`sma_signal.fast.length`, the nested-composite prefix), not the raw
|
||||
`param_space` — which is why the discovery lives on the sweep verb (it owns the wrapping), not
|
||||
`graph introspect`. Per-member trace-writing on `--trace` (#168) remains tracked debt.
|
||||
|
||||
@@ -1956,7 +1956,7 @@ content-addressed blueprint store (`aura reproduce`) shipped cycle 0094 (see C18
|
||||
Realization). What **remains** is a content-id that covers **structural-axis / whole-harness
|
||||
variants** (the scaffolding is not yet blueprint-data) and the debug-name-in-id question
|
||||
(#171/#170). Still deferred: retiring the pre-C24
|
||||
hard-wired `aura-cli` harnesses (`HarnessKind`, `run_stage1_r`, `*_sweep_family`) once
|
||||
hard-wired `aura-cli` harnesses (`HarnessKind`, `run_r_sma`, `*_sweep_family`) once
|
||||
the project-as-crate layer lands (#159, paired with #157's data-authoring surface). **Out of the first cut's round-trippable set**
|
||||
(deliberate; fails clean as `UnknownNodeType`, never a silent wrong graph): recording
|
||||
sinks (capture an `mpsc::Sender` — runtime identity, not param-generic data, C19) and
|
||||
|
||||
+2
-2
@@ -93,7 +93,7 @@ The set of harness instances produced by varying the structural axes (strategy
|
||||
|
||||
### exposure stream
|
||||
**Avoid:** — (superseded)
|
||||
**Superseded by `bias` (2026-06 R-reframe).** The pre-reframe primary output: a signed, bounded `f64 ∈ [-1,+1]` per cycle read as the desired fractional *position* (conflating direction + conviction + size). Reframed to `bias` (unsized direction + conviction), sizing moved downstream to the `Sizer`/`RiskExecutor`; the term and the `Exposure` node persist in pre-reframe code until the Stage-1 rename.
|
||||
**Superseded by `bias` (2026-06 R-reframe).** The pre-reframe primary output: a signed, bounded `f64 ∈ [-1,+1]` per cycle read as the desired fractional *position* (conflating direction + conviction + size). Reframed to `bias` (unsized direction + conviction), sizing moved downstream to the `Sizer`/`RiskExecutor`; the term and the `Exposure` node persisted in pre-reframe code until the r-family rename (#174).
|
||||
|
||||
### firing policy
|
||||
**Avoid:** —
|
||||
@@ -249,7 +249,7 @@ System Quality Number — `√n · mean_R / stdev_R`: the dispersion- and trade-
|
||||
|
||||
### Stage 1 / Stage 2
|
||||
**Avoid:** — (superseded)
|
||||
**Superseded (2026-06-28 C10 reframe): there is no Stage-2 currency/compounding gate.** Research is one pure feed-forward R loop — gross R → net R via the `cost model` — with money, compounding, and a real broker living only at a separate later **live / deploy edge** (compounding is a post-hoc money-management transform of the net-R sequence, not an in-loop axis). The pre-reframe cleave gated a Stage-2 currency / fixed-fractional / realistic-broker layer behind `E[R] > 0`; **"Stage-1"** now survives only as a historical cycle / identifier name for the shipped feed-forward R chain, not one half of a two-stage gate.
|
||||
**Superseded (2026-06-28 C10 reframe): there is no Stage-2 currency/compounding gate.** Research is one pure feed-forward R loop — gross R → net R via the `cost model` — with money, compounding, and a real broker living only at a separate later **live / deploy edge** (compounding is a post-hoc money-management transform of the net-R sequence, not an in-loop axis). The pre-reframe cleave gated a Stage-2 currency / fixed-fractional / realistic-broker layer behind `E[R] > 0`; **"Stage-1"** now survives only as a historical cycle name — the shipped feed-forward R chain's identifier family was renamed to the r-family (`r-sma` / `r-breakout` / `r-meanrev`, #174) — not one half of a two-stage gate.
|
||||
|
||||
### strategy
|
||||
**Avoid:** —
|
||||
|
||||
Reference in New Issue
Block a user