From 929b10517327c233d9d5d050c697571bed7d1800 Mon Sep 17 00:00:00 2001 From: boris Date: Mon, 7 Sep 2026 05:36:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=8C=89=E6=8C=81=E4=BB=93?= =?UTF-8?q?=E6=88=90=E4=BA=A4=E5=9D=87=E4=BB=B7=E6=AD=A2=E7=9B=88=E6=AD=A2?= =?UTF-8?q?=E6=8D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 56 +++++++++++++++++++ .../fidc-core/src/platform_strategy_spec.rs | 21 +++++++ 2 files changed, 77 insertions(+) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index fe83013..87e16b6 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -575,6 +575,7 @@ pub enum PlatformExplicitActionStage { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PlatformStopTakeReferencePriceMode { PositionCostBasis, + PositionAverageEntryPrice, SignalDayPostAdjustedClose, } @@ -11674,6 +11675,7 @@ impl PlatformExprStrategy { entry_avg_price } } + PlatformStopTakeReferencePriceMode::PositionAverageEntryPrice => entry_avg_price, PlatformStopTakeReferencePriceMode::SignalDayPostAdjustedClose => { let entry_date = self.position_entry_dates.get(symbol).copied().ok_or_else(|| { BacktestError::Execution(format!( @@ -19112,6 +19114,60 @@ mod tests { .expect("quoted stop check"), (true, false), ); + + let mut borderline_portfolio = PortfolioState::new(1_000_000.0); + let borderline_position = borderline_portfolio.position_mut(symbol); + borderline_position.buy(prev_date, 9_300, 25.495); + borderline_position.average_cost = 25.51; + let borderline_context = StrategyContext { + execution_date: date, + decision_date: date, + decision_index: 40, + data: &with_quote, + portfolio: &borderline_portfolio, + futures_account: None, + open_orders: &[], + dynamic_universe: None, + subscriptions: &subscriptions, + process_events: &[], + active_process_event: None, + active_datetime: None, + order_events: &[], + fills: &[], + }; + let mut average_price_cfg = PlatformExprStrategyConfig::microcap_rotation(); + average_price_cfg.rotation_enabled = false; + average_price_cfg.matching_type = MatchingType::CurrentBarClose; + average_price_cfg.intraday_execution_time = + Some(NaiveTime::from_hms_opt(9, 30, 0).expect("time")); + average_price_cfg.signal_symbol = symbol.to_string(); + average_price_cfg.stop_loss_expr = "holding_return <= -0.08".to_string(); + average_price_cfg.take_profit_expr.clear(); + average_price_cfg.stop_take_reference_price_mode = + PlatformStopTakeReferencePriceMode::PositionAverageEntryPrice; + let average_price_strategy = PlatformExprStrategy::new(average_price_cfg.clone()); + let borderline_day = average_price_strategy + .day_state(&borderline_context, date) + .expect("borderline day state"); + assert_eq!( + average_price_strategy + .stop_take_action(&borderline_context, date, date, &borderline_day, symbol,) + .expect("average entry price stop check"), + (false, false), + ); + + average_price_cfg.stop_take_reference_price_mode = + PlatformStopTakeReferencePriceMode::PositionCostBasis; + let cost_basis_strategy = PlatformExprStrategy::new(average_price_cfg); + let borderline_day = cost_basis_strategy + .day_state(&borderline_context, date) + .expect("cost basis day state"); + assert_eq!( + cost_basis_strategy + .stop_take_action(&borderline_context, date, date, &borderline_day, symbol,) + .expect("cost basis stop check"), + (true, false), + ); } #[test] diff --git a/crates/fidc-core/src/platform_strategy_spec.rs b/crates/fidc-core/src/platform_strategy_spec.rs index ce8674c..d5ff9ec 100644 --- a/crates/fidc-core/src/platform_strategy_spec.rs +++ b/crates/fidc-core/src/platform_strategy_spec.rs @@ -1259,6 +1259,9 @@ fn parse_stop_take_reference_price_mode( "position_cost_basis" | "position_cost" | "execution_cost_basis" => { Ok(PlatformStopTakeReferencePriceMode::PositionCostBasis) } + "position_average_entry_price" | "position_avg_price" | "average_entry_price" => { + Ok(PlatformStopTakeReferencePriceMode::PositionAverageEntryPrice) + } "signal_day_post_adjusted_close" | "signal_post_adjusted_close" | "model_signal_post_adjusted_close" => { @@ -4497,6 +4500,24 @@ mod tests { ); } + #[test] + fn parses_position_average_entry_stop_take_reference_price_mode() { + let spec = serde_json::json!({ + "runtimeExpressions": { + "risk": { + "stopTakeReferencePriceMode": "position_average_entry_price" + } + } + }); + + let cfg = platform_expr_config_from_value("", "", &spec).expect("config"); + + assert_eq!( + cfg.stop_take_reference_price_mode, + PlatformStopTakeReferencePriceMode::PositionAverageEntryPrice + ); + } + #[test] fn rejects_unknown_stop_take_reference_price_mode() { let spec = serde_json::json!({