test(cli): RED — real-data member pips ignore the resolved sidecar pip
The in-graph SimBroker divides by SYNTHETIC_PIP_SIZE regardless of the resolved instrument pip; the sidecar pip reaches only the manifest label. Pins the pip-invariance property total_pips * pip == raw price move. refs #232
This commit is contained in:
@@ -3409,6 +3409,64 @@ mod tests {
|
|||||||
assert_eq!(*bias.last().unwrap(), 1.0, "the down-fade long must hold to the end: {bias:?}");
|
assert_eq!(*bias.last().unwrap(), 1.0, "the down-fade long must hold to the end: {bias:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Property: the pip a run resolves and stamps into its manifest must be the
|
||||||
|
/// pip the in-graph `SimBroker` divides by — the resolved (real, per-instrument)
|
||||||
|
/// pip has to reach the graph, not just the manifest label. The `SimBroker`
|
||||||
|
/// integrates `exposure * (price - prev_price) / pip`, so the raw price-move
|
||||||
|
/// total `total_pips * pip` is pip-invariant: the SAME signal + prices run at
|
||||||
|
/// two different pips must agree on that product. If `pip` only decorates the
|
||||||
|
/// label and the graph always divides by the synthetic default, both runs
|
||||||
|
/// yield the identical `total_pips` and the invariant breaks (and real-data
|
||||||
|
/// pips are inflated by `1 / 0.0001 = 10^4`).
|
||||||
|
#[test]
|
||||||
|
fn run_blueprint_member_computes_pips_at_the_resolved_pip_not_a_hardwired_default() {
|
||||||
|
let env = project::Env::std();
|
||||||
|
// A price series that drives the meanrev signal into a definite non-flat
|
||||||
|
// exposure (short an up-move, long a down-move), so broker equity != 0 —
|
||||||
|
// the same idiom as the fade test above.
|
||||||
|
let closes = [
|
||||||
|
100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 130.0, 130.0, 130.0, 70.0, 70.0, 70.0, 70.0,
|
||||||
|
];
|
||||||
|
let make_source = || -> Vec<Box<dyn aura_engine::Source>> {
|
||||||
|
let prices: Vec<(Timestamp, Scalar)> = closes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, &c)| (Timestamp(i as i64), Scalar::f64(c)))
|
||||||
|
.collect();
|
||||||
|
vec![Box::new(VecSource::new(prices))]
|
||||||
|
};
|
||||||
|
let window = (Timestamp(0), Timestamp(closes.len() as i64 - 1));
|
||||||
|
let stop = StopRule::Vol { length: 3, k: 2.0 };
|
||||||
|
let space: Vec<ParamSpec> = vec![];
|
||||||
|
// Two per-instrument pips, an order of magnitude apart and both distinct
|
||||||
|
// from the synthetic 0.0001 — i.e. the real-data condition.
|
||||||
|
let pip_a = 1.0_f64;
|
||||||
|
let pip_b = 0.1_f64;
|
||||||
|
let report_a = run_blueprint_member(
|
||||||
|
r_meanrev_signal(Some(3), Some(0.0)), &[], &space, make_source(), window, 0, pip_a, "topo", &env, stop,
|
||||||
|
);
|
||||||
|
let report_b = run_blueprint_member(
|
||||||
|
r_meanrev_signal(Some(3), Some(0.0)), &[], &space, make_source(), window, 0, pip_b, "topo", &env, stop,
|
||||||
|
);
|
||||||
|
// Guard: the run must have actually traded, else the invariant is vacuous.
|
||||||
|
assert!(
|
||||||
|
report_a.metrics.total_pips.abs() > 0.0,
|
||||||
|
"test needs a non-flat run to be meaningful: {:?}",
|
||||||
|
report_a.metrics
|
||||||
|
);
|
||||||
|
// The pip-invariant raw price-move total must agree across the two pips.
|
||||||
|
let raw_a = report_a.metrics.total_pips * pip_a;
|
||||||
|
let raw_b = report_b.metrics.total_pips * pip_b;
|
||||||
|
assert!(
|
||||||
|
(raw_a - raw_b).abs() < 1e-9,
|
||||||
|
"total_pips must be computed at the resolved pip: pip={pip_a} gave total_pips={} \
|
||||||
|
(raw price-move {raw_a}), pip={pip_b} gave total_pips={} (raw price-move {raw_b}) \
|
||||||
|
— the resolved pip never reached the in-graph SimBroker",
|
||||||
|
report_a.metrics.total_pips,
|
||||||
|
report_b.metrics.total_pips,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Loads the shipped closed r-sma example (fast=2, slow=4 bound) through the
|
/// Loads the shipped closed r-sma example (fast=2, slow=4 bound) through the
|
||||||
/// public `blueprint_from_json` path — the single call site so a fixture
|
/// public `blueprint_from_json` path — the single call site so a fixture
|
||||||
/// rename or vocabulary change is one edit, not fourteen.
|
/// rename or vocabulary change is one edit, not fourteen.
|
||||||
|
|||||||
Reference in New Issue
Block a user