风险管理与绩效指标 (Risk & Performance)
章节概述
收益率只是故事的一半,风险才是另一半。本章系统讲解量化策略的绩效评估体系:Sharpe 比率、Sortino 比率、最大回撤、Calmar 比率、Alpha/Beta、VaR(在险价值)。每个指标都给出数学公式和 NumPy 实现,并讨论 CAPM 模型和风险平价的基本概念。这是从”看看赚了多少”到”科学评估策略质量”的关键一步。
核心理念:金融中的”风险”不是模糊的直觉,而是可以用数学定义的量。在 C 程序员眼中,这些指标不过是对收益率序列做几个统计运算:均值、标准差、最小累计值、分位数——都是 NumPy 一行代码的事。关键是要理解每个指标背后的假设和局限性。
第一节:收益率的基本处理
1.1 简单收益率与对数收益率
python -c "
import numpy as np
import pandas as pd
# 模拟每日价格
np.random.seed(42)
prices = 100 + np.cumsum(np.random.randn(1000) * 2)
prices = np.maximum(prices, 10)
# 简单收益率
simple_ret = (prices[1:] - prices[:-1]) / prices[:-1]
# 对数收益率
log_ret = np.log(prices[1:] / prices[:-1])
# 两者在很小的时候几乎相等
print('Simple return (first 5):', simple_ret[:5])
print('Log return (first 5): ', log_ret[:5])
print()
print('Differences are tiny for daily returns')
print(f'Max diff: {np.max(np.abs(log_ret - simple_ret)):.10f}')
"对数收益率的优势:时间可加性。N 天对数收益 = 各天对数收益之和。简单收益率则需连乘。
1.2 年化计算
python -c "
import numpy as np
# 假设交易日在 252 天/年
trading_days = 252
np.random.seed(1)
daily_returns = np.random.randn(1000) * 0.01 # 日收益率序列
# 年化收益率
annual_return = daily_returns.mean() * trading_days
# 年化波动率
annual_vol = daily_returns.std() * np.sqrt(trading_days)
# 累计收益率
cumulative_return = np.prod(1 + daily_returns) - 1
print(f'Mean daily return: {daily_returns.mean():.6f}')
print(f'Annualized return: {annual_return:.4f} ({annual_return*100:.2f}%)')
print(f'Annualized volatility:{annual_vol:.4f} ({annual_vol*100:.2f}%)')
print(f'Cumulative return: {cumulative_return:.4f} ({cumulative_return*100:.2f}%)')
"年化公式总结:
| 指标 | 日频 → 年化 |
|---|---|
| 收益率 | μ_daily * 252 |
| 波动率 | σ_daily * sqrt(252) |
| 夏普比率 | √252 * μ_daily / σ_daily |
第二节:核心绩效指标
2.1 夏普比率 (Sharpe Ratio)
衡量每单位风险(波动率)的超额收益。年化夏普比率 > 1 通常被认为不错。
python -c "
import numpy as np
np.random.seed(0)
returns = np.random.randn(1000) * 0.015 + 0.0005 # 模拟策略日收益
risk_free_rate = 0.03 / 252 # 日无风险利率(年化 3%)
# 超额收益
excess_returns = returns - risk_free_rate
# 夏普比率(年化)
sharpe_daily = excess_returns.mean() / excess_returns.std()
sharpe_annual = sharpe_daily * np.sqrt(252)
print(f'Daily Sharpe: {sharpe_daily:.4f}')
print(f'Annual Sharpe: {sharpe_annual:.4f}')
print()
print('Interpretation:')
print(' > 2.0 : Excellent')
print(' 1.0 - 2.0 : Good')
print(' 0.5 - 1.0 : Acceptable')
print(' < 0.5 : Poor')
"2.2 Sortino 比率
夏普比率惩罚上行波动(正收益的波动),而投资者只关心下行风险。Sortino 比率用下行标准差替代总标准差:
其中
python -c "
import numpy as np
np.random.seed(1)
returns = np.random.randn(1000) * 0.015 + 0.0003
risk_free_daily = 0.03 / 252
excess = returns - risk_free_daily
# 下行标准差
downside_returns = np.minimum(returns, 0)
downside_std = np.sqrt(np.mean(downside_returns ** 2))
sortino_daily = excess.mean() / downside_std
sortino_annual = sortino_daily * np.sqrt(252)
print(f'Downside std: {downside_std:.6f}')
print(f'Total std: {returns.std():.6f}')
print(f'Sortino (annual):{sortino_annual:.4f}')
print(f'Sharpe (annual): {excess.mean() / returns.std() * np.sqrt(252):.4f}')
print()
print('Sortino > Sharpe → most volatility is upside')
"2.3 最大回撤 (Max Drawdown)
回撤是从历史最高点到当前点的跌幅。最大回撤是期间最严重的损失。
python -c "
import numpy as np
np.random.seed(0)
returns = np.random.randn(500) * 0.015 + 0.0003
equity_curve = 100 * np.cumprod(1 + returns) # 净值曲线
# 计算最大回撤
rolling_max = np.maximum.accumulate(equity_curve)
drawdown = (equity_curve - rolling_max) / rolling_max
max_drawdown = np.min(drawdown)
max_dd_idx = np.argmin(drawdown)
print(f'Max drawdown: {max_drawdown:.2%}')
print(f'Occurred at index: {max_dd_idx}')
print(f'Drawdown end: {drawdown[-1]:.4%}')
# 回撤持续期(drawdown duration)
is_in_drawdown = drawdown < 0
# 寻找最长回撤持续期
def max_drawdown_duration(drawdown):
"""计算最长水下持续期(bar 数)"""
dd_series = drawdown < 0
duration = 0
current = 0
for d in dd_series:
if d:
current += 1
duration = max(duration, current)
else:
current = 0
return duration
print(f'Max drawdown duration: {max_drawdown_duration(drawdown)} days')
"图解回撤:
净值曲线
/\
/ \ /\
/ \ / \ 最高点(watermark)
/ \/ \/\
/ ← 当前点
|-------| 回撤(跌幅)
2.4 Calmar 比率
python -c "
import numpy as np
np.random.seed(0)
returns = np.random.randn(500) * 0.015 + 0.0005
equity = 100 * np.cumprod(1 + returns)
# 年化收益
annual_ret = returns.mean() * 252
# 最大回撤
roll_max = np.maximum.accumulate(equity)
max_dd = np.min((equity - roll_max) / roll_max)
calmar = annual_ret / abs(max_dd)
print(f'Annual return: {annual_ret:.2%}')
print(f'Max drawdown: {max_dd:.2%}')
print(f'Calmar ratio: {calmar:.4f}')
print('Interpretation: Calmar > 1 means annual return exceeds max drawdown')
"第三节:Alpha 与 Beta —— CAPM 模型
3.1 CAPM 基础
资本资产定价模型 (CAPM) 将股票/策略的收益分解为:
- Alpha ():独立于市场波动的超额收益(策略真正的”能力”)
- Beta ():策略收益对市场收益的敏感度
- :随机误差
3.2 NumPy 实现
python -c "
import numpy as np
np.random.seed(42)
n = 500
# 模拟市场收益和策略收益
market_returns = np.random.randn(n) * 0.01 + 0.0003
# 策略收益 = alpha + beta * 市场收益 + 噪声
true_alpha = 0.0002
true_beta = 1.2
noise = np.random.randn(n) * 0.008
strategy_returns = true_alpha + true_beta * market_returns + noise
# 用线性回归估计 alpha 和 beta
# y = alpha + beta * x → 用 np.polyfit 或公式
beta = np.cov(strategy_returns, market_returns)[0, 1] / np.var(market_returns)
alpha = strategy_returns.mean() - beta * market_returns.mean()
print(f'True Alpha: {true_alpha * 252:.4f} Estimated: {alpha * 252:.4f}')
print(f'True Beta: {true_beta:.4f} Estimated: {beta:.4f}')
print(f'Correlation: {np.corrcoef(strategy_returns, market_returns)[0, 1]:.4f}')
# 年化 Alpha(Jensen Alpha)
annual_alpha = alpha * 252
print(f'Annual Alpha: {annual_alpha:.2%}')
"3.3 信息比率 (Information Ratio)
Information Ratio 衡量每单位跟踪误差(相对于基准的偏离)的超额收益:
python -c "
import numpy as np
np.random.seed(1)
n = 500
benchmark = np.random.randn(n) * 0.01 + 0.0003
tracking_error = np.random.randn(n) * 0.008
strategy = benchmark + tracking_error + 0.0002
excess = strategy - benchmark
ir = excess.mean() / excess.std() * np.sqrt(252)
print(f'Tracking error (annual): {excess.std() * np.sqrt(252):.4f}')
print(f'Information Ratio: {ir:.4f}')
print('IR > 0.5 : Good active manager')
print('IR > 1.0 : Excellent')
"第四节:Value at Risk (VaR) 与 Conditional VaR
4.1 VaR 的定义
VaR(在险价值):在给定置信水平下,一定时期内的最大预期损失。
- VaR 95%:有 95% 的概率损失不超过 VaR(即 5% 概率损失超过 VaR)
python -c "
import numpy as np
np.random.seed(0)
returns = np.random.randn(1000) * 0.015 + 0.0003
# 历史 VaR(非参数法)
confidence = 0.95
var_95 = -np.percentile(returns, 100 * (1 - confidence))
var_99 = -np.percentile(returns, 100 * (1 - 0.99))
print(f'VaR 95% (daily): {var_95:.4f} ({var_95*100:.2f}%)')
print(f'VaR 99% (daily): {var_99:.4f} ({var_99*100:.2f}%)')
print(f'Meaning: 5% chance of losing > {var_95*100:.2f}% in a single day')
print()
# 参数法(假设正态分布)
from scipy.stats import norm
mu, sigma = returns.mean(), returns.std()
var_95_param = -(mu + sigma * norm.ppf(1 - 0.95))
print(f'VaR 95% (parametric): {var_95_param:.4f} ({var_95_param*100:.2f}%)')
"4.2 Conditional VaR (CVaR / Expected Shortfall)
VaR 只告诉你”阈值”,不告诉你阈值之外的损失有多大。CVaR 是超过 VaR 的所有损失的均值。
python -c "
import numpy as np
np.random.seed(1)
returns = np.random.randn(500) * 0.015 + 0.0002
# 历史 CVaR
confidence = 0.95
var_threshold = np.percentile(returns, 100 * (1 - confidence))
cvar = -np.mean(returns[returns <= var_threshold])
print(f'VaR 95% threshold: {var_threshold:.4f}')
print(f'CVaR 95% (expected shortfall): {cvar:.4f} ({cvar*100:.2f}%)')
print(f'Meaning: When things go wrong (worst 5%), avg loss is {cvar*100:.2f}%')
"所有指标汇总:
| 指标 | 公式 | 用途 |
|---|---|---|
| Sharpe | 风险调整收益(对称风险) | |
| Sortino | 风险调整收益(仅下行风险) | |
| Max DD | 最差情况损失 | |
| Calmar | $R_{\text{annual}} / | MDD |
| Alpha | 回归截距 | 独立于市场的收益 |
| Beta | 回归斜率 | 市场敏感度 |
| VaR | 分位数 | 阈值损失 |
| CVaR | 阈值外均值 | 尾部期望损失 |
练习
以下题目用于验证本章所学内容:
| 题号 | 题目 | 链接 | 涉及知识点 |
|---|---|---|---|
| — | 本章无对应力扣题 | — | 请用动手练习题自检 |