🚀 AI 一键生成 joinquant 策略代码

如何使用 JoinQuant 的 portfolio_optimizer 实现风险最小化策略?

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

问题描述

如何构建一个由10只股票组成的风险最小化的投资组合?

解决方案

构建一个由10只股票组成的风险最小化投资组合,通常包含两个步骤:

  1. 选股(Selection):从股票池(如沪深300)中筛选出波动率较低的10只股票。低波动率通常意味着个股风险较低。
  2. 优化(Optimization):使用投资组合优化器,对这10只股票的权重进行配置,使得整体组合的方差(风险)最小。

在聚宽(JoinQuant)中,我们可以利用 jqlib.optimizer 库中的 portfolio_optimizer 函数和 MinVariance(最小方差)目标函数来实现这一策略。

以下是完整的策略代码:

策略逻辑说明

  1. 股票池:以沪深300指数成分股为基础。
  2. 选股逻辑:每月调仓一次。在调仓日,计算所有成分股过去一年的日收益率标准差(波动率),选取波动率最低的10只股票。
  3. 权重优化:使用 MinVariance 模型,计算这10只股票在组合风险最小化情况下的最优权重。
  4. 交易执行:根据优化后的权重调整持仓。

策略代码

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

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')
    
    # 设定全局变量
    g.stock_num = 10  # 持仓股票数量
    
    # 设定每月第一个交易日进行调仓
    run_monthly(rebalance, monthday=1, time='09:30')

def rebalance(context):
    """
    调仓主函数
    """
    print(f"日期: {context.previous_date} 开始调仓")
    
    # 1. 获取股票池(沪深300)
    universe = get_index_stocks('000300.XSHG')
    
    # 2. 过滤停牌和ST股票
    current_data = get_current_data()
    universe = [stock for stock in universe if not current_data[stock].paused and not current_data[stock].is_st]
    
    if len(universe) < g.stock_num:
        print("可选股票不足,跳过本次调仓")
        return

    # 3. 选股:选取过去250个交易日波动率最低的10只股票
    # 获取过去250天的收盘价
    history_data = get_price(universe, end_date=context.previous_date, count=250, fields=['close'], panel=False)
    
    # 计算日收益率
    # pivot转换格式:行是日期,列是股票代码
    close_df = history_data.pivot(index='time', columns='code', values='close')
    returns_df = close_df.pct_change().dropna()
    
    # 计算每只股票的标准差(波动率)
    volatility = returns_df.std()
    
    # 取波动率最小的前10只股票
    target_stocks = volatility.nsmallest(g.stock_num).index.tolist()
    print(f"选定低波动股票: {target_stocks}")

    # 4. 组合优化:计算最小化方差的权重
    # 使用 jqlib.optimizer 中的 portfolio_optimizer
    # 目标:MinVariance (最小化组合方差)
    # 约束:WeightEqualConstraint(1.0) 保证总仓位为100%
    # 边界:Bound(0, 1) 每只股票权重在0到1之间(不做空)
    
    try:
        optimized_weights = portfolio_optimizer(
            date=context.previous_date,
            securities=target_stocks,
            target=MinVariance(count=250), # 优化目标:最小方差
            constraints=[
                WeightEqualConstraint(limit=1.0), # 总权重等于1
                WeightConstraint(low=0.0, high=1.0) # 单只股票权重限制
            ],
            bounds=[Bound(0.0, 1.0)], # 边界条件
            default_port_weight_range=[0.0, 1.0],
            return_none_if_fail=True
        )
    except Exception as e:
        print(f"优化器运行出错: {e}")
        optimized_weights = None

    # 5. 执行交易
    if optimized_weights is None:
        print("组合优化失败,使用等权重分配")
        # 如果优化失败,退化为等权重
        weight_value = 1.0 / len(target_stocks)
        optimized_weights = pd.Series(weight_value, index=target_stocks)
    
    # 获取当前持仓
    current_positions = context.portfolio.positions.keys()
    
    # 卖出不在目标列表中的股票
    for stock in current_positions:
        if stock not in target_stocks:
            order_target_value(stock, 0)
    
    # 买入/调整目标股票
    total_value = context.portfolio.total_value
    for stock in target_stocks:
        target_weight = optimized_weights.get(stock, 0.0)
        if target_weight > 0:
            target_value = total_value * target_weight
            order_target_value(stock, target_value)
            
    print("调仓完成")

代码关键点解析

  1. get_index_stocks('000300.XSHG'): 获取沪深300成分股作为基础池,保证流动性。
  2. volatility.nsmallest(g.stock_num): 这是实现“10只股票”约束的关键步骤。我们先通过历史波动率筛选出最稳定的10只股票,而不是直接把300只股票扔给优化器(那样优化器可能会选出几十只股票,或者集中在极少数股票上)。
  3. portfolio_optimizer: 这是聚宽提供的核心优化函数。
    • target=MinVariance(count=250): 告诉优化器我们的目标是让组合的整体方差最小,计算基于过去250天的数据。
    • constraints: 限制所有股票权重之和为1(满仓操作)。
  4. 容错处理: 如果优化器因为数据原因求解失败(返回 None),策略会自动退化为等权重持有这10只股票,保证策略的稳健性。