This content originally appeared on DEV Community and was authored by Henry Lin
"""
ADX趋势强度策略
基于ADX指标识别和跟随强趋势的策略
"""
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
from pandas import DataFrame
import talib.abstract as ta
from technical import qtpylib
import numpy as np
import logging
logger = logging.getLogger(__name__)
class ADXTrendStrategy(IStrategy):
"""
ADX趋势强度策略
策略逻辑:
1. 使用ADX识别趋势强度
2. 使用DI+和DI-确定趋势方向
3. 当ADX上升且DI+>DI-时买入
4. 当ADX下降或DI->DI+时卖出
5. 结合其他指标过滤假信号
"""
INTERFACE_VERSION = 3
# 基础配置
minimal_roi = {
"0": 0.20, # 20%收益立即止盈
"60": 0.12, # 1小时后12%收益
"120": 0.08, # 2小时后8%收益
"240": 0.04, # 4小时后4%收益
"480": 0.02 # 8小时后2%收益
}
stoploss = -0.06 # 6%止损
timeframe = '1h' # 1小时时间框架
# 策略控制
can_short = False
startup_candle_count = 100
process_only_new_candles = True
use_exit_signal = True
use_custom_stoploss = True
# 可优化参数
# ADX参数
adx_period = IntParameter(10, 20, default=14, space="buy")
adx_threshold_strong = IntParameter(25, 40, default=30, space="buy")
adx_threshold_weak = IntParameter(15, 25, default=20, space="sell")
# DI差值参数
di_diff_threshold = DecimalParameter(2.0, 8.0, default=5.0, space="buy")
# ADX趋势参数
adx_slope_periods = IntParameter(3, 7, default=5, space="buy")
adx_min_slope = DecimalParameter(0.5, 3.0, default=1.5, space="buy")
# 确认指标参数
# EMA趋势确认
ema_fast = IntParameter(8, 20, default=12, space="buy")
ema_slow = IntParameter(24, 40, default=30, space="buy")
# RSI过滤
rsi_period = IntParameter(10, 20, default=14, space="buy")
rsi_buy_threshold = IntParameter(45, 60, default=50, space="buy")
rsi_sell_threshold = IntParameter(65, 85, default=75, space="sell")
# MACD确认
macd_fast = IntParameter(8, 16, default=12, space="buy")
macd_slow = IntParameter(21, 35, default=26, space="buy")
macd_signal = IntParameter(7, 12, default=9, space="buy")
# 成交量参数
volume_factor = DecimalParameter(1.2, 2.5, default=1.6, space="buy")
# ATR止损参数
atr_period = IntParameter(10, 20, default=14, space="sell")
atr_multiplier = DecimalParameter(2.0, 4.0, default=2.5, space="sell")
# 抛物线SAR参数
sar_acceleration = DecimalParameter(0.01, 0.05, default=0.02, space="buy")
sar_maximum = DecimalParameter(0.1, 0.3, default=0.2, space="buy")
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
计算技术指标
"""
# ADX指标组
dataframe['adx'] = ta.ADX(dataframe, timeperiod=self.adx_period.value)
dataframe['di_plus'] = ta.PLUS_DI(dataframe, timeperiod=self.adx_period.value)
dataframe['di_minus'] = ta.MINUS_DI(dataframe, timeperiod=self.adx_period.value)
# DI差值和比率
dataframe['di_diff'] = dataframe['di_plus'] - dataframe['di_minus']
dataframe['di_ratio'] = dataframe['di_plus'] / (dataframe['di_minus'] + 0.001) # 避免除零
# ADX趋势和斜率
dataframe['adx_slope'] = (dataframe['adx'] - dataframe['adx'].shift(self.adx_slope_periods.value)) / self.adx_slope_periods.value
dataframe['adx_rising'] = dataframe['adx_slope'] > self.adx_min_slope.value
dataframe['adx_falling'] = dataframe['adx_slope'] < -self.adx_min_slope.value
# ADX强度分类
dataframe['adx_very_strong'] = dataframe['adx'] > 50
dataframe['adx_strong'] = (dataframe['adx'] > self.adx_threshold_strong.value) & (dataframe['adx'] <= 50)
dataframe['adx_moderate'] = (dataframe['adx'] > 25) & (dataframe['adx'] <= self.adx_threshold_strong.value)
dataframe['adx_weak'] = dataframe['adx'] <= self.adx_threshold_weak.value
# 趋势方向
dataframe['bullish_trend'] = (dataframe['di_plus'] > dataframe['di_minus']) & (dataframe['di_diff'] > self.di_diff_threshold.value)
dataframe['bearish_trend'] = (dataframe['di_minus'] > dataframe['di_plus']) & (dataframe['di_diff'] < -self.di_diff_threshold.value)
# EMA趋势确认
dataframe['ema_fast'] = ta.EMA(dataframe, timeperiod=self.ema_fast.value)
dataframe['ema_slow'] = ta.EMA(dataframe, timeperiod=self.ema_slow.value)
dataframe['ema_trend_up'] = dataframe['ema_fast'] > dataframe['ema_slow']
dataframe['price_above_ema_fast'] = dataframe['close'] > dataframe['ema_fast']
# RSI
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)
# MACD
macd = ta.MACD(dataframe,
fastperiod=self.macd_fast.value,
slowperiod=self.macd_slow.value,
signalperiod=self.macd_signal.value)
dataframe['macd'] = macd['macd']
dataframe['macd_signal'] = macd['macdsignal']
dataframe['macd_hist'] = macd['macdhist']
dataframe['macd_bullish'] = dataframe['macd'] > dataframe['macd_signal']
# ATR
dataframe['atr'] = ta.ATR(dataframe, timeperiod=self.atr_period.value)
dataframe['atr_percent'] = dataframe['atr'] / dataframe['close']
# 抛物线SAR
dataframe['sar'] = ta.SAR(dataframe,
acceleration=self.sar_acceleration.value,
maximum=self.sar_maximum.value)
dataframe['sar_bullish'] = dataframe['close'] > dataframe['sar']
# 成交量指标
dataframe['volume_sma'] = dataframe['volume'].rolling(window=20).mean()
dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_sma']
# 布林带
bollinger = qtpylib.bollinger_bands(dataframe['close'], window=20, stds=2)
dataframe['bb_upper'] = bollinger['upper']
dataframe['bb_middle'] = bollinger['mid']
dataframe['bb_lower'] = bollinger['lower']
dataframe['bb_percent'] = (dataframe['close'] - dataframe['bb_lower']) / (dataframe['bb_upper'] - dataframe['bb_lower'])
# 价格动量
dataframe['price_momentum'] = (dataframe['close'] - dataframe['close'].shift(5)) / dataframe['close'].shift(5)
# Aroon指标(趋势确认)
aroon = ta.AROON(dataframe, timeperiod=14)
dataframe['aroon_up'] = aroon['aroonup']
dataframe['aroon_down'] = aroon['aroondown']
dataframe['aroon_osc'] = dataframe['aroon_up'] - dataframe['aroon_down']
# 趋势综合评分
# 基于多个指标的趋势强度评分
trend_score = 0
trend_score += np.where(dataframe['bullish_trend'], 2, 0) # ADX方向
trend_score += np.where(dataframe['adx_strong'] | dataframe['adx_very_strong'], 2, 0) # ADX强度
trend_score += np.where(dataframe['adx_rising'], 1, 0) # ADX上升
trend_score += np.where(dataframe['ema_trend_up'], 1, 0) # EMA趋势
trend_score += np.where(dataframe['price_above_ema_fast'], 1, 0) # 价格位置
trend_score += np.where(dataframe['macd_bullish'], 1, 0) # MACD
trend_score += np.where(dataframe['sar_bullish'], 1, 0) # SAR
trend_score += np.where(dataframe['aroon_osc'] > 20, 1, 0) # Aroon
dataframe['trend_score'] = trend_score
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
定义买入条件 - ADX趋势策略
"""
conditions = [
# 主要ADX信号
dataframe['bullish_trend'], # DI+>DI-且差值足够大
dataframe['adx'] > self.adx_threshold_strong.value, # ADX强度足够
dataframe['adx_rising'], # ADX上升趋势
# 趋势确认
dataframe['ema_trend_up'], # EMA趋势向上
dataframe['price_above_ema_fast'], # 价格在快EMA之上
# RSI确认
dataframe['rsi'] > self.rsi_buy_threshold.value,
dataframe['rsi'] < 80, # 避免极度超买
# MACD确认
dataframe['macd_bullish'],
dataframe['macd_hist'] > 0,
dataframe['macd_hist'] > dataframe['macd_hist'].shift(1), # MACD柱状图增强
# SAR确认
dataframe['sar_bullish'],
# 成交量确认
dataframe['volume_ratio'] > self.volume_factor.value,
# 价格动量确认
dataframe['price_momentum'] > 0.01, # 至少1%的价格动量
# Aroon确认
dataframe['aroon_osc'] > 20, # Aroon振荡器显示上升趋势
# 布林带位置
dataframe['bb_percent'] > 0.3, # 不在布林带下轨附近
dataframe['bb_percent'] < 0.9, # 不在布林带上轨附近
# 综合趋势评分
dataframe['trend_score'] >= 6, # 趋势评分足够高
# ADX强度分类
dataframe['adx_strong'] | dataframe['adx_very_strong'],
]
# 组合条件
dataframe.loc[
(
conditions[0] & # 牛市趋势
conditions[1] & # ADX强度
conditions[2] & # ADX上升
conditions[3] & # EMA趋势
conditions[4] & # 价格位置
conditions[5] & # RSI下限
conditions[6] & # RSI上限
conditions[7] & # MACD信号
conditions[8] & # MACD柱状图
conditions[9] & # MACD增强
conditions[10] & # SAR信号
conditions[11] & # 成交量
conditions[12] & # 价格动量
conditions[13] & # Aroon信号
conditions[14] & # 布林带下限
conditions[15] & # 布林带上限
conditions[16] & # 趋势评分
conditions[17] # ADX强度分类
),
'enter_long'
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
定义卖出条件
"""
conditions = [
# 主要ADX信号转向
dataframe['bearish_trend'], # DI->DI+
dataframe['adx'] < self.adx_threshold_weak.value, # ADX弱化
dataframe['adx_falling'], # ADX下降
# 趋势转向
~dataframe['ema_trend_up'], # EMA趋势向下
dataframe['close'] < dataframe['ema_fast'], # 价格跌破快EMA
# RSI转向
dataframe['rsi'] > self.rsi_sell_threshold.value, # RSI超买
dataframe['rsi'] < 40, # RSI转弱
# MACD转向
~dataframe['macd_bullish'], # MACD死叉
dataframe['macd_hist'] < 0, # MACD柱状图转负
# SAR转向
~dataframe['sar_bullish'], # SAR信号转向
# Aroon转向
dataframe['aroon_osc'] < -20, # Aroon显示下降趋势
# 综合趋势评分下降
dataframe['trend_score'] <= 3,
# DI差值收窄
abs(dataframe['di_diff']) < 2, # DI差值过小,趋势不明确
]
dataframe.loc[
(
conditions[0] | # 熊市趋势
conditions[1] | # ADX弱化
conditions[2] | # ADX下降
conditions[3] | # EMA趋势转向
conditions[4] | # 价格跌破EMA
conditions[5] | # RSI超买
conditions[6] | # RSI转弱
conditions[7] | # MACD死叉
conditions[8] | # MACD柱状图
conditions[9] | # SAR转向
conditions[10] | # Aroon转向
conditions[11] | # 趋势评分下降
conditions[12] # DI差值收窄
),
'exit_long'
] = 1
return dataframe
def custom_stoploss(self, pair: str, trade, current_time, current_rate: float,
current_profit: float, **kwargs) -> float:
"""
基于ADX的动态止损
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
# ATR动态止损
atr_distance = last_candle['atr'] * self.atr_multiplier.value
atr_stop_distance = atr_distance / current_rate
# 基于ADX强度的止损调整
adx_value = last_candle['adx']
if current_profit > 0.10: # 盈利超过10%
if adx_value > 40: # 极强趋势
return max(-atr_stop_distance * 0.6, -0.015) # 紧跟趋势
elif adx_value > 30: # 强趋势
return max(-atr_stop_distance * 0.7, -0.02)
else: # 趋势减弱
return max(-atr_stop_distance * 0.8, -0.025)
elif current_profit > 0.05: # 盈利超过5%
if adx_value > 35:
return max(-atr_stop_distance * 0.7, -0.025)
else:
return max(-atr_stop_distance * 0.9, -0.03)
elif current_profit > 0.02: # 盈利超过2%
return max(-atr_stop_distance * 0.8, -0.035)
else:
# ADX策略需要给趋势发展更多空间
if adx_value > 30:
return max(-atr_stop_distance * 1.0, self.stoploss)
else:
return max(-atr_stop_distance * 1.2, self.stoploss)
def custom_exit(self, pair: str, trade, current_time, current_rate: float,
current_profit: float, **kwargs) -> str:
"""
基于ADX的自定义退出
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
# ADX急剧下降退出
if (last_candle['adx'] < 20 and
last_candle['adx_slope'] < -2 and
current_profit > 0.02):
return "adx_collapse"
# DI交叉退出
if (last_candle['di_minus'] > last_candle['di_plus'] and
abs(last_candle['di_diff']) > 3 and
current_profit > 0.01):
return "di_crossover"
# 趋势评分急剧下降
if (last_candle['trend_score'] <= 2 and
current_profit > 0.02):
return "trend_score_collapse"
# SAR反转
if (not last_candle['sar_bullish'] and
current_profit > 0.03):
return "sar_reversal"
# 时间止损
trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600
if trade_duration > 48 and current_profit < 0.01: # 48小时无显著盈利
return "time_exit"
return None
def confirm_trade_entry(self, pair: str, order_type: str, amount: float,
rate: float, time_in_force: str, current_time,
entry_tag: str, side: str, **kwargs) -> bool:
"""
ADX策略特有的交易确认
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) < 1:
return False
last_candle = dataframe.iloc[-1].squeeze()
# 确保ADX足够强
if last_candle['adx'] < self.adx_threshold_strong.value:
return False
# 确保DI差值足够大
if last_candle['di_diff'] < self.di_diff_threshold.value:
return False
# 确保ADX正在上升
if not last_candle['adx_rising']:
return False
# 确保趋势评分足够高
if last_candle['trend_score'] < 6:
return False
# 确保不是极端市况
if last_candle['atr_percent'] > 0.08: # 波动率过大
return False
# 确保价格不在极端位置
if last_candle['rsi'] > 85 or last_candle['rsi'] < 20:
return False
return True
This content originally appeared on DEV Community and was authored by Henry Lin