修复分钟执行价缺失档位量成交
This commit is contained in:
@@ -5596,7 +5596,8 @@ where
|
|||||||
if remaining_qty == 0 {
|
if remaining_qty == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let mut available_qty = if quote_quantity_limited {
|
let missing_level1_depth = Self::quote_lacks_level1_depth(quote);
|
||||||
|
let mut available_qty = if quote_quantity_limited && !missing_level1_depth {
|
||||||
let top_level_liquidity = match side {
|
let top_level_liquidity = match side {
|
||||||
OrderSide::Buy => quote.ask1_volume,
|
OrderSide::Buy => quote.ask1_volume,
|
||||||
OrderSide::Sell => quote.bid1_volume,
|
OrderSide::Sell => quote.bid1_volume,
|
||||||
@@ -5785,6 +5786,10 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn quote_lacks_level1_depth(quote: &IntradayExecutionQuote) -> bool {
|
||||||
|
quote.volume_delta > 0 && quote.bid1_volume == 0 && quote.ask1_volume == 0
|
||||||
|
}
|
||||||
|
|
||||||
fn matching_type_uses_intraday_quotes(&self) -> bool {
|
fn matching_type_uses_intraday_quotes(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self.matching_type,
|
self.matching_type,
|
||||||
@@ -6128,6 +6133,44 @@ mod tests {
|
|||||||
assert_eq!(fill.quantity, 1_200);
|
assert_eq!(fill.quantity, 1_200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn minute_last_uses_volume_delta_when_level1_depth_missing() {
|
||||||
|
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(true);
|
||||||
|
let mut quote = limit_test_quote(10.0, 0.0, 0.0);
|
||||||
|
quote.timestamp = date.and_hms_opt(10, 15, 0).expect("valid timestamp");
|
||||||
|
quote.volume_delta = 600;
|
||||||
|
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,
|
||||||
|
400,
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("bar-derived minute quote fill");
|
||||||
|
|
||||||
|
assert_eq!(fill.quantity, 100);
|
||||||
|
assert_eq!(fill.legs.len(), 1);
|
||||||
|
assert_eq!(fill.legs[0].price, 10.0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn minute_last_volume_limit_rejects_sub_lot_quote_volume() {
|
fn minute_last_volume_limit_rejects_sub_lot_quote_volume() {
|
||||||
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");
|
||||||
|
|||||||
@@ -1583,10 +1583,11 @@ impl PlatformExprStrategy {
|
|||||||
let constraints = self.config.risk_config.trading_constraints;
|
let constraints = self.config.risk_config.trading_constraints;
|
||||||
let mut max_fill = requested_qty;
|
let mut max_fill = requested_qty;
|
||||||
|
|
||||||
|
let missing_level1_depth = quote.is_some_and(Self::quote_lacks_level1_depth);
|
||||||
let enforce_top_level_liquidity = quote.is_some()
|
let enforce_top_level_liquidity = quote.is_some()
|
||||||
&& (self.config.quote_quantity_limit || constraints.liquidity_limit_enabled)
|
&& (self.config.quote_quantity_limit || constraints.liquidity_limit_enabled)
|
||||||
|| quote.is_none() && constraints.liquidity_limit_enabled;
|
|| quote.is_none() && constraints.liquidity_limit_enabled;
|
||||||
if enforce_top_level_liquidity {
|
if enforce_top_level_liquidity && !missing_level1_depth {
|
||||||
let top_level_liquidity = match (quote, side) {
|
let top_level_liquidity = match (quote, side) {
|
||||||
(Some(quote), OrderSide::Buy) => quote
|
(Some(quote), OrderSide::Buy) => quote
|
||||||
.ask1_volume
|
.ask1_volume
|
||||||
@@ -1654,6 +1655,10 @@ impl PlatformExprStrategy {
|
|||||||
Some(max_fill)
|
Some(max_fill)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn quote_lacks_level1_depth(quote: &crate::data::IntradayExecutionQuote) -> bool {
|
||||||
|
quote.volume_delta > 0 && quote.bid1_volume == 0 && quote.ask1_volume == 0
|
||||||
|
}
|
||||||
|
|
||||||
fn projected_select_execution_fill(
|
fn projected_select_execution_fill(
|
||||||
&self,
|
&self,
|
||||||
ctx: &StrategyContext<'_>,
|
ctx: &StrategyContext<'_>,
|
||||||
@@ -14086,7 +14091,7 @@ mod tests {
|
|||||||
fn platform_aiquant_weak_market_emits_daily_position_target_adjustment() {
|
fn platform_aiquant_weak_market_emits_daily_position_target_adjustment() {
|
||||||
let date = d(2023, 5, 5);
|
let date = d(2023, 5, 5);
|
||||||
let symbol = "300621.SZ";
|
let symbol = "300621.SZ";
|
||||||
let data = DataSet::from_components(
|
let data = DataSet::from_components_with_actions_and_quotes(
|
||||||
vec![Instrument {
|
vec![Instrument {
|
||||||
symbol: symbol.to_string(),
|
symbol: symbol.to_string(),
|
||||||
name: symbol.to_string(),
|
name: symbol.to_string(),
|
||||||
@@ -14150,6 +14155,20 @@ mod tests {
|
|||||||
prev_close: 999.0,
|
prev_close: 999.0,
|
||||||
volume: 1_000_000,
|
volume: 1_000_000,
|
||||||
}],
|
}],
|
||||||
|
Vec::new(),
|
||||||
|
vec![IntradayExecutionQuote {
|
||||||
|
date,
|
||||||
|
symbol: symbol.to_string(),
|
||||||
|
timestamp: date.and_hms_opt(10, 18, 0).expect("timestamp"),
|
||||||
|
last_price: 10.8,
|
||||||
|
bid1: 0.0,
|
||||||
|
ask1: 0.0,
|
||||||
|
bid1_volume: 0,
|
||||||
|
ask1_volume: 0,
|
||||||
|
volume_delta: 600,
|
||||||
|
amount_delta: 6_480.0,
|
||||||
|
trading_phase: Some("continuous".to_string()),
|
||||||
|
}],
|
||||||
)
|
)
|
||||||
.expect("dataset");
|
.expect("dataset");
|
||||||
let mut portfolio = PortfolioState::new(1_000_000.0);
|
let mut portfolio = PortfolioState::new(1_000_000.0);
|
||||||
@@ -14181,6 +14200,9 @@ mod tests {
|
|||||||
cfg.stock_filter_expr = "false".to_string();
|
cfg.stock_filter_expr = "false".to_string();
|
||||||
cfg.stop_loss_expr.clear();
|
cfg.stop_loss_expr.clear();
|
||||||
cfg.take_profit_expr.clear();
|
cfg.take_profit_expr.clear();
|
||||||
|
cfg.risk_config.trading_constraints.volume_limit_enabled = true;
|
||||||
|
cfg.risk_config.trading_constraints.liquidity_limit_enabled = true;
|
||||||
|
cfg.risk_config.trading_constraints.volume_percent = 0.25;
|
||||||
let mut strategy = PlatformExprStrategy::new(cfg);
|
let mut strategy = PlatformExprStrategy::new(cfg);
|
||||||
|
|
||||||
let decision = strategy.on_day(&ctx).expect("platform decision");
|
let decision = strategy.on_day(&ctx).expect("platform decision");
|
||||||
@@ -14193,7 +14215,7 @@ mod tests {
|
|||||||
reason
|
reason
|
||||||
} if intent_symbol == symbol
|
} if intent_symbol == symbol
|
||||||
&& reason == "daily_position_target_adjust"
|
&& reason == "daily_position_target_adjust"
|
||||||
&& *quantity > 0
|
&& *quantity == 100
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user