Compare commits
8 Commits
df949ab8ee
...
e146ad6e7d
| Author | SHA1 | Date | |
|---|---|---|---|
| e146ad6e7d | |||
| cf2c4fd179 | |||
| 6ba61ef80b | |||
| e45f990487 | |||
| 8e6c912a07 | |||
| 9a411f2403 | |||
| d2c65c91b7 | |||
| 5078aec840 |
@@ -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
Reference in New Issue
Block a user