🚀 AI 一键生成 joinquant 策略代码

JoinQuant 多因子策略基础:如何对因子数据进行去极值、标准化和中性化处理?

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

问题描述

在做多因子选股时,如何对原始的因子数据进行处理,比如去掉极端值、做标准化或者剔除行业和市值的影响?

解决方案

在多因子选股策略中,原始因子数据通常存在量纲不同、包含异常值以及受行业或市值因素主导等问题。为了得到纯净且具有可比性的因子暴露度,通常需要进行去极值(Winsorization)、**标准化(Standardization)中性化(Neutralization)**处理。

聚宽(JoinQuant)的 jqfactor 库提供了现成的函数来完成这些步骤。

数据处理标准流程

通常的处理顺序为:

  1. 去极值:剔除或压缩异常数据,防止极端值影响后续统计。
  2. 标准化:将不同量纲的数据转化为标准正态分布(Z-Score),使其具有可比性。
  3. 中性化:剔除因子中的行业暴露和市值暴露,获取纯粹的因子Alpha。

核心函数说明

1. 去极值 (Winsorization)

推荐使用 中位数去极值法 (MAD),相比于标准差法(3-sigma),它对异常值更不敏感,更加稳健。

  • API: winsorize_med(data, scale=3, inclusive=True, axis=1)
  • 原理: 将超过 中位数 ± 3 * 绝对中位差 的值替换为边界值。

2. 标准化 (Standardization)

通常使用 Z-Score 标准化

  • API: standardlize(data, inf2nan=True, axis=1)
  • 原理: $X_{new} = \frac{X - \mu}{\sigma}$,处理后均值为0,标准差为1。

3. 中性化 (Neutralization)

通过回归分析剔除特定风险因子的影响(通常是行业和市值)。

  • API: neutralize(data, how=['jq_l1', 'market_cap'], date=date, axis=1)
  • 参数: how 列表指定要剔除的风险因子,jq_l1 代表聚宽一级行业,market_cap 代表市值。

完整代码示例

以下代码展示了如何获取沪深300成分股的市盈率(PE)因子,并依次进行去极值、标准化和中性化处理。

# -*- coding: utf-8 -*-
from jqdata import *
from jqfactor import get_factor_values, winsorize_med, standardlize, neutralize
import pandas as pd
import numpy as np

def initialize(context):
    # 设定基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 每天开盘前运行
    run_daily(process_factors, '09:00')

def process_factors(context):
    # 1. 设定参数
    date = context.previous_date # 使用前一交易日数据,避免未来函数
    universe = get_index_stocks('000300.XSHG', date=date) # 获取沪深300股票池
    
    print(f"正在处理 {date} 的因子数据...")

    # 2. 获取原始因子数据
    # 这里以市盈率(pe_ratio)为例,也可以通过 get_fundamentals 获取
    # get_factor_values 返回的是一个字典,key是因子名,value是DataFrame
    factor_data_dict = get_factor_values(
        securities=universe, 
        factors=['pe_ratio'], 
        end_date=date, 
        count=1
    )
    
    # 提取 DataFrame,行是日期,列是股票代码
    # 我们只需要最近一天的数据,并转置为 Series (index为股票代码) 以便处理
    raw_factor = factor_data_dict['pe_ratio'].iloc[-1, :]
    
    # 过滤掉 NaN 值
    raw_factor = raw_factor.dropna()
    
    if raw_factor.empty:
        print("无有效因子数据")
        return

    # --- 数据处理流水线 ---

    # 3. 去极值 (Winsorization)
    # 使用中位数去极值法,将极值压缩到边界
    factor_winsorized = winsorize_med(
        raw_factor, 
        scale=3,          # 3倍中位数绝对偏差
        inclusive=True,   # 边界外的值替换为边界值
        inf2nan=True,     # 无穷大转为NaN
        axis=0            # 对 Series 处理时 axis=0
    )
    
    # 4. 标准化 (Standardization)
    # Z-Score 标准化,使均值为0,标准差为1
    factor_standardized = standardlize(
        factor_winsorized, 
        inf2nan=True, 
        axis=0
    )
    
    # 5. 中性化 (Neutralization)
    # 剔除行业(jq_l1)和市值(market_cap)的影响
    # 注意:neutralize 需要 date 参数来获取当天的行业和市值数据进行回归
    factor_neutralized = neutralize(
        factor_standardized, 
        how=['jq_l1', 'market_cap'], 
        date=date, 
        axis=0
    )

    # --- 打印结果对比 ---
    print("\n处理前前5只股票数据:")
    print(raw_factor.head())
    
    print("\n去极值后前5只股票数据:")
    print(factor_winsorized.head())
    
    print("\n标准化后前5只股票数据:")
    print(factor_standardized.head())
    
    print("\n中性化后(最终)前5只股票数据:")
    print(factor_neutralized.head())
    
    # 此时 factor_neutralized 即为可用于打分或回归的纯净因子

关键点总结

  1. 处理顺序:建议严格按照 去极值 -> 标准化 -> 中性化 的顺序进行。如果先做标准化再做去极值,极值会扭曲均值和方差的计算;如果先做中性化,异常值会影响回归系数的估计。
  2. 数据结构jqfactor 的处理函数既支持 pandas.Series 也支持 pandas.DataFrame。如果是 DataFrame(多行多列),通常设置 axis=1 表示在横截面(按天)上对所有股票进行处理。
  3. 中性化依赖neutralize 函数内部会自动获取指定日期的行业分类和市值数据,因此必须传入准确的 date 参数。
  4. 缺失值:在处理前最好先剔除 NaN 值,虽然聚宽的函数有 inf2nan 等参数,但保持输入数据的整洁可以避免很多潜在错误。

Q&A

Q: 为什么要进行行业和市值中性化?
A: 很多因子(如换手率、波动率)天然与市值大小或特定行业(如银行股通常低PE)相关。如果不进行中性化,选出的股票可能会集中在小市值或某些特定行业,导致组合风险暴露过大。中性化是为了剥离这些风险,保留因子纯粹的选股能力(Alpha)。

Q: winsorizewinsorize_med 有什么区别?
A: winsorize 通常指基于标准差(如3-sigma)或分位数(如1%-99%)的去极值;winsorize_med 基于中位数和绝对中位差(MAD)。由于金融数据往往不服从正态分布且存在厚尾,均值和标准差容易受极值影响,因此基于中位数的 winsorize_med 通常更加稳健。

Q: 处理后的因子数据如何使用?
A: 处理后的因子数据可以直接用于多因子打分模型(加权求和)、计算IC值(信息系数)进行因子有效性检验,或者作为输入放入机器学习模型中进行训练。