补充next open执行日风控测试

This commit is contained in:
boris
2026-07-04 20:04:30 +08:00
parent 652531ac63
commit e8ecc037c9
+140 -1
View File
@@ -5689,7 +5689,7 @@ mod tests {
use crate::instrument::Instrument; use crate::instrument::Instrument;
use crate::portfolio::PortfolioState; use crate::portfolio::PortfolioState;
use crate::rules::ChinaEquityRuleHooks; use crate::rules::ChinaEquityRuleHooks;
use crate::strategy::AlgoOrderStyle; use crate::strategy::{AlgoOrderStyle, OrderIntent, StrategyDecision};
fn limit_test_snapshot() -> DailyMarketSnapshot { fn limit_test_snapshot() -> DailyMarketSnapshot {
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date"); let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
@@ -5776,6 +5776,44 @@ mod tests {
} }
} }
fn dated_limit_test_snapshot(date: chrono::NaiveDate) -> DailyMarketSnapshot {
let mut snapshot = limit_test_snapshot();
snapshot.date = date;
snapshot.timestamp = Some(format!("{date} 09:31:00"));
snapshot
}
fn dated_limit_test_candidate(
date: chrono::NaiveDate,
is_st: bool,
is_paused: bool,
allow_buy: bool,
allow_sell: bool,
) -> CandidateEligibility {
let mut candidate = limit_test_candidate(allow_buy, allow_sell);
candidate.date = date;
candidate.is_st = is_st;
candidate.is_paused = is_paused;
candidate
}
fn dated_limit_test_benchmark(date: chrono::NaiveDate) -> BenchmarkSnapshot {
let mut benchmark = limit_test_benchmark();
benchmark.date = date;
benchmark
}
fn next_open_buy_decision() -> StrategyDecision {
StrategyDecision {
order_intents: vec![OrderIntent::Shares {
symbol: "000001.SZ".to_string(),
quantity: 100,
reason: "signal_day_buy".to_string(),
}],
..StrategyDecision::default()
}
}
#[test] #[test]
fn minute_last_without_volume_or_liquidity_limit_does_not_cap_quote_quantity() { fn minute_last_without_volume_or_liquidity_limit_does_not_cap_quote_quantity() {
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks) let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
@@ -5814,6 +5852,107 @@ mod tests {
assert!(broker.quote_quantity_limited(MatchingType::MinuteBestCounterparty)); assert!(broker.quote_quantity_limited(MatchingType::MinuteBestCounterparty));
} }
#[test]
fn next_open_buy_risk_uses_execution_date_not_signal_date() {
let signal_date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
let execution_date = chrono::NaiveDate::from_ymd_opt(2025, 1, 3).expect("valid date");
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
.with_volume_limit(false)
.with_liquidity_limit(false);
let data = DataSet::from_components_with_actions_and_quotes(
vec![limit_test_instrument()],
vec![
dated_limit_test_snapshot(signal_date),
dated_limit_test_snapshot(execution_date),
],
Vec::new(),
vec![
dated_limit_test_candidate(signal_date, true, true, false, false),
dated_limit_test_candidate(execution_date, false, false, true, true),
],
vec![
dated_limit_test_benchmark(signal_date),
dated_limit_test_benchmark(execution_date),
],
Vec::new(),
Vec::new(),
)
.expect("valid dataset");
let mut portfolio = PortfolioState::new(20_000.0);
let report = broker
.execute(
execution_date,
&mut portfolio,
&data,
&next_open_buy_decision(),
)
.expect("execute next open buy");
assert_eq!(report.fill_events.len(), 1);
assert_eq!(report.fill_events[0].date, execution_date);
assert_eq!(report.fill_events[0].quantity, 100);
assert_eq!(
portfolio
.position("000001.SZ")
.map(|position| position.quantity),
Some(100)
);
}
#[test]
fn next_open_buy_risk_rejects_execution_date_st_even_if_signal_date_was_clean() {
let signal_date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
let execution_date = chrono::NaiveDate::from_ymd_opt(2025, 1, 3).expect("valid date");
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
.with_volume_limit(false)
.with_liquidity_limit(false);
let data = DataSet::from_components_with_actions_and_quotes(
vec![limit_test_instrument()],
vec![
dated_limit_test_snapshot(signal_date),
dated_limit_test_snapshot(execution_date),
],
Vec::new(),
vec![
dated_limit_test_candidate(signal_date, false, false, true, true),
dated_limit_test_candidate(execution_date, true, false, true, true),
],
vec![
dated_limit_test_benchmark(signal_date),
dated_limit_test_benchmark(execution_date),
],
Vec::new(),
Vec::new(),
)
.expect("valid dataset");
let mut portfolio = PortfolioState::new(20_000.0);
let report = broker
.execute(
execution_date,
&mut portfolio,
&data,
&next_open_buy_decision(),
)
.expect("execute next open buy");
assert!(report.fill_events.is_empty());
assert!(portfolio.position("000001.SZ").is_none());
let rejected_order = report
.order_events
.iter()
.find(|event| event.side == OrderSide::Buy)
.expect("buy order event");
assert_eq!(rejected_order.date, execution_date);
assert_eq!(rejected_order.status, OrderStatus::Rejected);
assert!(
rejected_order.reason.contains("st"),
"{}",
rejected_order.reason
);
}
#[test] #[test]
fn current_bar_close_volume_limit_uses_daily_volume_when_minute_volume_missing() { fn current_bar_close_volume_limit_uses_daily_volume_when_minute_volume_missing() {
let mut snapshot = limit_test_snapshot(); let mut snapshot = limit_test_snapshot();