一、因子模型基础概念#
1.1 什么是因子模型?#
因子模型是量化金融中的核心工具,用于分解资产收益的来源。其基本形式为:
-
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 模型选择要点#
-
因子数量:风险分析应包含足够多的因子
-
数据频率:日频数据捕捉短期风险,月频数据适合长期分析
-
滚动窗口:建议使用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()