# source: https://raw.githubusercontent.com/henrylin99/freqtrade-tutorials/05528803bacc805a08027b6cf006694a31620e20/user_data/strategies/BreakoutTrendStrategy.py
"""
突破型趋势跟随策略
基于价格突破关键阻力位或支撑位的趋势跟随策略
"""

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__BreakoutTrendStrategy__20251215_080324(IStrategy):
    """
    突破型趋势跟随策略
    
    策略逻辑:
    1. 识别关键的支撑阻力位
    2. 当价格突破阻力位时买入
    3. 当价格跌破支撑位时卖出
    4. 结合成交量确认突破有效性
    5. 使用布林带和ATR确认突破强度
    """
    
    INTERFACE_VERSION = 3
    
    # 基础配置
    minimal_roi = {
        "0": 0.20,      # 20%收益立即止盈
        "30": 0.12,     # 30分钟后12%收益
        "60": 0.08,     # 1小时后8%收益
        "120": 0.04,    # 2小时后4%收益
        "240": 0.02     # 4小时后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
    
    # 可优化参数
    # 突破识别参数
    lookback_period = IntParameter(10, 30, default=20, space="buy")
    breakout_threshold = DecimalParameter(0.5, 3.0, default=1.5, space="buy")  # 突破阈值（%）
    
    # 成交量确认参数
    volume_factor = DecimalParameter(1.5, 4.0, default=2.5, space="buy")
    volume_lookback = IntParameter(10, 25, default=15, space="buy")
    
    # 布林带参数
    bb_period = IntParameter(15, 25, default=20, space="buy")
    bb_std = DecimalParameter(1.8, 2.5, default=2.0, space="buy")
    
    # ATR参数
    atr_period = IntParameter(10, 20, default=14, space="buy")
    atr_multiplier = DecimalParameter(2.0, 4.0, default=3.0, space="sell")
    
    # 趋势过滤参数
    ema_fast = IntParameter(10, 25, default=20, space="buy")
    ema_slow = IntParameter(40, 80, default=60, space="buy")
    
    # RSI过滤参数
    rsi_period = IntParameter(10, 20, default=14, space="buy")
    rsi_threshold = IntParameter(40, 60, default=50, space="buy")
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        计算技术指标
        """
        
        # 高低点识别
        dataframe['highest'] = dataframe['high'].rolling(window=self.lookback_period.value).max()
        dataframe['lowest'] = dataframe['low'].rolling(window=self.lookback_period.value).min()
        
        # 阻力支撑位
        dataframe['resistance'] = dataframe['highest'].shift(1)
        dataframe['support'] = dataframe['lowest'].shift(1)
        
        # 真实突破
        dataframe['true_breakout_up'] = (
            (dataframe['close'] > dataframe['resistance']) &
            (dataframe['close'] > dataframe['resistance'] * (1 + self.breakout_threshold.value / 100))
        )
        dataframe['true_breakdown'] = (
            (dataframe['close'] < dataframe['support']) &
            (dataframe['close'] < dataframe['support'] * (1 - self.breakout_threshold.value / 100))
        )
        
        # 布林带
        bollinger = qtpylib.bollinger_bands(
            dataframe['close'], 
            window=self.bb_period.value, 
            stds=self.bb_std.value
        )
        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['bb_width'] = (dataframe['bb_upper'] - dataframe['bb_lower']) / dataframe['bb_middle']
        
        # 布林带突破
        dataframe['bb_breakout_up'] = dataframe['close'] > dataframe['bb_upper']
        dataframe['bb_breakout_down'] = dataframe['close'] < dataframe['bb_lower']
        
        # ATR波动率
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=self.atr_period.value)
        dataframe['atr_percent'] = dataframe['atr'] / dataframe['close']
        
        # 趋势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'] = dataframe['ema_fast'] > dataframe['ema_slow']
        
        # RSI
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)
        
        # 成交量指标
        dataframe['volume_sma'] = dataframe['volume'].rolling(window=self.volume_lookback.value).mean()
        dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_sma']
        
        # 价格变动幅度
        dataframe['price_change'] = (dataframe['close'] - dataframe['close'].shift(1)) / dataframe['close'].shift(1)
        dataframe['price_change_abs'] = abs(dataframe['price_change'])
        
        # MACD
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macd_signal'] = macd['macdsignal']
        dataframe['macd_hist'] = macd['macdhist']
        
        # ADX趋势强度
        dataframe['adx'] = ta.ADX(dataframe, timeperiod=14)
        dataframe['di_plus'] = ta.PLUS_DI(dataframe, timeperiod=14)
        dataframe['di_minus'] = ta.MINUS_DI(dataframe, timeperiod=14)
        
        # 唐奇安通道
        dataframe['donchian_upper'] = dataframe['high'].rolling(window=20).max()
        dataframe['donchian_lower'] = dataframe['low'].rolling(window=20).min()
        dataframe['donchian_breakout_up'] = dataframe['close'] > dataframe['donchian_upper'].shift(1)
        dataframe['donchian_breakout_down'] = dataframe['close'] < dataframe['donchian_lower'].shift(1)
        
        # 相对位置
        dataframe['price_position'] = (dataframe['close'] - dataframe['lowest']) / (dataframe['highest'] - dataframe['lowest'])
        
        # 突破强度
        dataframe['breakout_strength'] = (
            (dataframe['close'] - dataframe['resistance']) / dataframe['resistance']
        ).fillna(0)
        
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        定义买入条件 - 突破策略
        """
        
        conditions = [
            # 主要信号：真实向上突破
            dataframe['true_breakout_up'] | dataframe['donchian_breakout_up'],
            
            # 成交量放大确认
            dataframe['volume_ratio'] > self.volume_factor.value,
            
            # 趋势确认：EMA排列向上
            dataframe['ema_trend'],
            
            # 价格高于EMA快线
            dataframe['close'] > dataframe['ema_fast'],
            
            # RSI不能过度超买
            dataframe['rsi'] > self.rsi_threshold.value,
            dataframe['rsi'] < 85,
            
            # ADX趋势强度确认
            dataframe['adx'] > 25,
            dataframe['di_plus'] > dataframe['di_minus'],
            
            # 突破幅度足够
            dataframe['breakout_strength'] > 0.01,  # 至少1%突破
            
            # 价格变动幅度适中
            dataframe['price_change_abs'] > 0.005,  # 有足够的变动
            dataframe['price_change_abs'] < 0.08,   # 不要过度波动
            
            # MACD确认
            dataframe['macd'] > dataframe['macd_signal'],
            dataframe['macd_hist'] > 0,
            
            # 布林带确认
            dataframe['bb_width'] > 0.02,  # 足够的波动率
            
            # 避免在极端位置买入
            dataframe['price_position'] < 0.95,
        ]
        
        # 组合条件
        dataframe.loc[
            (
                conditions[0] &   # 突破信号
                conditions[1] &   # 成交量
                conditions[2] &   # EMA趋势
                conditions[3] &   # 价格位置
                conditions[4] &   # RSI下限
                conditions[5] &   # RSI上限
                conditions[6] &   # ADX强度
                conditions[7] &   # DI确认
                conditions[8] &   # 突破幅度
                conditions[9] &   # 价格变动下限
                conditions[10] &  # 价格变动上限
                conditions[11] &  # MACD
                conditions[12] &  # MACD柱状图
                conditions[13] &  # 布林带宽度
                conditions[14]    # 价格位置上限
            ),
            'enter_long'
        ] = 1
        
        return dataframe
    
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        定义卖出条件
        """
        
        conditions = [
            # 主要信号：向下突破支撑
            dataframe['true_breakdown'] | dataframe['donchian_breakout_down'],
            
            # 趋势转向
            ~dataframe['ema_trend'],
            
            # 价格跌破快速EMA
            dataframe['close'] < dataframe['ema_fast'],
            
            # RSI超买或转弱
            dataframe['rsi'] > 80,
            
            # ADX下降
            dataframe['adx'] < 20,
            
            # DI转向
            dataframe['di_minus'] > dataframe['di_plus'],
            
            # MACD转弱
            dataframe['macd'] < dataframe['macd_signal'],
            
            # 价格接近布林带下轨
            dataframe['bb_percent'] < 0.2,
        ]
        
        dataframe.loc[
            (
                conditions[0] |   # 向下突破
                conditions[1] |   # 趋势转向
                conditions[2] |   # 跌破EMA
                conditions[3] |   # RSI超买
                conditions[4] |   # ADX弱化
                conditions[5] |   # DI转向
                conditions[6] |   # MACD转弱
                conditions[7]     # 布林带下轨
            ),
            'exit_long'
        ] = 1
        
        return dataframe
    
    def custom_stoploss(self, pair: str, trade, current_time, current_rate: float,
                       current_profit: float, **kwargs) -> float:
        """
        动态止损策略 - 突破策略特有
        """
        
        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
        
        # 支撑位止损
        support_distance = abs(current_rate - last_candle['support']) / current_rate
        
        # 突破策略的特殊止损逻辑
        if current_profit > 0.10:  # 盈利超过10%
            # 使用支撑位和ATR的较严格者
            return max(min(-support_distance * 0.8, -atr_stop_distance * 0.5), -0.02)
        elif current_profit > 0.05:  # 盈利超过5%
            return max(-atr_stop_distance * 0.7, -0.03)
        elif current_profit > 0.02:  # 盈利超过2%
            return max(-atr_stop_distance * 0.8, -0.04)
        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:
        """
        自定义退出逻辑
        """
        
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        
        # 突破失败退出
        if (current_rate < trade.open_rate * 0.98 and  # 跌破入场价2%
            last_candle['volume_ratio'] < 1.0):  # 成交量萎缩
            return "breakout_failure"
        
        # 假突破识别
        if (current_profit < -0.02 and
            last_candle['close'] < last_candle['resistance'] * 0.99):
            return "false_breakout"
        
        # 动能衰减
        if (current_profit > 0.05 and
            last_candle['rsi'] < 50 and
            last_candle['macd_hist'] < 0):
            return "momentum_exhaustion"
        
        # 时间止损
        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:
        """
        交易确认 - 突破策略特有验证
        """
        
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if len(dataframe) < 1:
            return False
            
        last_candle = dataframe.iloc[-1].squeeze()
        
        # 确保是真实突破
        if last_candle['breakout_strength'] < 0.008:  # 突破幅度不足
            return False
        
        # 确保成交量足够
        if last_candle['volume_ratio'] < self.volume_factor.value * 0.8:
            return False
        
        # 确保不是在极端波动中
        if last_candle['atr_percent'] > 0.08:
            return False
        
        # 确保趋势明确
        if last_candle['adx'] < 20:
            return False
        
        # 检查是否在合适的价格位置
        if last_candle['price_position'] > 0.95:  # 价格位置过高
            return False
            
        return True