import numpy as np import pandas as pd from typing import Optional, Dict, Any, List, Union # 假设这些是你项目中的模块 from src.core_data import Bar, Order from src.indicators.base_indicators import Indicator from src.indicators.indicators import Empty from src.strategies.base_strategy import Strategy from src.algo.TrendLine import calculate_latest_trendline_values class TrendlineHawkesStrategy(Strategy): """ 趋势线与霍克斯过程双重确认策略 (V4 - 终极性能版): - 霍克斯过程和滚动分位数都实现为高效的有状态增量计算。 - 使用固定长度的Numpy数组作为滚动窗口,避免Pandas.rolling的开销和不一致性。 """ def __init__( self, context: Any, main_symbol: str, # ... 参数与V3完全相同 ... trade_volume: int = 1, order_direction: Optional[List[str]] = None, reverse_logic: bool = False, trendline_n: int = 50, hawkes_kappa: float = 0.1, hawkes_lookback: int = 50, hawkes_entry_percent: float = 0.95, hawkes_exit_percent: float = 0.50, enable_log: bool = True, indicators: Union[Indicator, List[Indicator]] = None, ): super().__init__(context, main_symbol, enable_log) # ... 参数赋值与V3完全相同 ... self.main_symbol = main_symbol self.trade_volume = trade_volume self.order_direction = order_direction or ["BUY", "SELL"] self.reverse_logic = reverse_logic self.trendline_n = trendline_n self.hawkes_kappa = hawkes_kappa self.hawkes_lookback = hawkes_lookback self.hawkes_entry_percent = hawkes_entry_percent self.hawkes_exit_percent = hawkes_exit_percent self.pos_meta: Dict[str, Dict[str, Any]] = {} if indicators is None: indicators = [Empty(), Empty()] self.indicators = indicators # --- 【核心修改】状态缓存重构 --- # 只缓存上一个时间点的霍克斯强度值 (未缩放) self._last_hawkes_unscaled: float = 0.0 # 只维护一个固定长度的滚动窗口,用于计算分位数 self._hawkes_window: np.ndarray = np.array([], dtype=np.float64) # 衰减因子 self._hawkes_alpha = np.exp(-self.hawkes_kappa) # ... 日志与V3相同 ... def _initialize_state(self, initial_volumes: np.ndarray): """ 仅在策略开始时调用一次,用于填充初始的滚动窗口。 """ print("首次运行,正在初始化霍克斯状态和滚动窗口...") alpha = self._hawkes_alpha kappa = self.hawkes_kappa # 完整计算一次历史强度,只为填充窗口 temp_hawkes_history = np.zeros_like(initial_volumes, dtype=np.float64) if len(initial_volumes) > 0: temp_hawkes_history[0] = initial_volumes[0] if not np.isnan(initial_volumes[0]) else 0.0 for i in range(1, len(initial_volumes)): temp_hawkes_history[i] = temp_hawkes_history[i - 1] * alpha + ( initial_volumes[i] if not np.isnan(initial_volumes[i]) else 0.0) # 记录最后一个点的强度值,作为下一次增量计算的起点 self._last_hawkes_unscaled = temp_hawkes_history[-1] if len(temp_hawkes_history) > 0 else 0.0 # 用历史强度值的最后 hawkes_lookback 个点来填充滚动窗口 self._hawkes_window = (temp_hawkes_history * kappa)[-self.hawkes_lookback:] print("状态初始化完成。") def _update_state_incrementally(self, latest_volume: float): """ 【增量计算】在每个新的Bar上调用,更新强度值和滚动窗口。 """ # 1. 计算最新的霍克斯强度值 (未缩放) new_hawkes_unscaled = self._last_hawkes_unscaled * self._hawkes_alpha + ( latest_volume if not np.isnan(latest_volume) else 0.0) # 2. 更新上一个点的状态,为下一次计算做准备 self._last_hawkes_unscaled = new_hawkes_unscaled # 3. 将新的缩放后的强度值推入滚动窗口 new_hawkes_scaled = new_hawkes_unscaled * self.hawkes_kappa # np.roll 会高效地将数组元素移动,然后我们将新值放在第一个位置 # 这比 append + delete 的效率高得多 self._hawkes_window = np.roll(self._hawkes_window, -1) self._hawkes_window[-1] = new_hawkes_scaled def on_init(self): super().on_init() self.pos_meta.clear() # 重置状态 self._last_hawkes_unscaled = 0.0 self._hawkes_window = np.array([], dtype=np.float64) def on_open_bar(self, open_price: float, symbol: str): self.symbol = symbol bar_history = self.get_bar_history() min_bars_required = max(self.trendline_n + 2, self.hawkes_lookback + 2) if len(bar_history) < min_bars_required: return # --- 【核心修改】霍克斯过程的状态更新 --- # 检查是否是第一次运行 if self._hawkes_window.size == 0: initial_volumes = np.array([b.volume for b in bar_history], dtype=float) self._initialize_state(initial_volumes[:-1]) # 用到上一根bar为止的数据初始化 # 增量更新当前bar的状态 self._update_state_incrementally(float(bar_history[-1].volume)) # --- 后续逻辑使用更新后的状态进行计算 --- self.cancel_all_pending_orders(symbol) pos = self.get_current_positions().get(symbol, 0) # 【核心修改】直接在固定长度的窗口上计算分位数 # 这比pandas.rolling快几个数量级,且结果稳定 latest_hawkes_value = self._hawkes_window[-1] latest_hawkes_upper = np.quantile(self._hawkes_window, self.hawkes_entry_percent) latest_hawkes_lower = np.quantile(self._hawkes_window, self.hawkes_exit_percent) # 1. 平仓逻辑 (完全不变) meta = self.pos_meta.get(symbol) if meta and pos != 0: if latest_hawkes_value < latest_hawkes_lower: self.log(f"霍克斯出场信号...") # 日志简化 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: close_prices = np.array([b.close for b in bar_history]) prices_for_trendline = close_prices[-self.trendline_n - 1:-1] trend_upper, trend_lower = calculate_latest_trendline_values(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 and self.indicators[0].is_condition_met(*self.get_indicator_tuple()) lower_break_event = last_close < trend_lower and prev_close > trend_lower and self.indicators[1].is_condition_met(*self.get_indicator_tuple()) hawkes_confirmation = latest_hawkes_value > latest_hawkes_upper if hawkes_confirmation and (upper_break_event or lower_break_event): trade_direction = None if upper_break_event: trade_direction = "SELL" if self.reverse_logic else "BUY" elif lower_break_event: trade_direction = "BUY" if self.reverse_logic else "SELL" if trade_direction and trade_direction in self.order_direction: self.log(f"开仓信号确认...") # 日志简化 self.send_open_order(trade_direction, open_price, self.trade_volume) # send_open_order, send_market_order, on_rollover 等方法保持不变 # ... (代码省略,与之前版本相同) ... # send_open_order, send_market_order, on_rollover 等方法保持不变 def send_open_order(self, direction: str, entry_price: float, volume: int): 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} self.log(f"发送开仓订单: {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()