1、新增dp策略

This commit is contained in:
2025-09-16 09:59:38 +08:00
parent 5cd926884d
commit 9a58fec9ca
120 changed files with 69683 additions and 325 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,159 @@
# (这个文件可以放在 src/strategies/adaptive_strategy.py 中)
from typing import Dict, Optional
from src.core_data import Bar, Order
from src.strategies.base_strategy import Strategy
from src.indicators.base_indicators import Indicator
# 导入我们之前创建的两个子策略和状态指示器
class AdaptiveStrategySwitch(Strategy):
"""
自适应策略切换器 (ECU)
根据一个核心的状态指示器,动态地将交易决策权委托给
趋势跟踪或均值回归子策略。
"""
def __init__(self, context, main_symbol: str,
trend_strategy,
mean_strategy,
trend_strategy_params: Dict,
mean_reversion_strategy_params: Dict,
regime_indicator,
enable_log: bool = False):
"""
初始化调度中心
:param context: 策略上下文
:param main_symbol: 交易主合约
:param trend_strategy_params: 一个包含趋势策略所有初始化参数的字典
:param mean_reversion_strategy_params: 一个包含均值回归策略所有初始化参数的字典
:param regime_indicator: 用于判断市场状态的状态指示器实例
"""
super().__init__(context, main_symbol, enable_log=enable_log)
# 1. 初始化并存储状态指示器 (大脑)
self.regime_indicator = regime_indicator
# 2. 使用传入的参数字典,初始化两个子策略 (发动机)
self.trend_strategy = trend_strategy(context, main_symbol, **trend_strategy_params)
self.reversion_strategy = mean_strategy(context, main_symbol, **mean_reversion_strategy_params)
# 3. 确定本策略所需的最少K线数量
self.min_bars_required = max(self.trend_strategy.min_bars_required,
self.reversion_strategy.min_bars_required,
getattr(self.regime_indicator, 'min_bars_required', 0)) + 1
# 4. 状态变量:记录当前是由哪个策略持仓
self.active_strategy: Optional[Strategy] = None
def on_start_trading(self):
self.trend_strategy.trading = True
self.reversion_strategy.trading = True
def on_open_bar(self, open_price: float, symbol: str):
"""
主循环:调度中心的核心逻辑
"""
self.trend_strategy.symbol = symbol
self.reversion_strategy.symbol = symbol
bars = self.get_bar_history()
if len(bars) < self.min_bars_required:
return
pos = self.get_current_positions().get(symbol, 0)
# --- 如果有持仓 ---
# 决策权必须交还给当初开仓的那个策略,让它自己处理出场逻辑
if pos != 0:
if self.active_strategy:
self.active_strategy.on_open_bar(open_price, symbol)
# 检查仓位是否已平掉
if self.get_current_positions().get(symbol, 0) == 0:
self.log(f"{self.active_strategy.__class__.__name__} 平仓,控制权交还调度中心。")
self.active_strategy = None # 重置状态,交还控制权
else:
# 紧急出口:如果出现状态不一致(有仓位但不知由谁开仓),则强制平仓
self.log(f"警告: 发现未知来源的仓位 {pos}, 强制平仓。")
direction = 'CLOSE_LONG' if pos > 0 else 'CLOSE_SHORT'
self._send_market_order(direction, abs(pos)) # 需要实现一个下单接口
return
# --- 如果无持仓 ---
# 由“大脑”决定现在应该由哪个“发动机”来尝试开仓
current_regime = self.regime_indicator.get_regime(self.get_bar_history())
self.log(f"当前市场状态: {current_regime}")
chosen_strategy = None
if current_regime == "TREND":
chosen_strategy = self.trend_strategy
elif current_regime == "REVERSION":
chosen_strategy = self.reversion_strategy
if chosen_strategy:
# 将控制权临时交给被选中的子策略
chosen_strategy.on_open_bar(open_price, symbol)
# 检查是否成功开仓,如果开仓则记录下来
if pos == 0:
self.active_strategy = chosen_strategy
# Helper function for emergency exit
def _send_market_order(self, direction: str, vol: int):
offset = 'CLOSE'
oid = f"{self.main_symbol}_{direction}_{self.get_current_time():%Y%m%d%H%M%S}_EMG"
self.send_order(Order(id=oid, symbol=self.main_symbol, direction=direction,
volume=vol, price_type='MARKET', offset=offset))
# (这个文件可以放在 src/indicators/indicators.py 中)
import pandas as pd
from src.indicators.base_indicators import Indicator
from src.core_data import Bar # 假设Bar类型定义在此
class EfficiencyRatioRegimeIndicator():
"""
市场效率比率状态指示器
根据市场运动的效率,判断当前市场更可能处于“趋势”还是“盘整”状态
"""
def __init__(self, period: int = 20,
trend_threshold: float = 0.3,
reversion_threshold: float = 0.2):
"""
:param period: 计算周期
:param trend_threshold: 效率高于此值,判断为 "TREND"
:param reversion_threshold: 效率低于此值,判断为 "REVERSION"
"""
if reversion_threshold >= trend_threshold:
raise ValueError("盘整阈值必须小于趋势阈值")
self.period = period
self.trend_threshold = trend_threshold
self.reversion_threshold = reversion_threshold
# 所需的最小K线数量
self.min_bars_required = self.period + 1
def get_regime(self, bars) -> str:
"""
核心方法:返回当前的市场状态
:param indicator_tuple: 包含历史K线数据的元组
:return: "TREND", "REVERSION", or "NEUTRAL"
"""
if len(bars) < self.min_bars_required:
return "NEUTRAL"
df = pd.DataFrame([vars(b) for b in bars[-self.period - 1:]])
net_change = abs(df['close'].iloc[-1] - df['close'].iloc[0])
total_movement = abs(df['close'].diff()).sum()
if total_movement == 0:
efficiency_ratio = 1.0 # 价格无波动,视为非盘整
else:
efficiency_ratio = net_change / total_movement
if efficiency_ratio > self.trend_threshold:
return "TREND"
else:
return "REVERSION"

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,201 @@
from datetime import timedelta, time, datetime
from typing import List, Union, Optional
import numpy as np
import pandas as pd
from src.core_data import Bar, Order
from src.indicators.base_indicators import Indicator
from src.strategies.base_strategy import Strategy
from src.indicators.indicators import Empty # 假设您有一个默认的空指标类
class MeanReversionStrategy(Strategy):
"""
机构级思路 · 均值回归策略
1. 核心信号: 基于统计学 Z-Score 的超卖/超买
2. 状态过滤: a) 长期趋势过滤 (e.g., MA) b) 市场效率比率 (Efficiency Ratio)
3. 出场逻辑: 固定周期或回归至均线
核心参数可单值(多空一致)或双值 [多头, 空头]
"""
def __init__(self, context, main_symbol: str,
trade_volume: int = 1,
z_score_period: Union[int, List[int]] = 20,
z_score_threshold: Union[float, List[float]] = 2.0,
long_term_ma_period: int = 200,
efficiency_period: Union[int, List[int]] = 20,
efficiency_threshold: Union[float, List[float]] = 0.2,
exit_bars: Union[int, List[int]] = 5,
order_direction: Optional[list] = None,
enable_log: bool = False,
indicators: List[Union[Indicator, List[Indicator]]] = None, ):
"""
初始化策略
:param context: 策略上下文,用于与回测框架交互
:param main_symbol: 交易的主合约代码
:param trade_volume: 基础交易手数
:param z_score_period: Z-Score 计算周期 [多头, 空头]
:param z_score_threshold: Z-Score 触发阈值 [多头, 空头]
:param long_term_ma_period: 长期趋势过滤的均线周期
:param efficiency_period: 市场效率计算周期 [多头, 空头]
:param efficiency_threshold: 市场效率过滤阈值 (低于此值为盘整) [多头, 空头]
:param exit_bars: 持仓多少根K线后强制退出 [多头, 空头]
:param order_direction: 允许的交易方向, e.g., ['BUY', 'SELL']
:param indicators: 外部注入的指标过滤器列表, 格式: [多头指标, 空头指标]
"""
super().__init__(context, main_symbol, enable_log=enable_log)
# --- 统一参数格式为 [多头, 空头] ---
def _to_two(v):
if isinstance(v, (int, float)):
return [v, v]
if len(v) == 2:
return [v[0], v[1]]
raise ValueError(f"{v} 需为单值或长度为 2 的列表")
self.z_score_period = _to_two(z_score_period)
self.z_score_threshold = _to_two(z_score_threshold)
self.efficiency_period = _to_two(efficiency_period)
self.efficiency_threshold = _to_two(efficiency_threshold)
self.exit_bars = _to_two(exit_bars)
self.long_term_ma_period = long_term_ma_period
self.symbol = main_symbol
self.volume = trade_volume
self.order_direction = order_direction or ['BUY', 'SELL']
self.min_bars_required = max(self.z_score_period + self.efficiency_period + [self.long_term_ma_period]) + 5
self.bars_since_entry = 0
if indicators is None:
indicators = [Empty(), Empty()]
self.indicators = indicators
# -------------------- 主循环 --------------------
def on_open_bar(self, open_price: float, symbol: str):
"""K线开盘时被调用"""
bars = self.get_bar_history()
if len(bars) < self.min_bars_required:
return
self.cancel_all_pending_orders(symbol)
pos = self.get_current_positions().get(symbol, 0)
# 如果有持仓,执行出场逻辑
if pos != 0:
self.bars_since_entry += 1
self._exit_logic(bars, pos)
return
# 如果无持仓,分别判断多头和空头的入场机会
for side_idx, direction in enumerate([1, -1]): # side_idx 0=多头, 1=空头
if (direction > 0 and 'BUY' not in self.order_direction) or \
(direction < 0 and 'SELL' not in self.order_direction):
continue
if self._entry_logic(bars, side_idx):
break
# -------------------- 入场逻辑 --------------------
def _entry_logic(self, bars: List[Bar], side_idx: int) -> bool:
"""
判断是否满足入场条件
:param bars: 历史K线数据
:param side_idx: 0 代表多头, 1 代表空头
:return: bool, 是否成功下单
"""
# --- 1. 数据准备 ---
df = pd.DataFrame([vars(b) for b in bars])
current_close = df['close'].iloc[-1]
# --- 2. 状态过滤器 (第一道): 长期趋势过滤 ---
# 核心原则:只在长期上升趋势中做多头回归,只在长期下降趋势中做空头回归
long_term_ma = df['close'].rolling(self.long_term_ma_period).mean().iloc[-1]
if side_idx == 0 and current_close < long_term_ma: # 多头信号必须在长期均线之上
return False
if side_idx == 1 and current_close > long_term_ma: # 空头信号必须在长期均线之下
return False
# --- 3. 状态过滤器 (第二道): 市场效率过滤 ---
eff_period = self.efficiency_period[side_idx]
net_change = abs(df['close'].iloc[-1] - df['close'].iloc[-eff_period - 1])
total_movement = abs(df['close'].diff()).iloc[-eff_period:].sum()
if total_movement == 0:
efficiency_ratio = 1 # 价格无波动,非盘整
else:
efficiency_ratio = net_change / total_movement
eff_thresh = self.efficiency_threshold[side_idx]
# 只有市场效率足够低(确认盘整),才允许回归模块启动
if efficiency_ratio > eff_thresh:
return False
# --- 4. 核心信号: Z-Score 超卖/超买 ---
z_period = self.z_score_period[side_idx]
moving_average = df['close'].rolling(z_period).mean().iloc[-1]
std_dev = df['close'].rolling(z_period).std().iloc[-1]
if std_dev == 0:
return False # 价格无波动无法计算Z-Score
z_score = (current_close - moving_average) / std_dev
z_thresh = self.z_score_threshold[side_idx]
signal = False
side_str = ''
if side_idx == 0: # 多头 (超卖)
if z_score < -z_thresh:
signal = True
side_str = 'BUY'
else: # 空头 (超买)
if z_score > z_thresh:
signal = True
side_str = 'SELL'
if not signal:
return False
# --- 5. 外部指标过滤器 ---
indicator_condition_met = self.indicators[side_idx].is_condition_met(*self.get_indicator_tuple())
if not indicator_condition_met:
return False
# --- 6. 执行下单 ---
self.log(f"触发{side_str}信号: 长期MA过滤通过, 效率比率={efficiency_ratio:.2f} < {eff_thresh}, "
f"Z-Score={z_score:.2f} 超过阈值 {z_thresh}")
self._send_market_order(side_str, self.volume)
self.bars_since_entry = 0
return True
# -------------------- 出场逻辑 --------------------
def _exit_logic(self, bars: List[Bar], pos: int):
"""
固定周期出场
"""
side_idx = 0 if pos > 0 else 1
# 检查是否达到持仓K线数量上限
if self.bars_since_entry >= self.exit_bars[side_idx]:
side_str = 'CLOSE_LONG' if pos > 0 else 'CLOSE_SHORT'
self.log(f"达到持仓周期 {self.exit_bars[side_idx]} K线, {side_str}离场")
self._send_market_order(side_str, abs(pos))
# (可选的高级出场逻辑)
# df = pd.DataFrame([vars(b) for b in bars])
# z_period = self.z_score_period[side_idx]
# moving_average = df['close'].rolling(z_period).mean().iloc[-1]
# current_close = df['close'].iloc[-1]
# if (pos > 0 and current_close >= moving_average) or \
# (pos < 0 and current_close <= moving_average):
# # 回归至均线,提前离场
# ...
# -------------------- 下单接口 --------------------
def _send_market_order(self, direction: str, vol: int):
"""发送市价单"""
offset = 'OPEN' if direction in ('BUY', 'SELL') else 'CLOSE'
oid = f"{self.symbol}_{direction}_{self.get_current_time():%Y%m%d%H%M%S}"
self.send_order(Order(id=oid, symbol=self.symbol, direction=direction,
volume=vol, price_type='MARKET', offset=offset))

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,213 @@
from datetime import timedelta, time, datetime
from typing import List, Union, Optional
import numpy as np
import pandas as pd
from src.core_data import Bar, Order
from src.indicators.base_indicators import Indicator
from src.strategies.base_strategy import Strategy
from src.indicators.indicators import Empty # 假设您有一个默认的空指标类
class TrendFollowStrategy(Strategy):
"""
机构级思路 · 趋势跟踪策略
1. 核心信号: 唐奇安通道突破 (Donchian Channel)
2. 状态过滤: 市场效率比率 (Efficiency Ratio)
3. 风险管理: 基于ATR的动态头寸规模 (未在此处实现建议在更高层portfolio中管理)
核心参数可单值(多空一致)或双值 [多头, 空头]
"""
def __init__(self, context, main_symbol: str,
trade_volume: int = 1,
donchian_period: Union[int, List[int]] = 20,
efficiency_period: Union[int, List[int]] = 20,
efficiency_threshold: Union[float, List[float]] = 0.3,
exit_atr_multiplier: Union[float, List[float]] = 2.0,
exit_atr_period: int = 14,
order_direction: Optional[list] = None,
enable_log: bool = False,
indicators: List[Union[Indicator, List[Indicator]]] = None, ):
"""
初始化策略
:param context: 策略上下文,用于与回测框架交互
:param main_symbol: 交易的主合约代码
:param trade_volume: 基础交易手数 (注意: 专业的做法是基于ATR动态计算)
:param donchian_period: 唐奇安通道周期 [多头, 空头]
:param efficiency_period: 市场效率计算周期 [多头, 空头]
:param efficiency_threshold: 市场效率过滤阈值 [多头, 空头]
:param exit_atr_multiplier: ATR跟踪止损的倍数 [多头, 空头]
:param exit_atr_period: ATR计算周期
:param order_direction: 允许的交易方向, e.g., ['BUY', 'SELL']
:param indicators: 外部注入的指标过滤器列表, 格式: [多头指标, 空头指标]
"""
super().__init__(context, main_symbol, enable_log=enable_log)
# --- 统一参数格式为 [多头, 空头] ---
def _to_two(v):
if isinstance(v, (int, float)):
return [v, v]
if len(v) == 2:
return [v[0], v[1]]
raise ValueError(f"{v} 需为单值或长度为 2 的列表")
self.donchian_period = _to_two(donchian_period)
self.efficiency_period = _to_two(efficiency_period)
self.efficiency_threshold = _to_two(efficiency_threshold)
self.exit_atr_multiplier = _to_two(exit_atr_multiplier)
self.exit_atr_period = exit_atr_period
self.symbol = main_symbol
self.volume = trade_volume
self.order_direction = order_direction or ['BUY', 'SELL']
# 计算所需的最少K线数量取所有周期参数的最大值
self.min_bars_required = max(self.donchian_period + self.efficiency_period + [self.exit_atr_period]) + 5
self.entry_price = 0.0
self.trailing_stop_price = 0.0
if indicators is None:
# 如果未提供指标,则使用默认的“永远为真”的空指标
indicators = [Empty(), Empty()]
self.indicators = indicators
# -------------------- 主循环 --------------------
def on_open_bar(self, open_price: float, symbol: str):
"""K线开盘时被调用"""
bars = self.get_bar_history()
if len(bars) < self.min_bars_required:
return
self.cancel_all_pending_orders(symbol)
pos = self.get_current_positions().get(symbol, 0)
# 如果有持仓,执行出场和止损逻辑
if pos != 0:
self._trailing_stop_logic(bars, pos)
return
# 如果无持仓,分别判断多头和空头的入场机会
# 多空分别判断,互斥,一次只开一侧
for side_idx, direction in enumerate([1, -1]): # side_idx 0=多头, 1=空头
if (direction > 0 and 'BUY' not in self.order_direction) or \
(direction < 0 and 'SELL' not in self.order_direction):
continue
# _entry_logic 返回 True 表示已成功下单
if self._entry_logic(bars, side_idx):
break
# -------------------- 入场逻辑 --------------------
def _entry_logic(self, bars: List[Bar], side_idx: int) -> bool:
"""
判断是否满足入场条件
:param bars: 历史K线数据
:param side_idx: 0 代表多头, 1 代表空头
:return: bool, 是否成功下单
"""
# --- 1. 数据准备 ---
df = pd.DataFrame([vars(b) for b in bars])
# --- 2. 状态过滤器: 计算市场效率 ---
eff_period = self.efficiency_period[side_idx]
net_change = abs(df['close'].iloc[-1] - df['close'].iloc[-eff_period - 1])
total_movement = abs(df['close'].diff()).iloc[-eff_period:].sum()
# 避免除以零
if total_movement == 0:
efficiency_ratio = 0
else:
efficiency_ratio = net_change / total_movement
eff_thresh = self.efficiency_threshold[side_idx]
# 如果市场效率过低(盘整状态),则直接返回,不触发任何信号
if efficiency_ratio < eff_thresh:
return False
# --- 3. 核心信号: 唐奇安通道突破 ---
don_period = self.donchian_period[side_idx]
highest_high = df['high'].iloc[-don_period - 1:-1].max()
lowest_low = df['low'].iloc[-don_period - 1:-1].min()
current_close = df['close'].iloc[-1]
signal = False
side_str = ''
if side_idx == 0: # 多头
if current_close > highest_high:
signal = True
side_str = 'BUY'
else: # 空头
if current_close < lowest_low:
signal = True
side_str = 'SELL'
if not signal:
return False
# --- 4. 外部指标过滤器 ---
# 只有在核心信号和市场状态都满足后,才检查外部指标
indicator_condition_met = self.indicators[side_idx].is_condition_met(*self.get_indicator_tuple())
if not indicator_condition_met:
return False
# --- 5. 执行下单 ---
self.log(f"触发{side_str}信号: 效率比率={efficiency_ratio:.2f} > {eff_thresh}, "
f"价格={current_close}突破通道[{lowest_low}, {highest_high}]")
self._send_market_order(side_str, self.volume)
self.entry_price = current_close # 简化处理,实际应使用成交回报价
return True
# -------------------- 出场与止损 --------------------
def _trailing_stop_logic(self, bars: List[Bar], pos: int):
"""
ATR 跟踪止损逻辑
"""
df = pd.DataFrame([vars(b) for b in bars])
# 计算ATR
high_low = df['high'] - df['low']
high_close = abs(df['high'] - df['close'].shift())
low_close = abs(df['low'] - df['close'].shift())
tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
atr = tr.ewm(span=self.exit_atr_period, adjust=False).mean().iloc[-1]
current_close = df['close'].iloc[-1]
if pos > 0: # 持有多仓
atr_multiplier = self.exit_atr_multiplier[0]
# 止损价只上移,不下移
new_stop_price = current_close - atr_multiplier * atr
self.trailing_stop_price = max(self.trailing_stop_price, new_stop_price)
if current_close <= self.trailing_stop_price:
self.log(f"多头ATR止损触发: 价格={current_close} <= 止损价={self.trailing_stop_price:.2f}")
self._send_market_order('CLOSE_LONG', abs(pos))
elif pos < 0: # 持有空仓
atr_multiplier = self.exit_atr_multiplier[1]
# 止损价只下移,不上移
new_stop_price = current_close + atr_multiplier * atr
# 初始化时 self.trailing_stop_price 为 0需特殊处理
if self.trailing_stop_price == 0.0:
self.trailing_stop_price = new_stop_price
else:
self.trailing_stop_price = min(self.trailing_stop_price, new_stop_price)
if current_close >= self.trailing_stop_price:
self.log(f"空头ATR止损触发: 价格={current_close} >= 止损价={self.trailing_stop_price:.2f}")
self._send_market_order('CLOSE_SHORT', abs(pos))
# -------------------- 下单接口 --------------------
def _send_market_order(self, direction: str, vol: int):
"""发送市价单"""
offset = 'OPEN' if direction in ('BUY', 'SELL') else 'CLOSE'
# 重置入场状态
if offset == 'OPEN':
self.trailing_stop_price = 0.0
oid = f"{self.symbol}_{direction}_{self.get_current_time():%Y%m%d%H%M%S}"
self.send_order(Order(id=oid, symbol=self.symbol, direction=direction,
volume=vol, price_type='MARKET', offset=offset))