1、新增dp策略
This commit is contained in:
860
src/strategies/TrendRevesal/AdaptiveStrategySwitch.ipynb
Normal file
860
src/strategies/TrendRevesal/AdaptiveStrategySwitch.ipynb
Normal file
File diff suppressed because one or more lines are too long
159
src/strategies/TrendRevesal/AdaptiveStrategySwitch.py
Normal file
159
src/strategies/TrendRevesal/AdaptiveStrategySwitch.py
Normal 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"
|
||||
821
src/strategies/TrendRevesal/MeanReversionStrategy.ipynb
Normal file
821
src/strategies/TrendRevesal/MeanReversionStrategy.ipynb
Normal file
File diff suppressed because one or more lines are too long
201
src/strategies/TrendRevesal/MeanReversionStrategy.py
Normal file
201
src/strategies/TrendRevesal/MeanReversionStrategy.py
Normal 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))
|
||||
663
src/strategies/TrendRevesal/TrendFollowStrategy.ipynb
Normal file
663
src/strategies/TrendRevesal/TrendFollowStrategy.ipynb
Normal file
File diff suppressed because one or more lines are too long
213
src/strategies/TrendRevesal/TrendFollowStrategy.py
Normal file
213
src/strategies/TrendRevesal/TrendFollowStrategy.py
Normal 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))
|
||||
Reference in New Issue
Block a user