🚀 AI 一键生成 joinquant 策略代码

JoinQuant 投资组合优化器如何设置换手率约束?

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

问题描述

在使用 portfolio_optimizer 时如何限制组合的换手率?

解决方案

在使用 JoinQuant 的 portfolio_optimizer 进行投资组合优化时,可以通过在 constraints(约束条件)参数中添加 TurnoverConstraint 对象来限制组合的换手率。

核心步骤

  1. 导入模块:需要从 jqlib.optimizer 导入相关类。
  2. 获取当前持仓权重:为了计算换手率,优化器必须知道当前的持仓情况。你需要构建一个包含当前持仓权重的 pandas.Series
  3. 设置约束:实例化 TurnoverConstraint,传入换手率上限(limit)和当前持仓权重(current_portfolio)。
  4. 执行优化:将该约束对象放入 portfolio_optimizerconstraints 列表中。

代码实现示例

以下是一个完整的策略代码片段,展示了如何在调仓时限制换手率(例如限制单次调仓换手率不超过 20%):

# -*- coding: utf-8 -*-
from jqdata import *
from jqlib.optimizer import *
import pandas as pd

def initialize(context):
    set_benchmark('000300.XSHG')
    set_option('use_real_price', True)
    # 设定股票池
    g.stocks = get_index_stocks('000300.XSHG')
    # 每月调仓一次
    run_monthly(rebalance, monthday=1)

def rebalance(context):
    # 1. 获取当前持仓权重 (构建 current_portfolio)
    # 换手率是相对于当前持仓计算的,因此必须传入当前的权重分布
    total_value = context.portfolio.total_value
    current_weights_dict = {}
    
    for security, position in context.portfolio.positions.items():
        if position.total_amount > 0:
            # 计算个股权重:持仓市值 / 总资产
            weight = (position.total_amount * position.price) / total_value
            current_weights_dict[security] = weight
            
    # 转换为 pandas.Series,这是 TurnoverConstraint 要求的格式
    current_portfolio = pd.Series(current_weights_dict)
    
    # 2. 定义换手率约束
    # limit=0.2 表示限制本次优化的换手率不超过 20%
    # current_portfolio 传入当前的持仓结构
    turnover_constraint = TurnoverConstraint(limit=0.2, current_portfolio=current_portfolio)
    
    # 3. 定义其他约束 (例如:全仓投资,权重和为1)
    weight_constraint = WeightEqualConstraint(limit=1.0)
    
    # 4. 设定优化目标 (例如:最小化方差)
    target = MinVariance(count=250)
    
    # 5. 运行优化器
    # 注意:需要处理 g.stocks 可能停牌的情况,这里简化处理直接使用 g.stocks
    optimized_weights = portfolio_optimizer(
        date=context.previous_date,
        securities=g.stocks,
        target=target,
        constraints=[weight_constraint, turnover_constraint], # 将换手率约束加入列表
        default_port_weight_range=[0.0, 1.0],
        return_none_if_fail=True
    )
    
    # 6. 执行调仓
    if optimized_weights is not None:
        log.info("优化成功,开始调仓")
        for stock, weight in optimized_weights.items():
            order_target_value(stock, total_value * weight)
    else:
        log.warn("优化失败,不进行调仓")

参数详解

TurnoverConstraint(limit, current_portfolio=None)

  • limit (float): 换手率的上限。例如 0.2 代表 20%。
    • 换手率计算公式通常为:$\sum |w_{new} - w_{old}|$ (双边)或者 $\frac{1}{2} \sum |w_{new} - w_{old}|$(单边),具体取决于优化器内部实现,通常指调整权重的绝对值之和的限制。
  • current_portfolio (pandas.Series): 当前投资组合的权重。
    • 重要:如果该参数为 None(默认值),优化器会认为当前是空仓状态。此时,任何买入操作都会被视为 100% 的换手(相对于空仓)。因此,为了正确限制相对于“现有持仓”的换手率,必须传入当前持仓的权重 Series。

常见问题 (Q&A)

Q: 如果我不传 current_portfolio 参数会怎样?
A: 如果不传 current_portfolio,优化器默认当前为空仓。如果你设置了 limit=0.2,优化器会认为你只能建立总仓位为 20% 的新组合,这通常不是你想要的结果(除非你确实是第一次建仓)。

Q: TurnoverConstraint 能和其他约束(如行业中性)一起使用吗?
A: 可以。你可以将 TurnoverConstraintIndustryConstraintMarketConstraintBarraConstraint 等一起放入 constraints 列表中,优化器会尝试寻找同时满足所有条件的解。

Q: 为什么加上换手率限制后优化经常失败(返回 None)?
A: 加上换手率限制缩小了可行解的范围。如果限制过严(例如 limit 很小),或者与其他约束(如严格的行业暴露限制)发生冲突,可能导致无解。建议适当放宽 limit 或增加 ftol(容差),或者检查约束条件是否存在逻辑冲突。