fix: evaluate buy quote conditions at the active schedule clock
This commit is contained in:
@@ -12212,17 +12212,19 @@ impl PlatformExprStrategy {
|
||||
}
|
||||
let day = self.day_state(ctx, ctx.decision_date)?;
|
||||
let (market_date, _, factor_date) = self.selection_dates(ctx);
|
||||
let execution_time = ctx.active_datetime.filter(|value| value.date() == market_date)
|
||||
.map(|value| value.time()).or(self.config.intraday_execution_time);
|
||||
let needs_quote = Self::stock_filter_quote_usage_for_expr(&Self::normalize_expr(&self.config.buy_filter_expr))
|
||||
!= StockFilterQuoteUsage::DailyOnly;
|
||||
for symbol in symbols {
|
||||
if needs_quote && self.uses_intraday_execution_quotes() && !ctx.is_lagged_execution()
|
||||
&& self.scheduled_quote(ctx, market_date, &symbol).is_none()
|
||||
&& self.scheduled_quote_at_time(ctx, market_date, &symbol, execution_time).is_none()
|
||||
{
|
||||
return Err(BacktestError::Execution(format!(
|
||||
"buy condition quote unavailable: symbol={symbol} decision_date={}", ctx.decision_date,
|
||||
)));
|
||||
}
|
||||
let stock = self.stock_state_with_factor_date(ctx, market_date, factor_date, &symbol)?;
|
||||
let stock = self.stock_state_with_factor_date_and_time(ctx, market_date, factor_date, &symbol, execution_time, true)?;
|
||||
if !self.eval_bool(ctx, &self.config.buy_filter_expr, &day, Some(&stock), None)? {
|
||||
decision.buy_denials.insert(symbol, format!(
|
||||
"strategy_buy_condition_false decision_date={} expression={}",
|
||||
@@ -14073,6 +14075,40 @@ mod tests {
|
||||
assert_eq!(strategy.selection_quote_usage, StockFilterQuoteUsage::DailyOnly);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buy_filter_uses_active_schedule_time_instead_of_first_configured_time() {
|
||||
let date = d(2025, 1, 2);
|
||||
let symbol = "000001.SZ";
|
||||
let parts = single_symbol_platform_data(&[date], symbol).snapshot_components();
|
||||
let quotes = [(10, 18, 9.5), (14, 59, 10.5)].into_iter().map(|(hour, minute, price)| IntradayExecutionQuote {
|
||||
date, symbol: symbol.to_string(), timestamp: date.and_hms_opt(hour, minute, 0).unwrap(),
|
||||
last_price: price, bid1: price, ask1: price, bid1_volume: 1000, ask1_volume: 1000,
|
||||
volume_delta: 1000, amount_delta: price * 1000.0, trading_phase: Some("continuous".to_string()),
|
||||
}).collect();
|
||||
let data = DataSet::from_components_with_actions_and_quotes(parts.instruments, parts.market,
|
||||
parts.factors, parts.candidates, parts.benchmarks, Vec::new(), quotes).unwrap();
|
||||
let portfolio = PortfolioState::new(30_000.0);
|
||||
let subscriptions = BTreeSet::new();
|
||||
let mut 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: None, order_events: &[], fills: &[],
|
||||
};
|
||||
let mut cfg = PlatformExprStrategyConfig::generic();
|
||||
cfg.signal_symbol = symbol.to_string();
|
||||
cfg.buy_filter_expr = "last > 10".to_string();
|
||||
cfg.intraday_execution_time = NaiveTime::from_hms_opt(10, 18, 0);
|
||||
let strategy = PlatformExprStrategy::new(cfg);
|
||||
for (hour, minute, denied) in [(10, 18, true), (14, 59, false)] {
|
||||
ctx.active_datetime = Some(date.and_hms_opt(hour, minute, 0).unwrap());
|
||||
let mut decision = crate::StrategyDecision::default();
|
||||
decision.order_intents.push(OrderIntent::TargetValue { symbol: symbol.to_string(), target_value: 10_000.0, reason: "target".to_string() });
|
||||
strategy.attach_buy_denials(&ctx, &mut decision).unwrap();
|
||||
assert_eq!(decision.buy_denials.contains_key(symbol), denied);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completed_session_factor_dates_exclude_intraday_and_preserve_next_open() {
|
||||
let prev = d(2025, 1, 2);
|
||||
|
||||
Reference in New Issue
Block a user