补充next-open执行日风控回归测试

This commit is contained in:
boris
2026-07-03 22:49:50 +08:00
parent 9fa588fef8
commit fea09ce93c
+131
View File
@@ -4077,6 +4077,63 @@ mod tests {
} }
} }
#[derive(Debug)]
struct ScheduledSameDayRebuyStrategy {
rule: ScheduleRule,
buy_decision_date: NaiveDate,
rebuy_decision_date: NaiveDate,
}
impl Strategy for ScheduledSameDayRebuyStrategy {
fn name(&self) -> &str {
"scheduled_same_day_rebuy"
}
fn schedule_rules(&self) -> Vec<ScheduleRule> {
vec![self.rule.clone()]
}
fn on_scheduled(
&mut self,
ctx: &StrategyContext<'_>,
rule: &ScheduleRule,
) -> Result<StrategyDecision, super::BacktestError> {
assert_eq!(rule.name, self.rule.name);
if ctx.decision_date == self.buy_decision_date
&& ctx.portfolio.position(SYMBOL).is_none()
{
return Ok(StrategyDecision {
order_intents: vec![OrderIntent::Shares {
symbol: SYMBOL.to_string(),
quantity: 100,
reason: "same_day_rebuy_setup_buy".to_string(),
}],
..StrategyDecision::default()
});
}
if ctx.decision_date == self.rebuy_decision_date {
if let Some(position) = ctx.portfolio.position(SYMBOL) {
return Ok(StrategyDecision {
order_intents: vec![
OrderIntent::Shares {
symbol: SYMBOL.to_string(),
quantity: -(position.quantity as i32),
reason: "same_day_rebuy_sell".to_string(),
},
OrderIntent::Shares {
symbol: SYMBOL.to_string(),
quantity: 100,
reason: "same_day_rebuy_buy".to_string(),
},
],
..StrategyDecision::default()
});
}
}
Ok(StrategyDecision::default())
}
}
fn d(year: i32, month: u32, day: u32) -> NaiveDate { fn d(year: i32, month: u32, day: u32) -> NaiveDate {
NaiveDate::from_ymd_opt(year, month, day).expect("valid date") NaiveDate::from_ymd_opt(year, month, day).expect("valid date")
} }
@@ -4406,6 +4463,35 @@ mod tests {
.expect("backtest run") .expect("backtest run")
} }
fn run_scheduled_same_day_rebuy_next_open_with_dataset_and_broker(
dataset: DataSet,
broker: BrokerSimulator<ChinaAShareCostModel, ChinaEquityRuleHooks>,
) -> super::BacktestResult {
let first = d(2025, 1, 2);
let third = d(2025, 1, 6);
let config = BacktestConfig {
initial_cash: 100_000.0,
benchmark_code: "000852.SH".to_string(),
start_date: Some(first),
end_date: Some(d(2025, 1, 7)),
decision_lag_trading_days: 1,
execution_price_field: PriceField::Open,
};
BacktestEngine::new(
dataset,
ScheduledSameDayRebuyStrategy {
rule: ScheduleRule::daily("daily_same_day_rebuy", ScheduleStage::OnDay),
buy_decision_date: first,
rebuy_decision_date: third,
},
broker,
config,
)
.run()
.expect("backtest run")
}
fn assert_next_open_canceled_with_reason(result: &super::BacktestResult, reason: &str) { fn assert_next_open_canceled_with_reason(result: &super::BacktestResult, reason: &str) {
let execution_date = d(2025, 1, 3); let execution_date = d(2025, 1, 3);
assert!(result.fills.is_empty()); assert!(result.fills.is_empty());
@@ -4704,4 +4790,49 @@ mod tests {
assert_round_trip_sell_canceled_with_reason(&result, "daily volume limit"); assert_round_trip_sell_canceled_with_reason(&result, "daily volume limit");
} }
#[test]
fn next_bar_open_same_day_rebuy_uses_actual_execution_date() {
let first = d(2025, 1, 2);
let second = d(2025, 1, 3);
let third = d(2025, 1, 6);
let fourth = d(2025, 1, 7);
let result = run_scheduled_same_day_rebuy_next_open_with_dataset_and_broker(
dataset_from_market_and_candidates(
vec![
market(first, 10.0, 10.5),
market(second, 11.0, 11.5),
market(third, 12.0, 12.5),
market(fourth, 13.0, 13.5),
],
vec![
candidate(first),
candidate(second),
candidate(third),
candidate(fourth),
],
),
scheduled_next_open_broker(FidcRiskControlConfig::default()),
);
assert!(result.fills.iter().any(|fill| {
fill.side == OrderSide::Buy
&& fill.date == second
&& fill.reason == "same_day_rebuy_setup_buy"
}));
assert!(result.fills.iter().any(|fill| {
fill.side == OrderSide::Sell
&& fill.date == fourth
&& fill.reason == "same_day_rebuy_sell"
}));
assert!(result.order_events.iter().any(|event| {
event.date == fourth
&& event.side == OrderSide::Buy
&& matches!(event.status, OrderStatus::Canceled | OrderStatus::Rejected)
&& event.reason.contains("same_day_rebuy_forbidden")
}));
assert!(!result.order_events.iter().any(|event| {
event.date == third && event.reason.contains("same_day_rebuy_forbidden")
}));
}
} }