修复分钟成交量限制撮合
This commit is contained in:
@@ -5589,7 +5589,7 @@ where
|
||||
if remaining_qty == 0 {
|
||||
break;
|
||||
}
|
||||
let available_qty = if quote_quantity_limited {
|
||||
let mut available_qty = if quote_quantity_limited {
|
||||
let top_level_liquidity = match side {
|
||||
OrderSide::Buy => quote.ask1_volume,
|
||||
OrderSide::Sell => quote.bid1_volume,
|
||||
@@ -5600,6 +5600,15 @@ where
|
||||
} else {
|
||||
remaining_qty
|
||||
};
|
||||
if self.volume_limit {
|
||||
let raw_limit = ((quote.volume_delta as f64) * self.volume_percent).floor() as u32;
|
||||
let volume_limited = if side == OrderSide::Sell && allow_odd_lot_sell {
|
||||
raw_limit
|
||||
} else {
|
||||
self.round_buy_quantity(raw_limit, minimum_order_quantity, order_step_size)
|
||||
};
|
||||
available_qty = available_qty.min(volume_limited);
|
||||
}
|
||||
if available_qty == 0 {
|
||||
continue;
|
||||
}
|
||||
@@ -6076,6 +6085,76 @@ mod tests {
|
||||
assert!(liquidity_limited.quote_quantity_limited(MatchingType::MinuteLast));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minute_last_volume_limit_caps_quote_volume_delta_without_liquidity_limit() {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
|
||||
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||
.with_matching_type(MatchingType::MinuteLast)
|
||||
.with_volume_limit(true)
|
||||
.with_volume_percent(0.25)
|
||||
.with_liquidity_limit(false);
|
||||
let mut quote = limit_test_quote(10.0, 9.99, 10.01);
|
||||
quote.timestamp = date.and_hms_opt(10, 15, 0).expect("valid timestamp");
|
||||
quote.volume_delta = 4_900;
|
||||
quote.ask1_volume = 0;
|
||||
quote.bid1_volume = 0;
|
||||
|
||||
let fill = broker
|
||||
.select_execution_fill(
|
||||
&limit_test_snapshot(),
|
||||
&[quote],
|
||||
OrderSide::Buy,
|
||||
MatchingType::MinuteLast,
|
||||
Some(date.and_hms_opt(10, 15, 0).expect("valid timestamp")),
|
||||
None,
|
||||
3_200,
|
||||
100,
|
||||
100,
|
||||
100,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.expect("minute last fill");
|
||||
|
||||
assert_eq!(fill.quantity, 1_200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minute_last_volume_limit_rejects_sub_lot_quote_volume() {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
|
||||
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||
.with_matching_type(MatchingType::MinuteLast)
|
||||
.with_volume_limit(true)
|
||||
.with_volume_percent(0.25)
|
||||
.with_liquidity_limit(false);
|
||||
let mut quote = limit_test_quote(10.0, 9.99, 10.01);
|
||||
quote.timestamp = date.and_hms_opt(10, 15, 0).expect("valid timestamp");
|
||||
quote.volume_delta = 100;
|
||||
quote.ask1_volume = 10_000;
|
||||
quote.bid1_volume = 10_000;
|
||||
|
||||
let fill = broker.select_execution_fill(
|
||||
&limit_test_snapshot(),
|
||||
&[quote],
|
||||
OrderSide::Buy,
|
||||
MatchingType::MinuteLast,
|
||||
Some(date.and_hms_opt(10, 15, 0).expect("valid timestamp")),
|
||||
None,
|
||||
3_200,
|
||||
100,
|
||||
100,
|
||||
100,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(fill.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn daily_matching_does_not_require_intraday_quote_quantity() {
|
||||
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||
|
||||
Reference in New Issue
Block a user