问题描述
解决方案
在聚宽(JoinQuant)平台中,对选股因子进行行业和市值中性化处理,主要使用 jqfactor 库中的 neutralize 函数。
该函数可以剔除因子中的行业暴露和市值暴露,使因子更加纯粹,避免选股结果过度集中在某些行业或大/小市值股票上。
核心步骤
- 导入函数:
from jqfactor import neutralize - 准备数据:获取原始因子数据(通常为
pandas.Series或pandas.DataFrame,索引为股票代码)。 - 调用函数:使用
neutralize(data, how=['行业分类', 'market_cap'], date='日期')。
代码示例
以下代码展示了如何在研究环境中获取沪深300成分股的市盈率(PE)因子,并对其进行申万一级行业和市值的中性化处理。
# -*- coding: utf-8 -*-
from jqdata import *
from jqfactor import neutralize
import pandas as pd
# 1. 设定分析日期和股票池
target_date = '2023-05-10'
# 获取沪深300成分股
stocks = get_index_stocks('000300.XSHG', date=target_date)
# 2. 获取原始因子数据
# 这里以市盈率(pe_ratio)作为示例因子
q = query(
valuation.code,
valuation.pe_ratio
).filter(
valuation.code.in_(stocks)
)
df = get_fundamentals(q, date=target_date)
# 数据预处理:将code设为索引,确保数据是 Series 格式,且去除空值
# neutralize 接受 Series 或 DataFrame,索引必须是股票代码
raw_factor = df.set_index('code')['pe_ratio'].dropna()
print("--- 原始因子数据预览 (前5行) ---")
print(raw_factor.head())
# 3. 执行中性化处理
# how 参数列表:
# 'sw_l1': 代表申万一级行业 (也可以用 'jq_l1' 聚宽一级行业)
# 'market_cap': 代表市值 (也可以用 'ln_market_cap' 对数市值)
# date 参数:指定日期用于获取当天的行业分类和市值数据
neutralized_factor = neutralize(raw_factor, how=['sw_l1', 'market_cap'], date=target_date)
print("\n--- 中性化处理后数据预览 (前5行) ---")
print(neutralized_factor.head())
# 4. (可选) 结果对比
# 通常中性化后的数据均值接近0,且不再与市值线性相关
print("\n--- 统计信息对比 ---")
print(f"原始因子均值: {raw_factor.mean():.4f}")
print(f"中性化后均值: {neutralized_factor.mean():.4f}")
关键参数说明
在使用 neutralize 函数时,how 参数是核心,它决定了你要剔除哪些风险暴露:
- 行业分类代码:
'sw_l1','sw_l2','sw_l3':申万一、二、三级行业。'jq_l1','jq_l2':聚宽一、二级行业。'zjw':证监会行业。
- 市值因子:
'market_cap':直接使用市值。'ln_market_cap':使用市值的自然对数(推荐,因为市值通常呈偏态分布,取对数后更符合正态分布假设)。
- 其他风格因子:
- 如果需要剔除 Barra 风格因子,也可以在
how中加入,例如'beta','momentum','volatility'等。
- 如果需要剔除 Barra 风格因子,也可以在
注意事项
- 日期参数 (
date):务必传入准确的日期。函数需要根据这个日期去查找当天的行业分类和市值数据。如果在回测中使用,请使用context.current_dt或context.previous_date。 - 数据格式:输入的
series索引必须是股票代码(如'000001.XSHE')。 - 缺失值处理:如果原始数据中有
NaN,或者某只股票在指定日期没有行业/市值数据,该股票在结果中可能会被剔除或返回NaN。
Q&A
Q: 为什么要进行市值中性化?
A: 很多因子(如换手率、波动率)天然与市值有很强的相关性。如果不做市值中性化,选出的股票可能倾向于全部是小盘股或全部是大盘股,导致策略实际上是在赚取“市值因子”的钱,而不是你设计的因子的钱,且会承担巨大的市值风格暴露风险。
Q: neutralize 函数支持 DataFrame 吗?
A: 支持。如果传入的是 DataFrame,需要指定 axis 参数。axis=1(默认)表示对每行做中性化(通常用于行是股票,列是时间的情况,或者列是不同因子的情况,具体取决于数据结构)。但在处理单因子截面数据时,通常传入 Series(索引为股票代码)最为直观。
Q: 中性化处理后的数值代表什么?
A: 处理后的数值是原始因子对行业哑变量和市值变量做线性回归后的残差(Residuals)。它代表了剔除行业平均水平和市值影响后,该股票特有的因子暴露部分。数值通常围绕 0 波动,正值代表高于预期,负值代表低于预期。