卡尔曼策略新增md文件
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,196 @@
|
||||
import numpy as np
|
||||
import talib
|
||||
from typing import Optional, Any, List
|
||||
|
||||
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
|
||||
|
||||
|
||||
class AreaReversalStrategy(Strategy):
|
||||
"""
|
||||
面积反转策略(含跟踪止损出场)
|
||||
逻辑:
|
||||
- 面积扩张 + 强度达标 + 局部见顶 + 面积收缩 → 等待反向突破开仓
|
||||
- 出场:跟踪止损(回调出场)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: Any,
|
||||
main_symbol: str,
|
||||
enable_log: bool,
|
||||
trade_volume: int,
|
||||
ma_period: int = 14,
|
||||
area_window: int = 14,
|
||||
strength_window: int = 50,
|
||||
breakout_window: int = 20,
|
||||
quantile_threshold: float = 0.5,
|
||||
top_k: int = 3,
|
||||
trailing_points: float = 100.0, # 跟踪止损点数
|
||||
trailing_percent: float = None, # 或用百分比(如 0.01 = 1%)
|
||||
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 indicators is None:
|
||||
indicators = [Empty(), Empty()]
|
||||
|
||||
self.trade_volume = trade_volume
|
||||
self.ma_period = ma_period
|
||||
self.area_window = area_window
|
||||
self.strength_window = strength_window
|
||||
self.breakout_window = breakout_window
|
||||
self.quantile_threshold = quantile_threshold
|
||||
self.top_k = top_k
|
||||
self.trailing_points = trailing_points
|
||||
self.trailing_percent = trailing_percent
|
||||
self.order_direction = order_direction
|
||||
self.indicators = indicators
|
||||
|
||||
# 跟踪止损状态
|
||||
self.entry_price = None
|
||||
self.highest_high = None # 多头持仓期间最高价
|
||||
self.lowest_low = None # 空头持仓期间最低价
|
||||
|
||||
self.order_id_counter = 0
|
||||
self.min_bars_needed = max(
|
||||
ma_period,
|
||||
area_window * 3,
|
||||
strength_window,
|
||||
breakout_window
|
||||
) + 10
|
||||
self.log("AreaReversalStrategy with Trailing Stop Initialized")
|
||||
|
||||
def _calculate_areas(self, closes: np.array, ma: np.array) -> np.array:
|
||||
diffs = np.abs(closes - ma)
|
||||
areas = talib.SUM(diffs, self.area_window)
|
||||
return areas
|
||||
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
self.symbol = symbol
|
||||
bar_history = self.get_bar_history()
|
||||
|
||||
if len(bar_history) < self.min_bars_needed or not self.trading:
|
||||
return
|
||||
|
||||
position = self.get_current_positions().get(self.symbol, 0)
|
||||
current_bar = bar_history[-1]
|
||||
|
||||
# === 计算指标 ===
|
||||
closes = np.array([b.close for b in bar_history], dtype=float)
|
||||
ma = talib.SMA(closes, self.ma_period)
|
||||
areas = self._calculate_areas(closes, ma)
|
||||
|
||||
A1 = areas[-1]
|
||||
A2 = areas[-2] if len(areas) >= 2 else 0
|
||||
|
||||
# 强度评估窗口
|
||||
historical_areas = areas[-(self.strength_window + 1):-1]
|
||||
if len(historical_areas) < self.strength_window:
|
||||
return
|
||||
|
||||
# === 面积信号条件 ===
|
||||
area_contracting = (A1 < A2) and (A2 > 0)
|
||||
threshold = np.nanpercentile(historical_areas, self.quantile_threshold * 100)
|
||||
strength_satisfied = (A2 >= threshold)
|
||||
top_k_values = np.partition(historical_areas, -self.top_k)[-self.top_k:]
|
||||
local_peak = (A2 >= np.min(top_k_values))
|
||||
|
||||
area_signal = area_contracting and strength_satisfied and local_peak
|
||||
|
||||
# === 突破判断 ===
|
||||
recent_bars = bar_history[-self.breakout_window:]
|
||||
highest = max(b.high for b in recent_bars)
|
||||
lowest = min(b.low for b in recent_bars)
|
||||
|
||||
# =============== 开仓逻辑 ===============
|
||||
if position == 0 and area_signal:
|
||||
if "BUY" in self.order_direction and current_bar.high >= highest:
|
||||
self.send_market_order("BUY", self.trade_volume, "OPEN")
|
||||
self.entry_price = current_bar.close
|
||||
self.highest_high = current_bar.high
|
||||
self.lowest_low = None
|
||||
self.log(f"🚀 Long Entry | A2={A2:.4f}")
|
||||
|
||||
elif "SELL" in self.order_direction and current_bar.low <= lowest:
|
||||
self.send_market_order("SELL", self.trade_volume, "OPEN")
|
||||
self.entry_price = current_bar.close
|
||||
self.lowest_low = current_bar.low
|
||||
self.highest_high = None
|
||||
self.log(f"⬇️ Short Entry | A2={A2:.4f}")
|
||||
|
||||
# =============== 跟踪止损出场逻辑 ===============
|
||||
elif position != 0 and self.entry_price is not None:
|
||||
if position > 0:
|
||||
# 更新最高价
|
||||
if self.highest_high is None or current_bar.high > self.highest_high:
|
||||
self.highest_high = current_bar.high
|
||||
|
||||
# 计算止损价
|
||||
if self.trailing_percent is not None:
|
||||
trailing_offset = self.highest_high * self.trailing_percent
|
||||
else:
|
||||
trailing_offset = self.trailing_points
|
||||
|
||||
stop_loss_price = self.highest_high - trailing_offset
|
||||
|
||||
if current_bar.low <= stop_loss_price:
|
||||
self.close_position("CLOSE_LONG", position)
|
||||
self._reset_state()
|
||||
self.log(f"CloseOperation (Long Trailing Stop) @ {stop_loss_price:.5f}")
|
||||
|
||||
else: # position < 0
|
||||
# 更新最低价
|
||||
if self.lowest_low is None or current_bar.low < self.lowest_low:
|
||||
self.lowest_low = current_bar.low
|
||||
|
||||
# 计算止损价
|
||||
if self.trailing_percent is not None:
|
||||
trailing_offset = self.lowest_low * self.trailing_percent
|
||||
else:
|
||||
trailing_offset = self.trailing_points
|
||||
|
||||
stop_loss_price = self.lowest_low + trailing_offset
|
||||
|
||||
if current_bar.high >= stop_loss_price:
|
||||
self.close_position("CLOSE_SHORT", -position)
|
||||
self._reset_state()
|
||||
self.log(f"CloseOperation (Short Trailing Stop) @ {stop_loss_price:.5f}")
|
||||
|
||||
def _reset_state(self):
|
||||
"""重置跟踪止损状态"""
|
||||
self.entry_price = None
|
||||
self.highest_high = None
|
||||
self.lowest_low = None
|
||||
|
||||
# --- 模板方法 ---
|
||||
def on_init(self):
|
||||
super().on_init()
|
||||
self.cancel_all_pending_orders(self.main_symbol)
|
||||
self._reset_state()
|
||||
|
||||
def close_position(self, direction: str, volume: int):
|
||||
self.send_market_order(direction, volume, offset="CLOSE")
|
||||
|
||||
def send_market_order(self, direction: str, volume: int, offset: str):
|
||||
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._reset_state()
|
||||
self.log("Rollover: Reset trailing stop state.")
|
||||
@@ -0,0 +1,255 @@
|
||||
import numpy as np
|
||||
import talib
|
||||
from typing import Optional, Any, List
|
||||
|
||||
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
|
||||
|
||||
|
||||
class AreaReversalStrategy(Strategy):
|
||||
"""
|
||||
面积反转策略(开仓逻辑不变,出场替换为 ATR 动态跟踪止损)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: Any,
|
||||
main_symbol: str,
|
||||
enable_log: bool,
|
||||
trade_volume: int,
|
||||
ma_period: int = 14,
|
||||
area_window: int = 14,
|
||||
strength_window: int = 50,
|
||||
breakout_window: int = 20,
|
||||
quantile_threshold: float = 0.4,
|
||||
top_k: int = 3,
|
||||
# --- 原有跟踪止损(保留为后备)---
|
||||
trailing_points: float = 100.0,
|
||||
trailing_percent: float = None,
|
||||
# --- 新增 ATR 动态止损参数 ---
|
||||
atr_period: int = 14,
|
||||
initial_atr_mult: float = 3.0, # 初始止损 = 1.0 * ATR
|
||||
max_atr_mult: float = 9.0, # 最大止损 = 3.0 * ATR
|
||||
scale_threshold_mult: float = 1.0, # 盈利达 initial_atr_mult * ATR 时开始扩大
|
||||
use_atr_trailing: bool = True, # 是否启用 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"]
|
||||
if indicators is None:
|
||||
indicators = [Empty(), Empty()]
|
||||
|
||||
self.trade_volume = trade_volume
|
||||
self.ma_period = ma_period
|
||||
self.area_window = area_window
|
||||
self.strength_window = strength_window
|
||||
self.breakout_window = breakout_window
|
||||
self.quantile_threshold = quantile_threshold
|
||||
self.top_k = top_k
|
||||
self.trailing_points = trailing_points
|
||||
self.trailing_percent = trailing_percent
|
||||
self.atr_period = atr_period
|
||||
self.initial_atr_mult = initial_atr_mult
|
||||
self.max_atr_mult = max_atr_mult
|
||||
self.scale_threshold_mult = scale_threshold_mult
|
||||
self.use_atr_trailing = use_atr_trailing
|
||||
self.order_direction = order_direction
|
||||
self.indicators = indicators
|
||||
|
||||
# 状态(新增 entry_atr)
|
||||
self.entry_price = None
|
||||
self.highest_high = None
|
||||
self.lowest_low = None
|
||||
self.entry_atr = None # 入场时的 ATR 值
|
||||
|
||||
self.order_id_counter = 0
|
||||
self.min_bars_needed = max(
|
||||
ma_period,
|
||||
area_window * 3,
|
||||
strength_window,
|
||||
breakout_window,
|
||||
atr_period
|
||||
) + 10
|
||||
self.log("AreaReversalStrategy with ATR Trailing Stop Initialized")
|
||||
|
||||
def _calculate_areas(self, closes: np.array, ma: np.array) -> np.array:
|
||||
diffs = np.abs(closes - ma)
|
||||
areas = talib.SUM(diffs, self.area_window)
|
||||
return areas
|
||||
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
self.symbol = symbol
|
||||
bar_history = self.get_bar_history()
|
||||
|
||||
if len(bar_history) < self.min_bars_needed or not self.trading:
|
||||
return
|
||||
|
||||
position = self.get_current_positions().get(self.symbol, 0)
|
||||
current_bar = bar_history[-1]
|
||||
|
||||
# === 提取价格序列(新增 highs, lows 用于 ATR)===
|
||||
closes = np.array([b.close for b in bar_history], dtype=float)
|
||||
highs = np.array([b.high for b in bar_history], dtype=float)
|
||||
lows = np.array([b.low for b in bar_history], dtype=float)
|
||||
|
||||
# === 计算指标 ===
|
||||
ma = talib.SMA(closes, self.ma_period)
|
||||
areas = self._calculate_areas(closes, ma)
|
||||
|
||||
# 新增:计算 ATR
|
||||
if self.use_atr_trailing:
|
||||
atr = talib.ATR(highs, lows, closes, self.atr_period)
|
||||
current_atr = atr[-1]
|
||||
else:
|
||||
current_atr = None
|
||||
|
||||
A1 = areas[-1]
|
||||
A2 = areas[-2] if len(areas) >= 2 else 0
|
||||
|
||||
historical_areas = areas[-(self.strength_window + 1):-1]
|
||||
if len(historical_areas) < self.strength_window:
|
||||
return
|
||||
|
||||
# === 面积信号条件(完全不变)===
|
||||
area_contracting = (A1 < A2) and (A2 > 0)
|
||||
threshold = np.nanpercentile(historical_areas, self.quantile_threshold * 100)
|
||||
strength_satisfied = (A2 >= threshold)
|
||||
top_k_values = np.partition(historical_areas, -self.top_k)[-self.top_k:]
|
||||
local_peak = (A2 >= np.min(top_k_values))
|
||||
area_signal = area_contracting and strength_satisfied and local_peak
|
||||
|
||||
# === 突破判断(完全不变)===
|
||||
recent_bars = bar_history[-self.breakout_window:]
|
||||
highest = max(b.high for b in recent_bars)
|
||||
lowest = min(b.low for b in recent_bars)
|
||||
|
||||
# =============== 开仓逻辑(完全不变)==============
|
||||
if position == 0 and area_signal:
|
||||
if "BUY" in self.order_direction and current_bar.high >= highest:
|
||||
self.send_market_order("BUY", self.trade_volume, "OPEN")
|
||||
self.entry_price = current_bar.close
|
||||
self.highest_high = current_bar.high
|
||||
self.lowest_low = None
|
||||
if self.use_atr_trailing and current_atr is not None:
|
||||
self.entry_atr = current_atr # 记录入场 ATR
|
||||
self.log(f"🚀 Long Entry | A2={A2:.4f}")
|
||||
|
||||
elif "SELL" in self.order_direction and current_bar.low <= lowest:
|
||||
self.send_market_order("SELL", self.trade_volume, "OPEN")
|
||||
self.entry_price = current_bar.close
|
||||
self.lowest_low = current_bar.low
|
||||
self.highest_high = None
|
||||
if self.use_atr_trailing and current_atr is not None:
|
||||
self.entry_atr = current_atr
|
||||
self.log(f"⬇️ Short Entry | A2={A2:.4f}")
|
||||
|
||||
# =============== 出场逻辑:ATR 动态跟踪止损 ===============
|
||||
elif position != 0 and self.entry_price is not None:
|
||||
if self.use_atr_trailing and self.entry_atr is not None:
|
||||
# --- ATR 动态止损 ---
|
||||
if position > 0:
|
||||
if self.highest_high is None or current_bar.high > self.highest_high:
|
||||
self.highest_high = current_bar.high
|
||||
|
||||
unrealized_pnl = current_bar.close - self.entry_price
|
||||
scale_threshold_pnl = self.scale_threshold_mult * self.initial_atr_mult * self.entry_atr
|
||||
|
||||
if unrealized_pnl <= 0:
|
||||
trail_mult = self.initial_atr_mult
|
||||
elif unrealized_pnl >= scale_threshold_pnl:
|
||||
trail_mult = self.max_atr_mult
|
||||
else:
|
||||
ratio = unrealized_pnl / scale_threshold_pnl
|
||||
trail_mult = self.initial_atr_mult + ratio * (self.max_atr_mult - self.initial_atr_mult)
|
||||
|
||||
stop_loss_price = self.highest_high - trail_mult * self.entry_atr
|
||||
if current_bar.low <= stop_loss_price:
|
||||
self.close_position("CLOSE_LONG", position)
|
||||
self._reset_state()
|
||||
self.log(f"CloseOperation (ATR Trailing) | Mult={trail_mult:.2f}")
|
||||
|
||||
else: # short
|
||||
if self.lowest_low is None or current_bar.low < self.lowest_low:
|
||||
self.lowest_low = current_bar.low
|
||||
|
||||
unrealized_pnl = self.entry_price - current_bar.close
|
||||
scale_threshold_pnl = self.scale_threshold_mult * self.initial_atr_mult * self.entry_atr
|
||||
|
||||
if unrealized_pnl <= 0:
|
||||
trail_mult = self.initial_atr_mult
|
||||
elif unrealized_pnl >= scale_threshold_pnl:
|
||||
trail_mult = self.max_atr_mult
|
||||
else:
|
||||
ratio = unrealized_pnl / scale_threshold_pnl
|
||||
trail_mult = self.initial_atr_mult + ratio * (self.max_atr_mult - self.initial_atr_mult)
|
||||
|
||||
stop_loss_price = self.lowest_low + trail_mult * self.entry_atr
|
||||
if current_bar.high >= stop_loss_price:
|
||||
self.close_position("CLOSE_SHORT", -position)
|
||||
self._reset_state()
|
||||
self.log(f"CloseOperation (ATR Trailing) | Mult={trail_mult:.2f}")
|
||||
|
||||
else:
|
||||
# --- 保留原有跟踪止损(后备)---
|
||||
if position > 0:
|
||||
if self.highest_high is None or current_bar.high > self.highest_high:
|
||||
self.highest_high = current_bar.high
|
||||
if self.trailing_percent is not None:
|
||||
offset = self.highest_high * self.trailing_percent
|
||||
else:
|
||||
offset = self.trailing_points
|
||||
stop_loss_price = self.highest_high - offset
|
||||
if current_bar.low <= stop_loss_price:
|
||||
self.close_position("CLOSE_LONG", position)
|
||||
self._reset_state()
|
||||
|
||||
else:
|
||||
if self.lowest_low is None or current_bar.low < self.lowest_low:
|
||||
self.lowest_low = current_bar.low
|
||||
if self.trailing_percent is not None:
|
||||
offset = self.lowest_low * self.trailing_percent
|
||||
else:
|
||||
offset = self.trailing_points
|
||||
stop_loss_price = self.lowest_low + offset
|
||||
if current_bar.high >= stop_loss_price:
|
||||
self.close_position("CLOSE_SHORT", -position)
|
||||
self._reset_state()
|
||||
|
||||
def _reset_state(self):
|
||||
self.entry_price = None
|
||||
self.highest_high = None
|
||||
self.lowest_low = None
|
||||
self.entry_atr = None
|
||||
|
||||
# --- 模板方法(不变)---
|
||||
def on_init(self):
|
||||
super().on_init()
|
||||
self.cancel_all_pending_orders(self.main_symbol)
|
||||
self._reset_state()
|
||||
|
||||
def close_position(self, direction: str, volume: int):
|
||||
self.send_market_order(direction, volume, offset="CLOSE")
|
||||
|
||||
def send_market_order(self, direction: str, volume: int, offset: str):
|
||||
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._reset_state()
|
||||
self.log("Rollover: Reset trailing stop state.")
|
||||
File diff suppressed because one or more lines are too long
60
futures_trading_strategies/FG/AreaReversal/strategy.md
Normal file
60
futures_trading_strategies/FG/AreaReversal/strategy.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# 面积反转策略(Area Reversal Strategy)
|
||||
|
||||
本策略源自主观交易员“猛将兄”的反转交易思想:**观测价格与均线之间的“面积”变化,当面积先扩张、再收缩,且伴随反向突破时,视为趋势动能耗尽、反转启动的信号**。我们将该主观逻辑完全量化,形成一套可复现、可验证的系统化策略。
|
||||
|
||||
## 📌 核心逻辑
|
||||
|
||||
1. **面积定义**
|
||||
- 单根K线偏离:`|close_t - MA_t|`
|
||||
- 窗口面积(长度 L):`A_t = Σ_{i=t-L+1}^{t} |close_i - MA_i|`
|
||||
- 面积越大,表示价格对均线的**偏离强度越强且越持续**
|
||||
|
||||
2. **开仓条件(三者需同时满足)**
|
||||
- **面积扩张后收缩**:`A₁ < A₂`(最新面积小于前一段)
|
||||
- **强度达标**:`A₂ ≥ 过去 W 个面积的 50% 分位数`
|
||||
- **局部见顶**:`A₂` 为过去 W 个面积中的前 3 大值之一
|
||||
- **反向突破**:价格突破前 N 根K线高/低点
|
||||
|
||||
3. **策略类型**
|
||||
- **反转策略**:捕捉趋势末端的动能衰竭点
|
||||
- **右侧入场**:需等待面积收缩 + 突破双重确认,避免左侧抄底
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Strategy 1:固定点数跟踪止损(Baseline)
|
||||
|
||||
- **出场逻辑**:
|
||||
入场后记录持仓期间最高价(多头)或最低价(空头),设置**固定点数回撤阈值**(如 100 跳)作为跟踪止损。
|
||||
- 止损价 = `最高价 - 100`(多头)
|
||||
- 止损价 = `最低价 + 100`(空头)
|
||||
|
||||
- **优点**:
|
||||
- 逻辑简单,回测稳定
|
||||
- 在强趋势行情中能有效捕获大段利润
|
||||
|
||||
- **缺点**:
|
||||
- **100 跳对多数品种过大**,导致回撤不可控
|
||||
- **调小后(如 50 跳)易被震荡行情洗出**,错过后续趋势
|
||||
- **无法自适应不同品种的波动特性**(黄金 vs 外汇 vs 股指)
|
||||
|
||||
> 💡 此版本为策略基线,用于验证面积信号本身的有效性。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Strategy 2:ATR 动态跟踪止损(Optimized)
|
||||
|
||||
- **核心改进**:
|
||||
引入 **ATR(平均真实波幅)** 动态调整止损距离,实现:
|
||||
- **初始止损小**(1.0 × ATR)→ 控制单笔风险
|
||||
- **盈利后止损逐步扩大**(线性过渡至 3.0 × ATR)→ 容忍趋势中的正常回撤
|
||||
- **完全自适应品种波动率**,统一参数适用于多资产
|
||||
```
|
||||
|
||||
- **优势**:
|
||||
- **解决“100跳太大,调小就失效”的困境**
|
||||
- 在趋势行情中**让利润充分奔跑**,在震荡行情中**自动收紧风险**
|
||||
- 保留原始开仓逻辑不变,仅优化风险管理
|
||||
|
||||
> ✅ Strategy 2 在不改变信号生成的前提下,显著提升策略的风险调整后收益,是面积反转策略的**工程化优化方向**。
|
||||
|
||||
---
|
||||
103
futures_trading_strategies/FG/AreaReversal/utils.py
Normal file
103
futures_trading_strategies/FG/AreaReversal/utils.py
Normal file
@@ -0,0 +1,103 @@
|
||||
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, # 建议在调试和测试时开启日志
|
||||
}
|
||||
# 打印当前进程正在处理的组合信息
|
||||
# 注意:多进程打印会交错显示
|
||||
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}
|
||||
|
||||
Reference in New Issue
Block a user