修复目标组合零权重估值
This commit is contained in:
@@ -1661,10 +1661,17 @@ where
|
||||
) -> Result<(BTreeMap<String, u32>, Vec<String>), BacktestError> {
|
||||
let equity =
|
||||
self.rebalance_total_equity_at_with_overrides(date, portfolio, data, valuation_prices)?;
|
||||
let target_weight_sum = target_weights.values().copied().sum::<f64>();
|
||||
let target_weight_sum = target_weights
|
||||
.values()
|
||||
.copied()
|
||||
.filter(|weight| weight.abs() > f64::EPSILON)
|
||||
.sum::<f64>();
|
||||
let mut desired_targets = BTreeMap::new();
|
||||
let mut diagnostics = Vec::new();
|
||||
for (symbol, weight) in target_weights {
|
||||
if weight.abs() <= f64::EPSILON {
|
||||
continue;
|
||||
}
|
||||
let price = self.rebalance_valuation_price_with_overrides(
|
||||
date,
|
||||
symbol,
|
||||
@@ -5544,6 +5551,59 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_portfolio_smart_ignores_zero_weight_symbols_without_market_snapshot() {
|
||||
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_volume_limit(false)
|
||||
.with_liquidity_limit(false);
|
||||
let data = DataSet::from_components_with_actions_and_quotes(
|
||||
vec![limit_test_instrument()],
|
||||
vec![limit_test_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(1_000_000.0);
|
||||
let mut target_weights = BTreeMap::new();
|
||||
target_weights.insert("000001.SZ".to_string(), 0.50);
|
||||
target_weights.insert("001239.SZ".to_string(), 0.0);
|
||||
let mut report = BrokerExecutionReport::default();
|
||||
|
||||
broker
|
||||
.process_target_portfolio_smart(
|
||||
date,
|
||||
&mut portfolio,
|
||||
&data,
|
||||
&target_weights,
|
||||
None,
|
||||
None,
|
||||
"date_conditioned_target_weights",
|
||||
&mut BTreeMap::new(),
|
||||
&mut BTreeMap::new(),
|
||||
&mut None,
|
||||
&mut BTreeMap::new(),
|
||||
&mut report,
|
||||
)
|
||||
.expect("zero weight missing snapshot symbol ignored");
|
||||
|
||||
assert!(portfolio.position("000001.SZ").is_some());
|
||||
assert!(portfolio.position("001239.SZ").is_none());
|
||||
assert!(
|
||||
report
|
||||
.fill_events
|
||||
.iter()
|
||||
.all(|event| event.symbol != "001239.SZ")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timed_target_value_zero_sells_full_position_at_requested_time_quote() {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).expect("valid date");
|
||||
|
||||
Reference in New Issue
Block a user