3、基础因子构建:动量因子、反转因子、波动率因子

因子挖掘这件事,说白了就是找规律。你想想看,股票涨跌有没有惯性?跌多了会不会反弹?波动大的票是不是更容易出事?这三个问题,正好对应了我们今天要聊的三个基础因子:动量、反转、波动率。

我个人习惯把这三个因子称为「三板斧」。别看它们基础,我在实盘项目中见过太多人把简单的东西搞复杂了。嗯,咱们今天就把它彻底讲透。

3.1 动量因子:追涨杀跌的数学表达

动量因子的核心思想很简单:过去涨得好的股票,未来一段时间可能继续涨。这不是玄学,背后有行为金融学的解释——投资者存在反应不足和羊群效应。

核心公式:

动量因子 = (当前收盘价 - N日前收盘价) / N日前收盘价

或者更常用的:动量因子 = ln(当前收盘价 / N日前收盘价)

我在项目中遇到过一个问题:到底用多少天算动量最合适?有人用20天,有人用60天。我的经验是——先别纠结,把多个窗口都算出来

import pandas as pd
import numpy as np

def calc_momentum(close, window=20):
    """
    计算动量因子
    close: 收盘价序列
    window: 回溯窗口
    """
    # 我习惯用对数收益率,分布更对称
    momentum = np.log(close / close.shift(window))
    return momentum

# 多窗口动量
def calc_multi_momentum(close, windows=[5, 10, 20, 60]):
    df = pd.DataFrame(index=close.index)
    for w in windows:
        df[f'momentum_{w}d'] = calc_momentum(close, w)
    return df

避坑指南:

我曾经在回测中发现动量因子在牛熊转换期表现极差。后来加了「市场状态过滤」——当大盘处于下跌趋势时,降低动量因子的权重。这个改动让夏普比率提升了0.3。

3.2 反转因子:跌多了就会涨吗?

反转因子和动量因子看起来相反,但逻辑完全不同。反转因子捕捉的是过度反应后的价格修复。说白了,就是市场有时候反应过度了,把价格推得太高或太低,后面会往回拉。

你想想看,为什么会有反转效应?因为散户容易追涨杀跌,机构投资者会在极端位置反向操作。我见过一个有趣的案例:某只股票连续跌停5天后,第6天开盘又跌了7%,但当天收盘竟然翻红了。这就是典型的短期反转。

反转因子构建:

短期反转 = -1 × 过去5日收益率

中期反转 = -1 × 过去20日收益率

注意:取负号是因为我们要做多跌得多的股票

def calc_reversal(close, window=5):
    """
    计算反转因子
    返回值越大,表示反转信号越强
    """
    ret = close.pct_change(window)
    # 取负号:过去跌得多 → 因子值大 → 预期未来上涨
    reversal = -ret
    return reversal

# 我习惯做标准化,方便不同窗口比较
def standardize_factor(factor):
    return (factor - factor.mean()) / factor.std()

重要提醒:

反转因子在流动性差的股票上效果很差。我曾经踩过这个坑——小市值股票的反转信号看起来很强,但实际交易时滑点直接把收益吃掉了。建议加上流动性过滤,比如只交易日均成交额在1亿以上的股票。

3.3 波动率因子:风险本身就是信号

波动率因子很有意思。传统金融学认为高风险高收益,但实际市场中,高波动率的股票往往表现更差。这就是所谓的「低波动异象」。

为什么会这样?我个人理解是:高波动股票吸引了太多投机资金,这些资金来得快去得也快,导致价格被扭曲。而低波动股票多为机构重仓,走势更稳健。

波动率因子构建方法:

方法 公式 特点
历史波动率 std(ln(P_t / P_{t-1}), window) 简单直观,最常用
已实现波动率 sum(R_i^2) 日内高频 更精确,需要高频数据
振幅波动率 (High - Low) / Close 计算简单,适合日频
def calc_volatility(close, window=20):
    """
    计算历史波动率
    """
    log_ret = np.log(close / close.shift(1))
    volatility = log_ret.rolling(window).std() * np.sqrt(252)  # 年化
    return volatility

def calc_amplitude(high, low, close):
    """
    计算振幅波动率
    我在实盘中喜欢用这个,因为它对跳空更敏感
    """
    amplitude = (high - low) / close
    return amplitude

实战技巧:

我建议把波动率因子和动量因子结合起来用。比如:低波动 + 强动量的组合,往往能选出既稳健又有上涨动力的股票。我在一个CTA策略中用了这个组合,年化收益提升了8%。

3.4 三因子的协同与冲突

这三个因子不是孤立的。它们之间有关系,有时候还会打架。我画了一张图来说明它们的关系:

动量因子 趋势跟踪 反转因子 均值回归 波动率因子 风险度量 动量+反转 短期反转 长期动量 反转+波动 极端反转 动量+波动 低波动量 三因子 协同组合 三因子关系图:重叠区域表示因子协同效应

从图中可以看到:

  • 动量 + 反转:短期反转和长期动量可以共存。比如一只股票短期跌多了(反转信号),但中期趋势还在(动量信号),这时候要小心判断。
  • 反转 + 波动:高波动环境下的反转信号更可靠。因为波动大说明市场分歧大,反转的概率更高。
  • 动量 + 波动:低波动环境下的动量信号更稳定。高波动动量容易被打脸。

组合使用注意事项:

千万不要简单地把三个因子等权相加。我试过,效果很差。正确的做法是:先做因子正交化处理,再用IC加权或者机器学习方法确定权重。这个我们后面章节会详细讲。

3.5 实战中的坑与对策

讲了这么多理论,我来分享几个实战中踩过的坑:

  1. 幸存者偏差:回测时用了现在的股票池,但历史上很多股票已经退市了。我建议用「全历史样本」,包括已经退市的股票。
  2. 前视偏差:计算因子时不小心用了未来的数据。比如用当天的收盘价算当天的因子,这在实盘中是做不到的。记住:因子计算只能用过去的数据
  3. 行业中性化:不同行业的波动率差异很大。银行股波动率天然低,科技股波动率高。如果不做行业中性化,选出来的全是银行股。
def industry_neutralize(factor, industry):
    """
    行业中性化处理
    factor: 原始因子值
    industry: 行业分类
    """
    # 对每个行业做标准化
    neutralized = factor.groupby(industry).transform(
        lambda x: (x - x.mean()) / x.std()
    )
    return neutralized

我的建议:

刚开始做因子挖掘的同学,先把这三个基础因子吃透。不要一上来就搞复杂的机器学习因子。我见过太多人把简单问题复杂化,结果基础因子都没做好。记住:80%的收益来自20%的基础因子

好了,这一章的内容就到这里。这三个因子虽然基础,但它们是整个因子挖掘体系的基石。下一章我们会讲如何对这些因子进行有效性检验,包括IC分析、分层回测等。嗯,到时候见。

专注资料整理