问题描述
解决方案
在JoinQuant聚宽平台上,要构建一个让特定因子暴露值最大化的投资组合,主要使用 jqlib.optimizer 库中的 portfolio_optimizer 函数,配合 MaxFactorValue 目标函数来实现。
实现思路
- 定义因子:首先需要定义你想要最大化暴露的因子。这需要创建一个继承自
jqfactor.Factor的类,并实现calc方法。 - 设置优化目标:在调用优化器时,将
target参数设置为MaxFactorValue(factor=你的因子类)。 - 设置约束条件:为了防止持仓过于集中或出现不合理的仓位,通常需要设置权重约束(如总仓位限制、单只股票权重上限等)。
- 执行优化与交易:调用
portfolio_optimizer获取最优权重,并根据权重调整持仓。
策略代码示例
以下是一个完整的策略示例。该策略定义了一个简单的 动量因子(20日收益率),并构建一个投资组合,在满足单只股票权重不超过10%的约束下,最大化该动量因子的暴露值(即倾向于持有动量最强的股票)。
# -*- coding: utf-8 -*-
# 导入函数库
from jqdata import *
from jqfactor import Factor
from jqlib.optimizer import *
import pandas as pd
import numpy as np
# 1. 定义因子:这里以20日动量因子为例
class MomentumFactor(Factor):
name = 'momentum_20'
max_window = 21
dependencies = ['close']
def calc(self, data):
# 计算 (当前收盘价 / 20天前收盘价) - 1
close = data['close']
# iloc[-1]是最新收盘价,iloc[0]是20天前的收盘价
ret = close.iloc[-1] / close.iloc[0] - 1
return ret
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定股票类每笔交易时的手续费
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):
# 获取当前日期的前一个交易日,用于因子计算和优化
date = context.previous_date
# 1. 确定股票池:这里选用沪深300成分股
stock_pool = get_index_stocks('000300.XSHG', date=date)
# 过滤停牌、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
# 2. 设置优化参数
# 目标:最大化自定义的动量因子暴露
# count=1 表示使用最近1天的因子值进行优化
target = MaxFactorValue(factor=MomentumFactor, count=1)
# 约束条件:
# WeightConstraint: 组合总权重在 95% 到 100% 之间(接近满仓)
constraints = [
WeightConstraint(low=0.95, high=1.0)
]
# 边界条件:
# Bound: 单只股票权重上限为 10%,下限为 0%(不做空)
bounds = [Bound(low=0.0, high=0.1)]
# 3. 运行优化器
# 注意:portfolio_optimizer 返回的是一个 Series,index是股票代码,value是权重
optimized_weight = portfolio_optimizer(
date=date,
securities=stock_pool,
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("优化成功,开始调仓")
# 获取当前持仓
current_positions = context.portfolio.positions
# 先卖出不在优化结果中的股票
for stock in current_positions:
if stock not in optimized_weight.index:
order_target_value(stock, 0)
# 获取当前总资产
total_value = context.portfolio.total_value
# 根据优化后的权重买入/调整股票
for stock, weight in optimized_weight.items():
target_value = total_value * weight
order_target_value(stock, target_value)
关键点解析
-
自定义因子类 (
MomentumFactor):- 必须继承自
jqfactor.Factor。 max_window: 指定计算因子需要回溯的历史数据长度。dependencies: 指定需要用到的基础数据字段(如close,volume等)。calc(self, data): 核心计算逻辑,data是一个字典,包含了dependencies中请求的数据(DataFrame格式)。
- 必须继承自
-
优化目标 (
MaxFactorValue):target = MaxFactorValue(factor=MomentumFactor, count=1)- 这个目标函数告诉优化器,寻找一组权重,使得
Sum(Weight_i * FactorValue_i)最大化。
-
约束与边界 (
constraints&bounds):- 如果不加约束,优化器可能会将 100% 的资金全部分配给因子值最大的那一只股票,这通常风险过大。
Bound(low=0.0, high=0.1)限制了单只股票最大持仓为 10%,强制分散投资。WeightConstraint(low=0.95, high=1.0)保证资金利用率。
-
优化器调用 (
portfolio_optimizer):- 该函数是聚宽专门用于组合优化的接口,它会自动处理因子数据的获取、对齐和数学规划求解。
常见问题 Q&A
Q: 如果我想最小化某个因子(例如最小化波动率)怎么办?
A: 可以使用 MinFactorValue(factor=YourFactor),或者使用 MinVariance(如果是针对波动率/方差)。如果是自定义因子,也可以在 calc 方法中返回负值,然后继续使用 MaxFactorValue。
Q: 优化器运行失败返回 None 是什么原因?
A: 通常是因为约束条件过于苛刻,导致无解。例如,股票池中只有5只股票,但你要求单只股票权重不超过10%且总仓位要达到90%,这在数学上是不可能的。建议放宽约束条件或检查股票池数量。
Q: 可以同时最大化两个因子吗?
A: portfolio_optimizer 的 target 参数只能接受一个目标。如果需要综合考虑多个因子,建议先在 calc 方法中将多个因子合成一个新的综合因子(例如加权打分),然后对这个综合因子进行最大化。或者使用 MaxScore 目标函数,传入预先计算好的综合分数。