补充next open执行日风控测试
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user