1、新增傅里叶策略

2、新增策略管理、策略重启功能
This commit is contained in:
2025-11-20 16:15:45 +08:00
parent 2c917a467a
commit 711b86d33f
46 changed files with 12136 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,284 @@
import numpy as np
from scipy.signal import stft
from datetime import datetime, timedelta
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, NormalizedATR, AtrVolatility, ZScoreATR
from src.strategies.base_strategy import Strategy
# =============================================================================
# 策略实现 (SpectralTrendStrategy)
# =============================================================================
class SpectralTrendStrategy(Strategy):
"""
频域能量相变策略 - 捕获肥尾趋势
核心哲学:
1. 显式傅里叶变换: 直接分离低频(趋势)、高频(噪音)能量
2. 相变临界点: 仅当低频能量占比 > 阈值时入场
3. 低频交易: 每月仅2-5次信号持仓数日捕获肥尾
4. 完全参数化: 无硬编码,适配任何市场时间结构
参数说明:
- bars_per_day: 市场每日K线数量 (e.g., 23 for 15min US markets)
- low_freq_days: 低频定义下限 (天), 默认2.0
- high_freq_days: 高频定义上限 (天), 默认1.0
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
# --- 【市场结构参数】 ---
bars_per_day: int = 23, # 关键: 适配23根/天的市场
# --- 【频域核心参数】 ---
spectral_window_days: float = 2.0, # STFT窗口大小(天)
low_freq_days: float = 2.0, # 低频下限(天)
high_freq_days: float = 1.0, # 高频上限(天)
trend_strength_threshold: float = 0.1, # 相变临界值
exit_threshold: float = 0.4, # 退出阈值
# --- 【持仓管理】 ---
max_hold_days: int = 10, # 最大持仓天数
# --- 其他 ---
order_direction: Optional[List[str]] = None,
indicators: Optional[List[Indicator]] = None,
model_indicator: 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.bars_per_day = bars_per_day
self.spectral_window_days = spectral_window_days
self.low_freq_days = low_freq_days
self.high_freq_days = high_freq_days
self.trend_strength_threshold = trend_strength_threshold
self.exit_threshold = exit_threshold
self.max_hold_days = max_hold_days
self.order_direction = order_direction
if model_indicator is None:
model_indicator = Empty()
self.model_indicator = model_indicator
# --- 动态计算参数 ---
self.spectral_window = int(self.spectral_window_days * self.bars_per_day)
# 确保窗口大小为偶数 (STFT要求)
self.spectral_window = self.spectral_window if self.spectral_window % 2 == 0 else self.spectral_window + 1
# 频率边界 (cycles/day)
self.low_freq_bound = 1.0 / self.low_freq_days if self.low_freq_days > 0 else float('inf')
self.high_freq_bound = 1.0 / self.high_freq_days if self.high_freq_days > 0 else 0.0
# --- 内部状态变量 ---
self.main_symbol = main_symbol
self.order_id_counter = 0
self.indicators = indicators
self.entry_time = None # 入场时间
self.position_direction = None # 'LONG' or 'SHORT'
self.last_trend_strength = 0.0
self.last_dominant_freq = 0.0 # 主导周期(天)
self.log(f"SpectralTrendStrategy Initialized (bars/day={bars_per_day}, window={self.spectral_window} bars)")
def on_open_bar(self, open_price: float, symbol: str):
"""每根K线开盘时被调用"""
self.symbol = symbol
bar_history = self.get_bar_history()
current_time = self.get_current_time()
self.cancel_all_pending_orders(self.main_symbol)
# 需要足够的数据 (STFT窗口 + 缓冲)
if len(bar_history) < self.spectral_window + 10:
if self.enable_log and len(bar_history) % 50 == 0:
self.log(f"Waiting for {len(bar_history)}/{self.spectral_window + 10} bars")
return
position_volume = self.get_current_positions().get(self.symbol, 0)
# 获取历史价格 (使用完整历史)
closes = np.array([b.close for b in bar_history], dtype=float)
# 【核心】计算频域趋势强度 (显式傅里叶)
trend_strength, dominant_freq = self.calculate_trend_strength(closes)
self.last_trend_strength = trend_strength
self.last_dominant_freq = dominant_freq
# 检查最大持仓时间 (防止极端事件)
if self.entry_time and (current_time - self.entry_time) >= timedelta(days=self.max_hold_days):
self.log(f"Max hold time reached ({self.max_hold_days} days). Forcing exit.")
self.close_all_positions()
self.entry_time = None
self.position_direction = None
return
# 核心逻辑:相变入场/退出
if position_volume == 0:
self.evaluate_entry_signal(open_price, trend_strength, dominant_freq)
else:
self.manage_open_position(position_volume, trend_strength, dominant_freq)
def calculate_trend_strength(self, prices: np.array) -> (float, float):
"""
【显式傅里叶】计算低频能量占比 (完全参数化)
步骤:
1. 价格归一化 (窗口内)
2. 短时傅里叶变换 (STFT) - 采样率=bars_per_day
3. 动态计算频段边界 (基于bars_per_day)
4. 趋势强度 = 低频能量 / (低频+高频能量)
"""
# 1. 验证数据长度
if len(prices) < self.spectral_window:
return 0.0, 0.0
# 2. 价格归一化 (仅使用窗口内数据)
window_data = prices[-self.spectral_window:]
normalized = (window_data - np.mean(window_data)) / (np.std(window_data) + 1e-8)
# 3. STFT (采样率=bars_per_day)
try:
# fs: 每天的样本数 (bars_per_day)
f, t, Zxx = stft(
normalized,
fs=self.bars_per_day, # 关键: 适配市场结构
nperseg=self.spectral_window,
noverlap=max(0, self.spectral_window // 2),
boundary=None,
padded=False
)
except Exception as e:
self.log(f"STFT calculation error: {str(e)}")
return 0.0, 0.0
# 4. 过滤无效频率 (STFT返回频率范围: 0 到 fs/2)
valid_mask = (f >= 0) & (f <= self.bars_per_day / 2)
f = f[valid_mask]
Zxx = Zxx[valid_mask, :]
if Zxx.size == 0 or Zxx.shape[1] == 0:
return 0.0, 0.0
# 5. 计算最新时间点的能量
current_energy = np.abs(Zxx[:, -1]) ** 2
# 6. 动态频段定义 (cycles/day)
# 低频: 周期 > low_freq_days → 频率 < 1/low_freq_days
low_freq_mask = f < self.low_freq_bound
# 高频: 周期 < high_freq_days → 频率 > 1/high_freq_days
high_freq_mask = f > self.high_freq_bound
# 7. 能量计算
low_energy = np.sum(current_energy[low_freq_mask]) if np.any(low_freq_mask) else 0.0
high_energy = np.sum(current_energy[high_freq_mask]) if np.any(high_freq_mask) else 0.0
total_energy = low_energy + high_energy + 1e-8 # 防除零
# 8. 趋势强度 = 低频能量占比
trend_strength = low_energy / total_energy
# 9. 计算主导趋势周期 (天)
dominant_freq = 0.0
if np.any(low_freq_mask) and low_energy > 0:
# 找到低频段最大能量对应的频率
low_energies = current_energy[low_freq_mask]
max_idx = np.argmax(low_energies)
dominant_freq = 1.0 / (f[low_freq_mask][max_idx] + 1e-8) # 转换为周期(天)
return trend_strength, dominant_freq
def evaluate_entry_signal(self, open_price: float, trend_strength: float, dominant_freq: float):
"""评估相变入场信号"""
# 仅当趋势强度跨越临界点且有明确周期时入场
self.log(
f"Strength={trend_strength:.2f}")
if (trend_strength > self.trend_strength_threshold
and self.model_indicator.is_condition_met(*self.get_indicator_tuple())):
direction = None
indicator = self.model_indicator
# 做多信号: 价格在窗口均值上方
closes = np.array([b.close for b in self.get_bar_history()[-self.spectral_window:]], dtype=float)
if "BUY" in self.order_direction and np.mean(closes[-5:]) > np.mean(closes):
direction = "BUY" if indicator.is_condition_met(*self.get_indicator_tuple()) else "SELL"
# 做空信号: 价格在窗口均值下方
elif "SELL" in self.order_direction and np.mean(closes[-5:]) < np.mean(closes):
direction = "SELL" if indicator.is_condition_met(*self.get_indicator_tuple()) else "BUY"
if direction:
self.send_limit_order(direction, open_price, self.trade_volume, "OPEN")
self.entry_time = self.get_current_time()
self.position_direction = "LONG" if direction == "BUY" else "SHORT"
def manage_open_position(self, volume: int, trend_strength: float, dominant_freq: float):
"""管理持仓:仅当相变逆转时退出"""
# 相变逆转条件: 趋势强度 < 退出阈值
if trend_strength < self.exit_threshold:
direction = "CLOSE_LONG" if volume > 0 else "CLOSE_SHORT"
self.log(f"Phase Transition Exit: {direction} | Strength={trend_strength:.2f} < {self.exit_threshold}")
self.close_position(direction, abs(volume))
self.entry_time = None
self.position_direction = None
# --- 辅助函数区 ---
def close_all_positions(self):
"""强制平仓所有头寸"""
positions = self.get_current_positions()
if self.symbol in positions and positions[self.symbol] != 0:
direction = "CLOSE_LONG" if positions[self.symbol] > 0 else "CLOSE_SHORT"
self.close_position(direction, abs(positions[self.symbol]))
self.log(f"Forced exit of {abs(positions[self.symbol])} contracts")
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 send_limit_order(self, direction: str, limit_price: float, 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="LIMIT",
submitted_time=self.get_current_time(),
offset=offset,
limit_price=limit_price
)
self.send_order(order)
def on_init(self):
super().on_init()
self.cancel_all_pending_orders(self.main_symbol)
self.log("Strategy initialized. Waiting for phase transition signals...")
def on_rollover(self, old_symbol: str, new_symbol: str):
super().on_rollover(old_symbol, new_symbol)
self.log(f"Rollover from {old_symbol} to {new_symbol}. Resetting position state.")
self.entry_time = None
self.position_direction = None
self.last_trend_strength = 0.0

View File

@@ -0,0 +1,377 @@
import numpy as np
from scipy.signal import stft
from datetime import datetime, timedelta
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
# =============================================================================
# 策略实现 (VolatilityAdaptiveSpectralStrategy)
# =============================================================================
class SpectralTrendStrategy(Strategy):
"""
波动率自适应频域趋势策略
核心哲学:
1. 显式傅里叶变换: 分离低频(趋势)、高频(噪音)能量
2. 波动率条件信号: 根据波动率环境动态调整交易方向
- 低波动环境: 趋势策略 (高趋势强度 → 延续)
- 高波动环境: 反转策略 (高趋势强度 → 反转)
3. 无硬编码参数: 所有阈值通过配置参数设定
4. 严格无未来函数: 所有计算使用历史数据
参数说明:
- bars_per_day: 市场每日K线数量
- volatility_lookback: 波动率计算窗口(天)
- low_vol_threshold: 低波动环境阈值(0-1)
- high_vol_threshold: 高波动环境阈值(0-1)
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
# --- 【市场结构参数】 ---
bars_per_day: int = 23, # 适配23根/天的市场
# --- 【频域核心参数】 ---
spectral_window_days: float = 2.0, # STFT窗口大小(天)
low_freq_days: float = 2.0, # 低频下限(天)
high_freq_days: float = 1.0, # 高频上限(天)
trend_strength_threshold: float = 0.8, # 趋势强度阈值
exit_threshold: float = 0.5, # 退出阈值
# --- 【波动率参数】 ---
volatility_lookback_days: float = 5.0, # 波动率计算窗口(天)
low_vol_threshold: float = 0.3, # 低波动环境阈值(0-1)
high_vol_threshold: float = 0.7, # 高波动环境阈值(0-1)
# --- 【持仓管理】 ---
max_hold_days: int = 10, # 最大持仓天数
# --- 其他 ---
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.bars_per_day = bars_per_day
self.spectral_window_days = spectral_window_days
self.low_freq_days = low_freq_days
self.high_freq_days = high_freq_days
self.trend_strength_threshold = trend_strength_threshold
self.exit_threshold = exit_threshold
self.volatility_lookback_days = volatility_lookback_days
self.low_vol_threshold = low_vol_threshold
self.high_vol_threshold = high_vol_threshold
self.max_hold_days = max_hold_days
self.order_direction = order_direction
# --- 动态计算参数 ---
self.spectral_window = int(self.spectral_window_days * self.bars_per_day)
self.spectral_window = self.spectral_window if self.spectral_window % 2 == 0 else self.spectral_window + 1
self.volatility_window = int(self.volatility_lookback_days * self.bars_per_day)
# 频率边界 (cycles/day)
self.low_freq_bound = 1.0 / self.low_freq_days if self.low_freq_days > 0 else float('inf')
self.high_freq_bound = 1.0 / self.high_freq_days if self.high_freq_days > 0 else 0.0
# --- 内部状态变量 ---
self.main_symbol = main_symbol
self.order_id_counter = 0
self.indicators = indicators
self.entry_time = None # 入场时间
self.position_direction = None # 'LONG' or 'SHORT'
self.last_trend_strength = 0.0
self.last_dominant_freq = 0.0 # 主导周期(天)
self.last_volatility = 0.0 # 标准化波动率(0-1)
self.volatility_history = [] # 存储历史波动率
self.log(f"VolatilityAdaptiveSpectralStrategy Initialized (bars/day={bars_per_day}, "
f"window={self.spectral_window} bars, vol_window={self.volatility_window} bars)")
def on_open_bar(self, open_price: float, symbol: str):
"""每根K线开盘时被调用"""
self.symbol = symbol
bar_history = self.get_bar_history()
current_time = self.get_current_time()
# 需要足够的数据 (最大窗口 + 缓冲)
min_required = max(self.spectral_window, self.volatility_window) + 10
if len(bar_history) < min_required:
if self.enable_log and len(bar_history) % 50 == 0:
self.log(f"Waiting for {len(bar_history)}/{min_required} bars")
return
position_volume = self.get_current_positions().get(self.symbol, 0)
# 获取必要历史价格 (仅取所需部分)
recent_bars = bar_history[-(max(self.spectral_window, self.volatility_window) + 5):]
closes = np.array([b.close for b in recent_bars], dtype=np.float32)
highs = np.array([b.high for b in recent_bars], dtype=np.float32)
lows = np.array([b.low for b in recent_bars], dtype=np.float32)
# 【核心】计算频域趋势强度 (显式傅里叶)
trend_strength, dominant_freq = self.calculate_trend_strength(closes)
self.last_trend_strength = trend_strength
self.last_dominant_freq = dominant_freq
# 【核心】计算标准化波动率 (0-1范围)
volatility = self.calculate_normalized_volatility(highs, lows, closes)
self.last_volatility = volatility
# 检查最大持仓时间 (防止极端事件)
if self.entry_time and (current_time - self.entry_time) >= timedelta(days=self.max_hold_days):
self.log(f"Max hold time reached ({self.max_hold_days} days). Forcing exit.")
self.close_all_positions()
self.entry_time = None
self.position_direction = None
return
# 核心逻辑:相变入场/退出
if position_volume == 0:
self.evaluate_entry_signal(open_price, trend_strength, dominant_freq, volatility, recent_bars)
else:
self.manage_open_position(position_volume, trend_strength, volatility)
def calculate_trend_strength(self, closes: np.array) -> (float, float):
"""
【显式傅里叶】计算低频能量占比 (完全参数化)
"""
if len(closes) < self.spectral_window:
return 0.0, 0.0
# 仅使用窗口内数据
window_data = closes[-self.spectral_window:]
window_mean = np.mean(window_data)
window_std = np.std(window_data)
if window_std < 1e-8:
return 0.0, 0.0
normalized = (window_data - window_mean) / window_std
try:
f, t, Zxx = stft(
normalized,
fs=self.bars_per_day,
nperseg=self.spectral_window,
noverlap=max(0, self.spectral_window // 2),
boundary=None,
padded=False
)
except Exception as e:
self.log(f"STFT calculation error: {str(e)}")
return 0.0, 0.0
# 过滤无效频率
max_freq = self.bars_per_day / 2
valid_mask = (f >= 0) & (f <= max_freq)
if not np.any(valid_mask):
return 0.0, 0.0
f = f[valid_mask]
Zxx = Zxx[valid_mask, :]
if Zxx.size == 0 or Zxx.shape[1] == 0:
return 0.0, 0.0
# 计算最新时间点的能量
current_energy = np.abs(Zxx[:, -1]) ** 2
# 动态频段定义
low_freq_mask = f < self.low_freq_bound
high_freq_mask = f > self.high_freq_bound
# 能量计算
low_energy = np.sum(current_energy[low_freq_mask]) if np.any(low_freq_mask) else 0.0
high_energy = np.sum(current_energy[high_freq_mask]) if np.any(high_freq_mask) else 0.0
total_energy = low_energy + high_energy + 1e-8
# 趋势强度 = 低频能量占比
trend_strength = low_energy / total_energy
# 计算主导趋势周期 (天)
dominant_freq = 0.0
if np.any(low_freq_mask) and low_energy > 0:
low_energies = current_energy[low_freq_mask]
max_idx = np.argmax(low_energies)
dominant_freq = 1.0 / (f[low_freq_mask][max_idx] + 1e-8)
return float(trend_strength), float(dominant_freq)
def calculate_normalized_volatility(self, highs: np.array, lows: np.array, closes: np.array) -> float:
"""
计算标准化波动率 (0-1范围)
步骤:
1. 计算ATR (真实波幅)
2. 标准化ATR (除以价格)
3. 归一化到0-1范围 (基于历史波动率)
"""
if len(closes) < self.volatility_window + 1:
return 0.5 # 默认中性值
# 1. 计算真实波幅 (TR)
tr1 = highs[-self.volatility_window - 1:] - lows[-self.volatility_window - 1:]
tr2 = np.abs(highs[-self.volatility_window - 1:] - np.roll(closes, 1)[-self.volatility_window - 1:])
tr3 = np.abs(lows[-self.volatility_window - 1:] - np.roll(closes, 1)[-self.volatility_window - 1:])
tr = np.maximum(tr1, np.maximum(tr2, tr3))
# 2. 计算ATR
atr = np.mean(tr[-self.volatility_window:])
# 3. 标准化ATR (除以当前价格)
current_price = closes[-1]
normalized_atr = atr / current_price if current_price > 0 else 0.0
# 4. 归一化到0-1范围 (基于历史波动率)
self.volatility_history.append(normalized_atr)
if len(self.volatility_history) > 1000: # 保留1000个历史值
self.volatility_history.pop(0)
if len(self.volatility_history) < 50: # 需要足够历史数据
return 0.5
# 使用历史50-95百分位进行归一化
low_percentile = np.percentile(self.volatility_history, 50)
high_percentile = np.percentile(self.volatility_history, 95)
if high_percentile - low_percentile < 1e-8:
return 0.5
# 归一化到0-1范围
normalized_vol = (normalized_atr - low_percentile) / (high_percentile - low_percentile + 1e-8)
normalized_vol = max(0.0, min(1.0, normalized_vol)) # 限制在0-1范围内
return normalized_vol
def evaluate_entry_signal(self, open_price: float, trend_strength: float, dominant_freq: float,
volatility: float, recent_bars: List[Bar]):
"""评估波动率条件入场信号"""
# 仅当趋势强度跨越临界点且有明确周期时入场
if trend_strength > self.trend_strength_threshold:
direction = None
trade_type = ""
# 计算价格位置 (短期vs长期均值)
window_closes = np.array([b.close for b in recent_bars[-self.spectral_window:]], dtype=np.float32)
short_avg = np.mean(window_closes[-5:])
long_avg = np.mean(window_closes)
# 添加统计显著性过滤
if abs(short_avg - long_avg) < 0.0005 * long_avg:
return
# 【核心】根据波动率环境决定交易逻辑
if volatility < self.low_vol_threshold:
# 低波动环境: 趋势策略
trade_type = "TREND"
if "BUY" in self.order_direction and short_avg > long_avg:
direction = "BUY"
elif "SELL" in self.order_direction and short_avg < long_avg:
direction = "SELL"
elif volatility > self.high_vol_threshold:
# 高波动环境: 反转策略
trade_type = "REVERSAL"
if "BUY" in self.order_direction and short_avg < long_avg:
direction = "BUY" # 价格低于均值,预期回归
elif "SELL" in self.order_direction and short_avg > long_avg:
direction = "SELL" # 价格高于均值,预期反转
else:
# 中波动环境: 谨慎策略 (需要更强信号)
trade_type = "CAUTIOUS"
if trend_strength > 0.9 and "BUY" in self.order_direction and short_avg > long_avg:
direction = "BUY"
elif trend_strength > 0.9 and "SELL" in self.order_direction and short_avg < long_avg:
direction = "SELL"
if direction:
self.log(
f"Entry: {direction} | Type={trade_type} | Strength={trend_strength:.2f} | "
f"Volatility={volatility:.2f} | ShortAvg={short_avg:.4f} vs LongAvg={long_avg:.4f}"
)
self.send_market_order(direction, self.trade_volume, "OPEN")
self.entry_time = self.get_current_time()
self.position_direction = "LONG" if direction == "BUY" else "SHORT"
def manage_open_position(self, volume: int, trend_strength: float, volatility: float):
"""管理持仓:波动率条件退出"""
# 退出条件1: 趋势强度 < 退出阈值
if trend_strength < self.exit_threshold:
direction = "CLOSE_LONG" if volume > 0 else "CLOSE_SHORT"
self.log(f"Exit (Strength): {direction} | Strength={trend_strength:.2f} < {self.exit_threshold}")
self.close_position(direction, abs(volume))
self.entry_time = None
self.position_direction = None
return
# 退出条件2: 波动率环境突变 (从低波动变为高波动,或反之)
if self.position_direction == "LONG" and volatility > self.high_vol_threshold * 1.2:
# 多头仓位在波动率突增时退出
self.log(
f"Exit (Volatility Spike): CLOSE_LONG | Volatility={volatility:.2f} > {self.high_vol_threshold * 1.2:.2f}")
self.close_position("CLOSE_LONG", abs(volume))
self.entry_time = None
self.position_direction = None
elif self.position_direction == "SHORT" and volatility > self.high_vol_threshold * 1.2:
# 空头仓位在波动率突增时退出
self.log(
f"Exit (Volatility Spike): CLOSE_SHORT | Volatility={volatility:.2f} > {self.high_vol_threshold * 1.2:.2f}")
self.close_position("CLOSE_SHORT", abs(volume))
self.entry_time = None
self.position_direction = None
# --- 辅助函数区 ---
def close_all_positions(self):
"""强制平仓所有头寸"""
positions = self.get_current_positions()
if not positions or self.symbol not in positions or positions[self.symbol] == 0:
return
direction = "CLOSE_LONG" if positions[self.symbol] > 0 else "CLOSE_SHORT"
self.close_position(direction, abs(positions[self.symbol]))
if self.enable_log:
self.log(f"Closed {abs(positions[self.symbol])} contracts")
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[-6:]}_{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_init(self):
super().on_init()
self.cancel_all_pending_orders(self.main_symbol)
if self.enable_log:
self.log("Strategy initialized. Waiting for volatility-adaptive signals...")
def on_rollover(self, old_symbol: str, new_symbol: str):
super().on_rollover(old_symbol, new_symbol)
if self.enable_log:
self.log(f"Rollover: {old_symbol} -> {new_symbol}. Resetting state.")
self.entry_time = None
self.position_direction = None
self.last_trend_strength = 0.0
self.volatility_history = [] # 重置波动率历史

File diff suppressed because one or more lines are too long

View 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}