Compare commits

...

8 Commits

Author SHA1 Message Date
boris e146ad6e7d 补充涨停买入撮合约束测试 2026-06-16 09:15:13 +08:00
boris cf2c4fd179 修正AiQuant补仓预算口径 2026-06-16 08:38:19 +08:00
boris 6ba61ef80b 修正跌停止损预判调仓口径 2026-06-16 08:22:15 +08:00
boris e45f990487 修正平台目标调仓执行口径 2026-06-16 08:06:19 +08:00
boris 8e6c912a07 修正AiQuant目标市值估值口径 2026-06-16 07:49:10 +08:00
boris 9a411f2403 修正平台策略弱市调仓顺序 2026-06-16 07:23:51 +08:00
boris d2c65c91b7 修正平台策略投影撮合价口径 2026-06-16 06:22:40 +08:00
boris 5078aec840 修正AiQuant盘中组合估值口径 2026-06-16 06:04:37 +08:00
2 changed files with 1122 additions and 87 deletions
+69 -9
View File
@@ -2848,8 +2848,13 @@ where
return Ok(()); return Ok(());
} }
let current_value = if self.aiquant_rqalpha_execution_rules {
let valuation_price = self.target_value_valuation_price(date, data, symbol, snapshot); let valuation_price = self.target_value_valuation_price(date, data, symbol, snapshot);
let current_value = valuation_price * current_qty as f64; 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 cash_delta = target_value.max(0.0) - current_value; let cash_delta = target_value.max(0.0) - current_value;
if cash_delta.abs() > f64::EPSILON { if cash_delta.abs() > f64::EPSILON {
@@ -5278,7 +5283,7 @@ mod tests {
} }
#[test] #[test]
fn aiquant_target_value_valuation_uses_scheduled_tick_last_price() { fn aiquant_target_value_delta_uses_scheduled_mark_price() {
let date = chrono::NaiveDate::from_ymd_opt(2023, 5, 8).expect("valid date"); let date = chrono::NaiveDate::from_ymd_opt(2023, 5, 8).expect("valid date");
let symbol = "603101.SH"; let symbol = "603101.SH";
let broker = BrokerSimulator::new_with_execution_price( let broker = BrokerSimulator::new_with_execution_price(
@@ -5369,12 +5374,6 @@ mod tests {
vec![quote], vec![quote],
) )
.expect("valid dataset"); .expect("valid dataset");
let snapshot = data.market(date, symbol).expect("market snapshot");
assert_eq!(
broker.target_value_valuation_price(date, &data, symbol, snapshot),
5.83
);
let mut portfolio = PortfolioState::new(8_261_416.62); let mut portfolio = PortfolioState::new(8_261_416.62);
portfolio.position_mut(symbol).buy(date, 21_200, 5.8817); portfolio.position_mut(symbol).buy(date, 21_200, 5.8817);
let mut report = BrokerExecutionReport::default(); let mut report = BrokerExecutionReport::default();
@@ -5398,10 +5397,11 @@ mod tests {
portfolio.position(symbol).map(|pos| pos.quantity), portfolio.position(symbol).map(|pos| pos.quantity),
Some(21_400) Some(21_400)
); );
let order = report.order_events.last().expect("target value order"); if let Some(order) = report.order_events.last() {
assert_eq!(order.requested_quantity, 200); assert_eq!(order.requested_quantity, 200);
assert_eq!(order.filled_quantity, 200); assert_eq!(order.filled_quantity, 200);
} }
}
#[test] #[test]
fn next_tick_last_execution_uses_latest_quote_before_decision_time() { fn next_tick_last_execution_uses_latest_quote_before_decision_time() {
@@ -5515,6 +5515,66 @@ mod tests {
assert_eq!(report.order_events[0].filled_quantity, 14_300); assert_eq!(report.order_events[0].filled_quantity, 14_300);
} }
#[test]
fn value_buy_rejects_when_slippage_clamps_execution_price_to_upper_limit() {
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
let broker = BrokerSimulator::new_with_execution_price(
ChinaAShareCostModel::default(),
ChinaEquityRuleHooks,
PriceField::Open,
)
.with_slippage_model(SlippageModel::PriceRatio(0.002))
.with_strict_value_budget(true)
.with_volume_limit(false)
.with_liquidity_limit(false)
.with_inactive_limit(false);
let mut snapshot = limit_test_snapshot();
snapshot.day_open = 10.98;
snapshot.open = 10.98;
snapshot.last_price = 10.98;
snapshot.close = 10.98;
snapshot.bid1 = 10.98;
snapshot.ask1 = 10.98;
snapshot.upper_limit = 11.0;
let data = DataSet::from_components_with_actions_and_quotes(
vec![limit_test_instrument()],
vec![snapshot],
Vec::new(),
vec![limit_test_candidate(true, true)],
vec![limit_test_benchmark()],
Vec::new(),
Vec::new(),
)
.expect("valid dataset");
let mut portfolio = PortfolioState::new(10_000_000.0);
let mut report = BrokerExecutionReport::default();
broker
.process_value(
date,
&mut portfolio,
&data,
"000001.SZ",
125_000.0,
"periodic_rebalance_buy",
&mut BTreeMap::new(),
&mut BTreeMap::new(),
&mut None,
&mut BTreeMap::new(),
&mut report,
)
.expect("value buy processed");
assert!(portfolio.position("000001.SZ").is_none());
assert_eq!(report.order_events.len(), 1);
assert_eq!(report.order_events[0].filled_quantity, 0);
assert!(
report.order_events[0]
.reason
.contains("open at or above upper limit")
);
}
#[test] #[test]
fn strict_value_buy_budget_includes_commission_at_execution_price() { fn strict_value_buy_budget_includes_commission_at_execution_price() {
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");
File diff suppressed because it is too large Load Diff