修复显式股票权重被截断

This commit is contained in:
boris
2026-09-06 22:38:48 +08:00
parent dd376e4b32
commit 81f6b7d1a5
2 changed files with 50 additions and 2 deletions
+49 -1
View File
@@ -8187,7 +8187,16 @@ impl PlatformExprStrategy {
return Ok(1.0);
}
match self.eval_float(ctx, &self.config.buy_scale_expr, day, Some(stock), None) {
Ok(value) => Ok(value.clamp(0.0, 1.0)),
// `buy_scale` is a relative per-slot allocation multiplier, not the
// portfolio exposure ceiling. Explicit weights can legitimately
// exceed the equal-weight slot (for example 60/40 across two
// symbols uses scales 1.2/0.8) while `risk.index_exposure` still
// caps aggregate exposure. Clamping here silently changed saved
// stock-pool weights and caused cross-framework order drift.
Ok(value) if value.is_finite() => Ok(value.max(0.0)),
Ok(value) => Err(BacktestError::Execution(format!(
"allocation.buy_scale produced a non-finite value: {value}"
))),
Err(error) if Self::is_missing_rolling_mean_error(&error) => Ok(0.0),
Err(error) => Err(error),
}
@@ -13630,6 +13639,45 @@ mod tests {
);
}
#[test]
fn allocation_buy_scale_preserves_explicit_overweight_multiplier() {
let date = d(2025, 1, 2);
let symbol = "000001.SZ";
let data = single_symbol_platform_data(&[date], symbol);
let portfolio = PortfolioState::new(1_000_000.0);
let subscriptions = BTreeSet::new();
let ctx = StrategyContext {
execution_date: date,
decision_date: date,
decision_index: 0,
data: &data,
portfolio: &portfolio,
futures_account: None,
open_orders: &[],
dynamic_universe: None,
subscriptions: &subscriptions,
process_events: &[],
active_process_event: None,
active_datetime: None,
order_events: &[],
fills: &[],
};
let mut config = PlatformExprStrategyConfig::microcap_rotation();
config.signal_symbol = symbol.to_string();
config.buy_scale_expr = "1.2".to_string();
let strategy = PlatformExprStrategy::new(config);
let day = strategy.day_state(&ctx, date).expect("day state");
let stock = strategy
.stock_state(&ctx, date, symbol)
.expect("stock state");
let scale = strategy
.buy_scale(&ctx, &day, stock.as_ref())
.expect("buy scale");
assert!((scale - 1.2).abs() < 1e-12, "scale={scale}");
}
#[test]
fn universe_exclude_matches_explicit_symbols_and_bjse_alias() {
let excludes = vec![