test(engine): validate quote demand across different account capital

This commit is contained in:
boris
2026-09-13 11:21:09 +08:00
committed by boris
parent b1ca2dfada
commit effa0c6456
2 changed files with 23 additions and 21 deletions
+16 -17
View File
@@ -1,5 +1,4 @@
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
use chrono::{Datelike, Duration, NaiveDate, NaiveTime}; use chrono::{Datelike, Duration, NaiveDate, NaiveTime};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -2612,23 +2611,23 @@ where
if self.execution_quote_loader.is_some() && !decision_quote_times.is_empty() { if self.execution_quote_loader.is_some() && !decision_quote_times.is_empty() {
let decision_quote_symbols = let decision_quote_symbols =
self.strategy.decision_quote_symbols(&StrategyContext { self.strategy.decision_quote_symbols(&StrategyContext {
execution_date, execution_date,
decision_date,
decision_index,
data: &self.data,
portfolio: &portfolio,
futures_account: self.futures_account.as_ref(),
open_orders: &on_day_open_orders,
dynamic_universe: self.dynamic_universe.as_ref(),
subscriptions: &self.subscriptions,
process_events: &process_events,
active_process_event: None,
active_datetime: stage_datetime(
decision_date, decision_date,
decision_index, default_stage_time(ScheduleStage::OnDay),
data: &self.data, ),
portfolio: &portfolio, order_events: result.order_events.as_slice(),
futures_account: self.futures_account.as_ref(), fills: result.fills.as_slice(),
open_orders: &on_day_open_orders,
dynamic_universe: self.dynamic_universe.as_ref(),
subscriptions: &self.subscriptions,
process_events: &process_events,
active_process_event: None,
active_datetime: stage_datetime(
decision_date,
default_stage_time(ScheduleStage::OnDay),
),
order_events: result.order_events.as_slice(),
fills: result.fills.as_slice(),
})?; })?;
self.ensure_execution_quotes_for_symbols_at_times( self.ensure_execution_quotes_for_symbols_at_times(
execution_date, execution_date,
@@ -174,19 +174,21 @@ fn runtime_account_dependent_quote_scope_uses_the_actual_account() {
} else { BTreeSet::new() }) } else { BTreeSet::new() })
} }
fn on_day(&mut self, ctx: &StrategyContext<'_>) -> Result<StrategyDecision, fidc_core::BacktestError> { fn on_day(&mut self, ctx: &StrategyContext<'_>) -> Result<StrategyDecision, fidc_core::BacktestError> {
assert!(ctx.data.execution_quotes_on(ctx.execution_date, "000001.SZ").iter().any(|quote| let loaded = ctx.data.execution_quotes_on(ctx.execution_date, "000001.SZ").iter().any(|quote|
quote.timestamp.time()==t(10,17,59) && quote.last_price==10.0), quote.timestamp.time()==t(10,17,59) && quote.last_price==10.0);
"actual account-dependent quote scope must be loaded before decision"); assert_eq!(loaded, ctx.portfolio.cash() < 50_000.0,
"quote scope must match this account, not a fixed-capital planning account");
Ok(StrategyDecision::default()) Ok(StrategyDecision::default())
} }
} }
let date = d(2026, 1, 5); let date = d(2026, 1, 5);
for initial_cash in [10_000.0, 100_000.0] {
let broker = BrokerSimulator::new_with_execution_price( let broker = BrokerSimulator::new_with_execution_price(
ChinaAShareCostModel::default(), ChinaEquityRuleHooks, PriceField::Close, ChinaAShareCostModel::default(), ChinaEquityRuleHooks, PriceField::Close,
).with_volume_capacity_mode(fidc_core::execution_capacity::VolumeCapacityMode::SessionCapacityAudit) ).with_volume_capacity_mode(fidc_core::execution_capacity::VolumeCapacityMode::SessionCapacityAudit)
.with_matching_type(MatchingType::CurrentBarClose); .with_matching_type(MatchingType::CurrentBarClose);
let config = BacktestConfig { let config = BacktestConfig {
initial_cash:10_000.0, benchmark_code:"000852.SH".into(), initial_cash, benchmark_code:"000852.SH".into(),
start_date:Some(date), end_date:Some(date), decision_lag_trading_days:0, start_date:Some(date), end_date:Some(date), decision_lag_trading_days:0,
execution_price_field:PriceField::Close, execution_price_field:PriceField::Close,
}; };
@@ -198,6 +200,7 @@ fn runtime_account_dependent_quote_scope_uses_the_actual_account() {
trading_phase:Some("continuous".into()), trading_phase:Some("continuous".into()),
}).collect())); }).collect()));
engine.run().expect("account-dependent quote planning"); engine.run().expect("account-dependent quote planning");
}
} }
#[test] #[test]