
用 Python 揭秘均值回归策略:你的收益从何而来?
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 350 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
大多数交易系统都聚焦于价格动量:价格上涨时买入,价格下跌时卖空。但今天我们要分享一个有点「反直觉」的思路 —— 不盯价格的方向,而是盯波动率本身的加速度。
核心假设非常简单:
这并不是一个预测模型,而是一个带有波动率确认过滤器的趋势跟随策略。下面我们就用 Python 一步步实现并回测它。
历史波动率使用收益率的滚动标准差来度量:
import pandas as pdimport talib# 计算每日收益率,缺失值填 0returns = close.pct_change().fillna(0)# 用 TA-Lib 计算滚动标准差作为波动率指标volatility = pd.Series( talib.STDDEV( returns.to_numpy(), timeperiod=vol_window, # 波动率计算窗口 nbdev=1 ), index=close.index)波动率动量定义为当前波动率与若干周期前波动率的差值:
# 波动率动量 = 当前波动率 - N 个周期前的波动率vol_momentum = volatility - volatility.shift(vol_momentum_window)当 vol_momentum > 0 时,说明波动率正在扩张。
使用一条简单移动均线(SMA)来判断价格趋势方向:
# 用简单移动均线判断价格趋势price_sma = pd.Series( talib.SMA( close.to_numpy(), timeperiod=price_trend_sma_window ), index=close.index)为了防止 lookahead bias(前视偏差),所有判断都基于前一根 K 线的数值:
# 全部使用 shift(1) 取上一根 K 线的值,避免未来函数prev_vol_momentum = vol_momentum.shift(1)prev_close = close.shift(1)prev_sma = price_sma.shift(1)# 多头入场:波动率扩张 且 价格在均线之上entries = ( (prev_vol_momentum > 0) & (prev_close > prev_sma)).fillna(False)# 空头入场:波动率扩张 且 价格在均线之下short_entries = ( (prev_vol_momentum > 0) & (prev_close < prev_sma)).fillna(False)# 多头出场:波动率动量消失 或 跌破均线exits = ( (prev_vol_momentum <= 0) | (prev_close < prev_sma)).fillna(False)# 空头出场:波动率动量消失 或 突破均线short_exits = ( (prev_vol_momentum <= 0) | (prev_close > prev_sma)).fillna(False)为了控制风险,策略使用基于 ATR 的跟踪止损:
import numpy as np# 计算平均真实波幅 ATRatr = pd.Series( talib.ATR( high.to_numpy(), low.to_numpy(), close.to_numpy(), timeperiod=atr_window ), index=close.index)# 把 ATR 转换为百分比形式的止损距离,方便 vectorbt 使用atr_stop_pct = (atr_multiplier * atr / close).shift(1)# 处理可能出现的无穷大数值atr_stop_pct = atr_stop_pct.replace([np.inf, -np.inf], np.nan)下面是这套波动率动量策略的完整实现,可直接接入 Vectester / vectorbt 进行回测:
import pandas as pdimport numpy as npimport vectorbt as vbtimport talibfrom core.params import ParamDeffrom core.strategy_base import Strategy, StrategyResultclass VolMomentumStrategy(Strategy): name = "vol_momentum_talib" # 策略可调参数定义 param_defs = [ ParamDef("vol_window", 30, int, "波动率计算窗口"), ParamDef("vol_momentum_window", 7, int, "波动率动量窗口"), ParamDef("price_trend_sma_window", 30, int, "价格趋势 SMA 窗口"), ParamDef("atr_window", 14, int, "ATR 计算窗口"), ParamDef("atr_multiplier", 2.0, float, "ATR 跟踪止损倍数"), ParamDef("long_short", 1, int, "1 = 多空双向,0 = 仅做多"), ] def build(self, data: pd.DataFrame, params: dict) -> StrategyResult: # 提取价格数据 close = data["Close"].astype(float) high = data["High"].astype(float) low = data["Low"].astype(float) # 读取参数 vol_window = int(params["vol_window"]) vol_momentum_window = int(params["vol_momentum_window"]) price_trend_sma_window = int(params["price_trend_sma_window"]) atr_window = int(params["atr_window"]) atr_multiplier = float(params["atr_multiplier"]) long_short = int(params["long_short"]) # 1. 计算收益率与波动率 returns = close.pct_change().fillna(0) volatility = pd.Series( talib.STDDEV(returns.to_numpy(), timeperiod=vol_window, nbdev=1), index=close.index ) # 2. 计算波动率动量 vol_momentum = volatility - volatility.shift(vol_momentum_window) # 3. 计算价格 SMA 用于趋势判断 price_sma = pd.Series( talib.SMA(close.to_numpy(), timeperiod=price_trend_sma_window), index=close.index ) # 4. 计算 ATR 用于止损 atr = pd.Series( talib.ATR( high.to_numpy(), low.to_numpy(), close.to_numpy(), timeperiod=atr_window ), index=close.index ) # 5. 使用上一根 K 线数据,避免前视偏差 prev_vol_momentum = vol_momentum.shift(1) prev_close = close.shift(1) prev_sma = price_sma.shift(1) # 波动率状态判断 vol_accelerating = prev_vol_momentum > 0 # 波动率正在加速 vol_decelerating = prev_vol_momentum <= 0 # 波动率减速或停滞 # 6. 生成进出场信号 entries = (vol_accelerating & (prev_close > prev_sma)).fillna(False) exits = (vol_decelerating | (prev_close < prev_sma)).fillna(False) short_entries = (vol_accelerating & (prev_close < prev_sma)).fillna(False) short_exits = (vol_decelerating | (prev_close > prev_sma)).fillna(False) # 7. ATR 百分比止损 atr_stop_pct = (atr_multiplier * atr / close).shift(1) atr_stop_pct = atr_stop_pct.replace([np.inf, -np.inf], np.nan) # 8. 根据参数返回多空或仅多头模式 if long_short == 1: return StrategyResult( entries=entries, exits=exits, signal_kwargs={ "short_entries": short_entries, "short_exits": short_exits, "sl_stop": atr_stop_pct, "sl_trail": True, # 启用跟踪止损 "accumulate": False, }, pf_kwargs={"direction": "both"}, # 多空双向 ) return StrategyResult( entries=entries, exits=exits, signal_kwargs={ "sl_stop": atr_stop_pct, "sl_trail": True, "accumulate": False, }, pf_kwargs={"direction": "longonly"}, # 仅做多 )作者在 ETH-USD 上做了一年期回测,从权益曲线可以看到:
这说明该策略的核心价值并不是「跑赢买入持有」,而是在控制风险的前提下捕捉波动率扩张阶段的趋势行情。
这是一个干净、可测试的波动率扩张型趋势策略,核心规则可以浓缩成四句话:
波动率上升 + 价格上涨趋势 = 做多波动率上升 + 价格下跌趋势 = 做空波动率动量下降 = 离场ATR 跟踪止损 = 风控给 Python 量化学习者的几点启发:
shift(1) 取上一根 K 线值,这是回测真实可信的前提STDDEV、SMA、ATR 三个指标就能撑起一套完整策略如果你也对量化交易感兴趣,不妨把这套代码拿去自己跑一遍,换个标的、调调参数,看看会有什么新发现。
2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!
好文推荐
1. 用 Python 打造股票预测系统:Transformer 模型教程(一)
2. 用 Python 打造股票预测系统:Transformer 模型教程(二)
3. 用 Python 打造股票预测系统:Transformer 模型教程(三)
4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)
6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用
9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解
好书推荐