问题描述
解决方案
这是一个基于聚宽(JoinQuant)平台的投资组合优化策略,旨在通过 jqlib.optimizer 库中的 最大化夏普比率(MaxSharpeRatio) 模型来分配仓位。
策略逻辑说明
- 股票池选择:选取沪深300成分股作为基础股票池,代表大盘蓝筹股,流动性较好。
- 数据过滤:剔除停牌和ST股票,确保标的可交易。
- 优化目标:使用
MaxSharpeRatio模型,寻找在给定风险下收益最高的组合权重。 - 约束条件:
- 单只股票权重上限设为 10% (
Bound(0, 0.1)),防止持仓过于集中。 - 组合总权重设为 100% (
weight_sum_equal=1.0)。
- 单只股票权重上限设为 10% (
- 调仓频率:按月调仓(每月第一个交易日),因为组合优化计算量较大且基于中长期配置逻辑。
策略代码
# -*- 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')
# 设定手续费:买入万三,卖出万三加千一印花税,最低5元
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 设定每月第一个交易日进行调仓
run_monthly(rebalance, monthday=1, time='09:30')
def rebalance(context):
"""
调仓主函数
"""
log.info("开始进行组合优化调仓...")
# 1. 获取股票池:沪深300
stock_pool = get_index_stocks('000300.XSHG')
# 2. 过滤停牌和ST股票
current_data = get_current_data()
stock_pool = [stock for stock in stock_pool
if not current_data[stock].paused
and not current_data[stock].is_st]
# 如果股票池为空,则不操作
if len(stock_pool) == 0:
return
# 3. 组合优化配置
# 目标:最大化夏普比率
# rf=0.0: 无风险利率设为0
# weight_sum_equal=1.0: 权重之和为1
# count=250: 使用过去250个交易日的数据计算协方差矩阵和预期收益
target = MaxSharpeRatio(rf=0.0, weight_sum_equal=1.0, count=250)
# 边界约束:每只股票权重范围 0% ~ 10%
# 这样可以避免资金过度集中在某一只股票上
bounds = [Bound(0.0, 0.1)]
# 调用优化器
# 注意:优化器计算需要一定时间,且可能因为数据不足或无法收敛而返回None
optimized_weight = portfolio_optimizer(
date=context.previous_date,
securities=stock_pool,
target=target,
bounds=bounds,
ftol=1e-09, # 精度控制
return_none_if_fail=True
)
# 4. 执行交易
if optimized_weight is None:
log.warn("组合优化失败,本次不调仓")
return
# 获取当前账户总资产
total_value = context.portfolio.total_value
# 先卖出:不在优化结果中的,或者权重降低的
# 获取当前持仓
current_positions = context.portfolio.positions
for stock in current_positions.keys():
if stock not in optimized_weight.index:
order_target_value(stock, 0)
# 买入/调整:按照优化后的权重下单
# 遍历优化结果
for stock, weight in optimized_weight.items():
# 计算目标持仓市值
target_value = total_value * weight
# 下单调整到目标市值
order_target_value(stock, target_value)
log.info("调仓完成")
代码关键点解析
-
portfolio_optimizer函数:- 这是聚宽提供的核心优化函数。它需要历史数据来计算收益率均值和协方差矩阵。
date=context.previous_date:使用前一交易日的数据进行计算,避免未来函数。return_none_if_fail=True:如果优化求解失败(例如数据不足或无解),返回None,策略中做了相应的判断处理,防止报错停止。
-
MaxSharpeRatio目标:- 该目标函数会自动寻找有效前沿上与无风险利率连线斜率最大的点(即切点组合)。
count=250表示使用过去一年的数据来估算波动率和收益率,这是比较常用的参数。
-
Bound约束:- 如果不加
Bound(0.0, 0.1),优化器可能会将 100% 的资金分配给历史上表现最好的一两只股票,导致风险过度集中。限制单只股票权重是工程实践中必要的风控手段。
- 如果不加
-
交易执行:
- 使用
order_target_value可以直接根据目标市值下单,系统会自动计算需要买入或卖出的股数,非常方便。
- 使用
Q&A
Q: 为什么选择沪深300作为股票池?
A: 沪深300指数包含了A股市场中规模大、流动性好的300只股票。对于组合优化算法(特别是涉及协方差矩阵计算的算法),数据的完整性和股票的流动性非常重要。如果使用小盘股,可能会因为停牌、流动性不足或数据缺失导致优化器求解失败。
Q: 策略中的 count=250 可以修改吗?
A: 可以。count 代表用于计算历史统计量(如均值、方差)的回溯天数。
- 调大(如 500):样本更多,统计结果更稳定,但对近期市场变化的反应较慢。
- 调小(如 60):对近期趋势反应快,但噪音较大,换手率可能会显著增加。
Q: 为什么优化器有时会返回 None?
A: 常见原因包括:
- 数据不足:选定的股票在回溯期内(如过去250天)有大量停牌或未上市,导致无法计算协方差。
- 约束冲突:设置的约束条件过于苛刻,导致数学上无解(例如要求单只股票权重<1%,但总权重=100%,而股票池只有50只股票,这是不可能满足的)。
- 求解超时:股票数量过多(如几千只),计算量过大导致超时。
Q: 如何进一步改进这个策略?
A:
- 添加风险模型约束:使用
BarraConstraint来控制行业暴露或风格暴露(如限制在某个行业的权重不超过20%)。 - 更换股票池:可以先通过多因子模型选出前50只股票,再对这50只股票进行夏普比率优化,结合了Alpha选股和Beta风控。
- 调整调仓频率:根据市场波动情况,可以尝试周频调仓,但需注意手续费对收益的侵蚀。