From 203e17ce87d789f92a326bde9c24ef62517b4a7f Mon Sep 17 00:00:00 2001 From: boris Date: Wed, 8 Jul 2026 03:27:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=8C=87=E6=95=B0=E6=8B=A9?= =?UTF-8?q?=E6=97=B6=E4=BD=BF=E7=94=A8=E4=BF=A1=E5=8F=B7=E6=8C=87=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 | 30 +++++++++- .../fidc-core/src/platform_strategy_spec.rs | 59 ++++++++++++++++++- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index 8093b61..7ba87a7 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -400,6 +400,8 @@ struct DayExpressionState { benchmark_ma10: f64, benchmark_ma20: f64, benchmark_ma30: f64, + signal_ma_short: f64, + signal_ma_long: f64, signal_ma5: f64, signal_ma10: f64, signal_ma20: f64, @@ -890,6 +892,8 @@ impl PlatformExprStrategy { | "signal_ma10" | "signal_ma20" | "signal_ma30" + | "signal_ma_short" + | "signal_ma_long" | "benchmark_ma5" | "benchmark_ma10" | "benchmark_ma20" @@ -2672,6 +2676,14 @@ impl PlatformExprStrategy { .data .market_decision_close_moving_average(date, &self.config.signal_symbol, 5) .unwrap_or(benchmark_ma5); + let signal_ma_short = ctx + .data + .market_decision_close_moving_average( + date, + &self.config.signal_symbol, + self.config.benchmark_short_ma_days, + ) + .unwrap_or(signal_ma5); let signal_ma10 = ctx .data .market_decision_close_moving_average(date, &self.config.signal_symbol, 10) @@ -2684,6 +2696,14 @@ impl PlatformExprStrategy { .data .market_decision_close_moving_average(date, &self.config.signal_symbol, 30) .unwrap_or(benchmark_ma30); + let signal_ma_long = ctx + .data + .market_decision_close_moving_average( + date, + &self.config.signal_symbol, + self.config.benchmark_long_ma_days, + ) + .unwrap_or(signal_ma30); let account = ctx.account(); let cash = account.cash; let market_value = account.market_value; @@ -2708,6 +2728,8 @@ impl PlatformExprStrategy { benchmark_ma10, benchmark_ma20, benchmark_ma30, + signal_ma_short, + signal_ma_long, signal_ma5, signal_ma10, signal_ma20, @@ -3129,6 +3151,8 @@ impl PlatformExprStrategy { scope.push("signal_ma5", day.signal_ma5); scope.push("signal_ma10", day.signal_ma10); scope.push("signal_ma20", day.signal_ma20); + scope.push("signal_ma_short", day.signal_ma_short); + scope.push("signal_ma_long", day.signal_ma_long); scope.push("benchmark_ma5", day.benchmark_ma5); scope.push("benchmark_ma10", day.benchmark_ma10); scope.push("benchmark_ma20", day.benchmark_ma20); @@ -3256,6 +3280,8 @@ impl PlatformExprStrategy { day_factors.insert("signal_ma10".into(), Dynamic::from(day.signal_ma10)); day_factors.insert("signal_ma20".into(), Dynamic::from(day.signal_ma20)); day_factors.insert("signal_ma30".into(), Dynamic::from(day.signal_ma30)); + day_factors.insert("signal_ma_short".into(), Dynamic::from(day.signal_ma_short)); + day_factors.insert("signal_ma_long".into(), Dynamic::from(day.signal_ma_long)); day_factors.insert("benchmark_ma5".into(), Dynamic::from(day.benchmark_ma5)); day_factors.insert("benchmark_ma10".into(), Dynamic::from(day.benchmark_ma10)); day_factors.insert("benchmark_ma20".into(), Dynamic::from(day.benchmark_ma20)); @@ -9272,8 +9298,8 @@ impl Strategy for PlatformExprStrategy { "platform_expr signal={} last={:.2} ma_short={:.2} ma_long={:.2} band={:.2}-{:.2} tr={:.2}", self.config.signal_symbol, day.signal_close, - day.benchmark_ma_short, - day.benchmark_ma_long, + day.signal_ma_short, + day.signal_ma_long, band_low, band_high, trading_ratio diff --git a/crates/fidc-core/src/platform_strategy_spec.rs b/crates/fidc-core/src/platform_strategy_spec.rs index 20def52..a255472 100644 --- a/crates/fidc-core/src/platform_strategy_spec.rs +++ b/crates/fidc-core/src/platform_strategy_spec.rs @@ -1219,6 +1219,17 @@ fn stock_volume_ma_expr(days: usize) -> String { } } +fn normalize_index_throttle_exposure_expr(expr: &str) -> String { + let mut normalized = expr.to_string(); + for suffix in ["short", "long", "5", "10", "20", "30"] { + normalized = normalized.replace( + &format!("benchmark_ma{suffix}"), + &format!("signal_ma{suffix}"), + ); + } + normalized +} + fn infer_expression_windows( cfg: &mut PlatformExprStrategyConfig, benchmark_short_explicit: bool, @@ -1230,6 +1241,7 @@ fn infer_expression_windows( let mut benchmark_days = Vec::new(); for expr in [&cfg.exposure_expr, &cfg.buy_scale_expr] { benchmark_days.extend(prefixed_ma_lookbacks(expr, "benchmark_ma")); + benchmark_days.extend(prefixed_ma_lookbacks(expr, "signal_ma")); benchmark_days.extend(rolling_mean_lookbacks(expr, "benchmark_close")); } let benchmark_days = sorted_unique_positive(benchmark_days); @@ -1539,7 +1551,16 @@ pub fn platform_expr_config_from_spec( .as_ref() .filter(|value| !value.trim().is_empty()) { - cfg.exposure_expr = expr.clone(); + cfg.exposure_expr = if spec + .engine_config + .as_ref() + .and_then(|engine| engine.index_throttle.as_ref()) + .is_some() + { + normalize_index_throttle_exposure_expr(expr) + } else { + expr.clone() + }; } if let Some(expr) = risk .stop_loss_expr @@ -1715,7 +1736,7 @@ pub fn platform_expr_config_from_spec( let defensive = index_throttle.defensive_exposure.unwrap_or(0.5); let full = index_throttle.full_exposure.unwrap_or(1.0); cfg.exposure_expr = format!( - "benchmark_ma_short < benchmark_ma_long * {} ? {} : {}", + "signal_ma_short < signal_ma_long * {} ? {} : {}", ratio, defensive, full ); } @@ -2963,6 +2984,40 @@ mod tests { assert_eq!(cfg.stock_long_ma_days, 21); } + #[test] + fn index_throttle_uses_signal_ma_not_performance_benchmark_ma() { + let spec = serde_json::json!({ + "signalSymbol": "000852.SH", + "benchmark": { + "instrumentId": "932000.CSI" + }, + "engineConfig": { + "profileName": "aiquant", + "indexThrottle": { + "shortDays": 10, + "longDays": 30, + "defensiveExposure": 0.5, + "fullExposure": 1.0 + } + }, + "runtimeExpressions": { + "risk": { + "exposureExpr": "if benchmark_ma10 > benchmark_ma30 { 1.0 } else { 0.5 }" + } + } + }); + + let cfg = platform_expr_config_from_value("strategy88", "", &spec).expect("config"); + + assert_eq!(cfg.signal_symbol, "000852.SH"); + assert_eq!( + cfg.exposure_expr, + "if signal_ma10 > signal_ma30 { 1.0 } else { 0.5 }" + ); + assert_eq!(cfg.benchmark_short_ma_days, 10); + assert_eq!(cfg.benchmark_long_ma_days, 30); + } + #[test] fn parses_daily_schedule_time_for_aiquant_execution_quotes() { let spec = serde_json::json!({