删除隐藏兼容模式并统一撮合风控
This commit is contained in:
+144
-189
@@ -185,7 +185,6 @@ pub struct BrokerSimulator<C, R> {
|
||||
strict_value_budget: bool,
|
||||
rebalance_cash_mode: RebalanceCashMode,
|
||||
sell_then_buy_delay_slippage_rate: f64,
|
||||
aiquant_execution_rules: bool,
|
||||
same_day_buy_close_mark_at_fill: bool,
|
||||
risk_config: FidcRiskControlConfig,
|
||||
same_day_sold_symbols: RefCell<BTreeMap<NaiveDate, BTreeSet<String>>>,
|
||||
@@ -216,7 +215,6 @@ impl<C, R> BrokerSimulator<C, R> {
|
||||
strict_value_budget: true,
|
||||
rebalance_cash_mode: RebalanceCashMode::default(),
|
||||
sell_then_buy_delay_slippage_rate: 0.0,
|
||||
aiquant_execution_rules: false,
|
||||
same_day_buy_close_mark_at_fill: false,
|
||||
risk_config: FidcRiskControlConfig::default(),
|
||||
same_day_sold_symbols: RefCell::new(BTreeMap::new()),
|
||||
@@ -251,7 +249,6 @@ impl<C, R> BrokerSimulator<C, R> {
|
||||
strict_value_budget: true,
|
||||
rebalance_cash_mode: RebalanceCashMode::default(),
|
||||
sell_then_buy_delay_slippage_rate: 0.0,
|
||||
aiquant_execution_rules: false,
|
||||
same_day_buy_close_mark_at_fill: false,
|
||||
risk_config: FidcRiskControlConfig::default(),
|
||||
same_day_sold_symbols: RefCell::new(BTreeMap::new()),
|
||||
@@ -305,11 +302,6 @@ impl<C, R> BrokerSimulator<C, R> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_aiquant_execution_rules(mut self, enabled: bool) -> Self {
|
||||
self.aiquant_execution_rules = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_same_day_buy_close_mark_at_fill(mut self, enabled: bool) -> Self {
|
||||
self.same_day_buy_close_mark_at_fill = enabled;
|
||||
self
|
||||
@@ -444,7 +436,7 @@ where
|
||||
return execution_price;
|
||||
}
|
||||
}
|
||||
if self.aiquant_execution_rules && self.execution_price_field == PriceField::Last {
|
||||
if self.execution_price_field == PriceField::Last {
|
||||
let start_cursor = self
|
||||
.runtime_intraday_start_time
|
||||
.get()
|
||||
@@ -1075,6 +1067,8 @@ where
|
||||
|
||||
if current_qty > target_qty {
|
||||
let requested_qty = current_qty - target_qty;
|
||||
let fill_start = report.fill_events.len();
|
||||
let order_start = report.order_events.len();
|
||||
self.process_sell(
|
||||
date,
|
||||
portfolio,
|
||||
@@ -1093,6 +1087,23 @@ where
|
||||
None,
|
||||
&mut report,
|
||||
)?;
|
||||
let filled_quantity = report.fill_events[fill_start..]
|
||||
.iter()
|
||||
.filter(|fill| fill.symbol == symbol && fill.side == OrderSide::Sell)
|
||||
.map(|fill| fill.quantity)
|
||||
.sum::<u32>();
|
||||
if filled_quantity < requested_qty && report.diagnostics.len() < 32 {
|
||||
let denial_reason = report.order_events[order_start..]
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|event| event.symbol == symbol && event.side == OrderSide::Sell)
|
||||
.map(|event| event.reason.as_str())
|
||||
.unwrap_or("sell_not_fully_filled");
|
||||
report.diagnostics.push(format!(
|
||||
"rebalance_target_denied symbol={} side=sell requested={} filled={} reason={}",
|
||||
symbol, requested_qty, filled_quantity, denial_reason
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1104,6 +1115,15 @@ where
|
||||
.unwrap_or(0);
|
||||
if target_qty > current_qty {
|
||||
let requested_qty = target_qty - current_qty;
|
||||
if !self.can_afford_minimum_buy(date, portfolio, data, &symbol) {
|
||||
if report.diagnostics.len() < 32 {
|
||||
report.diagnostics.push(format!(
|
||||
"rebalance_buy_reduced symbol={} provisional={} final={} current={} reason=actual_cash_after_sells",
|
||||
symbol, target_qty, current_qty, current_qty
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
self.process_buy(
|
||||
date,
|
||||
portfolio,
|
||||
@@ -2732,6 +2752,25 @@ where
|
||||
&mut local_report,
|
||||
)?;
|
||||
}
|
||||
let filled_quantity = local_report
|
||||
.fill_events
|
||||
.iter()
|
||||
.filter(|fill| fill.symbol == *symbol && fill.side == OrderSide::Sell)
|
||||
.map(|fill| fill.quantity)
|
||||
.sum::<u32>();
|
||||
if filled_quantity < sell_qty && local_report.diagnostics.len() < 32 {
|
||||
let denial_reason = local_report
|
||||
.order_events
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|event| event.symbol == *symbol && event.side == OrderSide::Sell)
|
||||
.map(|event| event.reason.as_str())
|
||||
.unwrap_or("sell_not_fully_filled");
|
||||
local_report.diagnostics.push(format!(
|
||||
"rebalance_target_denied symbol={} side=sell requested={} filled={} reason={}",
|
||||
symbol, sell_qty, filled_quantity, denial_reason
|
||||
));
|
||||
}
|
||||
Self::extend_report(report, local_report);
|
||||
}
|
||||
|
||||
@@ -2745,6 +2784,15 @@ where
|
||||
continue;
|
||||
}
|
||||
let buy_qty = target_qty - current_qty;
|
||||
if !self.can_afford_minimum_buy(date, portfolio, data, symbol) {
|
||||
if report.diagnostics.len() < 32 {
|
||||
report.diagnostics.push(format!(
|
||||
"rebalance_buy_reduced symbol={} provisional={} final={} current={} reason=actual_cash_after_sells",
|
||||
symbol, target_qty, current_qty, current_qty
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let mut local_report = BrokerExecutionReport::default();
|
||||
if let Some(limit_price) =
|
||||
self.required_custom_order_price(date, symbol, limit_prices)?
|
||||
@@ -2886,7 +2934,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn aiquant_limit_check_price(
|
||||
fn execution_limit_check_price(
|
||||
&self,
|
||||
snapshot: &crate::data::DailyMarketSnapshot,
|
||||
side: OrderSide,
|
||||
@@ -2898,7 +2946,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn aiquant_order_limit_check_price(
|
||||
fn execution_order_limit_check_price(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
data: &DataSet,
|
||||
@@ -2922,7 +2970,7 @@ where
|
||||
false,
|
||||
)
|
||||
.and_then(|quote| self.select_quote_reference_price(snapshot, quote, side, matching_type))
|
||||
.unwrap_or_else(|| self.aiquant_limit_check_price(snapshot, side))
|
||||
.unwrap_or_else(|| self.execution_limit_check_price(snapshot, side))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -2933,11 +2981,7 @@ where
|
||||
candidate: &crate::data::CandidateEligibility,
|
||||
instrument: Option<&Instrument>,
|
||||
) -> RuleCheck {
|
||||
let check_price = if self.aiquant_execution_rules {
|
||||
self.aiquant_limit_check_price(snapshot, OrderSide::Buy)
|
||||
} else {
|
||||
ChinaAShareRiskControl::buy_check_price(snapshot, self.execution_price_field)
|
||||
};
|
||||
let check_price = self.execution_limit_check_price(snapshot, OrderSide::Buy);
|
||||
if let Some(reason) = ChinaAShareRiskControl::buy_rejection_reason_with_config(
|
||||
date,
|
||||
candidate,
|
||||
@@ -2948,7 +2992,7 @@ where
|
||||
) {
|
||||
return RuleCheck::reject(reason);
|
||||
}
|
||||
if !self.aiquant_execution_rules && !self.rules.duplicates_standard_china_risk() {
|
||||
if !self.rules.duplicates_standard_china_risk() {
|
||||
return self
|
||||
.rules
|
||||
.can_buy(date, snapshot, candidate, self.execution_price_field);
|
||||
@@ -2966,18 +3010,14 @@ where
|
||||
instrument: Option<&Instrument>,
|
||||
algo_request: Option<&AlgoExecutionRequest>,
|
||||
) -> RuleCheck {
|
||||
let check_price = if self.aiquant_execution_rules {
|
||||
self.aiquant_order_limit_check_price(
|
||||
date,
|
||||
data,
|
||||
symbol,
|
||||
snapshot,
|
||||
OrderSide::Buy,
|
||||
algo_request,
|
||||
)
|
||||
} else {
|
||||
ChinaAShareRiskControl::buy_check_price(snapshot, self.execution_price_field)
|
||||
};
|
||||
let check_price = self.execution_order_limit_check_price(
|
||||
date,
|
||||
data,
|
||||
symbol,
|
||||
snapshot,
|
||||
OrderSide::Buy,
|
||||
algo_request,
|
||||
);
|
||||
if let Some(reason) = self.same_day_rebuy_rejection_reason(date, symbol) {
|
||||
return RuleCheck::reject(reason);
|
||||
}
|
||||
@@ -2991,7 +3031,7 @@ where
|
||||
) {
|
||||
return RuleCheck::reject(reason);
|
||||
}
|
||||
if !self.aiquant_execution_rules && !self.rules.duplicates_standard_china_risk() {
|
||||
if !self.rules.duplicates_standard_china_risk() {
|
||||
return self
|
||||
.rules
|
||||
.can_buy(date, snapshot, candidate, self.execution_price_field);
|
||||
@@ -3010,36 +3050,25 @@ where
|
||||
position: &crate::portfolio::Position,
|
||||
algo_request: Option<&AlgoExecutionRequest>,
|
||||
) -> RuleCheck {
|
||||
if self.risk_config.static_rules.respect_allow_buy_sell
|
||||
&& !self.aiquant_execution_rules
|
||||
&& !candidate.allow_sell
|
||||
{
|
||||
return RuleCheck::reject("sell_disabled");
|
||||
}
|
||||
let check_price = if self.aiquant_execution_rules {
|
||||
self.aiquant_order_limit_check_price(
|
||||
date,
|
||||
data,
|
||||
symbol,
|
||||
snapshot,
|
||||
OrderSide::Sell,
|
||||
algo_request,
|
||||
)
|
||||
} else {
|
||||
ChinaAShareRiskControl::sell_check_price(snapshot, self.execution_price_field)
|
||||
};
|
||||
let check_price = self.execution_order_limit_check_price(
|
||||
date,
|
||||
data,
|
||||
symbol,
|
||||
snapshot,
|
||||
OrderSide::Sell,
|
||||
algo_request,
|
||||
);
|
||||
let adjusted_candidate;
|
||||
let candidate_for_check = if self.aiquant_execution_rules
|
||||
&& self.aiquant_sell_allow_flag_is_stale_lower_limit(candidate, snapshot, check_price)
|
||||
{
|
||||
adjusted_candidate = crate::data::CandidateEligibility {
|
||||
allow_sell: true,
|
||||
..candidate.clone()
|
||||
let candidate_for_check =
|
||||
if self.sell_allow_flag_is_stale_lower_limit(candidate, snapshot, check_price) {
|
||||
adjusted_candidate = crate::data::CandidateEligibility {
|
||||
allow_sell: true,
|
||||
..candidate.clone()
|
||||
};
|
||||
&adjusted_candidate
|
||||
} else {
|
||||
candidate
|
||||
};
|
||||
&adjusted_candidate
|
||||
} else {
|
||||
candidate
|
||||
};
|
||||
if let Some(reason) = ChinaAShareRiskControl::sell_rejection_reason_with_config(
|
||||
date,
|
||||
candidate_for_check,
|
||||
@@ -3051,7 +3080,7 @@ where
|
||||
) {
|
||||
return RuleCheck::reject(reason);
|
||||
}
|
||||
if !self.aiquant_execution_rules && !self.rules.duplicates_standard_china_risk() {
|
||||
if !self.rules.duplicates_standard_china_risk() {
|
||||
return self.rules.can_sell(
|
||||
date,
|
||||
snapshot,
|
||||
@@ -3063,7 +3092,7 @@ where
|
||||
RuleCheck::allow()
|
||||
}
|
||||
|
||||
fn aiquant_sell_allow_flag_is_stale_lower_limit(
|
||||
fn sell_allow_flag_is_stale_lower_limit(
|
||||
&self,
|
||||
candidate: &crate::data::CandidateEligibility,
|
||||
snapshot: &crate::data::DailyMarketSnapshot,
|
||||
@@ -3082,11 +3111,11 @@ where
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
portfolio: &PortfolioState,
|
||||
data: &DataSet,
|
||||
_data: &DataSet,
|
||||
symbol: &str,
|
||||
current_qty: u32,
|
||||
minimum_order_quantity: u32,
|
||||
order_step_size: u32,
|
||||
_minimum_order_quantity: u32,
|
||||
_order_step_size: u32,
|
||||
) -> u32 {
|
||||
if current_qty == 0 {
|
||||
return 0;
|
||||
@@ -3094,93 +3123,23 @@ where
|
||||
let Some(position) = portfolio.position(symbol) else {
|
||||
return 0;
|
||||
};
|
||||
if self.aiquant_execution_rules {
|
||||
let sellable = position
|
||||
.sellable_qty(date)
|
||||
.saturating_sub(self.reserved_open_sell_quantity(symbol, None));
|
||||
return current_qty.saturating_sub(sellable.min(current_qty));
|
||||
}
|
||||
let Ok(snapshot) = data.require_market(date, symbol) else {
|
||||
return current_qty;
|
||||
};
|
||||
let Ok(candidate) = data.require_candidate(date, symbol) else {
|
||||
return current_qty;
|
||||
};
|
||||
let rule = self.sell_rule_check_for_order(
|
||||
date,
|
||||
data,
|
||||
symbol,
|
||||
snapshot,
|
||||
candidate,
|
||||
data.instrument(symbol),
|
||||
position,
|
||||
None,
|
||||
);
|
||||
if !rule.allowed {
|
||||
return current_qty;
|
||||
}
|
||||
let sellable = position
|
||||
.sellable_qty(date)
|
||||
.saturating_sub(self.reserved_open_sell_quantity(symbol, None));
|
||||
let sell_limit = match self.market_fillable_quantity(
|
||||
snapshot,
|
||||
OrderSide::Sell,
|
||||
sellable.min(current_qty),
|
||||
minimum_order_quantity,
|
||||
order_step_size,
|
||||
0,
|
||||
sellable >= current_qty,
|
||||
) {
|
||||
Ok(quantity) => quantity.min(sellable).min(current_qty),
|
||||
Err(_) => 0,
|
||||
};
|
||||
current_qty.saturating_sub(sell_limit)
|
||||
current_qty.saturating_sub(sellable.min(current_qty))
|
||||
}
|
||||
|
||||
fn maximum_target_quantity(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
_date: NaiveDate,
|
||||
_portfolio: &PortfolioState,
|
||||
data: &DataSet,
|
||||
symbol: &str,
|
||||
current_qty: u32,
|
||||
minimum_order_quantity: u32,
|
||||
order_step_size: u32,
|
||||
_data: &DataSet,
|
||||
_symbol: &str,
|
||||
_current_qty: u32,
|
||||
_minimum_order_quantity: u32,
|
||||
_order_step_size: u32,
|
||||
) -> u32 {
|
||||
if self.aiquant_execution_rules {
|
||||
return u32::MAX;
|
||||
}
|
||||
let Ok(snapshot) = data.require_market(date, symbol) else {
|
||||
return current_qty;
|
||||
};
|
||||
let Ok(candidate) = data.require_candidate(date, symbol) else {
|
||||
return current_qty;
|
||||
};
|
||||
let rule = self.buy_rule_check_for_order(
|
||||
date,
|
||||
data,
|
||||
symbol,
|
||||
snapshot,
|
||||
candidate,
|
||||
data.instrument(symbol),
|
||||
None,
|
||||
);
|
||||
if !rule.allowed {
|
||||
return current_qty;
|
||||
}
|
||||
let additional_limit = match self.market_fillable_quantity(
|
||||
snapshot,
|
||||
OrderSide::Buy,
|
||||
u32::MAX,
|
||||
minimum_order_quantity,
|
||||
order_step_size,
|
||||
0,
|
||||
false,
|
||||
) {
|
||||
Ok(quantity) => quantity,
|
||||
Err(_) => 0,
|
||||
};
|
||||
current_qty.saturating_add(additional_limit)
|
||||
u32::MAX
|
||||
}
|
||||
|
||||
fn estimated_sell_net_cash(&self, date: NaiveDate, price: f64, quantity: u32) -> f64 {
|
||||
@@ -3298,6 +3257,32 @@ where
|
||||
gross + cost.total()
|
||||
}
|
||||
|
||||
fn can_afford_minimum_buy(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
portfolio: &PortfolioState,
|
||||
data: &DataSet,
|
||||
symbol: &str,
|
||||
) -> bool {
|
||||
let Some(snapshot) = data.market(date, symbol) else {
|
||||
return true;
|
||||
};
|
||||
let minimum_order_quantity = self.minimum_order_quantity(data, symbol);
|
||||
let order_step_size = self.order_step_size(data, symbol);
|
||||
let minimum_buy_quantity = self.round_buy_quantity(
|
||||
minimum_order_quantity,
|
||||
minimum_order_quantity,
|
||||
order_step_size,
|
||||
);
|
||||
if minimum_buy_quantity == 0 {
|
||||
return false;
|
||||
}
|
||||
let minimum_execution_price =
|
||||
self.snapshot_execution_price(snapshot, OrderSide::Buy, Some(minimum_buy_quantity));
|
||||
self.estimated_buy_cash_out(date, minimum_execution_price, minimum_buy_quantity)
|
||||
<= portfolio.cash() + 1e-6
|
||||
}
|
||||
|
||||
fn process_sell(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
@@ -3977,13 +3962,8 @@ where
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let current_value = if self.aiquant_execution_rules {
|
||||
let valuation_price = self.target_value_valuation_price(date, data, symbol, snapshot);
|
||||
valuation_price * current_qty as f64
|
||||
} else {
|
||||
let valuation_price = self.target_value_valuation_price(date, data, symbol, snapshot);
|
||||
valuation_price * current_qty as f64
|
||||
};
|
||||
let valuation_price = self.target_value_valuation_price(date, data, symbol, snapshot);
|
||||
let current_value = valuation_price * current_qty as f64;
|
||||
let cash_delta = target_value.max(0.0) - current_value;
|
||||
|
||||
if cash_delta.abs() > f64::EPSILON {
|
||||
@@ -4256,8 +4236,7 @@ where
|
||||
commission_state: &mut BTreeMap<u64, f64>,
|
||||
report: &mut BrokerExecutionReport,
|
||||
) -> Result<(), BacktestError> {
|
||||
let price = if self.aiquant_execution_rules && limit_price.is_finite() && limit_price > 0.0
|
||||
{
|
||||
let price = if limit_price.is_finite() && limit_price > 0.0 {
|
||||
limit_price
|
||||
} else {
|
||||
data.market(date, symbol)
|
||||
@@ -7645,7 +7624,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_value_valuation_uses_daily_snapshot_but_value_order_sizing_uses_intraday_minute() {
|
||||
fn scheduled_target_value_valuation_and_sizing_use_same_intraday_price() {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
|
||||
let broker = BrokerSimulator::new_with_execution_price(
|
||||
ChinaAShareCostModel::default(),
|
||||
@@ -7672,7 +7651,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
broker.target_value_valuation_price(date, &data, "000001.SZ", snapshot),
|
||||
10.0
|
||||
11.0
|
||||
);
|
||||
assert_eq!(
|
||||
broker.value_sell_sizing_price(date, &data, "000001.SZ", snapshot),
|
||||
@@ -8138,7 +8117,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aiquant_target_portfolio_smart_defers_buy_risk_during_target_sizing() {
|
||||
fn target_portfolio_smart_defers_buy_risk_until_order_validation() {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
|
||||
let data = DataSet::from_components_with_actions_and_quotes(
|
||||
vec![limit_test_instrument()],
|
||||
@@ -8154,30 +8133,17 @@ mod tests {
|
||||
let mut target_weights = BTreeMap::new();
|
||||
target_weights.insert("000001.SZ".to_string(), 0.50);
|
||||
|
||||
let default_broker = BrokerSimulator::new_with_execution_price(
|
||||
let broker = BrokerSimulator::new_with_execution_price(
|
||||
ChinaAShareCostModel::default(),
|
||||
ChinaEquityRuleHooks,
|
||||
PriceField::Open,
|
||||
)
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(false);
|
||||
let (default_targets, _) = default_broker
|
||||
let (targets, _) = broker
|
||||
.target_quantities(date, &portfolio, &data, &target_weights)
|
||||
.expect("default target quantities");
|
||||
assert_eq!(default_targets.get("000001.SZ").copied().unwrap_or(0), 0);
|
||||
|
||||
let aiquant_broker = BrokerSimulator::new_with_execution_price(
|
||||
ChinaAShareCostModel::default(),
|
||||
ChinaEquityRuleHooks,
|
||||
PriceField::Open,
|
||||
)
|
||||
.with_aiquant_execution_rules(true)
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(false);
|
||||
let (aiquant_targets, _) = aiquant_broker
|
||||
.target_quantities(date, &portfolio, &data, &target_weights)
|
||||
.expect("aiquant target quantities");
|
||||
assert_eq!(aiquant_targets.get("000001.SZ").copied(), Some(49_900));
|
||||
.expect("target quantities");
|
||||
assert_eq!(targets.get("000001.SZ").copied(), Some(49_900));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -8198,7 +8164,6 @@ mod tests {
|
||||
ChinaEquityRuleHooks,
|
||||
PriceField::Open,
|
||||
)
|
||||
.with_aiquant_execution_rules(true)
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(false);
|
||||
let mut portfolio = PortfolioState::new(1_000_000.0);
|
||||
@@ -8294,7 +8259,6 @@ mod tests {
|
||||
ChinaEquityRuleHooks,
|
||||
PriceField::Open,
|
||||
)
|
||||
.with_aiquant_execution_rules(true)
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(false)
|
||||
.with_inactive_limit(false);
|
||||
@@ -8660,7 +8624,6 @@ mod tests {
|
||||
ChinaEquityRuleHooks,
|
||||
PriceField::Open,
|
||||
)
|
||||
.with_aiquant_execution_rules(true)
|
||||
.with_rebalance_cash_mode(mode)
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(false)
|
||||
@@ -9093,16 +9056,12 @@ mod tests {
|
||||
Some(1_000)
|
||||
);
|
||||
assert!(report.fill_events.is_empty());
|
||||
assert!(report.order_events.is_empty());
|
||||
assert!(
|
||||
report
|
||||
.diagnostics
|
||||
.iter()
|
||||
.any(|item| item.contains("rebalance_target_clipped")
|
||||
&& item.contains("000001.SZ")),
|
||||
"{:?}",
|
||||
report.diagnostics
|
||||
);
|
||||
assert!(report.order_events.iter().any(|event| {
|
||||
event.symbol == symbol
|
||||
&& event.side == OrderSide::Sell
|
||||
&& event.status == OrderStatus::Rejected
|
||||
&& event.reason.contains("missing")
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -9204,7 +9163,6 @@ mod tests {
|
||||
.with_intraday_execution_start_time(date.and_hms_opt(10, 40, 0).unwrap().time())
|
||||
.with_slippage_model(SlippageModel::PriceRatio(0.002))
|
||||
.with_strict_value_budget(true)
|
||||
.with_aiquant_execution_rules(true)
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(false)
|
||||
.with_inactive_limit(false);
|
||||
@@ -9627,8 +9585,7 @@ mod tests {
|
||||
ChinaAShareCostModel::default(),
|
||||
ChinaEquityRuleHooks,
|
||||
PriceField::Last,
|
||||
)
|
||||
.with_aiquant_execution_rules(true);
|
||||
);
|
||||
let aiquant_rule = aiquant_broker.buy_rule_check(date, &snapshot, &candidate, None);
|
||||
assert!(!aiquant_rule.allowed);
|
||||
assert_eq!(aiquant_rule.reason.as_deref(), Some("buy_disabled"));
|
||||
@@ -9691,7 +9648,6 @@ mod tests {
|
||||
ChinaEquityRuleHooks,
|
||||
PriceField::Last,
|
||||
)
|
||||
.with_aiquant_execution_rules(true)
|
||||
.with_intraday_execution_start_time(date.and_hms_opt(10, 18, 0).unwrap().time())
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(false)
|
||||
@@ -9781,7 +9737,6 @@ mod tests {
|
||||
ChinaEquityRuleHooks,
|
||||
PriceField::Last,
|
||||
)
|
||||
.with_aiquant_execution_rules(true)
|
||||
.with_intraday_execution_start_time(date.and_hms_opt(10, 18, 0).unwrap().time())
|
||||
.with_volume_limit(false)
|
||||
.with_liquidity_limit(false)
|
||||
|
||||
Reference in New Issue
Block a user