更新策略邮件推送

This commit is contained in:
2025-12-16 00:36:36 +08:00
parent 548ea34238
commit 3648f9c790
25 changed files with 10229 additions and 2519 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,312 @@
import numpy as np
import talib
from scipy.signal import stft
from datetime import timedelta
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 SpectralTrendStrategy(Strategy):
"""
频域能量相变策略 - 塔勒布宽幅结构版 (Chandelier Exit)
修改重点:
1. 移除 Slope 离场,避免噪音干扰。
2. 引入状态变量记录持仓期间的极值 (pos_highest/pos_lowest)。
3. 实施“吊灯止损”:以持仓期间极值为锚点,回撤 N * ATR 离场。
4. 止损系数建议4.0 - 6.0 (给予极大的呼吸空间,仅防范趋势崩溃)。
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
# --- 市场参数 ---
bars_per_day: int = 23,
# --- STFT 策略参数 ---
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, # 仅用于判断方向,不用于离场
# --- 关键风控参数 (Chandelier Exit) ---
stop_loss_atr_multiplier: float = 5.0, # 塔勒布式宽止损,建议 4.0 ~ 6.0
stop_loss_atr_period: int = 14,
# --- 其他 ---
order_direction: Optional[List[str]] = 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']
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.sl_atr_multiplier = stop_loss_atr_multiplier
self.sl_atr_period = stop_loss_atr_period
self.order_direction = order_direction
self.model_indicator = model_indicator or Empty()
self.indicators = indicators or Empty()
self.reverse = reverse
# 计算 STFT 窗口大小
self.spectral_window = int(self.spectral_window_days * self.bars_per_day)
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.order_id_counter = 0
# --- 持仓状态追踪变量 ---
self.entry_price = 0.0
self.pos_highest = 0.0 # 持有多单期间的最高价
self.pos_lowest = 0.0 # 持有空单期间的最低价
self.log(
f"SpectralTrend Strategy Initialized. Window: {self.spectral_window}, "
f"Chandelier Stop: {self.sl_atr_multiplier}x ATR"
)
def on_open_bar(self, open_price: float, symbol: str):
self.symbol = symbol
bar_history = self.get_bar_history()
self.cancel_all_pending_orders(self.main_symbol)
# 1. 数据长度检查
required_len = max(self.spectral_window, self.sl_atr_period + 5)
if len(bar_history) < required_len:
return
# 2. 计算 ATR (用于止损)
atr_window = self.sl_atr_period + 10
highs = np.array([b.high for b in bar_history[-atr_window:]], dtype=float)
lows = np.array([b.low for b in bar_history[-atr_window:]], dtype=float)
closes = np.array([b.close for b in bar_history[-atr_window:]], dtype=float)
try:
atr_values = talib.ATR(highs, lows, closes, timeperiod=self.sl_atr_period)
current_atr = atr_values[-1]
if np.isnan(current_atr): current_atr = 0.0
except Exception as e:
self.log(f"ATR Calc Error: {e}")
current_atr = 0.0
# 3. 计算 STFT 核心指标
stft_closes = np.array([b.close for b in bar_history[-self.spectral_window:]], dtype=float)
trend_strength, trend_slope = self.calculate_market_state(stft_closes)
# 4. 交易逻辑
position_volume = self.get_current_positions().get(self.symbol, 0)
# 获取当前Bar的最高/最低价用于更新极值如果使用Bar内更新更加灵敏
# 这里为了稳健使用上一根Bar的High/Low来更新或者使用开盘价近似
current_high = bar_history[-1].high
current_low = bar_history[-1].low
if self.trading:
if position_volume == 0:
# 重置状态
self.pos_highest = 0.0
self.pos_lowest = 0.0
self.entry_price = 0.0
self.evaluate_entry_signal(open_price, trend_strength, trend_slope)
else:
# 传入 current_high/low 用于更新追踪止损的锚点
self.manage_open_position(
position_volume,
trend_strength,
open_price,
current_atr,
current_high,
current_low
)
def calculate_market_state(self, prices: np.array) -> (float, float):
"""
计算频域能量占比和线性回归斜率(仅用于方向)
"""
if len(prices) < self.spectral_window:
return 0.0, 0.0
# 标准化处理,消除绝对价格影响
window_data = prices[-self.spectral_window:]
mean_val = np.mean(window_data)
std_val = np.std(window_data)
if std_val == 0: std_val = 1.0
normalized = (window_data - mean_val) / (std_val + 1e-8)
# STFT 计算
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:
return 0.0, 0.0
# 提取有效频率
valid_mask = (f >= 0) & (f <= self.bars_per_day / 2)
f = f[valid_mask]
Zxx = Zxx[valid_mask, :]
if Zxx.size == 0: return 0.0, 0.0
# 计算能量
current_energy = np.abs(Zxx[:, -1]) ** 2
low_mask = f < self.low_freq_bound
high_mask = f > self.high_freq_bound
low_energy = np.sum(current_energy[low_mask]) if np.any(low_mask) else 0.0
high_energy = np.sum(current_energy[high_mask]) if np.any(high_mask) else 0.0
total_energy = low_energy + high_energy + 1e-8
trend_strength = low_energy / total_energy
# 计算简单线性斜率用于判断方向
x = np.arange(len(normalized))
slope, _ = np.polyfit(x, normalized, 1)
return trend_strength, slope
def evaluate_entry_signal(self, open_price: float, trend_strength: float, trend_slope: float):
"""
入场逻辑:高低频能量比 > 阈值 + 斜率确认方向
"""
# 必须有足够的趋势强度
if trend_strength > self.trend_strength_threshold:
direction = None
# 仅使用 slope 的正负号和基本阈值来决定方向,不作为离场依据
if "BUY" in self.order_direction and trend_slope > self.slope_threshold:
direction = "BUY"
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
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"Entry: {direction} | Strength={trend_strength:.2f} | DirSlope={trend_slope:.4f}")
self.send_limit_order(direction, open_price, self.trade_volume, "OPEN")
# 初始化持仓状态
self.entry_price = open_price
# 初始极值设为当前价格
self.pos_highest = open_price
self.pos_lowest = open_price
def manage_open_position(self, volume: int, trend_strength: float, current_price: float,
current_atr: float, high_price: float, low_price: float):
"""
离场逻辑核心:
1. 信号离场:能量自然衰竭 (Trend Strength < Threshold)
2. 结构离场Chandelier Exit (吊灯止损) - 宽幅 ATR 追踪
"""
exit_dir = "CLOSE_LONG" if volume > 0 else "CLOSE_SHORT"
# --- 更新持仓期间的极值 ---
if volume > 0: # 多头
if high_price > self.pos_highest or self.pos_highest == 0:
self.pos_highest = high_price
else: # 空头
if (low_price < self.pos_lowest or self.pos_lowest == 0) and low_price > 0:
self.pos_lowest = low_price
# --- 1. 计算宽幅吊灯止损 (Structural Stop) ---
is_stop_triggered = False
stop_line = 0.0
# 如果 ATR 无效,暂时不触发止损,或者使用百分比兜底(此处略)
if current_atr > 0:
stop_distance = current_atr * self.sl_atr_multiplier
if volume > 0:
# 多头止损线 = 最高价 - N * ATR
# 随着价格创新高,止损线不断上移;价格下跌,止损线不变
stop_line = self.pos_highest - stop_distance
if current_price <= stop_line:
is_stop_triggered = True
self.log(
f"STOP (Long): Price {current_price} <= Highest {self.pos_highest} - {self.sl_atr_multiplier}xATR")
else:
# 空头止损线 = 最低价 + N * ATR
stop_line = self.pos_lowest + stop_distance
if current_price >= stop_line:
is_stop_triggered = True
self.log(
f"STOP (Short): Price {current_price} >= Lowest {self.pos_lowest} + {self.sl_atr_multiplier}xATR")
if is_stop_triggered:
self.close_position(exit_dir, abs(volume))
return # 止损优先
# --- 2. 信号自然离场 (Signal Exit) ---
# 当 STFT 检测到低频能量消散,说明市场进入混沌或震荡,此时平仓
# 这是一个 "慢" 离场,通常在趋势走完后
if trend_strength < self.exit_threshold:
self.log(f"Exit (Signal): Strength {trend_strength:.2f} < {self.exit_threshold}")
self.close_position(exit_dir, abs(volume))
return
# --- 交易执行辅助函数 ---
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}_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
)
self.send_order(order)
def send_limit_order(self, direction: str, limit_price: float, volume: int, offset: str):
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,
limit_price=limit_price
)
self.send_order(order)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,312 @@
import numpy as np
import talib
from scipy.signal import stft
from datetime import timedelta
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 SpectralTrendStrategy(Strategy):
"""
频域能量相变策略 - 塔勒布宽幅结构版 (Chandelier Exit)
修改重点:
1. 移除 Slope 离场,避免噪音干扰。
2. 引入状态变量记录持仓期间的极值 (pos_highest/pos_lowest)。
3. 实施“吊灯止损”:以持仓期间极值为锚点,回撤 N * ATR 离场。
4. 止损系数建议4.0 - 6.0 (给予极大的呼吸空间,仅防范趋势崩溃)。
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
# --- 市场参数 ---
bars_per_day: int = 23,
# --- STFT 策略参数 ---
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, # 仅用于判断方向,不用于离场
# --- 关键风控参数 (Chandelier Exit) ---
stop_loss_atr_multiplier: float = 5.0, # 塔勒布式宽止损,建议 4.0 ~ 6.0
stop_loss_atr_period: int = 14,
# --- 其他 ---
order_direction: Optional[List[str]] = 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']
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.sl_atr_multiplier = stop_loss_atr_multiplier
self.sl_atr_period = stop_loss_atr_period
self.order_direction = order_direction
self.model_indicator = model_indicator or Empty()
self.indicators = indicators or Empty()
self.reverse = reverse
# 计算 STFT 窗口大小
self.spectral_window = int(self.spectral_window_days * self.bars_per_day)
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.order_id_counter = 0
# --- 持仓状态追踪变量 ---
self.entry_price = 0.0
self.pos_highest = 0.0 # 持有多单期间的最高价
self.pos_lowest = 0.0 # 持有空单期间的最低价
self.log(
f"SpectralTrend Strategy Initialized. Window: {self.spectral_window}, "
f"Chandelier Stop: {self.sl_atr_multiplier}x ATR"
)
def on_open_bar(self, open_price: float, symbol: str):
self.symbol = symbol
bar_history = self.get_bar_history()
self.cancel_all_pending_orders(self.main_symbol)
# 1. 数据长度检查
required_len = max(self.spectral_window, self.sl_atr_period + 5)
if len(bar_history) < required_len:
return
# 2. 计算 ATR (用于止损)
atr_window = self.sl_atr_period + 10
highs = np.array([b.high for b in bar_history[-atr_window:]], dtype=float)
lows = np.array([b.low for b in bar_history[-atr_window:]], dtype=float)
closes = np.array([b.close for b in bar_history[-atr_window:]], dtype=float)
try:
atr_values = talib.ATR(highs, lows, closes, timeperiod=self.sl_atr_period)
current_atr = atr_values[-1]
if np.isnan(current_atr): current_atr = 0.0
except Exception as e:
self.log(f"ATR Calc Error: {e}")
current_atr = 0.0
# 3. 计算 STFT 核心指标
stft_closes = np.array([b.close for b in bar_history[-self.spectral_window:]], dtype=float)
trend_strength, trend_slope = self.calculate_market_state(stft_closes)
# 4. 交易逻辑
position_volume = self.get_current_positions().get(self.symbol, 0)
# 获取当前Bar的最高/最低价用于更新极值如果使用Bar内更新更加灵敏
# 这里为了稳健使用上一根Bar的High/Low来更新或者使用开盘价近似
current_high = bar_history[-1].high
current_low = bar_history[-1].low
if self.trading:
if position_volume == 0:
# 重置状态
self.pos_highest = 0.0
self.pos_lowest = 0.0
self.entry_price = 0.0
self.evaluate_entry_signal(open_price, trend_strength, trend_slope)
else:
# 传入 current_high/low 用于更新追踪止损的锚点
self.manage_open_position(
position_volume,
trend_strength,
open_price,
current_atr,
current_high,
current_low
)
def calculate_market_state(self, prices: np.array) -> (float, float):
"""
计算频域能量占比和线性回归斜率(仅用于方向)
"""
if len(prices) < self.spectral_window:
return 0.0, 0.0
# 标准化处理,消除绝对价格影响
window_data = prices[-self.spectral_window:]
mean_val = np.mean(window_data)
std_val = np.std(window_data)
if std_val == 0: std_val = 1.0
normalized = (window_data - mean_val) / (std_val + 1e-8)
# STFT 计算
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:
return 0.0, 0.0
# 提取有效频率
valid_mask = (f >= 0) & (f <= self.bars_per_day / 2)
f = f[valid_mask]
Zxx = Zxx[valid_mask, :]
if Zxx.size == 0: return 0.0, 0.0
# 计算能量
current_energy = np.abs(Zxx[:, -1]) ** 2
low_mask = f < self.low_freq_bound
high_mask = f > self.high_freq_bound
low_energy = np.sum(current_energy[low_mask]) if np.any(low_mask) else 0.0
high_energy = np.sum(current_energy[high_mask]) if np.any(high_mask) else 0.0
total_energy = low_energy + high_energy + 1e-8
trend_strength = low_energy / total_energy
# 计算简单线性斜率用于判断方向
x = np.arange(len(normalized))
slope, _ = np.polyfit(x, normalized, 1)
return trend_strength, slope
def evaluate_entry_signal(self, open_price: float, trend_strength: float, trend_slope: float):
"""
入场逻辑:高低频能量比 > 阈值 + 斜率确认方向
"""
# 必须有足够的趋势强度
if trend_strength > self.trend_strength_threshold:
direction = None
# 仅使用 slope 的正负号和基本阈值来决定方向,不作为离场依据
if "BUY" in self.order_direction and trend_slope > self.slope_threshold:
direction = "BUY"
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
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"Entry: {direction} | Strength={trend_strength:.2f} | DirSlope={trend_slope:.4f}")
self.send_limit_order(direction, open_price, self.trade_volume, "OPEN")
# 初始化持仓状态
self.entry_price = open_price
# 初始极值设为当前价格
self.pos_highest = open_price
self.pos_lowest = open_price
def manage_open_position(self, volume: int, trend_strength: float, current_price: float,
current_atr: float, high_price: float, low_price: float):
"""
离场逻辑核心:
1. 信号离场:能量自然衰竭 (Trend Strength < Threshold)
2. 结构离场Chandelier Exit (吊灯止损) - 宽幅 ATR 追踪
"""
exit_dir = "CLOSE_LONG" if volume > 0 else "CLOSE_SHORT"
# --- 更新持仓期间的极值 ---
if volume > 0: # 多头
if high_price > self.pos_highest or self.pos_highest == 0:
self.pos_highest = high_price
else: # 空头
if (low_price < self.pos_lowest or self.pos_lowest == 0) and low_price > 0:
self.pos_lowest = low_price
# --- 1. 计算宽幅吊灯止损 (Structural Stop) ---
is_stop_triggered = False
stop_line = 0.0
# 如果 ATR 无效,暂时不触发止损,或者使用百分比兜底(此处略)
if current_atr > 0:
stop_distance = current_atr * self.sl_atr_multiplier
if volume > 0:
# 多头止损线 = 最高价 - N * ATR
# 随着价格创新高,止损线不断上移;价格下跌,止损线不变
stop_line = self.pos_highest - stop_distance
if current_price <= stop_line:
is_stop_triggered = True
self.log(
f"STOP (Long): Price {current_price} <= Highest {self.pos_highest} - {self.sl_atr_multiplier}xATR")
else:
# 空头止损线 = 最低价 + N * ATR
stop_line = self.pos_lowest + stop_distance
if current_price >= stop_line:
is_stop_triggered = True
self.log(
f"STOP (Short): Price {current_price} >= Lowest {self.pos_lowest} + {self.sl_atr_multiplier}xATR")
if is_stop_triggered:
self.close_position(exit_dir, abs(volume))
return # 止损优先
# --- 2. 信号自然离场 (Signal Exit) ---
# 当 STFT 检测到低频能量消散,说明市场进入混沌或震荡,此时平仓
# 这是一个 "慢" 离场,通常在趋势走完后
if trend_strength < self.exit_threshold:
self.log(f"Exit (Signal): Strength {trend_strength:.2f} < {self.exit_threshold}")
self.close_position(exit_dir, abs(volume))
return
# --- 交易执行辅助函数 ---
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}_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
)
self.send_order(order)
def send_limit_order(self, direction: str, limit_price: float, volume: int, offset: str):
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,
limit_price=limit_price
)
self.send_order(order)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,335 @@
import numpy as np
import talib
from scipy.signal import stft
from datetime import timedelta
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 SpectralTrendStrategy(Strategy):
"""
频域能量相变策略 - 双因子自适应版 (Dual-Factor Adaptive)
优化逻辑:
不再通过静态参数 reverse 控制方向,而是由两个 Indicator 动态决策:
1. 计算 STFT 基础方向 (Base Direction)。
2. 检查 indicator_primary若满足则采用 Base Direction (顺势/正向)。
3. 若不满足,检查 indicator_secondary若满足则采用 Reverse Direction (逆势/反转)。
4. 若都不满足,保持空仓。
状态追踪:
self.entry_signal_source 会记录当前持仓是 'PRIMARY' 还是 'SECONDARY'
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
# --- 市场参数 ---
bars_per_day: int = 23,
# --- STFT 策略参数 ---
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,
# --- 关键风控参数 (Chandelier Exit) ---
stop_loss_atr_multiplier: float = 5.0,
stop_loss_atr_period: int = 14,
# --- 信号控制指标 (核心优化) ---
indicator_primary: Indicator = None, # 满足此条件 -> 正向开仓 (reverse=False)
indicator_secondary: Indicator = None, # 满足此条件 -> 反向开仓 (reverse=True)
model_indicator: Indicator = None, # 可选额外的AI模型过滤器
# --- 其他 ---
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.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.sl_atr_multiplier = stop_loss_atr_multiplier
self.sl_atr_period = stop_loss_atr_period
self.order_direction = order_direction
# 初始指标容器 (默认为Empty即永远返回True或False视Empty具体实现而定建议传入具体指标)
self.indicator_primary = indicator_primary or Empty()
self.indicator_secondary = indicator_secondary or Empty()
self.model_indicator = model_indicator or Empty()
# 计算 STFT 窗口大小
self.spectral_window = int(self.spectral_window_days * self.bars_per_day)
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.order_id_counter = 0
# --- 持仓状态追踪变量 ---
self.entry_price = 0.0
self.pos_highest = 0.0
self.pos_lowest = 0.0
# 新增:记录开仓信号来源 ('PRIMARY' or 'SECONDARY')
self.entry_signal_source = None
self.log(
f"SpectralTrend Dual-Adaptive Strategy Initialized.\n"
f"Window: {self.spectral_window}, ATR Stop: {self.sl_atr_multiplier}x\n"
f"Primary Ind: {type(self.indicator_primary).__name__}, "
f"Secondary Ind: {type(self.indicator_secondary).__name__}"
)
def on_open_bar(self, open_price: float, symbol: str):
self.symbol = symbol
bar_history = self.get_bar_history()
self.cancel_all_pending_orders(self.main_symbol)
# 1. 数据长度检查
required_len = max(self.spectral_window, self.sl_atr_period + 5)
if len(bar_history) < required_len:
return
# 2. 计算 ATR
atr_window = self.sl_atr_period + 10
highs = np.array([b.high for b in bar_history[-atr_window:]], dtype=float)
lows = np.array([b.low for b in bar_history[-atr_window:]], dtype=float)
closes = np.array([b.close for b in bar_history[-atr_window:]], dtype=float)
try:
atr_values = talib.ATR(highs, lows, closes, timeperiod=self.sl_atr_period)
current_atr = atr_values[-1]
if np.isnan(current_atr): current_atr = 0.0
except Exception as e:
self.log(f"ATR Calc Error: {e}")
current_atr = 0.0
# 3. 计算 STFT 核心指标
stft_closes = np.array([b.close for b in bar_history[-self.spectral_window:]], dtype=float)
trend_strength, trend_slope = self.calculate_market_state(stft_closes)
# 4. 交易逻辑
position_volume = self.get_current_positions().get(self.symbol, 0)
current_high = bar_history[-1].high
current_low = bar_history[-1].low
if self.trading:
if position_volume == 0:
# 重置所有状态
self.pos_highest = 0.0
self.pos_lowest = 0.0
self.entry_price = 0.0
self.entry_signal_source = None # 重置信号来源
self.evaluate_entry_signal(open_price, trend_strength, trend_slope)
else:
self.manage_open_position(
position_volume,
trend_strength,
open_price,
current_atr,
current_high,
current_low
)
def calculate_market_state(self, prices: np.array) -> (float, float):
"""
计算频域能量占比和线性回归斜率
"""
if len(prices) < self.spectral_window:
return 0.0, 0.0
window_data = prices[-self.spectral_window:]
mean_val = np.mean(window_data)
std_val = np.std(window_data)
if std_val == 0: std_val = 1.0
normalized = (window_data - mean_val) / (std_val + 1e-8)
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:
return 0.0, 0.0
valid_mask = (f >= 0) & (f <= self.bars_per_day / 2)
f = f[valid_mask]
Zxx = Zxx[valid_mask, :]
if Zxx.size == 0: return 0.0, 0.0
current_energy = np.abs(Zxx[:, -1]) ** 2
low_mask = f < self.low_freq_bound
high_mask = f > self.high_freq_bound
low_energy = np.sum(current_energy[low_mask]) if np.any(low_mask) else 0.0
high_energy = np.sum(current_energy[high_mask]) if np.any(high_mask) else 0.0
total_energy = low_energy + high_energy + 1e-8
trend_strength = low_energy / total_energy
x = np.arange(len(normalized))
slope, _ = np.polyfit(x, normalized, 1)
return trend_strength, slope
def evaluate_entry_signal(self, open_price: float, trend_strength: float, trend_slope: float):
"""
入场逻辑优化:双因子控制
"""
# 1. 基础能量阈值检查
if trend_strength <= self.trend_strength_threshold:
return
# 2. 确定 STFT 原始方向 (Raw Direction)
raw_direction = None
if trend_slope > self.slope_threshold:
raw_direction = "BUY"
elif trend_slope < -self.slope_threshold:
raw_direction = "SELL"
if not raw_direction:
return
# 3. 双指标分支逻辑 (Dual-Indicator Branching)
# 获取指标计算所需的参数 (通常是 bar_history 等,依赖基类实现)
indicator_args = self.get_indicator_tuple()
final_direction = None
source_tag = None
# --- 分支 1: Primary Indicator (优先) ---
# 如果满足主条件 -> 使用原始方向 (Equivalent to reverse=False)
if self.indicator_primary.is_condition_met(*indicator_args):
final_direction = raw_direction
source_tag = "PRIMARY"
# --- 分支 2: Secondary Indicator (备选/Else) ---
# 如果不满足主条件,但满足备选条件 -> 使用反转方向 (Equivalent to reverse=True)
elif self.indicator_secondary.is_condition_met(*indicator_args):
final_direction = "SELL" if raw_direction == "BUY" else "BUY"
source_tag = "SECONDARY"
# --- 分支 3: 都不满足 ---
else:
return # 放弃交易
# 4. 全局模型过滤 (可选)
if not self.model_indicator.is_condition_met(*indicator_args):
return
# 5. 最终方向检查
if final_direction not in self.order_direction:
return
# 6. 执行开仓
self.log(
f"Entry Triggered [{source_tag}]: "
f"Raw={raw_direction} -> Final={final_direction} | "
f"Strength={trend_strength:.2f} | Slope={trend_slope:.4f}"
)
self.send_limit_order(final_direction, open_price, self.trade_volume, "OPEN")
# 更新状态
self.entry_price = open_price
self.pos_highest = open_price
self.pos_lowest = open_price
self.entry_signal_source = source_tag # 保存是由哪一个条件控制的
def manage_open_position(self, volume: int, trend_strength: float, current_price: float,
current_atr: float, high_price: float, low_price: float):
"""
离场逻辑 (保持不变,但日志中可以体现来源)
"""
exit_dir = "CLOSE_LONG" if volume > 0 else "CLOSE_SHORT"
# 更新极值
if volume > 0:
if high_price > self.pos_highest or self.pos_highest == 0:
self.pos_highest = high_price
else:
if (low_price < self.pos_lowest or self.pos_lowest == 0) and low_price > 0:
self.pos_lowest = low_price
# 1. 结构性止损 (Chandelier Exit)
is_stop_triggered = False
stop_line = 0.0
if current_atr > 0:
stop_distance = current_atr * self.sl_atr_multiplier
if volume > 0:
stop_line = self.pos_highest - stop_distance
if current_price <= stop_line:
is_stop_triggered = True
else:
stop_line = self.pos_lowest + stop_distance
if current_price >= stop_line:
is_stop_triggered = True
if is_stop_triggered:
self.log(
f"STOP Loss ({self.entry_signal_source}): Price {current_price} hit Chandelier line {stop_line:.2f}")
self.close_position(exit_dir, abs(volume))
return
# 2. 信号衰竭离场
if trend_strength < self.exit_threshold:
self.log(
f"Exit Signal ({self.entry_signal_source}): Energy Faded {trend_strength:.2f} < {self.exit_threshold}")
self.close_position(exit_dir, abs(volume))
return
# --- 交易辅助 ---
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}_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
)
self.send_order(order)
def send_limit_order(self, direction: str, limit_price: float, volume: int, offset: str):
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,
limit_price=limit_price
)
self.send_order(order)