修复目标权重映射预校验

This commit is contained in:
boris
2026-09-02 19:05:28 +08:00
parent 1215a04b7d
commit b15b93eec1
+71 -5
View File
@@ -1397,8 +1397,36 @@ impl PlatformExprStrategy {
/// Compile every configured expression before any market data is loaded. /// Compile every configured expression before any market data is loaded.
/// This validates syntax only; identifiers and runtime values are resolved /// This validates syntax only; identifiers and runtime values are resolved
/// later against the point-in-time execution scope. /// later against the point-in-time execution scope.
fn collect_preflight_float_map_values(
field: &str,
expression: &str,
output: &mut Vec<(String, String)>,
) -> Result<bool, BacktestError> {
let trimmed = expression.trim();
let Some(inner) = trimmed
.strip_prefix('{')
.and_then(|value| value.strip_suffix('}'))
else {
return Ok(false);
};
for (index, entry) in Self::split_top_level_args(inner).into_iter().enumerate() {
let Some((raw_key, raw_value)) = Self::split_top_level_key_value(&entry) else {
return Err(BacktestError::Execution(format!(
"platform float map entry must be key: value, got {entry}"
)));
};
let key = Self::parse_string_literal_key(raw_key)?;
output.push((
format!("{field}[{index}:{key}]"),
raw_value.trim().to_string(),
));
}
Ok(true)
}
pub fn validate_expression_syntax(&self) -> Result<(), BacktestError> { pub fn validate_expression_syntax(&self) -> Result<(), BacktestError> {
let normalized_prelude = Self::normalize_prelude_for_eval(&self.config.prelude); let normalized_prelude = Self::normalize_prelude_for_eval(&self.config.prelude);
let mut float_map_values = Vec::<(String, String)>::new();
let mut expressions = vec![ let mut expressions = vec![
( (
"refresh_rate_expr".to_string(), "refresh_rate_expr".to_string(),
@@ -1475,18 +1503,32 @@ impl PlatformExprStrategy {
when_expr, when_expr,
.. ..
} => { } => {
expressions.push(( let target_field =
format!("explicit_actions[{index}].target_weights_expr"), format!("explicit_actions[{index}].target_weights_expr");
if !Self::collect_preflight_float_map_values(
&target_field,
target_weights_expr, target_weights_expr,
)); &mut float_map_values,
)? {
expressions.push((target_field, target_weights_expr));
}
for (name, expression) in [ for (name, expression) in [
("order_prices_expr", order_prices_expr.as_deref()), ("order_prices_expr", order_prices_expr.as_deref()),
("valuation_prices_expr", valuation_prices_expr.as_deref()), ("valuation_prices_expr", valuation_prices_expr.as_deref()),
("when_expr", when_expr.as_deref()), ("when_expr", when_expr.as_deref()),
] { ] {
if let Some(expression) = expression { if let Some(expression) = expression {
expressions let field = format!("explicit_actions[{index}].{name}");
.push((format!("explicit_actions[{index}].{name}"), expression)); if name != "when_expr"
&& Self::collect_preflight_float_map_values(
&field,
expression,
&mut float_map_values,
)?
{
continue;
}
expressions.push((field, expression));
} }
} }
} }
@@ -1612,6 +1654,22 @@ impl PlatformExprStrategy {
)) ))
})?; })?;
} }
for (field, expression) in float_map_values {
if expression.trim().is_empty() {
continue;
}
let normalized_expression = Self::normalize_expr(&expression);
let script = if normalized_prelude.trim().is_empty() {
normalized_expression
} else {
format!("{normalized_prelude}\n{normalized_expression}")
};
self.engine.compile(&script).map_err(|error| {
BacktestError::Execution(format!(
"platform expr preflight compile failed field={field}: {error}"
))
})?;
}
Ok(()) Ok(())
} }
@@ -12906,6 +12964,14 @@ mod tests {
when_expr: Some("true".to_string()), when_expr: Some("true".to_string()),
reason: "preflight".to_string(), reason: "preflight".to_string(),
}, },
PlatformTradeAction::TargetPortfolioSmart {
target_weights_expr: "{\"000001.SZ\": 0.6, \"000002.SZ\": 0.4}".to_string(),
order_prices_expr: Some("{\"000001.SZ\": close * 1.01}".to_string()),
valuation_prices_expr: Some("{\"000001.SZ\": close}".to_string()),
time_in_force: None,
when_expr: Some("cash > 0.0".to_string()),
reason: "preflight_map".to_string(),
},
]; ];
PlatformExprStrategy::new(config) PlatformExprStrategy::new(config)