🚀 AI 一键生成 joinquant 策略代码

如何使用 JoinQuant 优化器实现最大夏普比率投资组合?

joinquant | 发布时间: 2025/12/8
以下内容由 EasyQuant 生成。

问题描述

请帮我构建一个以最大化夏普比率为目标的投资组合策略

解决方案

这是一个基于聚宽(JoinQuant)平台的投资组合优化策略,旨在通过 jqlib.optimizer 库中的 最大化夏普比率(MaxSharpeRatio) 模型来分配仓位。

策略逻辑说明

  1. 股票池选择:选取沪深300成分股作为基础股票池,代表大盘蓝筹股,流动性较好。
  2. 数据过滤:剔除停牌和ST股票,确保标的可交易。
  3. 优化目标:使用 MaxSharpeRatio 模型,寻找在给定风险下收益最高的组合权重。
  4. 约束条件
    • 单只股票权重上限设为 10% (Bound(0, 0.1)),防止持仓过于集中。
    • 组合总权重设为 100% (weight_sum_equal=1.0)。
  5. 调仓频率:按月调仓(每月第一个交易日),因为组合优化计算量较大且基于中长期配置逻辑。

策略代码

# -*- 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("调仓完成")

代码关键点解析

  1. portfolio_optimizer 函数

    • 这是聚宽提供的核心优化函数。它需要历史数据来计算收益率均值和协方差矩阵。
    • date=context.previous_date:使用前一交易日的数据进行计算,避免未来函数。
    • return_none_if_fail=True:如果优化求解失败(例如数据不足或无解),返回 None,策略中做了相应的判断处理,防止报错停止。
  2. MaxSharpeRatio 目标

    • 该目标函数会自动寻找有效前沿上与无风险利率连线斜率最大的点(即切点组合)。
    • count=250 表示使用过去一年的数据来估算波动率和收益率,这是比较常用的参数。
  3. Bound 约束

    • 如果不加 Bound(0.0, 0.1),优化器可能会将 100% 的资金分配给历史上表现最好的一两只股票,导致风险过度集中。限制单只股票权重是工程实践中必要的风控手段。
  4. 交易执行

    • 使用 order_target_value 可以直接根据目标市值下单,系统会自动计算需要买入或卖出的股数,非常方便。

Q&A

Q: 为什么选择沪深300作为股票池?
A: 沪深300指数包含了A股市场中规模大、流动性好的300只股票。对于组合优化算法(特别是涉及协方差矩阵计算的算法),数据的完整性和股票的流动性非常重要。如果使用小盘股,可能会因为停牌、流动性不足或数据缺失导致优化器求解失败。

Q: 策略中的 count=250 可以修改吗?
A: 可以。count 代表用于计算历史统计量(如均值、方差)的回溯天数。

  • 调大(如 500):样本更多,统计结果更稳定,但对近期市场变化的反应较慢。
  • 调小(如 60):对近期趋势反应快,但噪音较大,换手率可能会显著增加。

Q: 为什么优化器有时会返回 None?
A: 常见原因包括:

  1. 数据不足:选定的股票在回溯期内(如过去250天)有大量停牌或未上市,导致无法计算协方差。
  2. 约束冲突:设置的约束条件过于苛刻,导致数学上无解(例如要求单只股票权重<1%,但总权重=100%,而股票池只有50只股票,这是不可能满足的)。
  3. 求解超时:股票数量过多(如几千只),计算量过大导致超时。

Q: 如何进一步改进这个策略?
A:

  1. 添加风险模型约束:使用 BarraConstraint 来控制行业暴露或风格暴露(如限制在某个行业的权重不超过20%)。
  2. 更换股票池:可以先通过多因子模型选出前50只股票,再对这50只股票进行夏普比率优化,结合了Alpha选股和Beta风控。
  3. 调整调仓频率:根据市场波动情况,可以尝试周频调仓,但需注意手续费对收益的侵蚀。