
简介面向具备Python基础、希望切入金融数据场景的学习者与量化分析入门者这份资料围绕股票大数据采集、清洗、分析与预测展开覆盖爬虫获取行情、公告与新闻资讯Pandas处理缺失值和异常值NumPy/SciPy统计计算Matplotlib/Seaborn可视化以及线性回归、决策树、随机森林、神经网络等模型在股价走势预测中的应用并延伸至Tushare/Wind实时数据与Flask/Django结果展示较重视从数据获取到建模分析的链路串联。资源包为7z格式约4.47MB上游暂未提供文件数量与类型明细下载后可按主题自行整理。页面显示已有3659人学习适合需要补齐实战链路的中级学习者作为股票数据分析实战的参考材料帮助读者理解爬虫、预处理、可视化、模型评估与实时分析的基本流程并据此梳理个人学习重点。1. 先别急着打开 Notebook这份股票大数据分析包解决的是“数据链路”问题这份《基于股票大数据分析Python实战》不是单一脚本而是一条从 python 爬虫抓行情、落库、清洗到因子回测的链路。很多人拿到压缩包先翻 Notebook看到收益曲线很漂亮就以为能直接用真正跑起来才发现字段对不上、复权口径混乱、回测偷用了未来数据。它适合已经会一点 pandas、想补齐全流程的开发者也适合做数据岗位、需要一套能改能查的股票分析样例的人。先把它当成一套可验证的工程模板而不是现成策略。2. 拆包先看数据链路爬虫、落库、因子三段怎么接这份包最容易让人误判的地方是目录看起来像“几个脚本加一份样例数据”实际核心在链路顺序采集层只负责拿原始行情清洗层负责字段、复权和去重因子层只读清洗后的面板回测层不直接碰原始文件。顺序一旦反过来后面每一个指标都可能带着脏数据跑。先看目录职责比先跑 Notebook 更省时间。2.1 目录职责先分清采集层和分析层拿到包后我会先按“数据是否可追溯”拆目录而不是按文件后缀拆。采集层要留下原始响应和抓取时间清洗层要能重复执行因子层和回测层只依赖清洗后的结果。下面这张表是这类项目最常见的模块划分具体文件名可能不同但职责边界应该一致。目录/模块主要职责先检查什么collector请求行情接口、分页、重试、增量游标是否限速、是否保存原始响应、失败是否可重跑storage原始表、清洗表、状态表主键是否是 symbol date是否有抓取批次pipeline字段映射、类型转换、复权、去重是否覆盖停牌、零成交、重复行factorsMA、动量、波动率等因子是否按 symbol 分组、是否引用未来数据backtest信号、撮合、费用、净值信号是否 shift、成交价是否在信号之后report净值、回撤、换手、覆盖度是否只展示收益不展示样本覆盖采集层和分析层混在一起最常见的结果是“Notebook 能跑换一天数据就崩”。我一般会要求采集层输出两个东西原始数据和抓取日志。原始数据哪怕字段很丑也先落盘清洗规则后面可以改抓取日志记录请求参数、返回行数、异常类型出问题时不用靠猜。这一步不做后面排查空页、字段变更、重复抓取会非常被动。2.2 用一条最小链路验证包是否完整先不要跑全量也不要一上来调策略参数。用样例数据跑一条“读原始文件 → 字段检查 → 去重排序 → 落清洗表”的最小链路能最快判断这份包能不能继续用。下面这段代码只做一件事确认最关键的字段存在并把数据整理成后续因子计算能接受的面板格式。from pathlib import Path import pandas as pd ROOT Path(__file__).resolve().parent raw ROOT / data / raw / quotes_sample.csv out_dir ROOT / data / processed out_dir.mkdir(parentsTrue, exist_okTrue) df pd.read_csv(raw) need {date, symbol, open, high, low, close, volume, amount} missing need - set(df.columns) if missing: raise ValueError(f字段缺失: {missing}) df[date] pd.to_datetime(df[date], errorscoerce) df df.dropna(subset[date]) df[symbol] df[symbol].astype(str).str.zfill(6) df df.sort_values([symbol, date]).drop_duplicates([symbol, date]) df.to_parquet(out_dir / quotes_clean.parquet, indexFalse) print(df.groupby(symbol).size().describe())这段代码的关键不是to_parquet而是三个检查点字段集合、日期可解析、symbol date唯一。to_parquet需要环境里有 pyarrow没有就改成to_csv但不要跳过落盘直接进因子计算。最后的groupby(symbol).size().describe()是在看每个标的的样本量分布如果最小值只有几行说明样例数据或抓取区间有问题后面回测的净值曲线没有参考价值。参数上errorscoerce会把无法解析的日期变成空值宁可先报错也不要让脏日期混进排序。2.3 采集端先做限速与增量别让爬虫把任务跑成一次性python 爬虫在这类项目里最容易出问题的不是“抓不到”而是“抓得太猛”和“重复抓”。我一般会把采集脚本写成可重入的同一个标的、同一个日期区间跑第二遍不会产生重复行失败重试有上限退避时间逐步拉长每页返回后立刻校验字段而不是等全量跑完再发现接口变了。下面这段代码是一个带超时、重试和退避的最小采集骨架接口地址用本地 mock 占位接到自己的数据服务时只换BASE和参数映射。import random import time import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry BASE http://127.0.0.1:8000/api/quote session requests.Session() retry Retry( total3, backoff_factor0.6, status_forcelist[429, 500, 502, 503, 504], ) session.mount(http://, HTTPAdapter(max_retriesretry)) def fetch_quotes(symbol: str, start: str, end: str): params {symbol: symbol, start: start, end: end} for attempt in range(4): try: resp session.get(BASE, paramsparams, timeout(3, 10)) resp.raise_for_status() rows resp.json().get(rows, []) if rows: return rows time.sleep(1 random.random()) except requests.RequestException: if attempt 3: raise time.sleep(2 ** attempt random.random()) return [] if __name__ __main__: data fetch_quotes(SYM_A, 2024-01-01, 2024-03-31) print(len(data), data[:1])timeout(3, 10)分别是连接超时和读取超时股票行情接口偶尔会卡在读取阶段不设读取超时会让任务一直挂着。Retry只对限流和 5xx 做自动重试业务层再用attempt控制总次数避免无限重试。time.sleep(2 ** attempt random.random())是退避加随机抖动防止多个任务在同一秒一起重试。返回值这里用空列表表示“这次没拿到”但真实采集里不能把空列表直接当成“数据结束”还要看接口是否返回has_more或游标。增量抓取则要在本地状态表里记录每个标的最后成功日期下一次从下一天开始不要每次全量重跑。3. 把原始行情洗成可计算面板字段、复权与缺失值的三道关原始行情看起来只是日期、代码、开高低收、量额真正进入因子计算前至少要过三道关字段语义是否一致、价格是否复权、缺失和异常是否被标记。很多回测曲线“好得不真实”问题不在策略而在清洗阶段把不同口径的数据拼到了一起。下面按字段、复权、缺失值三个环节拆。3.1 字段对齐日期、代码、OHLCV 的约定不同数据源的字段名差异很大trade_date和date、ts_code和symbol、vol和volume都是常见变体。字段对齐不是简单改列名还要统一类型日期统一到“交易日”粒度代码保留前导零价格和量额转成数值型。下面这段代码把常见别名映射到统一字段并把日期归一到当天零点。import pandas as pd RENAME {trade_date: date, ts_code: symbol, vol: volume} NUM_COLS [open, high, low, close, volume, amount, adj_factor] def normalize_bars(df: pd.DataFrame) - pd.DataFrame: df df.rename(columns{k: v for k, v in RENAME.items() if k in df.columns}).copy() if symbol not in df.columns: raise ValueError(缺少 symbol/ts_code 列) df[symbol] df[symbol].astype(str).str.zfill(6) df[date] pd.to_datetime(df[date], errorscoerce).dt.normalize() for col in NUM_COLS: if col in df.columns: df[col] pd.to_numeric(df[col], errorscoerce) return df.dropna(subset[date, symbol, close])dt.normalize()会把带时分秒的日期压到当天避免同一天出现多个时间点导致重复。zfill(6)是为了防止代码被 Excel 或 CSV 读成整数后丢掉前导零。to_numeric(errorscoerce)不会让脏值直接抛异常而是变成空值后面在清洗阶段统一处理。这里不要急着把空值全部删掉先保留原始行等复权完成后再按规则过滤否则容易把停牌日和处理失败的记录混为一谈。3.2 复权与停牌处理先定口径再算收益复权是股票数据分析里最容易“看起来都对、放在一起就错”的地方。前复权适合展示价格连续、图好看后复权更适合回测因为 historical price 不会因为最新因子刷新而整体平移。我一般会在清洗层同时保留原始价和复权价回测收益统一用后复权价计算报告展示再用前复权价。下面这段代码按adj_factor生成复权价hfq是后复权qfq是前复权。def add_adj_price(df: pd.DataFrame, mode: str hfq) - pd.DataFrame: need {symbol, date, adj_factor, open, high, low, close} if not need.issubset(df.columns): raise ValueError(f缺少字段: {need - set(df.columns)}) df df.sort_values([symbol, date]).copy() if mode hfq: for col in [open, high, low, close]: df[f{col}_adj] df[col] * df[adj_factor] elif mode qfq: latest df.groupby(symbol)[adj_factor].transform(last) for col in [open, high, low, close]: df[f{col}_adj] df[col] * df[adj_factor] / latest else: raise ValueError(mode 只支持 hfq/qfq) return df参数mode不是随便选的回测用hfq因为每个历史时点的复权价只依赖当时已知的累计因子不会因为今天的最新因子变化而把整段历史重新缩放展示用qfq因为前复权价格更接近当前价格刻度。无论用哪种都要拿一个标的单独核对选中除权日前后几行看复权价差是否连续原始价是否有跳空。停牌日不要用前值硬填一条“假行情”可以保留空行并在因子计算时按交易日历对齐收益计算时跳过。3.3 缺失值与异常值涨跌停、零成交、重复行清洗阶段最常见的误操作是把缺失值一律fillna(methodffill)。价格前向填充还能接受收益率前向填充会制造出不存在的交易机会。更稳妥的做法是零成交量行先剔除成交额过低的行标记收益率超过制度阈值的行置空重复的symbol date去重。下面这段代码是一个可调的最小清洗函数。def clean_bars(df: pd.DataFrame, min_amount: float 1e6, limit: float 0.21): df df.copy() df df[df[volume].fillna(0) 0] df df[df[amount].fillna(0) min_amount] df[ret] df.groupby(symbol)[close_adj].pct_change() df.loc[df[ret].abs() limit, ret] float(nan) return df.dropna(subset[close_adj])min_amount是成交额下限示例用 100 万实际要按标的池和币种调整小盘标的用这个阈值可能过滤过多大盘标的可以适当提高。limit是单日收益率兜底阈值示例 0.21 只是通用示例真实使用时按标的涨跌幅制度和复权方式调整。这里没有对ret做前向填充因为回测里的缺失收益应该被识别为“不可交易”或“数据缺失”而不是补一个 0。重复行要在排序后按symbol date去重保留哪一条取决于数据源通常保留抓取时间最新的一条并在清洗表里加一列source_batch方便回溯。4. 从 MA 因子到净值曲线回测撮合里的四个关键参数因子计算和回测是这份包最容易被“调参幻觉”带偏的部分。均线、动量、波动率都不难写难的是让信号、成交价、费用和持仓变化在时间轴上对齐。下面先写向量化因子再把信号延迟到下一根 K 线成交最后用一组固定参数跑出可核验的净值。4.1 因子计算别用 apply 逐行向量化算 MA、动量和波动率逐行apply在样例数据上可能看不出问题数据量一上来就会变成性能瓶颈。更常见的写法是按symbol分组后用rolling计算再把结果对齐回原表。下面这段代码计算 5/20/60 日均线、5/20/60 日动量和 20 日波动率并生成一个简单的均线交叉信号。import pandas as pd def add_factors(df: pd.DataFrame, windows(5, 20, 60)) - pd.DataFrame: df df.sort_values([symbol, date]).copy() g df.groupby(symbol, group_keysFalse) for w in windows: df[fma{w}] g[close_adj].transform( lambda s: s.rolling(w, min_periodsw).mean() ) df[fret{w}] g[close_adj].pct_change(w) df[vol20] g[ret].transform( lambda s: s.rolling(20, min_periods20).std() ) df[vol_cap] df.groupby(symbol)[vol20].transform( lambda s: s.rolling(60, min_periods20).median() ) df[signal] ( (df[ma5] df[ma20]) (df[vol20] df[vol_cap]) ).astype(int) return dfmin_periods必须显式设置否则窗口前几行会产生空值不同 pandas 版本的默认行为也可能让人误判。vol_cap是波动率的上限参考用滚动中位数而不是全样本中位数避免把未来信息带进历史信号。signal这里只是示例逻辑不是建议照搬真正要改的是因子经济含义和持仓规则而不是把 5/20 改成 4/18 去凑收益。数据量大时可以把transform(lambda ...)换成groupby.rolling但要注意索引对齐。4.2 回测撮合信号、次日开盘、手续费与滑点回测最常见的错误是“今天收盘看到信号今天收盘就成交”。更保守的做法是信号在收盘后确认下一交易日开盘成交持仓期间用开盘到开盘的收益计算。下面这段代码把signal延迟一根 K 线用next_open计算持有收益并在持仓变化时扣费用和滑点。def backtest(df: pd.DataFrame, fee: float 3e-4, slip: float 5e-4) - pd.DataFrame: df df.sort_values([symbol, date]).copy() df[pos] df.groupby(symbol)[signal].shift(1).fillna(0) df[next_open] df.groupby(symbol)[open_adj].shift(-1) df[ret_open] df[next_open] / df[open_adj] - 1 df[trade] df[pos] - df.groupby(symbol)[pos].shift(1).fillna(0) df[cost] df[trade].abs() * (fee slip) df[strat_ret] df[pos] * df[ret_open] - df[cost] return df.dropna(subset[next_open]) def to_daily(df: pd.DataFrame) - pd.DataFrame: daily ( df.groupby(date) .agg(strat_ret(strat_ret, mean), n(symbol, nunique)) .reset_index() ) daily[nav] (1 daily[strat_ret]).cumprod() daily[dd] daily[nav] / daily[nav].cummax() - 1 return dailypos用shift(1)是为了让第 t 行的持仓来自第 t-1 行的信号成交发生在第 t 行开盘。next_open和ret_open把收益区间限定在开盘到开盘避免用收盘价成交造成前视。trade是持仓变化cost按成交方向取绝对值买卖都扣。to_daily里用等权平均把多标的收益合成组合收益n是当天参与计算的标的数样本太少时净值曲线不可信。参数上fee和slip不要设成 0哪怕策略频率很低也要留出成本否则回测收益会被系统性高估。4.3 绩效核验净值、回撤、换手与基准净值曲线只是结果不能当验证。至少要同时看年化收益、波动率、夏普、最大回撤、交易天数和平均持仓数。下面这段绩效函数用日频收益计算参数periods252表示一年交易日数按实际频率调整。def perf(daily: pd.DataFrame, periods: int 252) - dict: r daily[strat_ret].dropna() if p a hrefhttps://download.csdn.net/download/dp340823/14094388 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