Python股票量化交易全链路实战:从akshare数据清洗到backtrader回测
发布时间:2026/10/1 1:34:02来源:尧图网络
简介本资源是一份面向量化交易初学者与进阶学习者的系统性学习笔记聚焦股票市场中的数学建模、策略开发与实操落地帮助读者构建从理论理解到代码实现的完整能力链。压缩包共39个文件含16个Python脚本覆盖数据采集、因子计算、回测框架与主力资金分析、6个SQL文件用于财务与行情数据库建模、4个Markdown文档含粤传媒、万山红等实战案例解析、3个Shell脚本支持定时数据更新以及数据库文件、配置说明与可视化图表等整体仅822KB轻量易用。已有91人下载学习适合希望掌握Python量化开发全流程的学习者。笔记不仅梳理了时间序列预测、机器学习选股、风险度量VaR/最大回撤等核心方法更通过可运行代码与真实数据结构如daily_history.pystock.sql组合体现策略设计逻辑附带crontab自动化调度、SQLite本地数据库初始化等工程细节兼顾教学性与实用性。1. 这不是「炒股笔记」而是用 Python 搭建可验证、可回测、可迭代的股票量化交易分析最小闭环从数据获取、特征工程、策略逻辑到绩效评估全链路实操指南你下载了一个叫基于股票量化交易分析的学习笔记.zip的压缩包解压后发现一堆.py文件、data/目录和几份.md文档——但打开main.py却报错ModuleNotFoundError: No module named akshare运行backtest.py提示KeyError: close翻看strategy_notes.md里面写着「使用三因子模型选股」却没说明因子怎么计算、时间窗口怎么设、中性化是否做、IC 值怎么算。这不是学习笔记这是「玄学残卷」。真正的股票量化交易分析不是抄代码、不是调参、更不是把 K 线图喂给大模型然后等它说「明天涨」。它是一套有明确输入行情基本面另类数据、确定流程清洗→对齐→标准化→信号生成→组合构建→归因、可复现结果年化收益、最大回撤、夏普比、换手率的工程闭环。本篇不讲「如何用大模型分析 K 线图」这种悬浮概念只聚焦一个一线工程师每天在做的真实动作用akshare获取干净日频 A 股数据用pandas构建多因子信号用zipline或轻量级backtrader实现滚动回测用pyfolio输出专业归因报告。适合刚跑通pip install akshare的新手也适合被「同花顺 SuperMind 策略模板」卡在信号延迟问题上的熟手——所有代码本地可跑所有参数有依据所有坑都踩过。2. 用 akshare 获取结构化、时序对齐、无缺失的 A 股基础行情与财务数据避开「数据脏、字段乱、停牌跳空」三大陷阱股票量化分析的第一道生死线从来不是模型多深而是数据有多准。akshare是目前中文社区最稳定、更新最勤、字段最全的免费金融数据接口但它不是「开箱即用」的玩具。直接akshare.stock_zh_a_daily(symbolsh600519, start_date20200101)拿到的数据大概率会让你的后续计算全线崩溃涨停跌停导致的异常收益率、ST 股未剔除带来的信号污染、新股上市首日无成交量造成的 NaN 扩散、以及最关键的——不同股票交易日历不一致导致的merge后大量NaN。下面这三步是我过去三年在实盘前数据准备阶段雷打不动的 checklist。2.1 统一交易日历 全市场股票池同步拉取避免「某只股有数据、另一只没有」的对齐灾难不要单只股票循环拉取。必须先获取全市场 A 股代码列表再统一按沪深交易所官方交易日历对齐时间轴。否则你会在做行业分组或市值加权时发现某天len(df) 0——因为那天创业板某只小盘股停牌而你的merge默认howinner直接把整行干掉。import akshare as ak import pandas as pd import numpy as np from datetime import datetime, timedelta # 1. 获取全市场 A 股代码含退市股便于历史回溯 stock_zh_a_spot_df ak.stock_zh_a_spot() all_symbols stock_zh_a_spot_df[symbol].tolist() # 如 sh600519, sz000001 # 2. 获取最新交易日历注意akshare 返回的是 str 格式日期需转 datetime trade_days ak.tool_trade_date_hist_sina() trade_days[date] pd.to_datetime(trade_days[trade_date]) trade_days trade_days.sort_values(date).reset_index(dropTrue) # 取最近 3 年交易日排除节假日、周末 start_date (datetime.now() - timedelta(days1095)).strftime(%Y%m%d) end_date datetime.now().strftime(%Y%m%d) # 3. 批量拉取行情关键用 map tqdm 避免超时且强制指定 date 列为 datetime def fetch_stock_data(symbol): try: df ak.stock_zh_a_daily(symbolsymbol, start_datestart_date, end_dateend_date) if not df.empty: df[date] pd.to_datetime(df[date]) df df.set_index(date).sort_index() # 强制重采样到交易日历缺失值用前向填充但保留停牌标识 df df.reindex(trade_days[date]).fillna(methodffill) # 注意仅适用于价格类字段 df[symbol] symbol return df except Exception as e: print(fFailed to fetch {symbol}: {e}) return None # 并行拉取实际项目建议用 concurrent.futures.ProcessPoolExecutor from tqdm import tqdm raw_dfs [] for symbol in tqdm(all_symbols[:200]): # 先试 200 只避免内存爆炸 df fetch_stock_data(symbol) if df is not None: raw_dfs.append(df) # 合并成宽表列symbol open/high/low/close/volume full_df pd.concat(raw_dfs, axis0, ignore_indexFalse) full_df full_df.reset_index().rename(columns{index: date})提示ak.stock_zh_a_daily()返回的date是str类型必须转datetime再set_index否则reindex会失败reindex(trade_days[date])是对齐核心它让所有股票共享同一时间索引哪怕某天某只股停牌该行也存在值为NaN或前向填充值后续可用df[close].isna()显式标记停牌。2.2 财务数据清洗用akshare.stock_financial_abstract替代「手动爬财报 PDF」的原始方案很多学习笔记还在教你怎么用pdfplumber解析 PDF 财报这在实盘中是自杀行为。akshare.stock_financial_abstract()直接返回结构化季度财务摘要营收、净利润、ROE、资产负债率等但字段名是中文且带空格且存在大量--、-、None混合的脏值。必须做三件事字段标准化、数值强转、空值策略。# 获取全市场财务摘要按报告期非交易日 fin_df ak.stock_financial_abstract(symbolall) # 字段清洗去空格、转英文、小写 fin_df.columns [col.strip().replace( , _).replace(, _).replace(, ).lower() for col in fin_df.columns] # 关键字段映射避免中文字段名引发 KeyError col_map { 股票代码: symbol, 报告期: report_period, 营业总收入: operating_revenue, 归属于母公司股东的净利润: net_profit_parent, 净资产收益率roe: roe, 资产负债率: asset_liability_ratio } fin_df fin_df.rename(columnscol_map) # 数值清洗将 --、-、None 统一转为 np.nan再强制转 float for col in [operating_revenue, net_profit_parent, roe, asset_liability_ratio]: if col in fin_df.columns: fin_df[col] pd.to_numeric(fin_df[col].replace([--, -, ], np.nan), errorscoerce) # 报告期标准化转为 datetime如 2023Q3 → 2023-09-30 def q2date(q_str): if pd.isna(q_str) or not isinstance(q_str, str): return pd.NaT try: year int(q_str[:4]) quarter int(q_str[-1]) month [3, 6, 9, 12][quarter - 1] return pd.Timestamp(year, month, 1) pd.offsets.MonthEnd(0) except: return pd.NaT fin_df[report_date] fin_df[report_period].apply(q2date) fin_df fin_df.dropna(subset[report_date, symbol])参数说明q2date函数是关键——财报报告期是「季度末」不是「季度初」。2023Q3对应2023-09-30而非2023-07-01。这个细节决定你在做「T1」信号时是否把未发布的财报当作已知信息未来函数。pd.offsets.MonthEnd(0)确保返回当月最后一天这才是财报截止日。2.3 构建「干净日频宽表」把行情、财务、行业标签三表合一为因子计算铺平道路最终目标是得到一张date × symbol的宽表每行代表某只股票在某天的状态字段包括open,high,low,close,volume,pe,pb,roe,industry。这里不能简单merge因为财务数据是季度频行情是日频行业标签是静态。必须用asof或ffill实现「向前填充财务数据」并用akshare.stock_info_sh_name_code()补全行业。# 1. 行业映射表静态 ind_df ak.stock_info_sh_name_code() ind_df ind_df[[code, name, exchange, sector]].drop_duplicates() ind_df[symbol] ind_df[code].apply(lambda x: fsh{x} if x.startswith(6) else fsz{x}) # 2. 合并行情与行业 daily_df full_df.merge(ind_df[[symbol, sector]], onsymbol, howleft) # 3. 合并财务数据按 report_date 向前填充确保 T 日看到的是最新已发布财报 fin_df fin_df.sort_values([symbol, report_date]) daily_df daily_df.sort_values([symbol, date]) # 关键groupby symbol 后对每个股票单独做 asof merge daily_df daily_df.groupby(symbol).apply( lambda g: pd.merge_asof( g.sort_values(date), fin_df[fin_df[symbol]g.name][[report_date, roe, pe, pb]].sort_values(report_date), left_ondate, right_onreport_date, directionbackward, allow_exact_matchesTrue ) ).reset_index(dropTrue) # 4. 最终宽表删除中间冗余列确保 date 和 symbol 为主键 clean_df daily_df[[ date, symbol, open, high, low, close, volume, sector, roe, pe, pb ]].dropna(subset[close, volume]) # 至少要有收盘价和成交量 clean_df clean_df.set_index([date, symbol]).sort_index()逻辑说明pd.merge_asof是处理「时间不对齐」数据的黄金函数。它对每个symbol分组后在date上找report_date ≤ date的最近一条财报记录完美规避「用未来财报预测今天价格」的未来函数陷阱。directionbackward是硬性要求allow_exact_matchesTrue允许财报发布日当天即生效。3. 构建可复现的多因子信号从「市盈率倒数」到「三因子合成得分」的全流程代码实现有了干净宽表下一步是生成交易信号。很多学习笔记止步于「计算 PE、PB、ROE」但真正驱动收益的是因子暴露度exposure和因子方向long/short。比如 PE 低是价值但若全市场 PE 都高单纯选 PE 最低的 10% 可能仍是高估。必须做横截面标准化z-score和行业中性化。本节以「价值因子PE 倒数 质量因子ROE 动量因子20 日收益率」为例给出可直接运行的三因子合成代码。3.1 单因子计算为什么「PE 倒数」比「PE」更适合作为价值因子PE市盈率本身右偏严重且存在大量极端值如亏损股 PE 为负或无穷大。直接用1/pe会放大噪声。工业界标准做法是① 剔除 PE ≤ 0 或 PE 100 的股票② 对剩余股票做横截面 z-score③ 用1/pe的符号反转即 PE 越低得分越高。def calc_value_factor(df): 价值因子调整后的 PE 倒数剔除异常值 z-score 符号反转 输入clean_dfindex[date,symbol]columns 包含 pe 输出Seriesindex[date,symbol]值为因子得分 # 1. 剔除异常 PEPE 0 或 PE 100 valid_mask (df[pe] 0) (df[pe] 100) pe_series df.loc[valid_mask, pe].copy() # 2. 计算 1/pe但用 clip 防止极端值如 PE1 → 1.0PE100 → 0.01 inv_pe (1 / pe_series).clip(0.01, 1.0) # 限制在 [0.01, 1.0] 区间 # 3. 按日期分组做横截面 z-score减均值除标准差 inv_pe_z inv_pe.groupby(date).apply(lambda x: (x - x.mean()) / x.std(ddof1)) # 4. 符号反转PE 越低价值越高所以得分应为正 # 注意z-score 后低 PE 股票自然得分为正无需额外 * -1 return inv_pe_z # 应用 clean_df[value_score] calc_value_factor(clean_df)参数说明clip(0.01, 1.0)是关键防爆措施。若不做 clipPE0.5 的股票1/pe2.0PE0.1 的股票1/pe10.0后者会主导整个截面分布导致 z-score 失真。ddof1使用样本标准差n-1更稳健。3.2 动量因子用 20 日收益率替代「K 线图识别」的工程化表达「如何使用大模型分析不同股票的 K 线图」是伪需求。动量本质是价格持续性数学表达就是close / close.shift(20) - 1。但必须处理停牌、涨跌停导致的收益率失真。def calc_momentum_factor(df): 动量因子20 日价格动量修正停牌与涨跌停 # 1. 计算日收益率但用 shift(1) 避免当日数据污染 df_sorted df.sort_index(level[date, symbol]) df_sorted[ret_1d] df_sorted[close].pct_change() # 2. 识别涨跌停当日涨跌幅 9.8% 或 -9.8% 视为非正常交易ST 股为 5% # 简化版用 volume 是否为 0 判断停牌用 ret_1d 绝对值 0.098 判断涨跌停 df_sorted[is_suspension] df_sorted[volume] 0 df_sorted[is_limit] df_sorted[ret_1d].abs() 0.098 # 3. 对非停牌、非涨跌停日计算 20 日动量否则用前值填充 def rolling_ret(group): # 在 group 内单只股票对 close 做 20 日滚动 close_series group[close] # 用 dropnaFalse 确保返回长度一致 ret_20 close_series / close_series.shift(20) - 1 # 将停牌/涨跌停日的动量设为 NaN再前向填充但不超过 20 日 mask group[is_suspension] | group[is_limit] ret_20[mask] np.nan ret_20 ret_20.fillna(methodffill, limit20) # 最多填充 20 天 return ret_20 momentum df_sorted.groupby(symbol, group_keysFalse).apply(rolling_ret) momentum_z momentum.groupby(date).apply(lambda x: (x - x.mean()) / x.std(ddof1)) return momentum_z clean_df[momentum_score] calc_momentum_factor(clean_df)逻辑说明limit20是硬约束。若一只股票连续停牌 30 天其动量值不应继承 30 天前的旧值而应保持NaN直到复牌。这避免了「用半年前的动量信号交易今日」的未来函数。3.3 三因子合成与行业中性化为什么「直接加权」会失效直接score 0.4*value 0.3*momentum 0.3*roe是典型新手错误。不同因子量纲不同value 是 z-scoreroe 是百分比且行业偏差巨大银行股 PE 普遍低科技股 ROE 普遍高。必须做两步① 各因子独立 z-score② 按行业分组对每个行业内的股票做因子 z-score即行业中性化。def industry_neutralize(df, factor_col, industry_colsector): 行业中性化对每个行业内的因子值做 z-score # 先按日期分组再按行业分组 def neutralize_group(group): # group 是某天所有股票 if len(group) 5: # 行业股票数太少跳过中性化 return group[factor_col] # 对每个行业子组做 z-score result pd.Series(indexgroup.index, dtypefloat) for sector, sector_group in group.groupby(industry_col): if len(sector_group) 3: z (sector_group[factor_col] - sector_group[factor_col].mean()) / sector_group[factor_col].std(ddof1) result.loc[sector_group.index] z else: result.loc[sector_group.index] np.nan return result return df.groupby(date).apply(neutralize_group) # 1. 各因子独立 z-score clean_df[value_z] clean_df.groupby(date)[value_score].apply( lambda x: (x - x.mean()) / x.std(ddof1) ) clean_df[momentum_z] clean_df.groupby(date)[momentum_score].apply( lambda x: (x - x.mean()) / x.std(ddof1) ) clean_df[roe_z] clean_df.groupby(date)[roe].apply( lambda x: (x - x.mean()) / x.std(ddof1) ) # 2. 行业中性化 clean_df[value_neu] industry_neutralize(clean_df, value_z) clean_df[momentum_neu] industry_neutralize(clean_df, momentum_z) clean_df[roe_neu] industry_neutralize(clean_df, roe_z) # 3. 等权合成也可用 IC 加权见第 5 章 clean_df[factor_score] ( clean_df[value_neu] * 0.4 clean_df[momentum_neu] * 0.3 clean_df[roe_neu] * 0.3 )避坑点industry_neutralize中if len(sector_group) 3是必须的。若某个行业当天只有 1 只股票如「多元金融」对其做 z-score 会得到0/0结果为inf或nan后续排序全崩。4. 回测引擎选型与配置为什么放弃 zipline 选择 backtrader十行代码跑通滚动回测的最小可行方案很多学习笔记推荐zipline但它依赖numpy1.24、pandas2.0在 Python 3.11 环境下安装成功率低于 30%且文档陈旧、调试黑盒。backtrader虽然 API 略重但纯 Python 实现、兼容性好、日志清晰是当前最可靠的轻量级回测框架。本节给出一个「可复制、可调试、可扩展」的backtrader回测脚本支持① 滚动窗口避免未来函数② 按因子得分选股③ 等权持仓④ 手续费与滑点模拟。4.1 数据适配把 pandas DataFrame 转成 backtrader 可读的 DataFeedbacktrader不接受宽表必须为每只股票创建独立的pandasdata实例。但全市场 5000 只股全加载会 OOM。解决方案回测时只加载「当前滚动窗口内因子得分 top N」的股票。import backtrader as bt import pandas as pd class PandasData(bt.feeds.PandasData): # 增加 volume 字段默认不包含 lines (volume,) params ( (datetime, None), (open, open), (high, high), (low, low), (close, close), (volume, volume), (openinterest, -1), ) def create_datafeed(df, symbol, fromdate, todate): 为单只股票创建 datafeed stock_df df[df[symbol] symbol].copy() stock_df stock_df.set_index(date).sort_index() stock_df stock_df.loc[fromdate:todate] if len(stock_df) 10: return None data PandasData(datanamestock_df) return data # 示例为因子得分最高的 50 只股创建 datafeeds def get_top_stocks(factor_df, date, n50): 获取某天因子得分 top n 的股票 day_df factor_df[factor_df.index.get_level_values(date) date] if len(day_df) 0: return [] top_n day_df.nlargest(n, factor_score)[symbol].tolist() return top_n # 主回测类 class FactorStrategy(bt.Strategy): params ( (n_select, 50), (commission_pct, 0.0003), # 万三 (slippage, 0.001), # 千一滑点 ) def __init__(self): self.inds {} self.datafeeds {} def next(self): # 每月初调仓 if self.datas[0].datetime.date(0).day 1: self.rebalance() def rebalance(self): # 1. 获取当前日期 current_date self.datas[0].datetime.date(0) # 2. 获取当天 top 50 股票 top_symbols get_top_stocks(clean_df, current_date, self.p.n_select) if not top_symbols: return # 3. 计算目标权重等权 target_weight 1.0 / len(top_symbols) if top_symbols else 0 # 4. 执行调仓 for data in self.datas: symbol data._name if symbol in top_symbols: self.order_target_percent(data, target_weight) else: self.order_target_percent(data, 0.0)逻辑说明self.order_target_percent(data, target_weight)是核心。它自动计算需买入/卖出多少股以达到目标仓位比例内置手续费和滑点计算。target_weight是比例0~1不是股数彻底规避市值计算错误。4.2 运行回测用 10 行代码启动滚动窗口回测滚动窗口rolling window是避免未来函数的唯一正解。不能用2020-01-01到2024-01-01一次性回测而应从2020-01-01开始每 3 个月滚动一次训练集用最新因子模型预测下月信号。# 1. 初始化 cerebro cerebro bt.Cerebro() cerebro.broker.setcash(1000000.0) cerebro.broker.setcommission(commission0.0003) cerebro.addstrategy(FactorStrategy, n_select50) # 2. 设置回测时间范围滚动窗口起点 start_date pd.Timestamp(2020-01-01) end_date pd.Timestamp(2024-01-01) window_size pd.DateOffset(months3) # 3. 滚动加载数据关键每次只加载当前窗口内 top 股票 current_start start_date while current_start end_date: current_end min(current_start window_size, end_date) # 获取当前窗口内所有交易日 window_days clean_df.index.get_level_values(date).unique() window_days window_days[(window_days current_start) (window_days current_end)] if len(window_days) 20: current_start window_size continue # 获取窗口内所有可能涉及的股票top 100 预留 candidate_symbols set() for date in window_days: top get_top_stocks(clean_df, date, 100) candidate_symbols.update(top) # 为每只候选股创建 datafeed for symbol in candidate_symbols: datafeed create_datafeed(clean_df, symbol, current_start, current_end) if datafeed is not None: datafeed._name symbol cerebro.adddata(datafeed) # 运行回测仅当前窗口 print(fRunning backtest from {current_start} to {current_end}) results cerebro.run() # 更新滚动窗口 current_start pd.DateOffset(months1) # 每月滚动避免信号滞后参数说明pd.DateOffset(months1)是关键节奏。每月调仓一次确保信号时效性。若用months3则信号滞后 3 个月实盘不可用。4.3 避坑 / 常见问题 / 排查backtrader 回测必踩的 4 个坑现象回测结果中cerebro.broker.getvalue()曲线平直无任何交易原因get_top_stocks()返回空列表或create_datafeed()因len(stock_df) 10返回None导致cerebro.adddata(None)静默失败解决在create_datafeed()中加print(fLoaded {symbol} with {len(stock_df)} days)检查clean_df是否有date和symbol两级索引现象order_target_percent报错ZeroDivisionError: float division by zero原因top_symbols为空target_weight 1.0 / 0解决在rebalance()中加if not top_symbols: return并记录日志self.log(fNo stocks selected on {current_date})现象回测收益远高于指数但实盘亏损原因未考虑流动性——volume为 0 的股票被选入但无法成交解决在get_top_stocks()中增加流动性过滤day_df day_df[day_df[volume] 10000]日成交额 1000 万元现象cerebro.run()运行极慢内存飙升原因一次性加载全市场 5000 只股的 datafeed解决严格按滚动窗口 top N 加载candidate_symbols限制在 200 以内用gc.collect()清理上一轮 datafeed5. 绩效归因与因子有效性验证用 pyfolio 输出专业报告拒绝「年化 20%」的幻觉回测跑出一条漂亮曲线不代表策略有效。必须回答三个问题① 收益来自哪里市场Beta行业轮动纯Alpha② 因子是否真的有效IC 值、IR 值、分层回测③ 风险是否可控最大回撤、波动率、下行风险。pyfolio是唯一能一站式输出这三类分析的开源库但它的输入格式极其挑剔——必须是pd.Series索引为datetime值为日收益率。5.1 从 backtrader 结果提取日收益率序列绕过「净值曲线」陷阱backtrader的cerebro.run()返回results[0].analyzers.sharperatio.get_analysis()但这只是单个指标。要喂给pyfolio必须自己构造returnsSeries。# 在 Strategy 中添加记录器 class FactorStrategy(bt.Strategy): def __init__(self): self.returns [] # 存储每日收益率 self.dates [] def next(self): self.dates.append(self.datas[0].datetime.date(0)) # 计算当日收益率今日净值 - 昨日净值/ 昨日净值 if len(self.broker.get_value()) 1: value_today self.broker.getvalue() value_yesterday self.broker.getvalue() # 实际需缓存昨日值 # 正确做法在 __init__ 中初始化 self.prev_value self.broker.getvalue() # 在 next 开头ret (value_today - self.prev_value) / self.prev_value # self.prev_value value_today # self.returns.append(ret)更可靠的做法是用bt.analyzers.TimeReturncerebro.addanalyzer(bt.analyzers.TimeReturn, _nametimereturn) results cerebro.run() portfolio_stats results[0].analyzers.timereturn.get_analysis() # portfolio_stats 是 dictkey 为 datetimevalue 为 float净值 returns_series pd.Series(portfolio_stats).pct_change().dropna() returns_series.index pd.to_datetime(returns_series.index)注意TimeReturn返回的是净值Net Value不是收益率。pct_change()才是日收益率。dropna()删除首日NaN。5.2 用 pyfolio 生成四维归因报告收益、风险、风格、因子import pyfolio as pf # 1. 基础绩效 pf.create_full_tear_sheet( returnsreturns_series, benchmark_retshs300_returns, # 需提前获取沪深300日收益 live_start_date2022-01-01, # 实盘开始日 round_tripsTrue ) # 2. 因子分析计算 IC 值Information Coefficient def calc_ic(factor_df, forward_ret_colforward_1m_ret): 计算因子 IC因子值与未来收益的秩相关系数 ic_list [] for date in factor_df.index.get_level_values(date).unique(): day_df factor_df[factor_df.index.get_level_values(date) date] if len(day_df) 10: continue # 计算未来 20 日收益率需提前计算好 forward_ret_col # 这里假设 clean_df 已有 forward_1m_ret 列 if forward_ret_col in day_df.columns: ic day_df[factor_score].corr(day_df[forward_ret_col], methodspearman) ic_list.append({date: date, ic: ic}) ic_df pd.DataFrame(ic_list) return ic_df ic_df calc_ic(clean_df, forward_1m_ret) print(fIC 均值: {ic_df[ic].mean():.4f}, IR: {ic_df[ic].mean()/ic_df[ic].std():.4f})参数说明methodspearman是必须的。因子有效性看的是排序能力不是线性关系。Pearson 相关系数对异常值敏感Spearman秩相关更鲁棒。5.3 分层回测Quintile Analysis验证因子单调性IC 值高不代表策略稳。必须看「因子得分最高组 vs 最低组」的收益差是否显著。def quintile_analysis(factor_df, ret_colforward_1m_ret, n_quintiles5): 分层回测将股票按因子得分五等分计算各层未来收益 results [] for date in factor_df.index.get_level_values(date).unique(): day_df factor_df[factor_df.index.get_level_values(date) date] if len(day_df) 20: continue # 按因子得分分五组 day_df day_df.dropna(subset[factor p a hrefhttps://download.csdn.net/download/2301_81134882/92451354 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
网站建设高端定制企业官网