拒绝非法回测成交量比例配置

This commit is contained in:
boris
2026-07-03 04:30:40 +08:00
parent 564a2fb9b2
commit 32b6da5aca
+57 -23
View File
@@ -668,18 +668,17 @@ fn valid_non_negative(value: Option<f64>) -> Option<f64> {
value.filter(|item| item.is_finite() && *item >= 0.0)
}
fn normalize_percent_ratio(value: Option<f64>) -> Option<f64> {
value
.filter(|item| item.is_finite() && *item > 0.0)
.and_then(|item| {
if item <= 1.0 {
Some(item)
} else if item <= 100.0 {
Some(item / 100.0)
} else {
None
}
})
fn normalize_percent_ratio(value: f64, field_name: &str) -> Result<f64, String> {
if !value.is_finite() || value <= 0.0 {
return Err(format!("{field_name} must be a positive finite number"));
}
if value <= 1.0 {
Ok(value)
} else if value <= 100.0 {
Ok(value / 100.0)
} else {
Err(format!("{field_name} must be <= 100"))
}
}
fn parse_policy_date(value: Option<&str>) -> Option<NaiveDate> {
@@ -734,27 +733,29 @@ fn apply_flat_risk_overrides(
volume_limit: Option<bool>,
liquidity_limit: Option<bool>,
volume_percent: Option<f64>,
) {
) -> Result<(), String> {
if let Some(enabled) = volume_limit {
cfg.risk_config.trading_constraints.volume_limit_enabled = enabled;
}
if let Some(enabled) = liquidity_limit {
cfg.risk_config.trading_constraints.liquidity_limit_enabled = enabled;
}
if let Some(value) = normalize_percent_ratio(volume_percent) {
if let Some(raw_value) = volume_percent {
let value = normalize_percent_ratio(raw_value, "volume_percent")?;
cfg.risk_config.trading_constraints.volume_percent = value;
}
cfg.quote_quantity_limit = cfg.risk_config.trading_constraints.volume_limit_enabled
|| cfg.risk_config.trading_constraints.liquidity_limit_enabled
|| cfg.matching_type != MatchingType::MinuteLast;
Ok(())
}
fn apply_risk_policy_overrides(
cfg: &mut PlatformExprStrategyConfig,
policy: Option<&StrategyRiskPolicySpec>,
) {
) -> Result<(), String> {
let Some(policy) = policy else {
return;
return Ok(());
};
let static_rules = &mut cfg.risk_config.static_rules;
if let Some(value) = policy.reject_st_selection {
@@ -834,7 +835,7 @@ fn apply_risk_policy_overrides(
policy.volume_limit_enabled,
policy.liquidity_limit_enabled,
policy.volume_percent,
);
)?;
apply_cost_overrides(
cfg,
policy.commission_rate,
@@ -843,6 +844,7 @@ fn apply_risk_policy_overrides(
policy.stamp_tax_rate_after_change,
policy.stamp_tax_change_date.as_deref(),
);
Ok(())
}
fn normalize_model_name(value: &str) -> String {
@@ -1026,7 +1028,7 @@ fn infer_expression_windows(
stock_short_explicit: bool,
stock_mid_explicit: bool,
stock_long_explicit: bool,
) {
) -> Result<(), String> {
let mut benchmark_days = Vec::new();
for expr in [&cfg.exposure_expr, &cfg.buy_scale_expr] {
benchmark_days.extend(prefixed_ma_lookbacks(expr, "benchmark_ma"));
@@ -1057,6 +1059,7 @@ fn infer_expression_windows(
if !stock_long_explicit && let Some(long) = stock_days.last().copied() {
cfg.stock_long_ma_days = long;
}
Ok(())
}
pub fn platform_expr_config_from_spec(
@@ -1188,8 +1191,8 @@ pub fn platform_expr_config_from_spec(
engine.volume_limit,
engine.liquidity_limit,
engine.volume_percent,
);
apply_risk_policy_overrides(&mut cfg, engine.risk_policy.as_ref());
)?;
apply_risk_policy_overrides(&mut cfg, engine.risk_policy.as_ref())?;
apply_execution_behavior_overrides(
&mut cfg,
engine.matching_type.as_deref(),
@@ -1531,7 +1534,7 @@ pub fn platform_expr_config_from_spec(
stock_short_explicit,
stock_mid_explicit,
stock_long_explicit,
);
)?;
if !cfg.signal_symbol.trim().is_empty() {
cfg.signal_symbol = normalize_symbol(&cfg.signal_symbol, None);
@@ -1597,8 +1600,8 @@ pub fn platform_expr_config_from_spec(
execution.volume_limit,
execution.liquidity_limit,
execution.volume_percent,
);
apply_risk_policy_overrides(&mut cfg, execution.risk_policy.as_ref());
)?;
apply_risk_policy_overrides(&mut cfg, execution.risk_policy.as_ref())?;
apply_execution_behavior_overrides(
&mut cfg,
execution.matching_type.as_deref(),
@@ -2258,6 +2261,37 @@ mod tests {
assert_eq!(cfg.matching_type, MatchingType::MinuteLast);
}
#[test]
fn rejects_invalid_explicit_volume_percent_in_platform_config() {
let engine_value = serde_json::json!({
"engine_config": {
"volume_percent": 101
}
});
let engine_error = platform_expr_config_from_value("", "", &engine_value)
.expect_err("volume_percent above 100 must fail");
assert!(
engine_error
.to_string()
.contains("volume_percent must be <= 100")
);
let policy_value = serde_json::json!({
"execution": {
"riskPolicy": {
"volumePercent": 0
}
}
});
let policy_error = platform_expr_config_from_value("", "", &policy_value)
.expect_err("zero riskPolicy volumePercent must fail");
assert!(
policy_error
.to_string()
.contains("volume_percent must be a positive finite number")
);
}
#[test]
fn parses_execution_slippage_overrides_into_platform_config() {
let spec = serde_json::json!({