🚀 AI 一键生成 joinquant 策略代码
立即体验

如何在聚宽(JoinQuant)中构建红利、低波动与动量三因子选股策略?(附缓冲机制源码)

joinquant | 发布时间: 2026/8/13
以下内容由 EasyQuant 生成。

问题描述

聚宽多因子选股:如何实现红利、低波动与动量组合策略?
如何在聚宽中构建红利、低波动和动量三因子选股策略,并通过缓冲机制减少调仓?

解决方案

聚宽多因子选股:红利、低波动与动量组合策略构建指南

在量化投资中,红利(Value/Dividend)、**低波动(Low Volatility)动量(Momentum)是三种具有低相关性且长期被验证有效的经典风格因子。将三者结合构建多因子策略,不仅能平滑单一因子的周期性失效,还能通过因子互补提升组合的夏普比率。同时,引入调仓缓冲机制(Buffer Zone)**可以有效避免边缘标的频繁进出导致的过度交易,从而大幅降低交易成本。

本指南将教你如何在聚宽(JoinQuant)平台中,利用聚宽因子库(jqfactor)和组合优化思想,构建这一经典的多因子选股策略。


一、 核心因子定义与聚宽代码映射

在聚宽因子库中,我们可以直接获取这三类风格因子或其描述因子:

  1. 红利因子(Value)
    • 聚宽映射:book_to_price_ratio(账面市值比因子,即PB的倒数)。账面市值比越高,代表估值越低,红利属性越强。
  2. 低波动因子(Low Volatility)
    • 聚宽映射:residual_volatility(残差波动率因子)。该因子排除了市场Beta和市值因子的影响,能更纯粹地反映个股的特质低波动属性。因子值越小,波动越低。
  3. 动量因子(Momentum)
    • 聚宽映射:momentum(动量因子,即相对强弱)。反映个股过去一段时间的超额对数收益率。因子值越大,动量效应越强。

二、 缓冲机制(Buffer Zone)原理

为了防止在调仓日,排名在临界点(例如第50名)附近的股票因为微小的排名变动而被频繁买卖,我们设计如下缓冲机制:

  • 买入缓冲:只有当股票的综合因子得分排名前 $N$(如前40名)时,才新买入。
  • 卖出缓冲:已持有的股票,只有当其排名跌出 $M$(如第60名,其中 $M > N$)时,才卖出。
  • 维持持仓:排名在 $N$ 到 $M$ 之间的已持仓股票继续持有,不进行换仓。

三、 聚宽策略完整源码实现

以下是基于聚宽 API 编写的完整策略代码。策略采用按月调仓,使用 get_factor_values 获取因子,并进行去极值、标准化和等权重合成。

import pandas as pd
import numpy as np
from jqdata import *
from jqfactor import get_factor_values, winsorize_med, standardlize

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')
    
    # 策略参数设置
    g.factors = ['book_to_price_ratio', 'residual_volatility', 'momentum']
    g.N = 40  # 买入触发线(前40名)
    g.M = 60  # 卖出触发线(跌出前60名)
    
    # 每月第一个交易日开盘运行
    run_monthly(rebalance, monthday=1, time='09:30')

def rebalance(context):
    # 1. 获取全A股股票池(剔除ST、停牌、上市未满一年的股票)
    current_data = get_current_data()
    all_stocks = list(get_all_securities(['stock'], date=context.current_dt).index)
    
    # 过滤标的
    all_stocks = [s for s in all_stocks if not current_data[s].is_st 
                  and not current_data[s].paused 
                  and (context.current_dt.date() - get_security_info(s).start_date).days > 365]
    
    # 2. 获取因子值
    yesterday = context.previous_date
    factor_data = get_factor_values(securities=all_stocks, 
                                    factors=g.factors, 
                                    start_date=yesterday, 
                                    end_date=yesterday)
    
    # 3. 因子数据处理与合成
    df_list = []
    for factor in g.factors:
        val = factor_data[factor].loc[pd.Timestamp(yesterday)]
        # 去极值与标准化
        val = winsorize_med(val, scale=3)
        val = standardlize(val)
        df_list.append(val)
        
    df = pd.concat(df_list, axis=1, keys=g.factors).dropna()
    
    # 4. 计算综合得分 (红利+,低波-,动量+)
    # 低波动因子值越小越好,因此乘以 -1 进行合成
    df['score'] = df['book_to_price_ratio'] * 1.0 - df['residual_volatility'] * 1.0 + df['momentum'] * 1.0
    
    # 5. 股票排序
    df_sorted = df.sort_values(by='score', ascending=False)
    target_list = list(df_sorted.index)  # 综合得分从高到低的股票列表
    
    # 6. 缓冲机制逻辑应用
    current_holdings = list(context.portfolio.positions.keys())
    keep_list = []
    
    # 检查当前持仓,如果在前 M (60) 名内,则保留
    for stock in current_holdings:
        if stock in target_list:
            rank = target_list.index(stock)
            if rank < g.M:
                keep_list.append(stock)
                
    # 计算还需要新买入的股票数量
    num_to_buy = g.N - len(keep_list)
    new_buy_list = []
    
    # 从前 N (40) 名中挑选未持有的股票补足仓位
    for stock in target_list[:g.N]:
        if stock not in keep_list:
            new_buy_list.append(stock)
            if len(new_buy_list) == num_to_buy:
                break
                
    final_target_portfolio = keep_list + new_buy_list
    
    # 7. 执行交易(等权重配置)
    # 卖出不在最终目标组合中的股票
    for stock in current_holdings:
        if stock not in final_target_portfolio:
            order_target(stock, 0)
            log.info(f"[卖出] 跌出缓冲区: {stock}")
            
    # 等权重买入/调仓
    position_value = context.portfolio.total_value / g.N
    for stock in final_target_portfolio:
        order_target_value(stock, position_value)
        if stock not in current_holdings:
            log.info(f"[买入] 综合评分晋级: {stock}")

四、 策略优化建议

  1. 行业中性化(Neutralization):红利因子天然偏向银行、地产等低估值行业,而动量因子可能偏向科技、医药等高弹性行业。在合成因子前,可以使用聚宽内置的 neutralize 函数对因子进行行业和市值中性化,使组合行业分布更均衡。
  2. 动态缓冲比例:缓冲区的宽度($M - N$)可以根据市场波动率动态调整。在市场剧烈震荡、换手率飙升时,可适当拉大缓冲区,进一步节省交易摩擦成本。