Files
NewQuant/futures_trading_strategies/rb/KalmanStrategy/KalmanStrategy.py

200 lines
9.6 KiB
Python
Raw Normal View History

2025-11-07 16:37:16 +08:00
import numpy as np
import pandas as pd
import talib
from collections import deque
from typing import Optional, Any, List, Dict
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
# =============================================================================
# 策略实现 (Adaptive Kalman Strategy)
# =============================================================================
class AdaptiveKalmanStrategy(Strategy):
"""
一个基于市场状态识别与结构化风控的自适应波段策略
本策略旨在解决动能策略在回调中吐出利润震荡中持续亏损的核心痛点
它通过动态识别市场状态在有利的环境下交易并使用结构化的方法持有仓位
1. 状态过滤: 使用ATR历史分位数构建波动率状态机在低波动率的
震荡区主动休眠只在高波动率的趋势区寻找机会
2. 动能催化: 沿用卡尔曼滤波器估算内在趋势当价格以ATR标准化的
力量逃逸出内在趋势时视为入场信号
3. 结构化持仓: 抛弃紧跟价格峰值的传统追踪止损改用基于卡尔曼滤波线
本身的结构化止损给趋势以充分的呼吸空间旨在持有
一个完整的波段避免在健康回调中被过早洗出
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
# --- 【信号层】卡尔曼滤波器参数 ---
kalman_process_noise: float = 0.01,
kalman_measurement_noise: float = 0.5,
# --- 【状态过滤】波动率状态机 ---
atr_period: int = 20,
atr_lookback: int = 100, # 用于计算ATR分位数的历史窗口
atr_percentile_threshold: float = 25.0, # ATR必须高于其历史的哪个百分位才认为是“趋势区”
# --- 【执行与风控】 ---
entry_threshold_atr: float = 2.5,
initial_stop_atr_multiplier: float = 2.0,
structural_stop_atr_multiplier: float = 2.5, # 结构化止损的ATR乘数
order_direction: Optional[List[str]] = None,
indicators: Optional[List[Indicator]] = None,
):
super().__init__(context, main_symbol, enable_log)
if order_direction is None: order_direction = ['BUY', 'SELL']
self.trade_volume = trade_volume
self.atr_period = atr_period
self.atr_lookback = atr_lookback
self.atr_percentile_threshold = atr_percentile_threshold
self.entry_threshold_atr = entry_threshold_atr
self.initial_stop_atr_multiplier = initial_stop_atr_multiplier
self.structural_stop_atr_multiplier = structural_stop_atr_multiplier
self.order_direction = order_direction
# 卡尔曼滤波器状态
self.Q = kalman_process_noise
self.R = kalman_measurement_noise
self.P = 1.0
self.x_hat = 0.0
self.kalman_initialized = False
# 状态机与持仓元数据
self._atr_history: deque = deque(maxlen=self.atr_lookback)
self.position_meta: Dict[str, Any] = {}
self.main_symbol = main_symbol
self.order_id_counter = 0
if indicators is None: indicators = [Empty(), Empty()]
self.indicators = indicators
self.log("AdaptiveKalmanStrategy Initialized.")
def on_init(self):
super().on_init()
self.cancel_all_pending_orders(self.main_symbol)
def on_open_bar(self, open_price: float, symbol: str):
self.symbol = symbol
bar_history = self.get_bar_history()
if len(bar_history) < max(self.atr_period, self.atr_lookback) + 2: return
# --- 数据预处理与指标计算 ---
highs = np.array([b.high for b in bar_history], dtype=float)
lows = np.array([b.low for b in bar_history], dtype=float)
closes = np.array([b.close for b in bar_history], dtype=float)
current_close = closes[-1]
current_atr = talib.ATR(highs, lows, closes, self.atr_period)[-1]
self._atr_history.append(current_atr)
if current_atr <= 0 or len(self._atr_history) < self.atr_lookback: return
# --- 卡尔曼滤波器更新 ---
if not self.kalman_initialized:
self.x_hat = current_close
self.kalman_initialized = True
x_hat_minus = self.x_hat
P_minus = self.P + self.Q
K = P_minus / (P_minus + self.R)
self.x_hat = x_hat_minus + K * (current_close - x_hat_minus)
self.P = (1 - K) * P_minus
kalman_price = self.x_hat
# --- 管理现有持仓 ---
position_volume = self.get_current_positions().get(self.symbol, 0)
if position_volume != 0:
self.manage_open_position(position_volume, bar_history[-1], current_atr, kalman_price)
return
# --- 【状态过滤】检查波动率状态机 ---
atr_threshold = np.percentile(list(self._atr_history), self.atr_percentile_threshold)
if current_atr < atr_threshold:
# self.log(f"Market in Chop Zone. ATR {current_atr:.4f} < Threshold {atr_threshold:.4f}. Standing by.")
return # 市场处于震荡区,休眠
# --- 评估新机会 ---
self.evaluate_entry_signal(bar_history[-1], kalman_price, current_atr)
def manage_open_position(self, volume: int, current_bar: Bar, current_atr: float, kalman_price: float):
"""采用两阶段止损系统:初始硬止损 + 结构化卡尔曼止损。"""
meta = self.position_meta.get(self.symbol)
if not meta: return
# 阶段一:始终检查初始硬止损
initial_stop_price = meta['initial_stop_price']
if (volume > 0 and current_bar.low <= initial_stop_price) or \
(volume < 0 and current_bar.high >= initial_stop_price):
self.log(f"Phase 1: Initial Stop Loss hit at {initial_stop_price:.4f}")
self.close_position("CLOSE_LONG" if volume > 0 else "CLOSE_SHORT", abs(volume))
return
# 阶段二:检查结构化卡尔曼止损
structural_stop_price = 0
if volume > 0: # 多头持仓
structural_stop_price = kalman_price - self.structural_stop_atr_multiplier * current_atr
# 确保结构止损不会比初始止损更差
structural_stop_price = max(structural_stop_price, initial_stop_price)
if current_bar.low <= structural_stop_price:
self.log(f"Phase 2: Structural Kalman Stop hit for LONG at {structural_stop_price:.4f}")
self.close_position("CLOSE_LONG", abs(volume))
elif volume < 0: # 空头持仓
structural_stop_price = kalman_price + self.structural_stop_atr_multiplier * current_atr
# 确保结构止损不会比初始止损更差
structural_stop_price = min(structural_stop_price, initial_stop_price)
if current_bar.high >= structural_stop_price:
self.log(f"Phase 2: Structural Kalman Stop hit for SHORT at {structural_stop_price:.4f}")
self.close_position("CLOSE_SHORT", abs(volume))
def evaluate_entry_signal(self, current_bar: Bar, kalman_price: float, current_atr: float):
"""在“趋势区”内,执行基于“动能催化”的入场逻辑。"""
deviation = current_bar.close - kalman_price
deviation_in_atr = deviation / current_atr
direction = None
if "BUY" in self.order_direction and deviation_in_atr > self.entry_threshold_atr and self.indicators[
0].is_condition_met(*self.get_indicator_tuple()):
direction = "SELL"
elif "SELL" in self.order_direction and deviation_in_atr < -self.entry_threshold_atr and self.indicators[
1].is_condition_met(*self.get_indicator_tuple()):
direction = "BUY"
if direction:
self.log(
f"Trend Zone Active! Momentum Catalyst Fired. Direction: {direction}. Deviation: {deviation_in_atr:.2f} ATRs.")
entry_price = current_bar.close
stop_loss_price = entry_price - self.initial_stop_atr_multiplier * current_atr if direction == "BUY" else entry_price + self.initial_stop_atr_multiplier * current_atr
meta = {'entry_price': entry_price, 'initial_stop_price': stop_loss_price}
self.send_market_order(direction, self.trade_volume, "OPEN", meta)
# --- 订单发送与仓位管理辅助函数 ---
def close_position(self, direction: str, volume: int):
self.send_market_order(direction, volume, offset="CLOSE")
if self.symbol in self.position_meta: del self.position_meta[self.symbol]
def send_market_order(self, direction: str, volume: int, offset: str, meta: Optional[Dict] = None):
if offset == "OPEN" and meta: self.position_meta[self.symbol] = meta
order_id = f"{self.symbol}_{direction}_MARKET_{self.order_id_counter}"
self.order_id_counter += 1
order = Order(id=order_id, symbol=self.symbol, direction=direction, volume=volume, price_type="MARKET",
submitted_time=self.get_current_time(), offset=offset)
self.send_order(order)
def on_rollover(self, old_symbol: str, new_symbol: str):
super().on_rollover(old_symbol, new_symbol)
self.position_meta = {}
self.kalman_initialized = False
self._atr_history.clear()
self.log("Rollover detected. All strategy states have been reset.")