Skip to content

多因子选股入门:用多个指标综合选出好股票

如果你在选股时只看市盈率,可能会挑到一堆"价值陷阱"——便宜但持续下跌的股票。如果只看涨势,又可能追高被套。单独使用任何一个指标都有盲区。多因子选股的核心思想是:不依赖单一指标,而是将多个维度的信息综合起来,做出更全面的判断

多因子模型是现代量化投资的基石。从学术界的 Fama-French 三因子模型到业界实用的多因子打分体系,因子投资已经成为机构投资者最主流的方法论之一。本文将带你系统理解多因子模型的基本概念和构建方法。

什么是因子

因子(Factor)是对股票未来收益率具有预测能力的变量。简单来说,如果某个指标能够系统性地将好股票和差股票区分开来,这个指标就是一个因子。

举个例子:低市盈率的股票整体表现好于高市盈率的股票,那么"市盈率"就是一个因子。小市值股票整体表现好于大市值股票,那么"市值"也是一个因子。

一个有效的因子需要满足以下条件:

  • 持续性:不是偶然现象,而是在较长时间段内反复出现
  • 可解释性:背后有合理的经济学或行为学解释
  • 可操作性:能够被系统化地计算和执行
  • 显著性:统计上显著,而非来自随机噪声

因子的分类

学术界和业界对因子的分类方式很多,最常见的框架是按照经济含义分为以下几大类:

价值因子(Value)

价值因子衡量股票的"便宜程度"。核心逻辑是:价格低于内在价值的股票,长期来看会向价值回归,产生超额收益。

常见指标:

  • 市盈率(PE):股价 / 每股收益,越低越"便宜"
  • 市净率(PB):股价 / 每股净资产,越低越"便宜"
  • 市销率(PS):股价 / 每股营收
  • 股息率(Dividend Yield):每股分红 / 股价,越高越好
  • EV/EBITDA:企业价值 / 息税折旧前利润
python
def calc_value_factors(df: pd.DataFrame) -> pd.DataFrame:
    """计算价值因子"""
    df['pe_ratio'] = df['market_cap'] / df['net_income']
    df['pb_ratio'] = df['market_cap'] / df['total_equity']
    df['dividend_yield'] = df['dividend_per_share'] / df['close']
    # EP 和 BP 是 PE 和 PB 的倒数,更便于因子分析(越高越好)
    df['ep'] = df['net_income'] / df['market_cap']
    df['bp'] = df['total_equity'] / df['market_cap']
    return df

价值因子的风险在于"价值陷阱"——有些股票便宜是因为基本面在恶化,市场定价是合理的,此时买入反而会亏钱。

动量因子(Momentum)

动量因子衡量股票过去一段时间的价格趋势强度。核心逻辑是"强者恒强"——过去表现好的股票,短期内倾向于继续表现好。

常见指标:

  • 过去N个月收益率:最直接的动量度量
  • 风险调整动量:收益率 / 波动率
  • 跳月动量:跳过最近1个月,取更长期的动量
python
def calc_momentum_factors(df: pd.DataFrame, periods: list = [20, 60, 120]) -> pd.DataFrame:
    """计算动量因子"""
    df = df.sort_values(['code', 'date'])
    for p in periods:
        df[f'mom_{p}d'] = df.groupby('code')['close'].transform(
            lambda x: x / x.shift(p) - 1
        )
    return df

关于动量因子的详细内容,请参考动量策略一文。

质量因子(Quality)

质量因子衡量公司的盈利质量和财务健康程度。核心逻辑是:盈利稳定、财务结构健康的公司,长期来看更可能持续创造价值。

常见指标:

  • ROE(净资产收益率):净利润 / 净资产,衡量资本使用效率
  • ROA(总资产收益率):净利润 / 总资产
  • 毛利率稳定性:毛利率的波动率越低越好
  • 资产负债率:适度偏低为佳
  • 应计利润:经营现金流与净利润的差异,越小越好
python
def calc_quality_factors(df: pd.DataFrame) -> pd.DataFrame:
    """计算质量因子"""
    df['roe'] = df['net_income'] / df['total_equity']
    df['roa'] = df['net_income'] / df['total_assets']
    df['gross_margin'] = (df['revenue'] - df['cost_of_goods']) / df['revenue']
    df['accrual'] = (df['net_income'] - df['operating_cashflow']) / df['total_assets']
    return df

质量因子在A股市场近年来受到越来越多关注,因为A股市场存在较多财务质量参差不齐的公司,质量筛选具有较大的区分能力。

规模因子(Size)

规模因子衡量公司的市值大小。经典研究发现,小市值股票整体表现优于大市值股票,这被称为"小市值效应"。

常见指标:

  • 总市值的对数
  • 流通市值的对数
python
def calc_size_factor(df: pd.DataFrame) -> pd.DataFrame:
    """计算规模因子"""
    df['log_market_cap'] = np.log(df['market_cap'])
    df['log_float_cap'] = np.log(df['float_market_cap'])
    return df

在A股市场,小市值效应长期存在但波动较大。2017年以来的市场结构性变化使得大盘蓝筹在某些阶段表现更好,规模因子的表现出现了明显的阶段性特征。此外,小市值股票的流动性风险、壳价值变动等因素都需要在因子分析中加以考虑。

波动率因子(Volatility)

波动率因子衡量股票价格的波动程度。大量研究发现,低波动率股票的长期收益优于高波动率股票,这被称为"低波动率异象"。

常见指标:

  • 历史波动率:过去N天日收益率的标准差
  • 特质波动率:CAPM回归残差的标准差
  • Beta:股票对市场指数的敏感度
python
def calc_volatility_factors(df: pd.DataFrame, window: int = 60) -> pd.DataFrame:
    """计算波动率因子"""
    df = df.sort_values(['code', 'date'])
    df['daily_ret'] = df.groupby('code')['close'].pct_change()
    df['hist_vol'] = df.groupby('code')['daily_ret'].transform(
        lambda x: x.rolling(window).std() * np.sqrt(252)
    )
    return df

低波动率异象存在的原因可能包括:投资者对高波动率股票的过度偏好(彩票偏好)、机构投资者的基准跟踪约束、以及杠杆约束等。

其他常见因子

除了上述五大类之外,还有一些在A股市场受到关注的因子:

  • 流动性因子:换手率、Amihud非流动性指标等
  • 成长因子:营收增长率、利润增长率、分析师预期修正等
  • 技术因子:均线偏离度、RSI、MACD等技术指标衍生出的因子
  • 情绪因子:融资融券余额变化、北向资金流向等
  • 另类因子:新闻情感分析、供应链关系、管理层特征等

单因子测试

在将多个因子组合之前,必须先逐一检验每个因子的有效性。单因子测试是因子研究的基础环节。

分层回测法

分层回测是最直观的单因子测试方法。具体步骤如下:

  1. 在每个换仓日,将所有股票按因子值排序
  2. 等分为N组(通常为5组或10组)
  3. 构建每组股票的等权或市值加权组合
  4. 计算每组在未来一段时间的收益
  5. 观察:因子值从低到高,组合收益是否单调递增或递减

如果第一组(因子值最低)到第五组(因子值最高)的收益呈现单调变化,说明该因子具有区分能力。

python
def single_factor_backtest(df: pd.DataFrame, factor_col: str,
                           n_groups: int = 5, holding_days: int = 20) -> pd.DataFrame:
    """单因子分层回测"""
    dates = sorted(df['date'].unique())
    rebalance_dates = dates[::holding_days]

    group_returns = {i: [] for i in range(1, n_groups + 1)}

    for i in range(len(rebalance_dates) - 1):
        rb_date = rebalance_dates[i]
        next_rb = rebalance_dates[i + 1]

        daily = df[df['date'] == rb_date].dropna(subset=[factor_col])
        if len(daily) < n_groups * 10:
            continue

        daily['group'] = pd.qcut(daily[factor_col], n_groups, labels=False, duplicates='drop') + 1

        for g in range(1, n_groups + 1):
            stocks = daily[daily['group'] == g]['code'].tolist()
            if not stocks:
                continue

            # 计算该组持有期收益
            hold_data = df[(df['date'] > rb_date) & (df['date'] <= next_rb)]
            hold_data = hold_data[hold_data['code'].isin(stocks)]

            ret = hold_data.groupby('date')['close'].pct_change().mean()
            group_returns[g].append(ret.mean())

    # 汇总结果
    result = pd.DataFrame({
        f'Group_{g}': [np.mean(group_returns[g])] for g in range(1, n_groups + 1)
    }, index=['平均收益'])

    return result

IC信息系数

IC(Information Coefficient)是因子值与未来收益率的横截面相关系数。它是衡量因子预测能力最常用的统计指标。

  • IC:因子值与下期收益率的 Pearson 或 Spearman 相关系数
  • IC均值:所有截面IC的算术平均,绝对值越大越好
  • ICIR:IC均值 / IC标准差,衡量因子预测能力的稳定性
  • IC胜率:IC为正(或为负,取决于因子方向)的期数占比

判断标准(经验值):

指标较弱一般较强很强
IC均值绝对值<0.020.02-0.050.05-0.10>0.10
ICIR<0.20.2-0.50.5-1.0>1.0
IC胜率<52%52%-55%55%-60%>60%
python
def calc_ic(df: pd.DataFrame, factor_col: str, return_col: str = 'forward_ret') -> dict:
    """计算因子IC序列"""
    dates = sorted(df['date'].unique())
    ic_series = []

    for date in dates:
        daily = df[df['date'] == date].dropna(subset=[factor_col, return_col])
        if len(daily) < 30:
            continue
        # Spearman秩相关更稳健
        ic = daily[factor_col].corr(daily[return_col], method='spearman')
        ic_series.append({'date': date, 'ic': ic})

    ic_df = pd.DataFrame(ic_series)

    return {
        'IC均值': ic_df['ic'].mean(),
        'IC标准差': ic_df['ic'].std(),
        'ICIR': ic_df['ic'].mean() / ic_df['ic'].std() if ic_df['ic'].std() != 0 else 0,
        'IC胜率': (ic_df['ic'] > 0).mean(),
    }

多因子合成

当多个因子都通过有效性检验后,需要将它们综合成一个最终的选股信号。多因子的合成方法主要有以下几种:

等权合成

最简单的方法:将每个因子的排名等权相加,得到综合得分。

python
def equal_weight_score(df: pd.DataFrame, factor_cols: list) -> pd.Series:
    """等权因子合成"""
    ranks = pd.DataFrame()
    for col in factor_cols:
        ranks[col] = df[col].rank(pct=True)  # 百分位排名
    composite_score = ranks.mean(axis=1)
    return composite_score

优点:简单、不需要估计参数、不容易过拟合。 缺点:忽略了不同因子预测能力的差异。

IC加权合成

根据每个因子的历史IC表现,给予预测能力更强的因子更高的权重:

python
def ic_weighted_score(df: pd.DataFrame, factor_cols: list,
                      ic_window: int = 120) -> pd.Series:
    """IC加权因子合成"""
    # 假设已经预先计算了每个因子的IC均值
    ic_means = {}
    for col in factor_cols:
        # 简化示例:用因子与未来收益的相关系数近似IC
        ic_means[col] = abs(df[col].corr(df['forward_ret']))

    total_ic = sum(ic_means.values())
    weights = {k: v / total_ic for k, v in ic_means.items()}

    ranks = pd.DataFrame()
    for col in factor_cols:
        ranks[col] = df[col].rank(pct=True) * weights[col]

    composite_score = ranks.sum(axis=1)
    return composite_score

优点:考虑了因子的相对重要性,适应因子强弱变化。 缺点:IC估计本身有噪声,权重可能不稳定。

最优化加权

通过最大化组合收益(或最小化风险、最大化信息比率)来求解最优权重。这类方法通常使用历史数据进行优化:

python
from scipy.optimize import minimize

def optimized_weights(returns_matrix: pd.DataFrame, factor_returns: pd.DataFrame) -> np.ndarray:
    """最优化因子权重:最大化夏普比率"""
    n_factors = factor_returns.shape[1]

    def neg_sharpe(w):
        port_ret = factor_returns.values @ w
        return -port_ret.mean() / port_ret.std() if port_ret.std() > 0 else 0

    constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]
    bounds = [(0, 1)] * n_factors
    x0 = np.ones(n_factors) / n_factors

    result = minimize(neg_sharpe, x0, method='SLSQP',
                      bounds=bounds, constraints=constraints)
    return result.x

优点:理论上最优。 缺点:极易过拟合历史数据,对未来数据的适应性不确定。需要严格的样本外验证。

合成方法选择建议

方法适用场景复杂度过拟合风险
等权因子数量少、初期研究
IC加权因子预测能力差异大
最优化数据充足、有严格验证流程

对于初学者和大多数实际应用场景,建议从等权合成开始,在积累足够经验后再尝试更复杂的加权方法。无论选择哪种方法,都必须进行严格的样本外测试。

多因子模型的构建流程

将以上内容串联起来,一个完整的多因子选股模型构建流程如下:

  1. 因子定义与计算:根据经济逻辑定义因子,获取数据并计算因子值
  2. 因子预处理:去极值、标准化、缺失值处理(详见因子构建与筛选
  3. 单因子测试:通过分层回测和IC分析验证每个因子的有效性
  4. 因子相关性分析:检查因子之间是否高度相关,避免信息重复
  5. 因子合成:选择合成方法,将多个因子综合为一个选股信号
  6. 组合构建:根据综合得分构建股票组合
  7. 回测评估:全面评估策略的风险收益特征(详见因子回测与评估
  8. 实盘跟踪:小仓位验证后逐步放大

因子相关性问题

在多因子模型中,如果两个因子高度相关(比如PE和PB),那么等权合成会实际上给"价值"这个维度双倍的权重。这可能导致模型在某些市场环境下过度暴露于某个维度。

python
def check_factor_correlation(df: pd.DataFrame, factor_cols: list) -> pd.DataFrame:
    """检查因子间相关性"""
    corr_matrix = df[factor_cols].corr(method='spearman')
    return corr_matrix

处理高相关因子的方法:

  • 保留最优:在相关因子中只保留IC最高的那个
  • 正交化:将一个因子对另一个因子做回归,取残差作为新因子
  • 主成分分析:通过PCA降维,提取不相关的主成分

常见误区

在多因子选股的实践中,初学者容易陷入以下误区:

1. 因子越多越好

并非如此。增加一个弱因子不会提升模型表现,反而可能引入噪声。一般而言,5-15个有效且低相关的因子已经足够构建一个好的多因子模型。

2. 忽视因子相关性

将多个高度相关的因子放在一起,表面上看模型更全面,实际上只是重复计算了同一个维度的信息。

3. 过度优化

通过不断调整因子权重来最大化历史收益,是最常见的过拟合陷阱。一个好的模型应该在参数小幅变化时保持稳定。

4. 忽略交易成本

多因子策略的换仓频率可能很高,特别是小因子周期较短时。回测中必须扣除合理的交易成本(包括佣金、印花税和滑点),否则结果严重失真。

总结

多因子选股是量化投资的核心方法论。它通过综合多个维度的信息,克服了单一指标的局限性,能够更全面地评估股票的投资价值。掌握因子的定义、分类、单因子测试和多因子合成,是构建量化选股体系的基础。

本文仅为教学目的,不构成任何投资建议。因子模型的有效性会随市场环境变化,历史回测结果不代表未来表现。

仅供学习交流,不构成任何投资建议