diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 64912eb..53029e7 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -2578,6 +2578,7 @@ impl PlatformExprStrategy { working_symbols: &BTreeSet, value_symbols: &BTreeSet, pending_buy_value: f64, + deferred_target_values: Option<&BTreeMap>, slot_blocking_symbols: &BTreeSet, ) -> f64 { if selection_limit == 0 || !target_budget.is_finite() || target_budget <= 0.0 { @@ -2597,6 +2598,7 @@ impl PlatformExprStrategy { date, working_symbols, value_symbols, + deferred_target_values, ); let working_value = active_value + pending_buy_value.max(0.0); let per_slot_budget = (target_budget - working_value).max(0.0) / slots_remaining as f64; @@ -2632,6 +2634,7 @@ impl PlatformExprStrategy { delayed_sold_symbols: &BTreeSet, intraday_attempted_buys: &mut BTreeSet, pending_buy_value: &mut f64, + deferred_target_values: &BTreeMap, debug_daily_top_up: bool, daily_top_up_debug_notes: &mut Vec, ) -> Result { @@ -2668,6 +2671,7 @@ impl PlatformExprStrategy { projection_date, &budget_working_symbols, &value_symbols, + Some(deferred_target_values), ) } else { 0.0 @@ -2681,6 +2685,7 @@ impl PlatformExprStrategy { &budget_working_symbols, &value_symbols, *pending_buy_value, + Some(deferred_target_values), slot_blocking_symbols, ); let available_buy_cash = slot_buy_cash.min(*aiquant_available_cash); @@ -2832,11 +2837,18 @@ impl PlatformExprStrategy { date: NaiveDate, working_symbols: &BTreeSet, value_symbols: &BTreeSet, + deferred_target_values: Option<&BTreeMap>, ) -> f64 { working_symbols .iter() .filter(|symbol| value_symbols.contains(*symbol)) .map(|symbol| { + if let Some(target_value) = deferred_target_values + .and_then(|target_values| target_values.get(symbol)) + .filter(|target_value| target_value.is_finite() && **target_value >= 0.0) + { + return *target_value; + } if self.config.aiquant_transaction_cost { self.projected_strategy_visible_position_value_for_remaining_buy_cash( ctx, projected, date, symbol, @@ -9261,6 +9273,7 @@ impl Strategy for PlatformExprStrategy { && selection_limit > 0; let daily_top_up_target_budget = aiquant_total_value * trading_ratio; let mut daily_top_up_pending_buy_value = 0.0_f64; + let mut deferred_daily_target_values = BTreeMap::::new(); let mut pending_full_close_symbols = BTreeSet::::new(); let mut slot_blocking_symbols = BTreeSet::::new(); let should_block_partial_exit_residual_slot = trading_ratio < 1.0; @@ -9506,6 +9519,10 @@ impl Strategy for PlatformExprStrategy { target_value, reason: "daily_position_target_adjust".to_string(), }); + if defer_execution_risk { + deferred_daily_target_values + .insert(position.symbol.clone(), target_value); + } if quantity_delta > 0 { same_bar_buy_symbols.insert(position.symbol.clone()); } @@ -9592,6 +9609,7 @@ impl Strategy for PlatformExprStrategy { &delayed_sold_symbols, &mut intraday_attempted_buys, &mut daily_top_up_pending_buy_value, + &deferred_daily_target_values, debug_daily_top_up, &mut daily_top_up_debug_notes, )?; @@ -9691,6 +9709,7 @@ impl Strategy for PlatformExprStrategy { &delayed_sold_symbols, &mut intraday_attempted_buys, &mut daily_top_up_pending_buy_value, + &deferred_daily_target_values, debug_daily_top_up, &mut daily_top_up_debug_notes, )?; @@ -9766,6 +9785,7 @@ impl Strategy for PlatformExprStrategy { &delayed_sold_symbols, &mut intraday_attempted_buys, &mut daily_top_up_pending_buy_value, + &deferred_daily_target_values, debug_daily_top_up, &mut daily_top_up_debug_notes, )?; @@ -9835,6 +9855,7 @@ impl Strategy for PlatformExprStrategy { &delayed_sold_symbols, &mut intraday_attempted_buys, &mut daily_top_up_pending_buy_value, + &deferred_daily_target_values, debug_daily_top_up, &mut daily_top_up_debug_notes, )?; @@ -10084,6 +10105,7 @@ impl Strategy for PlatformExprStrategy { &rebalance_working_symbols, &rebalance_value_symbols, rebalance_pending_buy_value, + None, &pending_full_close_symbols, ); let target_cash = slot_buy_cash * stock_scale; @@ -21850,6 +21872,7 @@ mod tests { &working_symbols, &value_symbols, 0.0, + None, &BTreeSet::new(), ); @@ -21857,6 +21880,24 @@ mod tests { (cash - 5_000.0).abs() < 1e-6, "remaining cash should use strategy-visible price and projected 500-share quantity, got {cash}" ); + + let deferred_targets = BTreeMap::from([("000001.SZ".to_string(), 2_000.0)]); + let deferred_cash = strategy.remaining_buy_cash_per_slot( + &ctx, + &projected, + date, + 6_000.0, + 2, + &working_symbols, + &value_symbols, + 0.0, + Some(&deferred_targets), + &BTreeSet::new(), + ); + assert!( + (deferred_cash - 3_000.0).abs() < 1e-6, + "lagged target reductions should reserve their frozen target value, got {deferred_cash}" + ); } #[test]