Files
NewQuant/src/strategies/TrendlineBreakoutStrategy/DualModeTrendlineHawkesStrategy2.py

232 lines
11 KiB
Python

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()