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 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 = True

# 布林带参数
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[
        (
            # 主要卖出条件(均值回归完成)
            (conditions[0] & conditions[1] & conditions[2] & conditions[3]) |

            # 替代卖出条件(趋势转弱的任一信号)
            trend_weak_conditions[0] |
            trend_weak_conditions[1] |
            (trend_weak_conditions[2] & trend_weak_conditions[3])
        ),
        '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  # 不使用杠杆,降低风险
Enter fullscreen mode Exit fullscreen mode

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

if name == "main":
test_strategy()

Top comments (0)