引言:理解风险平价策略的核心价值
在当今充满不确定性的金融市场中,传统的资产配置方法往往难以应对极端波动。风险平价(Risk Parity)策略应运而生,它通过重新分配风险贡献度,让每种资产在投资组合中承担相等的风险,从而实现真正的风险平衡。这种策略的核心理念是:不是分配资金,而是分配风险。
与传统的60/40股债配置不同,风险平价策略认为股票的风险远高于债券。通过杠杆或调整权重,可以使债券和商品等低风险资产承担与股票相同的风险贡献,从而在市场波动中实现更稳健的收益。本文将详细探讨如何通过回测验证风险平价策略的有效性,并提供完整的Python实现代码。
风险平价策略的理论基础
风险贡献度的计算原理
风险平价策略的核心在于计算每种资产对整体组合的风险贡献。这需要使用边际风险贡献(Marginal Risk Contribution)和成分风险贡献(Component Risk Contribution)的概念。
对于一个包含N种资产的投资组合,其总风险(波动率)可以表示为:
σ_p = √(wᵀ * Σ * w)
其中w是权重向量,Σ是协方差矩阵。
每种资产的边际风险贡献为:
MRC_i = (Σ * w)_i / σ_p
成分风险贡献为:
RC_i = w_i * MRC_i
风险平价的目标是让所有资产的RC_i相等,即:
RC_1 = RC_2 = ... = RC_N
与传统配置策略的对比
传统60/40配置中,股票通常贡献超过90%的风险,而债券贡献不足10%。这种不平衡导致组合在股市崩盘时遭受重创。风险平价策略通过以下方式解决这一问题:
- 风险均衡:让每种资产承担相同风险
- 杠杆使用:对低风险资产(如债券)使用杠杆提升其风险贡献
- 多元化:纳入更多资产类别(如商品、REITs等)
Python实现:风险平价策略回测框架
环境准备与数据获取
首先,我们需要安装必要的Python库:
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
from scipy.optimize import minimize
import seaborn as sns
# 设置中文字体(如果需要显示中文)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
接下来,我们从Yahoo Finance获取历史数据:
# 定义资产代码和名称
assets = {
'SPY': '标普500ETF',
'TLT': '20年期国债ETF',
'GLD': '黄金ETF',
'QQQ': '纳斯达克100ETF'
}
# 获取2010年至今的历史数据
start_date = '2010-01-01'
end_date = '2023-12-31'
# 下载数据
data = yf.download(list(assets.keys()), start=start_date, end=end_date)['Adj Close']
returns = data.pct_change().dropna()
print(f"数据时间范围: {returns.index[0]} 至 {returns.index[-1]}")
print(f"数据形状: {returns.shape}")
协方差矩阵估计
准确估计协方差矩阵对风险平价至关重要。我们使用指数加权移动平均(EWMA)来捕捉时变波动率:
def calculate_ewma_covariance(returns, lambda_factor=0.94):
"""
计算指数加权移动平均协方差矩阵
参数:
returns: 收益率数据
lambda_factor: 衰减因子,通常取0.94
返回:
cov_matrix: 协方差矩阵
"""
# 计算指数加权协方差
cov_matrix = returns.ewm(alpha=1-lambda_factor).cov()
# 返回最新的协方差矩阵
return cov_matrix.iloc[-len(assets):, -len(assets):]
# 示例:计算最近一期的协方差矩阵
latest_cov = calculate_ewma_covariance(returns)
print("最新协方差矩阵:")
print(latest_cov)
风险平价权重优化
使用优化算法求解风险平价权重:
def calculate_risk_parity_weights(cov_matrix, max_leverage=2.0):
"""
计算风险平价权重
参数:
cov_matrix: 协方差矩阵
max_leverage: 最大杠杆倍数
返回:
weights: 优化后的权重
"""
n = len(cov_matrix)
# 目标函数:风险贡献度差异最小化
def risk_parity_objective(weights):
# 确保权重为正且总和不超过杠杆限制
weights = np.array(weights)
portfolio_variance = weights @ cov_matrix @ weights.T
portfolio_volatility = np.sqrt(portfolio_variance)
# 计算边际风险贡献
marginal_risk_contrib = (cov_matrix @ weights) / portfolio_volatility
# 计算成分风险贡献
risk_contrib = weights * marginal_risk_contrib
# 目标:最小化风险贡献的差异
target_risk_contrib = portfolio_volatility / n
return np.sum((risk_contrib - target_risk_contrib) ** 2)
# 约束条件
constraints = (
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1.0}, # 权重和为1
{'type': 'ineq', 'fun': lambda w: max_leverage - np.sum(np.abs(w))}, # 杠杆限制
{'type': 'ineq', 'fun': lambda w: w} # 权重非负
)
# 初始猜测
initial_weights = np.array([1/n] * n)
# 优化
result = minimize(
risk_parity_objective,
initial_weights,
method='SLSQP',
constraints=constraints,
options={'ftol': 1e-9, 'maxiter': 1000}
)
return result.x
# 示例:计算风险平价权重
weights = calculate_risk_parity_weights(latest_cov)
print("\n风险平价权重:")
for i, asset in enumerate(assets.keys()):
print(f"{asset}: {weights[i]:.2%}")
完整回测系统
现在我们构建一个完整的回测系统,包括交易成本和再平衡:
class RiskParityBacktester:
def __init__(self, returns, rebalance_freq='M', transaction_cost=0.001):
self.returns = returns
self.rebalance_freq = rebalance_freq
self.transaction_cost = transaction_cost
self.n_assets = len(returns.columns)
def run_backtest(self, max_leverage=2.0, start_date=None, end_date=None):
"""
运行风险平价回测
参数:
max_leverage: 最大杠杆倍数
start_date: 回测开始日期
end_date: 回测结束日期
返回:
result: 回测结果字典
"""
# 筛选日期范围
if start_date:
returns = self.returns.loc[start_date:]
if end_date:
returns = self.returns.loc[:end_date]
# 初始化
dates = returns.index
n_periods = len(dates)
# 存储结果
portfolio_values = [1.0]
weights_history = []
leverage_history = []
current_weights = np.array([1/self.n_assets] * self.n_assets)
# 每日回测
for i in range(1, n_periods):
current_date = dates[i]
prev_date = dates[i-1]
# 计算当前持仓价值
daily_return = returns.loc[current_date].values
portfolio_return = np.dot(current_weights, daily_return)
new_value = portfolio_values[-1] * (1 + portfolio_return)
# 检查是否需要再平衡
if self._need_rebalance(prev_date, current_date):
# 计算新的协方差矩阵(使用过去252个交易日)
cov_window = returns.loc[:prev_date].iloc[-252:]
if len(cov_window) < 60: # 需要至少60个数据点
cov_matrix = calculate_ewma_covariance(returns.loc[:prev_date].iloc[-60:])
else:
cov_matrix = calculate_ewma_covariance(cov_window)
# 计算新的风险平价权重
new_weights = calculate_risk_parity_weights(cov_matrix, max_leverage)
# 计算交易成本
turnover = np.sum(np.abs(new_weights - current_weights))
cost = turnover * self.transaction_cost
# 应用交易成本
new_value *= (1 - cost)
# 更新权重
current_weights = new_weights
leverage = np.sum(np.abs(current_weights))
weights_history.append({
'date': current_date,
'weights': current_weights.copy(),
'leverage': leverage
})
leverage_history.append(leverage)
else:
# 权重随价格变化自然漂移
# 重新归一化权重(保持杠杆不变)
current_weights = (current_weights * (1 + daily_return))
current_weights = current_weights / np.sum(current_weights) * np.sum(np.abs(current_weights))
portfolio_values.append(new_value)
# 转换为DataFrame
portfolio_curve = pd.Series(portfolio_values, index=dates)
# 计算绩效指标
metrics = self._calculate_metrics(portfolio_curve)
return {
'portfolio_curve': portfolio_curve,
'weights_history': pd.DataFrame(weights_history),
'leverage_history': leverage_history,
'metrics': metrics
}
def _need_rebalance(self, prev_date, current_date):
"""判断是否需要再平衡"""
if self.rebalance_freq == 'M':
return prev_date.month != current_date.month
elif self.rebalance_freq == 'Q':
return prev_date.quarter != current_date.quarter
elif self.rebalance_freq == 'W':
return prev_date.isocalendar()[1] != current_date.isocalendar()[1]
else:
return False
def _calculate_metrics(self, portfolio_curve):
"""计算绩效指标"""
returns = portfolio_curve.pct_change().dropna()
# 基础指标
total_return = portfolio_curve.iloc[-1] - 1
annual_return = (1 + total_return) ** (252 / len(returns)) - 1
annual_vol = returns.std() * np.sqrt(252)
sharpe_ratio = (annual_return - 0.02) / annual_vol if annual_vol > 0 else 0
# 最大回撤
rolling_max = portfolio_curve.expanding().max()
drawdown = (portfolio_curve - rolling_max) / rolling_max
max_drawdown = drawdown.min()
# 胜率
win_rate = (returns > 0).mean()
# 偏度和峰度
skewness = returns.skew()
kurtosis = returns.kurtosis()
# Calmar比率
calmar_ratio = annual_return / abs(max_drawdown) if max_drawdown < 0 else 0
return {
'总收益率': f"{total_return:.2%}",
'年化收益率': f"{annual_return:.2%}",
'年化波动率': f"{annual_vol:.2%}",
'夏普比率': f"{sharpe_ratio:.2f}",
'最大回撤': f"{max_drawdown:.2%}",
'Calmar比率': f"{calmar_ratio:.2f}",
'月胜率': f"{win_rate:.2%}",
'偏度': f"{skewness:.2f}",
'峰度': f"{kurtosis:.2f}"
}
# 运行回测
backtester = RiskParityBacktester(returns, rebalance_freq='M', transaction_cost=0.001)
result = backtester.run_backtest(max_leverage=2.0)
print("\n=== 风险平价策略回测结果 ===")
for key, value in result['metrics'].items():
print(f"{key}: {value}")
可视化分析
def plot_backtest_results(result, assets):
"""绘制回测结果图表"""
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
fig.suptitle('风险平价策略回测分析', fontsize=16)
# 1. 累计收益曲线
ax1 = axes[0, 0]
portfolio_curve = result['portfolio_curve']
portfolio_curve.plot(ax=ax1, linewidth=2, label='风险平价组合')
# 添加基准(等权组合)
equally_weighted = (returns.mean(axis=1) + 1).cumprod()
equally_weighted.plot(ax=ax1, alpha=0.7, label='等权组合')
ax1.set_title('累计收益曲线')
ax1.set_ylabel('累计净值')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 2. 月度收益热力图
ax2 = axes[0, 1]
monthly_returns = portfolio_curve.resample('M').last().pct_change().dropna()
monthly_returns.index = monthly_returns.index.to_period('M')
# 创建月度收益矩阵
monthly_df = monthly_returns.to_frame('returns')
monthly_df['year'] = monthly_df.index.year
monthly_df['month'] = monthly_df.index.month
pivot_table = monthly_df.pivot(index='year', columns='month', values='returns')
pivot_table = pivot_table.fillna(0)
sns.heatmap(pivot_table, annot=True, fmt='.1%', cmap='RdYlGn', center=0,
ax=ax2, cbar_kws={'label': '月度收益率'})
ax2.set_title('月度收益热力图')
ax2.set_xlabel('月份')
ax2.set_ylabel('年份')
# 3. 权重演变
ax3 = axes[1, 0]
if len(result['weights_history']) > 0:
weights_df = result['weights_history'].set_index('date')
weights_df.plot(kind='area', stacked=True, ax=ax3, alpha=0.8)
ax3.set_title('资产权重演变')
ax3.set_ylabel('权重')
ax3.legend(assets.values(), bbox_to_anchor=(1.05, 1), loc='upper left')
ax3.grid(True, alpha=0.3)
# 4. 回撤分析
ax4 = axes[1, 1]
rolling_max = portfolio_curve.expanding().max()
drawdown = (portfolio_curve - rolling_max) / rolling_max
drawdown.plot(ax=ax4, color='red', linewidth=2)
ax4.fill_between(drawdown.index, drawdown, 0, color='red', alpha=0.3)
ax4.set_title('回撤曲线')
ax4.set_ylabel('回撤幅度')
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 执行可视化
plot_backtest_results(result, assets)
高级优化:加入更多资产与约束
扩展资产类别
为了进一步分散风险,我们可以加入更多资产类别:
# 扩展的资产配置
extended_assets = {
'SPY': '美国大盘股',
'TLT': '长期国债',
'GLD': '黄金',
'QQQ': '科技股',
'VNQ': '房地产信托',
'DBC': '大宗商品',
'EEM': '新兴市场'
}
# 获取扩展数据
extended_data = yf.download(list(extended_assets.keys()), start=start_date, end=end_date)['Adj Close']
extended_returns = extended_data.pct_change().dropna()
# 运行扩展回测
extended_backtester = RiskParityBacktester(extended_returns, rebalance_freq='M')
extended_result = extended_backtester.run_backtest(max_leverage=2.5)
print("\n=== 扩展资产风险平价结果 ===")
for key, value in extended_result['metrics'].items():
print(f"{key}: {value}")
加入风险预算约束
在实际管理中,我们可能需要对单一资产的风险贡献设置上限:
def calculate_risk_parity_with_constraints(cov_matrix, max_leverage=2.0, max_risk_contrib=0.4):
"""
带约束的风险平价优化
参数:
max_risk_contrib: 单一资产最大风险贡献比例
"""
n = len(cov_matrix)
def objective(weights):
weights = np.array(weights)
portfolio_volatility = np.sqrt(weights @ cov_matrix @ weights.T)
marginal_risk_contrib = (cov_matrix @ weights) / portfolio_volatility
risk_contrib = weights * marginal_risk_contrib
# 基础风险平价目标
target_risk = portfolio_volatility / n
base_penalty = np.sum((risk_contrib - target_risk) ** 2)
# 约束惩罚
constraint_penalty = 0
if np.max(risk_contrib) > max_risk_contrib * portfolio_volatility:
constraint_penalty = 1000 * (np.max(risk_contrib) - max_risk_contrib * portfolio_volatility) ** 2
return base_penalty + constraint_penalty
# 约束条件
constraints = (
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1.0},
{'type': 'ineq', 'fun': lambda w: max_leverage - np.sum(np.abs(w))},
{'type': 'ineq', 'fun': lambda w: w}
)
initial_weights = np.array([1/n] * n)
result = minimize(objective, initial_weights, method='SLSQP', constraints=constraints)
return result.x
# 使用带约束的优化器
weights_constrained = calculate_risk_parity_with_constraints(latest_cov, max_risk_contrib=0.35)
print("\n带约束的风险平价权重:")
for i, asset in enumerate(assets.keys()):
print(f"{asset}: {weights_constrained[i]:.2%}")
实际应用中的关键考虑因素
1. 再平衡频率选择
再平衡频率对策略表现有重要影响:
- 月度再平衡:平衡了交易成本和风险控制
- 季度再平衡:降低交易成本,但可能增加风险暴露
- 波动率触发:当权重偏离超过阈值时再平衡
def volatility_triggered_rebalance(current_weights, target_weights, threshold=0.05):
"""
波动率触发的再平衡
参数:
threshold: 偏离阈值,例如0.05表示5%
"""
deviation = np.abs(current_weights - target_weights)
if np.any(deviation > threshold):
return True
return False
2. 杠杆与去杠杆化
风险平价策略通常需要使用杠杆来提升低风险资产的贡献。但杠杆是把双刃剑:
优点:
- 提升组合整体风险调整后收益
- 实现真正的风险均衡
风险:
- 在极端市场条件下可能需要去杠杆
- 融资成本影响最终收益
解决方案:
def dynamic_leverage_control(volatility_regime, base_leverage=2.0):
"""
动态杠杆控制
参数:
volatility_regime: 波动率状态(低/中/高)
"""
leverage_map = {
'low': base_leverage,
'medium': base_leverage * 0.8,
'high': base_leverage * 0.5
}
return leverage_map.get(volatility_regime, base_leverage)
3. 协方差矩阵估计的稳健性
协方差矩阵估计不准会导致权重计算错误。改进方法:
def robust_covariance_estimation(returns, method='shrinkage'):
"""
稳健协方差矩阵估计
方法:
- shrinkage: 收缩估计
- factor_model: 因子模型
- bayesian: 贝叶斯估计
"""
if method == 'shrinkage':
# Ledoit-Wolf收缩估计
from sklearn.covariance import LedoitWolf
cov = LedoitWolf().fit(returns).covariance_
return pd.DataFrame(cov, index=returns.columns, columns=returns.columns)
elif method == 'factor_model':
# 使用PCA因子模型
from sklearn.decomposition import PCA
pca = PCA(n_components=3)
factors = pca.fit_transform(returns)
# 这里简化处理,实际应使用因子模型
return returns.cov()
else:
return returns.cov()
风险平价策略的局限性与改进方向
局限性
- 对协方差矩阵敏感:估计误差会导致权重偏差
- 杠杆依赖:需要使用杠杆,增加复杂性和风险
- 极端市场失效:在危机时期资产相关性趋近1,风险平价可能失效
- 交易成本:频繁再平衡产生较高成本
改进方向
- 加入宏观因子:结合经济周期调整配置
- 动态风险预算:根据市场状态调整风险目标
- 机器学习优化:使用ML改进协方差预测
- 尾部风险对冲:加入期权等衍生品对冲极端风险
# 动态风险预算示例
def dynamic_risk_budget(current_vol, historical_vol):
"""
根据当前波动率与历史波动率的比较调整风险预算
"""
vol_ratio = current_vol / historical_vol
if vol_ratio > 1.5: # 高波动期
# 降低整体风险目标
return 0.7
elif vol_ratio < 0.7: # 低波动期
# 可以适度增加风险
return 1.2
else: # 正常波动期
return 1.0
结论
风险平价策略通过科学的风险分配机制,在波动市场中实现了稳健的风险调整后收益。通过本文提供的完整Python实现框架,投资者可以:
- 理解核心原理:掌握风险贡献度的计算方法
- 构建回测系统:使用真实历史数据验证策略
- 优化参数:根据实际需求调整杠杆和约束
- 风险管理:识别并控制策略的潜在风险
在实际应用中,建议从简单的资产组合开始,逐步加入更多资产和约束条件。同时,要持续监控协方差矩阵的估计质量,并根据市场变化动态调整策略参数。风险平价不是万能的,但它提供了一个科学的框架来应对不确定的市场环境,帮助投资者在追求收益的同时保持风险的平衡。
记住,没有完美的策略,只有适合的策略。风险平价的价值在于其系统性和纪律性,这正是长期投资成功的关键。# 风险平价策略资产配置模型回测:如何在波动市场中实现稳健收益与风险平衡
引言:理解风险平价策略的核心价值
在当今充满不确定性的金融市场中,传统的资产配置方法往往难以应对极端波动。风险平价(Risk Parity)策略应运而生,它通过重新分配风险贡献度,让每种资产在投资组合中承担相等的风险,从而实现真正的风险平衡。这种策略的核心理念是:不是分配资金,而是分配风险。
与传统的60/40股债配置不同,风险平价策略认为股票的风险远高于债券。通过杠杆或调整权重,可以使债券和商品等低风险资产承担与股票相同的风险贡献,从而在市场波动中实现更稳健的收益。本文将详细探讨如何通过回测验证风险平价策略的有效性,并提供完整的Python实现代码。
风险平价策略的理论基础
风险贡献度的计算原理
风险平价策略的核心在于计算每种资产对整体组合的风险贡献。这需要使用边际风险贡献(Marginal Risk Contribution)和成分风险贡献(Component Risk Contribution)的概念。
对于一个包含N种资产的投资组合,其总风险(波动率)可以表示为:
σ_p = √(wᵀ * Σ * w)
其中w是权重向量,Σ是协方差矩阵。
每种资产的边际风险贡献为:
MRC_i = (Σ * w)_i / σ_p
成分风险贡献为:
RC_i = w_i * MRC_i
风险平价的目标是让所有资产的RC_i相等,即:
RC_1 = RC_2 = ... = RC_N
与传统配置策略的对比
传统60/40配置中,股票通常贡献超过90%的风险,而债券贡献不足10%。这种不平衡导致组合在股市崩盘时遭受重创。风险平价策略通过以下方式解决这一问题:
- 风险均衡:让每种资产承担相同风险
- 杠杆使用:对低风险资产(如债券)使用杠杆提升其风险贡献
- 多元化:纳入更多资产类别(如商品、REITs等)
Python实现:风险平价策略回测框架
环境准备与数据获取
首先,我们需要安装必要的Python库:
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
from scipy.optimize import minimize
import seaborn as sns
# 设置中文字体(如果需要显示中文)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
接下来,我们从Yahoo Finance获取历史数据:
# 定义资产代码和名称
assets = {
'SPY': '标普500ETF',
'TLT': '20年期国债ETF',
'GLD': '黄金ETF',
'QQQ': '纳斯达克100ETF'
}
# 获取2010年至今的历史数据
start_date = '2010-01-01'
end_date = '2023-12-31'
# 下载数据
data = yf.download(list(assets.keys()), start=start_date, end=end_date)['Adj Close']
returns = data.pct_change().dropna()
print(f"数据时间范围: {returns.index[0]} 至 {returns.index[-1]}")
print(f"数据形状: {returns.shape}")
协方差矩阵估计
准确估计协方差矩阵对风险平价至关重要。我们使用指数加权移动平均(EWMA)来捕捉时变波动率:
def calculate_ewma_covariance(returns, lambda_factor=0.94):
"""
计算指数加权移动平均协方差矩阵
参数:
returns: 收益率数据
lambda_factor: 衰减因子,通常取0.94
返回:
cov_matrix: 协方差矩阵
"""
# 计算指数加权协方差
cov_matrix = returns.ewm(alpha=1-lambda_factor).cov()
# 返回最新的协方差矩阵
return cov_matrix.iloc[-len(assets):, -len(assets):]
# 示例:计算最近一期的协方差矩阵
latest_cov = calculate_ewma_covariance(returns)
print("最新协方差矩阵:")
print(latest_cov)
风险平价权重优化
使用优化算法求解风险平价权重:
def calculate_risk_parity_weights(cov_matrix, max_leverage=2.0):
"""
计算风险平价权重
参数:
cov_matrix: 协方差矩阵
max_leverage: 最大杠杆倍数
返回:
weights: 优化后的权重
"""
n = len(cov_matrix)
# 目标函数:风险贡献度差异最小化
def risk_parity_objective(weights):
# 确保权重为正且总和不超过杠杆限制
weights = np.array(weights)
portfolio_variance = weights @ cov_matrix @ weights.T
portfolio_volatility = np.sqrt(portfolio_variance)
# 计算边际风险贡献
marginal_risk_contrib = (cov_matrix @ weights) / portfolio_volatility
# 计算成分风险贡献
risk_contrib = weights * marginal_risk_contrib
# 目标:最小化风险贡献的差异
target_risk_contrib = portfolio_volatility / n
return np.sum((risk_contrib - target_risk_contrib) ** 2)
# 约束条件
constraints = (
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1.0}, # 权重和为1
{'type': 'ineq', 'fun': lambda w: max_leverage - np.sum(np.abs(w))}, # 杠杆限制
{'type': 'ineq', 'fun': lambda w: w} # 权重非负
)
# 初始猜测
initial_weights = np.array([1/n] * n)
# 优化
result = minimize(
risk_parity_objective,
initial_weights,
method='SLSQP',
constraints=constraints,
options={'ftol': 1e-9, 'maxiter': 1000}
)
return result.x
# 示例:计算风险平价权重
weights = calculate_risk_parity_weights(latest_cov)
print("\n风险平价权重:")
for i, asset in enumerate(assets.keys()):
print(f"{asset}: {weights[i]:.2%}")
完整回测系统
现在我们构建一个完整的回测系统,包括交易成本和再平衡:
class RiskParityBacktester:
def __init__(self, returns, rebalance_freq='M', transaction_cost=0.001):
self.returns = returns
self.rebalance_freq = rebalance_freq
self.transaction_cost = transaction_cost
self.n_assets = len(returns.columns)
def run_backtest(self, max_leverage=2.0, start_date=None, end_date=None):
"""
运行风险平价回测
参数:
max_leverage: 最大杠杆倍数
start_date: 回测开始日期
end_date: 回测结束日期
返回:
result: 回测结果字典
"""
# 筛选日期范围
if start_date:
returns = self.returns.loc[start_date:]
if end_date:
returns = self.returns.loc[:end_date]
# 初始化
dates = returns.index
n_periods = len(dates)
# 存储结果
portfolio_values = [1.0]
weights_history = []
leverage_history = []
current_weights = np.array([1/self.n_assets] * self.n_assets)
# 每日回测
for i in range(1, n_periods):
current_date = dates[i]
prev_date = dates[i-1]
# 计算当前持仓价值
daily_return = returns.loc[current_date].values
portfolio_return = np.dot(current_weights, daily_return)
new_value = portfolio_values[-1] * (1 + portfolio_return)
# 检查是否需要再平衡
if self._need_rebalance(prev_date, current_date):
# 计算新的协方差矩阵(使用过去252个交易日)
cov_window = returns.loc[:prev_date].iloc[-252:]
if len(cov_window) < 60: # 需要至少60个数据点
cov_matrix = calculate_ewma_covariance(returns.loc[:prev_date].iloc[-60:])
else:
cov_matrix = calculate_ewma_covariance(cov_window)
# 计算新的风险平价权重
new_weights = calculate_risk_parity_weights(cov_matrix, max_leverage)
# 计算交易成本
turnover = np.sum(np.abs(new_weights - current_weights))
cost = turnover * self.transaction_cost
# 应用交易成本
new_value *= (1 - cost)
# 更新权重
current_weights = new_weights
leverage = np.sum(np.abs(current_weights))
weights_history.append({
'date': current_date,
'weights': current_weights.copy(),
'leverage': leverage
})
leverage_history.append(leverage)
else:
# 权重随价格变化自然漂移
# 重新归一化权重(保持杠杆不变)
current_weights = (current_weights * (1 + daily_return))
current_weights = current_weights / np.sum(current_weights) * np.sum(np.abs(current_weights))
portfolio_values.append(new_value)
# 转换为DataFrame
portfolio_curve = pd.Series(portfolio_values, index=dates)
# 计算绩效指标
metrics = self._calculate_metrics(portfolio_curve)
return {
'portfolio_curve': portfolio_curve,
'weights_history': pd.DataFrame(weights_history),
'leverage_history': leverage_history,
'metrics': metrics
}
def _need_rebalance(self, prev_date, current_date):
"""判断是否需要再平衡"""
if self.rebalance_freq == 'M':
return prev_date.month != current_date.month
elif self.rebalance_freq == 'Q':
return prev_date.quarter != current_date.quarter
elif self.rebalance_freq == 'W':
return prev_date.isocalendar()[1] != current_date.isocalendar()[1]
else:
return False
def _calculate_metrics(self, portfolio_curve):
"""计算绩效指标"""
returns = portfolio_curve.pct_change().dropna()
# 基础指标
total_return = portfolio_curve.iloc[-1] - 1
annual_return = (1 + total_return) ** (252 / len(returns)) - 1
annual_vol = returns.std() * np.sqrt(252)
sharpe_ratio = (annual_return - 0.02) / annual_vol if annual_vol > 0 else 0
# 最大回撤
rolling_max = portfolio_curve.expanding().max()
drawdown = (portfolio_curve - rolling_max) / rolling_max
max_drawdown = drawdown.min()
# 胜率
win_rate = (returns > 0).mean()
# 偏度和峰度
skewness = returns.skew()
kurtosis = returns.kurtosis()
# Calmar比率
calmar_ratio = annual_return / abs(max_drawdown) if max_drawdown < 0 else 0
return {
'总收益率': f"{total_return:.2%}",
'年化收益率': f"{annual_return:.2%}",
'年化波动率': f"{annual_vol:.2%}",
'夏普比率': f"{sharpe_ratio:.2f}",
'最大回撤': f"{max_drawdown:.2%}",
'Calmar比率': f"{calmar_ratio:.2f}",
'月胜率': f"{win_rate:.2%}",
'偏度': f"{skewness:.2f}",
'峰度': f"{kurtosis:.2f}"
}
# 运行回测
backtester = RiskParityBacktester(returns, rebalance_freq='M', transaction_cost=0.001)
result = backtester.run_backtest(max_leverage=2.0)
print("\n=== 风险平价策略回测结果 ===")
for key, value in result['metrics'].items():
print(f"{key}: {value}")
可视化分析
def plot_backtest_results(result, assets):
"""绘制回测结果图表"""
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
fig.suptitle('风险平价策略回测分析', fontsize=16)
# 1. 累计收益曲线
ax1 = axes[0, 0]
portfolio_curve = result['portfolio_curve']
portfolio_curve.plot(ax=ax1, linewidth=2, label='风险平价组合')
# 添加基准(等权组合)
equally_weighted = (returns.mean(axis=1) + 1).cumprod()
equally_weighted.plot(ax=ax1, alpha=0.7, label='等权组合')
ax1.set_title('累计收益曲线')
ax1.set_ylabel('累计净值')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 2. 月度收益热力图
ax2 = axes[0, 1]
monthly_returns = portfolio_curve.resample('M').last().pct_change().dropna()
monthly_returns.index = monthly_returns.index.to_period('M')
# 创建月度收益矩阵
monthly_df = monthly_returns.to_frame('returns')
monthly_df['year'] = monthly_df.index.year
monthly_df['month'] = monthly_df.index.month
pivot_table = monthly_df.pivot(index='year', columns='month', values='returns')
pivot_table = pivot_table.fillna(0)
sns.heatmap(pivot_table, annot=True, fmt='.1%', cmap='RdYlGn', center=0,
ax=ax2, cbar_kws={'label': '月度收益率'})
ax2.set_title('月度收益热力图')
ax2.set_xlabel('月份')
ax2.set_ylabel('年份')
# 3. 权重演变
ax3 = axes[1, 0]
if len(result['weights_history']) > 0:
weights_df = result['weights_history'].set_index('date')
weights_df.plot(kind='area', stacked=True, ax=ax3, alpha=0.8)
ax3.set_title('资产权重演变')
ax3.set_ylabel('权重')
ax3.legend(assets.values(), bbox_to_anchor=(1.05, 1), loc='upper left')
ax3.grid(True, alpha=0.3)
# 4. 回撤分析
ax4 = axes[1, 1]
rolling_max = portfolio_curve.expanding().max()
drawdown = (portfolio_curve - rolling_max) / rolling_max
drawdown.plot(ax=ax4, color='red', linewidth=2)
ax4.fill_between(drawdown.index, drawdown, 0, color='red', alpha=0.3)
ax4.set_title('回撤曲线')
ax4.set_ylabel('回撤幅度')
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 执行可视化
plot_backtest_results(result, assets)
高级优化:加入更多资产与约束
扩展资产类别
为了进一步分散风险,我们可以加入更多资产类别:
# 扩展的资产配置
extended_assets = {
'SPY': '美国大盘股',
'TLT': '长期国债',
'GLD': '黄金',
'QQQ': '科技股',
'VNQ': '房地产信托',
'DBC': '大宗商品',
'EEM': '新兴市场'
}
# 获取扩展数据
extended_data = yf.download(list(extended_assets.keys()), start=start_date, end=end_date)['Adj Close']
extended_returns = extended_data.pct_change().dropna()
# 运行扩展回测
extended_backtester = RiskParityBacktester(extended_returns, rebalance_freq='M')
extended_result = extended_backtester.run_backtest(max_leverage=2.5)
print("\n=== 扩展资产风险平价结果 ===")
for key, value in extended_result['metrics'].items():
print(f"{key}: {value}")
加入风险预算约束
在实际管理中,我们可能需要对单一资产的风险贡献设置上限:
def calculate_risk_parity_with_constraints(cov_matrix, max_leverage=2.0, max_risk_contrib=0.4):
"""
带约束的风险平价优化
参数:
max_risk_contrib: 单一资产最大风险贡献比例
"""
n = len(cov_matrix)
def objective(weights):
weights = np.array(weights)
portfolio_volatility = np.sqrt(weights @ cov_matrix @ weights.T)
marginal_risk_contrib = (cov_matrix @ weights) / portfolio_volatility
risk_contrib = weights * marginal_risk_contrib
# 基础风险平价目标
target_risk = portfolio_volatility / n
base_penalty = np.sum((risk_contrib - target_risk) ** 2)
# 约束惩罚
constraint_penalty = 0
if np.max(risk_contrib) > max_risk_contrib * portfolio_volatility:
constraint_penalty = 1000 * (np.max(risk_contrib) - max_risk_contrib * portfolio_volatility) ** 2
return base_penalty + constraint_penalty
# 约束条件
constraints = (
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1.0},
{'type': 'ineq', 'fun': lambda w: max_leverage - np.sum(np.abs(w))},
{'type': 'ineq', 'fun': lambda w: w}
)
initial_weights = np.array([1/n] * n)
result = minimize(objective, initial_weights, method='SLSQP', constraints=constraints)
return result.x
# 使用带约束的优化器
weights_constrained = calculate_risk_parity_with_constraints(latest_cov, max_risk_contrib=0.35)
print("\n带约束的风险平价权重:")
for i, asset in enumerate(assets.keys()):
print(f"{asset}: {weights_constrained[i]:.2%}")
实际应用中的关键考虑因素
1. 再平衡频率选择
再平衡频率对策略表现有重要影响:
- 月度再平衡:平衡了交易成本和风险控制
- 季度再平衡:降低交易成本,但可能增加风险暴露
- 波动率触发:当权重偏离超过阈值时再平衡
def volatility_triggered_rebalance(current_weights, target_weights, threshold=0.05):
"""
波动率触发的再平衡
参数:
threshold: 偏离阈值,例如0.05表示5%
"""
deviation = np.abs(current_weights - target_weights)
if np.any(deviation > threshold):
return True
return False
2. 杠杆与去杠杆化
风险平价策略通常需要使用杠杆来提升低风险资产的贡献。但杠杆是把双刃剑:
优点:
- 提升组合整体风险调整后收益
- 实现真正的风险均衡
风险:
- 在极端市场条件下可能需要去杠杆
- 融资成本影响最终收益
解决方案:
def dynamic_leverage_control(volatility_regime, base_leverage=2.0):
"""
动态杠杆控制
参数:
volatility_regime: 波动率状态(低/中/高)
"""
leverage_map = {
'low': base_leverage,
'medium': base_leverage * 0.8,
'high': base_leverage * 0.5
}
return leverage_map.get(volatility_regime, base_leverage)
3. 协方差矩阵估计的稳健性
协方差矩阵估计不准会导致权重计算错误。改进方法:
def robust_covariance_estimation(returns, method='shrinkage'):
"""
稳健协方差矩阵估计
方法:
- shrinkage: 收缩估计
- factor_model: 因子模型
- bayesian: 贝叶斯估计
"""
if method == 'shrinkage':
# Ledoit-Wolf收缩估计
from sklearn.covariance import LedoitWolf
cov = LedoitWolf().fit(returns).covariance_
return pd.DataFrame(cov, index=returns.columns, columns=returns.columns)
elif method == 'factor_model':
# 使用PCA因子模型
from sklearn.decomposition import PCA
pca = PCA(n_components=3)
factors = pca.fit_transform(returns)
# 这里简化处理,实际应使用因子模型
return returns.cov()
else:
return returns.cov()
风险平价策略的局限性与改进方向
局限性
- 对协方差矩阵敏感:估计误差会导致权重偏差
- 杠杆依赖:需要使用杠杆,增加复杂性和风险
- 极端市场失效:在危机时期资产相关性趋近1,风险平价可能失效
- 交易成本:频繁再平衡产生较高成本
改进方向
- 加入宏观因子:结合经济周期调整配置
- 动态风险预算:根据市场状态调整风险目标
- 机器学习优化:使用ML改进协方差预测
- 尾部风险对冲:加入期权等衍生品对冲极端风险
# 动态风险预算示例
def dynamic_risk_budget(current_vol, historical_vol):
"""
根据当前波动率与历史波动率的比较调整风险预算
"""
vol_ratio = current_vol / historical_vol
if vol_ratio > 1.5: # 高波动期
# 降低整体风险目标
return 0.7
elif vol_ratio < 0.7: # 低波动期
# 可以适度增加风险
return 1.2
else: # 正常波动期
return 1.0
结论
风险平价策略通过科学的风险分配机制,在波动市场中实现了稳健的风险调整后收益。通过本文提供的完整Python实现框架,投资者可以:
- 理解核心原理:掌握风险贡献度的计算方法
- 构建回测系统:使用真实历史数据验证策略
- 优化参数:根据实际需求调整杠杆和约束
- 风险管理:识别并控制策略的潜在风险
在实际应用中,建议从简单的资产组合开始,逐步加入更多资产和约束条件。同时,要持续监控协方差矩阵的估计质量,并根据市场变化动态调整策略参数。风险平价不是万能的,但它提供了一个科学的框架来应对不确定的市场环境,帮助投资者在追求收益的同时保持风险的平衡。
记住,没有完美的策略,只有适合的策略。风险平价的价值在于其系统性和纪律性,这正是长期投资成功的关键。
