调整回测撮合为分钟线执行价语义

This commit is contained in:
boris
2026-06-26 17:03:48 +08:00
parent 380c34aa66
commit a131c761e5
8 changed files with 59 additions and 66 deletions
+25 -28
View File
@@ -73,10 +73,9 @@ pub enum MatchingType {
OpenAuction,
CurrentBarClose,
NextBarOpen,
NextMinuteLast,
NextMinuteBestOwn,
NextMinuteBestCounterparty,
CounterpartyOffer,
MinuteLast,
MinuteBestOwn,
MinuteBestCounterparty,
Vwap,
Twap,
}
@@ -563,7 +562,7 @@ where
matching_type: MatchingType,
) -> Option<f64> {
let raw_price = match matching_type {
MatchingType::NextMinuteBestOwn => match side {
MatchingType::MinuteBestOwn => match side {
OrderSide::Buy => {
if quote.bid1.is_finite() && quote.bid1 > 0.0 {
Some(quote.bid1)
@@ -587,13 +586,11 @@ where
}
}
},
MatchingType::NextMinuteBestCounterparty | MatchingType::CounterpartyOffer => {
match side {
OrderSide::Buy => quote.buy_price(),
OrderSide::Sell => quote.sell_price(),
}
}
MatchingType::NextMinuteLast | MatchingType::Vwap | MatchingType::Twap => {
MatchingType::MinuteBestCounterparty => match side {
OrderSide::Buy => quote.buy_price(),
OrderSide::Sell => quote.sell_price(),
},
MatchingType::MinuteLast | MatchingType::Vwap | MatchingType::Twap => {
if quote.last_price.is_finite() && quote.last_price > 0.0 {
Some(quote.last_price)
} else {
@@ -4998,7 +4995,7 @@ where
quotes,
start_cursor,
end_cursor,
matching_type == MatchingType::NextMinuteLast && start_cursor.is_some(),
matching_type == MatchingType::MinuteLast && start_cursor.is_some(),
)),
});
}
@@ -5056,12 +5053,12 @@ where
let quote_quantity_limited =
self.quote_quantity_limited_for_window(matching_type, start_cursor, end_cursor);
let lot = round_lot.max(1);
let exact_time_order_quote = matching_type != MatchingType::NextMinuteLast
let exact_time_order_quote = matching_type != MatchingType::MinuteLast
&& start_cursor.is_some()
&& end_cursor.is_some()
&& start_cursor == end_cursor;
let use_decision_time_quote = start_cursor.is_some()
&& (matching_type == MatchingType::NextMinuteLast || exact_time_order_quote);
&& (matching_type == MatchingType::MinuteLast || exact_time_order_quote);
let eligible_quotes: Vec<&IntradayExecutionQuote> = if use_decision_time_quote {
self.latest_known_quote_at_or_before(
quotes,
@@ -5290,7 +5287,7 @@ where
}
fn quote_quantity_limited(&self, matching_type: MatchingType) -> bool {
self.volume_limit || self.liquidity_limit || matching_type != MatchingType::NextMinuteLast
self.volume_limit || self.liquidity_limit || matching_type != MatchingType::MinuteLast
}
fn quote_quantity_limited_for_window(
@@ -5321,7 +5318,7 @@ fn matching_type_from_price_field(field: PriceField) -> MatchingType {
PriceField::DayOpen => MatchingType::OpenAuction,
PriceField::Open => MatchingType::NextBarOpen,
PriceField::Close => MatchingType::CurrentBarClose,
PriceField::Last => MatchingType::NextMinuteLast,
PriceField::Last => MatchingType::MinuteLast,
}
}
@@ -5487,17 +5484,17 @@ mod tests {
}
#[test]
fn next_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)
.with_volume_limit(false)
.with_liquidity_limit(false);
assert!(!broker.quote_quantity_limited(MatchingType::NextMinuteLast));
assert!(broker.quote_quantity_limited(MatchingType::CounterpartyOffer));
assert!(!broker.quote_quantity_limited(MatchingType::MinuteLast));
assert!(broker.quote_quantity_limited(MatchingType::MinuteBestCounterparty));
}
#[test]
fn next_minute_last_keeps_quote_quantity_cap_when_limits_enabled() {
fn minute_last_keeps_quote_quantity_cap_when_limits_enabled() {
let volume_limited =
BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
.with_volume_limit(true)
@@ -5507,8 +5504,8 @@ mod tests {
.with_volume_limit(false)
.with_liquidity_limit(true);
assert!(volume_limited.quote_quantity_limited(MatchingType::NextMinuteLast));
assert!(liquidity_limited.quote_quantity_limited(MatchingType::NextMinuteLast));
assert!(volume_limited.quote_quantity_limited(MatchingType::MinuteLast));
assert!(liquidity_limited.quote_quantity_limited(MatchingType::MinuteLast));
}
#[test]
@@ -5558,7 +5555,7 @@ mod tests {
PriceField::Last,
)
.with_intraday_execution_start_time(date.and_hms_opt(10, 18, 0).unwrap().time())
.with_matching_type(MatchingType::NextMinuteLast)
.with_matching_type(MatchingType::MinuteLast)
.with_slippage_model(SlippageModel::PriceRatio(0.001))
.with_volume_limit(false)
.with_liquidity_limit(false);
@@ -5751,7 +5748,7 @@ mod tests {
}
#[test]
fn next_minute_last_execution_uses_latest_quote_before_decision_time() {
fn minute_last_execution_uses_latest_quote_before_decision_time() {
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
let broker = BrokerSimulator::new_with_execution_price(
ChinaAShareCostModel::default(),
@@ -5772,7 +5769,7 @@ mod tests {
&snapshot,
&[quote],
OrderSide::Sell,
MatchingType::NextMinuteLast,
MatchingType::MinuteLast,
Some(decision_time),
Some(decision_time),
200,
@@ -6026,7 +6023,7 @@ mod tests {
&snapshot,
&[quote],
OrderSide::Buy,
MatchingType::NextMinuteLast,
MatchingType::MinuteLast,
Some(start),
None,
100,
@@ -6103,7 +6100,7 @@ mod tests {
&snapshot,
&[quote],
OrderSide::Sell,
MatchingType::NextMinuteLast,
MatchingType::MinuteLast,
Some(start),
None,
100,