问题描述
解决方案
在使用 JoinQuant 的 portfolio_optimizer 进行投资组合优化时,可以通过在 constraints(约束条件)参数中添加 TurnoverConstraint 对象来限制组合的换手率。
核心步骤
- 导入模块:需要从
jqlib.optimizer导入相关类。 - 获取当前持仓权重:为了计算换手率,优化器必须知道当前的持仓情况。你需要构建一个包含当前持仓权重的
pandas.Series。 - 设置约束:实例化
TurnoverConstraint,传入换手率上限(limit)和当前持仓权重(current_portfolio)。 - 执行优化:将该约束对象放入
portfolio_optimizer的constraints列表中。
代码实现示例
以下是一个完整的策略代码片段,展示了如何在调仓时限制换手率(例如限制单次调仓换手率不超过 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: 可以。你可以将 TurnoverConstraint 与 IndustryConstraint、MarketConstraint 或 BarraConstraint 等一起放入 constraints 列表中,优化器会尝试寻找同时满足所有条件的解。
Q: 为什么加上换手率限制后优化经常失败(返回 None)?
A: 加上换手率限制缩小了可行解的范围。如果限制过严(例如 limit 很小),或者与其他约束(如严格的行业暴露限制)发生冲突,可能导致无解。建议适当放宽 limit 或增加 ftol(容差),或者检查约束条件是否存在逻辑冲突。