From 0ea5fae69dc51f3ad188b67d80188cc64fc21a19 Mon Sep 17 00:00:00 2001 From: boris Date: Sun, 12 Jul 2026 05:55:55 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=AC=A1=E6=97=A5=E5=BC=80?= =?UTF-8?q?=E7=9B=98=E6=96=B0=E4=BB=93=E7=9B=AE=E6=A0=87=E5=B8=82=E5=80=BC?= =?UTF-8?q?=E6=8C=87=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 135 +++++++++++++----- 1 file changed, 101 insertions(+), 34 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index fbf2ff6..ee5cc63 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -2287,8 +2287,14 @@ impl PlatformExprStrategy { }; if self.config.matching_type == MatchingType::NextBarOpen { if let Some(market) = ctx.data.market(date, symbol) { - if market.prev_close.is_finite() && market.prev_close > 0.0 { - return position.quantity as f64 * market.prev_close; + let signal_visible_price = if ctx.is_lagged_execution() && date == ctx.decision_date + { + market.close + } else { + market.prev_close + }; + if signal_visible_price.is_finite() && signal_visible_price > 0.0 { + return position.quantity as f64 * signal_visible_price; } } } @@ -9579,22 +9585,57 @@ impl Strategy for PlatformExprStrategy { { continue; } - let target_cash = if self.config.aiquant_transaction_cost { - fixed_buy_cash * stock_scale - } else { - let slot_buy_cash = self.remaining_buy_cash_per_slot( + if self.config.aiquant_transaction_cost { + let target_value = fixed_buy_cash * stock_scale; + if target_value <= 0.0 { + continue; + } + if !defer_execution_risk + && self + .buy_rejection_reason( + ctx, + execution_date, + symbol, + &self.stock_state(ctx, execution_date, symbol)?, + )? + .is_some() + { + continue; + } + if !self.stock_passes_expr(ctx, &day, &decision_stock)? { + continue; + } + self.project_order_value( ctx, - &projected, + &mut projected, projection_date, - target_budget, - selection_limit, - &rebalance_working_symbols, - &rebalance_value_symbols, - rebalance_pending_buy_value, - &pending_full_close_symbols, + symbol, + target_value, + &mut projected_execution_state, ); - slot_buy_cash * stock_scale - }; + order_intents.push(OrderIntent::TargetValue { + symbol: symbol.clone(), + target_value, + reason: "periodic_rebalance_buy".to_string(), + }); + self.remember_position_entry_date(symbol, signal_date); + aiquant_available_cash = projected.cash().max(0.0); + rebalance_working_symbols.insert(symbol.clone()); + slot_working_symbols.insert(symbol.clone()); + continue; + } + let slot_buy_cash = self.remaining_buy_cash_per_slot( + ctx, + &projected, + projection_date, + target_budget, + selection_limit, + &rebalance_working_symbols, + &rebalance_value_symbols, + rebalance_pending_buy_value, + &pending_full_close_symbols, + ); + let target_cash = slot_buy_cash * stock_scale; let buy_cash = target_cash.min(aiquant_available_cash); if buy_cash <= 0.0 { if debug_projection { @@ -18579,6 +18620,11 @@ mod tests { OrderIntent::Value { symbol, value, .. } => { symbol == limit_symbol && *value > 0.0 } + OrderIntent::TargetValue { + symbol, + target_value, + .. + } => symbol == limit_symbol && *target_value > 0.0, _ => false, }), "{:?}", @@ -18779,6 +18825,13 @@ mod tests { intent, OrderIntent::Value { symbol, value, .. } if symbol == candidate_symbol && *value > 0.0 + ) || matches!( + intent, + OrderIntent::TargetValue { + symbol, + target_value, + .. + } if symbol == candidate_symbol && *target_value > 0.0 )), "{:?}", decision.order_intents @@ -25251,7 +25304,11 @@ mod tests { .filter(|intent| { matches!( intent, - OrderIntent::Value { reason, .. } if reason == "periodic_rebalance_buy" + OrderIntent::TargetValue { + target_value, + reason, + .. + } if *target_value > 0.0 && reason == "periodic_rebalance_buy" ) }) .count(); @@ -25429,8 +25486,13 @@ mod tests { assert!( !decision.order_intents.iter().any(|intent| matches!( intent, - OrderIntent::Value { symbol, reason, .. } - if symbol == "000001.SZ" && reason == "periodic_rebalance_buy" + OrderIntent::TargetValue { + symbol, + target_value, + reason, + } if symbol == "000001.SZ" + && *target_value > 0.0 + && reason == "periodic_rebalance_buy" )), "{:?}", decision.order_intents @@ -25438,8 +25500,13 @@ mod tests { assert!( decision.order_intents.iter().any(|intent| matches!( intent, - OrderIntent::Value { symbol, reason, .. } - if symbol == "000003.SZ" && reason == "periodic_rebalance_buy" + OrderIntent::TargetValue { + symbol, + target_value, + reason, + } if symbol == "000003.SZ" + && *target_value > 0.0 + && reason == "periodic_rebalance_buy" )), "{:?}", decision.order_intents @@ -25629,7 +25696,11 @@ mod tests { .filter(|intent| { matches!( intent, - OrderIntent::Value { reason, .. } if reason == "periodic_rebalance_buy" + OrderIntent::TargetValue { + target_value, + reason, + .. + } if *target_value > 0.0 && reason == "periodic_rebalance_buy" ) }) .count(); @@ -25650,8 +25721,12 @@ mod tests { .order_intents .iter() .filter_map(|intent| match intent { - OrderIntent::Value { value, reason, .. } if reason == "periodic_rebalance_buy" => { - Some(*value) + OrderIntent::TargetValue { + target_value, + reason, + .. + } if *target_value > 0.0 && reason == "periodic_rebalance_buy" => { + Some(*target_value) } _ => None, }) @@ -25660,18 +25735,10 @@ mod tests { assert!( periodic_buy_values .iter() - .take(periodic_buy_values.len().saturating_sub(1)) .all(|value| (*value - equal_weight_target).abs() < 1e-6), "{:?}", decision.order_intents ); - assert!( - periodic_buy_values - .last() - .is_some_and(|value| *value > 0.0 && *value <= equal_weight_target), - "{:?}", - decision.order_intents - ); } #[test] @@ -25819,13 +25886,13 @@ mod tests { assert!( decision.order_intents.iter().any(|intent| matches!( intent, - OrderIntent::Value { + OrderIntent::TargetValue { symbol, - value, + target_value, reason, } if symbol == "000002.SZ" && reason == "periodic_rebalance_buy" - && (*value - 10_000.0).abs() < 1e-6 + && (*target_value - 10_000.0).abs() < 1e-6 )), "{:?}", decision.order_intents