
用 Python 揭秘均值回归策略:你的收益从何而来?
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 350 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
你有没有想过,一只从 321 美元跌到不足 10 美元的股票,还能不能赚钱?
今天给大家拆解一个来自 Kryptera 的有趣量化策略 —— AKAM-QQE 动量减速 + EMA 过滤策略。它的核心理念非常反直觉:不追涨,不杀跌,而是在动量开始"打瞌睡"的时候进场。
在 Akamai Technologies(AKAM)这只股票上,2000 年至 2025 年的 25 年回测中,买入并持有的投资者亏损了 75%,而这个策略却把 10 万美元变成了 38.9 万美元,总收益 289%。
下面我们用 Python 一步步还原它的实现逻辑。
整个策略只建立在两个简单的想法上:
没有成交量过滤,没有宏观叠加,没有机器学习。只有 2 个指标,1 条入场规则,1 条离场规则。
我们使用 yfinance 下载 AKAM 从 2000 年到 2026 年的日线数据,使用 vectorbt 做向量化回测。
import pandas as pd
import numpy as np
import yfinance as yf
import vectorbt as vbt
# -------------------------
# 下载 AKAM 股票历史数据
# -------------------------
symbol = "AKAM" # 股票代码:Akamai Technologies
start_date = "2000-01-01" # 回测起始日期
end_date = "2026-01-01" # 回测结束日期
interval = "1d" # 日线级别
# 从 Yahoo Finance 下载数据并保存到本地 CSV
df = yf.download(symbol, start=start_date, end=end_date, interval=interval)
df.to_csv("AKAM_clean.csv", index=False)QQE(Quantitative Qualitative Estimation,定量定性估计)指标是基于 RSI 平滑和 ATR 类计算的动量衰减工具。
# QQE 参数设置
QQE_FACTOR = 4.236 # ATR 倍数,控制追踪线的敏感度
QQE_PERIOD = 14 # RSI 计算周期
QQE_SHIFT = 5 # 与 5 天前比较,判断动量方向
QQE_SMOOTH = 5 # RSI 平滑窗口
def calculate_qqe(df, rsi_period=QQE_PERIOD, smooth=QQE_SMOOTH, factor=QQE_FACTOR):
"""
基于 RSI 平滑和类 ATR 计算 QQE 的 Value1 与 Value2 两条线
"""
df = df.copy()
# --- 第一步:计算 RSI ---
delta = df['Close'].diff()
up = delta.clip(lower=0) # 上涨部分
down = -delta.clip(upper=0) # 下跌部分
roll_up = up.ewm(alpha=1/rsi_period, adjust=False).mean() # 平滑上涨均值
roll_down = down.ewm(alpha=1/rsi_period, adjust=False).mean() # 平滑下跌均值
rsi = 100 - (100 / (1 + roll_up / roll_down)) # RSI 计算公式
# --- 第二步:对 RSI 再次平滑 ---
rsi_ma = rsi.rolling(window=smooth).mean().fillna(method='bfill')
# --- 第三步:计算 RSI 变化的 ATR 类值 ---
rsi_delta = rsi_ma.diff().abs().fillna(0)
atr_rsi = rsi_delta.ewm(alpha=1/smooth, adjust=False).mean()
# --- 第四步:递推计算 QQE Value2(追踪线)---
value1 = rsi_ma.copy()
value2 = pd.Series(index=df.index, dtype=float)
value2.iloc[0] = value1.iloc[0]
for i in range(1, len(df)):
prev_trail = value2.iloc[i-1]
prev_value1 = value1.iloc[i-1]
atr = atr_rsi.iloc[i]
# 根据 Value1 与追踪线的相对位置决定方向
direction = 1 if prev_value1 > prev_trail else -1
value2.iloc[i] = prev_trail + direction * factor * atr
df['QQE_Value1'] = value1
df['QQE_Value2'] = value2
return df
def qqe_value1_falling(df, shift=QQE_SHIFT):
"""判断 QQE Value1 是否比 5 天前更低,即动量正在减速"""
df = calculate_qqe(df)
return df['QQE_Value1'] < df['QQE_Value1'].shift(shift)通俗理解:RSI 告诉你市场是超买还是超卖,QQE 则是在 RSI 基础上再"降噪",让信号更稳健。当 QQE Value1 比 5 天前还低时,说明市场正在"喘气"。
EMA(Exponential Moving Average,指数移动平均线)是最经典的趋势指标之一。这里我们用它作为离场依据。
EMA_PERIOD = 20 # 20 日 EMA,短期趋势判断
def calculate_ema(df, period=EMA_PERIOD):
"""计算收盘价的指数移动平均"""
df = df.copy()
df['EMA'] = df['Close'].ewm(span=period, adjust=False).mean()
return df
def close_below_ema(df, period=EMA_PERIOD):
"""判断收盘价是否跌破 EMA"""
df = calculate_ema(df, period)
return df['Close'] < df['EMA']# -------------------------
# 入场条件:QQE Value1 正在下降
# -------------------------
df["QQE_Value1_Falling"] = qqe_value1_falling(df)
entry_conditions = [
'QQE_Value1_Falling',
]
# 所有条件同时满足才产生入场信号
df['entry_signal'] = df[entry_conditions].all(axis=1)
# -------------------------
# 离场条件:收盘价跌破 EMA
# -------------------------
df["EMA_Close_Below"] = close_below_ema(df)
exit_conditions = [
'EMA_Close_Below',
]
df['exit_signal'] = df[exit_conditions].all(axis=1)为了避免未来函数(look-ahead bias),我们把信号向后平移 1 天,用次日开盘价成交。
# -------------------------
# 信号平移 1 天,避免未来函数
# -------------------------
shift_entries = df['entry_signal'].shift(1).astype(bool).fillna(False).to_numpy()
shift_exits = df['exit_signal'].shift(1).astype(bool).fillna(False).to_numpy()
# -------------------------
# 使用 vectorbt 构建回测组合
# -------------------------
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())
pf.plot().show()策略表现(2000-01-03 至 2025-12-31):
案例分析:
同样是 AKAM 这只股票,被动持有者从 2000 年股价 321 美元一路看着它跌到个位数,账户几乎归零;而该策略却在 2000 ~ 2004 年捕获了两次超过 127% 和 244% 的交易,正是这些暴跌期的"死猫跳"成就了策略的护城河。
这个策略不是高频系统,也不是追势交易,它更像一个"疲劳检测器":当买盘压力(通过平滑 RSI 衡量)开始放缓时进场等待,当价格跌破 EMA 确认趋势结束时离场。
核心盈利逻辑:
诚实的风险提示:
这篇文章展示了一个有意思的事实:简单的策略不代表平庸的结果。只用 2 个指标(QQE 和 EMA),就可以在 25 年尺度上跑赢买入持有 360 多个百分点。
但更重要的启示是:
对于 Python 量化学习者来说,这个案例是一个很好的入门模板 —— 数据下载用 yfinance,指标计算用 pandas,回测框架用 vectorbt,代码量不超过 100 行。
建议动手复现一遍,然后换几只不同行业的股票(如 AAPL、TSLA、KO)跑一跑,你会发现同一个策略在不同标的上的表现可能天差地别 —— 这正是量化交易的迷人之处。
2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!
好文推荐
1. 用 Python 打造股票预测系统:Transformer 模型教程(一)
2. 用 Python 打造股票预测系统:Transformer 模型教程(二)
3. 用 Python 打造股票预测系统:Transformer 模型教程(三)
4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)
6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用
9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解
好书推荐