首页  

《股票多因子模型实战:Python 核心代码解析》 笔记     所属分类 quant 浏览量 13
《股票多因子模型实战:Python 核心代码解析》 陆一潇 
聚焦于股票多因子模型的实战落地,以 Python 为工具,从因子构建、数据处理、模型验证到实盘应用,
系统拆解了多因子模型的核心逻辑与代码实现,是量化投资入门与进阶的实用参考书


量化投研全流程
数据→因子→模型→回测→优化

量化投研环境搭建(Python 库、数据来源);
多因子模型基础(因子分类、有效因子特征);
因子数据处理(清洗、标准化、中性化);
单因子检验(IC 值、分层回测、换手率);
多因子组合构建(因子加权、正交化、风险控制);
模型回测与绩效评估;
实战优化(因子衰减、参数调优、实盘适配)


第一章:量化投研环境与数据基础 核心工具栈(Python) 类别 核心库 用途 数据获取 Tushare/Wind/JoinQuant A 股行情、财务、行业数据获取 数据处理 Pandas/Numpy 清洗、转换、因子计算 因子分析 Scikit-learn 标准化、回归、中性化 回测与可视化 Matplotlib/Backtrader 回测框架、收益曲线绘制 高性能计算 Dask/Swifter 大规模因子数据并行处理 大规模数据建议用Parquet格式存储(比 CSV 节省 70% 空间,读取速度提升 5 倍) 第二章:多因子模型基础理论 2.1 因子分类(A 股核心有效因子) 因子类型 典型代表 逻辑说明 收益特征 量价因子 动量(20/60 日)、反转 趋势延续 / 均值回归 高频、收益波动大 价值因子 PE/PB/PCF 低估股票长期收益更高 低频、抗跌 成长因子 营收增速、净利润增速 高成长企业盈利增长带动股价 中高频、行业分化 质量因子 ROE/ROIC/ 资产负债率 盈利质量高的企业长期表现好 低频、稳健 风险因子 市值(Size)、波动率 小市值 / 低波动股票超额收益 长期有效、易拥挤 情绪因子 换手率、融资融券余额 市场情绪反映资金流向 高频、易衰减 2.2 有效因子的核心特征 单调性:因子值与股票收益呈稳定的正 / 负相关; 持续性:因子有效性跨周期(至少 1 年以上); 可解释性:有基本面 / 市场逻辑支撑(避免数据挖掘陷阱); 低相关性:多因子间相关性 < 0.5(避免共线性); 可交易性:考虑流动性(换手率 > 1%)、交易成本。 第三章:因子构建(代码 + 逻辑拆解) 3.1 量价因子(高频因子) 3.1.1 动量因子(避免未来函数) def build_momentum_factor(df, window=20, lag=1): """ 构建动量因子:window日累计收益率,滞后lag期(避免未来函数) :param df: 含close_adj的股票数据框 :param window: 统计窗口(20/60/120日) :param lag: 滞后期(至少1期,避免用当日数据预测当日) :return: 含动量因子的DataFrame """ # 计算window日收益率 df['momentum_raw'] = df.groupby('ts_code')['close_adj'].pct_change(window) # 滞后lag期,消除未来函数 df[f'momentum_{window}d'] = df.groupby('ts_code')['momentum_raw'].shift(lag) # 剔除极端值(3σ原则) df = remove_outlier(df, f'momentum_{window}d') return df def remove_outlier(df, col, sigma=3): """3σ剔除极端值:避免单只股票异常值影响""" mean = df[col].mean() std = df[col].std() df.loc[(df[col] > mean + sigma*std) | (df[col] < mean - sigma*std), col] = np.nan return df 3.1.2 换手率因子(情绪类) def build_turnover_factor(df, window=5): """构建5日平均换手率因子(标准化)""" # 计算滚动平均换手率 df['turnover_5d'] = df.groupby('ts_code')['turnover_rate'].rolling(window).mean().reset_index(0, drop=True) # 标准化(跨股票可比) df['turnover_5d_standard'] = df.groupby('trade_date')['turnover_5d'].apply( lambda x: (x - x.mean()) / x.std() ) return df 3.2 财务因子(低频因子) 3.2.1 核心问题:财务数据对齐 财务数据按 “报告期” 披露,需映射到 “交易日期”,书籍提供核心对齐逻辑: def map_finance_to_trade(fin_df, trade_df): """ 财务数据对齐交易日期: - 年报/中报/季报披露后,才能用于因子计算 """ # 1. 定义报告期对应的最早可用交易日期(如2023年报,2024-04-30后可用) fin_df['report_period'] = pd.to_datetime(fin_df['end_date']) fin_df['available_date'] = fin_df['report_period'].apply( lambda x: pd.Timestamp(f"{x.year+1}-04-30") if x.month == 12 # 年报 else pd.Timestamp(f"{x.year}-08-31") if x.month == 6 # 中报 else pd.Timestamp(f"{x.year}-10-31") if x.month == 9 # 三季报 else pd.Timestamp(f"{x.year}-04-30") # 一季报 ) # 2. 合并交易数据与财务数据(仅保留财务数据可用后的交易记录) merge_df = pd.merge(trade_df, fin_df[['ts_code', 'pe', 'roe', 'available_date']], on='ts_code', how='left') merge_df = merge_df[merge_df['trade_date'] >= merge_df['available_date']] return merge_df 3.2.2 ROE 因子(质量类) def build_roe_factor(fin_df, trade_df): """构建ROE因子(TTM,滚动四个季度)""" # 1. 计算ROE-TTM(避免单季度波动) fin_df['roe_ttm'] = fin_df.groupby('ts_code')['roe'].rolling(4).sum().reset_index(0, drop=True) # 2. 对齐交易日期 merge_df = map_finance_to_trade(fin_df, trade_df) # 3. 填充缺失值(用最新可用的ROE-TTM) merge_df['roe_ttm'] = merge_df.groupby('ts_code')['roe_ttm'].fillna(method='ffill') return merge_df 3.3 风险因子(市值 / 波动率) def build_risk_factors(df): """构建市值+波动率风险因子""" # 1. 市值因子(总市值取对数,消除量纲) df['size'] = np.log(df['total_mv']) # 2. 波动率因子(20日收益率标准差) df['volatility_20d'] = df.groupby('ts_code')['return'].rolling(20).std().reset_index(0, drop=True) return df 第四章:因子预处理(核心步骤) 4.1 预处理全流程 原始因子值 缺失值处理 极端值剔除 标准化 中性化 正交化 4.2 缺失值处理(分场景) 场景 处理方法 代码示例 单只股票短期缺失 前向填充(ffill) df['factor'] = df.groupby('ts_code')['factor'].ffill() 行业 / 整体大量缺失 行业均值填充 df['factor'] = df.groupby('industry')['factor'].fillna(df.groupby('industry')['factor'].mean()) 无有效数据(退市) 直接剔除 df = df[df['factor'].notna()] 4.3 标准化(消除量纲) 4.3.1 横截面标准化(每日 / 每月) def cross_section_standardize(df, factor_col, date_col='trade_date'): """ 横截面标准化:同一时间点,所有股票的因子值标准化 (避免时间序列标准化的“幸存者偏差”) """ def standardize_group(x): scaler = StandardScaler() x[factor_col + '_std'] = scaler.fit_transform(x[[factor_col]]) return x # 按日期分组标准化 df = df.groupby(date_col).apply(standardize_group).reset_index(drop=True) return df 4.3.2 行业内标准化(增强因子纯度) def industry_standardize(df, factor_col, industry_col, date_col='trade_date'): """行业内横截面标准化:先按行业分组,再标准化""" df[factor_col + '_ind_std'] = df.groupby([date_col, industry_col])[factor_col].apply( lambda x: (x - x.mean()) / x.std() ) return df 4.4 中性化(剔除干扰因素) 4.4.1 行业中性化(核心) def industry_neutralize_v2(df, factor_col, industry_col): """ 进阶行业中性化:回归法(更精准) 因子值 = 行业虚拟变量 * 系数 + 残差(中性化因子) """ # 1. 生成行业虚拟变量 industry_dummies = pd.get_dummies(df[industry_col], prefix='ind') df_with_dummies = pd.concat([df, industry_dummies], axis=1) # 2. 回归:因子值 ~ 行业虚拟变量 X = df_with_dummies[industry_dummies.columns] y = df_with_dummies[factor_col] model = LinearRegression() model.fit(X, y) # 3. 残差即为中性化因子 df[factor_col + '_neutral'] = y - model.predict(X) return df 4.4.2 市值中性化(剔除市值干扰) def size_neutralize(df, factor_col, size_col='size'): """市值中性化:因子值对市值回归,残差为中性化因子""" X = df[[size_col]] y = df[factor_col] model = LinearRegression() model.fit(X, y) df[factor_col + '_size_neutral'] = y - model.predict(X) return df 4.5 正交化(消除因子间共线性) def factor_orthogonalize(df, target_factor, other_factors): """ 因子正交化:目标因子剔除其他因子的影响 例:动量因子正交化(剔除市值+行业影响) """ X = df[other_factors] y = df[target_factor] model = LinearRegression() model.fit(X, y) df[target_factor + '_ortho'] = y - model.predict(X) return df 第五章:单因子检验(有效性验证) 5.1 核心检验指标 指标 计算公式 判定标准 IC 值 Spearman (因子值,下期收益) 绝对值 > 0.05,且显著(t 检验 p<0.05) IR 值 IC 均值 / IC 标准差 >0.5 为有效,>1 为优秀 分层收益 最高层 - 最低层累计收益 持续为正,且单调性明显 换手率 组合月度调仓比例 <30%(过高则交易成本侵蚀收益) 最大回撤 累计收益的最大下跌幅度 <10%(单因子) 5.2 IC 分析全流程(书籍核心代码) def ic_analysis_full(df, factor_col, return_col='next_return', date_col='trade_date'): """ 完整IC分析:月度IC、IC均值、IR、t检验、胜率 :param return_col: 下期收益率(如未来5/10/20日收益) """ # 1. 确保收益是“下期”(关键:避免未来函数) df[return_col] = df.groupby('ts_code')['return'].shift(-5) # 未来5日收益 # 2. 按月份分组计算IC df['month'] = df[date_col].dt.to_period('M') ic_results = [] for month, group in df.groupby('month'): # 剔除缺失值 group_clean = group.dropna(subset=[factor_col, return_col]) if len(group_clean) < 50: # 样本量不足跳过 continue # 计算Spearman IC ic = group_clean[[factor_col, return_col]].corr(method='spearman').iloc[0,1] ic_results.append({ 'month': month, 'ic': ic, 'sample_size': len(group_clean) }) ic_df = pd.DataFrame(ic_results) # 3. 计算核心统计量 ic_mean = ic_df['ic'].mean() ic_std = ic_df['ic'].std() ic_ir = ic_mean / ic_std if ic_std != 0 else 0 # t检验:IC均值是否显著不为0 t_stat = ic_mean / (ic_std / np.sqrt(len(ic_df))) p_value = ttest_1samp(ic_df['ic'], 0)[1] # 胜率:IC>0的月份占比 win_rate = len(ic_df[ic_df['ic'] > 0]) / len(ic_df) # 4. 输出结果 result = { 'ic_mean': round(ic_mean, 4), 'ic_std': round(ic_std, 4), 'ic_ir': round(ic_ir, 4), 't_stat': round(t_stat, 4), 'p_value': round(p_value, 4), 'win_rate': round(win_rate, 4), 'ic_series': ic_df } return result # 调用示例 ic_result = ic_analysis_full(df, 'momentum_20d_std_neutral', 'next_return') print(f"IC均值:{ic_result['ic_mean']},IR:{ic_result['ic_ir']},胜率:{ic_result['win_rate']}") 5.3 分层回测(可视化 + 统计) def quantile_backtest_full(df, factor_col, return_col='next_return', quantile=5, date_col='trade_date'): """ 5分层回测: - 按因子值从小到大分为Q1(最低)到Q5(最高) - 计算各层累计收益、夏普比率、最大回撤 """ # 1. 每日分层 df['quantile'] = df.groupby(date_col)[factor_col].rank(pct=True).apply( lambda x: int(x * quantile) + 1 ) # 2. 计算各层每日收益(等权) daily_return = df.groupby([date_col, 'quantile'])[return_col].mean().reset_index() daily_return_pivot = daily_return.pivot(index=date_col, columns='quantile', values=return_col) # 3. 计算累计收益 cumulative_return = (1 + daily_return_pivot).cumprod() # 4. 计算各层绩效指标 performance = {} for q in range(1, quantile+1): ret_series = daily_return_pivot[q] # 夏普比率(无风险利率取3%年化) sharpe = np.sqrt(252) * ret_series.mean() / ret_series.std() # 最大回撤 cum_ret = cumulative_return[q] drawdown = (cum_ret / cum_ret.cummax() - 1).min() # 年化收益 annual_return = (cum_ret.iloc[-1] ** (252/len(cum_ret))) - 1 performance[f'Q{q}'] = { 'annual_return': round(annual_return, 4), 'sharpe': round(sharpe, 4), 'max_drawdown': round(drawdown, 4), 'total_return': round(cum_ret.iloc[-1] - 1, 4) } # 5. 多空收益(Q5 - Q1) daily_return_pivot['long_short'] = daily_return_pivot[5] - daily_return_pivot[1] cumulative_return['long_short'] = (1 + daily_return_pivot['long_short']).cumprod() return cumulative_return, performance, daily_return_pivot # 可视化分层收益 def plot_quantile_return(cumulative_return): plt.figure(figsize=(12, 6)) for col in cumulative_return.columns: plt.plot(cumulative_return.index, cumulative_return[col], label=f'{col}') plt.title('因子分层累计收益') plt.xlabel('日期') plt.ylabel('累计收益(初始1)') plt.legend() plt.grid(True) plt.show() 5.4 因子衰减分析(时效性) def factor_decay_analysis(df, factor_col, max_lag=20): """ 因子衰减分析:计算不同滞后期的IC值 例:lag=0(当期因子)、lag=1(滞后1期)...lag=20 """ decay_results = [] for lag in range(max_lag+1): # 因子滞后lag期 df[f'factor_lag_{lag}'] = df.groupby('ts_code')[factor_col].shift(lag) # 计算IC ic = ic_analysis_full(df, f'factor_lag_{lag}')['ic_mean'] decay_results.append({ 'lag': lag, 'ic_mean': ic }) decay_df = pd.DataFrame(decay_results) # 可视化衰减曲线 plt.figure(figsize=(10, 5)) plt.plot(decay_df['lag'], decay_df['ic_mean'], marker='o') plt.title('因子衰减曲线') plt.xlabel('滞后期(交易日)') plt.ylabel('IC均值') plt.grid(True) plt.show() return decay_df 第六章:多因子组合构建 6.1 因子加权方法对比 加权方法 计算逻辑 适用场景 代码示例 等权法 各因子得分等权相加 因子有效性相近 score = (f1 + f2 + f3)/3 IC 加权法 按 IC 均值绝对值加权 因子有效性差异大 weights = IC_abs / sum(IC_abs) 回归加权法 收益~因子值 回归系数加权 追求收益最大化 见下方代码 机器学习加权 XGBoost/LightGBM 拟合因子权重 因子间非线性关系 进阶拓展 6.1.1 回归加权法(书籍核心) def regression_weight(df, factor_cols, return_col='next_return'): """ 回归加权:通过截面回归计算因子权重 每日回归:下期收益 = w1*f1 + w2*f2 + ... + wn*fn + ε """ # 1. 按日期分组回归 weight_list = [] for date, group in df.groupby('trade_date'): group_clean = group.dropna(subset=factor_cols + [return_col]) if len(group_clean) < 100: continue X = group_clean[factor_cols] y = group_clean[return_col] model = LinearRegression() model.fit(X, y) # 存储每日权重 weights = dict(zip(factor_cols, model.coef_)) weights['trade_date'] = date weight_list.append(weights) weight_df = pd.DataFrame(weight_list) # 2. 计算平均权重(用于组合构建) avg_weights = weight_df[factor_cols].mean().to_dict() # 3. 计算多因子得分 df['multi_factor_score'] = 0 for factor, weight in avg_weights.items(): df['multi_factor_score'] += df[factor] * weight return df, avg_weights 6.2 多因子组合构建流程 def build_multi_factor_portfolio(df, score_col='multi_factor_score', top_n=100): """ 构建多因子组合: 1. 每日按多因子得分排序,选前N只股票 2. 等权配置,月度调仓 """ # 1. 按月调仓(每月最后一个交易日调仓) df['month'] = df['trade_date'].dt.to_period('M') df['is_month_end'] = df.groupby('month')['trade_date'].rank(ascending=False) == 1 # 2. 筛选调仓日,选前top_n股票 rebalance_dates = df[df['is_month_end']]['trade_date'].unique() portfolio = [] for date in rebalance_dates: # 当日股票得分 score_df = df[df['trade_date'] == date].dropna(subset=[score_col]) # 按得分降序选前top_n top_stocks = score_df.nlargest(top_n, score_col)['ts_code'].tolist() portfolio.append({ 'rebalance_date': date, 'stocks': top_stocks }) portfolio_df = pd.DataFrame(portfolio) # 3. 计算组合收益(持有至下月调仓) def calculate_port_return(row): # 调仓日至下月调仓日的收益 start_date = row['rebalance_date'] end_date = portfolio_df[portfolio_df['rebalance_date'] > start_date]['rebalance_date'].min() if pd.isna(end_date): end_date = df['trade_date'].max() # 筛选持仓股票在该时间段的收益 hold_df = df[(df['ts_code'].isin(row['stocks'])) & (df['trade_date'] >= start_date) & (df['trade_date'] <= end_date)] # 等权日收益 daily_ret = hold_df.groupby('trade_date')['return'].mean() # 累计收益 cum_ret = (1 + daily_ret).prod() - 1 return pd.Series({ 'start_date': start_date, 'end_date': end_date, 'cum_return': cum_ret, 'daily_ret_mean': daily_ret.mean() }) portfolio_perf = portfolio_df.apply(calculate_port_return, axis=1) return portfolio_df, portfolio_perf 6.3 风险控制(书籍进阶内容) 6.3.1 行业暴露控制 def control_industry_exposure(df, score_col, industry_col, max_exposure=0.2): """ 行业暴露控制:单行业权重不超过max_exposure """ # 1. 初始得分排序 df_sorted = df.sort_values(score_col, ascending=False) # 2. 按行业分组,计算权重上限 industry_cap = df_sorted.groupby(industry_col).size() * max_exposure # 3. 按行业限额筛选股票 selected_stocks = [] for industry in df_sorted[industry_col].unique(): industry_stocks = df_sorted[df_sorted[industry_col] == industry] n_select = int(industry_cap[industry]) selected = industry_stocks.head(n_select) selected_stocks.append(selected) selected_df = pd.concat(selected_stocks) return selected_df 6.3.2 市值暴露控制 def control_size_exposure(df, score_col, size_col='size', target_size=0): """ 市值暴露控制:组合市值均值接近目标值(如0,即市场中性) """ # 1. 初始筛选 df_sorted = df.sort_values(score_col, ascending=False).head(200) # 2. 计算当前组合市值均值 current_size = df_sorted[size_col].mean() # 3. 调整筛选,使市值均值接近target_size if current_size > target_size: # 多选小市值股票 df_sorted['size_adjusted_score'] = df_sorted[score_col] - (df_sorted[size_col] - target_size) else: # 多选大市值股票 df_sorted['size_adjusted_score'] = df_sorted[score_col] + (target_size - df_sorted[size_col]) # 重新排序选前100 selected_df = df_sorted.sort_values('size_adjusted_score', ascending=False).head(100) return selected_df 第七章:回测与绩效评估 7.1 回测核心注意事项 避免未来函数:因子值必须在调仓日前可获取; 交易成本:佣金(0.03%)+ 滑点(0.05%)+ 印花税(0.1%,卖出); 流动性约束:剔除换手率 < 1%、流通市值 < 50 亿的股票; 调仓频率:月度 / 双周调仓(高频调仓成本高) 7.2 绩效评估指标体系 def evaluate_portfolio(return_series): """ 组合绩效评估:年化收益、夏普、最大回撤、胜率等 :param return_series: 日收益序列 """ # 基础指标 annual_return = (1 + return_series).prod() ** (252 / len(return_series)) - 1 sharpe = np.sqrt(252) * return_series.mean() / return_series.std() # 最大回撤 cum_return = (1 + return_series).cumprod() drawdown = (cum_return / cum_return.cummax() - 1).min() # 胜率 win_rate = len(return_series[return_series > 0]) / len(return_series) # 盈亏比 profit = return_series[return_series > 0].mean() loss = return_series[return_series < 0].mean() profit_loss_ratio = abs(profit / loss) if loss != 0 else np.inf # 信息比率(相对沪深300) hs300_return = get_hs300_return(return_series.index) # 需提前获取沪深300收益 excess_return = return_series - hs300_return ir = excess_return.mean() / excess_return.std() * np.sqrt(252) return { 'annual_return': round(annual_return, 4), 'sharpe_ratio': round(sharpe, 4), 'max_drawdown': round(drawdown, 4), 'win_rate': round(win_rate, 4), 'profit_loss_ratio': round(profit_loss_ratio, 4), 'information_ratio': round(ir, 4) } 第八章:实战优化与常见坑点 8.1 因子失效原因与应对 失效原因 典型表现 应对方法 市场风格切换 IC 值突然反转 动态因子权重(如月度更新 IC 权重) 因子拥挤 分层收益收窄 加入换手率约束,避开热门股票 数据挖掘陷阱 样本内有效,样本外失效 样本外验证(分训练集 / 测试集) 交易成本侵蚀 回测收益高,实盘收益低 提高调仓周期,控制换手率 8.2 实战优化技巧 因子融合:将量价因子(高频)与财务因子(低频)结合,兼顾短期与长期; 动态调仓:根据市场波动率调整仓位(高波动时降仓); 样本外验证:按时间划分训练集(2018-2022)、测试集(2023-2024),避免过拟合; 实盘适配:回测时加入真实交易成本,模拟滑点(如买入价 + 0.02%,卖出价 - 0.02%) 8.3 常见代码坑点 坑点 表现 解决方案 未来函数 回测收益极高,实盘亏损 所有因子滞后至少 1 期,收益用下期 数据对齐错误 财务因子用了未披露的数据 严格映射 “报告期 - 可用日期” 幸存者偏差 只包含当前上市的股票 用复权数据,保留退市股票记录 行业分类过时 因子中性化失效 每月更新申万行业分类数据 注意点: 因子的 “有效性” 是动态的,需持续监控 IC、分层收益; 预处理(标准化 / 中性化)是因子有效与否的关键; 回测必须贴近实盘,考虑交易成本、流动性、调仓频率; 多因子模型的核心是 “因子互补”,而非简单堆砌。 建议学习路径: 先复现单因子检验流程, 再尝试 2-3 个低相关因子构建组合, 最后加入风险控制与实盘适配, 逐步从 “回测有效” 走向 “实盘盈利”
一 其他示例代码 数据获取与清洗 import tushare as ts import pandas as pd import numpy as np # 1. 初始化Tushare(需申请token) ts.set_token("你的Tushare Token") pro = ts.pro_api() # 2. 获取股票日度行情数据 def get_stock_data(ts_code, start_date, end_date): df = pro.daily(ts_code=ts_code, start_date=start_date, end_date=end_date) # 数据清洗:处理缺失值、排序 df = df.dropna(subset=['close', 'vol']).sort_values('trade_date').reset_index(drop=True) # 转换日期格式 df['trade_date'] = pd.to_datetime(df['trade_date']) # 计算日收益率 df['return'] = df['close'].pct_change() return df # 3. 获取沪深300成分股(因子分析常用标的) def get_hs300_stocks(date): hs300 = pro.index_weight(index_code='000300.SH', start_date=date, end_date=date) return hs300['con_code'].tolist() # 示例调用 hs300_codes = get_hs300_stocks('20240101') stock_data = get_stock_data('000001.SZ', '20230101', '20240101') 二 量价因子:动量因子(20 日累计收益率) def build_momentum_factor(df, window=20): """构建20日动量因子""" df['momentum_20d'] = df['close'].pct_change(window).shift(1) # 滞后1期避免未来函数 return df # (2)财务因子:市盈率(PE) def build_pe_factor(stock_code, date): """获取单只股票的PE因子""" fina_indicator = pro.fina_indicator(ts_code=stock_code, start_date=date, end_date=date) pe = fina_indicator['pe'].iloc[0] if not fina_indicator.empty else np.nan return pe # (3)风险因子:市值因子 def build_size_factor(stock_code, date): """构建市值因子(总市值取对数)""" daily_basic = pro.daily_basic(ts_code=stock_code, trade_date=date) size = np.log(daily_basic['total_mv'].iloc[0]) if not daily_basic.empty else np.nan return size 三 因子预处理(标准化 + 中性化) 因子的有效性依赖于合理的预处理,书籍重点讲解了 Z-score 标准化和行业中性化: from sklearn.preprocessing import StandardScaler def standardize_factor(factor_df): """Z-score标准化:消除量纲影响""" scaler = StandardScaler() factor_df['factor_standard'] = scaler.fit_transform(factor_df[['factor_value']]) return factor_df def industry_neutralize(factor_df, industry_col): """行业中性化:剔除行业对因子的影响""" # 按行业分组,计算组内残差(因子值 - 行业均值) factor_df['industry_mean'] = factor_df.groupby(industry_col)['factor_value'].transform('mean') factor_df['factor_neutral'] = factor_df['factor_value'] - factor_df['industry_mean'] return factor_df 四. 单因子检验(核心:IC 值 + 分层回测) 单因子检验是验证因子有效性的关键,书籍详细实现了信息系数(IC)和分层回测: (1)信息系数(IC):因子与下期收益的相关性 def calculate_ic(factor_df, return_col, factor_col): """计算IC值(Spearman相关系数)""" # 确保因子与收益对应下期(避免未来函数) ic = factor_df[[factor_col, return_col]].corr(method='spearman').iloc[0,1] return ic # 批量计算月度IC def monthly_ic_analysis(factor_df): factor_df['month'] = factor_df['trade_date'].dt.to_period('M') ic_series = factor_df.groupby('month').apply( lambda x: calculate_ic(x, 'next_return', 'factor_neutral') ) # 输出IC均值、IR(信息比率=IC均值/IC标准差) ic_mean = ic_series.mean() ic_ir = ic_mean / ic_series.std() return {'ic_mean': ic_mean, 'ic_ir': ic_ir, 'ic_series': ic_series} (2)分层回测:验证因子的收益区分能力 def quantile_backtest(factor_df, quantile=5): """分层回测:将股票按因子值分为N层,计算各层收益""" # 按因子值排序,分层 factor_df['quantile'] = factor_df.groupby('trade_date')['factor_neutral'].rank(pct=True).apply( lambda x: int(x * quantile) + 1 ) # 计算各层日均收益 quantile_return = factor_df.groupby(['trade_date', 'quantile'])['next_return'].mean().reset_index() # 透视表:日期为行,分层为列 quantile_pivot = quantile_return.pivot(index='trade_date', columns='quantile', values='next_return') # 计算各层累计收益 cumulative_return = (1 + quantile_pivot).cumprod() return cumulative_return, quantile_pivot 五. 多因子组合构建 在单因子有效基础上,书籍讲解了多因子加权(等权、IC 加权、回归加权)和风险约束: def multi_factor_weight(factor_dict, weight_method='ic_weight'): """多因子加权:IC加权法""" # factor_dict: {因子名: 因子值, ...} factor_df = pd.DataFrame(factor_dict) # 计算各因子的IC均值(提前通过单因子检验得到) ic_means = { 'momentum_20d': 0.08, 'pe': -0.06, 'size': -0.05 } # 示例IC均值,实际需计算 if weight_method == 'ic_weight': # 按IC绝对值加权(归一化) ic_abs = np.abs(list(ic_means.values())) weights = ic_abs / sum(ic_abs) elif weight_method == 'equal': weights = [1/len(factor_df.columns)] * len(factor_df.columns) # 计算多因子综合得分 factor_df['multi_factor_score'] = np.dot(factor_df.values, weights) return factor_df

上一篇    
均值回归策略实例

python @classmethod 和 @staticmethod 区别

股票多因子模型实战