新增实盘策略:ITrendStrategy(SA)

This commit is contained in:
2026-01-25 23:26:03 +08:00
parent 4a37652269
commit fa4749b02e
66 changed files with 44943 additions and 5961 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,160 @@
import numpy as np
import talib
from typing import Optional, Any, List, Dict
from src.core_data import Bar, Order
from src.strategies.base_strategy import Strategy
class KalmanMeanReversion(Strategy):
"""
改进版卡尔曼均值回归策略
1. 以 Slow Line 为止盈目标 (真正的均值回归)
2. 严格的 ATR 止损 (防止趋势爆发)
3. 时间衰减退出机制
4. 趋势斜率过滤
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
fast_sensitivity: float = 0.05, # 快线灵敏度
slow_sensitivity: float = 0.01, # 慢线灵敏度 (更平滑作为均值)
lookback_variance: int = 60,
atr_period: int = 20,
entry_threshold: float = 1.2, # 偏离多少个ATR入场
stop_loss_multiplier: float = 2.0, # 止损倍数
max_hold_bars: int = 30, # 最大持仓时间
indicator: Any = None,
):
super().__init__(context, main_symbol, enable_log)
self.trade_volume = trade_volume
self.fast_sensitivity = fast_sensitivity
self.slow_sensitivity = slow_sensitivity
self.lookback_variance = lookback_variance
self.atr_period = atr_period
self.entry_threshold = entry_threshold
self.stop_loss_multiplier = stop_loss_multiplier
self.max_hold_bars = max_hold_bars
self.indicator = indicator
# 状态变量
self.kf_fast = {'x': 0.0, 'P': 1.0}
self.kf_slow = {'x': 0.0, 'P': 1.0}
self.kalman_initialized = False
# 记录持仓信息
self.entry_price = 0.0
self.entry_bar_count = 0
self.order_id_counter = 0
def _update_kalman(self, state: dict, measurement: float, Q: float, R: float) -> float:
"""递归卡尔曼滤波更新"""
p_minus = state['P'] + Q
k_gain = p_minus / (p_minus + R)
state['x'] = state['x'] + k_gain * (measurement - state['x'])
state['P'] = (1 - k_gain) * p_minus
return state['x']
def on_open_bar(self, open_price: float, symbol: str):
bar_history = self.get_bar_history()
if len(bar_history) < 100:
return
self.cancel_all_pending_orders()
closes = np.array([b.close for b in bar_history], dtype=float)
last_price = closes[-1]
# 1. 计算自适应噪声 (基于滚动方差)
rolling_var = np.var(closes[-self.lookback_variance:])
r_base = rolling_var if rolling_var > 0 else 1.0
# 2. 初始化或更新卡尔曼滤波器
if not self.kalman_initialized:
self.kf_fast['x'] = self.kf_slow['x'] = last_price
self.kalman_initialized = True
return
# 快线追踪价格,慢线代表平衡位置
fast_line = self._update_kalman(self.kf_fast, last_price, r_base * self.fast_sensitivity, r_base)
slow_line = self._update_kalman(self.kf_slow, last_price, r_base * self.slow_sensitivity, r_base * 10.0)
# 3. 计算 ATR 和 偏离度
highs = np.array([b.high for b in bar_history], dtype=float)
lows = np.array([b.low for b in bar_history], dtype=float)
atr = talib.ATR(highs, lows, closes, self.atr_period)[-1]
if atr <= 0: return
# 计算价格偏离慢线的程度 (Z-Score 的变体)
diff_in_atr = (last_price - slow_line) / atr
# 4. 仓位逻辑
pos = self.get_current_positions().get(symbol, 0)
if pos == 0:
# --- 入场逻辑 ---
# 只有在外部指标允许且偏离度足够大时入场
can_trade = self.indicator is None or self.indicator.is_condition_met(*self.get_indicator_tuple())
if can_trade:
if diff_in_atr > self.entry_threshold:
# 超买,做空
self.execute_order(symbol, "SELL", "OPEN", last_price)
elif diff_in_atr < -self.entry_threshold:
# 超卖,做多
self.execute_order(symbol, "BUY", "OPEN", last_price)
else:
# --- 出场逻辑 ---
self.entry_bar_count += 1
is_long = pos > 0
should_close = False
exit_reason = ""
# A. 均值回归止盈:触碰或穿过慢线
if is_long and last_price >= slow_line:
should_close = True
exit_reason = "Take Profit: Reached Mean"
elif not is_long and last_price <= slow_line:
should_close = True
exit_reason = "Take Profit: Reached Mean"
# B. 严谨止损:背离程度进一步扩大
elif is_long and last_price < self.entry_price - self.stop_loss_multiplier * atr:
should_close = True
exit_reason = "Stop Loss: Deviation Too Large"
elif not is_long and last_price > self.entry_price + self.stop_loss_multiplier * atr:
should_close = True
exit_reason = "Stop Loss: Deviation Too Large"
# C. 时间退出:久攻不下,由于均值回归的时效性,超时即走
elif self.entry_bar_count >= self.max_hold_bars:
should_close = True
exit_reason = "Time Exit: Holding Too Long"
if should_close:
direction = "CLOSE_LONG" if is_long else "CLOSE_SHORT"
self.log(f"EXIT {direction}: Price={last_price}, Reason={exit_reason}")
self.execute_order(symbol, direction, "CLOSE", last_price)
def execute_order(self, symbol, direction, offset, price):
"""执行订单并更新状态"""
if offset == "OPEN":
self.entry_price = price
self.entry_bar_count = 0
order_id = f"{symbol}_{direction}_{self.order_id_counter}"
self.order_id_counter += 1
order = Order(
id=order_id,
symbol=symbol,
direction=direction,
volume=self.trade_volume,
price_type="MARKET",
offset=offset
)
self.send_order(order)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,190 @@
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.strategies.base_strategy import Strategy
import numpy as np
import talib
from typing import Optional, Any, List, Dict
from src.core_data import Bar, Order
from src.strategies.base_strategy import Strategy
class KalmanTrendFollower(Strategy):
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
# --- 显著降低灵敏度,过滤日内杂波 ---
fast_sensitivity: float = 0.05, # 调低:让快线也变得稳重
slow_sensitivity: float = 0.005, # 极低:慢线只代表大趋势方向
lookback_variance: int = 60, # 增加窗口,计算更稳定的市场噪声
# --- 趋势跟踪参数 ---
atr_period: int = 23,
entry_threshold: float = 0.5, # 差值需超过0.5倍ATR才考虑入场
trailing_stop_multiplier: float = 4.0, # 关键4倍ATR跟踪止损给趋势留足空间
structural_stop_multiplier: float = 1.0, # 价格破位慢线多少ATR才出场
indicator: Indicator = None,
):
super().__init__(context, main_symbol, enable_log)
self.trade_volume = trade_volume
# 参数初始化
self.fast_sensitivity = fast_sensitivity
self.slow_sensitivity = slow_sensitivity
self.lookback_variance = lookback_variance
self.atr_period = atr_period
self.entry_threshold = entry_threshold
self.trailing_stop_multiplier = trailing_stop_multiplier
self.structural_stop_multiplier = structural_stop_multiplier
self.indicator = indicator
# 状态变量
self.kf_fast = {'x': 0.0, 'P': 1.0}
self.kf_slow = {'x': 0.0, 'P': 1.0}
self.kalman_initialized = False
self.position_meta: Dict[str, Any] = {}
self.order_id_counter = 0
def _update_kalman(self, state: dict, measurement: float, Q: float, R: float) -> float:
p_minus = state['P'] + Q
k_gain = p_minus / (p_minus + R)
state['x'] = state['x'] + k_gain * (measurement - state['x'])
state['P'] = (1 - k_gain) * p_minus
return state['x']
def on_open_bar(self, open_price: float, symbol: str):
bar_history = self.get_bar_history()
if len(bar_history) < 100: return
self.cancel_all_pending_orders()
closes = np.array([b.close for b in bar_history], dtype=float)
last_price = closes[-1]
# 1. 动态计算卡尔曼参数
# 增加基础噪声 R使曲线更平滑
rolling_var = np.var(closes[-self.lookback_variance:])
r_base = rolling_var if rolling_var > 0 else 1.0
# 计算快慢线
if not self.kalman_initialized:
self.kf_fast['x'] = self.kf_slow['x'] = last_price
self.kalman_initialized = True
return
fast_line = self._update_kalman(self.kf_fast, last_price, r_base * self.fast_sensitivity, r_base)
slow_line = self._update_kalman(self.kf_slow, last_price, r_base * self.slow_sensitivity, r_base * 5.0)
# 2. 计算 ATR 和 趋势指标
highs = np.array([b.high for b in bar_history], dtype=float)
lows = np.array([b.low for b in bar_history], dtype=float)
atr = talib.ATR(highs, lows, closes, self.atr_period)[-1]
diff = fast_line - slow_line
diff_in_atr = diff / atr if atr > 0 else 0
# 3. 仓位管理逻辑
pos = self.get_current_positions().get(symbol, 0)
if pos == 0 and (self.indicator is None or self.indicator.is_condition_met(*self.get_indicator_tuple())):
# --- 入场逻辑:必须形成明显的发散 ---
if diff_in_atr > self.entry_threshold:
self.open_trade(symbol, "BUY", open_price, atr, slow_line)
elif diff_in_atr < -self.entry_threshold:
self.open_trade(symbol, "SELL", open_price, atr, slow_line)
else:
# --- 出场逻辑:保护肥尾收益 ---
self.manage_exit(symbol, pos, last_price, atr, slow_line)
def open_trade(self, symbol, direction, price, atr, slow_line):
# 记录入场时的最高/最低价,用于动态跟踪止损
meta = {
'entry_price': price,
'extreme_price': price, # 记录持仓期间到达过的最高(多头)或最低(空头)
'direction': direction,
'initial_atr': atr
}
self.send_limit_order(symbol, direction, price, self.trade_volume, "OPEN", meta)
self.log(f"TREND ENTRY {direction}: Price={price}, ATR={atr:.2f}")
def manage_exit(self, symbol, pos, price, atr, slow_line):
meta = self.position_meta.get(symbol)
if not meta: return
is_long = pos > 0
should_close = False
# 更新持仓期间的极端价格(用于计算吊灯止损)
if is_long:
meta['extreme_price'] = max(meta['extreme_price'], price)
# 吊灯止损位:最高价回落 N 倍 ATR
chandelier_stop = meta['extreme_price'] - self.trailing_stop_multiplier * atr
# 结构止损位:跌破慢速趋势线一定距离
structural_stop = slow_line - self.structural_stop_multiplier * atr
# 综合取较严的价格作为保护,但不轻易离场
if price < max(chandelier_stop, structural_stop):
should_close = True
exit_reason = "Trailing/Structural Break"
else:
meta['extreme_price'] = min(meta['extreme_price'], price)
chandelier_stop = meta['extreme_price'] + self.trailing_stop_multiplier * atr
structural_stop = slow_line + self.structural_stop_multiplier * atr
if price > min(chandelier_stop, structural_stop):
should_close = True
exit_reason = "Trailing/Structural Break"
if should_close:
direction = "CLOSE_LONG" if is_long else "CLOSE_SHORT"
self.log(f"EXIT {direction}: Price={price}, Reason={exit_reason}")
self.close_position(symbol, direction, abs(pos))
# (底层 send_market_order / close_position 同前,注意更新 state 时保留 meta['extreme_price'])
# --- 底层封装 ---
def send_market_order(self, symbol, direction, volume, offset, meta=None):
if offset == "OPEN": self.position_meta[symbol] = meta
order_id = f"{symbol}_{direction}_{self.order_id_counter}"
self.order_id_counter += 1
order = Order(id=order_id, symbol=symbol, direction=direction,
volume=volume, price_type="MARKET", offset=offset)
self.send_order(order)
self.save_state(self.position_meta)
def send_limit_order(self, symbol, direction, price, volume, offset, meta=None):
if offset == "OPEN": self.position_meta[symbol] = meta
order_id = f"{symbol}_{direction}_{self.order_id_counter}"
self.order_id_counter += 1
order = Order(id=order_id, symbol=symbol, direction=direction,
volume=volume, price_type="LIMIT", offset=offset, limit_price=price)
self.send_order(order)
self.save_state(self.position_meta)
def close_position(self, symbol, direction, volume):
order_id = f"{symbol}_{direction}_{self.order_id_counter}"
self.order_id_counter += 1
order = Order(id=order_id, symbol=symbol, direction=direction,
volume=volume, price_type="MARKET", offset="CLOSE")
self.send_order(order)
self.position_meta.pop(symbol, None)
self.save_state(self.position_meta)

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,118 @@
import multiprocessing
from typing import Tuple, Dict, Any, Optional
from src.analysis.result_analyzer import ResultAnalyzer
from src.backtest_engine import BacktestEngine
from src.data_manager import DataManager
# --- 单个回测任务函数 ---
# 这个函数将在每个独立的进程中运行,因此它必须是自包含的
def run_single_backtest(
combination: Tuple[float, float], # 传入当前参数组合
common_config: Dict[str, Any] # 传入公共配置 (如数据路径, 初始资金等)
) -> Optional[Dict[str, Any]]:
"""
运行单个参数组合的回测任务。
此函数将在一个独立的进程中执行。
"""
p1_value, p2_value = combination
# 从 common_config 中获取必要的配置
symbol = common_config['symbol']
data_path = common_config['data_path']
initial_capital = common_config['initial_capital']
slippage_rate = common_config['slippage_rate']
commission_rate = common_config['commission_rate']
start_time = common_config['start_time']
end_time = common_config['end_time']
roll_over_mode = common_config['roll_over_mode']
# bar_duration_seconds = common_config['bar_duration_seconds'] # 如果DataManager需要可以再传
param1_name = common_config['param1_name']
param2_name = common_config['param2_name']
# 每个进程内部独立初始化 DataManager 和 BacktestEngine
# 确保每个进程有自己的数据副本和模拟状态
data_manager = DataManager(
file_path=data_path,
symbol=symbol,
# bar_duration_seconds=bar_duration_seconds, # 如果DataManager需要根据数据文件路径推断或者额外参数传入
# start_date=start_time.date(), # DataManager 现在通过 file_path 和 symbol 处理数据
# end_date=end_time.date(),
)
# data_manager.load_data() # DataManager 内部加载数据
strategy_parameters = {
'main_symbol': common_config['main_symbol'],
'trade_volume': 1,
param1_name: p1_value, # 15分钟扫荡K线下影线占其总范围的最小比例。
param2_name: p2_value, # 15分钟限价单的入场点位于扫荡K线低点到收盘价的斐波那契回撤比例。
# 'order_direction': common_config['order_direction'],
'enable_log': False, # 建议在调试和测试时开启日志
}
if 'order_direction' in common_config:
strategy_parameters['order_direction'] = common_config['order_direction']
if 'strategy_mode' in common_config:
strategy_parameters['strategy_mode'] = common_config['strategy_mode']
if 'kalman_measurement_noise' in common_config:
strategy_parameters['kalman_measurement_noise'] = common_config['kalman_measurement_noise']
# if 'entry_threshold_atr' in common_config and 'entry_threshold_atr' not in strategy_parameters:
# strategy_parameters['entry_threshold_atr'] = common_config['entry_threshold_atr']
# elif 'entry_threshold_atr' not in strategy_parameters:
# strategy_parameters['entry_threshold_atr'] = strategy_parameters['structural_stop_atr_multiplier']
# 打印当前进程正在处理的组合信息
# 注意:多进程打印会交错显示
# print(f"--- 正在运行组合: {strategy_parameters} (PID: {multiprocessing.current_process().pid}) ---")
try:
# 初始化回测引擎
engine = BacktestEngine(
data_manager=data_manager,
strategy_class=common_config['strategy'],
strategy_params=strategy_parameters,
initial_capital=initial_capital,
slippage_rate=slippage_rate,
commission_rate=commission_rate,
roll_over_mode=True, # 保持换月模式
start_time=common_config['start_time'],
end_time=common_config['end_time']
)
# 运行回测,传入时间范围
engine.run_backtest()
# 获取回测结果并分析
results = engine.get_backtest_results()
portfolio_snapshots = results["portfolio_snapshots"]
trade_history = results["trade_history"]
bars = results["all_bars"]
initial_capital_result = results["initial_capital"]
if portfolio_snapshots:
analyzer = ResultAnalyzer(portfolio_snapshots, trade_history, bars, initial_capital_result)
# analyzer.generate_report()
# analyzer.plot_performance()
metrics = analyzer.calculate_all_metrics()
# 将当前组合的参数和性能指标存储起来
result_entry = {**strategy_parameters, **metrics}
return result_entry
else:
print(
f" 组合 {strategy_parameters} 没有生成投资组合快照,无法进行结果分析。(PID: {multiprocessing.current_process().pid})")
# 返回一个包含参数和默认0值的结果以便追踪失败组合
return {**strategy_parameters, "total_return": 0.0, "annualized_return": 0.0, "sharpe_ratio": 0.0,
"max_drawdown": 0.0, "error": "No portfolio snapshots"}
except Exception as e:
import traceback
error_trace = traceback.format_exc()
print(
f" 组合 {strategy_parameters} 运行失败: {e}\n{error_trace} (PID: {multiprocessing.current_process().pid})")
# 返回错误信息,以便后续处理
return {**strategy_parameters, "error": str(e), "traceback": error_trace}