Files
NewQuant/futures_trading_strategies/MA/KalmanStrategy/KalmanStrategy2.py

264 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, ADX
from src.strategies.base_strategy import Strategy
# =============================================================================
# 策略实现 (Dual-Mode Kalman Strategy)
# =============================================================================
class DualModeKalmanStrategy(Strategy):
"""
一个内置两种相反交易逻辑(趋势跟踪 vs. 均值回归)的自适应策略。
本策略旨在通过一个核心参数 `strategy_mode`,在两种市场范式间切换,
以适应不同品种的内在“性格”。
人格1: 'TREND' (趋势模式)
- 哲学: 价格的强力突破会引发自我强化的趋势。
- 入场: 价格向上/下“逃逸”卡尔曼线 -> 做多/做空。
- 出场: 使用结构化卡尔曼止损,让利润奔跑。
人格2: 'REVERSION' (均值回归模式)
- 哲学: 价格的极端偏离是不可持续的,终将回归均值。
- 入场: 价格向上/下极端偏离(超买/超卖) -> 做空/做多。
- 出场: 当价格回归至卡爾曼線時获利了结。
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
# --- 【核心人格切换】 ---
strategy_mode: str = 'TREND', # 可选: 'TREND' 或 'REVERSION'
# --- 【通用参数】 ---
kalman_process_noise: float = 0.01,
kalman_measurement_noise: float = 0.5,
atr_period: int = 20,
atr_lookback: int = 100,
atr_percentile_threshold: float = 25.0,
entry_threshold_atr: float = 2.5,
initial_stop_atr_multiplier: float = 2.0,
# --- 【趋势模式专用】 ---
structural_stop_atr_multiplier: float = 2.5,
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']
# --- 参数验证与赋值 ---
if strategy_mode.upper() not in ['TREND', 'REVERSION']:
raise ValueError(f"strategy_mode must be 'TREND' or 'REVERSION', but got {strategy_mode}")
self.strategy_mode = strategy_mode.upper()
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.context.load_state()
self.main_symbol = main_symbol
self.order_id_counter = 0
if indicators is None: indicators = [Empty(), Empty()]
self.indicators = indicators
self.log(f"DualModeKalmanStrategy Initialized with Personality: [{self.strategy_mode}]")
def on_init(self):
super().on_init()
self.cancel_all_pending_orders(self.main_symbol)
self.position_meta = self.context.load_state()
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
self.cancel_all_pending_orders(symbol)
# --- 通用数据计算 ---
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_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 = closes[-1]
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 * (closes[-1] - x_hat_minus)
self.P = (1 - K) * P_minus
kalman_price = self.x_hat
# --- 分模式管理持仓 ---
position_volume = self.get_current_positions().get(self.symbol, 0)
meta = self.position_meta.get(symbol)
if position_volume != 0 and not meta:
self.log(f"警告:检测到实际持仓({position_volume})与策略状态(无记录)不一致!"
f"可能由状态加载失败导致。将强制平仓以同步状态。", level='WARNING')
direction_to_close = "CLOSE_LONG" if position_volume > 0 else "CLOSE_SHORT"
self.send_market_order(direction_to_close, abs(position_volume), 'CLOSE')
return
if position_volume == 0 and meta:
self.log(f"信息:检测到策略状态({meta.get('direction')})与实际持仓(0)不一致meta:{meta}"
f"可能是外部平仓导致。正在清理过时状态。", level='INFO')
new_pos_meta = {k: v for k, v in self.position_meta.items() if k != symbol}
self.position_meta = new_pos_meta
self.save_state(new_pos_meta)
if not self.trading:
return
if position_volume != 0:
self.log(f'manage_open_position')
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: return
# --- 分模式评估新机会 ---
self.log(f'evaluate_entry_signal')
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"Initial Stop Loss hit at {initial_stop_price:.4f}")
self.close_position("CLOSE_LONG" if volume > 0 else "CLOSE_SHORT", abs(volume))
return
# --- 分模式出场逻辑 ---
if self.strategy_mode == 'TREND':
# 【趋势模式】: 使用结构化卡尔曼止损
stop_price = 0
if volume > 0:
stop_price = max(kalman_price - self.structural_stop_atr_multiplier * current_atr, initial_stop_price)
self.log(f'stop_price: {stop_price:.4f}, kalman_price: {kalman_price:.4f}')
if current_bar.low <= stop_price:
self.log(f"TREND Mode: Structural Stop hit for LONG at {stop_price:.4f}")
self.close_position("CLOSE_LONG", abs(volume))
else: # volume < 0
stop_price = min(kalman_price + self.structural_stop_atr_multiplier * current_atr, initial_stop_price)
self.log(f'stop_price: {stop_price:.4f}, kalman_price: {kalman_price:.4f}')
if current_bar.high >= stop_price:
self.log(f"TREND Mode: Structural Stop hit for SHORT at {stop_price:.4f}")
self.close_position("CLOSE_SHORT", abs(volume))
elif self.strategy_mode == 'REVERSION':
# 【回归模式】: 检查是否触及均值作为止盈
if volume > 0:
stop_price = max(kalman_price - self.structural_stop_atr_multiplier * current_atr, initial_stop_price)
self.log(f'stop_price: {stop_price:.4f}, kalman_price: {kalman_price:.4f}')
if current_bar.low <= stop_price:
self.log(f"TREND Mode: Structural Stop hit for LONG at {stop_price:.4f}")
self.close_position("CLOSE_LONG", abs(volume))
else: # volume < 0
stop_price = min(kalman_price + self.structural_stop_atr_multiplier * current_atr, initial_stop_price)
self.log(f'stop_price: {stop_price:.4f}, kalman_price: {kalman_price:.4f}')
if current_bar.high >= stop_price:
self.log(f"TREND Mode: Structural Stop hit for SHORT at {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
self.log(f'deviation_in_atr: {deviation_in_atr:.4f}')
direction = None
if self.strategy_mode == 'TREND':
# 【趋势模式】入场逻辑
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 = "BUY"
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 = "SELL"
elif self.strategy_mode == 'REVERSION':
# 【回归模式】入场逻辑 (完全相反)
if "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 = "SELL" # 价格超买 -> 做空
elif "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 = "BUY" # 价格超卖 -> 做多
if direction:
self.log(
f"{self.strategy_mode} Mode: Catalyst Fired. Direction: {direction}. Deviation: {deviation_in_atr:.2f} ATRs., entry_threshold_atr: {self.entry_threshold_atr}")
entry_price = current_bar.close + (1 if direction == "BUY" else -1)
stop_loss_price = entry_price - self.initial_stop_atr_multiplier * current_atr if direction in ["BUY",
"CLOSE_SHORT"] else entry_price + self.initial_stop_atr_multiplier * current_atr
# stop_loss_price = min(stop_loss_price, entry_price - 30) if direction == 'BUY' else max(stop_loss_price, entry_price + 30)
meta = {'entry_price': entry_price, 'initial_stop_price': stop_loss_price}
self.send_limit_order(entry_price, direction, self.trade_volume, "OPEN", meta)
self.save_state(self.position_meta)
# ... (辅助函数 close_position, send_market_order, on_rollover 保持不变) ...
def close_position(self, direction: str, volume: int):
self.send_market_order(direction, volume, offset="CLOSE")
if self.symbol in self.position_meta:
self.position_meta = {}
self.save_state(self.position_meta)
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 send_limit_order(self, limit_price: float, 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="LIMIT",
submitted_time=self.get_current_time(), offset=offset, limit_price=limit_price)
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.")