import numpy as np import pandas as pd from typing import Optional, Dict, Any, List # 假设这些是你项目中的模块 from src.core_data import Bar, Order from src.strategies.base_strategy import Strategy from src.algo.TrendLine import calculate_latest_trendline_values_v2 class DualModeVolumeIntensityStrategy(Strategy): """ 双模式成交量强度策略 (V7 - 无状态高效版): - 【核心命名】将 "霍克斯过程" 概念替换为更准确的 "成交量强度 (Volume Intensity)"。 - 【核心优化】使用 NumPy 的卷积操作 (np.convolve) 代替循环,高效地计算滑动窗口的成交量强度,彻底消除路径依赖,同时保证高性能。 - 策略行为在任何时间点只与最近的固定窗口数据相关,保证了回测与实盘的绝对一致性。 - 完整保留了双模式(趋势/回归)和冲突解决机制。 """ def __init__( self, context: Any, main_symbol: str, trade_volume: int = 1, trend_params: Dict[str, Any] = None, reversion_params: Dict[str, Any] = None, enabled_modes: Optional[List[str]] = None, conflict_resolution: str = 'TREND_PRIORITY', enable_log: bool = True, ): super().__init__(context, main_symbol, enable_log) self.main_symbol = main_symbol self.trade_volume = trade_volume default_params = { "order_direction": ["BUY", "SELL"], "trendline_n": 50, # 【命名修改】参数名也同步调整 "intensity_kappa": 0.1, # 衰减因子 "intensity_lookback": 50, # 回看窗口 "intensity_entry_percent": 0.95, "intensity_exit_percent": 0.50, } self.trend_params = default_params.copy() if trend_params: self.trend_params.update(trend_params) self.reversion_params = default_params.copy() if reversion_params: self.reversion_params.update(reversion_params) self.enabled_modes = enabled_modes or ['TREND', 'REVERSION'] self.conflict_resolution = conflict_resolution self.pos_meta: Dict[str, Dict[str, Any]] = {} print("DualModeVolumeIntensityStrategy (V7) initialized.") print(f"Enabled modes: {self.enabled_modes}") print(f"Conflict resolution: {self.conflict_resolution}") print("Volume Intensity calculation is STATELESS and EFFICIENT (using np.convolve).") # --- 【核心修改】使用卷积实现无状态、高效的窗口计算 --- def _calculate_volume_intensity_window(self, volumes: np.ndarray, kappa: float, lookback: int) -> np.ndarray: """ 使用一维卷积高效计算成交量强度窗口。 这是一个纯函数,无任何副作用和路径依赖。 :param volumes: 历史成交量序列。长度应为 2*lookback - 1。 :param kappa: 强度衰减因子。 :param lookback: 强度计算的回看窗口,也是返回的强度窗口的长度。 :return: 一个长度为 `lookback` 的成交量强度值窗口。 """ # 权重是指数衰减的,越近的成交量权重越高 # weights = [exp(-kappa*lookback), ..., exp(-kappa*1)] weights = np.exp(-kappa * np.arange(lookback, 0, -1)) # 使用'valid'模式的卷积,本质上是一个滑动的点积运算 # 结果的长度将是 len(volumes) - len(weights) + 1 = (2*lookback-1) - lookback + 1 = lookback intensity_unscaled = np.convolve(volumes, weights, mode='valid') return intensity_unscaled * kappa def on_init(self): super().on_init() self.pos_meta.clear() def on_open_bar(self, open_price: float, symbol: str): bar_history = self.get_bar_history() # 确保有足够的数据来满足最长的回看需求 # 趋势线需要 trendline_n + 1 个价格 # 强度计算需要 2 * lookback - 1 个成交量 min_bars_required = max( self.trend_params['trendline_n'] + 2, 2 * self.trend_params['intensity_lookback'], self.reversion_params['trendline_n'] + 2, 2 * self.reversion_params['intensity_lookback'] ) if len(bar_history) < min_bars_required: return all_volumes = np.array([b.volume for b in bar_history], dtype=float) # 为每个模式计算其独立的成交量强度窗口 trend_intensity_window = np.array([], dtype=np.float64) if 'TREND' in self.enabled_modes: lookback = self.trend_params['intensity_lookback'] # 截取计算所需的、正确长度的成交量数据 volumes_slice = all_volumes[-(2 * lookback - 1):] trend_intensity_window = self._calculate_volume_intensity_window( volumes_slice, self.trend_params['intensity_kappa'], lookback ) reversion_intensity_window = np.array([], dtype=np.float64) if 'REVERSION' in self.enabled_modes: lookback = self.reversion_params['intensity_lookback'] volumes_slice = all_volumes[-(2 * lookback - 1):] reversion_intensity_window = self._calculate_volume_intensity_window( volumes_slice, self.reversion_params['intensity_kappa'], lookback ) self.cancel_all_pending_orders(symbol) pos = self.get_current_positions().get(symbol, 0) # --- 1. 平仓逻辑 --- meta = self.pos_meta.get(symbol) if meta and pos != 0: strategy_mode = meta.get('strategy_mode') params_to_use = self.trend_params if strategy_mode == 'TREND' else self.reversion_params window_to_use = trend_intensity_window if strategy_mode == 'TREND' else reversion_intensity_window if window_to_use.size > 0: latest_intensity_value = window_to_use[-1] exit_threshold = np.quantile(window_to_use, params_to_use['intensity_exit_percent']) if latest_intensity_value < exit_threshold: self.log(f"[{strategy_mode}模式] 成交量强度平仓信号 (市场热度下降),平仓。") self.send_market_order("CLOSE_LONG" if meta['direction'] == "BUY" else "CLOSE_SHORT", abs(pos)) del self.pos_meta[symbol] return # --- 2. 开仓逻辑 --- if pos == 0: trend_signal = self._calculate_entry_signal( 'TREND', bar_history, self.trend_params, trend_intensity_window ) if 'TREND' in self.enabled_modes else None reversion_signal = self._calculate_entry_signal( 'REVERSION', bar_history, self.reversion_params, reversion_intensity_window ) if 'REVERSION' in self.enabled_modes else None # ... 冲突解决和下单逻辑保持不变 ... final_direction, winning_mode = self.resolve_signals(trend_signal, reversion_signal) if final_direction and winning_mode: params_to_use = self.trend_params if winning_mode == 'TREND' else self.reversion_params if final_direction in params_to_use['order_direction']: self.log(f"[{winning_mode}模式] 开仓信号确认: {final_direction}") self.send_open_order(final_direction, open_price, self.trade_volume, winning_mode) def _calculate_entry_signal(self, mode: str, bar_history: List[Bar], params: Dict, intensity_window: np.ndarray) -> \ Optional[str]: if intensity_window.size == 0: return None # 1. 成交量强度确认 latest_intensity_value = intensity_window[-1] entry_threshold = np.quantile(intensity_window, params['intensity_entry_percent']) intensity_confirmation = latest_intensity_value > entry_threshold if not intensity_confirmation: return None # 2. 趋势线突破事件 close_prices = np.array([b.close for b in bar_history]) prices_for_trendline = close_prices[-params['trendline_n'] - 1:-1] trend_upper, trend_lower = calculate_latest_trendline_values_v2(prices_for_trendline) if trend_upper is not None and trend_lower is not None: prev_close = bar_history[-2].close last_close = bar_history[-1].close upper_break_event = last_close > trend_upper and prev_close < trend_upper lower_break_event = last_close < trend_lower and prev_close > trend_lower if upper_break_event: return "BUY" if mode == 'TREND' else "SELL" if lower_break_event: return "SELL" if mode == 'TREND' else "BUY" return None def resolve_signals(self, trend_signal: Optional[str], reversion_signal: Optional[str]) -> (Optional[str], Optional[str]): if trend_signal and reversion_signal: self.log(f"信号冲突:趋势模式 ({trend_signal}) vs 回归模式 ({reversion_signal})") if self.conflict_resolution == 'TREND_PRIORITY': return trend_signal, 'TREND' elif self.conflict_resolution == 'REVERSION_PRIORITY': return reversion_signal, 'REVERSION' else: self.log("冲突解决策略为'NONE',本次不开仓。") return None, None elif trend_signal: return trend_signal, 'TREND' elif reversion_signal: return reversion_signal, 'REVERSION' return None, None # send_open_order, send_market_order, on_rollover 等辅助函数保持不变 # ... (代码与之前版本相同) def send_open_order(self, direction: str, entry_price: float, volume: int, strategy_mode: str): current_time = self.get_current_time() order_id = f"{self.symbol}_{direction}_{current_time.strftime('%Y%m%d%H%M%S')}" order_direction = "BUY" if direction == "BUY" else "SELL" order = Order(id=order_id, symbol=self.symbol, direction=order_direction, volume=volume, price_type="MARKET", submitted_time=current_time, offset="OPEN") self.send_order(order) self.pos_meta[self.symbol] = { "direction": direction, "volume": volume, "entry_price": entry_price, "strategy_mode": strategy_mode } self.log(f"发送开仓订单 ({strategy_mode}): {direction} {volume}手 @ Market Price (执行价约 {entry_price:.2f})") def send_market_order(self, direction: str, volume: int): current_time = self.get_current_time() order_id = f"{self.symbol}_{direction}_{current_time.strftime('%Y%m%d%H%M%S')}" order = Order(id=order_id, symbol=self.symbol, direction=direction, volume=volume, price_type="MARKET", submitted_time=current_time, offset="CLOSE") self.send_order(order) self.log(f"发送平仓订单: {direction} {volume}手 @ Market Price") def on_rollover(self, old_symbol: str, new_symbol: str): super().on_rollover(old_symbol, new_symbol) self.cancel_all_pending_orders(new_symbol) self.pos_meta.clear()