diff --git a/crates/fidc-core/src/platform_strategy_spec.rs b/crates/fidc-core/src/platform_strategy_spec.rs index e244c16..4217e44 100644 --- a/crates/fidc-core/src/platform_strategy_spec.rs +++ b/crates/fidc-core/src/platform_strategy_spec.rs @@ -369,17 +369,18 @@ pub fn platform_expr_config_from_value( value: &Value, ) -> Result { if value.is_null() || value.as_object().is_some_and(|object| object.is_empty()) { - return Ok(platform_expr_config_from_spec( - strategy_id, - signal_symbol, - None, - )); + return platform_expr_config_from_spec(strategy_id, signal_symbol, None) + .map_err(platform_config_error); } let spec = serde_json::from_value::(value.clone())?; - Ok(platform_expr_config_from_spec( - strategy_id, - signal_symbol, - Some(&spec), + platform_expr_config_from_spec(strategy_id, signal_symbol, Some(&spec)) + .map_err(platform_config_error) +} + +fn platform_config_error(message: String) -> serde_json::Error { + serde_json::Error::io(std::io::Error::new( + std::io::ErrorKind::InvalidData, + message, )) } @@ -418,12 +419,17 @@ fn normalize_model_name(value: &str) -> String { value.trim().to_ascii_lowercase().replace('-', "_") } -fn parse_matching_type(value: Option<&str>) -> Option { - match normalize_model_name(value?).as_str() { - "current_bar_close" => Some(MatchingType::CurrentBarClose), - "next_bar_open" => Some(MatchingType::NextBarOpen), - "minute_last" => Some(MatchingType::MinuteLast), - _ => None, +fn parse_matching_type(value: Option<&str>) -> Result, String> { + let Some(raw) = value.map(str::trim).filter(|item| !item.is_empty()) else { + return Ok(None); + }; + match normalize_model_name(raw).as_str() { + "current_bar_close" => Ok(Some(MatchingType::CurrentBarClose)), + "next_bar_open" => Ok(Some(MatchingType::NextBarOpen)), + "minute_last" => Ok(Some(MatchingType::MinuteLast)), + _ => Err(format!( + "matchingType only supports current_bar_close, next_bar_open, minute_last: {raw}" + )), } } @@ -474,8 +480,8 @@ fn apply_execution_behavior_overrides( slippage_volatility_coefficient: Option, slippage_max_value: Option, strict_value_budget: Option, -) { - if let Some(matching_type) = parse_matching_type(matching_type) { +) -> Result<(), String> { + if let Some(matching_type) = parse_matching_type(matching_type)? { cfg.matching_type = matching_type; } if slippage_model.is_some() @@ -497,6 +503,7 @@ fn apply_execution_behavior_overrides( if let Some(enabled) = strict_value_budget { cfg.strict_value_budget = enabled; } + Ok(()) } fn parse_usize_after(text: &str, start: usize) -> Option<(usize, usize)> { @@ -626,7 +633,7 @@ pub fn platform_expr_config_from_spec( strategy_id: &str, signal_symbol: &str, strategy_spec: Option<&StrategyRuntimeSpec>, -) -> PlatformExprStrategyConfig { +) -> Result { let mut cfg = PlatformExprStrategyConfig::microcap_rotation(); cfg.strategy_name = strategy_id.to_string(); if !signal_symbol.trim().is_empty() { @@ -634,7 +641,7 @@ pub fn platform_expr_config_from_spec( } let Some(spec) = strategy_spec else { - return cfg; + return Ok(cfg); }; let mut benchmark_short_explicit = false; let mut benchmark_long_explicit = false; @@ -751,7 +758,7 @@ pub fn platform_expr_config_from_spec( engine.slippage_volatility_coefficient, engine.slippage_max_value, engine.strict_value_budget, - ); + )?; } if let Some(spec_signal_symbol) = spec @@ -1151,7 +1158,7 @@ pub fn platform_expr_config_from_spec( execution.slippage_volatility_coefficient, execution.slippage_max_value, execution.strict_value_budget, - ); + )?; } if cfg.aiquant_transaction_cost && cfg @@ -1161,7 +1168,7 @@ pub fn platform_expr_config_from_spec( cfg.minimum_commission = None; } - cfg + Ok(cfg) } fn parse_platform_rebalance_schedule( @@ -1706,9 +1713,15 @@ mod tests { } }); - let cfg = platform_expr_config_from_value("", "", &spec).expect("config"); + let err = + platform_expr_config_from_value("", "", &spec).expect_err("invalid matchingType"); - assert_eq!(cfg.matching_type, MatchingType::CurrentBarClose); + assert!( + err.to_string().contains( + "matchingType only supports current_bar_close, next_bar_open, minute_last" + ), + "{err}" + ); } }