diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 0ae7f8b..0f4e010 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -9728,7 +9728,7 @@ impl Strategy for PlatformExprStrategy { .unwrap_or(0); let quantity_delta = after_qty as i32 - before_qty as i32; if stop_take_exit_signal_symbols.contains(&position.symbol) - && quantity_delta >= 0 + && (self.config.target_portfolio_daily_enabled || quantity_delta >= 0) { continue; } @@ -9874,7 +9874,7 @@ impl Strategy for PlatformExprStrategy { } } } - if daily_top_up_active { + if daily_top_up_active && !self.config.target_portfolio_daily_enabled { self.try_daily_top_up_at_position( ctx, &day, @@ -9986,7 +9986,7 @@ impl Strategy for PlatformExprStrategy { } else if stop_hit { unresolved_stop_loss_symbols.insert(position.symbol.clone()); } - if daily_top_up_active { + if daily_top_up_active && !self.config.target_portfolio_daily_enabled { self.try_daily_top_up_at_position( ctx, &day, @@ -10073,7 +10073,7 @@ impl Strategy for PlatformExprStrategy { } else { unresolved_stop_loss_symbols.insert(position.symbol.clone()); } - if daily_top_up_active { + if daily_top_up_active && !self.config.target_portfolio_daily_enabled { self.try_daily_top_up_at_position( ctx, &day, @@ -10154,7 +10154,7 @@ impl Strategy for PlatformExprStrategy { } } } - if daily_top_up_active { + if daily_top_up_active && !self.config.target_portfolio_daily_enabled { self.try_daily_top_up_at_position( ctx, &day, @@ -10188,6 +10188,39 @@ impl Strategy for PlatformExprStrategy { } } + if daily_top_up_active && self.config.target_portfolio_daily_enabled { + self.try_daily_top_up_at_position( + ctx, + &day, + &stock_list, + decision_date, + execution_date, + projection_date, + selection_factor_date, + signal_date, + daily_top_up_target_budget, + selection_limit, + defer_execution_risk, + None, + &mut projected, + &mut projected_execution_state, + &mut order_intents, + &mut aiquant_available_cash, + &mut slot_working_symbols, + &mut same_bar_buy_symbols, + &pending_full_close_symbols, + &slot_blocking_symbols, + &same_day_sold_symbols, + &exit_symbols, + &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, + )?; + } + if periodic_rebalance { let rebalance_existing_positions = self.config.rebalance_existing_positions || self @@ -23988,6 +24021,8 @@ mod tests { cfg.exposure_expr = "1.0".to_string(); cfg.take_profit_expr = "1.1".to_string(); cfg.daily_top_up_enabled = true; + cfg.daily_position_target_adjust_enabled = true; + cfg.target_portfolio_daily_enabled = true; cfg.release_slot_on_exit_signal = true; cfg.aiquant_transaction_cost = true; cfg.intraday_execution_time = Some(NaiveTime::from_hms_opt(10, 18, 0).unwrap()); @@ -23995,13 +24030,15 @@ mod tests { strategy.rebalance_day_counter = 2; let decision = strategy.on_day(&ctx).expect("platform decision"); - let top_up_symbols = decision + let top_ups = decision .order_intents .iter() .filter_map(|intent| match intent { - OrderIntent::Value { symbol, reason, .. } if reason == "daily_top_up_buy" => { - Some(symbol.as_str()) - } + OrderIntent::Value { + symbol, + value, + reason, + } if reason == "daily_top_up_buy" => Some((symbol.as_str(), *value)), _ => None, }) .collect::>(); @@ -24019,11 +24056,19 @@ mod tests { decision.order_intents ); assert_eq!( - top_up_symbols, + top_ups + .iter() + .map(|(symbol, _)| *symbol) + .collect::>(), vec![buy_first, buy_second], "{:?}", decision.order_intents ); + assert!( + top_ups.iter().all(|(_, value)| *value >= 6_900.0), + "top ups must be sized after all same-batch sells: {:?}", + decision.order_intents + ); } fn partial_stop_loss_top_up_symbols_for_exposure(exposure_expr: &str) -> Vec {