补齐next-open卖出执行日风控测试
This commit is contained in:
@@ -5814,6 +5814,17 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn next_open_sell_decision() -> StrategyDecision {
|
||||
StrategyDecision {
|
||||
order_intents: vec![OrderIntent::Shares {
|
||||
symbol: "000001.SZ".to_string(),
|
||||
quantity: -100,
|
||||
reason: "signal_day_sell".to_string(),
|
||||
}],
|
||||
..StrategyDecision::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minute_last_without_volume_or_liquidity_limit_does_not_cap_quote_quantity() {
|
||||
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||
@@ -5953,6 +5964,120 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_open_sell_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 mut signal_snapshot = dated_limit_test_snapshot(signal_date);
|
||||
signal_snapshot.open = signal_snapshot.lower_limit;
|
||||
signal_snapshot.day_open = signal_snapshot.lower_limit;
|
||||
signal_snapshot.last_price = signal_snapshot.lower_limit;
|
||||
signal_snapshot.close = signal_snapshot.lower_limit;
|
||||
signal_snapshot.paused = true;
|
||||
let data = DataSet::from_components_with_actions_and_quotes(
|
||||
vec![limit_test_instrument()],
|
||||
vec![signal_snapshot, dated_limit_test_snapshot(execution_date)],
|
||||
Vec::new(),
|
||||
vec![
|
||||
dated_limit_test_candidate(signal_date, false, 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);
|
||||
portfolio
|
||||
.position_mut("000001.SZ")
|
||||
.buy(signal_date, 100, 10.0);
|
||||
|
||||
let report = broker
|
||||
.execute(
|
||||
execution_date,
|
||||
&mut portfolio,
|
||||
&data,
|
||||
&next_open_sell_decision(),
|
||||
)
|
||||
.expect("execute next open sell");
|
||||
|
||||
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!(portfolio.position("000001.SZ").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_open_sell_risk_rejects_execution_date_lower_limit_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 mut execution_snapshot = dated_limit_test_snapshot(execution_date);
|
||||
execution_snapshot.open = execution_snapshot.lower_limit;
|
||||
execution_snapshot.day_open = execution_snapshot.lower_limit;
|
||||
execution_snapshot.last_price = execution_snapshot.lower_limit;
|
||||
execution_snapshot.close = execution_snapshot.lower_limit;
|
||||
let data = DataSet::from_components_with_actions_and_quotes(
|
||||
vec![limit_test_instrument()],
|
||||
vec![dated_limit_test_snapshot(signal_date), execution_snapshot],
|
||||
Vec::new(),
|
||||
vec![
|
||||
dated_limit_test_candidate(signal_date, false, false, true, true),
|
||||
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);
|
||||
portfolio
|
||||
.position_mut("000001.SZ")
|
||||
.buy(signal_date, 100, 10.0);
|
||||
|
||||
let report = broker
|
||||
.execute(
|
||||
execution_date,
|
||||
&mut portfolio,
|
||||
&data,
|
||||
&next_open_sell_decision(),
|
||||
)
|
||||
.expect("execute next open sell");
|
||||
|
||||
assert!(report.fill_events.is_empty());
|
||||
assert_eq!(
|
||||
portfolio
|
||||
.position("000001.SZ")
|
||||
.map(|position| position.quantity),
|
||||
Some(100)
|
||||
);
|
||||
let rejected_order = report
|
||||
.order_events
|
||||
.iter()
|
||||
.find(|event| event.side == OrderSide::Sell)
|
||||
.expect("sell order event");
|
||||
assert_eq!(rejected_order.date, execution_date);
|
||||
assert_eq!(rejected_order.status, OrderStatus::Canceled);
|
||||
assert!(
|
||||
rejected_order
|
||||
.reason
|
||||
.contains("open at or below lower limit"),
|
||||
"{}",
|
||||
rejected_order.reason
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_bar_close_volume_limit_uses_daily_volume_when_minute_volume_missing() {
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
|
||||
Reference in New Issue
Block a user