diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 3e66741..64912eb 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -2540,28 +2540,31 @@ impl PlatformExprStrategy { self.projected_position_value_at_execution_price(ctx, projected, date, symbol) } - fn context_position_value_for_remaining_buy_cash( + fn projected_strategy_visible_position_value_for_remaining_buy_cash( &self, ctx: &StrategyContext<'_>, + projected: &PortfolioState, date: NaiveDate, symbol: &str, ) -> f64 { - let Some(position) = ctx.portfolio.position(symbol) else { + let Some(projected_position) = projected.position(symbol) else { return 0.0; }; - if position.quantity == 0 { + if projected_position.quantity == 0 { return 0.0; } + let context_position = ctx.portfolio.position(symbol); let mark_price = self .aiquant_scheduled_last_price(ctx, date, symbol) .or_else(|| ctx.data.price(date, symbol, PriceField::Last)) .or_else(|| ctx.data.price_on_or_before(date, symbol, PriceField::Last)) + .or_else(|| context_position.map(|position| position.last_price)) .filter(|price| price.is_finite() && *price > 0.0) - .unwrap_or(position.last_price); + .unwrap_or(projected_position.last_price); if mark_price.is_finite() && mark_price > 0.0 { - mark_price * position.quantity as f64 + mark_price * projected_position.quantity as f64 } else { - position.market_value() + projected_position.market_value() } } @@ -2775,7 +2778,9 @@ impl PlatformExprStrategy { *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 order_result.filled_quantity == 0 { + *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}", @@ -2833,7 +2838,9 @@ impl PlatformExprStrategy { .filter(|symbol| value_symbols.contains(*symbol)) .map(|symbol| { if self.config.aiquant_transaction_cost { - self.context_position_value_for_remaining_buy_cash(ctx, date, symbol) + self.projected_strategy_visible_position_value_for_remaining_buy_cash( + ctx, projected, date, symbol, + ) } else { self.projected_position_value_at_execution_price(ctx, projected, date, symbol) } @@ -17610,7 +17617,7 @@ mod tests { } #[test] - fn platform_aiquant_weak_market_threshold_top_up_uses_remaining_budget() { + fn platform_aiquant_weak_market_threshold_top_up_uses_projected_remaining_budget() { let prev_date = d(2023, 5, 4); let date = d(2023, 5, 5); let symbols = ["000001.SZ", "000002.SZ", "000003.SZ"]; @@ -17766,10 +17773,15 @@ mod tests { "{:?}", decision.order_intents ); - assert!(!decision.order_intents.iter().any(|intent| matches!( - intent, - OrderIntent::Value { reason, .. } if reason == "daily_top_up_buy" - ))); + assert!( + decision.order_intents.iter().any(|intent| matches!( + intent, + OrderIntent::Value { value, reason, .. } + if reason == "daily_top_up_buy" && (*value - 13_000.0).abs() < 1e-6 + )), + "{:?}", + decision.order_intents + ); } #[test] @@ -21713,7 +21725,7 @@ mod tests { } #[test] - fn platform_aiquant_remaining_buy_cash_uses_strategy_visible_positions() { + fn platform_aiquant_remaining_buy_cash_uses_strategy_visible_price_and_projected_quantity() { let prev_date = d(2025, 2, 2); let date = d(2025, 2, 3); let symbols = ["000001.SZ", "000002.SZ"]; @@ -21842,8 +21854,8 @@ mod tests { ); assert!( - (cash - 1_000.0).abs() < 1e-6, - "remaining cash should use strategy-visible 900-share position, got {cash}" + (cash - 5_000.0).abs() < 1e-6, + "remaining cash should use strategy-visible price and projected 500-share quantity, got {cash}" ); } @@ -22000,7 +22012,7 @@ mod tests { reason, } if symbol == "000002.SZ" && reason == "daily_top_up_buy" - && (*value - 1_500.0).abs() < 1e-6 + && (*value - 4_500.0).abs() < 1e-6 )), "{:?}", decision.order_intents