4. 单因子构建与测试(下):动量因子、质量因子、成长因子的构建与回测

上一节我们把价值因子和规模因子拆了个底朝天。这一节,咱们继续干剩下的三个:动量、质量、成长。这三个因子,说实话,在实战中比价值因子更「挑行情」。用好了,收益曲线漂亮得很;用不好,回撤能让你怀疑人生。

我个人习惯把这三个因子归为一类——「趋势与质地」因子。它们不像价值因子那样「捡烟蒂」,而是更关注股票本身的动能和基本面质量。嗯,咱们一个一个来。

4.1 动量因子:趋势是你的朋友,但别太晚下车

动量因子,说白了就是「强者恒强」。过去涨得好的股票,未来一段时间大概率继续涨。这个逻辑在A股有效吗?我做过测试,短期动量(1-3个月)在A股确实有效,但换手率极高,交易成本能吃掉大部分收益。

我建议用中期动量,也就是过去6-12个月的累计收益率。为什么?因为中期动量更稳定,不容易被短期噪音干扰。我在项目中遇到过,用3个月动量做轮动,结果频繁被「假突破」骗进去,手续费交得比收益还多。

动量因子的构建其实很简单:

import pandas as pd
import numpy as np

def calc_momentum_factor(df, window=252):
    """
    计算动量因子:过去252个交易日的累计收益率
    df: 包含'close'列的DataFrame
    """
    # 计算日收益率
    df['daily_ret'] = df['close'].pct_change()
    
    # 滚动窗口累计收益率
    df['momentum'] = (1 + df['daily_ret']).rolling(window=window).apply(
        lambda x: np.prod(x) - 1, raw=True
    )
    
    # 注意:要跳过最近20个交易日,避免短期反转效应
    df['momentum_clean'] = df['momentum'].shift(20)
    
    return df

这里有个坑,我必须提醒你。动量因子天然存在「反转效应」——涨多了会跌。所以构建动量因子时,一定要跳过最近1个月的数据。我曾经没做这个处理,结果回测曲线漂亮得很,实盘一跑就崩。为什么?因为回测时用了未来信息,把短期反转当成了动量。

避坑指南:动量因子必须做「滞后处理」。我建议至少滞后20个交易日,把最近一个月的收益剔除掉。否则你抓到的不是动量,是「追涨杀跌」。

4.2 质量因子:好公司不等于好股票

质量因子,衡量的是公司的「质地」。ROE高、负债率低、盈利稳定——这些指标听起来很美。但我要泼一盆冷水:质量因子单独使用,效果其实一般。

为什么?因为市场已经给好公司定价了。你想想看,茅台ROE常年30%+,但它的股价已经反映了这个预期。质量因子的真正价值,在于和其他因子组合使用。比如质量+价值,或者质量+动量,效果会好很多。

我常用的质量因子组合:

def calc_quality_factor(df_financial):
    """
    计算综合质量因子
    包含:ROE、毛利率、资产负债率、盈利稳定性
    """
    # ROE:净资产收益率
    df_financial['roe'] = df_financial['net_profit'] / df_financial['equity']
    
    # 毛利率
    df_financial['gross_margin'] = df_financial['gross_profit'] / df_financial['revenue']
    
    # 资产负债率(负向指标)
    df_financial['debt_ratio'] = df_financial['total_liab'] / df_financial['total_assets']
    
    # 盈利稳定性:过去5年ROE的标准差(越小越好)
    df_financial['roe_stability'] = df_financial.groupby('stock_code')['roe'].rolling(20).std().values
    
    # 综合打分:百分位标准化后等权相加
    for col in ['roe', 'gross_margin']:
        df_financial[f'{col}_rank'] = df_financial[col].rank(pct=True)
    
    # 负债率反向排名
    df_financial['debt_ratio_rank'] = 1 - df_financial['debt_ratio'].rank(pct=True)
    
    # ROE稳定性反向排名
    df_financial['roe_stability_rank'] = 1 - df_financial['roe_stability'].rank(pct=True)
    
    # 等权合成
    df_financial['quality_score'] = (
        df_financial['roe_rank'] + 
        df_financial['gross_margin_rank'] + 
        df_financial['debt_ratio_rank'] + 
        df_financial['roe_stability_rank']
    ) / 4
    
    return df_financial

个人经验:质量因子在熊市里特别管用。市场下跌时,资金会往「确定性」上靠。好公司的抗跌性明显强于垃圾股。我2018年做回测,质量因子多头的最大回撤比市场低了将近10个百分点。

4.3 成长因子:高增长背后的陷阱

成长因子,看的是营收增速、利润增速。这个因子很诱人——谁不喜欢高增长的公司呢?但我要告诉你,成长因子是「双刃剑」。

高增长的公司往往估值也高。一旦增速不及预期,股价会跌得很惨。我见过太多人追高成长股,结果业绩变脸,一天跌掉20%。所以成长因子一定要和估值因子配合使用。

成长因子的构建:

def calc_growth_factor(df_financial):
    """
    计算成长因子
    核心指标:营收增速、净利润增速、超预期增长
    """
    # 营收同比增长率
    df_financial['revenue_growth'] = df_financial.groupby('stock_code')['revenue'].pct_change(4)
    
    # 净利润同比增长率
    df_financial['profit_growth'] = df_financial.groupby('stock_code')['net_profit'].pct_change(4)
    
    # 超预期增长:实际增速 vs 分析师预期
    # 这里简化处理,用过去4个季度的平均增速作为预期
    df_financial['expected_growth'] = df_financial.groupby('stock_code')['profit_growth'].rolling(4).mean().values
    df_financial['surprise_growth'] = df_financial['profit_growth'] - df_financial['expected_growth']
    
    # 合成成长因子
    for col in ['revenue_growth', 'profit_growth', 'surprise_growth']:
        df_financial[f'{col}_rank'] = df_financial[col].rank(pct=True)
    
    df_financial['growth_score'] = (
        df_financial['revenue_growth_rank'] + 
        df_financial['profit_growth_rank'] + 
        df_financial['surprise_growth_rank']
    ) / 3
    
    return df_financial

核心要点:成长因子最怕「增速拐点」。一个公司从50%增速降到30%,虽然还是高增长,但股价可能腰斩。所以做成长因子时,我建议关注「增速变化的方向」,而不仅仅是增速的绝对值。

4.4 三因子回测框架

好了,三个因子都构建完了。咱们把它们放到一个回测框架里跑一跑。我习惯用分层回测法——把股票按因子值分成10组,看多空组合的表现。

def factor_backtest(df, factor_col, n_groups=10):
    """
    单因子分层回测
    """
    results = []
    
    for date in df['date'].unique():
        df_date = df[df['date'] == date].copy()
        
        # 按因子值分组
        df_date['group'] = pd.qcut(df_date[factor_col], n_groups, labels=False)
        
        # 计算每组下期收益
        for g in range(n_groups):
            group_stocks = df_date[df_date['group'] == g]['stock_code']
            next_ret = df[df['stock_code'].isin(group_stocks) & 
                         (df['date'] == next_date)]['return'].mean()
            results.append({
                'date': date,
                'group': g,
                'return': next_ret
            })
    
    return pd.DataFrame(results)

跑完回测,你会发现一个有意思的现象:动量因子在牛市中表现最好,质量因子在熊市中抗跌,成长因子在震荡市中容易「翻车」。这就是为什么我说,没有万能的因子,只有合适的场景。

我的建议:别单独用任何一个因子。把动量、质量、成长组合起来,做一个「复合因子」。比如动量占40%,质量占30%,成长占30%。这样在不同市场环境下,总有一个因子在起作用。

4.5 本章小结

这一节我们干了三件事:

  • 动量因子:中期动量有效,记得滞后处理
  • 质量因子:单独用效果一般,组合用才是王道
  • 成长因子:高增长是双刃剑,小心增速拐点

嗯,这三个因子构建起来不难,难的是怎么用。我见过太多人把因子库建得满满当当,结果实盘一跑就亏钱。为什么?因为因子之间会互相干扰,组合方式不对,反而会放大风险。

下一节,咱们聊聊因子组合的「化学反应」——怎么把多个因子合成一个有效的选股模型。到时候我会分享一个我用了三年的组合框架,保证让你少走弯路。

单因子构建与测试核心框架 动量因子 中期动量(6-12月) 滞后20日处理 避免反转效应 质量因子 ROE/毛利率/负债率 盈利稳定性 熊市抗跌性强 成长因子 营收/利润增速 超预期增长 警惕增速拐点 分层回测框架 按因子值分10组 → 计算每组下期收益 → 多空组合表现 关键指标:年化收益、最大回撤、夏普比率、IC值 因子有效性评估 动量:牛市最强 | 质量:熊市抗跌 | 成长:震荡市需谨慎

专注资料整理