04-绩效评估指标
预计学习时间:1.5 小时
难度:⭐⭐⭐
核心问题:如何全面评估一个策略的表现?哪些指标最重要?
指标分类体系
┌────────────────────────────────────────────────────────────────┐
│ 绩效评估指标体系 │
├────────────────────────────────────────────────────────────────┤
│ │
│ 绩效评估 │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ 收益类指标 风险类指标 风险调整收益 │
│ │ │ │ │
│ · 累计收益 · 波动率 · Sharpe │
│ · 年化收益 · 最大回撤 · IR │
│ · 超额收益 · VaR/CVaR · Sortino │
│ · 下行风险 · Calmar │
│ │
│ ┌───────────────────┴───────────────────┐ │
│ │ │ │
│ 交易类指标 收益分布 │
│ │ │ │
│ · 换手率 · 偏度/峰度 │
│ · 胜率 · 月度/年度分析 │
│ · 盈亏比 │
│ │
└────────────────────────────────────────────────────────────────┘
收益类指标
1. 累计收益率
定义:从开始到结束的总收益。
def total_return(returns):
"""
计算累计收益率
参数
----
returns : Series
日收益率序列
返回
----
total_ret : float
累计收益率
"""
return (1 + returns).prod() - 1
# 示例
returns = pd.Series([0.01, -0.005, 0.02, 0.015, -0.01])
print(f"累计收益率: {total_return(returns):.2%}")2. 年化收益率
定义:将累计收益率转换为年度收益率。
其中 是交易天数。
def annual_return(returns, periods_per_year=252):
"""
计算年化收益率
参数
----
returns : Series
收益率序列
periods_per_year : int
每年交易周期(日频252,周频52,月频12)
返回
----
ann_ret : float
年化收益率
"""
n = len(returns)
total_ret = total_return(returns)
return (1 + total_ret) ** (periods_per_year / n) - 1
# 示例
dates = pd.date_range('2020-01-01', '2022-12-31', freq='D')
returns = pd.Series(np.random.randn(len(dates)) * 0.01, index=dates)
print(f"年化收益率: {annual_return(returns):.2%}")3. 超额收益率
定义:策略收益超过基准收益的部分。
def excess_return(strategy_returns, benchmark_returns):
"""
计算超额收益率
参数
----
strategy_returns : Series
策略收益率
benchmark_returns : Series
基准收益率
返回
----
excess_returns : Series
超额收益率序列
"""
# 对齐日期
aligned = pd.concat([strategy_returns, benchmark_returns], axis=1).dropna()
return aligned.iloc[:, 0] - aligned.iloc[:, 1]
# 示例
strategy_ret = pd.Series([0.01, 0.02, -0.01, 0.015, 0.005])
benchmark_ret = pd.Series([0.008, 0.015, -0.005, 0.01, 0.003])
excess = excess_return(strategy_ret, benchmark_ret)
print(f"累计超额收益: {excess.sum():.2%}")风险类指标
1. 波动率 (Volatility)
定义:收益率的标准差,衡量收益的不确定性。
年化波动率:
def volatility(returns, periods_per_year=252):
"""
计算年化波动率
参数
----
returns : Series
收益率序列
periods_per_year : int
每年交易周期
返回
----
vol : float
年化波动率
"""
return returns.std() * np.sqrt(periods_per_year)
# 示例
returns = pd.Series(np.random.randn(252) * 0.01)
print(f"年化波动率: {volatility(returns):.2%}")2. 最大回撤 (Maximum Drawdown)
定义:从历史最高点到最低点的最大跌幅。
def max_drawdown(returns):
"""
计算最大回撤
参数
----
returns : Series
收益率序列
返回
----
max_dd : float
最大回撤
dd_duration : int
最大回撤持续天数
"""
# 计算累计净值
cumulative = (1 + returns).cumprod()
running_max = cumulative.expanding().max()
drawdown = (cumulative - running_max) / running_max
max_dd = drawdown.min()
# 计算最大回撤持续期
max_dd_idx = drawdown.idxmin()
max_dd_start = running_max[:max_dd_idx].idxmax()
dd_duration = (max_dd_idx - max_dd_start).days
return max_dd, dd_duration
# 示例
dates = pd.date_range('2020-01-01', '2022-12-31')
returns = pd.Series(np.random.randn(len(dates)) * 0.01, index=dates)
max_dd, duration = max_drawdown(returns)
print(f"最大回撤: {max_dd:.2%}")
print(f"持续天数: {duration}天")3. VaR (Value at Risk)
定义:在给定置信水平下,可能遭受的最大损失。
历史模拟法:
参数法(正态假设):
def value_at_risk(returns, confidence=0.95, method='historical'):
"""
计算VaR
参数
----
returns : Series
收益率序列
confidence : float
置信水平
method : str
'historical' - 历史模拟法
'parametric' - 参数法
返回
----
var : float
VaR值(负数表示损失)
"""
alpha = 1 - confidence
if method == 'historical':
return returns.quantile(alpha)
elif method == 'parametric':
mu = returns.mean()
sigma = returns.std()
from scipy.stats import norm
z = norm.ppf(alpha)
return mu + z * sigma
else:
raise ValueError(f"Unknown method: {method}")
# 示例
returns = pd.Series(np.random.randn(1000) * 0.01)
var_95 = value_at_risk(returns, confidence=0.95)
var_99 = value_at_risk(returns, confidence=0.99)
print(f"95% VaR: {var_95:.2%}")
print(f"99% VaR: {var_99:.2%}")4. CVaR (Conditional VaR / Expected Shortfall)
定义:超过VaR的平均损失,也称为预期亏损。
def conditional_var(returns, confidence=0.95):
"""
计算CVaR(条件VaR)
参数
----
returns : Series
收益率序列
confidence : float
置信水平
返回
----
cvar : float
CVaR值
"""
var = value_at_risk(returns, confidence)
return returns[returns <= var].mean()
# 示例
cvar_95 = conditional_var(returns, confidence=0.95)
print(f"95% CVaR: {cvar_95:.2%}")风险调整收益指标
1. Sharpe Ratio (夏普比率)
定义:承担单位风险获得的超额收益。
def sharpe_ratio(returns, risk_free_rate=0.03, periods_per_year=252):
"""
计算Sharpe比率
参数
----
returns : Series
收益率序列
risk_free_rate : float
年化无风险利率
periods_per_year : int
每年交易周期
返回
----
sharpe : float
年化Sharpe比率
"""
# 转换为日频无风险利率
daily_rf = (1 + risk_free_rate) ** (1 / periods_per_year) - 1
# 计算超额收益
excess_returns = returns - daily_rf
# 年化
ann_return = excess_returns.mean() * periods_per_year
ann_vol = excess_returns.std() * np.sqrt(periods_per_year)
return ann_return / ann_vol if ann_vol > 0 else 0
# 示例
returns = pd.Series(np.random.randn(252) * 0.01 + 0.0005)
sharpe = sharpe_ratio(returns)
print(f"Sharpe比率: {sharpe:.2f}")经验标准:
| Sharpe | 评价 |
|---|---|
| < 1.0 | 较差 |
| 1.0 - 2.0 | 良好 |
| > 2.0 | 优秀 |
| > 3.0 | 可疑(可能过拟合) |
2. Information Ratio (信息比率)
定义:超额收益与跟踪误差的比值。
def information_ratio(strategy_returns, benchmark_returns):
"""
计算信息比率
参数
----
strategy_returns : Series
策略收益率
benchmark_returns : Series
基准收益率
返回
----
ir : float
信息比率
"""
excess = excess_return(strategy_returns, benchmark_returns)
return excess.mean() / excess.std() * np.sqrt(252) if excess.std() > 0 else 0
# 示例
strategy_ret = pd.Series(np.random.randn(252) * 0.01 + 0.0005)
benchmark_ret = pd.Series(np.random.randn(252) * 0.008 + 0.0003)
ir = information_ratio(strategy_ret, benchmark_ret)
print(f"信息比率: {ir:.2f}")3. Sortino Ratio (索提诺比率)
定义:只考虑下行风险的Sharpe比率。
其中 是下行波动率(只计算负收益)。
def sortino_ratio(returns, risk_free_rate=0.03, target_return=0, periods_per_year=252):
"""
计算Sortino比率
参数
----
returns : Series
收益率序列
risk_free_rate : float
无风险利率
target_return : float
目标收益率(默认0)
periods_per_year : int
每年交易周期
返回
----
sortino : float
Sortino比率
"""
daily_rf = (1 + risk_free_rate) ** (1 / periods_per_year) - 1
excess_returns = returns - daily_rf
# 下行波动率
downside_returns = excess_returns[excess_returns < target_return]
downside_std = downside_returns.std()
ann_return = excess_returns.mean() * periods_per_year
ann_downside_std = downside_std * np.sqrt(periods_per_year)
return ann_return / ann_downside_std if ann_downside_std > 0 else 0
# 示例
sortino = sortino_ratio(returns)
print(f"Sortino比率: {sortino:.2f}")4. Calmar Ratio (卡玛比率)
定义:年化收益与最大回撤的比值。
def calmar_ratio(returns, periods_per_year=252):
"""
计算Calmar比率
参数
----
returns : Series
收益率序列
periods_per_year : int
每年交易周期
返回
----
calmar : float
Calmar比率
"""
ann_ret = annual_return(returns, periods_per_year)
max_dd, _ = max_drawdown(returns)
return ann_ret / abs(max_dd) if max_dd != 0 else np.inf
# 示例
calmar = calmar_ratio(returns)
print(f"Calmar比率: {calmar:.2f}")交易类指标
1. 换手率 (Turnover)
定义:组合权重变化的幅度。
def turnover(weights):
"""
计算换手率
参数
----
weights : DataFrame
每日权重 (T x N)
返回
----
turnover_series : Series
每日换手率
avg_turnover : float
平均换手率
"""
weight_diff = weights.diff().abs()
daily_turnover = weight_diff.sum(axis=1) / 2
return daily_turnover, daily_turnover.mean()
# 示例
dates = pd.date_range('2023-01-01', '2023-12-31')
stocks = [f'S{i}' for i in range(50)]
weights = pd.DataFrame(np.random.rand(len(dates), len(stocks)),
index=dates, columns=stocks)
weights = weights.div(weights.sum(axis=1), axis=0)
daily_to, avg_to = turnover(weights)
print(f"平均日换手率: {avg_to:.2%}")
print(f"年化换手率: {avg_to * 252:.2%}")2. 胜率 (Win Rate)
定义:盈利交易占总交易的比例。
def win_rate(returns):
"""
计算胜率
参数
----
returns : Series
交易收益率序列
返回
----
win_rate : float
胜率
"""
wins = (returns > 0).sum()
total = len(returns)
return wins / total if total > 0 else 0
# 示例
trade_returns = pd.Series([0.05, -0.03, 0.08, -0.02, 0.06, -0.01, 0.04])
wr = win_rate(trade_returns)
print(f"胜率: {wr:.2%}")3. 盈亏比 (Profit/Loss Ratio)
定义:平均盈利与平均亏损的比值。
def profit_loss_ratio(returns):
"""
计算盈亏比
参数
----
returns : Series
交易收益率序列
返回
----
pl_ratio : float
盈亏比
"""
profits = returns[returns > 0]
losses = returns[returns < 0]
if len(profits) == 0 or len(losses) == 0:
return 0
avg_profit = profits.mean()
avg_loss = abs(losses.mean())
return avg_profit / avg_loss if avg_loss > 0 else 0
# 示例
pl = profit_loss_ratio(trade_returns)
print(f"盈亏比: {pl:.2f}")完整绩效报告
PerformanceReport 类
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
class PerformanceReport:
"""完整的绩效评估报告"""
def __init__(self, strategy_returns, benchmark_returns=None,
risk_free_rate=0.03):
"""
参数
----
strategy_returns : Series
策略收益率序列
benchmark_returns : Series, optional
基准收益率序列
risk_free_rate : float
无风险利率
"""
self.strategy_returns = strategy_returns
self.benchmark_returns = benchmark_returns
self.rf = risk_free_rate
def generate_metrics(self):
"""生成所有绩效指标"""
metrics = {}
# 收益指标
metrics['累计收益率'] = total_return(self.strategy_returns)
metrics['年化收益率'] = annual_return(self.strategy_returns)
# 风险指标
metrics['年化波动率'] = volatility(self.strategy_returns)
metrics['最大回撤'], metrics['最大回撤天数'] = max_drawdown(self.strategy_returns)
metrics['95% VaR'] = value_at_risk(self.strategy_returns, 0.95)
metrics['95% CVaR'] = conditional_var(self.strategy_returns, 0.95)
# 风险调整收益
metrics['Sharpe比率'] = sharpe_ratio(self.strategy_returns, self.rf)
metrics['Sortino比率'] = sortino_ratio(self.strategy_returns, self.rf)
metrics['Calmar比率'] = calmar_ratio(self.strategy_returns)
# 与基准对比
if self.benchmark_returns is not None:
excess = excess_return(self.strategy_returns, self.benchmark_returns)
metrics['超额收益率'] = annual_return(excess)
metrics['信息比率'] = information_ratio(
self.strategy_returns, self.benchmark_returns
)
return pd.Series(metrics)
def plot_performance(self, save_path=None):
"""绘制绩效图表"""
# 计算累计净值
strategy_cum = (1 + self.strategy_returns).cumprod()
fig = plt.figure(figsize=(15, 10))
gs = GridSpec(3, 2, figure=fig)
# 1. 累计收益曲线
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(strategy_cum.index, strategy_cum.values,
label='策略', linewidth=2)
if self.benchmark_returns is not None:
benchmark_cum = (1 + self.benchmark_returns).cumprod()
ax1.plot(benchmark_cum.index, benchmark_cum.values,
label='基准', linewidth=2, alpha=0.7)
ax1.set_title('累计净值曲线', fontsize=14, fontproperties='SimHei')
ax1.legend(prop={'family': 'SimHei'})
ax1.grid(True, alpha=0.3)
# 2. 回撤图
ax2 = fig.add_subplot(gs[1, 0])
running_max = strategy_cum.expanding().max()
drawdown = (strategy_cum - running_max) / running_max
ax2.fill_between(drawdown.index, drawdown.values, 0,
color='red', alpha=0.3)
ax2.plot(drawdown.index, drawdown.values, color='red', linewidth=1)
ax2.set_title('回撤', fontsize=12, fontproperties='SimHei')
ax2.grid(True, alpha=0.3)
# 3. 滚动Sharpe
ax3 = fig.add_subplot(gs[1, 1])
rolling_sharpe = self.strategy_returns.rolling(60).apply(
lambda x: sharpe_ratio(x, self.rf)
)
ax3.plot(rolling_sharpe.index, rolling_sharpe.values, linewidth=1)
ax3.axhline(y=1, color='gray', linestyle='--', alpha=0.5)
ax3.set_title('滚动Sharpe比率(60日)', fontsize=12, fontproperties='SimHei')
ax3.grid(True, alpha=0.3)
# 4. 月度收益
ax4 = fig.add_subplot(gs[2, :])
monthly_returns = self.strategy_returns.resample('M').apply(lambda x: (1 + x).prod() - 1)
colors = ['green' if x > 0 else 'red' for x in monthly_returns.values]
ax4.bar(range(len(monthly_returns)), monthly_returns.values, color=colors, alpha=0.7)
ax4.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
ax4.set_xticks(range(len(monthly_returns)))
ax4.set_xticklabels([d.strftime('%Y-%m') for d in monthly_returns.index],
rotation=45, ha='right', fontsize=8)
ax4.set_title('月度收益率', fontsize=12, fontproperties='SimHei')
ax4.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
if save_path:
plt.savefig(save_path, dpi=300, bbox_inches='tight')
plt.show()
def generate_report(self, save_path=None):
"""生成完整报告"""
print("=" * 60)
print("策略绩效评估报告".center(50))
print("=" * 60)
# 打印指标
metrics = self.generate_metrics()
print("\n【绩效指标】")
for name, value in metrics.items():
if isinstance(value, float):
if '率' in name or '比率' in name or 'VaR' in name or 'CVaR' in name:
print(f" {name:15s}: {value:10.2%}")
elif '天数' in name:
print(f" {name:15s}: {int(value):10}天")
else:
print(f" {name:15s}: {value:10.4f}")
# 绘图
self.plot_performance(save_path)
return metrics
# 示例使用
if __name__ == "__main__":
# 生成模拟数据
np.random.seed(42)
dates = pd.date_range('2020-01-01', '2023-12-31')
strategy_returns = pd.Series(
np.random.randn(len(dates)) * 0.015 + 0.0003,
index=dates
)
benchmark_returns = pd.Series(
np.random.randn(len(dates)) * 0.012 + 0.0002,
index=dates
)
# 生成报告
report = PerformanceReport(strategy_returns, benchmark_returns)
metrics = report.generate_report()月度收益热力图
def plot_monthly_returns(returns, ax=None):
"""
绘制月度收益热力图
参数
----
returns : Series
日收益率序列
ax : matplotlib Axes, optional
子图对象
"""
# 计算月度收益
monthly = returns.resample('M').apply(lambda x: (1 + x).prod() - 1)
# 创建年月矩阵
monthly_df = pd.DataFrame({
'year': monthly.index.year,
'month': monthly.index.month,
'return': monthly.values
})
pivot = monthly_df.pivot(index='year', columns='month', values='return')
# 绘图
if ax is None:
fig, ax = plt.subplots(figsize=(12, 6))
im = ax.imshow(pivot.values, cmap='RdYlGn', aspect='auto',
vmin=-0.1, vmax=0.1)
# 设置刻度
ax.set_xticks(range(12))
ax.set_xticklabels(['1月', '2月', '3月', '4月', '5月', '6月',
'7月', '8月', '9月', '10月', '11月', '12月'])
ax.set_yticks(range(len(pivot.index)))
ax.set_yticklabels(pivot.index)
# 添加数值标签
for i in range(len(pivot.index)):
for j in range(12):
text = ax.text(j, i, f'{pivot.values[i, j]:.1%}',
ha='center', va='center',
color='black', fontsize=8)
ax.set_title('月度收益率热力图', fontproperties='SimHei', fontsize=14)
# 添加colorbar
cbar = plt.colorbar(im, ax=ax)
cbar.set_label('收益率', fontproperties='SimHei')
plt.tight_layout()
return ax收益分布分析
偏度 (Skewness)
定义:衡量收益分布的不对称性。
from scipy.stats import skew
def skewness(returns):
"""计算偏度"""
return skew(returns.dropna())
# 解释
# skew > 0: 正偏(右尾更长)
# skew < 0: 负偏(左尾更长,风险更大)
# skew = 0: 对称分布峰度 (Kurtosis)
定义:衡量收益分布的尖峰程度。
from scipy.stats import kurtosis
def kurt(returns):
"""计算峰度(超额峰度)"""
return kurtosis(returns.dropna())
# 解释
# kurt > 0: 尖峰分布(极端风险更高)
# kurt < 0: 平坦分布
# kurt = 0: 正态分布核心知识点总结
┌────────────────────────────────────────────────────────────────┐
│ 04-绩效评估指标 核心要点 │
├────────────────────────────────────────────────────────────────┤
│ │
│ 1. 收益指标 │
│ 累计收益 → 年化收益 → 超额收益 │
│ │
│ 2. 风险指标 │
│ 波动率、最大回撤、VaR、CVaR │
│ │
│ 3. 风险调整收益 │
│ Sharpe > 1 良好 > 2 优秀 │
│ IR 衡量相对基准的表现 │
│ Sortino 只考虑下行风险 │
│ │
│ 4. 交易指标 │
│ 换手率影响成本 │
│ 胜率 + 盈亏比 = 综合评估 │
│ │
│ 5. 可视化 │
│ 净值曲线、回撤图、滚动Sharpe、月度热力图 │
│ │
└────────────────────────────────────────────────────────────────┘
思考题
- 为什么Sharpe > 3 的回测结果需要警惕?
- 最大回撤和波动率,哪个对投资者更重要?
- 如何判断一个策略是”靠运气”还是”靠能力”?
下一步
现在你知道如何评估策略表现了。但回测中最重要的是识别陷阱——下一节将学习回测中的各种偏差和防范方法。