🚀 AI 一键生成 joinquant 策略代码

如何使用 JoinQuant 优化器构建因子值最大化(MaxFactorValue)的投资组合?

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

问题描述

我想构建一个投资组合,让某个特定因子的暴露值最大化,应该怎么做?

解决方案

在JoinQuant聚宽平台上,要构建一个让特定因子暴露值最大化的投资组合,主要使用 jqlib.optimizer 库中的 portfolio_optimizer 函数,配合 MaxFactorValue 目标函数来实现。

实现思路

  1. 定义因子:首先需要定义你想要最大化暴露的因子。这需要创建一个继承自 jqfactor.Factor 的类,并实现 calc 方法。
  2. 设置优化目标:在调用优化器时,将 target 参数设置为 MaxFactorValue(factor=你的因子类)
  3. 设置约束条件:为了防止持仓过于集中或出现不合理的仓位,通常需要设置权重约束(如总仓位限制、单只股票权重上限等)。
  4. 执行优化与交易:调用 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)

关键点解析

  1. 自定义因子类 (MomentumFactor):

    • 必须继承自 jqfactor.Factor
    • max_window: 指定计算因子需要回溯的历史数据长度。
    • dependencies: 指定需要用到的基础数据字段(如 close, volume 等)。
    • calc(self, data): 核心计算逻辑,data 是一个字典,包含了 dependencies 中请求的数据(DataFrame格式)。
  2. 优化目标 (MaxFactorValue):

    • target = MaxFactorValue(factor=MomentumFactor, count=1)
    • 这个目标函数告诉优化器,寻找一组权重,使得 Sum(Weight_i * FactorValue_i) 最大化。
  3. 约束与边界 (constraints & bounds):

    • 如果不加约束,优化器可能会将 100% 的资金全部分配给因子值最大的那一只股票,这通常风险过大。
    • Bound(low=0.0, high=0.1) 限制了单只股票最大持仓为 10%,强制分散投资。
    • WeightConstraint(low=0.95, high=1.0) 保证资金利用率。
  4. 优化器调用 (portfolio_optimizer):

    • 该函数是聚宽专门用于组合优化的接口,它会自动处理因子数据的获取、对齐和数学规划求解。

常见问题 Q&A

Q: 如果我想最小化某个因子(例如最小化波动率)怎么办?
A: 可以使用 MinFactorValue(factor=YourFactor),或者使用 MinVariance(如果是针对波动率/方差)。如果是自定义因子,也可以在 calc 方法中返回负值,然后继续使用 MaxFactorValue

Q: 优化器运行失败返回 None 是什么原因?
A: 通常是因为约束条件过于苛刻,导致无解。例如,股票池中只有5只股票,但你要求单只股票权重不超过10%且总仓位要达到90%,这在数学上是不可能的。建议放宽约束条件或检查股票池数量。

Q: 可以同时最大化两个因子吗?
A: portfolio_optimizertarget 参数只能接受一个目标。如果需要综合考虑多个因子,建议先在 calc 方法中将多个因子合成一个新的综合因子(例如加权打分),然后对这个综合因子进行最大化。或者使用 MaxScore 目标函数,传入预先计算好的综合分数。