修复表达式策略next-open风控日期语义

This commit is contained in:
boris
2026-07-04 03:05:45 +08:00
parent 5f2697540a
commit 5481db63df
+110 -71
View File
@@ -4915,6 +4915,7 @@ impl PlatformExprStrategy {
}; };
let selection_date = if self.config.aiquant_transaction_cost let selection_date = if self.config.aiquant_transaction_cost
&& self.config.intraday_execution_time.is_some() && self.config.intraday_execution_time.is_some()
&& !ctx.is_lagged_execution()
{ {
ctx.execution_date ctx.execution_date
} else { } else {
@@ -5756,9 +5757,10 @@ impl PlatformExprStrategy {
&self, &self,
ctx: &StrategyContext<'_>, ctx: &StrategyContext<'_>,
) -> Result<StrategyDecision, BacktestError> { ) -> Result<StrategyDecision, BacktestError> {
let day = self.day_state(ctx, ctx.execution_date)?; let decision_date = ctx.decision_date;
let day = self.day_state(ctx, decision_date)?;
let (order_intents, action_diagnostics) = let (order_intents, action_diagnostics) =
self.explicit_action_intents(ctx, ctx.execution_date, &day)?; self.explicit_action_intents(ctx, decision_date, &day)?;
let mut diagnostics = vec![format!( let mut diagnostics = vec![format!(
"platform_expr signal={} last={:.2} explicit_actions={} stage={}", "platform_expr signal={} last={:.2} explicit_actions={} stage={}",
self.config.signal_symbol, self.config.signal_symbol,
@@ -5921,7 +5923,8 @@ impl PlatformExprStrategy {
let Some(market) = ctx.data.market(date, &factor.symbol) else { let Some(market) = ctx.data.market(date, &factor.symbol) else {
continue; continue;
}; };
if self if !ctx.is_lagged_execution()
&& self
.selection_risk_rejection_reason(ctx, date, &factor.symbol, candidate, market) .selection_risk_rejection_reason(ctx, date, &factor.symbol, candidate, market)
.is_some() .is_some()
{ {
@@ -5956,6 +5959,9 @@ impl PlatformExprStrategy {
date: NaiveDate, date: NaiveDate,
factor_date: NaiveDate, factor_date: NaiveDate,
) -> Vec<FidcRiskDecisionAudit> { ) -> Vec<FidcRiskDecisionAudit> {
if ctx.is_lagged_execution() {
return Vec::new();
}
let mut decisions = Vec::new(); let mut decisions = Vec::new();
for factor in ctx.data.factor_snapshots_on(factor_date) { for factor in ctx.data.factor_snapshots_on(factor_date) {
if ctx.has_dynamic_universe() && !ctx.dynamic_universe_contains(&factor.symbol) { if ctx.has_dynamic_universe() && !ctx.dynamic_universe_contains(&factor.symbol) {
@@ -6401,13 +6407,18 @@ impl PlatformExprStrategy {
let mut selected = Vec::new(); let mut selected = Vec::new();
for (candidate, stock, _) in candidates { for (candidate, stock, _) in candidates {
if let Some(reason) = self.stock_selection_limit_rejection_reason(&stock) { if !ctx.is_lagged_execution()
&& let Some(reason) = self.stock_selection_limit_rejection_reason(&stock)
{
if diagnostics.len() < 12 { if diagnostics.len() < 12 {
diagnostics.push(format!("{} rejected by {}", candidate.symbol, reason)); diagnostics.push(format!("{} rejected by {}", candidate.symbol, reason));
} }
continue; continue;
} }
if let Some(reason) = self.buy_rejection_reason(ctx, date, &candidate.symbol, &stock)? { if !ctx.is_lagged_execution()
&& let Some(reason) =
self.buy_rejection_reason(ctx, date, &candidate.symbol, &stock)?
{
if diagnostics.len() < 12 { if diagnostics.len() < 12 {
diagnostics.push(format!("{} rejected by {}", candidate.symbol, reason)); diagnostics.push(format!("{} rejected by {}", candidate.symbol, reason));
} }
@@ -7083,13 +7094,17 @@ impl PlatformExprStrategy {
if quote_candidate_symbols.len() < quote_candidate_limit { if quote_candidate_symbols.len() < quote_candidate_limit {
quote_candidate_symbols.push(symbol.clone()); quote_candidate_symbols.push(symbol.clone());
} }
if let Some(reason) = self.stock_selection_limit_rejection_reason(stock) { if !ctx.is_lagged_execution()
&& let Some(reason) = self.stock_selection_limit_rejection_reason(stock)
{
if diagnostics.len() < 12 { if diagnostics.len() < 12 {
diagnostics.push(format!("{symbol} quote_plan rejected by {reason}")); diagnostics.push(format!("{symbol} quote_plan rejected by {reason}"));
} }
continue; continue;
} }
if let Some(reason) = self.buy_rejection_reason(ctx, date, symbol, &stock)? { if !ctx.is_lagged_execution()
&& let Some(reason) = self.buy_rejection_reason(ctx, date, symbol, &stock)?
{
if diagnostics.len() < 12 { if diagnostics.len() < 12 {
diagnostics.push(format!("{symbol} quote_plan rejected by {reason}")); diagnostics.push(format!("{symbol} quote_plan rejected by {reason}"));
} }
@@ -7123,11 +7138,11 @@ impl PlatformExprStrategy {
ctx: &StrategyContext<'_>, ctx: &StrategyContext<'_>,
_scope_limit: usize, _scope_limit: usize,
) -> Result<PlatformSelectionQuotePlan, BacktestError> { ) -> Result<PlatformSelectionQuotePlan, BacktestError> {
if !self.config.rotation_enabled || self.config.in_skip_window(ctx.execution_date) { if !self.config.rotation_enabled || self.config.in_skip_window(ctx.decision_date) {
return Ok(PlatformSelectionQuotePlan { return Ok(PlatformSelectionQuotePlan {
execution_date: ctx.execution_date, execution_date: ctx.execution_date,
decision_date: ctx.decision_date, decision_date: ctx.decision_date,
selection_date: ctx.execution_date, selection_date: ctx.decision_date,
factor_date: ctx.decision_date, factor_date: ctx.decision_date,
requires_intraday_selection_quotes: false, requires_intraday_selection_quotes: false,
band_low: 0.0, band_low: 0.0,
@@ -7178,21 +7193,21 @@ impl PlatformExprStrategy {
fn stop_take_action( fn stop_take_action(
&self, &self,
ctx: &StrategyContext<'_>, ctx: &StrategyContext<'_>,
_signal_date: NaiveDate, signal_date: NaiveDate,
execution_date: NaiveDate, _execution_date: NaiveDate,
day: &DayExpressionState, day: &DayExpressionState,
symbol: &str, symbol: &str,
) -> Result<(bool, bool), BacktestError> { ) -> Result<(bool, bool), BacktestError> {
let Some(position) = ctx.portfolio.position(symbol) else { let Some(position) = ctx.portfolio.position(symbol) else {
return Ok((false, false)); return Ok((false, false));
}; };
self.stop_take_action_for_position(ctx, execution_date, day, position) self.stop_take_action_for_position(ctx, signal_date, day, position)
} }
fn stop_take_action_for_position( fn stop_take_action_for_position(
&self, &self,
ctx: &StrategyContext<'_>, ctx: &StrategyContext<'_>,
execution_date: NaiveDate, signal_date: NaiveDate,
day: &DayExpressionState, day: &DayExpressionState,
position: &crate::portfolio::Position, position: &crate::portfolio::Position,
) -> Result<(bool, bool), BacktestError> { ) -> Result<(bool, bool), BacktestError> {
@@ -7219,7 +7234,7 @@ impl PlatformExprStrategy {
} }
let stock = match self.stock_state_at_time( let stock = match self.stock_state_at_time(
ctx, ctx,
execution_date, signal_date,
symbol, symbol,
Some(self.intraday_execution_start_time()), Some(self.intraday_execution_start_time()),
) { ) {
@@ -7249,9 +7264,9 @@ impl PlatformExprStrategy {
prev_close: stock.prev_close, prev_close: stock.prev_close,
holding_return, holding_return,
quantity: position.quantity as i64, quantity: position.quantity as i64,
sellable_qty: position.sellable_qty(execution_date) as i64, sellable_qty: position.sellable_qty(signal_date) as i64,
sellable: position.sellable_qty(execution_date) as i64, sellable: position.sellable_qty(signal_date) as i64,
closable: position.sellable_qty(execution_date) as i64, closable: position.sellable_qty(signal_date) as i64,
old_quantity: position.day_start_quantity() as i64, old_quantity: position.day_start_quantity() as i64,
bought_quantity: position.bought_quantity() as i64, bought_quantity: position.bought_quantity() as i64,
sold_quantity: position.sold_quantity() as i64, sold_quantity: position.sold_quantity() as i64,
@@ -7352,7 +7367,7 @@ impl Strategy for PlatformExprStrategy {
.filter(|symbol| !symbol.trim().is_empty()) .filter(|symbol| !symbol.trim().is_empty())
.cloned() .cloned()
.collect::<BTreeSet<_>>(); .collect::<BTreeSet<_>>();
if self.config.rotation_enabled && !self.config.in_skip_window(ctx.execution_date) { if self.config.rotation_enabled && !self.config.in_skip_window(ctx.decision_date) {
let plan = self.selection_quote_plan(ctx, 0)?; let plan = self.selection_quote_plan(ctx, 0)?;
symbols.extend(plan.order_symbols); symbols.extend(plan.order_symbols);
symbols.extend(plan.candidate_symbols); symbols.extend(plan.candidate_symbols);
@@ -7366,7 +7381,7 @@ impl Strategy for PlatformExprStrategy {
) -> Result<StrategyDecision, BacktestError> { ) -> Result<StrategyDecision, BacktestError> {
if self.config.explicit_action_stage == PlatformExplicitActionStage::OpenAuction if self.config.explicit_action_stage == PlatformExplicitActionStage::OpenAuction
&& !self.config.explicit_actions.is_empty() && !self.config.explicit_actions.is_empty()
&& self.explicit_actions_active(ctx.data.calendar(), ctx.execution_date) && self.explicit_actions_active(ctx.data.calendar(), ctx.decision_date)
{ {
return self.explicit_action_decision(ctx); return self.explicit_action_decision(ctx);
} }
@@ -7376,14 +7391,21 @@ impl Strategy for PlatformExprStrategy {
fn on_day(&mut self, ctx: &StrategyContext<'_>) -> Result<StrategyDecision, BacktestError> { fn on_day(&mut self, ctx: &StrategyContext<'_>) -> Result<StrategyDecision, BacktestError> {
let execution_date = ctx.execution_date; let execution_date = ctx.execution_date;
let decision_date = ctx.decision_date; let decision_date = ctx.decision_date;
let in_skip_window = self.config.in_skip_window(execution_date); let defer_execution_risk = ctx.is_lagged_execution();
let signal_date = decision_date;
let projection_date = if defer_execution_risk {
signal_date
} else {
execution_date
};
let in_skip_window = self.config.in_skip_window(signal_date);
let day = self.day_state(ctx, decision_date)?; let day = self.day_state(ctx, decision_date)?;
let (selection_market_date, selection_universe_factor_date, selection_factor_date) = let (selection_market_date, selection_universe_factor_date, selection_factor_date) =
self.selection_dates(ctx); self.selection_dates(ctx);
let (explicit_action_intents, explicit_action_diagnostics) = if !in_skip_window let (explicit_action_intents, explicit_action_diagnostics) = if !in_skip_window
&& self.config.explicit_action_stage == PlatformExplicitActionStage::OnDay && self.config.explicit_action_stage == PlatformExplicitActionStage::OnDay
&& self.explicit_actions_active(ctx.data.calendar(), execution_date) && self.explicit_actions_active(ctx.data.calendar(), signal_date)
{ {
self.explicit_action_intents(ctx, decision_date, &day)? self.explicit_action_intents(ctx, decision_date, &day)?
} else { } else {
@@ -7398,7 +7420,7 @@ impl Strategy for PlatformExprStrategy {
let previous_trading_ratio = self.last_trading_ratio.unwrap_or(1.0); let previous_trading_ratio = self.last_trading_ratio.unwrap_or(1.0);
let weak_market_shrink_due = let weak_market_shrink_due =
trading_ratio.is_finite() && trading_ratio < previous_trading_ratio - 1e-9; trading_ratio.is_finite() && trading_ratio < previous_trading_ratio - 1e-9;
let marked_total_value = self.marked_total_value(ctx, execution_date); let marked_total_value = self.marked_total_value(ctx, projection_date);
let mut aiquant_total_value = if marked_total_value.is_finite() && marked_total_value > 0.0 let mut aiquant_total_value = if marked_total_value.is_finite() && marked_total_value > 0.0
{ {
marked_total_value marked_total_value
@@ -7445,14 +7467,14 @@ impl Strategy for PlatformExprStrategy {
} else if let Some(schedule) = &self.config.rebalance_schedule { } else if let Some(schedule) = &self.config.rebalance_schedule {
schedule.matches( schedule.matches(
ctx.data.calendar(), ctx.data.calendar(),
execution_date, signal_date,
ScheduleStage::OnDay, ScheduleStage::OnDay,
default_stage_time(ScheduleStage::OnDay), default_stage_time(ScheduleStage::OnDay),
) || empty_rebalance_retry ) || empty_rebalance_retry
} else if self.config.calendar_rebalance_interval { } else if self.config.calendar_rebalance_interval {
self.last_rebalance_date self.last_rebalance_date
.map(|last| { .map(|last| {
execution_date.signed_duration_since(last).num_days() signal_date.signed_duration_since(last).num_days()
>= effective_refresh_rate as i64 >= effective_refresh_rate as i64
}) })
.unwrap_or(true) .unwrap_or(true)
@@ -7489,7 +7511,7 @@ impl Strategy for PlatformExprStrategy {
} }
match self.stock_is_at_upper_limit_at_time( match self.stock_is_at_upper_limit_at_time(
ctx, ctx,
execution_date, projection_date,
&position.symbol, &position.symbol,
delayed_limit_exit_time, delayed_limit_exit_time,
)? { )? {
@@ -7517,7 +7539,7 @@ impl Strategy for PlatformExprStrategy {
if self if self
.aiquant_scheduled_quote_at_time( .aiquant_scheduled_quote_at_time(
ctx, ctx,
execution_date, projection_date,
&symbol, &symbol,
Some(delayed_limit_exit_time), Some(delayed_limit_exit_time),
) )
@@ -7527,7 +7549,7 @@ impl Strategy for PlatformExprStrategy {
} }
let stock = match self.stock_state_at_time( let stock = match self.stock_state_at_time(
ctx, ctx,
execution_date, projection_date,
&symbol, &symbol,
Some(delayed_limit_exit_time), Some(delayed_limit_exit_time),
) { ) {
@@ -7559,7 +7581,7 @@ impl Strategy for PlatformExprStrategy {
}); });
let projected_sold = if ctx let projected_sold = if ctx
.data .data
.execution_quotes_on(execution_date, &symbol) .execution_quotes_on(projection_date, &symbol)
.is_empty() .is_empty()
{ {
false false
@@ -7567,7 +7589,7 @@ impl Strategy for PlatformExprStrategy {
self.project_target_zero_at_time( self.project_target_zero_at_time(
ctx, ctx,
&mut projected, &mut projected,
execution_date, projection_date,
&symbol, &symbol,
&mut projected_execution_state, &mut projected_execution_state,
Some(delayed_limit_exit_time), Some(delayed_limit_exit_time),
@@ -7583,7 +7605,7 @@ impl Strategy for PlatformExprStrategy {
} }
if !delayed_sold_symbols.is_empty() { if !delayed_sold_symbols.is_empty() {
let projected_total = let projected_total =
self.marked_total_value_for_portfolio(ctx, &projected, execution_date); self.marked_total_value_for_portfolio(ctx, &projected, projection_date);
if projected_total.is_finite() && projected_total > 0.0 { if projected_total.is_finite() && projected_total > 0.0 {
aiquant_total_value = projected_total; aiquant_total_value = projected_total;
} }
@@ -7608,7 +7630,7 @@ impl Strategy for PlatformExprStrategy {
reason: "seasonal_stop_window".to_string(), reason: "seasonal_stop_window".to_string(),
}); });
} }
let mut notes = vec![format!("seasonal stop window on {}", execution_date)]; let mut notes = vec![format!("seasonal stop window on {}", signal_date)];
if !delayed_sold_symbols.is_empty() { if !delayed_sold_symbols.is_empty() {
notes.push(format!( notes.push(format!(
"delayed limit open sells before seasonal stop: {}", "delayed limit open sells before seasonal stop: {}",
@@ -7659,7 +7681,7 @@ impl Strategy for PlatformExprStrategy {
} }
if self.regular_sell_should_wait_due_to_highlimit( if self.regular_sell_should_wait_due_to_highlimit(
ctx, ctx,
execution_date, projection_date,
&position.symbol, &position.symbol,
self.intraday_execution_start_time(), self.intraday_execution_start_time(),
)? { )? {
@@ -7667,7 +7689,7 @@ impl Strategy for PlatformExprStrategy {
} }
if self.candidate_requires_risk_level_forced_exit( if self.candidate_requires_risk_level_forced_exit(
ctx, ctx,
execution_date, projection_date,
&position.symbol, &position.symbol,
risk_level_forced_exit_time, risk_level_forced_exit_time,
)? { )? {
@@ -7676,7 +7698,7 @@ impl Strategy for PlatformExprStrategy {
continue; continue;
} }
let (stop_hit, profit_hit) = let (stop_hit, profit_hit) =
self.stop_take_action_for_position(ctx, execution_date, &day, position)?; self.stop_take_action_for_position(ctx, signal_date, &day, position)?;
if stop_hit { if stop_hit {
pending_full_close_symbols.insert(position.symbol.clone()); pending_full_close_symbols.insert(position.symbol.clone());
continue; continue;
@@ -7685,14 +7707,15 @@ impl Strategy for PlatformExprStrategy {
pending_full_close_symbols.insert(position.symbol.clone()); pending_full_close_symbols.insert(position.symbol.clone());
continue; continue;
} }
let can_sell = self.can_sell_position(ctx, execution_date, &position.symbol); let can_sell = defer_execution_risk
|| self.can_sell_position(ctx, execution_date, &position.symbol);
if !can_sell { if !can_sell {
continue; continue;
} }
if self.config.delayed_limit_open_exit_enabled { if self.config.delayed_limit_open_exit_enabled {
let stock = match self.stock_state_at_time( let stock = match self.stock_state_at_time(
ctx, ctx,
execution_date, projection_date,
&position.symbol, &position.symbol,
Some(self.intraday_execution_start_time()), Some(self.intraday_execution_start_time()),
) { ) {
@@ -7726,7 +7749,7 @@ impl Strategy for PlatformExprStrategy {
.project_target_zero_at_time( .project_target_zero_at_time(
ctx, ctx,
&mut projected, &mut projected,
execution_date, projection_date,
&symbol, &symbol,
&mut projected_execution_state, &mut projected_execution_state,
Some(risk_level_forced_exit_time), Some(risk_level_forced_exit_time),
@@ -7755,7 +7778,7 @@ impl Strategy for PlatformExprStrategy {
} }
if self.regular_sell_should_wait_due_to_highlimit( if self.regular_sell_should_wait_due_to_highlimit(
ctx, ctx,
execution_date, projection_date,
&position.symbol, &position.symbol,
self.intraday_execution_start_time(), self.intraday_execution_start_time(),
)? { )? {
@@ -7767,7 +7790,7 @@ impl Strategy for PlatformExprStrategy {
let current_value = self.projected_position_value_at_execution_price( let current_value = self.projected_position_value_at_execution_price(
ctx, ctx,
&projected, &projected,
execution_date, projection_date,
&position.symbol, &position.symbol,
); );
if let Some(threshold) = self if let Some(threshold) = self
@@ -7788,7 +7811,7 @@ impl Strategy for PlatformExprStrategy {
self.project_target_value( self.project_target_value(
ctx, ctx,
&mut trial_projected, &mut trial_projected,
execution_date, projection_date,
&position.symbol, &position.symbol,
target_value, target_value,
&mut trial_execution_state, &mut trial_execution_state,
@@ -7834,15 +7857,16 @@ impl Strategy for PlatformExprStrategy {
} }
if self.regular_sell_should_wait_due_to_highlimit( if self.regular_sell_should_wait_due_to_highlimit(
ctx, ctx,
execution_date, projection_date,
&position.symbol, &position.symbol,
self.intraday_execution_start_time(), self.intraday_execution_start_time(),
)? { )? {
continue; continue;
} }
let (stop_hit, profit_hit) = let (stop_hit, profit_hit) =
self.stop_take_action_for_position(ctx, execution_date, &day, projected_position)?; self.stop_take_action_for_position(ctx, signal_date, &day, projected_position)?;
let can_sell = self.can_sell_position(ctx, execution_date, &position.symbol); let can_sell = defer_execution_risk
|| self.can_sell_position(ctx, execution_date, &position.symbol);
if stop_hit { if stop_hit {
exit_symbols.insert(position.symbol.clone()); exit_symbols.insert(position.symbol.clone());
order_intents.push(OrderIntent::TargetValue { order_intents.push(OrderIntent::TargetValue {
@@ -7855,7 +7879,7 @@ impl Strategy for PlatformExprStrategy {
.project_target_zero( .project_target_zero(
ctx, ctx,
&mut projected, &mut projected,
execution_date, projection_date,
&position.symbol, &position.symbol,
&mut projected_execution_state, &mut projected_execution_state,
) )
@@ -7883,7 +7907,7 @@ impl Strategy for PlatformExprStrategy {
.project_target_zero( .project_target_zero(
ctx, ctx,
&mut projected, &mut projected,
execution_date, projection_date,
&position.symbol, &position.symbol,
&mut projected_execution_state, &mut projected_execution_state,
) )
@@ -7919,7 +7943,7 @@ impl Strategy for PlatformExprStrategy {
self.remaining_buy_cash_active_value( self.remaining_buy_cash_active_value(
ctx, ctx,
&projected, &projected,
execution_date, projection_date,
&working_symbols, &working_symbols,
&value_symbols, &value_symbols,
) )
@@ -7929,7 +7953,7 @@ impl Strategy for PlatformExprStrategy {
let slot_buy_cash = self.remaining_buy_cash_per_slot( let slot_buy_cash = self.remaining_buy_cash_per_slot(
ctx, ctx,
&projected, &projected,
execution_date, projection_date,
target_budget, target_budget,
selection_limit, selection_limit,
&working_symbols, &working_symbols,
@@ -7966,9 +7990,14 @@ impl Strategy for PlatformExprStrategy {
{ {
continue; continue;
} }
let execution_stock = self.stock_state(ctx, execution_date, symbol)?; if !defer_execution_risk
if self && self
.buy_rejection_reason(ctx, execution_date, symbol, &execution_stock)? .buy_rejection_reason(
ctx,
execution_date,
symbol,
&self.stock_state(ctx, execution_date, symbol)?,
)?
.is_some() .is_some()
{ {
continue; continue;
@@ -7990,7 +8019,7 @@ impl Strategy for PlatformExprStrategy {
let filled_qty = self.project_order_value( let filled_qty = self.project_order_value(
ctx, ctx,
&mut projected, &mut projected,
execution_date, projection_date,
symbol, symbol,
buy_cash, buy_cash,
&mut projected_execution_state, &mut projected_execution_state,
@@ -8041,13 +8070,13 @@ impl Strategy for PlatformExprStrategy {
} }
if self.regular_sell_should_wait_due_to_highlimit( if self.regular_sell_should_wait_due_to_highlimit(
ctx, ctx,
execution_date, projection_date,
symbol, symbol,
self.intraday_execution_start_time(), self.intraday_execution_start_time(),
)? { )? {
continue; continue;
} }
if !self.can_sell_position(ctx, execution_date, symbol) { if !defer_execution_risk && !self.can_sell_position(ctx, execution_date, symbol) {
continue; continue;
} }
order_intents.push(OrderIntent::TargetValue { order_intents.push(OrderIntent::TargetValue {
@@ -8059,7 +8088,7 @@ impl Strategy for PlatformExprStrategy {
.project_target_zero( .project_target_zero(
ctx, ctx,
&mut projected, &mut projected,
execution_date, projection_date,
symbol, symbol,
&mut projected_execution_state, &mut projected_execution_state,
) )
@@ -8109,16 +8138,21 @@ impl Strategy for PlatformExprStrategy {
let current_value = self.projected_position_value_at_execution_price( let current_value = self.projected_position_value_at_execution_price(
ctx, ctx,
&projected, &projected,
execution_date, projection_date,
symbol, symbol,
); );
let buy_cash = (target_cash - current_value).min(aiquant_available_cash); let buy_cash = (target_cash - current_value).min(aiquant_available_cash);
if buy_cash <= 0.0 { if buy_cash <= 0.0 {
continue; continue;
} }
let execution_stock = self.stock_state(ctx, execution_date, symbol)?; if !defer_execution_risk
if self && self
.buy_rejection_reason(ctx, execution_date, symbol, &execution_stock)? .buy_rejection_reason(
ctx,
execution_date,
symbol,
&self.stock_state(ctx, execution_date, symbol)?,
)?
.is_some() .is_some()
{ {
continue; continue;
@@ -8130,7 +8164,7 @@ impl Strategy for PlatformExprStrategy {
let filled_qty = self.project_order_value( let filled_qty = self.project_order_value(
ctx, ctx,
&mut projected, &mut projected,
execution_date, projection_date,
symbol, symbol,
buy_cash, buy_cash,
&mut projected_execution_state, &mut projected_execution_state,
@@ -8164,7 +8198,7 @@ impl Strategy for PlatformExprStrategy {
let slot_buy_cash = self.remaining_buy_cash_per_slot( let slot_buy_cash = self.remaining_buy_cash_per_slot(
ctx, ctx,
&projected, &projected,
execution_date, projection_date,
target_budget, target_budget,
selection_limit, selection_limit,
&rebalance_working_symbols, &rebalance_working_symbols,
@@ -8180,9 +8214,14 @@ impl Strategy for PlatformExprStrategy {
if self.config.aiquant_transaction_cost && buy_cash < target_cash * 0.5 { if self.config.aiquant_transaction_cost && buy_cash < target_cash * 0.5 {
break; break;
} }
let execution_stock = self.stock_state(ctx, execution_date, symbol)?; if !defer_execution_risk
if self && self
.buy_rejection_reason(ctx, execution_date, symbol, &execution_stock)? .buy_rejection_reason(
ctx,
execution_date,
symbol,
&self.stock_state(ctx, execution_date, symbol)?,
)?
.is_some() .is_some()
{ {
continue; continue;
@@ -8194,7 +8233,7 @@ impl Strategy for PlatformExprStrategy {
let filled_qty = self.project_order_value( let filled_qty = self.project_order_value(
ctx, ctx,
&mut projected, &mut projected,
execution_date, projection_date,
symbol, symbol,
buy_cash, buy_cash,
&mut projected_execution_state, &mut projected_execution_state,
@@ -8216,7 +8255,7 @@ impl Strategy for PlatformExprStrategy {
if self.config.rotation_enabled && self.config.rebalance_schedule.is_none() { if self.config.rotation_enabled && self.config.rebalance_schedule.is_none() {
if self.config.calendar_rebalance_interval { if self.config.calendar_rebalance_interval {
if periodic_rebalance { if periodic_rebalance {
self.last_rebalance_date = Some(execution_date); self.last_rebalance_date = Some(signal_date);
} }
} else if periodic_rebalance { } else if periodic_rebalance {
self.rebalance_day_counter = 1; self.rebalance_day_counter = 1;
@@ -13945,7 +13984,7 @@ mod tests {
} }
#[test] #[test]
fn platform_aiquant_intraday_selection_filters_limits_on_execution_date() { fn platform_next_open_selection_ignores_execution_day_limit_state() {
let factor_date = d(2023, 11, 10); let factor_date = d(2023, 11, 10);
let decision_date = d(2023, 11, 13); let decision_date = d(2023, 11, 13);
let execution_date = d(2023, 11, 14); let execution_date = d(2023, 11, 14);
@@ -14186,7 +14225,7 @@ mod tests {
decision decision
.diagnostics .diagnostics
.iter() .iter()
.any(|item| item.contains("selection_market_date=2023-11-14")), .any(|item| item.contains("selection_market_date=2023-11-13")),
"{:?}", "{:?}",
decision.diagnostics decision.diagnostics
); );
@@ -14202,7 +14241,7 @@ mod tests {
decision decision
.diagnostics .diagnostics
.iter() .iter()
.any(|item| item == "selected_symbols=000002.SZ"), .any(|item| item == "selected_symbols=600462.SH"),
"{:?}", "{:?}",
decision.diagnostics decision.diagnostics
); );
@@ -14210,7 +14249,7 @@ mod tests {
decision decision
.diagnostics .diagnostics
.iter() .iter()
.any(|item| item == "600462.SH rejected by upper_limit"), .all(|item| item != "600462.SH rejected by upper_limit"),
"{:?}", "{:?}",
decision.diagnostics decision.diagnostics
); );
@@ -14218,9 +14257,9 @@ mod tests {
decision.order_intents.iter().any(|intent| match intent { decision.order_intents.iter().any(|intent| match intent {
OrderIntent::Shares { OrderIntent::Shares {
symbol, quantity, .. symbol, quantity, ..
} => symbol == fallback_symbol && *quantity > 0, } => symbol == limit_symbol && *quantity > 0,
OrderIntent::Value { symbol, value, .. } => { OrderIntent::Value { symbol, value, .. } => {
symbol == fallback_symbol && *value > 0.0 symbol == limit_symbol && *value > 0.0
} }
_ => false, _ => false,
}), }),