资产定价与因子模型

核心问题: 股票收益从哪里来?多少是市场给的(Beta),多少是能力创造的(Alpha)?


学习目标

  • 理解有效市场假说及其与量化策略的关系
  • 掌握 CAPM 模型,理解风险与收益的线性关系
  • 学习 Fama-French 多因子模型的演进逻辑
  • 理解 Barra 风险模型在业界的应用
  • 用 Python 从零构建因子投资组合
  • 核心: 明确 ML 模型预测的到底是什么

一、有效市场假说 (EMH)

1.1 什么是有效市场假说?

有效市场假说 (Efficient Market Hypothesis, EMH) 由 Eugene Fama 提出,核心观点:

资产价格已经反映所有可用信息,因此无法持续获得超额收益。

1.2 三种形式的有效市场

形式信息集启示对量化策略的影响
弱式有效历史价格、成交量技术分析无效纯价格策略难赚钱
半强式有效公开信息(财报、新闻)基本分析无效需要挖掘非公开信息
强式有效所有信息(包括内幕)内幕交易也无效几乎不可能获得 Alpha

1.3 为什么市场不完全有效?

如果市场完全有效,为什么还有人能赚钱?现实中的市场摩擦创造了机会:

  1. 流动性约束: 机构资金量大,难以快速调仓
  2. 卖空限制: A 股融券成本高、券源少
  3. 行为偏差: 散户非理性交易(追涨杀跌)
  4. 信息扩散慢: 复杂信息需要时间被市场消化
  5. 套利成本: 交易费用、价格冲击、风险限制

1.4 EMH 对量化 ML 的启示

# EMH 的分层启示
market_efficiency = {
    "弱式有效": {
        "含义": "历史价格信息已被利用",
        "ML策略": "不要只用价格序列做特征",
        "可行方向": "挖掘另类数据(新闻、情绪、基本面)"
    },
    "半强式有效": {
        "含义": "公开信息快速被定价",
        "ML策略": "比拼信息处理速度和质量",
        "可行方向": "NLP 处理非结构化信息、高频交易"
    },
    "现实": {
        "状态": "介于弱式和半强式之间",
        "机会": "短期低效(行为偏差)、长期低效(制度约束)",
        "ML优势": "发现人类难以察觉的复杂模式"
    }
}

量化策略生存法则: 寻找市场低效的角落,在有效市场中赚取风险溢价


二、CAPM 模型

2.1 模型动机

CAPM (Capital Asset Pricing Model) 回答一个核心问题:

投资者承担风险,应该获得多少收益补偿?

2.2 模型公式

其中:

  • : 资产 i 的预期收益
  • : 无风险收益率
  • : 资产 i 对市场风险的敏感度
  • : 市场风险溢价

2.3 直观理解

预期收益 = 无风险收益 + β × 市场风险溢价

示例:
- 无风险收益 Rf = 3%
- 市场风险溢价 = 7%
- 股票 A 的 β = 1.2

则:E(Ra) = 3% + 1.2 × 7% = 11.4%

解释:
- 3% 是时间价值的补偿(等待)
- 8.4% 是风险的补偿(承担市场波动)

2.4 Beta 的计算

Beta 的含义:

  • : 与市场同步波动
  • : 放大市场波动(进攻型)
  • : 收敛市场波动(防御型)
  • : 与市场反向(对冲工具)

2.5 Alpha 的定义

Alpha 是实际收益与 CAPM 预期收益的差值:

Alpha 的含义:

  • : 跑赢预期(选股能力)
  • : 符合预期(市场定价合理)
  • : 跑输预期(选股失误)

2.6 Python 实现 CAPM

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
 
# 设置随机种子,确保结果可复现
np.random.seed(42)
 
# 模拟参数
n_days = 252  # 一年交易日
rf = 0.03     # 无风险收益率 3%
market_std = 0.20  # 市场年化波动率 20%
 
# 生成市场收益率(假设市场年化收益 10%)
market_daily_return = 0.10 / 252
market_daily_std = market_std / np.sqrt(252)
 
# 模拟市场收益
market_returns = np.random.normal(
    loc=market_daily_return,
    scale=market_daily_std,
    size=n_days
)
 
# 模拟三只不同 Beta 的股票
betas = {
    '防守型股票': 0.6,
    '市场股票': 1.0,
    '进攻型股票': 1.5
}
 
# 股票特异性波动率(不可分散的风险)
idiosyncratic_std = 0.15 / np.sqrt(252)
 
stock_returns = pd.DataFrame(index=range(n_days))
 
for name, beta in betas.items():
    # 股票收益 = β × 市场收益 + 特异性收益
    systematic_part = beta * market_returns
    idiosyncratic_part = np.random.normal(0, idiosyncratic_std, n_days)
    stock_returns[name] = systematic_part + idiosyncratic_part
 
# 计算累计收益
stock_cumulative = (1 + stock_returns).cumprod()
market_cumulative = (1 + pd.Series(market_returns)).cumprod()
 
# 计算统计指标
def calculate_capm_metrics(stock_returns, market_returns, rf_annual):
    """计算 CAPM 相关指标"""
    rf_daily = rf_annual / 252
 
    # 计算 Beta
    covariance = np.cov(stock_returns, market_returns)[0, 1]
    market_variance = np.var(market_returns)
    beta = covariance / market_variance
 
    # 计算 Alpha (年化)
    stock_annual_return = (1 + stock_returns.mean()) ** 252 - 1
    market_annual_return = (1 + market_returns.mean()) ** 252 - 1
    expected_return = rf_annual + beta * (market_annual_return - rf_annual)
    alpha = stock_annual_return - expected_return
 
    # 计算信息比率 (Alpha / Tracking Error)
    excess_returns = stock_returns - rf_daily - beta * (market_returns - rf_daily)
    tracking_error = np.std(excess_returns) * np.sqrt(252)
    information_ratio = alpha / tracking_error if tracking_error > 0 else 0
 
    return {
        'Beta': beta,
        'Alpha': alpha,
        '年化收益': stock_annual_return,
        '预期收益': expected_return,
        '信息比率': information_ratio
    }
 
# 计算每只股票的指标
metrics = {}
for stock in stock_returns.columns:
    metrics[stock] = calculate_capm_metrics(
        stock_returns[stock].values,
        market_returns,
        rf
    )
 
# 打印结果
print("=" * 60)
print("CAPM 分析结果")
print("=" * 60)
print(f"{'指标':<15} {'防守型':<12} {'市场股票':<12} {'进攻型':<12}")
print("-" * 60)
for metric in ['Beta', 'Alpha', '年化收益', '信息比率']:
    row = [metric]
    for stock in stock_returns.columns:
        row.append(f"{metrics[stock][metric]:.4f}")
    print(f"{row[0]:<15} {row[1]:<12} {row[2]:<12} {row[3]:<12}")
print("=" * 60)
 
# 绘制累计收益曲线
plt.figure(figsize=(12, 6))
plt.plot(market_cumulative.values, label='市场', linewidth=2, linestyle='--')
for stock in stock_returns.columns:
    plt.plot(stock_cumulative[stock].values, label=stock, linewidth=2)
plt.title('不同 Beta 股票的累计收益对比', fontsize=14)
plt.xlabel('交易日', fontsize=12)
plt.ylabel('累计收益 (初始=1)', fontsize=12)
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
 
# 绘制收益分布
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.hist(market_returns, bins=50, alpha=0.7, label='市场', edgecolor='black')
plt.hist(stock_returns['进攻型股票'], bins=50, alpha=0.5, label='进攻型 (β=1.5)', edgecolor='black')
plt.xlabel('日收益率')
plt.ylabel('频数')
plt.title('收益分布对比')
plt.legend()
 
plt.subplot(1, 2, 2)
# 散点图展示股票收益与市场收益的关系
plt.scatter(market_returns, stock_returns['进攻型股票'], alpha=0.5, s=20)
# 添加拟合线
z = np.polyfit(market_returns, stock_returns['进攻型股票'], 1)
p = np.poly1d(z)
plt.plot(market_returns, p(market_returns), "r--", linewidth=2, label=f'拟合线 (β≈{z[0]:.2f})')
plt.xlabel('市场收益率')
plt.ylabel('股票收益率')
plt.title('CAPM: 股票收益 vs 市场收益')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

2.7 CAPM 的局限与启示

局限说明对量化策略的启示
单因子只有市场风险因子需要更多风险因子解释收益
假设投资者同质所有投资者预期相同投资者异质性产生机会
历史数据估计Beta 不稳定需要动态调整 Beta 暴露
无法解释异象动量、价值效应存在这些”异象”是因子投资的基础

三、Fama-French 多因子模型

3.1 从单因子到多因子

CAPM 只有一个市场因子,但实证发现很多异象 (Anomalies):

已发现的市场异象:
├── 价值效应: 低市盈率股票长期收益高
├── 规模效应: 小盘股长期收益高
├── 动量效应: 过去上涨股票继续上涨
├── 质量效应: 财务健康公司收益高
└── 低波效应: 低波动股票风险调整后收益高

Fama 和 French 认为这些不是”异象”,而是风险因子——投资者承担了某种系统性风险,需要补偿。

3.2 三因子模型 (1992)

新增因子:

  • SMB (Small Minus Big): 小盘股因子
  • HML (High Minus Low): 价值因子(高账面市值比 - 低账面市值比)

因子构造逻辑

def construct_ff_factors(data):
    """
    Fama-French 三因子构造逻辑
 
    参数:
        data: 包含市值(ME)和账面市值比(BE/ME)的DataFrame
 
    返回:
        SMB, HML 因子收益
    """
    # 1. 按市值中位数分大/小盘
    me_median = data['ME'].median()
    data['size_group'] = np.where(data['ME'] > me_median, 'B', 'S')
 
    # 2. 按账面市值比三分位数分价值/中性/成长
    bm_30 = data['BE_ME'].quantile(0.3)
    bm_70 = data['BE_ME'].quantile(0.7)
 
    def get_value_group(bm):
        if bm <= bm_30:
            return 'L'  # Low = 成长
        elif bm >= bm_70:
            return 'H'  # High = 价值
        else:
            return 'N'  # Neutral
 
    data['value_group'] = data['BE_ME'].apply(get_value_group)
 
    # 3. 形成 6 个投资组合
    # SL: 小盘低BM(成长) | SM: 小盘中 | SH: 小盘高(价值)
    # BL: 大盘低BM(成长) | BM: 大盘中 | BH: 大盘高(价值)
 
    # 4. SMB = 1/3(小盘价值+小盘中+小盘成长) - 1/3(大盘价值+大盘中+大盘成长)
    #    = 做多小盘,做空大盘
 
    # 5. HML = 1/2(小盘价值+大盘价值) - 1/2(小盘成长+大盘成长)
    #    = 做多价值,做空成长
 
    return smb, hml

3.3 五因子模型 (2015)

新增因子:

  • RMW (Robust Minus Weak): 盈利能力因子(高盈利 - 低盈利)
  • CMA (Conservative Minus Aggressive): 投资因子(低投资 - 高投资)

逻辑:

  • 高盈利公司 → 持续赚钱能力强 → 应该有溢价
  • 保守投资公司 → 资本配置谨慎 → 应该有溢价

3.4 Python 实现 Fama-French 回归

import numpy as np
import pandas as pd
import statsmodels.api as sm
from sklearn.linear_model import LinearRegression
 
# 设置随机种子
np.random.seed(42)
 
# 模拟参数
n_days = 252
n_stocks = 100
 
# 模拟因子收益 (年化)
factor_annual_returns = {
    'MKT': 0.08,   # 市场风险溢价
    'SMB': 0.03,   # 小盘股溢价
    'HML': 0.04,   # 价值溢价
    'RMW': 0.03,   # 盈利能力溢价
    'CMA': 0.02    # 保守投资溢价
}
 
# 模拟因子收益率序列
factor_daily_returns = pd.DataFrame({
    factor: np.random.normal(ann/252, 0.01, n_days)
    for factor, ann in factor_annual_returns.items()
})
 
# 确保市场因子(MKT)与实际市场风险溢价相关
factor_daily_returns['MKT'] = np.random.normal(0.08/252, 0.01, n_days)
 
# 生成股票的因子暴露 (Beta 值)
# 每只股票对不同因子有不同的敏感度
np.random.seed(42)
factor_loadings = pd.DataFrame({
    'MKT': np.random.uniform(0.5, 1.5, n_stocks),
    'SMB': np.random.uniform(-0.5, 1.0, n_stocks),
    'HML': np.random.uniform(-0.5, 1.0, n_stocks),
    'RMW': np.random.uniform(-0.3, 0.8, n_stocks),
    'CMA': np.random.uniform(-0.3, 0.6, n_stocks),
}, index=[f'Stock_{i}' for i in range(n_stocks)])
 
# 生成股票收益率
stock_returns = pd.DataFrame(index=range(n_days))
alphas = {}  # 每只股票的真实 Alpha
 
for stock in factor_loadings.index:
    loadings = factor_loadings.loc[stock]
 
    # 系统性收益 = Σ(因子暴露 × 因子收益)
    systematic_return = (
        loadings['MKT'] * factor_daily_returns['MKT'] +
        loadings['SMB'] * factor_daily_returns['SMB'] +
        loadings['HML'] * factor_daily_returns['HML'] +
        loadings['RMW'] * factor_daily_returns['RMW'] +
        loadings['CMA'] * factor_daily_returns['CMA']
    )
 
    # 特异性收益 (不能被因子解释的部分)
    idiosyncratic = np.random.normal(0, 0.008, n_days)
 
    # 真实 Alpha (年化 3% 的正态分布,有些股票有,有些没有)
    true_alpha = np.random.normal(0.02, 0.015)
    alphas[stock] = true_alpha
 
    # 最终收益 = Alpha + 系统性收益 + 特异性收益
    stock_returns[stock] = true_alpha/252 + systematic_return + idiosyncratic
 
# 选取一只股票进行详细分析
sample_stock = 'Stock_0'
y = stock_returns[sample_stock].values
X = factor_daily_returns.values
X_with_const = sm.add_constant(X)
 
# 三因子回归
model_3factor = sm.OLS(
    y,
    sm.add_constant(factor_daily_returns[['MKT', 'SMB', 'HML']].values)
).fit()
 
# 五因子回归
model_5factor = sm.OLS(y, X_with_const).fit()
 
# 打印回归结果
print("=" * 80)
print(f"股票 {sample_stock} 的因子分析")
print("=" * 80)
 
print("\n【三因子模型回归结果】")
print("-" * 80)
print(f"{'因子':<15} {'系数':<12} {'t值':<12} {'p值':<12} {'解释':<20}")
print("-" * 80)
factor_names_3 = ['Alpha', 'MKT', 'SMB', 'HML']
for i, name in enumerate(factor_names_3):
    coef = model_3factor.params[i]
    t_val = model_3factor.tvalues[i]
    p_val = model_3factor.pvalues[i]
 
    explanations = [
        '超额收益能力',
        '市场风险暴露',
        '小盘股暴露',
        '价值股暴露'
    ]
    print(f"{name:<15} {coef:>10.4f}   {t_val:>10.2f}   {p_val:>10.4f}   {explanations[i]}")
 
print(f"\nR-squared: {model_3factor.rsquared:.4f}")
print(f"Adjusted R-squared: {model_3factor.rsquared_adj:.4f}")
 
print("\n" + "=" * 80)
print("【五因子模型回归结果】")
print("-" * 80)
print(f"{'因子':<15} {'系数':<12} {'t值':<12} {'p值':<12} {'显著吗':<10}")
print("-" * 80)
factor_names_5 = ['Alpha', 'MKT', 'SMB', 'HML', 'RMW', 'CMA']
for i, name in enumerate(factor_names_5):
    coef = model_5factor.params[i]
    t_val = model_5factor.tvalues[i]
    p_val = model_5factor.pvalues[i]
    significant = "***" if p_val < 0.01 else "**" if p_val < 0.05 else "*" if p_val < 0.1 else ""
    print(f"{name:<15} {coef:>10.4f}   {t_val:>10.2f}   {p_val:>10.4f}   {significant:<10}")
 
print(f"\nR-squared: {model_5factor.rsquared:.4f}")
print(f"Adjusted R-squared: {model_5factor.rsquared_adj:.4f}")
print("=" * 80)
 
# 比较三因子和五因子
print("\n【模型对比】")
print(f"三因子 R²: {model_3factor.rsquared:.4f}")
print(f"五因子 R²: {model_5factor.rsquared:.4f}")
print(f"R² 提升: {(model_5factor.rsquared - model_3factor.rsquared)*100:.2f}%")
 
# 因子收益贡献分解
print("\n【收益来源分解】(五因子模型,年化)")
print("-" * 60)
total_return = (stock_returns[sample_stock].mean() * 252)
contributions = {
    'Alpha (年化)': model_5factor.params[0] * 252,
    'MKT 贡献': model_5factor.params[1] * factor_annual_returns['MKT'],
    'SMB 贡献': model_5factor.params[2] * factor_annual_returns['SMB'],
    'HML 贡献': model_5factor.params[3] * factor_annual_returns['HML'],
    'RMW 贡献': model_5factor.params[4] * factor_annual_returns['RMW'],
    'CMA 贡献': model_5factor.params[5] * factor_annual_returns['CMA'],
}
 
for source, contrib in contributions.items():
    pct = contrib / total_return * 100 if total_return != 0 else 0
    print(f"{source:<15}: {contrib:>7.2%} ({pct:>6.2f}%)")
print("-" * 60)
print(f"{'总收益':<15}: {total_return:>7.2%}")
print("=" * 80)
 
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
 
# 1. 累计收益对比
ax1 = axes[0, 0]
cumulative_stock = (1 + stock_returns[sample_stock]).cumprod()
cumulative_predicted = (1 + (
    model_5factor.params[0] +
    model_5factor.params[1] * factor_daily_returns['MKT'] +
    model_5factor.params[2] * factor_daily_returns['SMB'] +
    model_5factor.params[3] * factor_daily_returns['HML'] +
    model_5factor.params[4] * factor_daily_returns['RMW'] +
    model_5factor.params[5] * factor_daily_returns['CMA']
)).cumprod()
ax1.plot(cumulative_stock.values, label='实际收益', linewidth=2)
ax1.plot(cumulative_predicted.values, label='因子模型预测', linewidth=2, linestyle='--')
ax1.set_title('实际收益 vs 因子模型预测', fontsize=12)
ax1.set_xlabel('交易日')
ax1.set_ylabel('累计收益')
ax1.legend()
ax1.grid(True, alpha=0.3)
 
# 2. 残差分析
ax2 = axes[0, 1]
residuals = model_5factor.resid
ax2.hist(residuals, bins=50, edgecolor='black', alpha=0.7)
ax2.axvline(0, color='red', linestyle='--', linewidth=2)
ax2.set_title('残差分布(特异性收益)', fontsize=12)
ax2.set_xlabel('残差')
ax2.set_ylabel('频数')
ax2.grid(True, alpha=0.3)
 
# 3. 因子暴露条形图
ax3 = axes[1, 0]
factor_exposure = model_5factor.params[1:]
colors = ['steelblue' if x > 0 else 'coral' for x in factor_exposure]
ax3.barh(factor_names_5[1:], factor_exposure, color=colors, edgecolor='black')
ax3.axvline(0, color='black', linewidth=0.8)
ax3.set_title('因子暴露(Beta 系数)', fontsize=12)
ax3.set_xlabel('暴露值')
ax3.grid(True, alpha=0.3, axis='x')
 
# 4. 预测 vs 实际散点图
ax4 = axes[1, 1]
predicted = model_5factor.predict(X_with_const)
ax4.scatter(predicted, y, alpha=0.5, s=20)
min_val = min(predicted.min(), y.min())
max_val = max(predicted.max(), y.max())
ax4.plot([min_val, max_val], [min_val, max_val], 'r--', linewidth=2)
ax4.set_title('预测收益 vs 实际收益', fontsize=12)
ax4.set_xlabel('因子模型预测收益')
ax4.set_ylabel('实际收益')
ax4.grid(True, alpha=0.3)
 
plt.tight_layout()
plt.show()

3.5 因子相关性分析

import seaborn as sns
 
# 计算因子相关系数矩阵
factor_corr = factor_daily_returns.corr()
 
# 绘制相关性热力图
plt.figure(figsize=(10, 8))
sns.heatmap(
    factor_corr,
    annot=True,
    fmt='.2f',
    cmap='RdBu_r',
    center=0,
    vmin=-1,
    vmax=1,
    square=True,
    linewidths=1,
    cbar_kws={'label': '相关系数'}
)
plt.title('Fama-French 五因子相关性矩阵', fontsize=14, pad=20)
plt.tight_layout()
plt.show()
 
print("【因子相关性分析】")
print("-" * 50)
print("因子相关系数反映了风险因子的独立性和共线性")
print("高相关性 (>0.5) 说明因子可能有重叠")
print("低相关性说明因子捕捉了不同的风险源")
print("-" * 50)

四、APT 套利定价理论

4.1 与 CAPM 的区别

特性CAPMAPT
理论基础均衡模型套利无边界
因子数量1个(市场)多个(不确定)
因子识别明确(市场收益)待定(实证发现)
假设强度强(投资者同质等)弱(无套利机会)

4.2 APT 公式

其中 是因子 j 的风险溢价。

4.3 APT 的实用价值

APT 不告诉你具体有哪些因子,但提供因子投资的哲学基础

  1. 收益来自对多个系统性风险的暴露
  2. 因子需要经济学直觉支持,不能只是数据挖掘
  3. 因子之间应该低相关,否则重复计算风险

五、Barra 风险模型

5.1 业界标准

Barra 风险模型(现 MSCI Barra)是机构投资者的核心工具:

收益分解方程:

r_i = α_i + Σ(β_ij × f_j) + ε_i

其中:
- r_i: 股票 i 的收益
- α_i: 特异性收益(选股 Alpha)
- β_ij: 股票 i 对因子 j 的暴露
- f_j: 因子 j 的收益
- ε_i: 残差(不能被因子解释)

5.2 Barra 因子体系

因子类型具体因子计算方式
风格因子规模ln(市值)
波动率日收益波动率
动量过去12个月累计收益
价值市净率倒数
盈利质量ROE、ROA
成长营收/净利润增长率
杠杆总资产/股东权益
行业因子中信一级行业虚拟变量(属于=1,不属于=0)
市场因子市场收益Cap加权市场收益

5.3 Barra 模型 Python 实现

import numpy as np
import pandas as pd
from sklearn.linear_model import Ridge
from sklearn.preprocessing import StandardScaler
 
np.random.seed(42)
 
# 模拟参数
n_stocks = 200
n_days = 252
 
# 1. 生成风格因子暴露 (每个股票的基本面特征)
style_factors = pd.DataFrame({
    'Size': np.random.normal(0, 1, n_stocks),          # 规模因子
    'Beta': np.random.normal(1, 0.3, n_stocks),        # Beta因子
    'Momentum': np.random.normal(0, 1, n_stocks),      # 动量因子
    'Value': np.random.normal(0, 1, n_stocks),         # 价值因子
    'Volatility': np.random.normal(0, 1, n_stocks),    # 波动率因子
    'Profit': np.random.normal(0, 1, n_stocks),        # 盈利能力
    'Growth': np.random.normal(0, 1, n_stocks),        # 成长因子
    'Leverage': np.random.normal(0, 1, n_stocks),      # 杠杆因子
}, index=[f'Stock_{i}' for i in(n_stocks)])
 
# 2. 生成行业暴露 (每个股票只属于一个行业)
industries = ['银行', '地产', '医药', '科技', '消费', '能源', '材料', '工业']
industry_exposure = pd.DataFrame(
    np.zeros((n_stocks, len(industries))),
    columns=industries,
    index=style_factors.index
)
# 随机分配行业
for stock in style_factors.index:
    assigned_industry = np.random.choice(industries)
    industry_exposure.loc[stock, assigned_industry] = 1
 
# 3. 模拟因子收益率序列
factor_returns = pd.DataFrame({
    'Size': np.random.normal(0.0002, 0.005, n_days),
    'Beta': np.random.normal(0.0001, 0.003, n_days),
    'Momentum': np.random.normal(0.0003, 0.006, n_days),
    'Value': np.random.normal(0.0001, 0.004, n_days),
    'Volatility': np.random.normal(-0.0001, 0.004, n_days),
    'Profit': np.random.normal(0.0002, 0.003, n_days),
    'Growth': np.random.normal(0.0001, 0.005, n_days),
    'Leverage': np.random.normal(-0.0001, 0.003, n_days),
})
 
# 添加行业因子收益
for industry in industries:
    factor_returns[industry] = np.random.normal(0.0002, 0.004, n_days)
 
# 4. 生成股票收益率
stock_returns = pd.DataFrame(index=range(n_days), columns=style_factors.index)
 
# 每只股票的特异性波动率
specific_vol = np.random.uniform(0.008, 0.02, n_stocks)
 
for i, stock in enumerate(style_factors.index):
    # 风格因子贡献
    style_contribution = (
        style_factors.loc[stock, 'Size'] * factor_returns['Size'] +
        style_factors.loc[stock, 'Beta'] * factor_returns['Beta'] +
        style_factors.loc[stock, 'Momentum'] * factor_returns['Momentum'] +
        style_factors.loc[stock, 'Value'] * factor_returns['Value'] +
        style_factors.loc[stock, 'Volatility'] * factor_returns['Volatility'] +
        style_factors.loc[stock, 'Profit'] * factor_returns['Profit'] +
        style_factors.loc[stock, 'Growth'] * factor_returns['Growth'] +
        style_factors.loc[stock, 'Leverage'] * factor_returns['Leverage']
    )
 
    # 行业因子贡献
    industry_contribution = sum(
        industry_exposure.loc[stock, ind] * factor_returns[ind]
        for ind in industries
    )
 
    # 特异性收益
    specific_return = np.random.normal(0, specific_vol[i], n_days)
 
    # 添加一些真实的 Alpha(选股能力)
    true_alpha = np.random.normal(0.0001, 0.0002)
 
    stock_returns[stock] = (
        true_alpha + style_contribution +
        industry_contribution + specific_return
    )
 
# 5. 横截面回归:每天做一次回归,提取因子收益
estimated_factor_returns = []
factor_covariances = []  # 因子协方差矩阵
specific_risks = []      # 特异性风险
 
for day in range(n_days):
    # 当天的股票收益率
    y = stock_returns.iloc[day].values
 
    # 合并风格因子和行业因子暴露
    X = pd.concat([
        style_factors,
        industry_exposure
    ], axis=1).values
 
    # 加权最小二乘回归(用市值作为权重)
    # 这里简单用等权重
    weights = np.ones(n_stocks)
 
    try:
        # 使用岭回归避免多重共线性
        model = Ridge(alpha=0.1)
        model.fit(X, y, sample_weight=weights)
 
        # 记录因子收益(去掉截距项即 Alpha)
        factor_return = pd.Series(
            model.coef_,
            index=list(style_factors.columns) + industries
        )
        estimated_factor_returns.append(factor_return)
 
        # 计算特异性风险
        residuals = y - model.predict(X)
        specific_std = np.std(residuals)
        specific_risks.append(specific_std)
 
    except Exception as e:
        # 如果回归失败,用上一天的值
        if estimated_factor_returns:
            estimated_factor_returns.append(estimated_factor_returns[-1])
            specific_risks.append(specific_risks[-1])
 
# 转换为 DataFrame
estimated_factor_returns = pd.DataFrame(estimated_factor_returns)
 
# 6. 分析结果
print("=" * 80)
print("Barra 风险模型分析结果")
print("=" * 80)
 
# 因子收益率统计
print("\n【风格因子收益率统计】(年化)")
print("-" * 80)
style_cols = list(style_factors.columns)
factor_stats = estimated_factor_returns[style_cols].describe()
factor_annual = factor_stats.loc[['mean', 'std']] * 252
factor_annual.loc['夏普比率'] = factor_annual.loc['mean'] / factor_annual.loc['std'] * np.sqrt(252)
 
print(f"{'因子':<15} {'年化收益':<12} {'年化波动':<12} {'夏普比率':<12}")
print("-" * 80)
for factor in style_cols:
    ann_return = factor_annual.loc['mean', factor]
    ann_std = factor_annual.loc['std', factor]
    sharpe = factor_annual.loc['夏普比率', factor]
    print(f"{factor:<15} {ann_return:>10.2%}   {ann_std:>10.2%}   {sharpe:>10.2f}")
 
print("\n【行业因子收益率统计】(年化)")
print("-" * 80)
industry_stats = estimated_factor_returns[industries].describe()
industry_annual = industry_stats.loc['mean'] * 252
 
print(f"{'行业':<10} {'年化收益':<12}")
print("-" * 80)
for ind in industries:
    print(f"{ind:<10} {industry_annual[ind]:>10.2%}")
 
# 因子相关性分析
print("\n【风格因子相关性矩阵】")
print("-" * 80)
factor_corr = estimated_factor_returns[style_cols].corr()
print(factor_corr.round(2))
 
# 7. 风险归因示例:对某只股票进行风险分解
sample_stock = 'Stock_0'
stock_return_mean = stock_returns[sample_stock].mean() * 252
 
# 风格因子贡献
style_contribution = sum(
    style_factors.loc[sample_stock, factor] * estimated_factor_returns[factor].mean() * 252
    for factor in style_cols
)
 
# 行业因子贡献
stock_industry = industry_exposure.loc[sample_stock].idxmax()
industry_contribution = estimated_factor_returns[stock_industry].mean() * 252
 
# 特异性贡献(用平均特异性波动率估算)
specific_contribution = np.mean(specific_risks) * np.sqrt(252) * 0.5  # 假设半倍标准差
 
print("\n【风险归因分析】")
print("-" * 80)
print(f"股票: {sample_stock}")
print(f"所属行业: {stock_industry}")
print("-" * 80)
print(f"{'组成部分':<20} {'年化贡献':<15} {'占比':<10}")
print("-" * 80)
print(f"{'风格因子合计':<20} {style_contribution:>13.2%}   {style_contribution/stock_return_mean*100:>6.1f}%")
print(f"{'行业因子贡献':<20} {industry_contribution:>13.2%}   {industry_contribution/stock_return_mean*100:>6.1f}%")
print(f"{'特异性收益估计':<20} {specific_contribution:>13.2%}   {specific_contribution/stock_return_mean*100:>6.1f}%")
print("-" * 80)
print(f"{'总收益':<20} {stock_return_mean:>13.2%}   {100.0:>6.1f}%")
 
# 8. 可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
 
# 累计因子收益
ax1 = axes[0, 0]
for factor in ['Momentum', 'Value', 'Size']:
    cumulative = (1 + estimated_factor_returns[factor]).cumprod()
    ax1.plot(cumulative.values, label=factor, linewidth=2)
ax1.set_title('主要风格因子累计收益', fontsize=12)
ax1.set_xlabel('交易日')
ax1.set_ylabel('累计收益')
ax1.legend()
ax1.grid(True, alpha=0.3)
 
# 因子收益率分布
ax2 = axes[0, 1]
factor_means = estimated_factor_returns[style_cols].mean() * 252
factor_stds = estimated_factor_returns[style_cols].std() * np.sqrt(252)
ax2.scatter(factor_means, factor_stds, s=100, alpha=0.7, edgecolors='black')
for factor in style_cols:
    ax2.annotate(
        factor,
        (factor_means[factor], factor_stds[factor]),
        fontsize=9, ha='center'
    )
ax2.axhline(0, color='red', linestyle='--', alpha=0.5)
ax2.axvline(0, color='red', linestyle='--', alpha=0.5)
ax2.set_xlabel('年化收益率')
ax2.set_ylabel('年化波动率')
ax2.set_title('因子风险-收益分布', fontsize=12)
ax2.grid(True, alpha=0.3)
 
# 行业因子收益条形图
ax3 = axes[1, 0]
industry_annual_sorted = industry_annual.sort_values(ascending=False)
colors = ['green' if x > 0 else 'red' for x in industry_annual_sorted.values]
ax3.barh(industry_annual_sorted.index, industry_annual_sorted.values, color=colors, edgecolor='black')
ax3.axvline(0, color='black', linewidth=0.8)
ax3.set_xlabel('年化收益率')
ax3.set_title('行业因子收益率', fontsize=12)
ax3.grid(True, alpha=0.3, axis='x')
 
# 特异性风险分布
ax4 = axes[1, 1]
ax4.hist(specific_risks, bins=50, edgecolor='black', alpha=0.7)
ax4.axvline(np.mean(specific_risks), color='red', linestyle='--', linewidth=2, label=f'均值: {np.mean(specific_risks):.4f}')
ax4.set_xlabel('日特异性波动率')
ax4.set_ylabel('频数')
ax4.set_title('特异性风险分布', fontsize=12)
ax4.legend()
ax4.grid(True, alpha=0.3)
 
plt.tight_layout()
plt.show()

六、因子投资的实践问题

6.1 因子拥挤 (Factor Crowding)

定义: 当太多资金追逐同一因子,因子收益会下降甚至反转。

检测指标:

def detect_factor_crowding(factor_returns, prices):
    """
    检测因子拥挤度
 
    指标:
    1. 因子相关性上升
    2. 因子波动率下降(同质化交易)
    3. 因子收益偏度负化(拥挤时更容易崩盘)
    """
    # 滚动相关性
    rolling_corr = factor_returns.rolling(60).corr()
 
    # 滚动波动率
    rolling_vol = factor_returns.rolling(60).std()
 
    # 滚动偏度
    rolling_skew = factor_returns.rolling(60).skew()
 
    return {
        'correlation_trend': rolling_corr.iloc[-1].mean(),
        'volatility_decline': rolling_vol.iloc[-1] / rolling_vol.mean(),
        'skewness_warning': rolling_skew.iloc[-1] < -1
    }

6.2 因子衰减 (Factor Decay)

现象: 发现的因子在使用一段时间后收益消失。

原因:

  1. 数据挖掘偏差: 原本就是假象
  2. 市场学习: 其他机构发现并交易,套利机会消失
  3. 制度变化: 市场规则改变(如 T+0 → T+1)

应对:

  • 样本外测试严格
  • 因子组合需要动态调整
  • 理解因子的经济学逻辑

6.3 风格轮动 (Style Rotation)

不同时期表现最好的风格不同:

经济周期 → 风格轮动路径:

衰退 → 复苏 → 扩张 → 滞胀
  ↓        ↓        ↓        ↓
防守      价值      成长      质量
(高股息)  (低估值)  (高增长)  (稳定盈利)

识别方法: 宏观指标(PMI、利率、信用利差)→ 预测下一阶段强势风格


七、动量 vs 反转

7.1 两种时间序列模式

价格路径示意:

动量效应 (Momentum):
  价格 ─────────────────────╮
                            ╰╮╭─ 上涨持续
                              ╰

反转效应 (Reversal):
  价格 ────────╭╮
              ╰╯────── 回落

7.2 时间序列动量 vs 截面动量

类型定义策略
时间序列动量过去上涨的资产未来继续上涨做多过去赢家,做空过去输家
截面动量相对其他资产表现好的资产未来继续好买入最强势的,卖出最弱势的

7.3 Python 实现动量策略

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
 
np.random.seed(42)
 
# 生成模拟价格数据(带有动量特征)
n_days = 500
n_stocks = 10
 
# 基础收益
base_returns = np.random.normal(0.0005, 0.02, (n_days, n_stocks))
 
# 添加动量效应:过去上涨的股票倾向于继续上涨
momentum_signal = pd.DataFrame(index=range(n_days), columns=range(n_stocks))
for t in range(20, n_days):
    # 过去20天的累计收益作为动量信号
    past_returns = base_returns[t-20:t]
    cumulative_momentum = np.sum(past_returns, axis=0)
 
    # 动量越强,未来收益越高(效应强度为信号方差的20%)
    momentum_effect = 0.2 * cumulative_momentum / 20
    base_returns[t] += momentum_effect
 
# 生成价格序列
prices = pd.DataFrame(
    np.cumprod(1 + base_returns, axis=0),
    columns=[f'Stock_{i}' for i in range(n_stocks)]
)
 
# 1. 时间序列动量策略
def time_series_momentum(prices, lookback=20, holding=5):
    """
    时间序列动量策略
 
    参数:
        prices: 价格 DataFrame
        lookback: 动量回望期
        holding: 持有期
    """
    returns = prices.pct_change()
    signals = pd.DataFrame(index=prices.index, columns=prices.columns)
 
    for i in range(lookback, len(prices)):
        # 计算过去 lookback 天的累计收益率
        past_cumret = (prices.iloc[i-lookback:i] / prices.iloc[i-lookback] - 1).iloc[-1]
 
        # 正收益做多,负收益做空
        signals.iloc[i] = np.sign(past_cumret)
 
    # 计算 holding 期后的收益
    strategy_returns = pd.Series(index=prices.index)
    for i in range(lookback, len(prices) - holding):
        if i % holding == 0:  # 每 holding 天调仓
            current_signal = signals.iloc[i]
            future_returns = returns.iloc[i:i+holding]
            # 等权重做多或做空
            strategy_returns.iloc[i] = (
                current_signal * future_returns
            ).mean().mean()  # 横截面平均
 
    return strategy_returns, signals
 
# 2. 截面动量策略
def cross_sectional_momentum(prices, lookback=20, top_n=3):
    """
    截面动量策略
 
    参数:
        prices: 价格 DataFrame
        lookback: 动量回望期
        top_n: 买入前 n 名,做空后 n 名
    """
    returns = prices.pct_change()
    signals = pd.DataFrame(index=prices.index, columns=prices.columns)
 
    for i in range(lookback, len(prices)):
        # 计算每只股票的过去累计收益
        momentum = (prices.iloc[i-lookback:i] / prices.iloc[i-lookback] - 1).iloc[-1]
 
        # 排名
        ranked = momentum.rank()
 
        # 买入前 top_n,做空后 top_n
        signals.iloc[i] = np.where(
            ranked >= len(prices.columns) - top_n + 1,  # 前几名
            1,  # 做多
            np.where(ranked <= top_n, -1, 0)  # 后几名做空,其他不持有
        )
 
    # 计算策略收益
    strategy_returns = (signals.shift(1) * returns).mean(axis=1)
 
    return strategy_returns, signals
 
# 运行策略
ts_momentum_returns, ts_signals = time_series_momentum(prices)
cs_momentum_returns, cs_signals = cross_sectional_momentum(prices)
 
# 计算累计收益
ts_cumulative = (1 + ts_momentum_returns.fillna(0)).cumprod()
cs_cumulative = (1 + cs_momentum_returns.fillna(0)).cumprod()
market_cumulative = (1 + prices.pct_change().mean(axis=1)).cumprod()
 
# 计算策略指标
def calculate_performance_metrics(returns):
    """计算策略绩效指标"""
    returns = returns.dropna()
    if len(returns) == 0:
        return {}
 
    annual_return = (1 + returns.mean()) ** 252 - 1
    annual_vol = returns.std() * np.sqrt(252)
    sharpe = annual_return / annual_vol if annual_vol > 0 else 0
 
    # 最大回撤
    cumulative = (1 + returns).cumprod()
    running_max = cumulative.expanding().max()
    drawdown = (cumulative - running_max) / running_max
    max_drawdown = drawdown.min()
 
    return {
        '年化收益': annual_return,
        '年化波动': annual_vol,
        '夏普比率': sharpe,
        '最大回撤': max_drawdown
    }
 
ts_metrics = calculate_performance_metrics(ts_momentum_returns)
cs_metrics = calculate_performance_metrics(cs_momentum_returns)
market_metrics = calculate_performance_metrics(prices.pct_change().mean(axis=1))
 
# 打印结果
print("=" * 80)
print("动量策略回测结果")
print("=" * 80)
 
print("\n【策略对比】")
print("-" * 80)
print(f"{'指标':<15} {'时间序列动量':<18} {'截面动量':<15} {'买入持有':<15}")
print("-" * 80)
 
for metric in ['年化收益', '年化波动', '夏普比率', '最大回撤']:
    ts_val = ts_metrics.get(metric, 0)
    cs_val = cs_metrics.get(metric, 0)
    mkt_val = market_metrics.get(metric, 0)
 
    if metric == '年化收益' or metric == '年化波动':
        print(f"{metric:<15} {ts_val:>15.2%}  {cs_val:>15.2%}  {mkt_val:>15.2%}")
    else:
        print(f"{metric:<15} {ts_val:>15.2f}  {cs_val:>15.2f}  {mkt_val:>15.2f}")
 
print("=" * 80)
 
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
 
# 1. 累计收益对比
ax1 = axes[0, 0]
ax1.plot(ts_cumulative.values, label='时间序列动量', linewidth=2)
ax1.plot(cs_cumulative.values, label='截面动量', linewidth=2)
ax1.plot(market_cumulative.values, label='等权市场', linewidth=2, linestyle='--')
ax1.set_title('累计收益对比', fontsize=12)
ax1.set_xlabel('交易日')
ax1.set_ylabel('累计收益')
ax1.legend()
ax1.grid(True, alpha=0.3)
 
# 2. 动量信号热力图(截面动量)
ax2 = axes[0, 1]
# 取最后 100 天的信号
signal_sample = cs_signals.iloc[-100:]
im = ax2.imshow(signal_sample.T, aspect='auto', cmap='RdBu_r', vmin=-1, vmax=1)
ax2.set_title('截面动量信号热力图(最近100天)', fontsize=12)
ax2.set_xlabel('交易日')
ax2.set_ylabel('股票')
plt.colorbar(im, ax=ax2, label='信号(红=做多,蓝=做空)')
 
# 3. 滚动夏普比率
ax3 = axes[1, 0]
window = 60
ts_rolling_sharpe = (
    ts_momentum_returns.rolling(window).mean() /
    ts_momentum_returns.rolling(window).std() * np.sqrt(252)
)
cs_rolling_sharpe = (
    cs_momentum_returns.rolling(window).mean() /
    cs_momentum_returns.rolling(window).std() * np.sqrt(252)
)
ax3.plot(ts_rolling_sharpe.values, label='时间序列动量', linewidth=2)
ax3.plot(cs_rolling_sharpe.values, label='截面动量', linewidth=2)
ax3.axhline(0, color='black', linestyle='--', alpha=0.5)
ax3.set_title(f'滚动夏普比率({window}天窗口)', fontsize=12)
ax3.set_xlabel('交易日')
ax3.set_ylabel('夏普比率')
ax3.legend()
ax3.grid(True, alpha=0.3)
 
# 4. 动量因子收益率分布
ax4 = axes[1, 1]
# 计算每天的动量因子收益(多空组合收益)
momentum_factor_returns = pd.Series({
    '时间序列': ts_momentum_returns.mean(),
    '截面': cs_momentum_returns.mean()
})
# 计算标准差
momentum_factor_std = pd.Series({
    '时间序列': ts_momentum_returns.std(),
    '截面': cs_momentum_returns.std()
})
# 年化
momentum_annual = momentum_factor_returns * 252
momentum_std_annual = momentum_factor_std * np.sqrt(252)
 
x_pos = np.arange(len(momentum_annual))
bars = ax4.bar(x_pos, momentum_annual.values,
               yerr=momentum_std_annual.values,
               capsize=5, alpha=0.7, edgecolor='black')
colors = ['green' if x > 0 else 'red' for x in momentum_annual.values]
for bar, color in zip(bars, colors):
    bar.set_color(color)
ax4.set_xticks(x_pos)
ax4.set_xticklabels(momentum_annual.index)
ax4.set_ylabel('年化收益率')
ax4.set_title('动量因子年化收益(±1标准差)', fontsize=12)
ax4.axhline(0, color='black', linewidth=0.8)
ax4.grid(True, alpha=0.3, axis='y')
 
plt.tight_layout()
plt.show()

7.4 反转策略

def short_term_reversal(prices, lookback=5, holding=5):
    """
    短期反转策略
 
    逻辑: 过去下跌的股票倾向于短期反弹
    """
    returns = prices.pct_change()
    signals = pd.DataFrame(index=prices.index, columns=prices.columns)
 
    for i in range(lookback, len(prices)):
        # 过去 lookback 天的累计收益
        past_cumret = (prices.iloc[i-lookback:i] / prices.iloc[i-lookback] - 1).iloc[-1]
 
        # 负收益做多(抄底),正收益做空(止盈)
        signals.iloc[i] = -np.sign(past_cumret)
 
    # 计算策略收益
    strategy_returns = pd.Series(index=prices.index)
    for i in range(lookback, len(prices) - holding):
        if i % holding == 0:
            current_signal = signals.iloc[i]
            future_returns = returns.iloc[i:i+holding]
            strategy_returns.iloc[i] = (
                current_signal * future_returns
            ).mean().mean()
 
    return strategy_returns
 
# 运行反转策略
reversal_returns = short_term_reversal(prices)
reversal_cumulative = (1 + reversal_returns.fillna(0)).cumprod()
 
print("\n【短期反转策略】")
print("-" * 60)
reversal_metrics = calculate_performance_metrics(reversal_returns)
for metric, value in reversal_metrics.items():
    if '收益' in metric or '波动' in metric:
        print(f"{metric}: {value:.2%}")
    else:
        print(f"{metric}: {value:.2f}")

7.5 动量与反转的 ML 应用

# ML 模型预测时需要考虑:
# 1. 时间序列特征: 过去收益、波动率
# 2. 截面特征: 相对排名、行业相对表现
# 3. 交互特征: 市场状态(高波动时动量更强?)
 
def create_momentum_features(prices, lookback_periods=[5, 20, 60]):
    """
    构造动量相关特征供 ML 使用
    """
    returns = prices.pct_change()
    features = pd.DataFrame(index=prices.index)
 
    for period in lookback_periods:
        # 时间序列动量
        features[f'momentum_{period}'] = (
            prices.pct_change(period).shift(1)
        ).mean(axis=1)
 
        # 动量强度(过去收益的绝对值)
        features[f'momentum_strength_{period}'] = (
            returns.rolling(period).apply(lambda x: np.abs(x).mean())
        ).mean(axis=1).shift(1)
 
        # 动量一致性(正收益天数占比)
        features[f'momentum_consistency_{period}'] = (
            (returns.rolling(period).apply(lambda x: (x > 0).sum()) / period
        ).mean(axis=1).shift(1)
        )
 
    # 添加市场状态特征
    market_return = returns.mean(axis=1)
    features['market_volatility'] = market_return.rolling(20).std().shift(1)
    features['market_trend'] = market_return.rolling(60).mean().shift(1)
 
    return features
 
# 使用示例
momentum_features = create_momentum_features(prices)
print("\n【动量特征示例】")
print(momentum_features.dropna().head())

八、价值 vs 成长

8.1 定义与度量

因子定义常用指标
价值相对内在价值被低估市净率(P/B)、市盈率(P/E)、市销率(P/S)、EV/EBITDA
成长盈利/营收高速增长营收增长率、净利润增长率、ROE 变化

8.2 价值溢价之谜

现象: 价值股长期收益高于成长股,但并非持续。

解释:

  1. 风险补偿: 价值股往往财务状况较差
  2. 行为偏差: 投资者过度追捧成长股(漂亮股票)
  3. 周期性: 经济复苏期价值股表现更好

8.3 Python 实现

def value_factor_analysis(prices, fundamental_data):
    """
    价值因子分析
 
    参数:
        prices: 价格数据
        fundamental_data: 包含估值指标的基本面数据
    """
    # 计算估值因子
    # 市净率倒数 = 账面价值 / 市值
    pb_inverse = fundamental_data['book_value'] / prices
 
    # 市盈率倒数 = 每股收益 / 股价
    pe_inverse = fundamental_data['eps'] / prices
 
    # 综合价值得分(Z-score 标准化后平均)
    pb_z = (pb_inverse - pb_inverse.mean()) / pb_inverse.std()
    pe_z = (pe_inverse - pe_inverse.mean()) / pe_inverse.std()
 
    value_score = (pb_z + pe_z) / 2
 
    # 分组回测
    quintiles = pd.qcut(value_score, 5, labels=['低估值', '', '', '', '高估值'])
 
    # 计算每组收益
    group_returns = {}
    for group in ['低估值', '高估值']:
        mask = quintiles == group
        group_returns[group] = (
            prices.pct_change().mean(axis=1)[mask]
        ).mean()
 
    # 价值因子收益 = 高估值组 - 低估值组
    value_premium = group_returns['高估值'] - group_returns['低估值']
 
    return value_premium, group_returns

九、你用 ML 预测的到底是什么?

9.1 收益分解视角

股票收益 = Alpha + Beta_1 * 因子1 + Beta_2 * 因子2 + ... + 残差

ML 模型预测的可能是:
1. 真正的 Alpha(选股能力)
2. 某些已知因子的暴露(换一种方式计算 Beta)
3. 过拟合的噪声

9.2 区分 Alpha 与 Beta

def analyze_prediction_source(model_predictions, factor_returns, factor_loadings):
    """
    分析 ML 模型预测的收益来源
 
    参数:
        model_predictions: ML 模型预测的股票收益
        factor_returns: 因子收益率
        factor_loadings: 股票的因子暴露
    """
    # 将预测收益对因子暴露做回归
    # 预测收益 = α + Σ(β_i × f_i) + ε
 
    # 如果回归 R² 很高,说明模型主要是在预测 Beta
    # 如果 α 仍然显著,说明模型有真正的选股能力
 
    from sklearn.linear_model import LinearRegression
 
    X = factor_loadings.values
    y = model_predictions.values
 
    model = LinearRegression()
    model.fit(X, y)
 
    # 计算每个股票的预测收益中,多少来自因子,多少来自 Alpha
    factor_contribution = model.predict(X)
    alpha_contribution = y - factor_contribution
 
    # 方差分解
    var_total = np.var(y)
    var_factor = np.var(factor_contribution)
    var_alpha = np.var(alpha_contribution)
 
    return {
        'beta_explained_ratio': var_factor / var_total,
        'alpha_ratio': var_alpha / var_total,
        'model_r2': model.score(X, y)
    }

9.3 实践建议

  1. 回归测试: 对模型预测做因子回归,检查因子暴露
  2. 中性化: 如果模型暴露了 unwanted factor,进行因子中性化
  3. 理解模型: 分析模型学到了什么,而不是只看预测准确率

十、核心知识点总结

理论核心观点对量化 ML 的启示
EMH价格反映信息,市场有效寻找市场低效的角落,理解信息扩散机制
CAPM收益 = 无风险 + β × 市场溢价区分 Alpha 和 Beta,理解风险溢价
FF 三因子市场风险 + 规模 + 价值因子投资的鼻祖,风格暴露很重要
Barra多因子风险模型行业风险管理,组合风险控制
APT收益来自多个风险因子因子需要有经济学直觉,不只是统计

关键公式:

CAPM: E(R) = Rf + β × (Rm - Rf)
FF3:  E(R) = Rf + β×MKT + s×SMB + h×HML
Alpha: α = 实际收益 - 预期收益
IR:   信息比率 = α / σ(α)

实践要点:

  1. 因子拥挤会导致收益衰减,需要持续研究新因子
  2. 风格轮动要求动态调整因子暴露
  3. ML 预测需要区分是 Alpha 还是 Beta
  4. 因子相关性高时需要正交化处理

下一步: 继续阅读 02-市场微观结构.md,理解真实市场的交易机制。