01-经典因子体系

预计学习时间:3-4 小时

难度:⭐⭐⭐

核心问题:有哪些经过学术验证的因子能预测股票收益?它们是怎么构造的?


从一个直觉出发

假设你在超市买水果。两颗看起来差不多的苹果,一颗卖 5 块,一颗卖 10 块。你会选哪颗?

大多数人会选 5 块的那颗。因为在质量看起来一样的情况下,便宜的那个”性价比更高”。

把同样的逻辑搬到股票市场:两家业务差不多的公司,一家市值 100 亿,一家市值 10 亿。市场是不是”低估”了小公司?

这就是因子投资的底层逻辑——寻找系统性的”定价偏差”或”风险补偿”,用结构化的方法从中获利。

但”看起来被低估”和”真的被低估”之间有巨大的鸿沟。这一章的核心就是帮你建立因子投资的完整认知框架,并掌握经典因子的构造方法。


一、什么是因子?

1.1 因子的定义

因子(Factor)是一个能在截面上预测未来收益差异的变量。

白话版本:如果你按某个变量把所有股票排序,排在前面的股票和排在后面的股票在未来一段时间内的平均收益有系统性差异,那这个变量就是一个因子。

其中:

  • 是股票 的未来收益
  • 是股票 的因子值
  • 是因子收益(如果 显著不为零,因子有效)

1.2 因子 vs 信号 vs 特征

概念含义例子
因子经过理论和实证验证的、能系统预测收益的变量市值、B/P、动量
信号任何能产生交易想法的变量(未经验证的)“昨天涨停的股票”
特征股票的任何可观测属性(不一定是因子)行业、上市日期

不是所有特征都是因子,但所有因子都是特征。 因子是特征的子集——那些经过严格验证、有理论支撑的特征。

1.3 为什么需要因子框架?

如果你只是随机地尝试各种变量预测收益,本质上是在做数据挖掘——试了 1000 个变量,总有几个碰巧”显著”。

因子框架的意义在于:

  1. 理论约束:因子需要经济学理论支撑,减少数据挖掘的风险
  2. 结构化分析:不是”哪个变量有效”,而是”哪类风险有补偿”
  3. 可积累性:因子知识是可叠加的——新因子是对旧框架的补充
  4. 风险管理:理解因子暴露,才能管理组合风险

二、六大经典因子

学术界和业界经过几十年研究,公认有 6 大经典因子类别。每个因子都有大量的学术论文支撑。

2.1 价值因子(Value)

直觉

“便宜的股票长期收益更高”——因为市场过度悲观时,好公司也会被低估,等市场纠偏,你就能获利。

理论解释

  • 风险补偿:价值股可能是困境公司,投资者要求额外补偿才愿意持有
  • 行为金融:投资者过度外推过去的好/坏表现,导致成长股被高估、价值股被低估

常用指标

指标公式含义
B/P账面价值 / 市值买 1 元账面资产花多少钱
E/P净利润 / 市值买 1 元盈利花多少钱(PE 的倒数)
CF/P经营现金流 / 市值买 1 元现金流花多少钱
S/P销售收入 / 市值买 1 元收入花多少钱

Python 实现

import numpy as np
import pandas as pd
 
np.random.seed(42)
 
# ============================================================
# 模拟 300 只股票的财务数据
# ============================================================
n_stocks = 300
 
# 市值(亿元)—— 对数正态分布
market_cap = np.random.lognormal(mean=5, sigma=1.5, size=n_stocks)
 
# 账面价值、盈利、现金流、销售收入
book_value = market_cap * np.random.uniform(0.3, 1.5, n_stocks)
earnings = book_value * np.random.uniform(0.02, 0.25, n_stocks)
cash_flow = earnings * np.random.uniform(1.0, 2.0, n_stocks)
sales = book_value * np.random.uniform(0.5, 3.0, n_stocks)
 
# 构造价值因子(越大越"便宜")
bp_ratio = book_value / market_cap        # 账面市值比
ep_ratio = earnings / market_cap          # 盈利市值比
cfp_ratio = cash_flow / market_cap        # 现金流市值比
sp_ratio = sales / market_cap             # 销售市值比
 
# 综合价值因子:四个指标等权平均,再标准化
value_factors = pd.DataFrame({
    'B/P': bp_ratio,
    'E/P': ep_ratio,
    'CF/P': cfp_ratio,
    'S/P': sp_ratio
})
 
# 标准化
value_z = (value_factors - value_factors.mean()) / value_factors.std()
value_composite = value_z.mean(axis=1)
 
print("价值因子统计:")
print(f"  B/P 范围: [{bp_ratio.min():.3f}, {bp_ratio.max():.3f}]")
print(f"  E/P 范围: [{ep_ratio.min():.4f}, {ep_ratio.max():.4f}]")
print(f"  综合价值因子范围: [{value_composite.min():.2f}, {value_composite.max():.2f}]")

预期表现

  • 长期(3-5 年)价值因子有正溢价
  • 短期可能出现”价值陷阱”——便宜的公司可能一直便宜
  • 价值因子在危机后复苏期表现最好

2.2 规模因子(Size)

直觉

“小公司比大公司收益更高”——因为小公司更容易被市场忽视,信息不对称更大,投资者要求额外补偿。

理论解释

  • 风险补偿:小公司融资渠道少、经营风险大、破产概率高
  • 行为金融:分析师和机构对小公司覆盖不足,信息传播慢
  • 流动性:小公司流动性差,需要流动性溢价补偿

常用指标

指标含义
总市值公司总价值
自由流通市值剔除大股东锁定股后可交易的部分(更常用)

Python 实现

import numpy as np
import pandas as pd
 
np.random.seed(42)
n_stocks = 300
 
# 总市值
total_cap = np.random.lognormal(mean=5, sigma=1.5, size=n_stocks)
 
# 自由流通比例(大公司通常比例更高)
free_float_ratio = np.clip(
    0.3 + 0.5 * (total_cap - total_cap.min()) / (total_cap.max() - total_cap.min())
    + np.random.normal(0, 0.05, n_stocks), 0.1, 0.9)
 
free_float_cap = total_cap * free_float_ratio
 
# 规模因子通常取市值的对数(分布更对称)
size_factor = np.log(total_cap)
size_factor_ff = np.log(free_float_cap)
 
print("规模因子统计:")
print(f"  总市值范围: [{total_cap.min():.0f}, {total_cap.max():.0f}] 亿元")
print(f"  流通市值范围: [{free_float_cap.min():.0f}, {free_float_cap.max():.0f}] 亿元")
print(f"  对数市值范围: [{size_factor.min():.2f}, {size_factor.max():.2f}]")
 
# 按市值分 5 组
df_size = pd.DataFrame({'cap': total_cap, 'log_cap': size_factor})
df_size['size_group'] = pd.qcut(df_size['cap'], 5, labels=['微小', '小', '中', '大', '超大'])
print("\n市值分组:")
print(df_size.groupby('size_group')['cap'].describe()[['count', 'mean']])

预期表现

  • 小盘股长期超额收益在美股和 A 股都有验证
  • 但小盘股效应在近年来有所衰减
  • 小盘股在 1-2 月表现尤其好(“一月效应”)
  • 小盘股波动率更高、流动性更差

2.3 动量因子(Momentum)

直觉

“涨的还会涨”——趋势有惯性。或者说,“涨太多了会跌回来”——短期反转。

这是两个不同的效应,需要区分:

类型持仓期逻辑
中期动量过去 3-12 个月(剔除最近 1 个月)趋势惯性,信息逐步被市场消化
短期反转过去 1 周到 1 个月短期过度反应后的回调

理论解释

  • 信息扩散缓慢:好消息不会立刻反映在价格中,需要时间
  • 行为金融:处置效应(卖出赚钱的、持有亏钱的)导致动量
  • 风险补偿:动量股可能承担了”崩盘风险”

经典构造方法

12-1 月动量:过去 12 个月的累计收益,但剔除最近 1 个月。

为什么剔除最近 1 个月?因为存在短期反转效应——最近 1 个月涨太多的股票反而会跌回来。

Python 实现

import numpy as np
import pandas as pd
 
np.random.seed(42)
n_stocks = 300
n_days = 252  # 一年交易日
 
# ============================================================
# 模拟 300 只股票一年的日收益率
# ============================================================
# 基础收益:市场因子 + 个体特质
market_factor = np.random.normal(0.0003, 0.01, n_days)
betas = np.random.uniform(0.5, 1.5, n_stocks)
 
daily_returns = np.zeros((n_days, n_stocks))
for s in range(n_stocks):
    daily_returns[:, s] = (betas[s] * market_factor
                           + np.random.normal(0, 0.02, n_days))
 
# 转成 DataFrame
dates = pd.date_range('2024-01-01', periods=n_days, freq='B')
stock_names = [f'S{s+1:03d}' for s in range(n_stocks)]
returns_df = pd.DataFrame(daily_returns, index=dates, columns=stock_names)
 
# ============================================================
# 计算动量因子
# ============================================================
# 累计收益
cum_returns = (1 + returns_df).cumprod()
 
# 12-1 月动量:过去 252 天的累计收益 - 过去 21 天的累计收益
# 实际操作中:过去 252 天到过去 21 天的收益
momentum_12_1 = cum_returns.shift(21) / cum_returns.shift(252) - 1
 
# 短期反转:过去 5 天的收益取负
reversal_5d = -returns_df.rolling(5).sum()
 
# 标准化
mom_factor = (momentum_12_1.iloc[-1] - momentum_12_1.iloc[-1].mean()
              ) / momentum_12_1.iloc[-1].std()
rev_factor = (reversal_5d.iloc[-1] - reversal_5d.iloc[-1].mean()
              ) / reversal_5d.iloc[-1].std()
 
print("动量因子统计(最近一天):")
print(f"  12-1 月动量范围: [{momentum_12_1.iloc[-1].min():.3f}, "
      f"{momentum_12_1.iloc[-1].max():.3f}]")
print(f"  短期反转范围: [{reversal_5d.iloc[-1].min():.3f}, "
      f"{reversal_5d.iloc[-1].max():.3f}]")
print(f"\n  动量因子最高的 5 只: "
      f"{momentum_12_1.iloc[-1].nlargest(5).index.tolist()}")
print(f"  动量因子最低的 5 只: "
      f"{momentum_12_1.iloc[-1].nsmallest(5).index.tolist()}")

预期表现

  • 中期动量是最稳健的因子之一,年化超额收益 5-10%
  • 但动量崩溃(Momentum Crash)风险极大——在市场反转时,动量策略可能巨亏
  • 短期反转在日频/周频策略中有效
  • 动量和反转可以共存:中期看趋势,短期看反转

2.4 质量因子(Quality)

直觉

“好公司”的股票应该有更好的表现——盈利能力强、财务健康、利润真实。

常用指标

指标公式含义
ROE净利润 / 净资产每一块钱股东权益能赚多少钱
毛利率(收入 - 成本) / 收入产品竞争力
应计项(净利润 - 经营现金流) / 总资产利润的”水分”

应计项是质量因子中最有趣的一个:利润和现金流差距越大,说明利润越”虚”(可能是应收账款增加、存货积压),公司质量越差。

Python 实现

import numpy as np
import pandas as pd
 
np.random.seed(42)
n_stocks = 300
 
# 模拟财务数据
total_assets = np.random.lognormal(mean=5, sigma=1, n_stocks)
net_income = total_assets * np.random.uniform(-0.02, 0.15, n_stocks)
equity = total_assets * np.random.uniform(0.3, 0.7, n_stocks)
revenue = total_assets * np.random.uniform(0.5, 2.0, n_stocks)
cost = revenue * np.random.uniform(0.5, 0.9, n_stocks)
operating_cf = net_income + np.random.normal(0, total_assets * 0.02, n_stocks)
 
# 构造质量因子
roe = net_income / equity                          # ROE
gross_margin = (revenue - cost) / revenue           # 毛利率
accruals = (net_income - operating_cf) / total_assets  # 应计项
 
quality_df = pd.DataFrame({
    'ROE': roe,
    '毛利率': gross_margin,
    '应计项': accruals
})
 
# 综合质量因子:ROE 和毛利率越大越好,应计项越小越好
quality_z = (quality_df - quality_df.mean()) / quality_df.std()
quality_composite = (quality_z['ROE'] + quality_z['毛利率'] - quality_z['应计项']) / 3
 
print("质量因子统计:")
print(f"  ROE 范围: [{roe.min():.3f}, {roe.max():.3f}]")
print(f"  毛利率范围: [{gross_margin.min():.3f}, {gross_margin.max():.3f}]")
print(f"  应计项范围: [{accruals.min():.3f}, {accruals.max():.3f}]")
print(f"  综合质量因子: [{quality_composite.min():.2f}, {quality_composite.max():.2f}]")

预期表现

  • 高质量公司长期跑赢低质量公司
  • 质量因子在市场下跌时防御性较强
  • 应计项因子是质量因子中最稳健的子因子

2.5 低波动因子(Low Volatility)

直觉

“低波动的股票长期收益更稳定”——高风险不一定有高收益,这是对 CAPM 最直接的挑战。

这个发现被称为**“Beta 异象””低波动异象”**,因为它直接挑战了”风险越高、收益越高”的经典理论。

理论解释

  • 杠杆约束:有些投资者只能加杠杆买高 Beta 资产,推高了高 Beta 资产价格
  • 基准竞争:基金经理倾向于买高 Beta 股票来”跟上大盘”
  • 彩票偏好:散户喜欢买”可能暴涨”的股票,推高了高波动股票价格

常用指标

指标含义
Beta相对于市场组合的系统性风险
特异波动率剔除市场影响后的波动率
历史波动率过去收益的标准差

Python 实现

import numpy as np
import pandas as pd
 
np.random.seed(42)
n_stocks = 100
n_days = 252
 
# 模拟市场收益
market_returns = np.random.normal(0.0005, 0.01, n_days)
 
# 每只股票有不同的 Beta 和特异波动率
true_betas = np.random.uniform(0.3, 1.8, n_stocks)
idio_vol = np.random.uniform(0.01, 0.04, n_stocks)
 
stock_returns = np.zeros((n_days, n_stocks))
for s in range(n_stocks):
    stock_returns[:, s] = (true_betas[s] * market_returns
                           + np.random.normal(0, idio_vol[s], n_days))
 
returns_df = pd.DataFrame(stock_returns)
 
# ============================================================
# 估计 Beta 和特异波动率
# ============================================================
betas = []
idio_vols = []
hist_vols = []
 
for s in range(n_stocks):
    y = returns_df.iloc[:, s].values
    X = np.column_stack([np.ones(n_days), market_returns])
    # OLS 回归:r_stock = alpha + beta * r_market + epsilon
    beta_hat = np.linalg.lstsq(X, y, rcond=None)[0][1]
    residuals = y - X @ np.linalg.lstsq(X, y, rcond=None)[0]
    idio_vol_hat = np.std(residuals, ddof=2)
    hist_vol = np.std(y, ddof=1)
 
    betas.append(beta_hat)
    idio_vols.append(idio_vol_hat)
    hist_vols.append(hist_vol)
 
betas = np.array(betas)
idio_vols = np.array(idio_vols)
hist_vols = np.array(hist_vols)
 
print("低波动因子统计:")
print(f"  Beta 范围: [{betas.min():.3f}, {betas.max():.3f}]")
print(f"  特异波动率范围: [{idio_vols.min():.4f}, {idio_vols.max():.4f}]")
print(f"  历史波动率范围: [{hist_vols.min():.4f}, {hist_vols.max():.4f}]")
 
# 低波动因子:取波动率的负数(波动越低,因子值越高)
low_vol_factor = -hist_vols

预期表现

  • 低波动股票长期跑赢高波动股票,但超额收益不是很大(年化 2-4%)
  • 低波动因子在市场下跌时表现极佳
  • 低波动 ≠ 低收益,这是对传统金融理论的重要修正

2.6 流动性因子(Liquidity)

直觉

“不好卖的股票,投资者需要额外补偿”——流动性差的资产持有成本高,你必须得到补偿才愿意买。

常用指标

指标公式含义
Amihud 非流动性日收益绝对值 / 成交额,越大越不流动
换手率日成交量 / 流通股本交易活跃度
买卖价差(卖一价 - 买一价) / 中间价交易成本

Python 实现

import numpy as np
import pandas as pd
 
np.random.seed(42)
n_stocks = 100
n_days = 252
 
# 模拟成交额(大公司成交额大)
avg_turnover = np.random.lognormal(mean=12, sigma=1.5, n_stocks)
daily_turnover = np.zeros((n_days, n_stocks))
for s in range(n_stocks):
    daily_turnover[:, s] = avg_turnover[s] * np.random.uniform(0.5, 1.5, n_days)
 
# 模拟日收益率
daily_returns = np.zeros((n_days, n_stocks))
for s in range(n_stocks):
    # 流动性越差的股票,波动越大(风险更大)
    vol = 0.015 + 0.01 * (np.log(avg_turnover[s]).mean() < 12)
    daily_returns[:, s] = np.random.normal(0.0005, vol, n_days)
 
# ============================================================
# 计算 Amihud 非流动性指标
# ============================================================
amihud = np.zeros(n_stocks)
avg_turnover_rate = np.zeros(n_stocks)
 
for s in range(n_stocks):
    returns = daily_returns[:, s]
    turnover = daily_turnover[:, s]
 
    # Amihud = 平均(日收益绝对值 / 成交额) * 10^6
    # 注意:分母是成交额(金额),不是成交量(股数)
    illiquidity = np.mean(np.abs(returns) / turnover) * 1e6
    amihud[s] = illiquidity
 
    # 平均换手率(简化版:日成交额 / 估计流通市值)
    avg_turnover_rate[s] = np.mean(turnover)
 
print("流动性因子统计:")
print(f"  Amihud 范围: [{amihud.min():.2f}, {amihud.max():.2f}]")
print(f"  Amihud 中位数: {np.median(amihud):.2f}")
 
# 流动性因子:Amihud 越大(越不流动),因子值越高(需要补偿)
liquidity_factor = amihud

预期表现

  • 低流动性股票长期有正溢价(年化 2-5%)
  • 但流动性溢价容易被交易成本吞噬
  • 在流动性危机时(如 2008 年),低流动性股票的溢价会急剧扩大

2.7 盈利因子(RMW)与投资因子(CMA)

这是 Fama-French 五因子模型在三因子基础上新增的两个因子。

盈利因子(Profitability / RMW)

  • Robust Minus Weak:高盈利公司 - 低盈利公司
  • 高盈利公司通常经营效率高、有定价权
  • 常用指标:ROE、毛利率、总资产收益率(ROA)

投资因子(Investment / CMA)

  • Conservative Minus Aggressive:低投资公司 - 高投资公司
  • 激进投资的公司往往是”帝国建造者”,投资效率低下
  • 常用指标:资产增长率、资本支出增长率

Python 实现

import numpy as np
import pandas as pd
 
np.random.seed(42)
n_stocks = 300
 
# 模拟盈利和投资数据
total_assets = np.random.lognormal(mean=5, sigma=1, n_stocks)
operating_income = total_assets * np.random.uniform(0, 0.2, n_stocks)
book_equity = total_assets * np.random.uniform(0.3, 0.7, n_stocks)
 
# ROA 和 ROE
roa = operating_income / total_assets
roe = operating_income / book_equity
 
# 资产增长率(本期资产 / 上期资产 - 1)
asset_growth = np.random.uniform(-0.1, 0.3, n_stocks)
 
# RMW:高盈利 - 低盈利
rmw = roa  # ROA 越大,盈利能力越强
rmw_group = pd.qcut(rmw, 3, labels=['弱盈利', '中等', '强盈利'])
 
# CMA:保守投资(低增长)- 激进投资(高增长)
cma = -asset_growth  # 资产增长率越低,越保守
cma_group = pd.qcut(asset_growth, 3, labels=['保守', '中等', '激进'])
 
print("盈利因子(RMW)统计:")
print(f"  ROA 范围: [{roa.min():.3f}, {roa.max():.3f}]")
print(rmw_group.value_counts().sort_index())
 
print("\n投资因子(CMA)统计:")
print(f"  资产增长率范围: [{asset_growth.min():.3f}, {asset_growth.max():.3f}]")
print(cma_group.value_counts().sort_index())

三、因子体系总览

3.1 八大因子对比

因子核心逻辑代表指标预期年化溢价稳健性
价值便宜的股票被低估B/P, E/P3-6%
规模小公司被忽视log(市值)2-4%
动量趋势有惯性12-1 月动量5-10%
质量好公司值得信赖ROE, 应计项2-4%中高
低波动低风险也有好收益Beta, 波动率2-4%
流动性难卖的股票需补偿Amihud2-5%
盈利高盈利公司效率高ROA, ROE2-4%中高
投资保守投资更高效资产增长率1-3%

3.2 Fama-French 因子模型演进

CAPM (1964)     →  一个因子:市场
FF3 (1993)      →  三个因子:市场 + 规模 + 价值
Carhart (1997)  →  四个因子:FF3 + 动量
FF5 (2015)      →  五个因子:FF3 + 盈利 + 投资
FF6 (2018)      →  六个因子:FF5 + 动量

四、因子构建实战:用模拟数据构建 6 个因子

下面用一个完整的例子,模拟 200 只股票 5 年的月度数据,构建 6 个因子。

import numpy as np
import pandas as pd
 
np.random.seed(42)
 
# ============================================================
# 参数设置
# ============================================================
n_stocks = 200
n_months = 60  # 5 年
 
# ============================================================
# 模拟基本面数据
# ============================================================
# 市值(稳定但有缓慢变化)
base_cap = np.random.lognormal(mean=5, sigma=1.5, n_stocks)
market_cap = np.zeros((n_months, n_stocks))
for t in range(n_months):
    # 市值有自相关性
    if t == 0:
        market_cap[t] = base_cap
    else:
        market_cap[t] = market_cap[t-1] * np.random.uniform(0.95, 1.05, n_stocks)
 
# 账面价值、盈利、现金流
book_value = market_cap * np.random.uniform(0.3, 1.5, (n_months, n_stocks))
earnings = book_value * np.random.uniform(0.02, 0.2, (n_months, n_stocks))
cash_flow = earnings * np.random.uniform(1.0, 2.0, (n_months, n_stocks))
 
# ============================================================
# 第 1 步:构建因子
# ============================================================
factors = {}
 
# 1) 价值因子:B/P
factors['value'] = book_value / market_cap
 
# 2) 规模因子:log(市值)
factors['size'] = np.log(market_cap)
 
# 3) 动量因子:过去 12 个月收益(剔除最近 1 个月)
# 先模拟月收益率
monthly_returns = np.random.normal(0.01, 0.06, (n_months, n_stocks))
prices = np.zeros((n_months, n_stocks))
prices[0] = market_cap[0]
for t in range(1, n_months):
    prices[t] = prices[t-1] * (1 + monthly_returns[t])
 
# 12-1 月动量
momentum = np.full((n_months, n_stocks), np.nan)
for t in range(12, n_months):
    # 过去 12 个月的累计收益 - 过去 1 个月的收益
    ret_12m = prices[t-1] / prices[t-12] - 1
    ret_1m = prices[t-1] / prices[t-2] - 1
    momentum[t] = ret_12m - ret_1m
factors['momentum'] = momentum
 
# 4) 质量因子:ROE
equity = book_value * np.random.uniform(0.3, 0.7, (n_months, n_stocks))
factors['quality'] = earnings / equity  # ROE
 
# 5) 低波动因子:过去 12 个月收益率标准差的负数
volatility = np.full((n_months, n_stocks), np.nan)
for t in range(12, n_months):
    volatility[t] = np.std(monthly_returns[t-11:t+1], axis=0, ddof=1)
factors['low_vol'] = -volatility  # 取负数:波动越低,因子值越高
 
# 6) 流动性因子:用换手率模拟
turnover = np.random.uniform(0.01, 0.1, (n_months, n_stocks))
factors['liquidity'] = -turnover  # 取负数:换手率越低,流动性越差
 
# ============================================================
# 第 2 步:标准化因子
# ============================================================
factor_df = pd.DataFrame()
for name, factor in factors.items():
    # 跳过前 12 个月(动量和波动率需要历史数据)
    series = pd.Series(factor[12:, 0])  # 取第一只股票的因子值示例
    factor_df[name] = series
 
print("6 个因子的描述统计(以第一只股票为例):")
print(factor_df.describe().round(4))
 
# ============================================================
# 第 3 步:截面标准化(每个月对所有股票标准化)
# ============================================================
standardized_factors = {}
for name, factor in factors.items():
    # factor shape: (n_months, n_stocks)
    std_factor = np.full_like(factor, np.nan)
    for t in range(n_months):
        valid = ~np.isnan(factor[t])
        if np.sum(valid) > 10:
            std_factor[t, valid] = (
                (factor[t, valid] - np.nanmean(factor[t, valid]))
                / np.nanstd(factor[t, valid])
            )
    standardized_factors[name] = std_factor
 
# 标准化后,每个因子的截面均值=0,标准差=1
print("\n标准化后因子值范围(以第一个有数据的月份为例,t=12):")
for name, sf in standardized_factors.items():
    vals = sf[12]
    print(f"  {name:>10}: 均值={np.nanmean(vals):.4f}, "
          f"标准差={np.nanstd(vals):.4f}, "
          f"范围=[{np.nanmin(vals):.2f}, {np.nanmax(vals):.2f}]")

五、因子相关性矩阵

因子之间往往不是独立的。如果两个因子高度相关,同时使用它们不会带来额外收益,只会增加复杂度。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
 
np.random.seed(42)
 
# ============================================================
# 模拟因子暴露(300 只股票,每个股票 6 个因子值)
# ============================================================
n_stocks = 300
 
# 各因子的基础值
value_exposure = np.random.normal(0, 1, n_stocks)
size_exposure = np.random.normal(0, 1, n_stocks)
# 规模和价值有一定负相关(小公司倾向于更"便宜")
size_exposure = -0.3 * value_exposure + np.random.normal(0, 0.95, n_stocks)
momentum_exposure = np.random.normal(0, 1, n_stocks)
# 动量和价值有负相关(价值股通常不是动量股)
momentum_exposure = -0.2 * value_exposure + np.random.normal(0, 0.98, n_stocks)
quality_exposure = np.random.normal(0, 1, n_stocks)
# 质量和价值有正相关(便宜的好公司更好)
quality_exposure = 0.25 * value_exposure + np.random.normal(0, 0.97, n_stocks)
low_vol_exposure = np.random.normal(0, 1, n_stocks)
# 低波动和规模有负相关(小公司波动更大)
low_vol_exposure = -0.35 * size_exposure + np.random.normal(0, 0.94, n_stocks)
 
factor_exposures = pd.DataFrame({
    '价值': value_exposure,
    '规模': size_exposure,
    '动量': momentum_exposure,
    '质量': quality_exposure,
    '低波动': low_vol_exposure,
    '流动性': np.random.normal(0, 1, n_stocks)  # 独立
})
 
# ============================================================
# 计算相关性矩阵
# ============================================================
corr_matrix = factor_exposures.corr()
 
print("因子相关性矩阵:")
print(corr_matrix.round(3))
 
# ============================================================
# 可视化
# ============================================================
fig, ax = plt.subplots(figsize=(8, 6))
im = ax.imshow(corr_matrix.values, cmap='RdBu_r', vmin=-1, vmax=1)
ax.set_xticks(range(len(corr_matrix.columns)))
ax.set_yticks(range(len(corr_matrix.index)))
ax.set_xticklabels(corr_matrix.columns, fontsize=10)
ax.set_yticklabels(corr_matrix.index, fontsize=10)
 
# 在每个格子里标注数值
for i in range(len(corr_matrix)):
    for j in range(len(corr_matrix)):
        text = ax.text(j, i, f'{corr_matrix.iloc[i, j]:.2f}',
                       ha="center", va="center", color="black", fontsize=10)
 
plt.colorbar(im, ax=ax)
ax.set_title('因子相关性矩阵', fontsize=14)
plt.tight_layout()
plt.show()

输出大致如下:

因子相关性矩阵:
          价值    规模    动量    质量  低波动  流动性
价值    1.000 -0.300 -0.200  0.250 -0.075  0.010
规模   -0.300  1.000  0.060 -0.075 -0.350 -0.030
动量   -0.200  0.060  1.000 -0.050  0.035  0.020
质量    0.250 -0.075 -0.050  1.000  0.018  0.015
低波动 -0.075 -0.350  0.035  0.018  1.000 -0.010
流动性  0.010 -0.030  0.020  0.015 -0.010  1.000

关键观察

  1. 价值和动量负相关:价值股通常不是动量股,这是经典的因子配置难题
  2. 规模和低波动负相关:小公司波动更大
  3. 质量和价值正相关:便宜的好公司——这是最有力的因子组合
  4. 流动性相对独立:和其他因子的相关性较低

六、因子构建中的常见问题

问题表现解决方法
前视偏差用了未来才能知道的数据严格对齐数据时间戳
生存偏差只用还在交易的股票包含退市股票
异常值B/P 为负、市值极端大Winsorize 截断或取对数
因子滞后财报数据有滞后使用最近可获得的财报
行业偏差某个行业因子值天然偏高行业中性化处理

小结

概念要点
因子能预测截面收益差异的变量,需要理论和实证支撑
价值便宜的股票长期有溢价(B/P、E/P、CF/P、S/P)
规模小公司有溢价(log 市值)
动量涨的还会涨(12-1 月动量),但短期会反转
质量好公司有溢价(ROE、应计项)
低波动低风险也有好收益,挑战 CAPM
流动性难卖的股票需要补偿(Amihud)
因子相关性价值和动量负相关,需要理解因子的共性和差异

记住:知道有哪些因子只是第一步。更重要的问题是——“为什么这些因子有效?“这就是下一章的内容。

→ 下一章:02-因子溢价来源 —— 从风险补偿、行为金融、市场摩擦理解因子为什么有效