🚀 AI 一键生成 joinquant 策略代码

如何使用 JoinQuant 优化器实现收益最大化(MaxProfit)投资组合?

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

问题描述

如何构建一个以收益最大化为目标的投资组合?

解决方案

在聚宽(JoinQuant)平台上构建以“收益最大化”为目标的投资组合,主要依赖于 jqlib.optimizer 库中的 portfolio_optimizer 函数以及 MaxProfit 优化目标。

核心逻辑说明

  1. 优化目标 (target):使用 MaxProfit(count=250)。该目标函数会寻找在过去一段时间(如250个交易日)内,组合预期收益率最高的权重配置。
  2. 约束条件 (constraints):为了防止资金全部集中在某一只历史表现最好的股票上(导致风险过高),通常需要添加约束条件,例如:
    • 权重和限制WeightEqualConstraint(limit=1.0),保证满仓操作。
    • 单标的权重边界 (bounds):例如 Bound(0.0, 0.1),限制单只股票持仓不超过总资产的10%,强制分散投资。
  3. 股票池:通常选择一个指数成分股(如沪深300)作为待选股票池。

策略代码实现

以下是一个完整的策略代码。该策略每月调仓一次,在沪深300成分股中寻找历史收益最高的组合配置,同时限制单只股票权重不超过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)
    
    # 设置交易成本:印花税千分之一(卖出),佣金万分之三,最低5元
    set_order_cost(OrderCost(open_tax=0, close_tax=0.001, open_commission=0.0003, close_commission=0.0003, close_today_commission=0, min_commission=5), type='stock')
    
    # 设定每月第一个交易日进行调仓
    run_monthly(rebalance, monthday=1, time='09:30')

def rebalance(context):
    """
    调仓函数:执行组合优化并调整持仓
    """
    # 1. 获取待优化股票池:这里选择沪深300成分股
    # 注意:为了演示速度和稳定性,实际使用中可以先进行基本面筛选
    target_index = '000300.XSHG'
    securities = get_index_stocks(target_index)
    
    # 2. 设定优化参数
    # 目标:最大化组合收益 (基于过去250个交易日的数据)
    target = MaxProfit(count=250)
    
    # 约束条件:
    # (1) 组合总权重等于 1.0 (满仓)
    # (2) 单只股票权重范围 0.0 ~ 0.1 (防止过度集中)
    constraints = [
        WeightEqualConstraint(limit=1.0)
    ]
    bounds = [Bound(0.0, 0.1)]
    
    # 3. 运行优化器
    # 注意:date参数使用 context.previous_date 以避免未来函数
    log.info("开始进行组合优化计算...")
    optimized_weight = portfolio_optimizer(
        date=context.previous_date,
        securities=securities,
        target=target,
        constraints=constraints,
        bounds=bounds,
        default_port_weight_range=[0.0, 1.0],
        ftol=1e-09,
        return_none_if_fail=True
    )
    
    # 4. 执行交易
    if optimized_weight is None:
        log.warn("组合优化失败,本次不调整仓位。")
        return
    
    log.info("优化成功,开始调仓。")
    
    # 获取当前总资产
    total_value = context.portfolio.total_value
    
    # 获取当前持仓的股票列表
    current_positions = list(context.portfolio.positions.keys())
    
    # 卖出不在优化结果中的股票,或者权重变为0的股票
    for stock in current_positions:
        if stock not in optimized_weight.index or optimized_weight[stock] == 0:
            order_target_value(stock, 0)
    
    # 买入/调整优化结果中的股票
    # 遍历优化后的权重 Series
    for stock, weight in optimized_weight.items():
        if weight > 0:
            target_value = total_value * weight
            order_target_value(stock, target_value)

def handle_data(context, data):
    """
    按分钟回测时每分钟调用,按天回测时每天调用
    本策略为月度调仓,此处留空
    """
    pass

代码关键点解析

  1. from jqlib.optimizer import *: 必须导入此库才能使用优化器功能。
  2. MaxProfit(count=250): 这是核心。它告诉优化器,我们的目标是基于过去250天(约一年)的历史数据,找到收益率最高的组合。你可以根据需求修改 count 的值(例如60代表过去一个季度)。
  3. bounds=[Bound(0.0, 0.1)]: 如果不加这个边界限制,MaxProfit 算法通常会倾向于将 100% 的资金分配给过去涨幅最大的那一只股票,这会带来巨大的个股风险。设置 0.1 的上限强制资金分散到至少 10 只股票上。
  4. portfolio_optimizer(...): 这是执行计算的函数。
    • date=context.previous_date: 必须使用前一个交易日的数据进行计算,如果在开盘时使用当日数据会引入未来函数。
    • return_none_if_fail=True: 优化是一个数学求解过程,可能会因为约束冲突或数据缺失而失败。设置此项可以防止程序报错崩溃,允许我们在失败时跳过调仓。

Q&A

Q: 为什么使用 MaxProfit 优化出来的组合风险很高?
A: MaxProfit 仅仅关注历史收益率的最大化。在没有其他约束的情况下,它倾向于选择波动率极大、历史涨幅极高的股票。为了控制风险,建议配合 bounds(单标的权重限制)使用,或者结合 RiskParity(风险平价)等混合目标,或在选股阶段先剔除高风险标的。

Q: portfolio_optimizer 运行速度慢怎么办?
A: 组合优化涉及复杂的数学规划求解。如果股票池很大(如全A股),计算会非常耗时甚至超时。建议:

  1. 缩小股票池(如仅使用沪深300或中证500)。
  2. 在调用优化器前,先通过因子筛选(如市值、PE等)过滤掉一部分股票,只对剩下的几十只股票进行权重优化。
  3. 适当调大 ftol 参数(精度容忍度),例如设为 1e-6

Q: 如何在最大化收益的同时限制最大回撤?
A: 可以在 constraints 列表中添加 MaxDrawdownConstraint。例如:

constraints = [
    WeightEqualConstraint(limit=1.0),
    MaxDrawdownConstraint(limit=-0.2, count=250) # 限制组合过去一年的最大回撤不超过20%
]

注意:约束条件越多,优化失败(无解)的概率越高。