refactor(stage1-r): rename the strategy-output param namespace exposure -> bias
Complete the deferred rename tail from cycle 0065's #126 (which renamed only the typed metric, keeping the param namespace uniformly "exposure" so the single-run rename would not smuggle in a ~30-site sweep-pinned change). Now renamed together as one consistent unit, all driven by the Bias node's instance name: - Bias::builder().named("exposure") -> .named("bias") (the harness builders + the engine fixtures in test_fixtures.rs / blueprint.rs); - the derived knob path exposure.scale -> bias.scale (Composite::param_space derives the knob from the node name) -- the sweep .axis/.range bindings and .with() points (blueprint.rs, main.rs), the walkforward ParamSpecs (walkforward.rs), the member_key dir names, and the JSON byte-pins in cli_run.rs / report.rs / main.rs tests; - the exposure_scale manifest param label -> bias_scale (the single-run manifest builders + the streaming_seam / real_bars fixtures). The rename is surgical: the three targets (named("exposure"), exposure.scale, exposure_scale) are syntactically distinct from the deliberately-kept "exposure" forms, which are untouched -- SimBroker's pre-reframe exposure input slot + prev_exposure + the exposure-integral; the LongOnly / gate / latch ports named "exposure"; and the on-disk "exposure" tap label (the recorded bias series that feeds `chart --tap exposure`, decoupled from the node name via ColumnarTrace::from_rows("exposure", ...)). No on-disk back-compat break: the knob path is a runtime param address, and recorded params in old runs.jsonl / families.jsonl are inert data strings (a pre-existing recorded "exposure.scale" still loads, it is just data). INDEX.md cycle-0065 realization note amended: the param namespace is recorded as the now-completed tail; the SimBroker slot + the on-disk tap label remain the deliberate permanent keeps (#117), and exposure_sign_flips stays a serde alias. Verified: cargo build --workspace --all-targets clean; clippy --all-targets -D warnings clean; full suite 514 passed / 0 failed (source + byte-pins renamed together); live `aura run` emits "bias_scale", `aura sweep` emits "bias.scale". closes #134 refs #126 #117
This commit is contained in:
+34
-34
@@ -556,7 +556,7 @@ fn run_sample(trace: Option<&str>) -> RunReport {
|
||||
vec![
|
||||
("sma_fast".to_string(), Scalar::i64(2)),
|
||||
("sma_slow".to_string(), Scalar::i64(4)),
|
||||
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||
("bias_scale".to_string(), Scalar::f64(0.5)),
|
||||
],
|
||||
window,
|
||||
0,
|
||||
@@ -593,7 +593,7 @@ fn run_sample_real(symbol: &str, from_ms: Option<i64>, to_ms: Option<i64>, trace
|
||||
vec![
|
||||
("sma_fast".to_string(), Scalar::i64(2)),
|
||||
("sma_slow".to_string(), Scalar::i64(4)),
|
||||
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||
("bias_scale".to_string(), Scalar::f64(0.5)),
|
||||
],
|
||||
window,
|
||||
0,
|
||||
@@ -949,7 +949,7 @@ fn sample_blueprint_with_sinks(pip_size: f64) -> (
|
||||
let (tx_ex, rx_ex) = mpsc::channel();
|
||||
let mut g = GraphBuilder::new("sample");
|
||||
let sig = g.add(signals("signals"));
|
||||
let exposure = g.add(Bias::builder().named("exposure"));
|
||||
let exposure = g.add(Bias::builder().named("bias"));
|
||||
let broker = g.add(SimBroker::builder(pip_size));
|
||||
let eq = g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq));
|
||||
let ex = g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex));
|
||||
@@ -994,7 +994,7 @@ fn sweep_family(trace: Option<&str>, data: &DataSource) -> SweepFamily {
|
||||
.axis("signals.momentum.signal.length", [msig])
|
||||
.axis("signals.blend.weights[0]", [1.0])
|
||||
.axis("signals.blend.weights[1]", [1.0])
|
||||
.axis("exposure.scale", [0.5]);
|
||||
.axis("bias.scale", [0.5]);
|
||||
let varying: HashSet<String> = binder.varying_axes().into_iter().collect();
|
||||
binder
|
||||
.sweep(|point| {
|
||||
@@ -1023,7 +1023,7 @@ fn sweep_family(trace: Option<&str>, data: &DataSource) -> SweepFamily {
|
||||
/// The signal is how far price sits above/below its own EMA (`price - ema`), bounded
|
||||
/// into a directional (unsized) bias by `Bias`, then passed through the long-only
|
||||
/// `LongOnly` gate. Three swept
|
||||
/// knobs of three kinds — `ema.length` (i64), `exposure.scale` (f64),
|
||||
/// knobs of three kinds — `ema.length` (i64), `bias.scale` (f64),
|
||||
/// `longonly.enabled` (bool) — with nothing in common with the SMA-cross demo: the
|
||||
/// generic-sweep proof. The exposure sink taps the FINAL (gated) exposure so the
|
||||
/// bool's effect is visible in the trace.
|
||||
@@ -1037,11 +1037,11 @@ fn momentum_blueprint_with_sinks(pip_size: f64) -> (
|
||||
let (tx_ex, rx_ex) = mpsc::channel();
|
||||
let mut g = GraphBuilder::new("momentum");
|
||||
// Name the param-bearing nodes explicitly so the swept paths are guaranteed
|
||||
// ema.length / exposure.scale / longonly.enabled regardless of default-name
|
||||
// ema.length / bias.scale / longonly.enabled regardless of default-name
|
||||
// derivation (the member-key examples + the param-space test depend on these).
|
||||
let ema = g.add(Ema::builder().named("ema")); // ema.length
|
||||
let dist = g.add(Sub::builder()); // momentum = price - ema
|
||||
let expo = g.add(Bias::builder().named("exposure")); // exposure.scale
|
||||
let expo = g.add(Bias::builder().named("bias")); // bias.scale
|
||||
let gate = g.add(LongOnly::builder().named("longonly")); // longonly.enabled (the bool param)
|
||||
let broker = g.add(SimBroker::builder(pip_size));
|
||||
let eq = g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq));
|
||||
@@ -1059,7 +1059,7 @@ fn momentum_blueprint_with_sinks(pip_size: f64) -> (
|
||||
}
|
||||
|
||||
/// Run the momentum strategy over its built-in grid — `ema.length ∈ {5,10}` ×
|
||||
/// `exposure.scale ∈ {0.5,1.0}` × `longonly.enabled ∈ {true,false}` = 8 points,
|
||||
/// `bias.scale ∈ {0.5,1.0}` × `longonly.enabled ∈ {true,false}` = 8 points,
|
||||
/// all three axes varying. Mirrors `sweep_family`: capture the binder's varying
|
||||
/// axes, key each member via the generic portable `member_key`. With `--trace`,
|
||||
/// persist each member under `runs/traces/<name>/<member_key>/`.
|
||||
@@ -1070,7 +1070,7 @@ fn momentum_sweep_family(trace: Option<&str>, data: &DataSource) -> SweepFamily
|
||||
let space = bp.param_space();
|
||||
let binder = bp
|
||||
.axis("ema.length", [5, 10])
|
||||
.axis("exposure.scale", [0.5, 1.0])
|
||||
.axis("bias.scale", [0.5, 1.0])
|
||||
.axis("longonly.enabled", [true, false]);
|
||||
let varying: HashSet<String> = binder.varying_axes().into_iter().collect();
|
||||
binder
|
||||
@@ -1315,7 +1315,7 @@ fn sweep_over(from: Timestamp, to: Timestamp, data: &DataSource) -> SweepFamily
|
||||
.axis("signals.momentum.signal.length", [msig])
|
||||
.axis("signals.blend.weights[0]", [1.0])
|
||||
.axis("signals.blend.weights[1]", [1.0])
|
||||
.axis("exposure.scale", [0.5])
|
||||
.axis("bias.scale", [0.5])
|
||||
.sweep(|point| {
|
||||
let (bp, rx_eq, rx_ex) = sample_blueprint_with_sinks(pip);
|
||||
let mut h = bp
|
||||
@@ -1438,7 +1438,7 @@ fn mc_family(trace: Option<&str>) -> McFamily {
|
||||
vec![
|
||||
("sma_fast".to_string(), Scalar::i64(2)),
|
||||
("sma_slow".to_string(), Scalar::i64(4)),
|
||||
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||
("bias_scale".to_string(), Scalar::f64(0.5)),
|
||||
],
|
||||
window,
|
||||
seed,
|
||||
@@ -1600,7 +1600,7 @@ fn macd_strategy_blueprint(
|
||||
) -> Composite {
|
||||
let mut g = GraphBuilder::new("macd_strategy");
|
||||
let macd_node = g.add(macd("macd"));
|
||||
let exposure = g.add(Bias::builder().named("exposure"));
|
||||
let exposure = g.add(Bias::builder().named("bias"));
|
||||
let broker = g.add(SimBroker::builder(SYNTHETIC_PIP_SIZE));
|
||||
let eq = g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq));
|
||||
let ex = g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex));
|
||||
@@ -1674,7 +1674,7 @@ fn run_macd(trace: Option<&str>) -> RunReport {
|
||||
("ema_fast".to_string(), Scalar::i64(2)),
|
||||
("ema_slow".to_string(), Scalar::i64(4)),
|
||||
("ema_signal".to_string(), Scalar::i64(3)),
|
||||
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||
("bias_scale".to_string(), Scalar::f64(0.5)),
|
||||
],
|
||||
window,
|
||||
0,
|
||||
@@ -1737,7 +1737,7 @@ fn stage1_r_blueprint(
|
||||
let fast = g.add(Sma::builder().named("fast").bind("length", Scalar::i64(2)));
|
||||
let slow = g.add(Sma::builder().named("slow").bind("length", Scalar::i64(4)));
|
||||
let spread = g.add(Sub::builder());
|
||||
let exposure = g.add(Bias::builder().named("exposure").bind("scale", Scalar::f64(0.5)));
|
||||
let exposure = g.add(Bias::builder().named("bias").bind("scale", Scalar::f64(0.5)));
|
||||
// pip branch (verbatim from sample_blueprint_with_sinks).
|
||||
let broker = g.add(SimBroker::builder(SYNTHETIC_PIP_SIZE));
|
||||
let eq = g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq));
|
||||
@@ -1824,7 +1824,7 @@ fn run_stage1_r(data: RunData, trace: Option<&str>) -> RunReport {
|
||||
vec![
|
||||
("sma_fast".to_string(), Scalar::i64(2)),
|
||||
("sma_slow".to_string(), Scalar::i64(4)),
|
||||
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||
("bias_scale".to_string(), Scalar::f64(0.5)),
|
||||
("stop_length".to_string(), Scalar::i64(STAGE1_R_STOP_LENGTH)), // vol_stop EWMA length
|
||||
("stop_k".to_string(), Scalar::f64(STAGE1_R_STOP_K)), // vol_stop multiplier (1R = k·σ)
|
||||
],
|
||||
@@ -2394,7 +2394,7 @@ mod tests {
|
||||
vec![
|
||||
("sma_fast".to_string(), Scalar::i64(2)),
|
||||
("sma_slow".to_string(), Scalar::i64(4)),
|
||||
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||
("bias_scale".to_string(), Scalar::f64(0.5)),
|
||||
],
|
||||
window,
|
||||
seed,
|
||||
@@ -2441,7 +2441,7 @@ mod tests {
|
||||
.with("signals.momentum.signal.length", 3)
|
||||
.with("signals.blend.weights[0]", 1.0)
|
||||
.with("signals.blend.weights[1]", 1.0)
|
||||
.with("exposure.scale", 0.5)
|
||||
.with("bias.scale", 0.5)
|
||||
.bootstrap()
|
||||
.expect("sample blueprint compiles under a valid point");
|
||||
h.run(vec![Box::new(VecSource::new(showcase_prices()))]);
|
||||
@@ -2460,10 +2460,10 @@ mod tests {
|
||||
for line in &lines {
|
||||
assert!(line.starts_with(r#"{"manifest":{"commit":""#), "not a RunReport: {line}");
|
||||
}
|
||||
assert!(lines[0].contains(r#""params":[["signals.trend.fast.length",{"I64":2}],["signals.trend.slow.length",{"I64":4}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["exposure.scale",{"F64":0.5}]]"#), "line0: {}", lines[0]);
|
||||
assert!(lines[1].contains(r#""params":[["signals.trend.fast.length",{"I64":2}],["signals.trend.slow.length",{"I64":5}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["exposure.scale",{"F64":0.5}]]"#), "line1: {}", lines[1]);
|
||||
assert!(lines[2].contains(r#""params":[["signals.trend.fast.length",{"I64":3}],["signals.trend.slow.length",{"I64":4}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["exposure.scale",{"F64":0.5}]]"#), "line2: {}", lines[2]);
|
||||
assert!(lines[3].contains(r#""params":[["signals.trend.fast.length",{"I64":3}],["signals.trend.slow.length",{"I64":5}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["exposure.scale",{"F64":0.5}]]"#), "line3: {}", lines[3]);
|
||||
assert!(lines[0].contains(r#""params":[["signals.trend.fast.length",{"I64":2}],["signals.trend.slow.length",{"I64":4}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["bias.scale",{"F64":0.5}]]"#), "line0: {}", lines[0]);
|
||||
assert!(lines[1].contains(r#""params":[["signals.trend.fast.length",{"I64":2}],["signals.trend.slow.length",{"I64":5}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["bias.scale",{"F64":0.5}]]"#), "line1: {}", lines[1]);
|
||||
assert!(lines[2].contains(r#""params":[["signals.trend.fast.length",{"I64":3}],["signals.trend.slow.length",{"I64":4}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["bias.scale",{"F64":0.5}]]"#), "line2: {}", lines[2]);
|
||||
assert!(lines[3].contains(r#""params":[["signals.trend.fast.length",{"I64":3}],["signals.trend.slow.length",{"I64":5}],["signals.momentum.fast.length",{"I64":2}],["signals.momentum.slow.length",{"I64":4}],["signals.momentum.signal.length",{"I64":3}],["signals.blend.weights[0]",{"F64":1.0}],["signals.blend.weights[1]",{"F64":1.0}],["bias.scale",{"F64":0.5}]]"#), "line3: {}", lines[3]);
|
||||
for line in &lines {
|
||||
assert!(line.contains(r#""total_pips":"#), "missing total_pips: {line}");
|
||||
assert!(line.contains(r#""max_drawdown":"#), "missing max_drawdown: {line}");
|
||||
@@ -2565,7 +2565,7 @@ mod tests {
|
||||
/// EMA `length` slots by node name to `macd.fast.length` / `macd.slow.length` /
|
||||
/// `macd.signal.length` — the named composite boundary visible end-to-end through
|
||||
/// `param_space()`, with the slot count and order unchanged (C23 — node names are
|
||||
/// non-load-bearing: every interior slot stays sweepable, the `exposure.scale`
|
||||
/// non-load-bearing: every interior slot stays sweepable, the `bias.scale`
|
||||
/// knob is unaffected).
|
||||
#[test]
|
||||
fn macd_param_space_surfaces_the_three_named_legs() {
|
||||
@@ -2579,7 +2579,7 @@ mod tests {
|
||||
"macd.fast.length".to_string(),
|
||||
"macd.slow.length".to_string(),
|
||||
"macd.signal.length".to_string(),
|
||||
"exposure.scale".to_string(),
|
||||
"bias.scale".to_string(),
|
||||
],
|
||||
"MACD param surface must expose the three named EMA lengths + scale",
|
||||
);
|
||||
@@ -2757,13 +2757,13 @@ mod tests {
|
||||
fn member_key_renders_varying_axes_portably() {
|
||||
let named = vec![
|
||||
pair("ema.length", Scalar::i64(5)),
|
||||
pair("exposure.scale", Scalar::f64(0.5)),
|
||||
pair("bias.scale", Scalar::f64(0.5)),
|
||||
pair("longonly.enabled", Scalar::bool(true)),
|
||||
];
|
||||
let varying: std::collections::HashSet<String> =
|
||||
named.iter().map(|(n, _)| n.clone()).collect();
|
||||
let key = member_key(&named, &varying);
|
||||
assert_eq!(key, "ema.length-5_exposure.scale-0.5_longonly.enabled-true");
|
||||
assert_eq!(key, "ema.length-5_bias.scale-0.5_longonly.enabled-true");
|
||||
assert!(key.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-')));
|
||||
}
|
||||
|
||||
@@ -2771,7 +2771,7 @@ mod tests {
|
||||
fn member_key_omits_pinned_axes() {
|
||||
let named = vec![
|
||||
pair("ema.length", Scalar::i64(5)),
|
||||
pair("exposure.scale", Scalar::f64(0.5)),
|
||||
pair("bias.scale", Scalar::f64(0.5)),
|
||||
pair("longonly.enabled", Scalar::bool(false)),
|
||||
];
|
||||
let mut varying = std::collections::HashSet::new();
|
||||
@@ -2781,10 +2781,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn member_key_handles_negative_float_and_sanitises_names() {
|
||||
let named = vec![pair("exposure.scale", Scalar::f64(-0.5))];
|
||||
let named = vec![pair("bias.scale", Scalar::f64(-0.5))];
|
||||
let varying: std::collections::HashSet<String> =
|
||||
["exposure.scale".to_string()].into_iter().collect();
|
||||
assert_eq!(member_key(&named, &varying), "exposure.scale--0.5");
|
||||
["bias.scale".to_string()].into_iter().collect();
|
||||
assert_eq!(member_key(&named, &varying), "bias.scale--0.5");
|
||||
|
||||
let named2 = vec![pair("weird key!", Scalar::f64(1.0))];
|
||||
let varying2: std::collections::HashSet<String> =
|
||||
@@ -2794,7 +2794,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn member_key_is_m_when_no_axis_varies() {
|
||||
let named = vec![pair("exposure.scale", Scalar::f64(0.5))];
|
||||
let named = vec![pair("bias.scale", Scalar::f64(0.5))];
|
||||
let varying = std::collections::HashSet::new();
|
||||
assert_eq!(member_key(&named, &varying), "m");
|
||||
}
|
||||
@@ -2816,15 +2816,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn member_key_collision_free_over_a_non_trend_axis_set() {
|
||||
// #105 regression: vary exposure.scale + a bool (NOT the old hardcoded
|
||||
// #105 regression: vary bias.scale + a bool (NOT the old hardcoded
|
||||
// trend.fast/slow). Distinct points -> distinct keys; the old f…s… key
|
||||
// would have collapsed them all to one dir.
|
||||
let varying: std::collections::HashSet<String> =
|
||||
["exposure.scale".to_string(), "longonly.enabled".to_string()].into_iter().collect();
|
||||
["bias.scale".to_string(), "longonly.enabled".to_string()].into_iter().collect();
|
||||
let p = |s: f64, b: bool| {
|
||||
vec![
|
||||
pair("ema.length", Scalar::i64(5)), // pinned -> omitted from key
|
||||
pair("exposure.scale", Scalar::f64(s)),
|
||||
pair("bias.scale", Scalar::f64(s)),
|
||||
pair("longonly.enabled", Scalar::bool(b)),
|
||||
]
|
||||
};
|
||||
@@ -2846,7 +2846,7 @@ mod tests {
|
||||
names,
|
||||
vec![
|
||||
"ema.length".to_string(),
|
||||
"exposure.scale".to_string(),
|
||||
"bias.scale".to_string(),
|
||||
"longonly.enabled".to_string(),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -635,7 +635,7 @@ fn sweep_prints_four_family_json_lines_and_exits_zero() {
|
||||
// `broker` (the commit value is the volatile git HEAD, pinned by params below).
|
||||
assert!(first.starts_with("{\"family_id\":\"sweep-0\",\"report\":{\"manifest\":{"), "got: {stdout}");
|
||||
assert!(
|
||||
first.contains("\"params\":[[\"signals.trend.fast.length\",{\"I64\":2}],[\"signals.trend.slow.length\",{\"I64\":4}],[\"signals.momentum.fast.length\",{\"I64\":2}],[\"signals.momentum.slow.length\",{\"I64\":4}],[\"signals.momentum.signal.length\",{\"I64\":3}],[\"signals.blend.weights[0]\",{\"F64\":1.0}],[\"signals.blend.weights[1]\",{\"F64\":1.0}],[\"exposure.scale\",{\"F64\":0.5}]]"),
|
||||
first.contains("\"params\":[[\"signals.trend.fast.length\",{\"I64\":2}],[\"signals.trend.slow.length\",{\"I64\":4}],[\"signals.momentum.fast.length\",{\"I64\":2}],[\"signals.momentum.slow.length\",{\"I64\":4}],[\"signals.momentum.signal.length\",{\"I64\":3}],[\"signals.blend.weights[0]\",{\"F64\":1.0}],[\"signals.blend.weights[1]\",{\"F64\":1.0}],[\"bias.scale\",{\"F64\":0.5}]]"),
|
||||
"got: {stdout}"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
@@ -1035,7 +1035,7 @@ fn sweep_strategy_momentum_prints_eight_typed_bool_member_lines() {
|
||||
/// Property (#105, the bool actually gates — observable on disk): the
|
||||
/// `longonly.enabled` param is genuinely WIRED into the momentum harness, not a
|
||||
/// no-op knob. For one grid point's two bool members (same `ema.length` +
|
||||
/// `exposure.scale`, differing only in the bool), the `enabled=true` member
|
||||
/// `bias.scale`, differing only in the bool), the `enabled=true` member
|
||||
/// records a long-only exposure stream — every recorded value `>= 0` — while the
|
||||
/// otherwise-identical `enabled=false` member records at least one NEGATIVE
|
||||
/// exposure over the same deterministic showcase stream. This pins the gate's
|
||||
@@ -1063,8 +1063,8 @@ fn momentum_longonly_true_clamps_exposure_nonnegative_false_keeps_negatives() {
|
||||
|
||||
// One grid point, its two bool members (identical but for the bool).
|
||||
let base = cwd.join("runs/traces/gate");
|
||||
let enabled = base.join("ema.length-5_exposure.scale-1_longonly.enabled-true");
|
||||
let disabled = base.join("ema.length-5_exposure.scale-1_longonly.enabled-false");
|
||||
let enabled = base.join("ema.length-5_bias.scale-1_longonly.enabled-true");
|
||||
let disabled = base.join("ema.length-5_bias.scale-1_longonly.enabled-false");
|
||||
|
||||
let exposures = |dir: &std::path::Path| -> Vec<f64> {
|
||||
let body = std::fs::read_to_string(dir.join("exposure.json")).expect("read exposure.json");
|
||||
|
||||
@@ -304,7 +304,7 @@ impl Composite {
|
||||
/// fluent alternative to a positional `bootstrap_with_params` vector). The
|
||||
/// bound name is the exact `param_space()` name — `<node>.<param>` at every
|
||||
/// level, e.g. `sma_cross.fast.length` for a composite-interior knob and
|
||||
/// `exposure.scale` for a root-level knob.
|
||||
/// `bias.scale` for a root-level knob.
|
||||
pub fn with(self, name: &str, v: impl Into<Scalar>) -> Binder {
|
||||
Binder { bp: self, bound: vec![(name.to_string(), v.into())] }
|
||||
}
|
||||
@@ -962,7 +962,7 @@ mod tests {
|
||||
.0
|
||||
.range("sma_cross.fast.length", fast)
|
||||
.range("sma_cross.slow.length", slow)
|
||||
.range("exposure.scale", scale)
|
||||
.range("bias.scale", scale)
|
||||
.sweep(count, seed, run_point)
|
||||
.expect("named ranges resolve against param_space()");
|
||||
|
||||
@@ -972,7 +972,7 @@ mod tests {
|
||||
.0
|
||||
.range("sma_cross.slow.length", slow)
|
||||
.range("sma_cross.fast.length", fast)
|
||||
.range("exposure.scale", scale)
|
||||
.range("bias.scale", scale)
|
||||
.sweep(count, seed, run_point)
|
||||
.expect("named ranges resolve regardless of call order");
|
||||
|
||||
@@ -1078,7 +1078,7 @@ mod tests {
|
||||
let result = bp
|
||||
.axis("sma_cross.fast.length", [2.0, 3.0]) // F64 values for the I64 slot
|
||||
.axis("sma_cross.slow.length", [4])
|
||||
.axis("exposure.scale", [0.5])
|
||||
.axis("bias.scale", [0.5])
|
||||
.sweep(|_: &[Cell]| -> RunReport { panic!("axis pre-validation must reject before running") });
|
||||
assert_eq!(
|
||||
result,
|
||||
@@ -1100,7 +1100,7 @@ mod tests {
|
||||
&[
|
||||
("sma_cross.fast.length".to_string(), vec![Scalar::i64(2), Scalar::i64(3)]),
|
||||
("sma_cross.slow.length".to_string(), vec![Scalar::i64(4), Scalar::i64(5)]),
|
||||
("exposure.scale".to_string(), vec![Scalar::f64(0.5)]),
|
||||
("bias.scale".to_string(), vec![Scalar::f64(0.5)]),
|
||||
],
|
||||
)
|
||||
.expect("named axes resolve");
|
||||
@@ -1209,7 +1209,7 @@ mod tests {
|
||||
let mut named = bp
|
||||
.with("sma_cross.fast.length", 2)
|
||||
.with("sma_cross.slow.length", 4)
|
||||
.with("exposure.scale", 0.5)
|
||||
.with("bias.scale", 0.5)
|
||||
.bootstrap()
|
||||
.expect("named binding resolves and bootstraps");
|
||||
named.run(vec![Box::new(VecSource::new(synthetic_prices()))]);
|
||||
@@ -2104,7 +2104,7 @@ mod tests {
|
||||
// scale (F64); Sub/SimBroker/Recorder declare none
|
||||
assert_eq!(
|
||||
space.iter().map(|p| p.name.as_str()).collect::<Vec<_>>(),
|
||||
["sma_cross.fast.length", "sma_cross.slow.length", "exposure.scale"],
|
||||
["sma_cross.fast.length", "sma_cross.slow.length", "bias.scale"],
|
||||
);
|
||||
assert_eq!(
|
||||
space.iter().map(|p| p.kind).collect::<Vec<_>>(),
|
||||
@@ -2563,7 +2563,7 @@ mod tests {
|
||||
"root",
|
||||
vec![
|
||||
BlueprintNode::Composite(cross),
|
||||
Bias::builder().named("exposure").into(),
|
||||
Bias::builder().named("bias").into(),
|
||||
Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex).into(),
|
||||
],
|
||||
vec![
|
||||
@@ -2583,7 +2583,7 @@ mod tests {
|
||||
let mut h = bp
|
||||
.with("sma_cross.fast.length", 2)
|
||||
.with("sma_cross.slow.length", 4)
|
||||
.with("exposure.scale", 0.5)
|
||||
.with("bias.scale", 0.5)
|
||||
.bootstrap()
|
||||
.expect("named (injective) cross resolves by name and bootstraps");
|
||||
h.run(vec![Box::new(VecSource::new(prices))]);
|
||||
|
||||
@@ -236,7 +236,7 @@ mod tests {
|
||||
use aura_core::Scalar;
|
||||
|
||||
// Shared param vector for the harness param-space (sma_cross.fast.length,
|
||||
// sma_cross.slow.length, exposure.scale) — applied to both sides, so it
|
||||
// sma_cross.slow.length, bias.scale) — applied to both sides, so it
|
||||
// cancels in the comparison. Params size buffers, not topology (C11), so
|
||||
// edges/sources are param-independent; a fixed vector just satisfies the
|
||||
// arity gate compile() (no-param) trips on this value-empty harness.
|
||||
|
||||
@@ -637,7 +637,7 @@ mod tests {
|
||||
params: vec![
|
||||
("sma_fast".to_string(), Scalar::i64(2)),
|
||||
("sma_slow".to_string(), Scalar::i64(4)),
|
||||
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||
("bias_scale".to_string(), Scalar::f64(0.5)),
|
||||
],
|
||||
window: (Timestamp(1), Timestamp(5)),
|
||||
seed: 0,
|
||||
@@ -879,7 +879,7 @@ mod tests {
|
||||
params: vec![
|
||||
("sma_fast".to_string(), Scalar::i64(2)),
|
||||
("sma_slow".to_string(), Scalar::i64(4)),
|
||||
("exposure_scale".to_string(), Scalar::f64(1.0)),
|
||||
("bias_scale".to_string(), Scalar::f64(1.0)),
|
||||
],
|
||||
window: (Timestamp(1), Timestamp(6)),
|
||||
seed: 0,
|
||||
@@ -894,7 +894,7 @@ mod tests {
|
||||
};
|
||||
assert_eq!(
|
||||
report.to_json(),
|
||||
r#"{"manifest":{"commit":"abc123","params":[["sma_fast",{"I64":2}],["sma_slow",{"I64":4}],["exposure_scale",{"F64":1.0}]],"window":[1,6],"seed":0,"broker":"sim-optimal(pip_size=1.0)"},"metrics":{"total_pips":12.0,"max_drawdown":1.0,"bias_sign_flips":1}}"#,
|
||||
r#"{"manifest":{"commit":"abc123","params":[["sma_fast",{"I64":2}],["sma_slow",{"I64":4}],["bias_scale",{"F64":1.0}]],"window":[1,6],"seed":0,"broker":"sim-optimal(pip_size=1.0)"},"metrics":{"total_pips":12.0,"max_drawdown":1.0,"bias_sign_flips":1}}"#,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -907,7 +907,7 @@ mod tests {
|
||||
params: vec![
|
||||
("sma_fast".to_string(), Scalar::i64(2)),
|
||||
("sma_slow".to_string(), Scalar::i64(4)),
|
||||
("exposure_scale".to_string(), Scalar::f64(1.0)),
|
||||
("bias_scale".to_string(), Scalar::f64(1.0)),
|
||||
],
|
||||
window: (Timestamp(1), Timestamp(6)),
|
||||
seed: 0,
|
||||
@@ -927,7 +927,7 @@ mod tests {
|
||||
params: vec![
|
||||
("sma_fast".to_string(), Scalar::i64(2)),
|
||||
("sma_slow".to_string(), Scalar::i64(4)),
|
||||
("exposure_scale".to_string(), Scalar::f64(1.0)),
|
||||
("bias_scale".to_string(), Scalar::f64(1.0)),
|
||||
],
|
||||
window: (Timestamp(1), Timestamp(6)),
|
||||
seed: 0,
|
||||
|
||||
@@ -62,7 +62,7 @@ pub(crate) fn composite_sma_cross_harness() -> (
|
||||
"root",
|
||||
vec![
|
||||
BlueprintNode::Composite(sma_cross()),
|
||||
Bias::builder().named("exposure").into(),
|
||||
Bias::builder().named("bias").into(),
|
||||
SimBroker::builder(0.0001).into(),
|
||||
Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq).into(),
|
||||
Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex).into(),
|
||||
|
||||
@@ -467,7 +467,7 @@ mod tests {
|
||||
let result = WalkForwardResult {
|
||||
space: vec![
|
||||
ParamSpec { name: "sma.fast".to_string(), kind: ScalarKind::I64 },
|
||||
ParamSpec { name: "exposure.scale".to_string(), kind: ScalarKind::F64 },
|
||||
ParamSpec { name: "bias.scale".to_string(), kind: ScalarKind::F64 },
|
||||
],
|
||||
windows: vec![mk(8, 0.25), mk(13, 0.75)],
|
||||
stitched_oos_equity: vec![],
|
||||
@@ -476,14 +476,14 @@ mod tests {
|
||||
result.named_params(0),
|
||||
vec![
|
||||
("sma.fast".to_string(), Scalar::i64(8)),
|
||||
("exposure.scale".to_string(), Scalar::f64(0.25)),
|
||||
("bias.scale".to_string(), Scalar::f64(0.25)),
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
result.named_params(1),
|
||||
vec![
|
||||
("sma.fast".to_string(), Scalar::i64(13)),
|
||||
("exposure.scale".to_string(), Scalar::f64(0.75)),
|
||||
("bias.scale".to_string(), Scalar::f64(0.75)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ fn run_sample_over(prices: Vec<(Timestamp, Scalar)>) -> RunReport {
|
||||
params: vec![
|
||||
("sma_fast".to_string(), Scalar::i64(2)),
|
||||
("sma_slow".to_string(), Scalar::i64(4)),
|
||||
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||
("bias_scale".to_string(), Scalar::f64(0.5)),
|
||||
],
|
||||
window,
|
||||
seed: 0,
|
||||
|
||||
@@ -84,7 +84,7 @@ fn run_sample(window: (Timestamp, Timestamp), source: Box<dyn Source>) -> RunRep
|
||||
params: vec![
|
||||
("sma_fast".to_string(), Scalar::i64(2)),
|
||||
("sma_slow".to_string(), Scalar::i64(4)),
|
||||
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||
("bias_scale".to_string(), Scalar::f64(0.5)),
|
||||
],
|
||||
window,
|
||||
seed: 0,
|
||||
|
||||
@@ -606,9 +606,12 @@ runnable GER40 examples (already at the correct `1.0`) are routed through the sa
|
||||
lookup separately (#98).
|
||||
**Realization (cycle 0065 — Stage-1 R signal quality, #119/#126/#127/#128/#129).**
|
||||
The Stage-1 chain above is now built. `Exposure → Bias` renames the unsized strategy
|
||||
output (the node + its output field); the persisted `exposure_sign_flips` metric key,
|
||||
the `SimBroker` `exposure` input slot, and the `exposure_scale` manifest param keep
|
||||
the old name as **serde-stable residue** — a deferred rename tail. A **stop-rule**
|
||||
output (the node + its output field); the persisted `exposure_sign_flips` metric key
|
||||
(kept as a serde alias) and the `SimBroker` `exposure` input slot + the on-disk
|
||||
`exposure` **tap** label (a deliberate permanent keep, #117) retain the old name. The
|
||||
strategy-output **param namespace** — the `Bias` instance, its `bias.scale` knob, and
|
||||
the `bias_scale` manifest param — was the deferred rename tail, **completed in #134**
|
||||
(the knob path is a runtime address, so no on-disk back-compat break). A **stop-rule**
|
||||
defines 1R: `FixedStop` (a triggered-constant primitive) and a `vol_stop(length, k)`
|
||||
**composition** `k·√EMA(Δ²)` (the originally-fused node was corrected to a composition
|
||||
of new `Mul`/`Sqrt` primitives — a node is a primitive only if not DAG-expressible
|
||||
|
||||
Reference in New Issue
Block a user