问题描述
解决方案
在聚宽(JoinQuant)的投资组合优化器(jqlib.optimizer)中,限制组合对特定风格因子(如市值、动量、贝塔等)的暴露,主要有两种方式:
- 使用
BarraConstraint(推荐):专门用于限制标准的风险风格因子(如市值size、贝塔beta、动量momentum等)。 - 使用
ExposureConstraint:用于限制自定义因子或特定的Factor类因子的暴露。
针对您提到的限制市值(Size)因子暴露,最直接且常用的方法是使用 BarraConstraint。
方法一:使用 BarraConstraint(针对标准风格因子)
这是限制市值(Size)暴露最简单的方法。BarraConstraint 内置了常见的风险因子。
语法:
BarraConstraint(size=[low, high], ...)
- 参数:传入一个列表
[下限, 上限]。 - 单位:通常是标准化后的因子值(Z-Score,即标准差倍数)。例如
[-0.5, 0.5]表示组合在市值因子上的暴露控制在相对于基准/全市场的正负 0.5 个标准差以内。
方法二:使用 ExposureConstraint(针对自定义因子)
如果您需要限制的是一个非标准的市值定义(例如您自己计算的对数市值),或者其他自定义因子,可以使用此方法。
语法:
ExposureConstraint(factor_class, low=..., high=...)
完整策略代码示例
以下是一个完整的策略示例。该策略的目标是最小化组合方差,同时强制要求组合在**市值因子(Size)**上的暴露保持在 -0.5 到 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)
# 设定手续费
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 设定每月第一个交易日运行
run_monthly(market_open, monthday=1, time='09:30')
def market_open(context):
# 1. 确定股票池:这里以上证50成分股为例
stock_list = get_index_stocks('000016.XSHG')
# 2. 设定优化目标:最小化组合方差 (MinVariance)
# count=250 表示使用过去250天的收益率数据来计算协方差矩阵
target = MinVariance(count=250)
# 3. 设定约束条件
constraints = []
# 约束 1: 组合总仓位限制在 95% 到 100% 之间
constraints.append(WeightConstraint(low=0.95, high=1.0))
# 约束 2: 【核心部分】限制市值因子(size)暴露
# 这里限制组合在市值因子上的暴露度在 -0.5 到 0.5 个标准差之间
# standardlize=True 表示对因子进行标准化处理(默认即为True)
# winsorize=True 表示对因子进行去极值处理(默认即为True)
constraints.append(BarraConstraint(size=[-0.5, 0.5]))
# 约束 3: 个股权重上限限制 (防止单只股票仓位过重)
# 每只股票权重不超过 10%
bounds = [Bound(0.0, 0.1)]
print("开始进行组合优化,限制市值因子暴露在 [-0.5, 0.5]...")
# 4. 运行优化器
# 注意:date应为前一个交易日,避免未来函数
optimized_weight = portfolio_optimizer(
date=context.previous_date,
securities=stock_list,
target=target,
constraints=constraints,
bounds=bounds,
ftol=1e-09,
return_none_if_fail=True
)
# 5. 执行调仓
if optimized_weight is None:
print("警告:组合优化失败,保持当前仓位")
else:
print("优化成功,开始调仓")
# 获取当前总资产
total_value = context.portfolio.total_value
# 先卖出不在优化列表中的股票
for stock in context.portfolio.positions.keys():
if stock not in optimized_weight.index:
order_target_value(stock, 0)
# 根据优化后的权重买入/调整
for stock in optimized_weight.index:
target_value = total_value * optimized_weight[stock]
order_target_value(stock, target_value)
代码关键点解析
from jqlib.optimizer import *: 必须导入优化器库。BarraConstraint(size=[-0.5, 0.5]):- 这是限制风格暴露的核心代码。
size代表市值因子。[-0.5, 0.5]是限制范围。由于 Barra 风格因子通常经过标准化(Z-Score),0 代表市场平均水平。限制在 0 附近意味着你的组合在市值风格上是“中性”的,既不偏向大盘也不偏向小盘(相对于股票池而言)。- 除了
size,你还可以限制beta(贝塔)、momentum(动量)、book_to_price(账面市值比/价值)、liquidity(流动性)等。
portfolio_optimizer: 这是执行优化的主函数,它会尝试找到满足所有constraints(包括市值暴露限制)且使target(如方差)最小化的权重组合。
常见问题 (Q&A)
Q1: BarraConstraint 中的数值范围(如 [-0.5, 0.5])单位是什么?
A: 单位是标准差(Standard Deviation)。优化器内部会对因子数据进行标准化(Standardization)。0 表示该因子的平均水平,1 表示高于平均水平 1 个标准差。
Q2: 如果我想限制组合偏向小盘股,应该怎么设置?
A: 你可以将 size 的范围设置为负数区间。例如 BarraConstraint(size=[-2.0, -0.5])。这会强制优化器选择市值较小的股票,或者给予小市值股票更高的权重。
Q3: 优化器返回 None 是什么原因?
A: 这通常意味着“无解”。即在给定的约束条件下(例如:既要市值暴露极低,又要个股权重极低,还要满足全仓),数学上找不到满足条件的组合。解决方法是放宽约束条件(例如扩大 size 的范围,或放宽 Bound)。
Q4: 可以同时限制多个风格因子吗?
A: 可以。例如:BarraConstraint(size=[-0.5, 0.5], momentum=[0.0, 1.0]),这表示限制市值中性,同时要求组合具有正向动量(动量暴露大于均值)。