跳到主内容
研究主线资产定价与组合构建 · 第30–36讲5/7模块导览 →
讲义

第34讲:因子风险暴露 (Factor Risk Exposure)

读取 Fama-French 因子并回归资产超额收益,分解因子与特异风险,再用约束优化检验组合暴露。

完成状态保存在当前浏览器,可随时取消。

一、因子模型基础概念#

1.1 什么是因子模型?#

因子模型是量化金融中的核心工具,用于分解资产收益的来源。其基本形式为:

R=α+β1F1+β2F2+...+βnFn+ϵR = \alpha + \beta_1F_1 + \beta_2F_2 + ... + \beta_nF_n + \epsilon
  • R:资产收益率

  • α:模型未解释的平均收益(Alpha)

  • β:因子暴露(因子敏感度)

  • F:因子收益率

  • ε:特异收益(不可解释部分)

1.2 关键术语解析#

  • 因子暴露 (β):资产收益对因子变动的敏感程度

  • 主动收益:投资组合收益 - 基准收益

  • 主动风险:主动收益的标准差(跟踪误差)

  • FMCAR:因子边际贡献主动风险平方

小练习:假设某基金年化主动收益为5%,主动风险为8%,请计算其信息比率。

二、实践:计算因子风险暴露#

2.1 数据准备(使用yfinance)#

code
import pandas as pd
import yfinance as yf
from pandas_datareader import data as web

# Fama-French 数据不是 Yahoo 行情代码,应从 Ken French Data Library 获取
ff_raw = web.DataReader(
    'F-F_Research_Data_Factors_daily', 'famafrench',
    start='2015-01-01', end='2020-01-01',
)[0]
ff3 = ff_raw / 100  # 官方文件以百分数表示

# 获取个股数据
stocks = yf.download(
    ['AAPL', 'MSFT', 'GOOG'], start='2015-01-01', end='2020-01-01',
    auto_adjust=True, group_by='column', multi_level_index=True,
)['Close']
stock_returns = stocks.pct_change().dropna()

# CAPM/FF 回归的被解释变量应为超额收益,并与因子日期严格对齐
combined = stock_returns.join(ff3, how='inner')
stock_excess_returns = combined[stock_returns.columns].sub(combined['RF'], axis=0)
ff3 = combined[['Mkt-RF', 'SMB', 'HML']]

2.2 因子暴露计算(OLS回归)#

code
import statsmodels.api as sm

def calculate_factor_exposure(returns, factors):
    X = sm.add_constant(factors)
    model = sm.OLS(returns, X).fit()
    return model

# 示例:计算苹果股票的因子暴露
aapl_returns = stock_excess_returns['AAPL']
factor_model = calculate_factor_exposure(aapl_returns, ff3)
factor_exposures = factor_model.params.drop('const')
print(
    f"AAPL因子暴露:\n市场因子: {factor_exposures['Mkt-RF']:.2f}"
    f"\n规模因子: {factor_exposures['SMB']:.2f}"
    f"\n价值因子: {factor_exposures['HML']:.2f}"
)

2.3 滚动回归分析#

code
# 滚动窗口设置(60天窗口)
window_size = 60
rolling_betas = pd.DataFrame(index=aapl_returns.index, columns=ff3.columns)

for i in range(window_size, len(aapl_returns)):
    X = sm.add_constant(ff3.iloc[i-window_size:i])
    model = sm.OLS(aapl_returns.iloc[i-window_size:i], X).fit()
    rolling_betas.iloc[i] = model.params.drop('const')

rolling_betas.plot(figsize=(12,6))
plt.title('苹果股票滚动因子暴露')
plt.ylabel('Beta系数');

三、风险贡献分析#

3.1 边际风险贡献计算#

code
def calculate_factor_risk_contributions(betas, factor_cov, specific_variance):
    """
    betas: 因子暴露向量
    factor_cov: 因子协方差矩阵
    specific_variance: 回归残差方差
    """
    factor_variance_contributions = betas * (factor_cov @ betas)
    modeled_variance = factor_variance_contributions.sum() + specific_variance
    factor_shares = factor_variance_contributions / modeled_variance
    return factor_shares, specific_variance / modeled_variance

# 示例计算
factor_cov = ff3[['Mkt-RF', 'SMB', 'HML']].cov()
specific_variance = factor_model.resid.var(ddof=1)
fmcar, specific_share = calculate_factor_risk_contributions(
    factor_exposures, factor_cov, specific_variance
)

print("各因子边际风险贡献:")
for factor, contribution in zip(fmcar.index, fmcar.values):
    print(f"{factor}: {contribution:.2%}")

3.2 风险归因可视化#

code
risk_shares = pd.concat([fmcar, pd.Series({'Specific': specific_share})])
plt.pie(risk_shares, labels=risk_shares.index, autopct='%1.1f%%')
plt.title('风险贡献分解');

四、构建追踪组合#

4.1 组合权重计算#

假设需要构建对价值因子(HML)中性的组合:

code
from scipy.optimize import minimize

def neutralize_factor(benchmark_exposure, stock_exposures):
    n_stocks = len(stock_exposures)
    initial_weights = np.ones(n_stocks)/n_stocks

    def objective(weights):
        return np.sum((weights @ stock_exposures - benchmark_exposure)**2)

    constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x)-1})
    bounds = [(0,1) for _ in range(n_stocks)]

    result = minimize(objective, initial_weights,
                     constraints=constraints, bounds=bounds)
    return result.x

# 示例:中性化价值因子
hml_exposures = pd.DataFrame({
    'AAPL': 0.12,
    'MSFT': -0.08,
    'GOOG': 0.05
}, index=['HML'])

optimal_weights = neutralize_factor([0], hml_exposures.T)
print("最优组合权重:", optimal_weights)

五、实践建议#

5.1 模型选择要点#

  1. 因子数量:风险分析应包含足够多的因子

  2. 数据频率:日频数据捕捉短期风险,月频数据适合长期分析

  3. 滚动窗口:建议使用60-120个交易日窗口

5.2 常见误区#

  • 过度依赖历史数据:需结合经济逻辑判断

  • 忽视因子相关性:使用正交化处理或主成分分析

  • 忽略非线性关系:考虑交互项或非线性模型

小练习:选取5只中国A股股票,使用FF3模型计算其2022年因子暴露,并分析行业因素的影响。


附录:yfinance数据获取注意事项

code
# 获取多只股票数据;显式固定复权和列结构,避免依赖版本默认值
data = yf.download(
    ['AAPL', 'MSFT'], start='2020-01-01', end='2023-01-01',
    auto_adjust=True, group_by='column', multi_level_index=True,
)

# 数据清洗步骤示例
closes = data['Close'].ffill().dropna()
returns = closes.pct_change().dropna()