作者:墨星
掘金技术社区
一、引言
在量化交易中,策略回测只是第一步。更关键的是:如何科学地评估策略的表现?
今天给大家介绍一个强大的 Python 量化分析库——QuantStats。它可以帮你一键计算夏普比率、最大回撤、年化收益等核心指标,让绩效分析变得非常简单。
二、QuantStats 是什么?
QuantStats 是一个专为投资组合分析设计的 Python 库。它的特点是:
- 一键生成绩效报告:只需几行代码,就能生成专业的分析报告
- 丰富指标库:夏普比率、索提诺比率、卡玛比率、最大回撤等
- 可视化图表:收益率曲线、回撤曲线、收益分布图等
- 与 Backtrader 无缝集成:可以直接分析回测结果
三、安装与环境配置
# 安装 QuantStats
pip install quantstats
# 如果需要绘图功能
pip install matplotlib seaborn
四、快速入门:基础使用
4.1 准备数据
QuantStats 需要收益率数据(Series 格式):
import quantstats as qs
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 方法1:使用随机数据演示(仅供学习参考)
np.random.seed(42)
dates = pd.date_range('2023-01-01', '2025-12-31', freq='B')
returns = np.random.normal(0.001, 0.02, len(dates)) # 日收益率
returns = pd.Series(returns, index=dates)
# 方法2:从文件读取实际数据
# returns = pd.read_csv('returns.csv', index_col=0, parse_dates=True)['returns']
print(f"数据点数: {len(returns)}")
print(f"平均日收益: {returns.mean():.4f}")
print(f"累计收益: {((1 + returns).prod() - 1) * 100:.2f}%")
4.2 一键生成完整报告
# 一行代码生成 HTML 报告(仅供学习参考)
qs.reports.html(returns, title="策略绩效报告", output="performance_report.html")
这会生成一个交互式 HTML 报告,包含所有核心指标。
五、核心指标计算详解
5.1 年化收益率
def calculate_annual_return(returns):
"""计算年化收益率"""
total_return = (1 + returns).prod() - 1
years = len(returns) / 252 # 假设一年252个交易日
annual_return = (1 + total_return) ** (1 / years) - 1
return annual_return
annual_ret = calculate_annual_return(returns)
print(f"年化收益率: {annual_ret * 100:.2f}%")
5.2 夏普比率(Sharpe Ratio)
def calculate_sharpe_ratio(returns, risk_free_rate=0.0):
"""计算夏普比率"""
excess_returns = returns - risk_free_rate / 252
if excess_returns.std() == 0:
return 0
sharpe = excess_returns.mean() / excess_returns.std() * np.sqrt(252)
return sharpe
sharpe_ratio = calculate_sharpe_ratio(returns)
print(f"夏普比率: {sharpe_ratio:.2f}")
# QuantStats 内置方法
print(f"夏普比率(QuantStats): {qs.stats.sharpe(returns):.2f}")
夏普比率解读:
- < 0.5:较差
- 0.5-1.0:一般
- 1.0-2.0:良好
-
2.0:优秀
5.3 最大回撤(Maximum Drawdown)
def calculate_max_drawdown(returns):
"""计算最大回撤"""
cumulative = (1 + returns).cumprod()
running_max = cumulative.cummax()
drawdown = (cumulative - running_max) / running_max
max_dd = drawdown.min()
return max_dd
max_dd = calculate_max_drawdown(returns)
print(f"最大回撤: {max_dd * 100:.2f}%")
# QuantStats 内置方法
print(f"最大回撤(QuantStats): {qs.stats.max_drawdown(returns) * 100:.2f}%")
5.4 索提诺比率(Sortino Ratio)
def calculate_sortino_ratio(returns, target_return=0):
"""计算索提诺比率"""
excess_returns = returns - target_return / 252
downside_returns = excess_returns[excess_returns < 0]
if len(downside_returns) == 0 or downside_returns.std() == 0:
return 0
sortino = excess_returns.mean() / downside_returns.std() * np.sqrt(252)
return sortino
sortino = calculate_sortino_ratio(returns)
print(f"索提诺比率: {sortino:.2f}")
# QuantStats 内置方法
print(f"索提诺比率(QuantStats): {qs.stats.sortino(returns):.2f}")
索提诺比率解读: 只关注下行风险,比夏普比率更适合波动大的策略。
5.5 卡玛比率(Calmar Ratio)
def calculate_calmar_ratio(returns):
"""计算卡玛比率(年化收益/最大回撤)"""
annual_ret = calculate_annual_return(returns)
max_dd = abs(calculate_max_drawdown(returns))
if max_dd == 0:
return 0
return annual_ret / max_dd
calmar = calculate_calmar_ratio(returns)
print(f"卡玛比率: {calmar:.2f}")
# QuantStats 内置方法
print(f"卡玛比率(QuantStats): {qs.stats.calmar(returns):.2f}")
卡玛比率解读:越高越好,表示单位风险承担的收益越高。
六、可视化分析
6.1 收益率曲线
import quantstats as qs
import matplotlib.pyplot as plt
# 计算累计收益
cumulative_returns = (1 + returns).cumprod()
# 绘制收益率曲线
plt.figure(figsize=(12, 6))
plt.plot(cumulative_returns, label='策略累计收益')
plt.axhline(y=1, color='r', linestyle='--', label='基准线')
plt.title('策略收益率曲线', fontsize=14)
plt.xlabel('日期')
plt.ylabel('累计收益倍数')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
# QuantStats 内置绘图
qs.plots.returns(returns)
6.2 回撤曲线
import quantstats as qs
# 绘制回撤曲线
qs.plots.drawdown(returns, title='策略回撤分析')
# 或者自定义计算并绘制
cumulative = (1 + returns).cumprod()
running_max = cumulative.cummax()
drawdown = (cumulative - running_max) / running_max * 100
plt.figure(figsize=(12, 6))
plt.fill_between(drawdown.index, drawdown.values, 0, color='red', alpha=0.3)
plt.plot(drawdown.index, drawdown.values, color='red')
plt.title('回撤曲线', fontsize=14)
plt.xlabel('日期')
plt.ylabel('回撤百分比 (%)')
plt.grid(True, alpha=0.3)
plt.show()
6.3 收益分布图
import quantstats as qs
import matplotlib.pyplot as plt
# QuantStats 内置分布图
qs.plots.histogram(returns, resample='A')
# 或自定义
daily_returns_pct = returns * 100
plt.figure(figsize=(10, 6))
plt.hist(daily_returns_pct, bins=50, edgecolor='black', alpha=0.7)
plt.axvline(daily_returns_pct.mean(), color='red', linestyle='--',
label=f'均值: {daily_returns_pct.mean():.2f}%')
plt.axvline(daily_returns_pct.median(), color='green', linestyle='--',
label=f'中位数: {daily_returns_pct.median():.2f}%')
plt.xlabel('日收益率 (%)')
plt.ylabel('频次')
plt.title('日收益率分布')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
6.4 月度收益热力图
import quantstats as qs
# 生成月度收益热力图
qs.plots.monthly_heatmap(returns)
七、完整示例:策略对比分析
# 完整对比分析示例(仅供学习参考)
import quantstats as qs
import pandas as pd
import numpy as np
np.random.seed(42)
# 模拟三种策略
dates = pd.date_range('2024-01-01', periods=252, freq='B')
# 策略A:激进型(高风险高波动)
strategy_a = pd.Series(
np.random.normal(0.0015, 0.025, len(dates)),
index=dates
)
# 策略B:稳健型(低风险低波动)
strategy_b = pd.Series(
np.random.normal(0.0008, 0.015, len(dates)),
index=dates
)
# 策略C:平衡型
strategy_c = pd.Series(
np.random.normal(0.001, 0.02, len(dates)),
index=dates
)
# 策略对比报告
comparison = pd.DataFrame({
'策略A(激进)': [
qs.stats.sharpe(strategy_a),
qs.stats.max_drawdown(strategy_a),
qs.stats.sortino(strategy_a),
qs.stats.calmar(strategy_a),
(strategy_a.sum() / len(strategy_a)) * 252 * 100, # 年化收益
],
'策略B(稳健)': [
qs.stats.sharpe(strategy_b),
qs.stats.max_drawdown(strategy_b),
qs.stats.sortino(strategy_b),
qs.stats.calmar(strategy_b),
(strategy_b.sum() / len(strategy_b)) * 252 * 100,
],
'策略C(平衡)': [
qs.stats.sharpe(strategy_c),
qs.stats.max_drawdown(strategy_c),
qs.stats.sortino(strategy_c),
qs.stats.calmar(strategy_c),
(strategy_c.sum() / len(strategy_c)) * 252 * 100,
]
}, index=['夏普比率', '最大回撤', '索提诺比率', '卡玛比率', '年化收益(%)'])
print("\n📊 策略绩效对比:")
print(comparison.round(2))
# 选择最优策略
best_sharpe = comparison.columns[comparison.loc['夏普比率'].idxmax()]
best_calmar = comparison.columns[comparison.loc['卡玛比率'].idxmax()]
print(f"\n🏆 夏普比率最高: {best_sharpe}")
print(f"🏆 卡玛比率最高: {best_calmar}")
八、与 Backtrader 集成
如果使用 Backtrader 进行策略回测,可以直接将回测结果传入 QuantStats 分析:
import quantstats as qs
import backtrader as bt
# 回测结束后获取收益数据
# cerebro.run() 完成后
def analyze_results(cerebro):
"""分析回测结果"""
# 从回测结果中提取收益率序列
# 这里假设 returns 是你的日收益率 Series
# 使用 QuantStats 分析
qs.reports.full(returns)
# 生成 HTML 报告
qs.reports.html(returns,
title="Backtrader 回测报告",
output="backtrader_report.html")
# 打印关键指标
print(f"\n📈 夏普比率: {qs.stats.sharpe(returns):.2f}")
print(f"📉 最大回撤: {qs.stats.max_drawdown(returns)*100:.2f}%")
print(f"📊 索提诺比率: {qs.stats.sortino(returns):.2f}")
print(f"🏆 卡玛比率: {qs.stats.calmar(returns):.2f}")
# 执行分析
# analyze_results(cerebro)
九、常见问题与解决方案
Q1:为什么我的夏普比率是负数?
原因:策略跑输无风险利率(如国债收益率)。
建议:
- 检查收益率计算是否正确
- 对比基准是否为合理基准
- 考虑调整策略参数或换策略
Q2:最大回撤多少算合理?
参考:
- 单只股票型基金:20-30%
- 指数基金:15-25%
- 稳健型策略:<15%
注意:回撤大小和收益预期成正比,需找到适合自身风险偏好的策略。
Q3:量化指标阈值推荐
| 指标 | 优秀 | 良好 | 一般 | 较差 |
|---|---|---|---|---|
| 夏普比率 | >2.0 | 1.0-2.0 | 0.5-1.0 | <0.5 |
| 卡玛比率 | >3.0 | 1.5-3.0 | 1.0-1.5 | <1.0 |
| 最大回撤 | <10% | 10-20% | 20-30% | >30% |
十、总结
QuantStats 是量化交易者不可或缺的分析工具,它的核心价值在于:
- 快速计算:一键生成绩效报告,节省大量开发时间
- 标准指标:提供业界通用的风险收益指标
- 可视化:直观的图表展示
- 开源免费:社区活跃,持续更新
学习建议:
- 先用模拟数据跑通完整流程
- 结合实际Portfolio数据进行对比分析
- 逐步理解每个指标的含义和适用场景
免责声明
⚠️ 风险提示:
- 本文仅供学习参考,不构成任何投资建议
- 量化交易涉及高风险,过往业绩不代表未来收益
- 使用策略前请充分回测和模拟验证
- 实盘交易需结合自身风险承受能力
- 投资有风险,入市需谨慎
参考资源
- QuantStats GitHub: github.com/ranaroussi/…
- 官方文档: quantstats.readthedocs.io/
- 掘金技术社区: juejin.cn/
作者:墨星
掘金技术社区 | 专注量化交易技术分享
本文代码仅供学习参考,欢迎技术交流!