fix(stock-pool): scan decoded native conditions instead of serialized source echoes
This commit is contained in:
@@ -221,7 +221,15 @@ pub fn specs_in_value(value: &Value) -> Result<Vec<PatternSpec>, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Object(items) => {
|
Value::Object(items) => {
|
||||||
for v in items.values() {
|
let typed_pool = items.get("stockPool").or_else(|| items.get("stock_pool"))
|
||||||
|
.is_some_and(Value::is_object);
|
||||||
|
for (key, v) in items {
|
||||||
|
// The executable pool already supplies decoded expressions.
|
||||||
|
// Its display/source serialization escapes those expressions
|
||||||
|
// one more time and is not another executable program.
|
||||||
|
if typed_pool && matches!(key.as_str(), "sourceCode" | "source_code") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
specs.extend(specs_in_value(v)?);
|
specs.extend(specs_in_value(v)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,6 +256,25 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{BenchmarkSnapshot, DailyFactorSnapshot, DailyMarketSnapshot, Instrument};
|
use crate::{BenchmarkSnapshot, DailyFactorSnapshot, DailyMarketSnapshot, Instrument};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
#[test]
|
||||||
|
fn structured_pool_conditions_are_not_rescanned_inside_serialized_source_code() {
|
||||||
|
let pattern = json!({"template":"expression","parameters":{"history_window":20},
|
||||||
|
"expression":{"kind":"operator","name":"GT","args":[{"kind":"field","name":"amount"},{"kind":"number","value":0}]}});
|
||||||
|
let expr = format!("pattern_signal({})", serde_json::to_string(&pattern.to_string()).unwrap());
|
||||||
|
let pool = json!({"schema_version":1,"pool_id":"fixture","version_id":"v1","members":[],
|
||||||
|
"allocation_policy":{},"timing_policy":{},"stop_take_policy":{},"out_of_pool_policy":"hold",
|
||||||
|
"exit_signals":[{"role":"risk_exit","when_expr":expr,"remaining_position_bps":5000,"reason":"fixture"}]});
|
||||||
|
let source = format!("stock_pool.config({pool})");
|
||||||
|
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);
|
||||||
|
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_eq!(specs_in_value(&json!({"sourceCode":format!("risk.stop_loss({expr})")})).unwrap().len(),1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalized_rule_does_not_turn_an_omitted_window_into_explicit_null() {
|
fn normalized_rule_does_not_turn_an_omitted_window_into_explicit_null() {
|
||||||
let expression:Expr=serde_json::from_value(json!({"kind":"operator","name":"GT","args":[{"kind":"field","name":"close"},{"kind":"number","value":1}]})).unwrap();
|
let expression:Expr=serde_json::from_value(json!({"kind":"operator","name":"GT","args":[{"kind":"field","name":"close"},{"kind":"number","value":1}]})).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user