feat(cli,docs): the r_channel OHLC example + ledger amendment C26 (#231 tasks 6-7)
The acceptance proof: hl_channel — a causal Donchian channel (Delay(1) excludes the current bar, C2) consuming high/low/close roles, built from rostered vocabulary, shipped as the r_channel/r_channel_open example pair (builder + regenerator + serialize pins, the r_* pattern). Proven at three layers: a non-gated loaded-vs-carve equivalence over inline sources (non-zero-bias hardened per review), the unconditional synthetic multi-column refusal, and archive-gated CLI e2e — aura run on GER40 end-to-end plus a sweep whose members run and reproduce 2/2 bit-identically. Ledger: new C26 entry (the binding vocabulary — closed column set + price alias, name-driven default + campaign data.bindings override, canonical order = the C4 tie-break, Blockly-litmus argument, the #71 extension point for recorded non-price sources) and the C20/C24 scaffolding-clause annotations: the single-price data weld is retired, wrap_r's remaining R-scaffolding retirement stays #159. Verified: all proof tests green, full workspace suite green, clippy -D warnings clean; independent quality review approved. closes #231
This commit is contained in:
@@ -1010,6 +1010,125 @@ fn sweep_real_blueprint_member_manifest_stamps_project_provenance() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Acceptance (harness-input-binding spec, #231): an OHLC-consuming strategy
|
||||
/// blueprint runs end to end on real archive data through `aura run` — the
|
||||
/// role names bind columns, three real sources open in canonical order, the
|
||||
/// close column feeds broker/executor, and the report carries the R summary.
|
||||
/// Gated on the local GER40 archive; skips cleanly when absent.
|
||||
#[test]
|
||||
fn run_real_ohlc_channel_example_end_to_end() {
|
||||
if !local_data_present() {
|
||||
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
|
||||
return;
|
||||
}
|
||||
let cwd = temp_cwd("ohlc-run-real");
|
||||
let fixture = format!("{}/examples/r_channel.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(BIN)
|
||||
.args([
|
||||
"run", &fixture,
|
||||
"--real", "GER40",
|
||||
"--from", GER40_SEPT2024_FROM_MS,
|
||||
"--to", GER40_SEPT2024_TO_MS,
|
||||
])
|
||||
.current_dir(&cwd)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
let v: serde_json::Value = serde_json::from_str(stdout.trim()).expect("run report parses as JSON");
|
||||
assert!(
|
||||
v["manifest"]["topology_hash"].as_str().is_some(),
|
||||
"report carries the signal hash: {stdout}"
|
||||
);
|
||||
assert!(v["metrics"]["r"].is_object(), "the R summary is computed: {stdout}");
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
|
||||
/// The synthetic walk generates a close series only: a multi-column blueprint
|
||||
/// without `--real` refuses honestly (exit 1, columns + remedy named). NOT
|
||||
/// gated — the refusal precedes any data access.
|
||||
#[test]
|
||||
fn run_synthetic_refuses_a_multi_column_blueprint() {
|
||||
let fixture = format!("{}/examples/r_channel.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let out = std::process::Command::new(BIN)
|
||||
.args(["run", &fixture])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(out.status.code(), Some(1));
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(stderr.contains("consumes columns beyond close"), "names the shape: {stderr}");
|
||||
assert!(stderr.contains("--real"), "names the remedy: {stderr}");
|
||||
}
|
||||
|
||||
/// The dissolved real-data sweep + reproduce over the OHLC example (#231
|
||||
/// acceptance): `--list-axes` pins the ganged channel knob's wrapped axis
|
||||
/// name, the sweep runs one member per grid point over three real columns,
|
||||
/// and every member re-derives bit-identically from the store (#229 real-data
|
||||
/// reproduce, now multi-column). Gated on the local GER40 archive.
|
||||
#[test]
|
||||
fn sweep_real_ohlc_channel_members_run_and_reproduce() {
|
||||
if !local_data_present() {
|
||||
eprintln!("skip: no local data at {}", data_server::DEFAULT_DATA_PATH);
|
||||
return;
|
||||
}
|
||||
let (dir, _g) = fresh_project();
|
||||
let fixture = format!("{}/examples/r_channel_open.json", env!("CARGO_MANIFEST_DIR"));
|
||||
let axes = std::process::Command::new(BIN)
|
||||
.args(["sweep", &fixture, "--list-axes"])
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&axes.stdout).trim(),
|
||||
"hl_channel.channel_length:I64",
|
||||
"the ganged channel knob is the one sweepable axis; stderr: {}",
|
||||
String::from_utf8_lossy(&axes.stderr)
|
||||
);
|
||||
let out = std::process::Command::new(BIN)
|
||||
.args([
|
||||
"sweep", &fixture,
|
||||
"--real", "GER40",
|
||||
"--from", GER40_SEPT2024_FROM_MS,
|
||||
"--to", GER40_SEPT2024_TO_MS,
|
||||
"--axis", "hl_channel.channel_length=3,5",
|
||||
"--name", "chan",
|
||||
])
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
|
||||
assert_eq!(stdout.lines().count(), 2, "one member line per channel length: {stdout}");
|
||||
for line in stdout.lines() {
|
||||
let v: serde_json::Value = serde_json::from_str(line).expect("member line parses as JSON");
|
||||
assert_eq!(
|
||||
v["report"]["manifest"]["instrument"].as_str(),
|
||||
Some("GER40"),
|
||||
"campaign member manifest stamps the instrument: {line}"
|
||||
);
|
||||
}
|
||||
let family_id = serde_json::from_str::<serde_json::Value>(stdout.lines().next().unwrap())
|
||||
.expect("first member line parses")["family_id"]
|
||||
.as_str()
|
||||
.expect("member line carries family_id")
|
||||
.to_string();
|
||||
let rep = std::process::Command::new(BIN)
|
||||
.args(["reproduce", &family_id])
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
rep.status.success(),
|
||||
"reproduce stderr: {}",
|
||||
String::from_utf8_lossy(&rep.stderr)
|
||||
);
|
||||
assert!(
|
||||
String::from_utf8_lossy(&rep.stdout).contains("reproduced 2/2 members bit-identically"),
|
||||
"stdout: {}",
|
||||
String::from_utf8_lossy(&rep.stdout)
|
||||
);
|
||||
}
|
||||
|
||||
/// Property: the real-data blueprint sweep path (`aura sweep <blueprint.json>
|
||||
/// --real SYM --axis …`, dispatched as sugar over the one campaign executor)
|
||||
/// prints one member line per grid point, IN AXIS ORDER (odometer: the first
|
||||
|
||||
Reference in New Issue
Block a user