🏠
AI量化交易 · 第19章:均值回归策略
目录
Chapter 19

均值回归

高抛低吸的数学表达——当价格偏离正常时,押注它会回家

本章学习目标

你将学会

  1. 理解均值回归的核心逻辑和统计学基础
  2. 掌握布林带均值回归策略的实现方法
  3. 了解RSI超卖反弹策略的设计思路
  4. 学会配对交易的基本原理和协整性检验
  5. 能够区分趋势跟踪和均值回归的适用场景

如果说趋势跟踪是"顺势而为",那么均值回归就是"逆势而为"。它赌的是:当价格偏离其"正常水平"太远时,最终一定会回归。这种策略在震荡市中如鱼得水,但在强趋势市中会损失惨重。掌握它,你就拥有了与趋势跟踪互补的第二大武器。

故事引入:弹簧与橡皮筋

想象你手里拿着一根橡皮筋,一端系着一个乒乓球。你把球拉得很远,然后松手——球会怎样?它会弹回来,回到平衡点。拉得越远,弹回来的力量越大。

金融市场中的很多价格行为就像这根橡皮筋。当某只股票因为短期恐慌被过度抛售时,价格会偏离其内在价值。虽然短期内恐慌可能继续,但从统计上看,极端偏离最终会被修正。这就是均值回归的直觉。

当然,市场不总是橡皮筋。有时候它是瀑布——一旦开始下跌,根本停不下来。所以均值回归策略最危险的时刻,就是把瀑布当成橡皮筋的时候。2015年A股股灾中,无数"抄底"的均值回归者被市场碾压,因为他们没有意识到:均值回归只在"有均值可回归"的市场中有效

趋势跟踪和均值回归就像白天和黑夜。趋势跟踪在趋势市中赚钱,在震荡市中亏钱;均值回归在震荡市中赚钱,在趋势市中亏钱。没有哪种策略永远有效,真正成熟的量化交易者会同时拥有这两种武器,并根据市场环境切换。

核心知识:当价格想"回家"

2.1 均值回归的统计学基础

均值回归(Mean Reversion)是统计学中的一个核心概念。它的理论基础是:如果价格序列是平稳的(stationary),那么偏离均值的部分最终会收敛回均值

判断一个价格序列是否适合均值回归,最常用的方法是ADF检验(Augmented Dickey-Fuller Test)。如果检验结果的p值小于0.05,我们就可以认为该序列是平稳的,适合均值回归策略。

但股市价格本身通常不是平稳的——它更像随机游走。所以均值回归策略通常不直接作用于价格,而是作用于价格的某种变换

2.2 布林带均值回归策略

布林带(Bollinger Bands)是均值回归策略中最经典的工具。它由三条线组成:

中轨 = N日移动平均线
上轨 = 中轨 + K倍标准差
下轨 = 中轨 - K倍标准差

布林带的核心逻辑是:价格有95%的概率落在2倍标准差的轨道内(假设价格服从正态分布)。当价格触及上轨时,视为"超买",可能回落;当价格触及下轨时,视为"超卖",可能反弹。

flowchart LR A[计算20日均线] --> B[计算2倍标准差轨道] B --> C{价格上穿上轨?} C -->|是| D[卖出/做空] C -->|否| E{价格跌破下轨?} E -->|是| F[买入/做多] E -->|否| G[等待] D --> H[价格回归中轨时平仓] F --> H style D fill:#e74c3c,color:#fff style F fill:#10b981,color:#fff style H fill:#f97316,color:#fff

2.3 布林带策略的Python实现

PYTHON
"""
布林带均值回归策略
逻辑:价格触及下轨时买入,触及上轨时卖出
"""
import backtrader as bt
import akshare as ak
import pandas as pd

class BollingerMeanReversion(bt.Strategy):
    params = (
        ('period', 20),      # 布林带周期
        ('devfactor', 2.0),  # 标准差倍数
        ('stop_loss', 0.05), # 5%止损(防止趋势延续)
    )
    
    def __init__(self):
        self.boll = bt.indicators.BollingerBands(
            period=self.p.period,
            devfactor=self.p.devfactor
        )
        self.order = None
        self.entry_price = None
    
    def next(self):
        if self.order:
            return
        
        current_price = self.data.close[0]
        
        # 止损检查
        if self.position and self.entry_price:
            if current_price < self.entry_price * (1 - self.p.stop_loss):
                self.order = self.sell()
                print(f'止损卖出 @ {current_price:.2f}')
                return
        
        # 买入信号: 价格跌破下轨
        if not self.position and current_price < self.boll.lines.bot[0]:
            self.order = self.buy()
            print(f'买入 @ {current_price:.2f} (下轨: {self.boll.lines.bot[0]:.2f})')
        
        # 卖出信号: 价格突破上轨 或 回归中轨上方
        elif self.position and current_price > self.boll.lines.mid[0]:
            self.order = self.sell()
            pnl = (current_price - self.entry_price) / self.entry_price * 100
            print(f'卖出 @ {current_price:.2f}, 盈亏: {pnl:.2f}%')
    
    def notify_order(self, order):
        if order.status in [order.Completed]:
            if order.isbuy():
                self.entry_price = order.executed.price
            else:
                self.entry_price = None
        self.order = None


# ========== 回测设置 ==========
if __name__ == "__main__":
    cerebro = bt.Cerebro()
    cerebro.addstrategy(BollingerMeanReversion)
    cerebro.broker.setcash(100000.0)
    cerebro.broker.setcommission(commission=0.0003)
    
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='dd')
    
    # 请在此处添加数据加载代码
    # df = ak.stock_zh_a_hist(...)
    # data = bt.feeds.PandasData(dataname=df, ...)
    # cerebro.adddata(data)
    
    results = cerebro.run()
    strat = results[0]
    print(f'最终资金: {cerebro.broker.getvalue():.2f}')
    print(f"夏普: {strat.analyzers.sharpe.get_analysis().get('sharperatio','N/A')}")
    print(f"最大回撤: {strat.analyzers.dd.get_analysis().max.drawdown:.2f}%")

2.4 RSI超卖反弹策略

RSI(相对强弱指标)是另一个经典的均值回归工具。它的取值范围是0-100:

纯粹的RSI策略有一个问题:在强趋势市中,RSI可以长期停留在超买或超卖区。比如一只妖股连续涨停,RSI可能连续20天大于80。如果你在RSI>70时就卖出,会错过后续的巨大涨幅。

改进方法是结合趋势过滤:只在价格高于200日均线时用RSI>70做空,只在价格低于200日均线时用RSI<30做多。这样可以避免在强趋势中逆势操作。

2.5 配对交易:寻找"连体婴儿"

配对交易(Pairs Trading)是均值回归策略的"高级形态"。它的核心思想是:找到两只历史上价格走势高度相关的股票,当它们的价差异常扩大时,做空贵的、买入便宜的,等价差回归时平仓

配对交易的经典案例是可口可乐和百事可乐。两家公司处于同一行业,受相似的基本面因素影响,股价长期走势高度相关。当某一天可口可乐因为某个临时性利空暴跌,而百事没有跌时,两者的价差异常扩大——这就是配对交易者的入场信号。

PYTHON
"""
配对交易简化版 - 协整性检验 + 阈值交易
需要安装: pip install statsmodels
"""
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import coint

class PairsTrading:
    def __init__(self, stock_a, stock_b, lookback=60, entry_z=2.0, exit_z=0.5):
        """
        stock_a, stock_b: 价格序列 (pandas Series)
        lookback: 计算价差的回看窗口
        entry_z: 开仓阈值 (Z-Score)
        exit_z: 平仓阈值 (Z-Score)
        """
        self.stock_a = stock_a
        self.stock_b = stock_b
        self.lookback = lookback
        self.entry_z = entry_z
        self.exit_z = exit_z
        self.hedge_ratio = None
    
    def test_cointegration(self):
        """检验两只股票是否协整"""
        score, pvalue, _ = coint(self.stock_a, self.stock_b)
        return {
            'coint_score': score,
            'pvalue': pvalue,
            'is_cointegrated': pvalue < 0.05
        }
    
    def calculate_spread(self):
        """计算价差序列 (A - hedge_ratio * B)"""
        # 用线性回归计算对冲比率
        self.hedge_ratio = np.polyfit(self.stock_b, self.stock_a, 1)[0]
        spread = self.stock_a - self.hedge_ratio * self.stock_b
        return spread
    
    def generate_signals(self):
        """生成交易信号"""
        spread = self.calculate_spread()
        
        # 计算滚动Z-Score
        mean = spread.rolling(window=self.lookback).mean()
        std = spread.rolling(window=self.lookback).std()
        zscore = (spread - mean) / std
        
        signals = pd.DataFrame(index=spread.index)
        signals['zscore'] = zscore
        signals['spread'] = spread
        
        # 信号逻辑
        signals['long_a'] = zscore < -self.entry_z    # A被低估,做多A做空B
        signals['short_a'] = zscore > self.entry_z     # A被高估,做空A做多B
        signals['exit'] = abs(zscore) < self.exit_z    # 平仓
        
        return signals


# ========== 使用示例 ==========
if __name__ == "__main__":
    # 模拟两只相关股票的价格
    np.random.seed(42)
    n = 500
    
    # 创建协整的价格序列
    trend = np.cumsum(np.random.normal(0, 1, n))
    stock_a = 100 + trend + np.random.normal(0, 2, n)
    stock_b = 80 + 0.8 * trend + np.random.normal(0, 2, n)
    
    df = pd.DataFrame({
        'A': stock_a,
        'B': stock_b
    })
    
    # 运行配对交易分析
    pt = PairsTrading(df['A'], df['B'])
    
    # 检验协整性
    coint_result = pt.test_cointegration()
    print("=== 协整性检验 ===")
    print(f"P值: {coint_result['pvalue']:.4f}")
    print(f"是否协整: {coint_result['is_cointegrated']}")
    
    # 生成信号
    if coint_result['is_cointegrated']:
        signals = pt.generate_signals()
        print("\n=== 最近交易信号 ===")
        recent_signals = signals[signals['long_a'] | signals['short_a']].tail(10)
        print(recent_signals[['zscore', 'long_a', 'short_a']].to_string())

2.6 均值回归的致命陷阱

均值回归策略最大的敌人是"假回归"——你以为价格会回归,但它只是短暂停顿后继续狂奔。以下几种情况尤其危险:

  1. 基本面恶化:公司突然爆出财务造假,之前的"均值"已经不复存在
  2. 行业结构性变化:传统行业被新技术颠覆,旧的估值中枢被彻底打破
  3. 流动性危机:市场恐慌时,所有相关性都失效,即使是协整的配对也会 diverge
  4. 过度杠杆:均值回归策略通常使用高杠杆,一次"不归"就能致命
科学争议:均值回归在A股有效吗?

正方:A股散户占比高,情绪波动大,导致价格经常偏离基本面。这种"非理性波动"为均值回归策略提供了丰富的机会。特别是在蓝筹股中,均值回归效果显著。

反方:A股的"涨停跌停"制度限制了价格发现效率。一只股票跌停后可能连续跌停,根本没有"回归"的机会。而且A股的"炒题材"文化让价格可以长期偏离基本面。

量化交易者的务实态度:均值回归在A股确实有效,但需要严格的止损和仓位控制。不要试图"抄底"连续跌停的股票,只在波动率正常、流动性充足的环境中使用均值回归策略。

2.7 布林带交互式模拟器

调整参数,观察不同标准差倍数对交易频率和潜在收益的影响:

布林带参数探索器

预计触及上轨频率: 约2.3%
预计触及下轨频率: 约2.3%
适合市场环境: 震荡市
风险提示: 趋势较强,假信号风险中等

与AI一起练习

练习1:让AI检验两只股票的协整性

Prompt模板

PROMPT
请用Python和statsmodels检验贵州茅台(600519)和五粮液(000858)
从2020年到2024年的日收盘价是否协整。

要求:
1. 使用AKShare获取数据
2. 进行ADF检验和协整检验
3. 如果协整,计算最优对冲比率
4. 绘制价差序列图
5. 给出交易建议

练习2:让AI对比布林带和RSI策略

Prompt模板

PROMPT
请对比布林带均值回归策略和RSI超卖反弹策略:

1. 各自的核心逻辑是什么?
2. 在什么市场环境下表现最好?
3. 各自最大的风险点是什么?
4. 如果我要同时运行这两个策略,如何分配资金比例?

请用表格输出对比结果。

练习3:让AI设计一个"趋势+均值回归"的混合策略

Prompt模板

PROMPT
请设计一个混合策略:
- 当ADX>25时,使用趋势跟踪(海龟法则)
- 当ADX<20时,使用均值回归(布林带)
- 当ADX在20-25之间时,空仓观望

请用Backtrader实现,并添加注释。

动手实践:构建你的第一个均值回归策略

下面是一个完整的、可直接运行的布林带+RSI组合策略。它同时要求价格触及布林带下轨和RSI<30才入场,双重过滤提高了信号质量:

PYTHON
"""
布林带+RSI双重过滤均值回归策略
"""
import backtrader as bt

class MeanReversionStrategy(bt.Strategy):
    params = (
        ('bb_period', 20),
        ('bb_dev', 2.0),
        ('rsi_period', 14),
        ('rsi_oversold', 30),
        ('rsi_overbought', 70),
        ('stop_loss', 0.05),
    )
    
    def __init__(self):
        self.boll = bt.indicators.BollingerBands(
            period=self.p.bb_period,
            devfactor=self.p.bb_dev
        )
        self.rsi = bt.indicators.RSI(period=self.p.rsi_period)
        self.order = None
        self.entry_price = None
    
    def next(self):
        if self.order:
            return
        
        price = self.data.close[0]
        
        # 止损
        if self.position and self.entry_price:
            if price < self.entry_price * (1 - self.p.stop_loss):
                self.order = self.sell()
                return
        
        # 双重过滤买入: 价格跌破下轨 AND RSI超卖
        if not self.position:
            if price < self.boll.lines.bot[0] and self.rsi[0] < self.p.rsi_oversold:
                self.order = self.buy()
                print(f'双重过滤买入: 价格={price:.2f}, RSI={self.rsi[0]:.1f}')
        
        # 卖出: 价格回归中轨上方 OR RSI脱离超卖
        elif self.position:
            if price > self.boll.lines.mid[0] or self.rsi[0] > 50:
                self.order = self.sell()
                pnl = (price - self.entry_price) / self.entry_price * 100
                print(f'卖出: 价格={price:.2f}, 盈亏={pnl:.2f}%')
    
    def notify_order(self, order):
        if order.status in [order.Completed]:
            if order.isbuy():
                self.entry_price = order.executed.price
            else:
                self.entry_price = None
        self.order = None


if __name__ == "__main__":
    cerebro = bt.Cerebro()
    cerebro.addstrategy(MeanReversionStrategy)
    cerebro.broker.setcash(100000.0)
    cerebro.broker.setcommission(commission=0.0003)
    
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
    cerebro.addanalyzer(bt.analyzers.DrawDown, _name='dd')
    
    print('初始资金: %.2f' % cerebro.broker.getvalue())
    results = cerebro.run()
    strat = results[0]
    print('最终资金: %.2f' % cerebro.broker.getvalue())
    print(f"夏普: {strat.analyzers.sharpe.get_analysis().get('sharperatio','N/A')}")
    print(f"最大回撤: {strat.analyzers.dd.get_analysis().max.drawdown:.2f}%")

知识自测

快速检验你的理解
1. 均值回归策略的核心假设是什么?
  • 价格偏离均值后,最终会回归均值
  • 价格总是朝着同一个方向运动
  • 价格波动是完全随机的
  • 价格只受基本面影响
  • 均值回归策略赌的是价格会回到"正常水平"。这与趋势跟踪的"顺势而为"正好相反。关键在于判断什么构成了"正常水平"以及偏离多远才算"异常"。
    2. 配对交易的前提条件是什么?
  • 两只股票的市值相同
  • 两只股票属于同一行业
  • 两只股票的价格序列具有协整关系
  • 两只股票的波动率相同
  • 配对交易的核心统计学基础是协整性(cointegration)。即使两只股票的价格各自随机游走,只要它们的价差是平稳的(均值回归的),就可以进行配对交易。同行业只是寻找候选配对的一个启发式方法,不是必要条件。
    3. 均值回归策略在哪种市场环境下最危险?
  • 震荡市
  • 强趋势市
  • 低波动市
  • 高成交量市
  • 均值回归策略在强趋势市中最危险,因为价格可以长期偏离"均值"而不回归。此时均值回归的"抄底"或"摸顶"操作会变成"接飞刀"——每次买入后继续下跌,每次卖出后继续上涨。
    4. 为什么布林带策略通常需要配合止损使用?
  • 防止趋势延续导致巨额亏损
  • 减少交易次数
  • 提高胜率
  • 降低手续费
  • 布林带策略假设价格会回归均值,但如果市场进入了强趋势,价格可能持续沿着轨道外侧运行。止损的作用就是在这种"不归"的情况下及时退出,防止单笔亏损过大。

    下一章预告

    恭喜你完成了第19章!至此,你已经掌握了量化交易的两大核心策略流派:趋势跟踪均值回归。你学会了用Backtrader实现完整的策略,理解了入场、出场、仓位、止损四大组件,还掌握了基本面数据和另类数据的应用。

    但这只是开始。在后续的章节中,我们将进入更高阶的领域:多因子模型、机器学习策略、风险模型、以及如何把策略从回测推向实盘。量化交易的世界没有终点,每一天都有新的数据、新的工具、新的机会。

    记住这本书的核心理念:用AI降低技术门槛,让每个人都能把交易想法变成可运行的策略。你现在已经具备了把想法变成代码的能力。去实践吧,市场是最好的老师。

    课后行动清单
    1. 运行布林带策略:在你的环境中回测布林带均值回归策略
    2. 对比测试:用同一段数据分别运行趋势跟踪和均值回归策略,观察哪个表现更好
    3. 寻找配对:找两只你认为相关的A股股票,用Python检验它们是否协整
    4. 混合实验:尝试把ADX过滤器加入布林带策略,观察在趋势市中的保护作用

    觉得有收获?分享给同样对量化交易感兴趣的朋友

    上一章
    趋势跟踪策略——让利润奔跑
    掌握趋势跟踪的核心逻辑和实现方法
    下一章
    多因子模型——把多个信号合成为一个
    学习如何把多个因子组合成更强的预测信号

    如果这本书对你有所启发,欢迎支持我继续创作

    赞赏二维码