feat(0081): cost-model graph cycle 1 — ConstantCost node + net-R seam
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
This commit is contained in:
@@ -1620,6 +1620,32 @@ fn stage1_r_trace_persists_r_equity_and_charts_it() {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Property (Task 3, the run-path cost seam): `aura run --harness stage1-r
|
||||
/// --cost-per-trade <c>` charges a flat round-trip cost in R end to end — the run
|
||||
/// persists a fourth `net_r_equity` tap beside r_equity and the report's
|
||||
/// `net_expectancy_r` is STRICTLY below the gross `expectancy_r`. Pins the whole
|
||||
/// flag→ConstantCost node→net_r_equity tap→summarize_r net fold loop (a no-cost run
|
||||
/// leaves net == gross, the held golden); a regression that dropped the cost node or
|
||||
/// left the net fold reading an empty stream would collapse net back onto gross here.
|
||||
#[test]
|
||||
fn stage1_r_cost_run_persists_net_r_equity_and_charges_cost() {
|
||||
let dir = temp_cwd("stage1-r-cost");
|
||||
let run = Command::new(BIN)
|
||||
.current_dir(&dir)
|
||||
.args(["run", "--harness", "stage1-r", "--cost-per-trade", "2", "--trace", "c1"])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(run.status.success(), "exit: {:?}; stderr: {}", run.status,
|
||||
String::from_utf8_lossy(&run.stderr));
|
||||
assert!(dir.join("runs/traces/c1/net_r_equity.json").exists(), "net_r_equity trace persisted");
|
||||
let s = String::from_utf8(run.stdout).expect("utf-8 stdout");
|
||||
let v: serde_json::Value = serde_json::from_str(s.trim()).unwrap();
|
||||
let gross = v["metrics"]["r"]["expectancy_r"].as_f64().unwrap();
|
||||
let net = v["metrics"]["r"]["net_expectancy_r"].as_f64().unwrap();
|
||||
assert!(net < gross, "cost charged: net {net} < gross {gross}");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Property (the spec's dual-yardstick headline): `aura run --harness stage1-r` scores
|
||||
/// ONE signal in BOTH yardsticks at once — the single emitted `RunReport` carries the pip
|
||||
/// `metrics.total_pips` (off the SimBroker branch) AND the nested `r` block (off the
|
||||
|
||||
Reference in New Issue
Block a user