
用 Python 揭秘均值回归策略:你的收益从何而来?
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 500 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
在量化交易的世界里,K 线形态与技术指标是最经典的两类工具。很多初学者都听说过「锤子线」(Hammer)这种看涨反转形态,也知道 RSI(相对强弱指数)可以判断超买超卖。但真正的难点在于:如何把这些直觉性的信号,变成一套可以被严格回测、可以量化评估、并且不容易「过拟合」的交易系统?
今天我们就用 Python 来拆解一个完整的实战案例:以特斯拉(TSLA)2020 至 2024 年的日线数据为研究对象,构建一个「置信度评分锤子线 + RSI 过滤」的均值回归策略,并通过网格搜索(Grid Search)做参数寻优,最后用走向前优化(Walk-Forward Optimization,简称 WFO)来检验它在未见过的数据上是否依然靠谱。
这篇文章非常适合正在学习 Python、又对量化感兴趣的朋友,既能学到策略思路,也能顺带练习 pandas、numpy 的实战技巧。
均值回归策略的核心假设是:价格在短期大幅下跌后,往往会出现反弹。要验证这类策略的稳健性,就需要一个「波动足够大」的标的。
TSLA 在 2020 至 2023 年间经历了剧烈的价格波动,暴涨、暴跌、快速反转层出不穷,这恰恰会产生大量的锤子线形态,给策略提供了丰富的测试样本。相比之下,走势平稳的标的很难真正检验出策略的成色。简单说:波动越大,越能考验一个反转策略是不是「真有本事」。
传统做法只问一句「这是不是锤子线」,答案非黑即白。但真实的 K 线千差万别,一根「勉强算」的锤子线和一根「教科书级别」的锤子线,反转的可信度显然不同。
因此,这个策略的第一个创新点是:给每根 K 线打一个 0 到 1 之间的置信度分数,分数越高,形态越标准。
锤子线的几何特征包括:实体较小、下影线很长(说明卖方一度打压价格,但买方重新夺回控制权)、上影线很短、实体位于当日区间的顶部。评分函数正是围绕这些特征来设计的:
import numpy as npdef hammer_confidence(row): # 实体大小:开盘价与收盘价之差的绝对值 body = abs(row.Close - row.Open) # 当日整体波动区间 candle_range = row.High - row.Low # 防止除以零导致报错 if body == 0 or candle_range == 0: return 0 # 下影线长度:实体底部到最低价的距离 lower_wick = min(row.Open, row.Close) - row.Low # 上影线长度:最高价到实体顶部的距离 upper_wick = row.High - max(row.Open, row.Close) lower_ratio = lower_wick / body # 下影线相对实体的比例 upper_ratio = upper_wick / body # 上影线相对实体的比例 # 下影线越长,得分越高(上限为 1) lower_score = min(lower_ratio / 3, 1) # 上影线越短越好 upper_score = max(1 - upper_ratio, 0) # 实体在当日区间中的位置:越靠顶部越理想 body_position = (min(row.Open, row.Close) - row.Low) / candle_range position_score = min(body_position / 0.7, 1) # 加权合成最终置信度:下影线占 50%,实体位置占 30%,上影线占 20% confidence = ( 0.5 * lower_score + 0.3 * position_score + 0.2 * upper_score ) return confidence这样一来,我们就把「主观看图」变成了「客观打分」。通常置信度超过 0.5 就可以视为锤子线,而策略实际交易时会用更严格的阈值(例如 0.75 或 0.80)来筛选高质量信号。
小提示:这种「置信度评分」的思路可以迁移到几乎所有 K 线形态识别中。相比 TA-Lib 直接返回「是 / 否」,评分制能让你按信号强弱做分层,灵活性高很多。
只看形态还不够,我们还要确认市场确实处于「超卖」状态,这样反弹的概率才更高。RSI 就是用来衡量近期买卖力量对比的动量指标,一般的解读是:低于 30 为超卖、50 附近为中性、高于 70 为超买。
def calculate_rsi(close, period): delta = close.diff() # 相邻两日收盘价之差 gain = delta.clip(lower=0) # 只保留上涨部分 loss = -delta.clip(upper=0) # 只保留下跌部分(取正值) avg_gain = gain.rolling(period).mean() # 平均涨幅 avg_loss = loss.rolling(period).mean() # 平均跌幅 rs = avg_gain / avg_loss # 相对强度 return 100 - (100 / (1 + rs)) # 转换为 0~100 的 RSI 值易错点:这里用的是简单移动平均(rolling mean),而经典的 RSI 采用的是 Wilder 平滑法。两者算出来的数值会略有差异,实盘时要注意统一口径,否则回测和实盘信号可能对不上。
策略的逻辑其实很朴素,可以概括为四步:
买入条件(三者同时满足才进场):检测到锤子线、置信度高于阈值、且 RSI 低于买入线。
卖出条件(满足任意一个即离场):RSI 高于卖出线(视为已反弹)、盈利达到止盈目标、或持有超过最大天数(避免资金被无限期占用)。
下面是回测核心循环的简化版:
import pandas as pdimport numpy as npdef backtest(data, RSI_PERIOD, RSI_BUY, RSI_SELL, HAMMER_CONF_THRESHOLD, TAKE_PROFIT, MAX_HOLD_DAYS): df = data.copy() df["RSI"] = calculate_rsi(df.Close, RSI_PERIOD) # 生成买入信号:锤子线 + 置信度达标 + RSI 超卖 df["BUY"] = ( df.Hammer & (df.Hammer_confidence > HAMMER_CONF_THRESHOLD) & (df.RSI < RSI_BUY) ) trades = [] in_trade = False for date, row in df.iterrows(): if not in_trade: if row.BUY: # 满足买入条件则开仓 in_trade = True entry_date = date entry_price = row.Close else: holding_days = (date - entry_date).days profit = (row.Close - entry_price) / entry_price # 当前浮动盈亏 exit_reason = None if row.RSI > RSI_SELL: # 已反弹,离场 exit_reason = "RSI" elif profit >= TAKE_PROFIT: # 达到止盈 exit_reason = "TAKE_PROFIT" elif holding_days >= MAX_HOLD_DAYS: # 超过持有天数 exit_reason = "TIME" if exit_reason: trades.append({"Return": profit, "Reason": exit_reason}) in_trade = False # 交易样本过少时统计不可靠,直接丢弃 trades_df = pd.DataFrame(trades) if len(trades_df) < 5: return {"Sharpe": -999, "Trades": len(trades_df), "Return": 0} returns = trades_df.Return sharpe = returns.mean() / returns.std() * np.sqrt(252) # 年化夏普比率 total_return = (1 + returns).prod() - 1 # 复利总收益 win_rate = (returns > 0).mean() # 胜率 return {"Sharpe": sharpe, "Trades": len(trades_df), "Return": total_return, "WinRate": win_rate}策略里有一堆参数:RSI 周期、买入线、卖出线、锤子线置信度阈值、止盈目标、最大持有天数。这些值到底该取多少?与其凭感觉,不如把所有可能的组合都跑一遍,用夏普比率来挑出历史表现最好的那组。
参数空间的组合数是:
3 × 4 × 3 × 5 × 3 × 4 = 2160 种策略
每一种都独立回测一次,再按夏普比率排序:
from itertools import productRSI_PERIODS = [7, 14, 21]RSI_BUYS = [30, 35, 40, 45]RSI_SELLS = [50, 60, 70]HAMMER_THRESHOLDS = [0.65, 0.70, 0.75, 0.80, 0.85]TAKE_PROFITS = [0.05, 0.10, 0.15]MAX_HOLDS = [5, 10, 15, 20]results = []# product 会自动生成所有参数的笛卡尔积(共 2160 种)for params in product(RSI_PERIODS, RSI_BUYS, RSI_SELLS, HAMMER_THRESHOLDS, TAKE_PROFITS, MAX_HOLDS): result = backtest(df, *params) result["Params"] = params results.append(result)results_df = pd.DataFrame(results).sort_values("Sharpe", ascending=False)print(results_df.head(10)) # 查看夏普比率最高的 10 组策略最终选出的最优参数为:RSI 周期 21、买入线 30、卖出线 70、置信度阈值 0.75、止盈 10 %、最大持有 20 天。
用最优参数跑完整回测,结果相当漂亮:
也就是说,策略不仅赚了钱,还成功躲开了基准所遭遇的漫长熊市,在保护本金方面表现突出。
但这里必须泼一盆冷水:整个测试期内策略只执行了 6 笔交易。样本量这么小,夏普比率、胜率、平均收益都可能被个别交易严重影响。换句话说,好看的数字未必可靠,这正是我们接下来需要走向前验证的原因。
前面的网格搜索是在整个历史数据上找最优参数,这本质上是「开了上帝视角」——用未来数据反推过去,很容易过拟合。
WFO 则更贴近真实交易:只用过去的数据来优化参数,然后拿到紧接着的未来一段数据上做「样本外测试」,再把时间窗口向前滑动、不断重复。它模拟的正是我们在实盘中「只能用已知历史做决策」的处境。
def walk_forward(df, train_years=2, test_years=1): df = df.copy() all_equity, all_results = [], [] start = df.index.min() test_start = start + pd.DateOffset(years=train_years) while True: # 训练集:测试开始日之前的所有数据 train = df[df.index < test_start] test_end = test_start + pd.DateOffset(years=test_years) # 测试集:紧接着的一段未见过的数据 test = df[(df.index >= test_start) & (df.index < test_end)] if len(test) < 50: # 剩余数据不足,结束循环 break # 仅用过去数据寻优 best_params, _ = optimize(train) # 在样本外数据上验证 result = backtest(test, **best_params) all_results.append(result) test_start = test_end # 时间窗口向前滑动 return all_resultsWFO 的结果如下:
收益从 64.71 % 降到 33.49 % 是完全正常的——因为 WFO 去掉了「用未来选参数」的作弊优势。即便如此,策略在更真实的条件下依然稳定盈利,并且大幅降低了回撤,这才是更有说服力的结果。
案例本身写得很坦诚,也点出了几个关键的现实问题,学习量化时务必记在心里:
这个案例给学习 Python 量化的朋友提供了一套非常完整的方法论:先用置信度评分把主观的 K 线识别变成客观打分,再用 RSI 过滤叠加动量确认,然后用网格搜索系统化寻优,最后用走向前优化做样本外检验。
它最值得借鉴的地方不是那个 64.71 % 的收益数字,而是思维方式:任何在历史数据上看起来很美的策略,都必须经过样本外的严格检验,才谈得上稳健。回测漂亮不等于实盘能赚钱,交易样本太少、忽略成本、缺乏止损,都可能让策略在真实市场中原形毕露。
如果你想动手实践,建议从这几点入手:把策略推广到更多标的、加入更真实的成交假设、补充止损等风险控制手段。记住——在把任何策略投入实盘之前,多做几轮验证永远是值得的。
(本文仅用于 Python 技术学习与交流,不构成任何投资建议。)
加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。
2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!
好文推荐
1. 用 Python 打造股票预测系统:Transformer 模型教程(一)
2. 用 Python 打造股票预测系统:Transformer 模型教程(二)
3. 用 Python 打造股票预测系统:Transformer 模型教程(三)
4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)
6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用
9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解
好书推荐