3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
风险平价(Risk Parity)策略的核心思想是让组合中各个资产(或资产类别)对整体组合的风险贡献保持相等,而非简单地平均分配资金(如 1/N 策略)。这种方法能有效避免传统组合过度集中于高波动资产(如股票)风险的问题。
本文将介绍如何在 PTrade 平台上,利用 Python 及第三方科学计算库(如 scipy.optimize)求解资产权重,并结合 PTrade 的定时任务接口实现动态调仓。
假设资产组合包含 $N$ 个资产,权重向量为 $w = [w_1, w_2, ..., w_N]^T$,资产收益率的协方差矩阵为 $\Sigma$。
组合总体波动率:
$$\sigma_p = \sqrt{w^T \Sigma w}$$
资产 $i$ 的边际风险贡献(MRC):
$$MRC_i = \frac{\partial \sigma_p}{\partial w_i} = \frac{(\Sigma w)_i}{\sigma_p}$$
资产 $i$ 的总风险贡献(TRC):
$$TRC_i = w_i \times MRC_i = \frac{w_i (\Sigma w)_i}{\sigma_p}$$
风险平价的目标是寻找一组权重 $w$,使得所有资产的 $TRC_i$ 相等,即对于任意 $i, j$ 满足 $TRC_i = TRC_j$。
initialize):定义多资产股票/ETF池(例如覆盖权益、债券、黄金等不同风险特征的标的),设置定时调仓任务(如每月第一个交易日执行)。before_trading_start):过滤停牌及状态异常标的。get_history 获取历史日线收盘价,计算各资产的日收益率及协方差矩阵 $\Sigma$。scipy.optimize.minimize 求解目标函数,得到使各资产风险贡献相等的最终权重 $w$。order_target_percent / order_target_value):结合 PTrade 的账户持仓与资产总值,将持仓调整至目标权重。import numpy as np
import pandas as pd
from scipy.optimize import minimize
def initialize(context):
# 设置多资产ETF标的池(涵盖股票、债券、黄金等)
# 510300.SS: 沪深300ETF, 511010.SS: 国债ETF, 518880.SS: 黄金ETF, 513100.SS: 纳指ETF
g.security_list = ['510300.SS', '511010.SS', '518880.SS', '513100.SS']
set_universe(g.security_list)
# 历史数据窗口大小(交易日)
g.lookback_days = 120
# 设置每月初第一个交易日执行调仓
run_daily(context, rebalance_portfolio, time='09:35')
# 风险平价目标函数
def risk_parity_objective(weights, cov_matrix):
# 计算组合波动率
portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
# 计算各资产边际风险贡献
mrc = np.dot(cov_matrix, weights) / portfolio_vol
# 计算各资产总风险贡献
trc = weights * mrc
# 目标:最小化各资产TRC两两之间的方差/差距
target_trc = portfolio_vol / len(weights)
diff = trc - target_trc
return np.sum(np.square(diff))
# 求解风险平价权重
def get_risk_parity_weights(cov_matrix):
n = len(cov_matrix)
init_weights = np.array([1.0 / n] * n)
# 约束条件:权重之和为1
constraints = ({'type': 'eq', 'fun': lambda w: np.sum(w) - 1.0})
# 边界条件:做多限制,各资产权重在 0 到 1 之间
bounds = tuple((0.0, 1.0) for _ in range(n))
res = minimize(risk_parity_objective, init_weights, args=(cov_matrix,),
method='SLSQP', bounds=bounds, constraints=constraints)
return res.x if res.success else init_weights
def rebalance_portfolio(context):
# 仅在每月第一个交易日调仓
current_dt = context.blotter.current_dt
if g.last_month == current_dt.month:
return
log.info("开始执行月度风险平价调仓分析...")
# 获取历史日收盘价
price_df = get_history(g.lookback_days, '1d', 'close', security_list=g.security_list, fq='pre')
if price_df is None or price_df.empty:
log.error("历史行情数据获取失败")
return
# 处理数据格式,计算收益率与协方差矩阵
returns = price_df.pct_change().dropna()
cov_matrix = returns.cov().values * 252 # 年化协方差矩阵
# 计算风险平价目标权重
target_weights = get_risk_parity_weights(cov_matrix)
# 获取组合总资产
total_value = context.portfolio.portfolio_value
# 执行调仓下单
for idx, security in enumerate(g.security_list):
weight = target_weights[idx]
target_value = total_value * weight
log.info("标的: %s, 目标权重: %.2f%%, 目标金额: %.2f" % (security, weight * 100, target_value))
order_target_value(security, target_value)
g.last_month = current_dt.month
def before_trading_start(context, data):
if not hasattr(g, 'last_month'):
g.last_month = -1
def handle_data(context, data):
pass
numpy、pandas 和 scipy,可以直接调用相关数学求解器。