halo 的技术博客

返回

量化因子挖掘是量化投资最核心的环节之一。简单来说,因子就是能够解释和预测股票收益的变量——比如市盈率、动量、波动率等。而”因子挖掘”就是系统性地发现、验证和组合这些变量的过程。

数据清洗:一切因子的地基#

从业者常说”garbage in, garbage out”。量化因子挖掘的第一步不是找因子,而是洗数据。

一个典型的量化数据清洗流程包括以下环节:

缺失值处理。A股财报数据经常出现季度缺失,不能简单删除或用均值填充。实践中,我们通常用前向填充(forward fill)配合行业均值补充交叉使用——对周期性指标(如ROE)用前向填充,对截面可比指标(如市值)用行业均值。

异常值处理。MAD(Median Absolute Deviation)法比Z-score更稳健。我习惯的做法是:先对每个截面的因子值做5倍MAD以外的缩尾处理(winsorize),再做行业市值中性化,最后做标准化。这个三板斧做下来,大部分因子值都处于合理范围。

存活者偏差。这是很多新手容易踩的坑。如果你只拿当前还活着的股票做回测,你会高估收益约15%-30%。必须使用”point-in-time”数据——即回溯到每个历史时刻,使用当时实际存在的股票池。

# 存活者偏差修正示意
def clean_universe(df, trade_date):
    """使用当时实际上市且未退市的股票"""
    mask = (
        (df['listed_date'] <= trade_date) &
        ((df['delisted_date'].isna()) | (df['delisted_date'] > trade_date))
    )
    return df[mask]
python

因子构建:从简单到复杂#

因子构建的路线图大致可以分三个层次:

第一层:基础财务因子。市盈率(PE)、市净率(PB)、ROE、毛利率、营收增速等。这些因子大家都用,关键是看你怎么用——是原始值、同比变化、还是三年均值?参数的选择往往决定因子的效果。

第二层:衍生计算因子。比如用应收账款周转天数加上存货周转天数构造”运营效率因子”,或者把多个估值指标做等权排名加总成”综合估值因子”。这个层次的创造力空间很大。

第三层:另类数据因子。舆情情感得分、供应链关系图谱、卫星图像识别的零售流量等。目前国内比较成熟的是新闻舆情和龙虎榜数据。

一个有意思的发现是:在A股市场,预期数据(分析师一致预期)构造的因子远比历史数据因子效果好。比如,一致预期EPS变化率因子的IC均值能达到0.04-0.06,而历史EP因子的IC均值只有0.02左右。这说明A股市场price in预期信息的速度非常快。

量化因子分析

因子检验:IC、IR与分组回测#

构建出一个因子后,需要经过严格的统计检验:

IC(Information Coefficient) 衡量因子值与未来收益的相关性。Rank IC比Pearson IC更稳健,不容易被极端值扭曲。Rank IC > 0.03且IC_IR > 0.5是及格线。

分层回测。把股票按因子值分成5-10组,观察各组未来收益的单调性。理想的分层图是”阶梯状”的——第1组(最便宜/最强动量)收益最高,第10组最低,且间距均匀。

换手率与交易成本。一个IC高达0.08但因换手率200%导致扣费后收益归零的因子,不如一个IC只有0.03但换手率20%的因子。实盘中,交易成本会吃掉因子收益的30%-50%。

因子合成与组合优化#

单个因子很难持续有效。多因子合成通常用以下方法:

  • 等权加总:最简单,但对噪音因子没有惩罚
  • IC加权:用历史IC作为权重,有一定过拟合风险
  • 机器学习:用XGBoost、LightGBM做非线性合成,是目前业界主流

合成后的因子做组合优化时,要注意约束条件:个股上限、行业偏离度、风格暴露等。一个常见的错误是回测做得很好,但组合优化引入了不可控的空头暴露,实盘完全不能做。

多因子回测

写在最后#

因子挖掘是一门手艺活——既需要统计学的严谨,也需要对市场的直觉。数据清洗决定了因子的下限,因子构建能力决定了上限,而因子合成与风控决定了能不能活着赚到钱。

对于初学者,我的建议是:先用3个月把数据清洗流程做到极致,再用单因子检验的方法论把市面上所有经典因子复现一遍,最后才开始尝试自己构建因子。慢就是快。

量化因子挖掘实战:从数据清洗到Alpha信号的完整路径
https://blog.halo26812.eu.org/blog/quant-factor-mining-guide
Author halo
Published at 2026年7月10日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨