From bbe60537ff22b04841f65cca1784f84186073609 Mon Sep 17 00:00:00 2001 From: boris Date: Mon, 11 May 2026 20:13:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DMA=E8=BF=87=E6=BB=A4=E5=99=A8?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E9=94=99=E8=AF=AF=E5=92=8C=E6=88=90=E4=BA=A4?= =?UTF-8?q?=E9=87=8F=E8=BF=87=E6=BB=A4=E5=99=A8=E7=AD=96=E7=95=A5=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复MA过滤器:第二个比较添加 * rsi_rate (ma10 * rsi_rate > ma30) - 修复成交量过滤器:使用contains匹配策略名称而非精确匹配 - 添加调试日志用于诊断MA过滤问题 - 同时修复strategy.rs和platform_strategy_spec.rs中的逻辑 --- crates/bt-demo/src/main.rs | 51 +++++++++++++++ .../fidc-core/src/platform_strategy_spec.rs | 4 +- crates/fidc-core/src/strategy.rs | 64 ++++++++++++++++++- 3 files changed, 116 insertions(+), 3 deletions(-) diff --git a/crates/bt-demo/src/main.rs b/crates/bt-demo/src/main.rs index 3a30c5c..15238b6 100644 --- a/crates/bt-demo/src/main.rs +++ b/crates/bt-demo/src/main.rs @@ -100,6 +100,57 @@ fn main() -> Result<(), Box> { let mut engine = BacktestEngine::new(data, strategy, broker, config); engine.run()? } + "aiquant-v104" => { + let mut strategy_cfg = OmniMicroCapConfig::aiquant_v104(); + if let Ok(signal_symbol) = std::env::var("FIDC_BT_SIGNAL_SYMBOL") { + if !signal_symbol.trim().is_empty() { + strategy_cfg.benchmark_signal_symbol = signal_symbol; + } + } + if let Some(date) = debug_date { + let eligible = data.eligible_universe_on(date); + eprintln!( + "DEBUG eligible_universe_on {} count={}", + date, + eligible.len() + ); + for row in eligible.iter().take(20) { + eprintln!(" {} {:.6}", row.symbol, row.market_cap_bn); + } + let mut debug_strategy = OmniMicroCapStrategy::new(strategy_cfg.clone()); + let debug_subscriptions = BTreeSet::new(); + let decision = debug_strategy.on_day(&StrategyContext { + execution_date: date, + decision_date: date, + decision_index: 1, + data: &data, + portfolio: &PortfolioState::new(20_000.0), + futures_account: None, + open_orders: &[], + dynamic_universe: None, + subscriptions: &debug_subscriptions, + process_events: &[], + active_process_event: None, + active_datetime: None, + order_events: &[], + fills: &[], + })?; + eprintln!("DEBUG notes={:?}", decision.notes); + eprintln!("DEBUG diagnostics={:?}", decision.diagnostics); + return Ok(()); + } + config.decision_lag_trading_days = decision_lag.unwrap_or(1); + config.execution_price_field = execution_price.unwrap_or(PriceField::Close); + config.initial_cash = initial_cash.unwrap_or(20_000.0); + let strategy = OmniMicroCapStrategy::new(strategy_cfg); + let broker = BrokerSimulator::new_with_execution_price( + ChinaAShareCostModel::default(), + ChinaEquityRuleHooks::default(), + config.execution_price_field, + ); + let mut engine = BacktestEngine::new(data, strategy, broker, config); + engine.run()? + } _ => { let mut strategy_cfg = OmniMicroCapConfig::omni_microcap(); if let Ok(signal_symbol) = std::env::var("FIDC_BT_SIGNAL_SYMBOL") { diff --git a/crates/fidc-core/src/platform_strategy_spec.rs b/crates/fidc-core/src/platform_strategy_spec.rs index ada8137..cd49a22 100644 --- a/crates/fidc-core/src/platform_strategy_spec.rs +++ b/crates/fidc-core/src/platform_strategy_spec.rs @@ -636,8 +636,8 @@ pub fn platform_expr_config_from_spec( if let Some(stock_ma_filter) = engine.stock_ma_filter.as_ref() { let ratio = stock_ma_filter.rsi_rate.unwrap_or(1.0001); cfg.stock_filter_expr = format!( - "stock_ma_short > stock_ma_mid * {} && stock_ma_mid > stock_ma_long", - ratio + "stock_ma_short > stock_ma_mid * {} && stock_ma_mid * {} > stock_ma_long", + ratio, ratio ); } if let Some(index_throttle) = engine.index_throttle.as_ref() { diff --git a/crates/fidc-core/src/strategy.rs b/crates/fidc-core/src/strategy.rs index c45ba5e..728359e 100644 --- a/crates/fidc-core/src/strategy.rs +++ b/crates/fidc-core/src/strategy.rs @@ -1547,6 +1547,29 @@ impl OmniMicroCapConfig { } } + pub fn aiquant_v104() -> Self { + Self { + strategy_name: "aiquant-v1.0.4".to_string(), + refresh_rate: 120, + stocknum: 5, + xs: 3.0 / 500.0, + base_index_level: 2000.0, + base_cap_floor: 7.0, + cap_span: 25.0, + benchmark_signal_symbol: "000852.SH".to_string(), + benchmark_short_ma_days: 5, + benchmark_long_ma_days: 20, + stock_short_ma_days: 5, + stock_mid_ma_days: 10, + stock_long_ma_days: 30, + rsi_rate: 1.0001, + trade_rate: 0.5, + stop_loss_ratio: 0.92, + take_profit_ratio: 1.16, + skip_month_day_ranges: Vec::new(), + } + } + fn in_skip_window(&self, date: NaiveDate) -> bool { let month = date.month(); let day = date.day(); @@ -2175,7 +2198,46 @@ impl OmniMicroCapStrategy { return false; }; - ma_short > ma_mid * self.config.rsi_rate && ma_mid > ma_long + // MA filter: ma_short > ma_mid * rsi_rate && ma_mid * rsi_rate > ma_long + let ma_pass = ma_short > ma_mid * self.config.rsi_rate && ma_mid * self.config.rsi_rate > ma_long; + + // Debug logging for first few stocks + static DEBUG_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + let count = DEBUG_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + if count < 10 { + eprintln!("[DEBUG MA] {} date={} ma5={:.4} ma10={:.4} ma30={:.4} rsi_rate={:.6} pass={} (ma5 > ma10*rsi={:.4}? {} && ma10*rsi > ma30={:.4}? {})", + symbol, date, ma_short, ma_mid, ma_long, self.config.rsi_rate, ma_pass, + ma_mid * self.config.rsi_rate, ma_short > ma_mid * self.config.rsi_rate, + ma_long, ma_mid * self.config.rsi_rate > ma_long); + } + + if !ma_pass { + return false; + } + + // Volume filter: V5 < V60 (applied for omni_microcap strategies) + if self.config.strategy_name.contains("aiquant") || self.config.strategy_name.contains("AiQuant") || self.config.strategy_name.contains("omni") { + let Some(volume_ma5) = ctx.data.market_decision_volume_moving_average( + date, + symbol, + 5, + ) else { + return false; + }; + let Some(volume_ma60) = ctx.data.market_decision_volume_moving_average( + date, + symbol, + 60, + ) else { + return false; + }; + + if volume_ma5 >= volume_ma60 { + return false; + } + } + + true } fn special_name(&self, ctx: &StrategyContext<'_>, symbol: &str) -> bool {