用Python和Pandas实战Fama-French三因子模型:一步步计算股票特质波动率
用Python和Pandas实战Fama-French三因子模型一步步计算股票特质波动率在量化投资领域特质波动率Idiosyncratic Volatility作为衡量个股特有风险的重要指标近年来受到学术界和业界的广泛关注。不同于系统性风险特质波动率捕捉的是无法通过市场因子解释的个股特异性波动这种非系统性风险的度量对于构建多因子模型、优化投资组合具有独特价值。本文将手把手教你如何用Python的Pandas和statsmodels库从原始数据开始完整实现Fama-French三因子模型下的特质波动率计算流程。1. 环境准备与数据获取特质波动率计算的第一步是准备Python环境和获取必要数据。推荐使用Anaconda创建独立的Python 3.8环境确保各库版本兼容conda create -n ff3 python3.8 conda activate ff3 pip install pandas numpy statsmodels openpyxl金融数据来源有多种选择国内常用Wind、CSMAR或RESSET国际常用CRSP或Kenneth French官网。假设我们从CSMAR获取了以下数据个股日收益率包含股票代码(Stkcd)、交易日期(Date)、考虑现金红利再投资的日个股回报率(Dretnd)无风险收益率通常使用国债日收益率(Nrrdaydt)三因子数据市场风险溢价(RiskPremium)、规模因子(SMB)、价值因子(HML)提示实际应用中需注意数据频率一致性所有数据应为日频且日期对齐。缺失值处理建议采用向前填充或删除整条记录。将下载的Excel文件按sheet分开保存结构如下Data/ ├── FF3_Factors.xlsx # 三因子数据 ├── Stock_Returns.xlsx # 个股收益率 └── RiskFree_Rate.xlsx # 无风险利率2. 数据清洗与合并数据质量决定模型效果金融数据处理需特别注意日期格式和缺失值import pandas as pd from datetime import datetime # 读取原始数据 factors pd.read_excel(Data/FF3_Factors.xlsx, parse_dates[Date]) returns pd.read_excel(Data/Stock_Returns.xlsx, parse_dates[Date]) rf pd.read_excel(Data/RiskFree_Rate.xlsx, parse_dates[Date]) # 统一日期格式 def format_date(df): df[Date] pd.to_datetime(df[Date]).dt.normalize() return df factors format_date(factors) returns format_date(returns) rf format_date(rf)使用Pandas的merge函数进行表连接时注意处理多对多关系# 三表合并 merged_data pd.merge( pd.merge(factors, returns, onDate, howinner), rf, onDate, howinner ) # 检查合并后数据 print(f合并后数据量{len(merged_data)}) print(f日期范围{merged_data[Date].min()} 至 {merged_data[Date].max()}) print(f包含股票数量{merged_data[Stkcd].nunique()})常见数据问题及处理方法问题类型检测方法处理方案日期不连续pd.date_range对比填充或删除缺失日期收益率异常值3σ原则或分位数检查Winsorize处理或设为NaN因子缺失isnull().sum()删除或插值补全3. Fama-French三因子模型实现特质波动率的核心是通过三因子模型分离系统性风险和特异性风险。我们使用statsmodels的OLS进行回归import statsmodels.formula.api as smf def ff3_regression(df): 执行Fama-French三因子回归 model smf.ols(Dretnd - Nrrdaydt ~ RiskPremium SMB HML, datadf) results model.fit() return results # 示例单只股票单月回归 sample merged_data[ (merged_data[Stkcd] 600000) (merged_data[Date].between(2020-01-01, 2020-01-31)) ].dropna() results ff3_regression(sample) print(results.summary())回归结果解析要点Adj. R-squared模型解释力度通常0.6-0.8为佳系数显著性p-value应小于0.05残差正态性Jarque-Bera检验p-value需大于0.05计算残差和特质波动率的完整流程def calculate_residuals(model, df): 计算日频残差序列 predicted model.predict(df) actual df[Dretnd] - df[Nrrdaydt] return actual - predicted def idiosyncratic_volatility(residuals, ddof1): 计算特质波动率 return residuals.std(ddofddof) * (len(residuals)**0.5)4. 批量计算与结果分析实际研究中需要计算全市场股票的特质波动率这里展示批量处理方法# 添加年月字段便于分组 merged_data[YearMonth] merged_data[Date].dt.to_period(M) # 定义计算函数 def batch_compute_iv(group): try: model ff3_regression(group) residuals calculate_residuals(model, group) iv idiosyncratic_volatility(residuals) return pd.Series({ IV: iv, Obs: len(group), R2: model.rsquared }) except: return pd.Series({ IV: None, Obs: len(group), R2: None }) # 分组计算 iv_results merged_data.groupby([Stkcd, YearMonth]).apply(batch_compute_iv) iv_results iv_results.reset_index()结果分析时需关注以下质量指标观测值数量单月交易天数应大于15天假设22个交易日R平方过低可能预示模型设定问题IV极端值检查是否数据错误或特殊事件保存结果供后续研究# 保存到Excel iv_results.to_excel(Output/Idiosyncratic_Volatility.xlsx, indexFalse) # 也可保存到数据库 from sqlalchemy import create_engine engine create_engine(sqlite:///Output/finance.db) iv_results.to_sql(IV_Results, engine, if_existsreplace)5. 高级应用与注意事项实际应用中特质波动率计算有几个进阶技巧滚动窗口计算使用60个月滚动窗口提高稳定性from tqdm import tqdm def rolling_iv(stock_code, window60): stock_data merged_data[merged_data[Stkcd] stock_code] dates sorted(stock_data[Date].unique()) results [] for i in tqdm(range(window, len(dates))): window_data stock_data[ stock_data[Date].between(dates[i-window], dates[i-1]) ] if len(window_data) window * 0.8: # 缺失值超过20%则跳过 continue model ff3_regression(window_data) residuals calculate_residuals(model, window_data) iv idiosyncratic_volatility(residuals) results.append({ Stkcd: stock_code, Date: dates[i], IV: iv, R2: model.rsquared }) return pd.DataFrame(results)行业调整先按行业分类再计算相对特质波动率# 假设有行业分类数据 industry_data pd.read_excel(Data/Industry_Classification.xlsx) iv_industry pd.merge(iv_results, industry_data, onStkcd) # 计算行业中性化IV iv_industry[IV_Adj] iv_industry.groupby([Industry, YearMonth])[IV].transform( lambda x: x - x.mean() )常见问题排查指南回归不收敛检查因子间多重共线性VIF 10需处理确保收益率数值合理通常绝对值0.2特质波动率异常高验证是否使用了对数收益率检查是否包含停牌日数据结果不稳定增加滚动窗口长度考虑使用Robust Regression减少异常值影响在实盘应用中我们发现特质波动率因子通常需要与其他因子结合使用。例如将IV与动量因子结合构建多空组合或者作为风险模型中的控制变量。存储计算结果时建议同时保存中间指标如R平方、观测值数量以便后续质量检验。