# source: https://raw.githubusercontent.com/henrylin99/freqtrade-tutorials/691c3799846fc7ff6173a3b9241ce5a00b346141/user_data/strategies/ADXTrendStrategy.py
"""
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 Github_henrylin99_freqtrade_tutorials__ADXTrendStrategy__20251212_023733(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