DEV Community

Henry Lin
Henry Lin

Posted on

FreqTrade突破型趋势跟随策略

"""
突破型趋势跟随策略
基于价格突破关键阻力位或支撑位的趋势跟随策略
"""

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 BreakoutTrendStrategy(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
Enter fullscreen mode Exit fullscreen mode

Top comments (0)