《从CAPM到Barra:多因子模型的演进与基于AmazingData的实战》
一、规则选股 vs Alpha因子:为什么统计检验如此重要?
1.1 规则选股的问题
传统的规则选股基于主观逻辑设定阈值,例如筛选PE小于15的股票。这种方式存在四大缺陷。
第一,缺乏统计验证,未检验该规则与未来收益的相关性是否显著。第二,存在幸存者偏差,回测时可能只纳入存活至今的股票。第三,有过拟合风险,阈值可能是数据挖掘的结果。第四,参数敏感,将15改为16,结果可能大相径庭。
1.Alpha因子的定义
Alpha因子需满足严格的统计标准。第一,显著性:与未来收益的相关系数(IC)统计显著,t统计量绝对值大于2。第二,稳定性:IC在不同时间窗口、不同市场中保持稳定。第三,可解释性:具有清晰的经济学逻辑支撑。第四,低相关性:与其他已知因子的相关性较低。
以下是一个基于AmazingData的单因子IC分析框架:
import AmazingData as ad
import pandas as pd
import numpy as np
from scipy import stats
登录AmazingData
ad.login(username=‘your_username’, password=‘your_password’,
host=‘your_host’, port=your_port)
获取基础数据和市场数据
base_data = ad.BaseData()
market_data = ad.MarketData(base_data.get_calendar())
获取沪深A股代码列表
stock_codes = base_data.get_code_list(security_type=‘EXTRA_STOCK_A’)
获取历史K线数据(日频)
kline_dict = market_data.query_kline(
code_list=stock_codes,
begin_date=20200101,
end_date=20241231,
period=ad.constant.Period.day.value
)
构建因子值DataFrame(以PE为例)
info_data = ad.InfoData()
income_data = info_data.get_income(stock_codes)
balance_data = info_data.get_balance_sheet(stock_codes)
计算PE因子
def calc_pe_factor(income_dict, base_data_obj):
“”"
计算PE因子(市盈率)
使用PIT原则:确保使用当时可获取的财务数据
“”"
pe_factors = {}
for code in income_dict.keys(): df = income_dict[code] if df is None or len(df) == 0: continue # 获取净利润(使用TTM或最新报告期) net_profit = df['NETPROINCLMIN_INT_INC'].astype(float) # 获取对应日期的市值数据(从K线计算) if code in kline_dict: kline = kline_dict[code] # 计算总市值 = 收盘价 * 总股本 # 这里简化处理,实际需从股本结构数据获取 market_cap = kline['close'] * 1e8 # 假设股本数据 # 计算PE pe = market_cap / net_profit.iloc[-1] if net_profit.iloc[-1] != 0 else np.nan pe_factors[code] = pe return pd.Series(pe_factors)单因子IC分析
def factor_ic_analysis(factor_series, forward_return, method=‘spearman’):
“”"
因子IC分析
factor_series: 因子值Series (index为股票代码)
forward_return: 未来一期收益Series
“”"
# 对齐数据
merged = pd.concat([factor_series, forward_return], axis=1)
merged.columns = [‘factor’, ‘return’]
merged = merged.dropna()
if len(merged) < 30: # 最小样本量 return None if method == 'spearman': ic, pvalue = stats.spearmanr(merged['factor'], merged['return']) else: ic, pvalue = stats.pearsonr(merged['factor'], merged['return']) results = { 'IC': ic, 'pvalue': pvalue, 'sample_size': len(merged) } return results计算未来一期收益(简化示例)
def calc_forward_return(kline_dict, period=5):
“”"
计算未来N期收益
“”"
forward_returns = {}
for code, kline in kline_dict.items():
if len(kline) < period + 1:
continue
future_return = (kline[‘close’].shift(-period) / kline[‘close’] - 1).iloc[-period-1]
forward_returns[code] = future_return
return pd.Series(forward_returns)
二、风格因子 vs Alpha因子:风险与收益的分工
1.核心区别
风格因子和Alpha因子在五个维度上存在本质区别。经济含义方面,风格因子解释市场风险的系统性来源,Alpha因子获取超额收益的非系统性来源。典型代表方面,风格因子包括市值、估值、动量和波动率,Alpha因子包括质量、分析师预期和事件驱动。收益特征方面,风格因子波动大、长期超额不稳定,可能数年失效;Alpha因子追求稳定、持续的超额收益。组合作用方面,风格因子用于风险暴露管理和业绩归因基准,Alpha因子用于收益增强和分散化叠加。风险属性方面,风格因子承担系统性风险(有Beta),Alpha因子追求与市场低相关的Alpha。
2.基于AmazingData的风格因子计算
from AmazingData.operator.time_series_function import TimeSeriesFunction
from AmazingData.operator.math_function import MathFunction
def calc_style_factors(kline_dict, info_data):
“”"
基于AmazingData计算经典风格因子
“”"
style_factors = {}
for code, kline in kline_dict.items(): if len(kline) < 252: # 至少一年数据 continue factors = {} # 市值因子(Size):使用收盘价作为代理,实际应从股本结构获取 factors['size'] = np.log(kline['close'].iloc[-1]) # 估值因子(Value):PB = 市值 / 净资产 # 实际应从财务数据获取净资产 factors['value'] = 1 / kline['close'].iloc[-1] # 简化代理 # 动量因子(Momentum):过去252日收益 factors['momentum'] = ( kline['close'].iloc[-1] / kline['close'].iloc[-252] - 1 ) # 波动率因子(Volatility):过去20日收益标准差 returns = kline['close'].pct_change().dropna() factors['volatility'] = TimeSeriesFunction.STD(returns, 20).iloc[-1] style_factors[code] = factors return pd.DataFrame(style_factors).T使用截面函数进行因子标准化
from AmazingData.operator.cross_section_function import CrossSectionFunction
def normalize_factors(factor_df):
“”"
截面Z-score标准化
“”"
return CrossSectionFunction.CSZSCORE(factor_df)
三、多因子模型的演进史
1.CAPM(1964):单因子时代
CAPM模型的公式为:期望收益等于无风险利率加上Beta乘以市场风险溢价。其核心假设是投资者只关心系统性风险,唯一的因子是市场风险(Market Beta)。其局限性在于无法解释市场异象,如小市值效应和价值效应。
2.Fama-French三因子(1993)
三因子模型在CAPM基础上新增了两个因子。SMB(Small Minus Big)是市值因子,代表小市值股票溢价。HML(High Minus Low)是价值因子,代表高账面市值比溢价。三因子模型的解释力从CAPM的约70%提升至约90%。
3.Fama-French五因子(2015)
五因子模型在三因子基础上又新增了两个因子。RMW(Robust Minus Weak)是盈利因子,代表高盈利公司溢价。CMA(Conservative Minus Aggressive)是投资因子,代表低投资公司溢价。五因子模型的核心发现是盈利能力和投资风格是独立的定价因子。
4.Barra风险模型:工业标准
MSCI Barra模型是当前业界最广泛使用的风险模型。以Barra USE4模型为例,一级因子共13个,包括10个风格因子和按GICS分类的行业因子。风格因子包括Beta、动量、规模、盈利、波动率、成长性、价值、杠杆率、流动性和非线性市值。
Barra模型的核心应用包括四个方面。第一是风险分解,分析组合风险来源于哪些因子暴露。第二是业绩归因,比较实际收益与因子预期收益。第三是组合优化,在风险约束下最大化预期收益。第四是压力测试,进行因子冲击情景分析。
以下是一个基于AmazingData的Barra协方差矩阵结构示意:
import numpy as np
def barra_covariance(factor_exposures, factor_cov, specific_var):
“”"
Barra协方差矩阵:系统性风险 + 特质风险
V = X * F * X^T + D
X: 因子暴露矩阵 (N×K)
F: 因子协方差矩阵 (K×K)
D: 特质方差对角矩阵 (N×N)
“”"
systematic_risk = factor_exposures @ factor_cov @ factor_exposures.T
specific_risk = np.diag(specific_var)
total_cov = systematic_risk + specific_risk
return total_cov
基于AmazingData计算因子暴露
def calc_factor_exposure(stock_returns, factor_returns):
“”"
计算个股对因子的暴露(Beta)
使用AmazingData的统计函数
“”"
from AmazingData.operator.math_function import StatisticsFunction
# 计算每个股票对每个因子的Beta betas = {} for code in stock_returns.columns: stock_ret = stock_returns[code] factor_betas = {} for factor_name in factor_returns.columns: factor_ret = factor_returns[factor_name] # 使用BETA函数计算 beta = StatisticsFunction.BETA(stock_ret, factor_ret, 252) factor_betas[factor_name] = beta.iloc[-1] if hasattr(beta, 'iloc') else beta betas[code] = factor_betasreturn pd.DataFrame(betas).T
四、模型演进的核心逻辑
从CAPM到Barra,多因子模型的演进遵循统一逻辑:每增加一个解释变量,残差(特质收益)就降低,定价准确性提高,从而更精准识别真实Alpha。
关键洞察在于,残差项代表模型无法解释的部分。残差越小,说明因子对收益的解释力越强。真正的Alpha存在于残差中,即因子模型无法解释的稳定超额收益。
五、实战建议
因子投资的实战建议有五条。第一,因子挖掘应从经典文献出发,而非数据挖掘。第二,正交化处理需剔除已知风格因子的影响,寻找纯Alpha。第三,样本外验证要区分训练集、验证集和测试集,防止过拟合。第四,每个因子必须有清晰的经济学解释。第五,因子会衰减、会拥挤,需定期评估有效性。
