
用 Python 揭秘均值回归策略:你的收益从何而来?
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 500 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
在投资世界里,有一类股票最让长期持有者头疼:它不会归零,也不会干脆利落地崩盘让你死心,而是年复一年地横盘、阴跌、偶尔反弹。AES 这家在纽交所上市的全球电力公司,就是典型代表。
从 2000 年 1 月到 2025 年 12 月,整整 25 年里,单纯买入并持有 AES 的投资者会亏掉将近 40% 的本金(基准收益 -39.80%)。这不是一段“小回调”,而是长达四分之一世纪的价值毁灭。
于是问题来了:如果这只股票本身是个“赔钱货”,一套系统化的交易策略,能否从它的波动噪声里榨出收益?本文就用 Python 带大家完整复现这套策略,看看结果到底如何。
这套策略的核心思想,是回答一个具体的问题:这只股票是不是被暂时打到了超卖区,而短期动量正在悄悄回升?
它由两个指标组成,逻辑非常清晰:
下面我们逐个拆解。
AO 衡量的是“中位价”的 5 周期简单移动平均与 34 周期简单移动平均之差。这里有个关键细节:我们不只要求 AO 为正,而是要求它比 5 根 K 线之前更高,以此确认短期买盘正在加速。
import pandas as pd
# AO 参数设置
AO_CHANGE_SHIFT_1 = 5 # 用于对比 5 根 K 线前的数值
AO_LONG_PERIOD = 34 # 长周期 SMA
AO_SHORT_PERIOD = 5 # 短周期 SMA
def calculate_ao(df, short_period=AO_SHORT_PERIOD, long_period=AO_LONG_PERIOD):
"""计算 Awesome Oscillator(动量震荡指标)"""
df = df.copy()
# 中位价 = (最高价 + 最低价)/ 2
df["MedianPrice"] = (df["High"] + df["Low"]) / 2
# 短周期与长周期的简单移动平均
df["SMA_short"] = df["MedianPrice"].rolling(window=short_period).mean()
df["SMA_long"] = df["MedianPrice"].rolling(window=long_period).mean()
# AO = 短周期均线 - 长周期均线
df["AO"] = df["SMA_short"] - df["SMA_long"]
return df
def ao_is_rising(df, short_period=AO_SHORT_PERIOD,
long_period=AO_LONG_PERIOD, shift=AO_CHANGE_SHIFT_1):
"""判断 AO 是否在上升:当前值 > 5 根 K 线前的值"""
df = calculate_ao(df, short_period, long_period)
return df["AO"] > df["AO"].shift(shift)小知识补充:AO 由著名交易者 Bill Williams 提出,本质上是一个衡量市场动能的柱状图。用“当前值高于 N 根 K 线前”的写法,比单纯看正负号更能捕捉“动能拐头向上”的瞬间,能有效过滤掉在零轴附近反复震荡的假信号。
离场条件是收盘价跌破肯特纳通道下轨。该通道以 20 周期的典型价 EMA 为中轨,上下轨分别为中轨加减 2 倍 ATR(平均真实波幅)。一旦收盘价跌穿下轨,就说明价格已被打到了一个经过波动率调整后的极端位置,此时果断离场止损。
KC_MULTIPLIER = 2 # 通道宽度倍数
KC_PERIOD = 20 # 计算周期
def calculate_keltner_channel(df, period=KC_PERIOD, multiplier=KC_MULTIPLIER):
"""计算肯特纳通道(中轨、上轨、下轨)和 ATR"""
df = df.copy()
# 典型价 = (最高价 + 最低价 + 收盘价)/ 3
df["TP"] = (df["High"] + df["Low"] + df["Close"]) / 3
# 中轨:典型价的指数移动平均(EMA)
df["KC_Mid"] = df["TP"].ewm(span=period, adjust=False).mean()
# 计算真实波幅 TR,再求 ATR
df["H-L"] = df["High"] - df["Low"]
df["H-PC"] = abs(df["High"] - df["Close"].shift(1))
df["L-PC"] = abs(df["Low"] - df["Close"].shift(1))
df["TR"] = df[["H-L", "H-PC", "L-PC"]].max(axis=1)
df["ATR"] = df["TR"].rolling(period).mean()
# 上轨与下轨
df["KC_Upper"] = df["KC_Mid"] + multiplier * df["ATR"]
df["KC_Lower"] = df["KC_Mid"] - multiplier * df["ATR"]
return df
def kc_close_below_lower(df):
"""判断收盘价是否跌破下轨"""
df = calculate_keltner_channel(df)
return df["Close"] < df["KC_Lower"]易错点提醒:肯特纳通道与布林带(Bollinger Bands)很像,但通道宽度用的是 ATR 而非标准差,因此对极端跳空更敏感,更适合用来界定“波动率意义上的极端价位”。
把入场、离场条件组合起来,用 vectorbt 完成回测。注意一个工程上的关键细节:信号当天不立刻成交,而是在次日开盘价执行,并加入 0.1% 手续费和 0.2% 滑点,初始资金 10 万美元。这样做是为了避免“用今天才知道的信号去交易今天”的未来函数陷阱。
import vectorbt as vbt
# 生成入场、离场信号
df["AO_Rising"] = ao_is_rising(df)
df["KC_Close_Below_Lower"] = kc_close_below_lower(df)
df["entry_signal"] = df["AO_Rising"]
df["exit_signal"] = df["KC_Close_Below_Lower"]
# 关键:信号延后一天执行,用次日开盘价成交,避免未来函数
shift_entries = df["entry_signal"].shift(1).astype(bool).fillna(False)
shift_exits = df["exit_signal"].shift(1).astype(bool).fillna(False)
# 构建投资组合:用开盘价成交,含手续费与滑点
pf = vbt.Portfolio.from_signals(
close=df["Open"], # 以开盘价撮合
entries=shift_entries,
exits=shift_exits,
init_cash=100_000, # 初始资金 10 万美元
fees=0.001, # 手续费 0.1%
slippage=0.002, # 滑点 0.2%
freq="1d"
)
print(pf.stats()) # 打印绩效指标跑完 26 年数据,结果相当有看头:
这里最反直觉的一点是:胜率只有 33%,相当于每 3 笔交易里输掉 2 笔,但策略整体却是盈利的。
秘密在于盈亏的不对称:
“小亏快跑、大赚慢走” 正是这套系统能活下来的引擎。这也印证了量化交易里一句老话:胜率高不等于赚钱,盈亏比和期望值才是关键。
漂亮的总收益掩盖了一个残酷事实:最大回撤高达 85.61%,且持续了近 8 年(2743 天)。最痛苦的阶段在 2001—2003 年,安然事件引发能源板块崩塌,AES 从 40 美元上方一路跌到 2 美元以下。
8 年的水下煎熬,已经不是回测里的一个数字,而是对人性的极限拷问。绝大多数交易者根本撑不到 2021—2022 年那波把累计收益推到近 5 倍的反弹。
这也是为什么夏普比率只有 0.352、卡玛比率仅 0.065——收益确实存在,但代价是巨大的波动与煎熬。
对比非常清晰:同样 10 万美元,单纯买入持有 26 年后只剩约 6.02 万美元(尚未计入分红),而策略却把它变成了约 26.37 万美元。在一只长期没有方向、只有波动的股票上,一套基于规则的均值回归纪律,确实能榨出被动持有者拿不到的收益。
需要强调的是,这并非对 AES 的“看多背书”。它长期跑输背后是能源转型压力、拉美货币风险等结构性逆风,这些都不是任何震荡指标能预测的。策略的真正价值,是用机械化纪律避免了最糟糕的结局——傻傻持有 25 年。
这套“动量 + 均值回归”策略给 Python 量化学习者的启示很实在:
最后照例提醒:本文仅用于 Python 与量化学习交流,不构成任何投资建议。所有回测都基于历史数据,无法保证未来表现,实盘前请务必自行充分研究与测试。
2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!
好文推荐
1. 用 Python 打造股票预测系统:Transformer 模型教程(一)
2. 用 Python 打造股票预测系统:Transformer 模型教程(二)
3. 用 Python 打造股票预测系统:Transformer 模型教程(三)
4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)
6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用
9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解
好书推荐