修正AiQuant兼容回测盘中估值口径
This commit is contained in:
@@ -361,30 +361,12 @@ where
|
||||
symbol: &str,
|
||||
snapshot: &crate::data::DailyMarketSnapshot,
|
||||
) -> f64 {
|
||||
if self.execution_price_field == PriceField::Last {
|
||||
let start_cursor = self
|
||||
.runtime_intraday_start_time
|
||||
.get()
|
||||
.or(self.intraday_execution_start_time)
|
||||
.map(|start_time| date.and_time(start_time));
|
||||
if let Some(price) = self
|
||||
.latest_known_quote_at_or_before(
|
||||
data.execution_quotes_on(date, symbol),
|
||||
start_cursor,
|
||||
snapshot,
|
||||
OrderSide::Buy,
|
||||
MatchingType::NextTickLast,
|
||||
false,
|
||||
)
|
||||
.and_then(|quote| {
|
||||
(quote.last_price.is_finite() && quote.last_price > 0.0)
|
||||
.then_some(quote.last_price)
|
||||
})
|
||||
{
|
||||
return price;
|
||||
}
|
||||
let _ = (date, data, symbol);
|
||||
if snapshot.close.is_finite() && snapshot.close > 0.0 {
|
||||
snapshot.close
|
||||
} else {
|
||||
self.sizing_price(snapshot)
|
||||
}
|
||||
self.sizing_price(snapshot)
|
||||
}
|
||||
|
||||
fn value_order_sizing_price(
|
||||
@@ -3675,16 +3657,11 @@ where
|
||||
requested_qty
|
||||
}
|
||||
|
||||
fn value_buy_gross_limit(
|
||||
&self,
|
||||
value_budget: Option<f64>,
|
||||
requested_qty: u32,
|
||||
reference_price: f64,
|
||||
) -> Option<f64> {
|
||||
fn value_buy_gross_limit(&self, value_budget: Option<f64>) -> Option<f64> {
|
||||
if !self.strict_value_budget {
|
||||
return None;
|
||||
}
|
||||
value_budget.map(|budget| budget.max(reference_price * requested_qty as f64))
|
||||
value_budget.filter(|budget| budget.is_finite() && *budget > 0.0)
|
||||
}
|
||||
|
||||
fn process_buy(
|
||||
@@ -3843,8 +3820,15 @@ where
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let value_gross_limit =
|
||||
self.value_buy_gross_limit(value_budget, constrained_qty, self.sizing_price(snapshot));
|
||||
let value_gross_limit = self.value_buy_gross_limit(value_budget);
|
||||
let buy_cash_limit = if self.strict_value_budget {
|
||||
value_budget
|
||||
.filter(|budget| budget.is_finite() && *budget > 0.0)
|
||||
.map(|budget| portfolio.cash().min(budget))
|
||||
.unwrap_or_else(|| portfolio.cash())
|
||||
} else {
|
||||
portfolio.cash()
|
||||
};
|
||||
|
||||
let fill = self.resolve_execution_fill(
|
||||
date,
|
||||
@@ -3859,7 +3843,7 @@ where
|
||||
false,
|
||||
execution_cursors,
|
||||
None,
|
||||
Some(portfolio.cash()),
|
||||
Some(buy_cash_limit),
|
||||
value_gross_limit,
|
||||
algo_request,
|
||||
limit_price,
|
||||
@@ -3896,7 +3880,7 @@ where
|
||||
self.execution_price_with_limit_slippage(execution_price, limit_price);
|
||||
let filled_qty = self.affordable_buy_quantity(
|
||||
date,
|
||||
portfolio.cash(),
|
||||
buy_cash_limit,
|
||||
value_gross_limit,
|
||||
execution_price,
|
||||
constrained_qty,
|
||||
@@ -3913,7 +3897,7 @@ where
|
||||
partial_fill_reason = merge_partial_fill_reason(
|
||||
partial_fill_reason,
|
||||
self.buy_reduction_reason(
|
||||
portfolio.cash(),
|
||||
buy_cash_limit,
|
||||
value_gross_limit,
|
||||
execution_price,
|
||||
constrained_qty,
|
||||
@@ -5119,7 +5103,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_value_sizing_uses_intraday_tick_instead_of_daily_snapshot() {
|
||||
fn target_value_valuation_uses_daily_snapshot_but_value_order_sizing_uses_intraday_tick() {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
|
||||
let broker = BrokerSimulator::new_with_execution_price(
|
||||
ChinaAShareCostModel::default(),
|
||||
@@ -5128,7 +5112,7 @@ mod tests {
|
||||
)
|
||||
.with_intraday_execution_start_time(date.and_hms_opt(9, 33, 0).unwrap().time());
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
snapshot.last_price = 10.0;
|
||||
snapshot.last_price = 9.0;
|
||||
snapshot.close = 10.0;
|
||||
let mut quote = limit_test_quote(11.0, 10.99, 11.01);
|
||||
quote.timestamp = date.and_hms_opt(9, 32, 58).expect("valid timestamp");
|
||||
@@ -5146,7 +5130,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
broker.target_value_valuation_price(date, &data, "000001.SZ", snapshot),
|
||||
11.0
|
||||
10.0
|
||||
);
|
||||
assert_eq!(
|
||||
broker.value_sell_sizing_price(date, &data, "000001.SZ", snapshot),
|
||||
@@ -5266,6 +5250,69 @@ mod tests {
|
||||
assert_eq!(report.order_events[0].filled_quantity, 14_300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_value_buy_budget_includes_commission_at_execution_price() {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
|
||||
let broker = BrokerSimulator::new_with_execution_price(
|
||||
ChinaAShareCostModel::default(),
|
||||
ChinaEquityRuleHooks,
|
||||
PriceField::Last,
|
||||
)
|
||||
.with_intraday_execution_start_time(date.and_hms_opt(9, 33, 0).unwrap().time())
|
||||
.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 = 7.14;
|
||||
snapshot.open = 7.14;
|
||||
snapshot.high = 7.20;
|
||||
snapshot.low = 7.10;
|
||||
snapshot.last_price = 7.14;
|
||||
snapshot.bid1 = 7.14;
|
||||
snapshot.ask1 = 7.15;
|
||||
snapshot.close = 7.14;
|
||||
snapshot.upper_limit = 7.85;
|
||||
snapshot.lower_limit = 6.43;
|
||||
let mut quote = limit_test_quote(7.14, 7.14, 7.15);
|
||||
quote.timestamp = date.and_hms_opt(9, 32, 55).expect("valid timestamp");
|
||||
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![quote],
|
||||
)
|
||||
.expect("valid dataset");
|
||||
let value_budget = 125_216.8131;
|
||||
let mut portfolio = PortfolioState::new(10_000_000.0);
|
||||
let mut report = BrokerExecutionReport::default();
|
||||
|
||||
broker
|
||||
.process_value(
|
||||
date,
|
||||
&mut portfolio,
|
||||
&data,
|
||||
"000001.SZ",
|
||||
value_budget,
|
||||
"periodic_rebalance_buy",
|
||||
&mut BTreeMap::new(),
|
||||
&mut BTreeMap::new(),
|
||||
&mut None,
|
||||
&mut BTreeMap::new(),
|
||||
&mut report,
|
||||
)
|
||||
.expect("value buy processed");
|
||||
|
||||
let fill = report.fill_events.first().expect("fill event");
|
||||
assert_eq!(fill.quantity, 17_400);
|
||||
assert!(fill.gross_amount + fill.commission <= value_budget + 1e-6);
|
||||
assert!((fill.price - 7.15428).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instantaneous_twap_without_limits_does_not_cap_quote_quantity() {
|
||||
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||
|
||||
Reference in New Issue
Block a user