问题描述
解决方案
这是一个使用聚宽(JoinQuant)内置优化器 jqlib.optimizer 实现的**风险平价(Risk Parity)**资产配置策略示例。
策略逻辑说明
- 核心思想:风险平价模型旨在分配资金,使得投资组合中每个资产对总风险的贡献相等。这通常意味着波动率低的资产(如债券)会获得更高的权重,而波动率高的资产(如股票)权重较低。
- 标的选择:为了体现风险平价在大类资产配置中的优势,本示例选取了三个代表性的ETF作为标的:
- 权益类:华泰柏瑞沪深300ETF (510300.XSHG)
- 债券类:国泰上证5年期国债ETF (511010.XSHG)
- 商品类:华安黄金ETF (518880.XSHG)
- 调仓频率:按月调仓。
- 优化器:使用聚宽内置的
portfolio_optimizer函数配合RiskParity目标函数进行计算。
策略代码
# -*- coding: utf-8 -*-
from jqdata import *
from jqlib.optimizer import *
import pandas as pd
def initialize(context):
"""
初始化函数
"""
# 设定基准为沪深300
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定手续费:股票类ETF买卖佣金万分之三,无印花税
set_order_cost(OrderCost(open_tax=0, close_tax=0, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 定义资产池:股票、债券、黄金
g.etf_pool = [
'510300.XSHG', # 沪深300ETF (权益)
'511010.XSHG', # 5年期国债ETF (债券)
'518880.XSHG' # 黄金ETF (商品)
]
# 设定调仓周期:每月第一个交易日09:30运行
run_monthly(rebalance, monthday=1, time='09:30')
def rebalance(context):
"""
调仓函数
"""
log.info("开始进行风险平价调仓...")
# 获取当前可交易的标的(过滤停牌或未上市)
current_date = context.previous_date
valid_securities = []
for sec in g.etf_pool:
# 获取标的信息,检查上市时间
sec_info = get_security_info(sec)
if sec_info.start_date < current_date:
valid_securities.append(sec)
if not valid_securities:
log.warn("没有可交易的标的")
return
# 使用聚宽内置优化器计算风险平价权重
# target=RiskParity: 风险平价模型
# count=250: 使用过去250个交易日的数据计算协方差矩阵
try:
optimized_weight = portfolio_optimizer(
date=current_date,
securities=valid_securities,
target=RiskParity(count=250, risk_budget=None), # None表示各资产风险贡献相等
constraints=[
WeightConstraint(low=0.0, high=1.0), # 权重在0到1之间(不做空)
WeightEqualConstraint(limit=1.0) # 总权重等于1(满仓)
],
bounds=[Bound(0.0, 1.0)],
default_port_weight_range=[0.0, 1.0],
ftol=1e-09,
return_none_if_fail=True
)
except Exception as e:
log.error("优化器计算出错: %s" % e)
return
# 检查优化结果
if optimized_weight is None:
log.warn("组合优化失败,本次不调仓")
return
log.info("目标权重: \n%s" % optimized_weight)
# 执行调仓
# 1. 先卖出不在优化列表中的持仓(如果有)
for stock in context.portfolio.positions:
if stock not in optimized_weight.index:
order_target_value(stock, 0)
# 2. 根据优化权重调整持仓
total_value = context.portfolio.total_value
for stock in optimized_weight.index:
target_value = total_value * optimized_weight[stock]
order_target_value(stock, target_value)
def before_trading_start(context):
"""
盘前处理
"""
pass
def after_trading_end(context):
"""
盘后处理
"""
pass
代码关键点解析
jqlib.optimizer: 这是聚宽提供的投资组合优化库。相比于自己手写scipy.optimize,直接调用这个库更加稳定且方便。RiskParity(count=250):- 这是优化的目标函数。
count=250表示使用过去250个交易日(约一年)的历史数据来计算资产的波动率和相关性矩阵。risk_budget=None默认意味着每个资产对组合总风险的贡献度是相等的(即 1/N 的风险贡献)。
constraints(约束条件):WeightConstraint(low=0.0, high=1.0): 限制单只标的权重不能为负(不做空),也不能超过100%。WeightEqualConstraint(limit=1.0): 限制所有标的权重之和为1,即全额投资。
- 异常处理: 代码中加入了对
portfolio_optimizer返回值的检查。如果因为数据不足(例如ETF刚上市)导致优化失败,函数会返回None,策略会跳过本次调仓以避免报错。
常见问题 (Q&A)
Q: 为什么回测初期(如2013年)没有交易?
A: 策略中使用的ETF(如国债ETF 511010 或 黄金ETF 518880)在2013年左右才上市。RiskParity 模型需要过去250天的数据来计算协方差。如果数据长度不足,优化器会返回失败,策略将保持空仓直到数据充足。建议回测时间设置在2014年或2015年之后。
Q: 如何调整风险预算(Risk Budget)?
A: 如果你希望股票承担更多的风险,可以修改 RiskParity 中的 risk_budget 参数。例如,希望股票贡献60%风险,债券和黄金各20%,可以传入 risk_budget=pd.Series([0.6, 0.2, 0.2], index=g.etf_pool)。
Q: 可以加入更多资产吗?
A: 可以。只需在 g.etf_pool 列表中添加对应的标的代码即可。风险平价策略通常在资产相关性较低时表现最好,因此建议加入不同类别的资产(如纳指ETF、豆粕ETF等)。
Q: 为什么使用ETF而不是个股?
A: 风险平价模型依赖于对波动率和相关性的估计。个股的特质风险(非系统性风险)较高,且容易发生停牌或暴雷,导致协方差矩阵不稳定。ETF代表一篮子资产,波动特性更稳定,更适合用于大类资产配置策略。