多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

基于股票大数据分析Python实战:从行情数据到可验证结论的最小闭环

基于股票大数据分析Python实战:从行情数据到可验证结论的最小闭环 简介这份《基于股票大数据分析 Python 实战》资料包面向金融数据分析初学者及对量化投研感兴趣的开发者聚焦从数据采集到建模预测的完整流程。内容覆盖 BeautifulSoup、Scrapy 爬虫抓取历史行情与公告HTTP 协议与反爬处理Pandas 清洗缺失值、异常值并统一格式NumPy/SciPy 统计计算Matplotlib/Seaborn 绘制 K 线图、箱型图与成交量分析同时演示 Scikit-learn 中线性回归、决策树、随机森林与神经网络在股价预测中的应用以及 Tushare/Wind 实时数据接口和 Flask/Django 轻量部署。整个知识体系环环相扣既能帮助入门者建立整体分析框架也为已有基础者提供可直接改写的实战脚本与调试思路。压缩包采用 7z 格式整体大小仅 4.47MB便于快速下载与解压。已有 3646 人学习下载适合个人项目练习或课程配套资料。1. 基于股票大数据分析Python实战从行情数据到可验证结论的最小闭环在本地跑通一次股票数据分析没有想象中那么难但也没那么简单。拉数据、清洗、算指标、跑模型、画净值曲线这条链路里最常见的翻车点并不在模型而在于数据处理口径复权有没有做对、信号有没有延迟一天、样本有没有悄悄丢失。基于股票大数据分析Python实战核心就是用Python把从行情数据到可验证结论的最小闭环打通——数据怎么拿、特征怎么造、因子怎么检验、回测怎么不骗人。这篇文章适合正在搭本地分析环境的Python开发者也适合想用免费数据源做研究的个人投资者。我会按做过且跑得通的方案把每一步的参数、代码和坑摊开讲。2. 数据从哪来免费行情接口选型与本地化存储2.1 三个常用免费数据源怎么选做股票数据分析第一步卡在数据源。A股日线行情看起来哪都能下真落到接口层面限制比想象中多有的接口要token有的没有复权字段有的对请求频率卡得很死。常见做法是先看三个免费源——akshare、tushare pro、baostock从中选一个作为主数据源。项目aksharetushare probaostock是否免费免费基础积分免费免费是否需要token不需要需要注册获取不需要日线复权qfq/hfq/不复权qfq/hfq/不复权前/后复权退市股票覆盖有限较完整较完整稳定性依赖上游网页接口稳定稳定我的选择习惯是日常做策略研究和特征工程用akshare零门槛、接口直观做严肃回测或需要剔除幸存者偏差时用tushare pro补退市股票数据baostock作为备用源接口稳定但字段偏基础。数据源没有绝对的好坏关键是先想清楚你到底要做哪一层分析——只看几只龙头股做因子验证akshare完全够做全市场量化回测退市股票数据就是必需品。2.2 批量拉取日线行情并本地保存的Python脚本数据源确定后接下来的常规动作是批量拉取并落地保存。下面这个脚本用akshare拉取几只股票的日线存成csv请求之间加一个休眠时间避免被限流。import time import akshare as ak import pandas as pd stock_list [600519, 000001, 600036, 000858] start_date 20150101 end_date 20241231 for code in stock_list: df ak.stock_zh_a_hist( symbolcode, perioddaily, start_datestart_date, end_dateend_date, adjusthfq # 后复权回测场景推荐 ) if df is None or df.empty: print(f{code} 拉取失败检查代码或网络) continue df[股票代码] code # 多股票合并时保留来源 df.to_csv(fdata/{code}.csv, indexFalse, encodingutf-8-sig) print(f{code} 已保存 {len(df)} 条记录) time.sleep(0.6) # 控制请求频率防止被限流这段代码里stock_zh_a_hist是akshare的A股历史行情接口symbol传六位股票代码period指定日线start_date和end_date格式必须是YYYYMMDD。adjust参数是最关键的一个qfq是前复权hfq是后复权不传则返回不复权数据。保存时加一列股票代码后续多股票合并时不会丢来源信息utf-8-sig编码是为了让Excel打开csv不乱码。拉取后建议先看行数和日期范围再决定要不要清洗。如果发现某只股票记录数明显偏少很可能它经历过长期停牌或者上市日期晚于起始日期这个在后续清洗时要单独处理。多股票合并时用pd.concat并按日期排序即可但要注意停牌日缺失的问题下面一章专门讲。2.3 复权口径HFQ、QFQ与不复权各自的适用场景复权是股票数据分析里最容易踩坑、又最容易被忽略的参数。股票分红送转后价格会除权除息K线图上出现一个断层。不复权的数据价格在除权日直接跳空向下均线、MACD、RSI这些指标都会被这个跳变打乱。复权就是把历史和当前价格拉到同一可比口径。前复权以最新价格为基准把历史价格往下调整好处是看到的价格和当前市价一致但问题在于每次发生新的分红除权整段历史数据都要重新计算同一只股票上周拉的历史K线和今天拉的不一样回测结果也会跟着变。后复权以最早价格为基准新股分红往后续价格上累加历史数据不会因为未来事件被改写。所以我的习惯是回测统一用后复权数据展示当前价位走势分析用前复权做除权除息事件统计才用不复权数据。这个选择直接影响回测可复现性提前定好口径能省掉后面大量返工。提示如果做事件驱动类策略比如分红预案、除权日套利必须保留不复权数据加上除权除息事件表复权数据反而会把事件窗口的价格关系抹平。3. 清洗与特征工程把行情表变成模型训练集3.1 停牌缺失与异常值处理行情数据拉下来不会直接能用。A股停牌是常态——重大资产重组、年报问询、股东大会都可能停牌一两天甚至几个月。停牌日没有交易K线直接就缺了这一天如果不处理时间序列上就会出现断点。更麻烦的是pct_change()这类按行计算的函数并不会报错只会默默把停牌前后的涨跌幅算成一个巨大的数字特征就在这一步悄悄失真。处理缺失的标准做法是先把日历补全再做填充。下面代码演示了如何生成完整交易日历并将停牌日缺失的成交量填0、价格用前值填充。import pandas as pd import numpy as np # 读入已保存的行情数据 df pd.read_csv(data/600519.csv, parse_dates[日期]) df df.rename(columns{日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume, 成交额: amount}) df df.set_index(date).sort_index() # 生成完整交易日历从数据首日到末日 trade_cal pd.date_range(df.index.min(), df.index.max(), freqB) df df.reindex(trade_cal) # 价格类序列前向填充成交量缺失填0 price_cols [open, high, low, close] df[price_cols] df[price_cols].ffill() df[volume] df[volume].fillna(0) df[amount] df[amount].fillna(0)填充逻辑分两类价格序列用ffill()前向填充理由是停牌期间价格沿用上一交易日收盘价是业内通用假设成交量填0因为当天确实没有交易发生。如果用bfill()回填反而会把下一交易日的价格数据泄漏到停牌日属于典型的未来函数不要这么干。异常值这边重点看两类数据一是涨跌幅异常A股主板单日涨跌幅限制是±10%创业板和科创板是±20%如果某天涨跌幅超过限制先怀疑数据源本身错了二是成交量为0但价格有变动的情况一般是停牌复牌首日的集合竞价数据这类记录可以保留但要在特征里打标。顺手可以检查每列的最大最小值确认数据范围合理。3.2 技术指标特征与未来收益标签特征工程的目的是把原始行情转换成模型能学习的变量。最常见的构造方式有两类一是价格类特征比如均线偏离度、RSI、MACD二是量能类特征比如成交量相对20日均值的倍数。下面这个函数把基础特征和标签一起构造出来。def make_features(df, hold5): data df.copy() # 基础收益特征 data[ret_1d] data[close].pct_change() # 均线偏离度价格相对均线的百分比 data[ma5_dev] data[close] / data[close].rolling(5).mean() - 1 data[ma20_dev] data[close] / data[close].rolling(20).mean() - 1 data[ma60_dev] data[close] / data[close].rolling(60).mean() - 1 # 成交量相对20日均量倍数 data[vol_ratio] data[volume] / data[volume].rolling(20).mean() # RSI(14) delta data[close].diff() up delta.clip(lower0).rolling(14).mean() down (-delta.clip(upper0)).rolling(14).mean() data[rsi14] up / (up down) # 未来持有期收益与分类标签 data[fwd_ret] data[close].shift(-hold) / data[close] - 1 data[label] (data[fwd_ret] 0).astype(int) # 删除前期的NaN窗口和尾部无标签样本 data data.dropna(subset[ma60_dev, vol_ratio, fwd_ret]) return data feature_df make_features(pd.read_csv(data/600519.csv, parse_dates[日期]) .set_index(日期).sort_index())这里几个参数值得多说一句。hold5表示预测未来5个交易日的收益持有期可以按策略需求改成2、10、20不同持有期的标签分布差别很大如果改成20正负样本比例会明显变化。rolling(5)、rolling(20)、rolling(60)分别对应周线、月线、季线级别的均线窗口量级上不要差太多30分钟级别的策略用这些日线参数就不合适了。label用的是二分类未来收益是否大于0也可以改成回归任务直接预测fwd_ret的数值两种做法都能跑通但分类问题对数据噪声更有容忍度。3.3 特征对齐为什么T日特征加shift标签是底线特征对齐是这条链路里最容易出错、也最容易被忽略的环节。核心要求很简单T日收盘后能拿到的信息只能用来预测T1日及之后的收益T日当天的收益不能用T日当天的特征去解释。严格说T日收盘那一刻你就已经知道T日的收盘价、成交量、均线了所以T日收盘价算出来的特征预测T日收益没有意义——那不是预测是复盘。代码里有两处对齐要特别注意。第一处是标签构造用了shift(-hold)也就是T日的特征对应的是T1到Thold这段未来收益特征和标签在时间上错开了。第二处是后续回测时信号执行要再推迟一天也就是T日收盘后产生信号T1日开盘才真正买入这个在回测章节会具体写。一个典型的错误写法是先算ret close.pct_change()然后直接label (ret 0)再把当天的均线特征和这个标签配对看着没什么问题本质上就是让模型用收盘价信息去预测同一个已发生的涨跌训练出来的模型在回测里涨得飞起实盘一塌糊涂。凡是遇到回测收益曲线漂亮得不像话先查特征对齐这个原则比任何模型调参都重要。4. 因子检验与基础回测从单因子IC到时序交叉验证4.1 单因子IC怎么算、怎么读特征构造完下一步是验证特征到底有没有预测能力。金融里最常用的指标是ICInformation Coefficient本质是因子值和未来收益的秩相关性。IC为正说明因子值越大未来收益越高IC为负说明因子值越大未来收益越差绝对值在0.03以上就算有微弱但可用的预测力。单只股票的IC只能算时间序列相关性严格的做法是横截面IC——每个交易日取全市场股票的因子值和当天所有股票的未来收益做秩相关再把每天的IC求均值。下面代码演示了在少量股票上的近似实现。from scipy.stats import spearmanr import pandas as pd # 模拟多股票数据把两只股票拼接成一个长表 df1 make_features(pd.read_csv(data/600519.csv, parse_dates[日期]) .set_index(日期).sort_index()) df2 make_features(pd.read_csv(data/000001.csv, parse_dates[日期]) .set_index(日期).sort_index()) df1[code] 600519 df2[code] 000001 all_df pd.concat([df1, df2]) def ic_series(df, factor_col, label_colfwd_ret): ics [] for date, grp in df.groupby(level0): # 按交易日分组 if len(grp) 5: continue ic, _ spearmanr(grp[factor_col], grp[label_col]) ics.append(ic) return pd.Series(ics, dtypefloat) for f in [ma5_dev, ma20_dev, vol_ratio, rsi14]: ic ic_series(all_df, f).mean() print(f{f}: 平均IC {ic:.4f})groupby(level0)按日期索引分组每个交易日形成一个小横截面组内算因子值与未来收益的秩相关。样本量只有两只股票时这个横截面非常单薄IC的波动会很大所以这段代码定位是教你怎么算、怎么套用到更大股票池真正使用时建议至少放到50只以上。需要注意IC的稳定性比大小更重要。单看平均IC不够还要看IC的标准差、IC为正的天数占比、以及逐日IC的时间序列是否稳定。一个因子如果只在一段时间有效、其他时间随机波动平均IC可能看着不错实盘用起来却很不舒服。4.2 用随机森林做多因子合成单因子检验通过后可以尝试把多个特征组合起来做预测。随机森林是起步阶段比较稳妥的选择对特征尺度不敏感不需要归一化能捕捉非线性关系也不会像线性回归那样被特征之间的多重共线性干扰。下面用时间序列交叉验证评估效果。from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import TimeSeriesSplit, cross_val_score features [ma5_dev, ma20_dev, vol_ratio, rsi14, ret_1d] X feature_df[features].dropna() y feature_df[label].loc[X.index] # 时序交叉验证按时间顺序切分不打乱数据 tscv TimeSeriesSplit(n_splits5) model RandomForestClassifier( n_estimators200, max_depth4, min_samples_leaf20, random_state42 ) scores cross_val_score(model, X, y, cvtscv, scoringroc_auc) print(fAUC 均值: {scores.mean():.3f}, 标准差: {scores.std():.3f})这里故意用了TimeSeriesSplit而不是普通的KFold。时间序列数据一旦被打乱切分未来数据就会混进训练集AUC会虚高这也是很多机器学习教程里没讲透的细节。n_estimators200控制树的数量max_depth4控制树的深度min_samples_leaf20强制叶子节点至少20个样本这三个参数组合是针对金融数据噪声大的特点做的保守设置。对于单只股票的历史数据样本量通常只有几千条训练出来的AUC如果能稳定在0.55以上就算不错。AUC 0.6以上已经属于值得深挖的特征组合。如果AUC接近0.5说明这些特征对这只股票没有明显预测力换股票池或者换特征都比继续调参强。4.3 均线策略回测延迟一天执行信号模型预测可以作为信号源但很多人的第一套系统是从传统技术指标开始的。均线交叉策略是最基础的对照基准短期均线上穿长期均线时买入下穿时卖出。这里关键在信号对齐——收盘后确认信号下一个交易日才执行。def backtest_ma(df, fast5, slow20, cost0.0006): data df.copy() data[fast_ma] data[close].rolling(fast).mean() data[slow_ma] data[close].rolling(slow).mean() # 收盘后产生信号position 整体 shift(1)次日才生效 data[signal] (data[fast_ma] data[slow_ma]).astype(int) data[position] data[signal].shift(1).fillna(0) data[ret] data[close].pct_change() data[strategy_ret] data[position] * data[ret] data[net_ret] data[strategy_ret] - cost * data[position].diff().abs().fillna(0) data[cum] (1 data[net_ret]).cumprod() return data bt backtest_ma(pd.read_csv(data/600519.csv, parse_dates[日期]) .set_index(日期).sort_index()) print(f累计收益: {bt[cum].iloc[-1]:.2%})shift(1)这一行是整个回测的灵魂。signal在T日收盘后确定position等于前一日的signal意味着T1日才开始按这个信号持仓。没有这个shiftT日收盘后算出信号、T日收盘就成交相当于你用当天收盘价预测当天涨跌回测结果会明显偏乐观在这个策略里可能把年化收益抬高几个百分点。cost参数是交易成本估算按双边万六估计。成本对高频策略的影响远大于中低频如果后续做模型策略交易频繁了成本要单独测敏感性。策略对比时先跑一个买入持有基准再跑均线策略两个都画在同一条时间轴上观察相对走势这样看超额收益更直观。5. 避坑指南回测翻车的四个隐蔽细节5.1 未来函数收益曲线漂亮到不敢信现象回测里的累计收益曲线一路上扬年化50%以上回撤又小仿佛找到了印钞机。拿到实盘模拟盘一跑收益大幅缩水。原因最常见的是特征与标签错位。T日收盘价算出的信号被当成T日开盘就能成交的价格或者标签用的是当日收益而不是未来收益再或者数据预处理时用了bfill()把未来数据回填到了过去。解决统一执行“次日成交”规则。凡是T日收盘后能确认的信息收盘价、均线、成交量一律只允许影响T1日及之后的持仓状态。代码上强制用shift(1)处理信号并在代码注释里写明信号产生时间和成交时间的对应关系。回测完成后随机挑一天信号人工确认一下当天收盘价和实际成交价之间的逻辑链条。5.2 复权选择失误历史K线几天一变现象同一只股票、同一段日期上周跑的策略结果和这周跑的结果不一样净值曲线在分红除权附近出现莫名其妙的跳变。原因用了前复权数据做回测。前复权以最新价格为基准重新计算历史价格每发生一次新的分红送转整段历史K线就被改写一次回测结果自然跟着变。解决回测统一使用后复权数据后复权的历史价格不会因为未来事件被修改。如果要向别人复现你的结果把“数据源复权方式起止日期”一并写清楚不然同样的代码换个人拉数据结果完全对不上。展示当前走势图时再切前复权两种口径分开用。5.3 幸存者偏差剔掉退市股票等于开挂现象回测选股组合的收益显著跑赢指数但对照真实市场发现很多同期退市的股票根本没出现在数据集里。原因很多免费接口默认只返回当前仍在上市的股票退市股不在列表里。你的回测池子里只剩活下来的股票等于每只股票都开天眼预判了它不会退市收益虚高在数学上是必然的。解决做严肃的全市场回测时股票池需要包含历史时点的全部样本包括已经退市的。tushare pro可以拉退市股票行情数据完整度比免费爬虫类接口高。如果数据源确实覆盖不了退市股至少在结论里明确标注“未剔除幸存者偏差结果偏乐观”不要直接拿这个收益数字去外部对比。5.4 涨跌停与停牌回测成交在现实中买不到现象回测显示某天大幅买入某只股票收益贡献很高。查看那天的行情股票开盘直接一字涨停根本没有卖单或者处于停牌状态全天无成交。原因回测模型只按信号下单没有校验涨跌停和停牌约束。涨停时买不进跌停时卖不出停牌时无法交易这些在无约束回测里都被忽略了。解决在下单逻辑里加约束——当日涨跌幅接近涨停主板涨幅超过9.8%或跌停跌幅低于-9.8%时不产生新买单和卖单持仓股遇到跌停无法卖出持仓顺延一天。停牌日的成交一律禁止判断标准是当日成交量为0。加完约束再跑一遍收益曲线如果差异很大说明之前回测里的很多利润来自现实中根本无法成交的纸上交易。6. 参数扫描与因子分层验证你的结论不是玄学6.1 参数网格扫描换一组参数还成立吗单组参数的回测跑通了不能说明策略可用。很多策略的光鲜业绩严重依赖某一组参数参数一改就面目全非这类策略在实盘里经不起市场风格切换的考验。常见做法是做参数网格扫描看收益在参数邻域内的分布。一段简单的双参数扫描代码def backtest_ma_return(df, fast, slow, cost0.0006): data df.copy() data[fast_ma] data[close].rolling(fast).mean() data[slow_ma] data[close].rolling(slow).mean() data[position] (data[fast_ma] data[slow_ma]).astype(int).shift(1).fillna(0) data[ret] data[close].pct_change() data[net] data[position] * data[ret] - cost * data[position].diff().abs().fillna(0) return (1 data[net]).cumprod().iloc[-1] - 1 for fast in [3, 5, 10, 20]: for slow in [20, 30, 60]: if fast slow: continue total_ret backtest_ma_return(bt, fast, slow) print(ffast{fast:2}, slow{slow:2}, 累计收益{total_ret:8.2%})看扫描结果时先看整体分布如果参数附近区域收益普遍为正且不是某一个孤立点特别高结论相对可信如果只有某个参数组合收益炸裂、旁边参数组合直接亏钱基本可以判定是过拟合。我的习惯是再把时间窗口切分成两段各跑一遍看参数有效性在时间上是否稳定。6.2 因子分层组合用单调性替代单票运气单股票的单次收益带有极大的运气成分验证因子有效性更可靠的替代方案是分层回测按因子值把股票池分成五组分别构成等权组合观察各组收益是否呈单调关系。如果第一组到第五组收益严格递减说明因子有真实区分度如果只有某一组异常突出其他组随机分布这个因子大概率是噪声。分层回测需要一定规模的股票池几十只起步。实现时按每个交易日所有股票的因子值分位数分组组内等权配置持有到下一个调仓日再重平衡。单调性比单组收益更值得信任——单调的关系意味着因子在整个取值范围内都具备区分能力实盘使用时的稳健性要好得多。这套流程走完你对一个因子的判断就不再是“感觉挺准”而是有一组可复验的证据链数据口径、IC均值与稳定性、模型AUC、延迟执行后的回测收益、参数邻域内的稳定性。我自己曾经跳过这些步骤凭一组漂亮的回测曲线就上了模拟盘结果连续三周跑输基准回头看就是特征对齐漏了一行shift。那次之后我把数据口径检查写成了固定流程换任何一个新思路都先过一遍。希望帮到你。本文还有配套的精品资源点击获取
返回列表