
用 Python 揭秘均值回归策略:你的收益从何而来?
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 350 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
最近读到一篇非常有意思的文章:一位交易者用 8 个月时间运行了一个模拟交易账户,最终算出信息比率(Information Ratio)高达 1.62——远超机构投资者公认的 1.0"卓越"门槛。
作为 Python 学习者,我们完全可以用代码把这篇文章中的每一个关键指标"复刻"出来。今天这篇推文,就带你一步一步用 Python 实现:
无论你是对量化金融感兴趣,还是想找一个真实场景练手 Python,这篇文章都值得收藏。
信息比率衡量的是每承担一单位主动风险,能获得多少主动收益。公式为:
IR = 主动收益 / 跟踪误差
其中:
在机构投资领域,IR 高于 0.5 被认为是"良好",高于 1.0 被认为是"卓越"。
原文中的这个组合,年化主动收益为 7.0%,跟踪误差为 4.4%,我们来验证一下:
# 计算信息比率(IR)
active_return = 0.070 # 年化主动收益 7.0%
tracking_error = 0.044 # 年化跟踪误差 4.4%
ir = active_return / tracking_error
print(f"信息比率(IR):{ir:.2f}")
# 输出:信息比率(IR):1.59计算结果为 1.59,与原文中从另一个角度得出的 1.62 基本吻合(原文也提到两种方法结果互相印证)。
信息比率还可以通过 Grinold-Kahn 主动管理基本定律 进行分解:
IR = IC × √Breadth × TC
三个因子分别代表:
原文给出的参数为:IC = 0.39,Breadth = 35,TC = 0.70。我们来计算:
import math
# 主动管理基本定律(Fundamental Law of Active Management)
ic = 0.39 # 信息系数:预测准确度
breadth = 35 # 广度:年化独立下注次数
tc = 0.70 # 转换系数:执行效率(模拟实盘摩擦后的折扣)
# 计算理论信息比率
ir_flam = ic * math.sqrt(breadth) * tc
print(f"IC:{ic}")
print(f"√Breadth:{math.sqrt(breadth):.2f}")
print(f"TC:{tc}")
print(f"FLAM 信息比率(IR):{ir_flam:.2f}")
# 输出:
# IC:0.39
# √Breadth:5.92
# TC:0.70
# FLAM 信息比率(IR):1.62得到 IR = 1.62,与原文完全一致。
值得注意的是,如果 TC = 1.0(即纸上交易无摩擦),IR 将高达:
# 无摩擦情况下的理论 IR
ir_no_friction = ic * math.sqrt(breadth) * 1.0
print(f"无摩擦理论 IR:{ir_no_friction:.2f}")
# 输出:无摩擦理论 IR:2.31这说明真实交易中的摩擦会"吃掉"大约 30% 的信号质量。
原文给出了 23 笔已平仓交易的关键统计数据。我们用 Python 来验证这些数字之间的一致性,并模拟一个简单的交易数据集。
import numpy as np
# === 原文交易统计 ===
total_trades = 23 # 总交易笔数
wins = 16 # 盈利笔数
losses = total_trades - wins # 亏损笔数
# 计算胜率
win_rate = wins / total_trades
print(f"胜率:{win_rate:.1%}")
# 输出:胜率:69.6%
# 盈亏比(平均盈利 / 平均亏损)
win_loss_ratio = 1.47 # 原文数据:平均盈利是平均亏损的 1.47 倍
# 利润因子 = (胜率 × 平均盈利) / (败率 × 平均亏损)
# 也等于 = (wins × avg_win) / (losses × avg_loss)
# = (wins / losses) × (avg_win / avg_loss)
# = (wins / losses) × win_loss_ratio
profit_factor_calc = (wins / losses) * win_loss_ratio
print(f"利润因子(计算值):{profit_factor_calc:.2f}")
print(f"利润因子(原文值):5.90")
# 输出:
# 利润因子(计算值):3.36
# 利润因子(原文值):5.90这里我们发现一个有趣的问题:通过胜率和盈亏比简单相乘得到的利润因子(3.36)与原文声称的 5.90 存在较大差异。这说明原文中的"平均盈利是平均亏损的 1.47 倍"是中位数或简单均值概念,而利润因子使用的是总盈利 / 总亏损,两者在分布不均匀时可以产生差异——比如有几笔大赢家拉高了总盈利。
我们可以用 Python 模拟一组满足所有约束条件的交易:
np.random.seed(42)
# 模拟一组交易数据来逼近原文的利润因子 5.90
# 假设平均亏损为 1 个单位
avg_loss = 1.0
# 生成 7 笔亏损(金额围绕 avg_loss 波动)
loss_amounts = np.random.uniform(0.5, 1.5, size=losses)
total_loss = loss_amounts.sum()
# 利润因子 = 总盈利 / 总亏损 = 5.90
target_profit_factor = 5.90
total_win_needed = target_profit_factor * total_loss
# 生成 16 笔盈利,使总盈利达到目标
# 引入右偏分布(少数大赢家)
win_amounts = np.random.exponential(scale=1.0, size=wins)
win_amounts = win_amounts / win_amounts.sum() * total_win_needed
print(f"--- 模拟交易数据 ---")
print(f"总亏损:{total_loss:.2f}")
print(f"总盈利:{total_win_needed:.2f}")
print(f"利润因子:{total_win_needed / total_loss:.2f}")
print(f"平均盈利:{win_amounts.mean():.2f}")
print(f"平均亏损:{loss_amounts.mean():.2f}")
print(f"盈亏比:{win_amounts.mean() / loss_amounts.mean():.2f}")这个模拟可以帮助我们理解:利润因子、胜率和盈亏比三者之间的关系并非简单的乘法,分布形态(特别是右偏性)会显著影响总量比值。
原文提到组合的 Beta 仅为 0.08,这意味着超额收益几乎与大盘无关。在 Python 中,我们通常用线性回归来计算 Beta:
from numpy.polynomial.polynomial import polyfit
# 模拟 8 个月的月度数据
# 组合月度超额收益(相对于无风险利率)
portfolio_excess = np.array([0.025, 0.031, -0.005, 0.042, 0.018, 0.035, -0.010, 0.028])
# S&P 500 月度超额收益
benchmark_excess = np.array([0.020, 0.035, 0.015, -0.010, 0.025, 0.040, -0.030, 0.022])
# 用最小二乘法拟合:portfolio = alpha + beta × benchmark
# np.polyfit 中 deg=1 表示一次多项式(线性回归)
beta, alpha = np.polyfit(benchmark_excess, portfolio_excess, deg=1)
print(f"Alpha(月度):{alpha:.4f}")
print(f"Beta:{beta:.4f}")
# 低 Beta 意味着组合收益与市场走势相关性很低
# 原文中 Beta = 0.08,说明超额收益几乎是"纯 Alpha"当 Beta 接近零时,说明你的收益来源主要是选股和择时能力(Alpha),而不是简单地跟随市场上涨(Beta 暴露)。
原文揭示了一个经典的行为金融学现象——处置效应(Disposition Effect):亏损的交易平均持有 59 天,而盈利的交易只持有 43 天。人们倾向于"割肉太慢,止盈太快"。
我们可以用 Python 来可视化这种偏差:
import matplotlib.pyplot as plt
# 设置中文字体(如果在本地运行需要配置,这里用英文标签替代)
# 盈利交易和亏损交易的持仓天数
np.random.seed(0)
winner_days = np.random.normal(loc=43, scale=10, size=16).clip(min=5) # 盈利交易
loser_days = np.random.normal(loc=59, scale=12, size=7).clip(min=10) # 亏损交易
fig, ax = plt.subplots(figsize=(8, 5))
# 绘制箱线图对比
bp = ax.boxplot(
[winner_days, loser_days],
labels=['Winners (盈利)', 'Losers (亏损)'],
patch_artist=True,
boxprops=dict(facecolor='lightblue'),
medianprops=dict(color='red', linewidth=2)
)
ax.set_ylabel('Holding Days (持仓天数)')
ax.set_title('Disposition Effect: Winners vs Losers Holding Period')
ax.axhline(y=43, color='green', linestyle='--', alpha=0.5, label='Avg Winner: 43 days')
ax.axhline(y=59, color='red', linestyle='--', alpha=0.5, label='Avg Loser: 59 days')
ax.legend()
plt.tight_layout()
plt.savefig('disposition_effect.png', dpi=150)
plt.show()
print("处置效应可视化完成:亏损交易平均多持有 16 天")解决方案很简单:设置严格的止损规则。不需要改变选股策略,只需要缩短亏损交易的持有时间,就能显著提升整体 IC 和主动收益。
下面我们把上面所有的分析整合成一个完整的 Python 脚本:
import math
import numpy as np
def calculate_ir_from_returns(active_return: float, tracking_error: float) -> float:
"""
通过主动收益和跟踪误差计算信息比率
参数:
active_return: 年化主动收益(小数形式)
tracking_error: 年化跟踪误差(小数形式)
返回:
信息比率
"""
return active_return / tracking_error
def calculate_ir_from_flam(ic: float, breadth: int, tc: float) -> float:
"""
通过主动管理基本定律(FLAM)计算信息比率
参数:
ic: 信息系数(预测准确度)
breadth: 年化独立下注次数
tc: 转换系数(0 到 1 之间)
返回:
信息比率
"""
return ic * math.sqrt(breadth) * tc
def analyze_trades(wins: int, losses: int, avg_win: float, avg_loss: float) -> dict:
"""
分析交易统计指标
参数:
wins: 盈利交易笔数
losses: 亏损交易笔数
avg_win: 平均盈利金额
avg_loss: 平均亏损金额
返回:
包含各项指标的字典
"""
total = wins + losses
win_rate = wins / total
win_loss_ratio = avg_win / avg_loss
profit_factor = (wins * avg_win) / (losses * avg_loss)
return {
"总交易数": total,
"胜率": f"{win_rate:.1%}",
"盈亏比": f"{win_loss_ratio:.2f}×",
"利润因子": f"{profit_factor:.2f}×",
}
def check_disposition_effect(avg_winner_days: float, avg_loser_days: float) -> str:
"""
检测是否存在处置效应
参数:
avg_winner_days: 盈利交易平均持仓天数
avg_loser_days: 亏损交易平均持仓天数
返回:
诊断结论
"""
if avg_loser_days > avg_winner_days:
diff = avg_loser_days - avg_winner_days
return f"检测到处置效应:亏损交易比盈利交易平均多持有 {diff:.0f} 天"
return "检测到明显的处置效应"
# === 主程序 ===
if __name__ == "__main__":
print("=" * 50)
print("量化交易账户分析报告")
print("=" * 50)
# 1. 信息比率(两种方法)
ir1 = calculate_ir_from_returns(0.070, 0.044)
ir2 = calculate_ir_from_flam(ic=0.39, breadth=35, tc=0.70)
print(f"\n【信息比率】")
print(f" 收益/风险法:IR = {ir1:.2f}")
print(f" FLAM 分解法:IR = {ir2:.2f}")
# 2. 交易统计
# 注意:此处 avg_win 和 avg_loss 为模拟值
stats = analyze_trades(wins=16, losses=7, avg_win=2.58, avg_loss=1.0)
print(f"\n【交易统计】")
for key, value in stats.items():
print(f" {key}:{value}")
# 3. 处置效应检测
disposition = check_disposition_effect(avg_winner_days=43, avg_loser_days=59)
print(f"\n【行为偏差检测】")
print(f" {disposition}")
# 4. Alpha / Beta 分析
print(f"\n【风险分解】")
print(f" 组合收益:+26.8%")
print(f" 基准收益:+22.7%(S&P 500)")
print(f" 原始 Alpha:+4.1%")
print(f" Beta:0.08(近似纯 Alpha)")
print(f"\n{'=' * 50}")
print("分析完毕。")运行输出如下:
==================================================
量化交易账户分析报告
==================================================
【信息比率】
收益/风险法:IR = 1.59
FLAM 分解法:IR = 1.62
【交易统计】
总交易数:23
胜率:69.6%
盈亏比:2.58×
利润因子:5.90×
【行为偏差检测】
检测到处置效应:亏损交易比盈利交易平均多持有 16 天
【风险分解】
组合收益:+26.8%
基准收益:+22.7%(S&P 500)
原始 Alpha:+4.1%
Beta:0.08(近似纯 Alpha)
==================================================
分析完毕。这篇文章基于一个真实的 8 个月模拟交易账户数据,用 Python 带你完成了以下分析:
关键启示:你不需要每笔都赚钱,你需要的是胜率略高于随机、盈利略大于亏损、并且严格执行止损纪律。这就是主动管理基本定律告诉我们的核心逻辑。
对 Python 学习者来说,量化金融是一个绝佳的练手场景——它涉及数学计算、数据分析、可视化和软件工程,每一个环节都能帮你提升编程能力。
2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!
好文推荐
1. 用 Python 打造股票预测系统:Transformer 模型教程(一)
2. 用 Python 打造股票预测系统:Transformer 模型教程(二)
3. 用 Python 打造股票预测系统:Transformer 模型教程(三)
4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)
6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用
9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解
好书推荐