From 8c4156948e9599e5955c65c6cf075a075468e497 Mon Sep 17 00:00:00 2001 From: boris Date: Wed, 8 Jul 2026 01:48:15 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DALV=E6=97=A5=E5=86=85?= =?UTF-8?q?=E8=A1=A5=E4=BB=93=E5=BE=AA=E7=8E=AF=E6=AC=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 255 +++++++++--------- 1 file changed, 133 insertions(+), 122 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 67e2ce1..bcc6749 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -2241,7 +2241,7 @@ impl PlatformExprStrategy { } #[allow(clippy::too_many_arguments)] - fn try_daily_top_up_once( + fn try_daily_top_up_at_position( &mut self, ctx: &StrategyContext<'_>, day: &DayExpressionState, @@ -2274,136 +2274,147 @@ impl PlatformExprStrategy { return Ok(false); } - let mut budget_working_symbols = slot_working_symbols.clone(); - budget_working_symbols.extend(same_bar_buy_symbols.iter().cloned()); - if Self::effective_slot_count(&budget_working_symbols, pending_full_close_symbols) - >= selection_limit - { - return Ok(false); - } - let value_symbols = budget_working_symbols.clone(); - let active_value = if debug_daily_top_up { - self.remaining_buy_cash_active_value( + let mut submitted_any = false; + loop { + let mut budget_working_symbols = slot_working_symbols.clone(); + budget_working_symbols.extend(same_bar_buy_symbols.iter().cloned()); + if Self::effective_slot_count(&budget_working_symbols, pending_full_close_symbols) + >= selection_limit + { + break; + } + let value_symbols = budget_working_symbols.clone(); + let active_value = if debug_daily_top_up { + self.remaining_buy_cash_active_value( + ctx, + projected, + projection_date, + &budget_working_symbols, + &value_symbols, + ) + } else { + 0.0 + }; + let slot_buy_cash = self.remaining_buy_cash_per_slot( ctx, projected, projection_date, + target_budget, + selection_limit, &budget_working_symbols, &value_symbols, - ) - } else { - 0.0 - }; - let slot_buy_cash = self.remaining_buy_cash_per_slot( - ctx, - projected, - projection_date, - target_budget, - selection_limit, - &budget_working_symbols, - &value_symbols, - *pending_buy_value, - pending_full_close_symbols, - ); - let available_buy_cash = slot_buy_cash.min(*aiquant_available_cash); - if debug_daily_top_up { - daily_top_up_debug_notes.push(format!( - "daily_top_up_budget date={} working={} value_symbols={} same_bar_buys={} active_value={:.4} pending_buy_value={:.4} target_budget={:.4} slot_buy_cash={:.4} available_cash={:.4} portfolio_cash={:.4}", - execution_date, - budget_working_symbols.len(), - value_symbols.len(), - same_bar_buy_symbols.len(), - active_value, *pending_buy_value, - target_budget, - slot_buy_cash, - available_buy_cash, - *aiquant_available_cash - )); - } - if slot_buy_cash <= 0.0 || available_buy_cash < slot_buy_cash * 0.5 { - return Ok(false); - } - - for symbol in stock_list { - if current_position_symbol - .map(|current| current == symbol) - .unwrap_or(false) - { - continue; - } - let released_exit_position = projected.positions().contains_key(symbol) - && !slot_working_symbols.contains(symbol) - && !same_day_sold_symbols.contains(symbol) - && !pending_full_close_symbols.contains(symbol); - if (projected.positions().contains_key(symbol) && !released_exit_position) - || same_day_sold_symbols.contains(symbol) - || (exit_symbols.contains(symbol) && !released_exit_position) - || pending_full_close_symbols.contains(symbol) - || delayed_sold_symbols.contains(symbol) - || intraday_attempted_buys.contains(symbol) - { - continue; - } - if !defer_execution_risk - && self - .buy_rejection_reason( - ctx, - execution_date, - symbol, - &self.stock_state(ctx, execution_date, symbol)?, - )? - .is_some() - { - continue; - } - let decision_stock = self.stock_state_with_factor_date( - ctx, - decision_date, - selection_factor_date, - symbol, - )?; - let buy_cash = available_buy_cash * self.buy_scale(ctx, day, &decision_stock)?; - if buy_cash <= 0.0 { - return Ok(false); - } - intraday_attempted_buys.insert(symbol.clone()); - let cash_before_buy = projected.cash(); - let order_result = self.project_order_value( - ctx, - projected, - projection_date, - symbol, - buy_cash, - projected_execution_state, + pending_full_close_symbols, ); - if order_result.was_submitted() { - order_intents.push(OrderIntent::Value { - symbol: symbol.clone(), - value: buy_cash, - reason: "daily_top_up_buy".to_string(), - }); - self.remember_position_entry_date(symbol, signal_date); - let spent = (cash_before_buy - projected.cash()).max(0.0); - *aiquant_available_cash = (*aiquant_available_cash - spent).max(0.0); - slot_working_symbols.insert(symbol.clone()); - same_bar_buy_symbols.insert(symbol.clone()); - *pending_buy_value += available_buy_cash; - if debug_daily_top_up { - daily_top_up_debug_notes.push(format!( - "daily_top_up_fill date={} symbol={} requested_cash={:.4} filled_qty={} spent={:.4} remaining_cash={:.4}", - execution_date, - symbol, - buy_cash, - order_result.filled_quantity, - spent, - *aiquant_available_cash - )); + let available_buy_cash = slot_buy_cash.min(*aiquant_available_cash); + if debug_daily_top_up { + daily_top_up_debug_notes.push(format!( + "daily_top_up_budget date={} working={} value_symbols={} same_bar_buys={} active_value={:.4} pending_buy_value={:.4} target_budget={:.4} slot_buy_cash={:.4} available_cash={:.4} portfolio_cash={:.4}", + execution_date, + budget_working_symbols.len(), + value_symbols.len(), + same_bar_buy_symbols.len(), + active_value, + *pending_buy_value, + target_budget, + slot_buy_cash, + available_buy_cash, + *aiquant_available_cash + )); + } + if slot_buy_cash <= 0.0 || available_buy_cash < slot_buy_cash * 0.5 { + break; + } + + let mut attempted_any = false; + let mut filled_any = false; + for symbol in stock_list { + if current_position_symbol + .map(|current| current == symbol) + .unwrap_or(false) + { + continue; } - return Ok(true); + let released_exit_position = projected.positions().contains_key(symbol) + && !slot_working_symbols.contains(symbol) + && !same_day_sold_symbols.contains(symbol) + && !pending_full_close_symbols.contains(symbol); + if (projected.positions().contains_key(symbol) && !released_exit_position) + || same_day_sold_symbols.contains(symbol) + || (exit_symbols.contains(symbol) && !released_exit_position) + || pending_full_close_symbols.contains(symbol) + || delayed_sold_symbols.contains(symbol) + || intraday_attempted_buys.contains(symbol) + { + continue; + } + if !defer_execution_risk + && self + .buy_rejection_reason( + ctx, + execution_date, + symbol, + &self.stock_state(ctx, execution_date, symbol)?, + )? + .is_some() + { + continue; + } + let decision_stock = self.stock_state_with_factor_date( + ctx, + decision_date, + selection_factor_date, + symbol, + )?; + let buy_cash = available_buy_cash * self.buy_scale(ctx, day, &decision_stock)?; + if buy_cash <= 0.0 { + return Ok(submitted_any); + } + intraday_attempted_buys.insert(symbol.clone()); + attempted_any = true; + let cash_before_buy = projected.cash(); + let order_result = self.project_order_value( + ctx, + projected, + projection_date, + symbol, + buy_cash, + projected_execution_state, + ); + if order_result.was_submitted() { + order_intents.push(OrderIntent::Value { + symbol: symbol.clone(), + value: buy_cash, + reason: "daily_top_up_buy".to_string(), + }); + self.remember_position_entry_date(symbol, signal_date); + let spent = (cash_before_buy - projected.cash()).max(0.0); + *aiquant_available_cash = (*aiquant_available_cash - spent).max(0.0); + slot_working_symbols.insert(symbol.clone()); + same_bar_buy_symbols.insert(symbol.clone()); + *pending_buy_value += available_buy_cash; + if debug_daily_top_up { + daily_top_up_debug_notes.push(format!( + "daily_top_up_fill date={} symbol={} requested_cash={:.4} filled_qty={} spent={:.4} remaining_cash={:.4}", + execution_date, + symbol, + buy_cash, + order_result.filled_quantity, + spent, + *aiquant_available_cash + )); + } + submitted_any = true; + filled_any = true; + break; + } + } + if !attempted_any || !filled_any { + break; } } - Ok(false) + Ok(submitted_any) } fn effective_slot_count( @@ -8697,7 +8708,7 @@ impl Strategy for PlatformExprStrategy { unresolved_stop_loss_symbols.insert(position.symbol.clone()); } if daily_top_up_active { - self.try_daily_top_up_once( + self.try_daily_top_up_at_position( ctx, &day, &stock_list, @@ -8762,7 +8773,7 @@ impl Strategy for PlatformExprStrategy { } } if daily_top_up_active { - self.try_daily_top_up_once( + self.try_daily_top_up_at_position( ctx, &day, &stock_list,