fix(stock-pool): execute verified ETF daily fallbacks with frozen next-open targets
This commit is contained in:
@@ -169,6 +169,8 @@ pub enum BacktestTerminalAssetClass {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BacktestTerminalAudit {
|
||||
#[serde(default, skip_serializing_if = "is_zero_count")]
|
||||
pub deferred_etf_target_count: usize,
|
||||
pub status: BacktestTerminalStatus,
|
||||
pub last_execution_date: Option<NaiveDate>,
|
||||
pub stock_open_order_count: usize,
|
||||
@@ -185,6 +187,7 @@ pub struct BacktestTerminalAudit {
|
||||
impl Default for BacktestTerminalAudit {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
deferred_etf_target_count: 0,
|
||||
status: BacktestTerminalStatus::Clean,
|
||||
last_execution_date: None,
|
||||
stock_open_order_count: 0,
|
||||
@@ -200,6 +203,8 @@ impl Default for BacktestTerminalAudit {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_zero_count(value: &usize) -> bool { *value == 0 }
|
||||
|
||||
impl BacktestTerminalAudit {
|
||||
pub fn is_clean(&self) -> bool {
|
||||
self.status == BacktestTerminalStatus::Clean
|
||||
@@ -787,6 +792,17 @@ where
|
||||
end_time: Option<NaiveTime>,
|
||||
symbols: &mut BTreeSet<String>,
|
||||
) -> Result<(), BacktestError> {
|
||||
// A missing point is not proof of an absent ETF minute dataset. Query
|
||||
// its complete formal session first; loader/contract failures propagate.
|
||||
if start_time.is_some() || end_time.is_some() {
|
||||
let mut etfs = symbols.iter().filter(|symbol| {
|
||||
self.broker.requires_etf_absence_check(&self.data, symbol)
|
||||
&& !self.execution_quote_request_cache.contains(&(execution_date, (*symbol).clone(), None, None))
|
||||
}).cloned().collect::<BTreeSet<_>>();
|
||||
if !etfs.is_empty() {
|
||||
self.load_missing_execution_quotes(execution_date, None, None, &mut etfs)?;
|
||||
}
|
||||
}
|
||||
let mut available = BTreeSet::new();
|
||||
for symbol in symbols.iter() {
|
||||
let instrument = self.data.instrument(symbol).ok_or_else(|| BacktestError::Execution(format!(
|
||||
@@ -813,6 +829,9 @@ where
|
||||
}
|
||||
*symbols = available;
|
||||
symbols.retain(|symbol| {
|
||||
if (start_time.is_some() || end_time.is_some()) && self.broker.has_verified_etf_minute_absence(execution_date, symbol) {
|
||||
return false;
|
||||
}
|
||||
let request_key = (execution_date, symbol.clone(), start_time, end_time);
|
||||
if self.execution_quote_request_cache.contains(&request_key) {
|
||||
return false;
|
||||
@@ -857,6 +876,7 @@ where
|
||||
}
|
||||
self.data.add_execution_quotes(quotes);
|
||||
if start_time.is_none() && end_time.is_none() {
|
||||
self.broker.record_complete_etf_minute_query(execution_date, &self.data, &requested_symbols);
|
||||
self.validate_full_day_execution_quote_coverage(execution_date, &requested_symbols)?;
|
||||
}
|
||||
for symbol in requested_symbols {
|
||||
@@ -893,7 +913,7 @@ where
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if market.volume > 0 && !has_quotes {
|
||||
if market.volume > 0 && !has_quotes && !self.broker.has_verified_etf_minute_absence(execution_date, symbol) {
|
||||
missing_active.push(symbol.clone());
|
||||
}
|
||||
}
|
||||
@@ -1395,6 +1415,7 @@ where
|
||||
let status = if open_order_count == 0
|
||||
&& pending_cash_flow_count == 0
|
||||
&& cash_receivable_count == 0
|
||||
&& self.broker.pending_etf_target_count() == 0
|
||||
{
|
||||
BacktestTerminalStatus::Clean
|
||||
} else {
|
||||
@@ -1402,6 +1423,7 @@ where
|
||||
};
|
||||
|
||||
BacktestTerminalAudit {
|
||||
deferred_etf_target_count: self.broker.pending_etf_target_count(),
|
||||
status,
|
||||
last_execution_date,
|
||||
stock_open_order_count,
|
||||
@@ -2204,7 +2226,7 @@ where
|
||||
.and_then(|(_, decision_slot)| *decision_slot);
|
||||
let Some((decision_index, decision_date)) = decision_slot else {
|
||||
let mut process_events = Vec::new();
|
||||
let mut report = BrokerExecutionReport::default();
|
||||
let mut report = self.broker.execute_deferred_etf_targets(execution_date, &mut portfolio, &self.data)?;
|
||||
portfolio.update_prices_with_options(
|
||||
execution_date,
|
||||
&self.data,
|
||||
@@ -2566,6 +2588,9 @@ where
|
||||
"open_auction:post",
|
||||
)?;
|
||||
|
||||
let deferred_etfs = self.broker.execute_deferred_etf_targets(execution_date, &mut portfolio, &self.data)?;
|
||||
merge_broker_report(&mut report, deferred_etfs);
|
||||
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&mut self.process_event_bus,
|
||||
|
||||
Reference in New Issue
Block a user