Compare commits

...

1 Commits

Author SHA1 Message Date
boris 61bd14d001 fix(backtest): inspect the authoritative strategy inside runner bundles 2026-09-12 16:55:16 +08:00
+11
View File
@@ -212,6 +212,11 @@ pub fn build_dataset_context(
}
pub fn specs_in_value(value: &Value) -> Result<Vec<PatternSpec>, String> {
// A runner bundle also contains source/extract copies. Follow the same
// authoritative spec selection as the execution loader, not those copies.
if let Some(spec) = value.get("strategySpec").or_else(|| value.get("strategy_spec")) {
return specs_in_value(spec);
}
let mut specs = Vec::new();
match value {
Value::String(text) => specs.extend(crate::daily_patterns::expression_specs(text)?),
@@ -268,9 +273,15 @@ mod tests {
for (pool_key, source_key) in [("stockPool", "sourceCode"), ("stock_pool", "source_code")] {
let value = json!({pool_key:pool,source_key:source,"runtimeExpressions":{"trading":{"buyFilterExpr":expr}}});
assert_eq!(specs_in_value(&value).unwrap().len(), 2);
for wrapper in ["strategySpec", "strategy_spec"] {
let bundle = json!({wrapper:value,"strategy_source":{"source_code":source},
"strategy_extract":{"parameters":{"source_code":source}}});
assert_eq!(specs_in_value(&bundle).unwrap().len(), 2);
}
let mut invalid = value.clone();
invalid[pool_key]["exit_signals"][0]["when_expr"] = json!("pattern_signal(not-json)");
assert!(specs_in_value(&invalid).is_err(), "invalid actual conditions must still fail");
assert!(specs_in_value(&json!({"strategySpec":invalid})).is_err());
}
assert_eq!(specs_in_value(&json!({"sourceCode":format!("risk.stop_loss({expr})")})).unwrap().len(),1);
}