简介这份资源面向股票投资者、量化研究员与算法交易学习者提供沪深股市自早期至2022年1月10日的全部日线数据可用于趋势分析、技术指标计算、策略回测与预测建模。数据涵盖开盘价、收盘价、最高价、最低价、振幅、成交量、成交额、换手率等基础行情字段并包含MACD、CCI以及同花顺手机版多空指标等常用技术分析维度便于直接开展多因子研究与买卖信号验证。资源包为rar压缩格式内含1个sql文件整体约424.52MB采用结构化查询语言存储方便通过SQL语句完成数据提取、筛选、统计与复杂分析。目前已有606人学习下载适合需要完整历史行情底稿、希望省去繁琐采集整理环节的中高级用户也能为构建回测框架与验证技术指标有效性提供扎实的数据基础。1. 沪深股票历史日线数据从数据源到本地可查询的全流程拆解做量化回测最怕什么不是策略逻辑写错而是数据本身有问题。复权因子对不上、停牌日混进了假K线、退市股票凭空消失——这些问题在回测阶段才暴露出来往往意味着前面几周的功夫全白费。沪深股票历史以来到2022-01-10的全部日线数据这个标题背后真正要解决的问题是如何拿到一份覆盖全市场、时间跨度足够长、字段完整且可复现的日线数据集并且把它落到本地能随时查询、随时增量更新的状态。适合谁看正在搭建回测框架的量化开发者、需要做因子挖掘的数据分析师以及想用A股全历史数据做统计研究的工程师。这篇内容不讲虚的从数据获取、清洗、存储到校验每一步都给出可执行的方案和参数。2. 数据获取从公开接口到本地落盘的完整链路2.1 为什么选 Tushare Pro 而不是直接爬网页做A股日线数据常见的数据源就那么几个Tushare、AkShare、Baostock、Wind付费、聚宽平台绑定。如果目标是「历史以来到2022-01-10」这个时间截面且要求覆盖沪深全部股票包括已退市的Tushare Pro 是目前个人开发者性价比最高的选择。原因有三第一它的daily接口按交易日拉取单次可返回全市场当日数据5000股票一天一次请求就能搞定第二退市股票在stock_basic里用list_statusD可以单独拉取不会像某些源那样直接消失第三积分门槛对于日线数据来说并不高120积分就能用daily接口。AkShare 的优势是免费但它的stock_zh_a_hist接口是按单只股票循环拉取5000只股票意味着5000次请求速度慢且容易被限流。Baostock 免费且支持复权但数据更新频率和字段丰富度不如 Tushare。我一般会建议如果只是做少量股票的快速验证AkShare 够用如果要构建全市场历史数据库Tushare Pro 是更稳妥的起点。2.2 拉取全市场日线的最小可行脚本下面这段代码做三件事初始化 Tushare、获取全部A股列表含退市、按交易日循环拉取日线并落盘为 Parquet。注意这里没有用pro_bar逐股拉取而是用daily按交易日拉效率差一个数量级。import tushare as ts import pandas as pd import time import os # 初始化token 需要去 Tushare 官网注册后获取 ts.set_token(你的token) pro ts.pro_api() # 第一步获取全部股票列表包括上市、退市、暂停上市 # list_status: L上市 D退市 P暂停上市 stock_basic pro.stock_basic(exchange, list_statusL, fieldsts_code,symbol,name,area,industry,list_date) stock_delisted pro.stock_basic(exchange, list_statusD, fieldsts_code,symbol,name,area,industry,list_date) stock_paused pro.stock_basic(exchange, list_statusP, fieldsts_code,symbol,name,area,industry,list_date) all_stocks pd.concat([stock_basic, stock_delisted, stock_paused]) all_stocks.to_parquet(stock_basic_all.parquet, indexFalse) print(f股票总数{len(all_stocks)}) # 第二步获取交易日历确定拉取范围 cal pro.trade_cal(exchangeSSE, start_date19901219, end_date20220110, is_open1) trade_dates cal[cal_date].tolist() print(f交易日总数{len(trade_dates)}) # 第三步按交易日循环拉取日线 os.makedirs(daily_data, exist_okTrue) for i, date in enumerate(trade_dates): filepath fdaily_data/{date}.parquet if os.path.exists(filepath): continue # 断点续传已下载的跳过 try: df pro.daily(trade_datedate) if df is not None and len(df) 0: df.to_parquet(filepath, indexFalse) time.sleep(0.15) # 控制频率避免触发限流 except Exception as e: print(f{date} 拉取失败{e}) time.sleep(1) if (i 1) % 200 0: print(f已处理 {i1}/{len(trade_dates)} 个交易日)逻辑说明stock_basic分三次调用是为了把退市和暂停上市的股票也纳入股票池否则回测时会出现「幸存者偏差」——你只看到了活到今天的股票那些退市的在历史上可能贡献了显著的负收益。trade_cal的start_date设为 19901219 是因为上交所 1990 年 12 月 19 日开市这是A股最早的交易日。按交易日拉取的好处是每天一次请求5000股票一次返回比逐股拉取快 5000 倍。参数说明time.sleep(0.15)是经验值Tushare 对daily接口的限制是每分钟 500 次0.15 秒间隔对应每分钟约 400 次留了余量。如果你的积分更高可以适当降低这个值。is_open1只取开市日避免拉取周末和节假日返回空数据。2.3 复权处理前复权、后复权和不复权的选择日线数据拿到手是不复权的原始价格直接用来算收益率会出大问题。比如某股票 10 送 10除权日价格直接腰斩你的策略会误判为暴跌 50%。复权方式有三种复权方式适用场景优点缺点前复权回测、技术指标计算当前价格与实际一致历史价格会随新除权变化后复权长期收益分析历史价格固定不变当前价格与实际不符不复权需要真实成交价场景价格真实无法直接算收益我一般会同时存两份一份不复权的原始数据用于计算真实成交金额和滑点一份后复权的用于计算收益率和因子。后复权的计算方式是后复权价 不复权价 × 复权因子复权因子可以从 Tushare 的adj_factor接口获取。# 获取复权因子并与日线合并 adj pro.adj_factor(trade_date20220110) daily pd.read_parquet(daily_data/20220110.parquet) merged daily.merge(adj[[ts_code, adj_factor]], onts_code, howleft) # 后复权收盘价 merged[close_hfq] merged[close] * merged[adj_factor] # 前复权收盘价 后复权价 / 最新复权因子 latest_adj merged.set_index(ts_code)[adj_factor] merged[close_qfq] merged[close_hfq] / merged[ts_code].map(latest_adj)注意前复权需要以「当前最新」的复权因子为基准所以如果你在 2022-01-10 这个时间点做前复权用的就是当天的复权因子。如果后续有新的除权前复权价格会变这就是为什么回测中更推荐用后复权——它不会因为未来事件而改变历史。3. 数据清洗把「能用」变成「敢用」的关键步骤3.1 停牌、一字板和新股上市首日的处理原始日线数据里藏着不少陷阱。停牌期间 Tushare 不会返回数据这没问题但有些股票停牌前一天和复牌后一天的价格会出现巨大跳空如果你直接算收益率会得到一个虚假的极端值。处理方式是在计算日收益率时先检查vol成交量是否为 0如果为 0 说明当天实际上没有交易应该把收益率设为 NaN 而不是 0。一字板是另一个坑。涨停板打开时成交量极低你的策略如果按收盘价成交会严重高估收益。常见做法是如果high low全天一个价格标记为「无法成交」回测时跳过这些交易日。新股上市首日的数据也要注意。A股新股首日涨幅限制经历过多次调整早期没有涨跌幅限制后来是 44%再后来科创板/创业板是 20% 起步。如果你不做区分把首日涨幅直接纳入因子计算会引入极大的噪声。def clean_daily(df): 清洗单日日线数据 df df.copy() # 标记停牌成交量为0 df[is_suspended] df[vol] 0 # 标记一字板最高价等于最低价 df[is_limit_lock] df[high] df[low] # 计算日收益率停牌日设为NaN df df.sort_values([ts_code, trade_date]) df[ret] df.groupby(ts_code)[close].pct_change() df.loc[df[is_suspended], ret] float(nan) # 剔除上市首日用list_date判断 df df.merge(all_stocks[[ts_code, list_date]], onts_code, howleft) df[is_first_day] df[trade_date] df[list_date] df.loc[df[is_first_day], ret] float(nan) return df逻辑说明pct_change()是按股票分组后计算的这样不会把不同股票的价格混在一起。停牌日设为 NaN 而不是 0是因为 0 会拉低波动率估计而 NaN 在后续计算中会被自动跳过。上市首日设为 NaN 是为了避免首日涨幅污染因子。3.2 退市股票和ST股票的标记退市股票如果不纳入回测结果会严重偏乐观。但纳入之后你还需要标记它们的状态因为 ST 股票和正常股票的涨跌幅限制不同ST 是 5%正常是 10%科创板/创业板是 20%。Tushare 的stock_basic里有name字段如果包含「ST」或「*ST」就说明是风险警示股票。# 标记ST股票 all_stocks[is_st] all_stocks[name].str.contains(ST, naFalse) # 标记退市股票 all_stocks[is_delisted] all_stocks[list_status] D # 合并到日线数据 daily_all daily_all.merge( all_stocks[[ts_code, is_st, is_delisted, list_date]], onts_code, howleft )这里有个细节ST 状态是随时间变化的一只股票可能在 2018 年被 ST2020 年摘帽。如果你只用最新的stock_basic来标记历史数据里的 ST 状态就是错的。更严谨的做法是去查namechange接口获取每只股票的历史名称变更记录然后按日期匹配。这个工作量不小但如果你的策略对涨跌幅限制敏感这一步不能省。3.3 用 DuckDB 做本地化查询和校验数据清洗完之后存成 Parquet 文件按日期分片是合理的但查询起来不方便。我一般会再用 DuckDB 建一个本地数据库把 Parquet 文件挂载进去这样可以用 SQL 直接查询速度比 pandas 快很多而且不占内存。import duckdb con duckdb.connect(ashare.db) # 直接从 Parquet 文件创建视图不复制数据 con.execute( CREATE OR REPLACE VIEW daily AS SELECT * FROM read_parquet(daily_data/*.parquet) ) # 查询示例贵州茅台2021年全年日线 result con.execute( SELECT trade_date, open, high, low, close, vol FROM daily WHERE ts_code 600519.SH AND trade_date BETWEEN 20210101 AND 20211231 ORDER BY trade_date ).fetchdf() print(result.head())DuckDB 的好处是它直接读 Parquet不需要导入过程而且支持完整的 SQL 语法。你可以用它做数据质量校验比如检查是否有重复的(ts_code, trade_date)组合-- 检查重复记录 SELECT ts_code, trade_date, COUNT(*) as cnt FROM daily GROUP BY ts_code, trade_date HAVING cnt 1 LIMIT 10;如果这条 SQL 返回了结果说明数据有重复需要去重。正常情况下应该返回空集。4. 避坑指南五个让我重新拉数据的血泪教训4.1 坑一退市股票没拉回测收益虚高现象回测年化收益 25%实盘跑起来只有 8%。原因股票池只用了当前上市的股票那些退市的在历史上可能跌了 80%但你的回测里根本没有它们。解决在stock_basic调用时加上list_statusD和list_statusP把退市和暂停上市的股票全部纳入。这一步做完回测收益通常会下降 5-10 个百分点但更接近真实。4.2 坑二复权因子用错了时间点现象某股票在除权日前后收益率出现 ±50% 的跳变。原因用了前复权价格但复权因子是以「当前」为基准算的历史价格被扭曲了。解决回测中统一用后复权价格计算收益率用不复权价格计算成交金额。如果你非要用前复权确保每次除权后重新拉取全量数据否则历史价格会对不上。4.3 坑三交易日历用了自然日现象拉取数据时发现某些日期返回空程序报错中断。原因用了自然日循环周末和节假日也去请求。解决先用trade_cal获取交易日列表只循环交易日。另外注意trade_cal的is_open字段是字符串1不是整数1直接比较会出问题。4.4 坑四Parquet 文件按日期分片太多导致查询慢现象DuckDB 查询一年数据要等十几秒。原因按日分片产生了 7000 个小文件每次查询都要打开大量文件。解决按年合并 Parquet 文件或者用 DuckDB 的COPY命令把数据重组成按股票分区的结构。我一般会保留按日分片的原始文件作为备份另外生成一份按年合并的版本用于日常查询。4.5 坑五没做数据校验用了半年才发现缺了三个月现象某天发现 2015 年 7 月的数据全是 NaN。原因当时拉取时遇到网络问题程序跳过了失败的日期但没有记录。解决拉取完成后用交易日历做一次全量比对检查每个交易日是否有对应的 Parquet 文件且文件内记录数大于 0。这个校验脚本应该作为数据管道的最后一步自动运行。# 数据完整性校验 missing_dates [] for date in trade_dates: filepath fdaily_data/{date}.parquet if not os.path.exists(filepath): missing_dates.append(date) else: df pd.read_parquet(filepath) if len(df) 0: missing_dates.append(date) print(f缺失交易日{len(missing_dates)} 天) if missing_dates: print(f前10个缺失日期{missing_dates[:10]})5. 增量更新与长期维护让数据管道自己跑起来数据拉到 2022-01-10 不是终点如果你打算持续用这套数据做研究增量更新机制必须提前设计好。我的习惯是把拉取脚本改造成「检查最新日期 → 拉取缺失日期 → 校验 → 合并」的自动化流程用 cron 或 Windows 计划任务每天收盘后跑一次。具体做法是维护一个last_update.txt文件记录最后成功拉取的日期每次运行时从该日期1 天开始拉取到当前交易日。拉取完成后更新这个文件。如果中间有失败下次运行会自动补上。这个机制简单但有效比每次全量拉取省时间也比手动检查靠谱。另一个技巧是用 DuckDB 的INSERT INTO做增量合并而不是每次重新创建视图。这样查询性能不会随着数据量增长而下降。# 增量更新示例 import datetime def get_last_update(): try: with open(last_update.txt, r) as f: return f.read().strip() except FileNotFoundError: return 19901219 def update_daily(): last get_last_update() today datetime.datetime.now().strftime(%Y%m%d) cal pro.trade_cal(exchangeSSE, start_datelast, end_datetoday, is_open1) dates_to_fetch [d for d in cal[cal_date].tolist() if d last] for date in dates_to_fetch: df pro.daily(trade_datedate) if df is not None and len(df) 0: df.to_parquet(fdaily_data/{date}.parquet, indexFalse) time.sleep(0.15) if dates_to_fetch: with open(last_update.txt, w) as f: f.write(dates_to_fetch[-1]) print(f更新完成新增 {len(dates_to_fetch)} 个交易日)最后说一个我自己的习惯每次拉完数据我会随机抽 3 只股票手动去行情软件上核对最近 5 天的 OHLCV 数据。这个动作花不了两分钟但能帮你发现接口字段错位、复权因子异常这类低级错误。数据这件事宁可多花十分钟校验也别等回测跑出离谱结果再回头查——那时候你连是哪天出的问题都不知道。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
Oracle SQLT 工具包实战:从10g到19c安装、诊断报告生成与跨版本执行计划对比 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 1:56:20
如何给AI Agent会话打量化分数?CANNBot-Sentry六维轨迹评分体系完整解析 如何给AI Agent会话打量化分数?CANNBot-Sentry六维轨迹评分体系完整解析 【免费下载链接】cannbot-sentry CANN 生态中面向 Agent 工作流的“哨兵”:观测 审计 评测三位一体的质量基础设施 项目地址: https://gitcode.com/cann/cannbot-sentry
… · 2026/9/26 1:56:20
DankMaterialShell 的 Void Linux 打包与安装指南:XBPS 模板、自托管仓库与运行配置 桌面应用 【免费下载链接】DankMaterialShell Desktop shell for wayland compositors built with Quickshell & GO, optimized for niri, hyprland, sway, MangoWC, labwc, and MiracleWM. 项目地址: https://gitcode.com/gh_mirrors/da/DankMaterialShell 点击… · 2026/9/26 2:35:15
Mumble 网络协议深度解析:TCP 控制通道与 UDP 语音通道的通信机制全解 音视频即时通讯 【免费下载链接】mumble Mumble is an open-source, low-latency, high quality voice chat software. 项目地址: https://gitcode.com/gh_mirrors/mu/mumble 点击查看 免费下载 Mumble 是一款开源、低延迟、高质量语音聊天软件,其客户端… · 2026/9/26 2:35:15
ADG408BRZ-REEL7模拟多路复用器详解:选型、原理与设计要点 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 2:35:09
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46