fix: reject invalid historical slippage bounds before execution
This commit is contained in:
@@ -15008,6 +15008,31 @@ mod tests {
|
|||||||
assert_eq!(shared.version_sha256(),shared_version);
|
assert_eq!(shared.version_sha256(),shared_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn projected_historical_slippage_does_not_swallow_missing_calibration() {
|
||||||
|
let date = d(2025, 1, 7);
|
||||||
|
let symbol = "000001.SZ";
|
||||||
|
let data = single_symbol_platform_data(&[date], symbol);
|
||||||
|
let portfolio = PortfolioState::new(100_000.0);
|
||||||
|
let subscriptions = BTreeSet::new();
|
||||||
|
let ctx = StrategyContext {
|
||||||
|
execution_date: date, decision_date: date, decision_index: 0, data: &data, portfolio: &portfolio,
|
||||||
|
futures_account: None, open_orders: &[], dynamic_universe: None, subscriptions: &subscriptions,
|
||||||
|
process_events: &[], active_process_event: None, active_datetime: Some(date.and_hms_opt(15, 0, 0).unwrap()),
|
||||||
|
order_events: &[], fills: &[],
|
||||||
|
};
|
||||||
|
let mut config = PlatformExprStrategyConfig::generic();
|
||||||
|
config.signal_symbol = symbol.into();
|
||||||
|
config.slippage_model = SlippageModel::HistoricalVolumeVolatility(crate::DynamicSlippageConfig::default());
|
||||||
|
let strategy = PlatformExprStrategy::new(config);
|
||||||
|
let mut projected = portfolio.clone();
|
||||||
|
let result = strategy.project_order_value(&ctx, &mut projected, date, symbol, 50_000.0, &mut ProjectedExecutionState::default());
|
||||||
|
let error = result.expect_err("calibration failures must reach the strategy caller");
|
||||||
|
assert!(error.to_string().contains("historical_slippage_calibration_missing"), "{error}");
|
||||||
|
assert_eq!(projected.cash(), portfolio.cash());
|
||||||
|
assert!(projected.positions().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn portfolio_loss_observes_finalized_nav_after_fees_and_cash_flows() {
|
fn portfolio_loss_observes_finalized_nav_after_fees_and_cash_flows() {
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|||||||
@@ -1579,10 +1579,6 @@ fn parse_slippage_model(
|
|||||||
return Err(format!("{name} must be finite and non-negative"));
|
return Err(format!("{name} must be finite and non-negative"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let value = valid_non_negative(value);
|
|
||||||
let impact_coefficient = valid_non_negative(impact_coefficient);
|
|
||||||
let volatility_coefficient = valid_non_negative(volatility_coefficient);
|
|
||||||
let max_value = valid_non_negative(max_value);
|
|
||||||
let model = model
|
let model = model
|
||||||
.map(normalize_slippage_model_name)
|
.map(normalize_slippage_model_name)
|
||||||
.filter(|item| !item.is_empty())
|
.filter(|item| !item.is_empty())
|
||||||
@@ -1599,11 +1595,15 @@ fn parse_slippage_model(
|
|||||||
"price_ratio" => Ok(SlippageModel::PriceRatio(value.unwrap_or(0.0))),
|
"price_ratio" => Ok(SlippageModel::PriceRatio(value.unwrap_or(0.0))),
|
||||||
"tick_size" => Ok(SlippageModel::TickSize(value.unwrap_or(0.0))),
|
"tick_size" => Ok(SlippageModel::TickSize(value.unwrap_or(0.0))),
|
||||||
"limit_price" => Ok(SlippageModel::LimitPrice),
|
"limit_price" => Ok(SlippageModel::LimitPrice),
|
||||||
"historical_volume_volatility" => Ok(SlippageModel::HistoricalVolumeVolatility(DynamicSlippageConfig::new(
|
"historical_volume_volatility" => {
|
||||||
impact_coefficient.unwrap_or(0.5),
|
let max_ratio = max_value.or(value).unwrap_or(0.01);
|
||||||
volatility_coefficient.unwrap_or(0.3),
|
if max_ratio >= 1.0 {
|
||||||
max_value.or(value).unwrap_or(0.01),
|
return Err("historical slippage maximum must be less than 1".into());
|
||||||
))),
|
}
|
||||||
|
Ok(SlippageModel::HistoricalVolumeVolatility(DynamicSlippageConfig::new(
|
||||||
|
impact_coefficient.unwrap_or(0.5), volatility_coefficient.unwrap_or(0.3), max_ratio,
|
||||||
|
)))
|
||||||
|
},
|
||||||
"dynamic" | "dynamic_volume_volatility" => Err(
|
"dynamic" | "dynamic_volume_volatility" => Err(
|
||||||
"retired_slippage_model: dynamic used unfinished daily data; explicitly select historical_volume_volatility or another supported model".into()
|
"retired_slippage_model: dynamic used unfinished daily data; explicitly select historical_volume_volatility or another supported model".into()
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user