SpectralStrategy更新

This commit is contained in:
2025-11-29 16:35:02 +08:00
parent 29199f9492
commit 687d8a180b
35 changed files with 40381 additions and 1153 deletions

File diff suppressed because one or more lines are too long

View File

@@ -47,14 +47,15 @@ class SpectralTrendStrategy(Strategy):
max_hold_days: int = 10, # 最大持仓天数
# --- 其他 ---
order_direction: Optional[List[str]] = None,
indicators: Optional[List[Indicator]] = None,
indicators: Indicator = None,
model_indicator: Indicator = None,
reverse: bool = False,
):
super().__init__(context, main_symbol, enable_log)
if order_direction is None:
order_direction = ['BUY', 'SELL']
if indicators is None:
indicators = [Empty(), Empty()] # 保持兼容性
indicators = Empty() # 保持兼容性
# --- 参数赋值 (完全参数化) ---
self.trade_volume = trade_volume
@@ -88,6 +89,8 @@ class SpectralTrendStrategy(Strategy):
self.last_trend_strength = 0.0
self.last_dominant_freq = 0.0 # 主导周期(天)
self.reverse = reverse
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):
@@ -107,7 +110,7 @@ class SpectralTrendStrategy(Strategy):
position_volume = self.get_current_positions().get(self.symbol, 0)
# 获取历史价格 (使用完整历史)
closes = np.array([b.close for b in bar_history], dtype=float)
closes = np.array([b.close for b in bar_history[-self.spectral_window:]], dtype=float)
# 【核心】计算频域趋势强度 (显式傅里叶)
trend_strength, dominant_freq = self.calculate_trend_strength(closes)
@@ -123,10 +126,11 @@ class SpectralTrendStrategy(Strategy):
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)
if self.trading:
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):
"""
@@ -143,8 +147,9 @@ class SpectralTrendStrategy(Strategy):
return 0.0, 0.0
# 2. 价格归一化 (仅使用窗口内数据)
window_data = prices[-self.spectral_window:]
window_data = prices[-self.spectral_window * 10:]
normalized = (window_data - np.mean(window_data)) / (np.std(window_data) + 1e-8)
normalized = normalized[-self.spectral_window:]
# 3. STFT (采样率=bars_per_day)
try:
@@ -201,8 +206,7 @@ class SpectralTrendStrategy(Strategy):
# 仅当趋势强度跨越临界点且有明确周期时入场
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())):
if trend_strength > self.trend_strength_threshold:
direction = None
indicator = self.model_indicator
@@ -215,7 +219,10 @@ class SpectralTrendStrategy(Strategy):
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:
if direction and self.indicators.is_condition_met(*self.get_indicator_tuple()):
if self.reverse:
direction = "SELL" if direction == "BUY" else "BUY"
self.log(f"Direction={direction}, Open Position")
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"

File diff suppressed because one or more lines are too long

View File

@@ -1,26 +1,24 @@
from typing import Any
import numpy as np
import pywt
import talib
from scipy.signal import stft
from datetime import datetime, timedelta
from typing import Optional, Any, List, Dict
from src.core_data import Order
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
# =============================================================================
# 策略实现 (WaveletDynamicsStrategy - 全新动态分析策略)
# =============================================================================
class WaveletSignalNoiseStrategy(Strategy):
class SpectralTrendStrategy(Strategy):
"""
小波信噪比策略 (最终版)
频域能量相变策略 - 极简回归版
核心哲学:
1. 信任小波: 策略完全基于小波变换最独特的“信号/噪音”分离能力。
2. 简洁因子: 使用一个核心因子——趋势信噪比(TNR),衡量趋势的质量。
3. 可靠逻辑:
- 当信噪比高(趋势清晰)时入场
- 当信噪比低(噪音过大)时出场。
1. 频域 (STFT): 负责"判势" —— 现在的市场是震荡(噪音主导)还是趋势(低频主导)
2. 时域 (Regression): 负责"定向" —— 这个低频趋势是向上的还是向下的?
这种组合避免了频域相位计算的复杂性和不稳定性,回归了量化的本质
"""
def __init__(
@@ -29,172 +27,229 @@ class WaveletSignalNoiseStrategy(Strategy):
main_symbol: str,
enable_log: bool,
trade_volume: int,
# --- 【核心参数 ---
# --- 市场参数 ---
bars_per_day: int = 23,
analysis_window_days: float = 2.0, # 窗口长度适中即可
wavelet_family: str = 'db4',
# --- 【信噪比交易阈值】 ---
tnr_entry_threshold: float = 5, # 入场阈值信号强度至少是噪音的2倍
tnr_exit_threshold: float = 5, # 离场阈值:信号强度不再显著高于噪音
# --- 【持仓管理】 ---
# --- 策略参数 ---
spectral_window_days: float = 2.0,
low_freq_days: float = 2.0,
high_freq_days: float = 1.0,
trend_strength_threshold: float = 0.2, # 强度阈值
exit_threshold: float = 0.1, # 退出阈值
slope_threshold: float = 0.0, # 斜率阈值 (0.05表示每根K线移动0.05个标准差)
max_hold_days: int = 10,
# --- 其他 ---
order_direction: Optional[List[str]] = None,
indicators: Indicator = None,
model_indicator: Indicator = None,
reverse: bool = False,
):
super().__init__(context, main_symbol, enable_log)
# ... (参数赋值) ...
self.bars_per_day = bars_per_day
self.analysis_window_days = analysis_window_days
self.wavelet = wavelet_family
self.tnr_entry_threshold = tnr_entry_threshold
self.tnr_exit_threshold = tnr_exit_threshold
if order_direction is None:
order_direction = ['BUY', 'SELL']
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.slope_threshold = slope_threshold
self.max_hold_days = max_hold_days
self.order_direction = order_direction
self.model_indicator = model_indicator or Empty()
self.indicators = indicators or Empty()
self.reverse = reverse
self.analysis_window = int(self.analysis_window_days * self.bars_per_day)
self.decomposition_level = pywt.dwt_max_level(self.analysis_window, self.wavelet)
# 计算窗口大小
self.spectral_window = int(self.spectral_window_days * self.bars_per_day)
# 确保偶数 (STFT偏好)
if self.spectral_window % 2 != 0:
self.spectral_window += 1
# 频率边界
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.entry_time = None
self.order_id_counter = 0
self.log("WaveletSignalNoiseStrategy Initialized.")
self.entry_time = None
self.position_direction = None
def calculate_trend_noise_ratio(self, prices: np.array) -> (float, np.array):
"""
【最终核心】计算趋势信噪比(TNR)和内在趋势线
返回: (tnr_factor, trend_signal)
"""
if len(prices) < self.analysis_window:
return 0.0, None
window_data = prices[-self.analysis_window:]
try:
coeffs = pywt.wavedec(window_data, self.wavelet, level=self.decomposition_level)
# 1. 重构内在趋势信号 (Signal)
trend_coeffs = [coeffs[0]] + [np.zeros_like(d) for d in coeffs[1:]]
trend_signal = pywt.waverec(trend_coeffs, self.wavelet)
trend_signal = trend_signal[:len(window_data)]
# 2. 重构噪音信号 (Noise)
noise_coeffs = [np.zeros_like(coeffs[0])] + coeffs[1:]
noise_signal = pywt.waverec(noise_coeffs, self.wavelet)
noise_signal = noise_signal[:len(window_data)]
# 3. 计算各自的强度 (标准差)
strength_trend = np.std(trend_signal)
strength_noise = np.std(noise_signal)
# 4. 计算信噪比因子
if strength_noise < 1e-9: # 避免除以零
tnr_factor = np.inf
else:
tnr_factor = strength_trend / strength_noise
return tnr_factor, trend_signal
except Exception as e:
self.log(f"TNR calculation error: {e}", "ERROR")
return 0.0, None
self.log(f"SpectralTrendStrategy (Regression) Init. Window: {self.spectral_window} bars")
def on_open_bar(self, open_price: float, symbol: str):
self.symbol = symbol
bar_history = self.get_bar_history()
position_volume = self.get_current_positions().get(self.symbol, 0)
current_time = self.get_current_time()
self.cancel_all_pending_orders(self.main_symbol)
if len(bar_history) < self.analysis_window:
if len(bar_history) < self.spectral_window + 5:
return
closes = np.array([b.close for b in bar_history], dtype=float)
tnr_factor, trend_signal = self.calculate_trend_noise_ratio(closes)
if trend_signal is None: return
if position_volume == 0:
self.evaluate_entry_signal(open_price, tnr_factor, trend_signal)
else:
self.manage_open_position(position_volume, tnr_factor)
def evaluate_entry_signal(self, open_price: float, tnr_factor: float, trend_signal: np.array):
"""入场逻辑:信噪比达标 + 方向确认"""
if tnr_factor < self.tnr_entry_threshold:
return
direction = None
# 方向判断:内在趋势线的斜率
# if len(trend_signal) < 5: return
if trend_signal[-1] > trend_signal[-5]:
direction = "SELL"
elif trend_signal[-1] < trend_signal[-5]:
direction = "BUY"
if direction:
self.log(f"Entry Signal: {direction} | Trend-Noise Ratio={tnr_factor:.2f}")
self.entry_time = self.get_current_time()
self.send_limit_order(direction, open_price, self.trade_volume, "OPEN")
def manage_open_position(self, volume: int, tnr_factor: float):
"""出场逻辑:信噪比低于退出阈值"""
if tnr_factor < self.tnr_exit_threshold:
direction_str = "CLOSE_LONG" if volume > 0 else "CLOSE_SHORT"
self.log(f"Exit Signal: TNR ({tnr_factor:.2f}) < Threshold ({self.tnr_exit_threshold})")
self.close_position(direction_str, abs(volume))
# 强制平仓检查
if self.entry_time and (current_time - self.entry_time) >= timedelta(days=self.max_hold_days):
self.close_all_positions()
self.entry_time = None
self.position_direction = None
return
# --- 辅助函数区 (与之前版本相同) ---
# (此处省略,以保持简洁)
# 获取数据并归一化
closes = np.array([b.close for b in bar_history[-self.spectral_window:]], dtype=float)
# --- 辅助函数区 (与之前版本相同) ---
# --- 辅助函数区 ---
# 计算核心指标
trend_strength, trend_slope = self.calculate_market_state(closes)
position_volume = self.get_current_positions().get(self.symbol, 0)
if self.trading:
if position_volume == 0:
self.evaluate_entry_signal(open_price, trend_strength, trend_slope)
else:
self.manage_open_position(position_volume, trend_strength, trend_slope)
def calculate_market_state(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)
normalized = normalized[-self.spectral_window:]
# 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
# --- 3. 时域分析 (Regression) - 只负责"方向" ---
# 使用最小二乘法拟合一条直线 y = kx + b
# x 是时间序列 [0, 1, 2...], y 是归一化价格
# slope 代表每经过一根K线价格变化多少个标准差
x = np.arange(len(normalized))
slope, intercept = np.polyfit(x, normalized, 1)
return trend_strength, slope
def evaluate_entry_signal(self, open_price: float, trend_strength: float, trend_slope: float):
"""
入场逻辑:
当频域告诉我们"有趋势"(Strength高),且时域告诉我们"方向明确"(Slope陡峭)时入场。
"""
# 1. 滤除噪音震荡 (STFT关卡)
if trend_strength > self.trend_strength_threshold:
direction = None
# 2. 确认方向 (回归关卡)
# slope > 0.05 意味着趋势向上且有一定力度
if "BUY" in self.order_direction and trend_slope > self.slope_threshold:
direction = "BUY"
# slope < -0.05 意味着趋势向下且有一定力度
elif "SELL" in self.order_direction and trend_slope < -self.slope_threshold:
direction = "SELL"
if direction:
# 辅助指标过滤
if not self.indicators.is_condition_met(*self.get_indicator_tuple()):
return
# 反向逻辑
direction = direction
if not self.model_indicator.is_condition_met(*self.get_indicator_tuple()):
direction = "SELL" if direction == "BUY" else "BUY"
if self.reverse:
direction = "SELL" if direction == "BUY" else "BUY"
self.log(f"Signal: {direction} | Strength={trend_strength:.2f} | Slope={trend_slope:.4f}")
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, trend_slope: float):
"""
离场逻辑:
仅依赖频域能量。只要低频能量依然主导,说明趋势(无论方向)未被破坏。
一旦能量降到 exit_threshold 以下,说明市场进入混乱/震荡,离场观望。
"""
if trend_strength < self.exit_threshold:
direction = "CLOSE_LONG" if volume > 0 else "CLOSE_SHORT"
self.log(f"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")
dir = "CLOSE_LONG" if positions[self.symbol] > 0 else "CLOSE_SHORT"
self.close_position(dir, abs(positions[self.symbol]))
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}"
order_id = f"{self.symbol}_{direction}_MKT_{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
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}"
order_id = f"{self.symbol}_{direction}_LMT_{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,
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
self.send_order(order)

View File

@@ -0,0 +1,193 @@
import numpy as np
from typing import Optional, Any, List
from src.core_data import Bar, Order
from src.strategies.base_strategy import Strategy
class SemiVarianceAsymmetryStrategy(Strategy):
"""
已实现半方差不对称策略 (RSVA)
核心原理:
放弃"阈值计数",改用"波动能量占比"
因子 = (上行波动能量 - 下行波动能量) / 总波动能量
优势:
1. 自适应自动适应2021的高波动和2023的低波动无需调整阈值。
2. 灵敏:能捕捉到没有大阳线但持续上涨的"蠕动趋势"
3. 稳健:使用平方项(Variance)而非三次方(Skewness),对异常值更鲁棒。
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
# --- 窗口参数 ---
season_days: int = 20, # 计算日内季节性基准的回溯天数
calc_window: int = 120, # 计算不对称因子的窗口 (约5天)
cycle_length: int = 23, # 固定周期 (每天23根Bar)
# --- 信号阈值 ---
# RSVA 范围是 [-1, 1]。
# 0.2 表示上涨能量比下跌能量多20% (即 60% vs 40%),是一个显著的失衡信号。
entry_threshold: float = 0.2,
exit_threshold: float = 0.05,
order_direction: Optional[List[str]] = None,
):
super().__init__(context, main_symbol, enable_log)
if order_direction is None:
order_direction = ['BUY', 'SELL']
self.trade_volume = trade_volume
self.season_days = season_days
self.calc_window = calc_window
self.cycle_length = cycle_length
self.entry_threshold = entry_threshold
self.exit_threshold = exit_threshold
self.order_direction = order_direction
# 计算最小历史需求
# 我们需要: calc_window 个标准化数据
# 每个标准化数据需要回溯: season_days * cycle_length
self.min_history = self.calc_window + (self.season_days * self.cycle_length)
# 缓冲区设大一点,避免频繁触发边界检查
self.calc_buffer_size = self.min_history + 100
self.log(f"RSVA Strategy Init: Window={calc_window}, Thresh={entry_threshold}")
def on_open_bar(self, open_price: float, symbol: str):
self.cancel_all_pending_orders(symbol)
# 1. 获取历史数据 (切片优化)
all_history = self.get_bar_history()
total_len = len(all_history)
if total_len < self.min_history:
return
# 只取计算所需的最后一段数据,保证计算复杂度恒定
start_idx = max(0, total_len - self.calc_buffer_size)
relevant_bars = all_history[start_idx:]
# 转为 numpy array
closes = np.array([b.close for b in relevant_bars])
# 2. 计算对数收益率 (Log Returns)
# 对数收益率消除了价格水平(Price Level)的影响
log_rets = np.diff(np.log(closes))
current_idx = len(log_rets) - 1
# 3. 标准化收益率计算 (De-seasonalization)
# 这一步至关重要:剔除日内季节性(早盘波动大、午盘波动小)的干扰
std_rets = []
# 循环计算过去 calc_window 个点的标准化值
for i in range(self.calc_window):
target_idx = current_idx - i
# 高效切片:利用 stride=cycle_length 提取同一时间槽的历史
# slot_history 包含 [t, t-23, t-46, ...]
slot_history = log_rets[target_idx::-self.cycle_length]
# 截取 season_days
if len(slot_history) > self.season_days:
slot_history = slot_history[:self.season_days]
# 计算该时刻的基准波动率
if len(slot_history) < 5:
# 降级处理:样本不足时用近期全局波动率
slot_vol = np.std(log_rets[-self.cycle_length:]) + 1e-9
else:
slot_vol = np.std(slot_history) + 1e-9
# 标准化 (Z-Score)
std_ret = log_rets[target_idx] / slot_vol
std_rets.append(std_ret)
# 转为数组 (注意std_rets 是倒序的,但这不影响平方和计算)
std_rets_arr = np.array(std_rets)
# 4. 【核心】计算已实现半方差不对称性 (RSVA)
# 分离正收益和负收益
pos_rets = std_rets_arr[std_rets_arr > 0]
neg_rets = std_rets_arr[std_rets_arr < 0]
# 计算上行能量 (Upside Variance) 和 下行能量 (Downside Variance)
rv_pos = np.sum(pos_rets ** 2)
rv_neg = np.sum(neg_rets ** 2)
total_rv = rv_pos + rv_neg + 1e-9 # 防止除零
# 计算因子: [-1, 1]
# > 0 说明上涨更有力(或更频繁)< 0 说明下跌主导
rsva_factor = (rv_pos - rv_neg) / total_rv
# 5. 交易逻辑
current_pos = self.get_current_positions().get(symbol, 0)
self.log_status(rsva_factor, rv_pos, rv_neg, current_pos)
if current_pos == 0:
self.evaluate_entry(rsva_factor)
else:
self.evaluate_exit(current_pos, rsva_factor)
def evaluate_entry(self, factor: float):
direction = None
# 因子 > 0.2: 哪怕没有极端K线只要累计的上涨能量显著压过下跌能量就开仓
if factor > self.entry_threshold:
if "BUY" in self.order_direction:
direction = "BUY"
elif factor < -self.entry_threshold:
if "SELL" in self.order_direction:
direction = "SELL"
if direction:
self.log(f"ENTRY: {direction} | RSVA={factor:.4f}")
self.send_market_order(direction, self.trade_volume, "OPEN")
def evaluate_exit(self, volume: int, factor: float):
do_exit = False
reason = ""
# 当多空能量趋于平衡 (因子回到 0 附近),说明趋势动能耗尽,平仓
# 这种离场方式对震荡市非常友好一旦陷入震荡rv_pos 和 rv_neg 会迅速接近,因子归零
if volume > 0 and factor < self.exit_threshold:
do_exit = True
reason = f"Bull Energy Fade (RSVA={factor:.4f})"
elif volume < 0 and factor > -self.exit_threshold:
do_exit = True
reason = f"Bear Energy Fade (RSVA={factor:.4f})"
if do_exit:
direction = "CLOSE_LONG" if volume > 0 else "CLOSE_SHORT"
self.log(f"EXIT: {reason}")
self.send_market_order(direction, abs(volume), "CLOSE")
def send_market_order(self, direction: str, volume: int, offset: str):
# 严格遵守要求:使用 get_current_time()
current_time = self.get_current_time()
order = Order(
id=f"{self.main_symbol}_{direction}_{current_time.timestamp()}",
symbol=self.symbol,
direction=direction,
volume=volume,
price_type="MARKET",
submitted_time=current_time,
offset=offset
)
self.send_order(order)
def log_status(self, factor: float, pos_e: float, neg_e: float, current_pos: int):
if self.enable_log:
# 仅在有持仓或信号明显时打印
if current_pos != 0 or abs(factor) > self.entry_threshold * 0.8:
self.log(f"Status: Pos={current_pos} | RSVA={factor:.4f} | Energy(+/-)={pos_e:.1f}/{neg_e:.1f}")

File diff suppressed because one or more lines are too long

View File

@@ -50,6 +50,11 @@ def run_single_backtest(
'order_direction': common_config['order_direction'],
'enable_log': False, # 建议在调试和测试时开启日志
}
# strategy_parameters['spectral_window_days'] = 2
strategy_parameters['low_freq_days'] = strategy_parameters['spectral_window_days']
strategy_parameters['high_freq_days'] = int(strategy_parameters['spectral_window_days'] / 2)
strategy_parameters['exit_threshold'] = max(strategy_parameters['trend_strength_threshold'] - 0.3, 0)
# 打印当前进程正在处理的组合信息
# 注意:多进程打印会交错显示
print(f"--- 正在运行组合: {strategy_parameters} (PID: {multiprocessing.current_process().pid}) ---")