修复目标权重映射预校验
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user