DEV Community

Henry Lin
Henry Lin

Posted on

均值回归策略-基于布林带的均值回归交易策略

"""
均值回归策略
基于布林带的均值回归交易策略
"""

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 MeanReversionStrategy(IStrategy):
    """
    均值回归策略 - 基于布林带的反转交易

    策略逻辑:
    1. 使用布林带识别超买超卖状态
    2. 当价格触及下轨时买入(低买)
    3. 当价格触及上轨时卖出(高卖)
    4. 结合RSI确认反转信号
    5. 使用ATR动态调整止损
    """

    INTERFACE_VERSION = 3

    # 基础策略参数
    minimal_roi = {
        "0": 0.08,      # 8%收益立即止盈
        "30": 0.04,     # 30分钟后4%收益
        "60": 0.02,     # 1小时后2%收益
        "120": 0.01     # 2小时后1%收益
    }

    stoploss = -0.05    # 5%止损
    timeframe = '15m'   # 15分钟时间框架

    # 策略控制参数
    can_short = False
    startup_candle_count = 50
    process_only_new_candles = True
    use_exit_signal = True
    use_custom_stoploss = False  # 禁用 custom stoploss,避免 trailing stop 导致的大量亏损

    # 布林带参数
    bb_period = IntParameter(15, 25, default=20, space="buy")
    bb_std = DecimalParameter(1.5, 2.5, default=2.0, space="buy") 

    # RSI参数
    rsi_period = IntParameter(10, 20, default=14, space="buy")
    rsi_oversold = IntParameter(35, 50, default=45, space="buy")  # 放宽RSI条件
    rsi_overbought = IntParameter(55, 75, default=65, space="sell")  # 放宽RSI条件

    # 均值回归确认参数
    price_deviation_threshold = DecimalParameter(0.001, 0.01, default=0.005, space="buy")  # 大幅放宽偏离度
    volume_threshold = DecimalParameter(0.8, 1.5, default=1.0, space="buy")  # 放宽成交量要求

    # ATR参数(用于动态止损)
    atr_period = IntParameter(10, 20, default=14, space="buy")
    atr_multiplier = DecimalParameter(1.5, 3.0, default=2.0, space="sell")

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        计算技术指标
        """

        # 布林带
        bollinger = qtpylib.bollinger_bands(
            dataframe['close'], 
            window=self.bb_period.value, 
            stds=self.bb_std.value
        )
        dataframe['bb_lower'] = bollinger['lower']
        dataframe['bb_middle'] = bollinger['mid']
        dataframe['bb_upper'] = bollinger['upper']

        # 布林带宽度和位置
        dataframe['bb_width'] = (dataframe['bb_upper'] - dataframe['bb_lower']) / dataframe['bb_middle']
        dataframe['bb_position'] = (dataframe['close'] - dataframe['bb_lower']) / (dataframe['bb_upper'] - dataframe['bb_lower'])

        # RSI指标
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=self.rsi_period.value)

        # 均线系统
        dataframe['sma_20'] = ta.SMA(dataframe, timeperiod=20)
        dataframe['ema_12'] = ta.EMA(dataframe, timeperiod=12)
        dataframe['ema_26'] = ta.EMA(dataframe, timeperiod=26)

        # 价格偏离度
        dataframe['price_deviation'] = abs(dataframe['close'] - dataframe['sma_20']) / dataframe['sma_20']

        # 成交量指标
        dataframe['volume_sma'] = dataframe['volume'].rolling(window=20).mean()
        dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_sma']

        # ATR(平均真实波动率)
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=self.atr_period.value)

        # MACD(作为趋势确认)
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macd_signal'] = macd['macdsignal']
        dataframe['macd_hist'] = macd['macdhist']

        # 威廉指标(额外的超买超卖确认)
        dataframe['williams_r'] = ta.WILLR(dataframe, timeperiod=14)

        # 价格动量
        dataframe['momentum'] = ta.MOM(dataframe, timeperiod=10)

        # 支撑阻力位(简化版)
        dataframe['resistance'] = dataframe['high'].rolling(window=20).max()
        dataframe['support'] = dataframe['low'].rolling(window=20).min()

        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        均值回归买入信号

        买入条件(低买策略):
        1. 价格触及或突破布林带下轨
        2. RSI处于超卖状态
        3. 成交量放大确认
        4. 价格偏离均线足够远
        """

        conditions = [
            # 主要条件:价格接近布林带下轨
            (dataframe['close'] <= dataframe['bb_lower'] * 1.005) |  # 允许价格接近下轨
            (dataframe['low'] <= dataframe['bb_lower']),

            # RSI相对较低(放宽条件)
            dataframe['rsi'] < self.rsi_oversold.value,

            # 威廉指标相对较低(放宽条件)
            dataframe['williams_r'] < -50,  # 从-80放宽到-50

            # 价格偏离均线(大幅放宽)
            dataframe['price_deviation'] > self.price_deviation_threshold.value,

            # 成交量确认(放宽)
            dataframe['volume_ratio'] > self.volume_threshold.value,

            # 趋势不要太强(放宽条件)
            dataframe['ema_12'] > dataframe['ema_26'] * 0.95,  # 允许更多下跌趋势

            # 布林带宽度足够(大幅放宽)
            dataframe['bb_width'] > 0.005,  # 从2%降到0.5%

            # 动量转正(可选条件)
            dataframe['momentum'] > dataframe['momentum'].shift(1),
        ]

        # 组合主要条件(简化逻辑)
        dataframe.loc[
            (
                conditions[0] &  # 布林带下轨
                conditions[1] &  # RSI相对较低
                conditions[2] &  # 威廉指标相对较低
                conditions[3] &  # 价格偏离(已大幅放宽)
                conditions[4] &  # 成交量(已放宽)
                conditions[5]    # 趋势条件(已放宽)
                # 暂时移除布林带宽度和动量条件以增加信号
            ),
            'enter_long'
        ] = 1

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        均值回归卖出信号

        卖出条件(高卖策略):
        1. 价格触及或突破布林带上轨
        2. RSI进入超买区域
        3. 价格回归至均线附近
        """

        conditions = [
            # 主要条件:价格接近布林带上轨
            (dataframe['close'] >= dataframe['bb_upper'] * 0.998) |  # 允许接近上轨
            (dataframe['high'] >= dataframe['bb_upper']),

            # RSI相对较高
            dataframe['rsi'] > self.rsi_overbought.value,

            # 威廉指标相对较高
            dataframe['williams_r'] > -30,  # 从-20放宽到-30

            # 价格回归至布林带中上部(放宽)
            dataframe['bb_position'] > 0.7,  # 从0.8降到0.7
        ]

        # 替代卖出条件:趋势转弱
        trend_weak_conditions = [
            # MACD转弱
            qtpylib.crossed_below(dataframe['macd'], dataframe['macd_signal']),

            # 短期均线下穿长期均线
            qtpylib.crossed_below(dataframe['ema_12'], dataframe['ema_26']),

            # 动量转负
            dataframe['momentum'] < 0,

            # RSI从高位回落
            (dataframe['rsi'] < dataframe['rsi'].shift(1)) & 
            (dataframe['rsi'] > 60),
        ]

        dataframe.loc[
            (
                # 主要卖出条件(均值回归完成)- 3个条件即可,移除bb_position要求
                (conditions[0] & conditions[1] & conditions[2]) |

                # 趋势转弱需要2个条件同时满足(更严格)
                (trend_weak_conditions[0] & trend_weak_conditions[1])
            ),
            'exit_long'
        ] = 1

        return dataframe

    def custom_stoploss(self, pair: str, trade: 'Trade', current_time, 
                       current_rate: float, current_profit: float, **kwargs) -> float:
        """
        动态止损基于ATR
        """

        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()

        # 基于ATR的动态止损
        atr_stop = last_candle['atr'] * self.atr_multiplier.value
        atr_stop_pct = atr_stop / current_rate

        # 根据持仓时间调整止损
        if current_profit > 0.02:  # 盈利超过2%时收紧止损
            return max(-atr_stop_pct * 0.5, -0.02)
        elif current_profit > 0.01:  # 盈利超过1%时适度收紧
            return max(-atr_stop_pct * 0.7, -0.03)
        else:
            # 正常ATR止损,但不超过最大止损
            return max(-atr_stop_pct, self.stoploss)

    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['bb_width'] < 0.015:  # 小于1.5%
            return False

        # 确保不是在强烈下跌趋势中
        if last_candle['ema_12'] < last_candle['ema_26'] * 0.95:  # 短期均线低于长期5%以上
            return False

        # 确保RSI不会过度超卖(可能继续下跌)
        if last_candle['rsi'] < 15:  # 极度超卖可能继续下跌
            return False

        return True

    def custom_entry_price(self, pair: str, current_time, proposed_rate: float,
                         entry_tag: str, side: str, **kwargs) -> float:
        """
        自定义入场价格 - 尝试获得更好的入场点
        """

        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()

        # 如果当前价格高于布林带下轨,尝试以更低价格入场
        if proposed_rate > last_candle['bb_lower']:
            # 在布林带下轨和当前价格之间设置限价单
            target_price = (proposed_rate + last_candle['bb_lower']) / 2
            return min(target_price, proposed_rate * 0.999)  # 最多降低0.1%

        return proposed_rate

    def informative_pairs(self):
        """
        定义需要的额外数据对
        """
        pairs = self.dp.current_whitelist()
        informative_pairs = []

        # 添加1小时时间框架数据用于趋势确认
        for pair in pairs:
            informative_pairs.append((pair, '1h'))

        return informative_pairs

    def leverage(self, pair: str, current_time, current_rate: float,
                proposed_leverage: float, max_leverage: float, entry_tag: str,
                side: str, **kwargs) -> float:
        """
        杠杆设置 - 均值回归策略使用保守杠杆
        """
        return 1.0  # 不使用杠杆,降低风险


def test_strategy():
    """
    测试策略的基本功能
    """
    import pandas as pd
    import numpy as np

    print("测试均值回归策略...")

    # 创建测试数据
    np.random.seed(42)
    dates = pd.date_range('2023-01-01', periods=200, freq='15min')

    # 模拟有均值回归特征的价格数据
    price = 50000  # BTC起始价格
    prices = [price]

    for i in range(199):
        # 添加均值回归特性:价格偏离均值时有回归倾向
        mean_price = np.mean(prices[-20:]) if len(prices) >= 20 else price
        deviation = (price - mean_price) / mean_price

        # 均值回归力度
        reversion_force = -deviation * 0.1
        random_walk = np.random.normal(0, 0.005)

        change = reversion_force + random_walk
        price *= (1 + change)
        prices.append(price)

    # 生成测试数据
    data = pd.DataFrame({
        'timestamp': dates,
        'open': prices,
        'high': [p * (1 + abs(np.random.normal(0, 0.002))) for p in prices],
        'low': [p * (1 - abs(np.random.normal(0, 0.002))) for p in prices],
        'close': prices,
        'volume': np.random.randint(100, 1000, 200)
    })

    # 测试策略
    strategy = MeanReversionStrategy()

    # 计算指标
    data_with_indicators = strategy.populate_indicators(data, {'pair': 'BTC/USDT'})

    # 生成信号
    data_with_signals = strategy.populate_entry_trend(data_with_indicators, {'pair': 'BTC/USDT'})
    data_with_signals = strategy.populate_exit_trend(data_with_signals, {'pair': 'BTC/USDT'})

    # 统计信号
    buy_signals = data_with_signals['enter_long'].sum() if 'enter_long' in data_with_signals.columns else 0
    sell_signals = data_with_signals['exit_long'].sum() if 'exit_long' in data_with_signals.columns else 0

    print(f"测试结果:")
    print(f"数据点数: {len(data)}")
    print(f"买入信号: {buy_signals}")
    print(f"卖出信号: {sell_signals}")
    print(f"价格范围: {min(prices):.2f} - {max(prices):.2f}")

    # 显示一些关键指标
    print(f"\n关键指标示例(最后5行):")
    key_columns = ['close', 'bb_lower', 'bb_upper', 'rsi', 'bb_position']
    available_columns = [col for col in key_columns if col in data_with_signals.columns]

    if available_columns:
        print(data_with_signals[available_columns].tail())

    return data_with_signals


if __name__ == "__main__":
    test_strategy()
Enter fullscreen mode Exit fullscreen mode

Top comments (0)