
做A股量化或者单纯想写个盯盘小工具的朋友应该都有同感实时行情API不难找难的是“实时”和“历史”能不能互相配合。市面上能免费拿数据的A股实时行情API其实不少但每个接口都有自己的脾气有的实时快照延迟低、历史分钟线却要积分有的历史数据免费又全但字段缩写得翻半天文档。更别提停牌、复权、限流、字段改名这些坑搞不好能把人折腾到怀疑人生。这篇就围绕A股实时行情API与历史行情获取这件事把我自己实际摸过的数据源、写过的封装代码、踩过的坑全部摊开讲。无论你是想搭一个自动盯盘脚本还是准备用历史数据做策略回测这篇文章都能帮你少走弯路。内容不会推荐个股也不做投资建议只谈数据怎么拿、怎么处理、怎么落库这是所有行情系统的地基。1. 动手之前先把需求和坑摸清楚1.1 实时行情和分钟级历史行情到底有什么不一样很多人第一次接触行情接口时容易把“实时行情”和“历史行情”混在一起。其实这两类数据服务的场景完全不同用错地方会得出很离谱的结论。实时行情本质上是“当前市场状态”的快照包含最新价、涨跌幅、买一卖一、成交量、成交额这些字段。它解决的问题是现在价格是多少要不要触发提醒今天市场整体是涨是跌。这类数据对延迟敏感但对复权不敏感因为你看的是此时此刻的真实成交价。实时行情又分Level-1和Level-2Level-1是基础快照通常延迟几秒到十几秒Level-2是逐笔成交和十档行情那基本是券商和付费数据商的领域个人开发者很少直接接触。历史行情则是把已经发生的交易按K线组织起来比如1分钟、5分钟、日线。它解决的问题是过去一段时间价格怎么走基于历史数据计算指标、训练模型、做回测。历史行情对复权非常敏感因为分红送股会导致价格出现“断层”不复权的数据直接算收益率会失真。还有一个特别容易忽略的差异实时接口返回的是毫秒级时间戳历史K线返回的是bar的结束时间。比如一根5分钟K线时间戳是14:35那么它代表的是14:30到14:35这五分钟的成交聚合。很多人直接把这两个时间戳混用排序和回测就全乱了。1.2 数据源选型不是越贵越好也不是越全越好我前前后后用过大概四类数据源各有适合的场景。先把结论放在前面学习和小型项目用AkShare加Baostock工程化和需要稳定分钟线可以上Tushare Pro的积分服务临时瞅一眼实时行情用新浪腾讯的HTTP接口也完全够用。数据源实时/历史分钟数据复权支持主要限制适合场景AkShare两者都有1/5/15/30/60分钟前复权、后复权、不复权依赖第三方网页可能失效研究、爬取全市场快照Baostock历史为主5/15/30/60分钟前复权、后复权、不复权无1分钟只有历史免费历史回测Tushare Pro两者都有分钟线需高积分复权因子积分制分钟线门槛高工程化、数据规范新浪/腾讯HTTP实时为主无无字段不稳定有反爬快速取快照低频率调用AkShare是我个人最常用的一款因为它把很多数据接口统一成了Python函数装一个库就能拿全市场五千多只股票的实时快照。缺点也比较明显它本质上是把网页接口包装了一层上游网页改版之后老版本库可能就报错所以用之前要记得升级。Baostock则是一股清流不需要token也不需要登录网页直接pip安装就能用历史日线和分钟线都很稳定。但它没有1分钟线而且只提供历史数据拿不到当天实时行情。所以很多时候我是两者配合着用实时用AkShare回测用Baostock。Tushare Pro适合对数据规范性要求高的项目。它的数据结构非常干净文档也全但分钟级别的数据需要很高的积分对免费用户不太友好。如果只是拿日线基础积分也够用。2. 核心细节拆解API调用背后那些容易翻车的地方2.1 交易日、集合竞价、停牌这些时间概念先对齐接任何A股行情API之前建议先把交易所的时间规则在脑子里过一遍。很多人拿到的数据“看起来不对”其实不是接口的问题而是自己没理解A股的交易机制。先说交易日历。A股并不是每个工作日都交易元旦、春节、国庆这些法定节假日会休市有时候周末还会调休补班但股市依然休市。所以写程序时不要用“星期一到星期五”判断交易日应该直接拉交易所的交易日历。AkShare里有交易日历接口Tushare Pro也有trade_cal最好提前把日历缓存下来。再说交易时段。A股连续竞价时间是9:30到11:30、13:00到15:00但9:15到9:25还有集合竞价。很多实时接口在9:25之前会返回一些“虚拟”的竞价价格如果你拿这个价格去做实盘判断要特别注意时间窗口。我习惯在代码里过滤掉9:30之前的数据避免把集合竞价的波动当成实时行情。停牌也是个容易忽略的点。股票停牌期间历史接口通常会跳过那几天实时接口则往往返回最后一条有效数据或者空值。如果你直接用“前一天的收盘价”填充停牌日然后计算连续涨跌幅结果会完全失真。正确的做法是保留空行或者标记为NaN等复牌后再根据公告处理。2.2 复权到底怎么选不复权、前复权、后复权做历史行情获取时复权是最容易翻车的概念之一。打个比方一只股票昨天收盘10元今天分红每股1元现金除权后今天的开盘参考价会变成9元附近。如果不做任何处理K线图上会出现一个向下的跳空缺口但你的账户资产并没有减少只是钱变成了现金。这个“缺口”会让所有基于价格的指标算错。不复权就是原始成交价适合实时监控和当前价格判断但绝对不适合直接做长周期回测。前复权是以“当前最新价”为基准把历史价格整体下调好处是看走势图很直观坏处是每一次新除权发生历史数据都得重新算一遍所以前复权数据不是一成不变的。后复权则是以最早的某个价格为基准让价格一路向上不受后续除权影响。做收益计算和因子回测时后复权通常更稳定因为它不会因为中途分红送股而改变历史数值。不同数据源的复权实现还有细节差异比如有的把现金分红按“税前”处理有的按“税后”处理这会导致同一只股票在两家平台的复权价差个几分钱。所以我的一个实操原则是回测全程只用同一个数据源的同一类复权不要混用。你不需要强迫自己手工复权但一定要知道选的是哪种并且把参数写死在代码里。2.3 限流、频率限制、字段漂移免费接口最难忍受的就是限流。有些HTTP接口如果每秒请求超过几次会直接返回403甚至封你IP一段时间。AkShare这类库内部没有帮你做节流所有请求频率都得自己控制。我的习惯是每次循环之间至少sleep 0.5到1秒如果需要拉几千只股票就分段跑千万别开个线程池一把梭。字段漂移是另一个隐性坑。免费接口的返回结果列名经常变比如上周还是“最新价”这周可能改成“最新价_”或者直接变成英文。如果你在代码里写死了df[最新价]数据源一改版整个脚本就崩。更稳妥的做法是先打印df.columns看看然后做个字段映射字典尽量用索引加列名双保险。还有单位问题。有的接口返回的价格单位是元有的是分成交量有的是手有的是股成交额有的是元有的是万元。我在接新浪接口时就被“手”和“股”坑过一次明明显示成交量很大实际换算过来少了好几个数量级。拿到数据后第一件事不是算指标而是对一下最新价和成交量是否符合常识。3. 实操写一个能直接跑通的A股行情API封装3.1 用AkShare快速搞定实时行情快照实时行情最直接的拿法就是用AkShare的东方财富接口一行代码拉回全市场快照。在命令行先安装pip install akshare pandas然后运行这段代码import akshare as ak df ak.stock_zh_a_spot_em() print(df.columns.tolist()) print(df[[代码, 名称, 最新价, 涨跌幅, 成交量, 成交额]].head(10))第一次运行可能比较慢因为它要拉全市场的数据通常需要几秒到十几秒。输出里会看到类似“000001”“600000”这样的六位代码注意不同板块的代码前缀不需要自己加AkShare已经帮你处理好了。这个接口返回的是实时快照字段里包含最新价、涨跌幅、今开、昨收、最高、最低、成交量、成交额等。如果你是写监控告警脚本直接用这个DataFrame做筛选就行不需要反复请求单只股票。有一点要提醒实时快照的“最新价”在非交易时段会停留在收盘价不要误以为接口坏了。另外每分钟请求这类全市场接口的次数不要太频繁我一般只在开盘后每隔几分钟拉一次或者干脆做事件触发不搞轮询。3.2 拿分钟级历史行情AkShare的三种方式如果你要历史日线或分钟线AkShare也有对应接口。日线是stock_zh_a_hist分钟线是stock_zh_a_hist_min_em。先看日线import akshare as ak df_daily ak.stock_zh_a_hist( symbol000001, perioddaily, start_date20240101, end_date20240301, adjustqfq ) print(df_daily.head())adjust参数可以传qfq前复权、hfq后复权、不复权。注意start_date和end_date的格式是“YYYYMMDD”不是带横杠的日期写了带横杠会被接口拒绝或者解析错误。分钟线接口稍微有点不一样。它不但要日期还要补上时间部分起始和结束格式类似“2024-01-02 09:30:00”。period参数可以是1、5、15、30、60对应1分钟、5分钟等。import akshare as ak df_min ak.stock_zh_a_hist_min_em( symbol000001, period5, start_date2024-01-02 09:30:00, end_date2024-01-05 15:00:00, adjust ) print(df_min.head())这里有个大坑分钟线接口单次返回的条数有限制如果你一次性拉一年5分钟数据很可能会被截断。我的处理办法是按月分段拉取最后用pandas的concat拼接。3.3 用Baostock补历史日线和分钟线免费且稳定如果觉得AkShare分钟线拉长周期太吃力可以试试Baostock。安装pip install baostockBaostock的代码风格有点特别它要先login用完再logout。代码里会频繁出现rs.next()这是数据库游标式的遍历习惯了就好。import baostock as bs import pandas as pd lg bs.login() rs bs.query_history_k_data_plus( sh.600000, date,code,open,high,low,close,volume,amount,adjustflag, start_date2024-01-01, end_date2024-03-01, frequencyd, adjustflag2 ) rows [] while (rs.error_code 0) rs.next(): rows.append(rs.get_row_data()) df pd.DataFrame(rows, columnsrs.fields) print(df.head()) bs.logout()注意几点。第一股票代码必须带市场前缀比如sh.600000、sz.000001不能只写六位代码。第二adjustflag参数是字符串1表示后复权2表示前复权3表示不复权。第三Baostock默认返回的都是字符串你得自己转成float。Baostock不提供1分钟线但5分钟、15分钟、30分钟、60分钟都有对于大部分回测场景其实够用了。它的优势是稳定、免费、没有token而且很少出现接口失效的情况。3.4 自己封装一个统一行情类每次都手动调不同库的接口很容易写乱。我建议项目一开始就做一个非常薄的封装统一入口。下面是一个简化版class AStockData: def __init__(self): self._bs_logged_in False def realtime_quotes(self): import akshare as ak return ak.stock_zh_a_spot_em() def history_daily(self, symbol, start, end, adjustqfq): import akshare as ak return ak.stock_zh_a_hist( symbolsymbol, perioddaily, start_datestart, end_dateend, adjustadjust ) def history_min(self, symbol, period, start, end): import akshare as ak return ak.stock_zh_a_hist_min_em( symbolsymbol, periodperiod, start_datestart, end_dateend, adjust )有了这个类后续脚本里只需要调用ds.history_daily(000001, 20240101, 20240301)而不用关心底层是AkShare还是别的。如果你的项目可能会切换数据源可以在类内部再套一层try/except一个源失败自动换另一个。这样哪怕免费接口临时抽风你的脚本也不会立刻崩掉。4. 扒一扒实战中遇到的高频问题4.1 常见报错速查表手里有代码心里有底气但报错还是会如期而至。下面是我实战中总结的报错场景和对应解法现象原因处理方式KeyError: 最新价AkShare列名改版打印df.columns升级akshare改用新列名分钟线数据只有最近几天接口单次返回条数限制按月分段拉取最后concatBaostock返回空结果代码前缀写错或股票停牌补上sh./sz.前缀检查日期范围HTTP 403请求频率过高被拒绝增加sleep降低请求频率加随机延时Tushare提示积分不足分钟线权限不够攒积分或改用Baostock/AkShare实时快照长时间不变非交易时段或接口缓存核对当前时间确认是否在交易时段数据里有大量NaN停牌或接口数据缺失保留空值不要用0填充这张表常看常新因为你永远不知道数据源哪天会改版。我遇到KeyError之后的第一反应就是看列名而不是去代码里找bug。4.2 拿到数据之后要先做几件事行情数据不是拿来就能直接用的直接跑策略是灾难。我的标准流程是四步排序、去重、清洗、校验。先排序。K线数据必须按照时间升序排列否则后续算移动平均、MACD全都乱套。pandas里直接df.sort_values(date, inplaceTrue)就可以。再去重。有的接口在边界情况下会返回重复数据尤其分段拉取时重叠日期。用drop_duplicates(subsetdate, keeplast)处理注意保留最后一条而不是第一条因为后拉的数据通常修正更准确。接着清洗。把成交量、成交额统一转成数值类型把字符串空值替换成NaN。停牌日期的数据如果返回的是零我一般会删掉而不是当0处理因为“成交量为0”和“停牌没交易”是两码事。最后校验。你可以拿官方行情软件或者公开网站上的某只股票、某天数据做抽查确保价格、成交量在一个量级。这个小动作花不了两分钟但能挡住大部分接口异常。4.3 反爬与接口稳定性免费接口的底线思维很多免费接口本质上是爬虫抓取公开网页数据稳定性全看上游心情。使用这些接口要有底线思维不要高频请求不要绕过限制更不要拿来做商业服务。我见过有人写个循环每0.1秒访问一次结果IP被目标站封掉连累整个网段都进不去。这种操作在法律和道德上都站不住脚。更推荐的做法是把免费接口当成“开发环境”和“研究工具”正式项目如果真的要商用或者实盘去找有授权的数据商。那点钱相对于交易风险和数据稳定性真不算贵。多数据源兜底是我一直在用的方案。写一个简单的重试函数AkShare失败就切Baostock实在不行再等几秒重试一次。免费方案就是这样你能做的就是尽量降低单点故障的影响。另外本地缓存一定要做哪怕只是存成一个CSV也能在接口挂掉时给你留一条后路。5. 后续扩展从行情数据到回测系统5.1 把历史数据喂给Backtrader历史数据拿到手一个常见需求就是拿去做策略回测。Backtrader是目前比较流行的开源回测框架它可以直接接收pandas DataFrame作为数据源。下面是一个最小示例import backtrader as bt import pandas as pd # 假设df是已经清洗好的日线数据 # 必须保证列名是 date, open, high, low, close, volume df df[[date, open, high, low, close, volume]].copy() df[date] pd.to_datetime(df[date]) df.set_index(date, inplaceTrue) data bt.feeds.PandasData( datanamedf, datetimeNone, openopen, highhigh, lowlow, closeclose, volumevolume, openinterest-1 ) cerebro bt.Cerebro() cerebro.adddata(data) cerebro.broker.setcash(100000.0) # 接下来添加你自己的策略类这里有几个细节。Backtrader的PandasData默认datetime是从DataFrame的索引读取所以要么把date列设成索引要么显式指定datetime列。openinterest字段如果你没有持仓兴趣数据就传-1不要留空。另外分钟线回测的成交量单位要和数据源保持一致否则最终资金曲线会非常奇怪。我建议第一次跑回测时先不做任何策略只加载数据打印一下K线数量确认数据量对不对再往里面加策略逻辑。否则数据边界的问题和策略逻辑的问题混在一起很难排查。5.2 增量更新与数据落地行情数据量会越攒越大尤其是分钟线。如果不做增量更新每次都全量拉取既慢又容易触发限流。画个简单的表结构用SQLite就能满足大部分个人场景CREATE TABLE daily_kline ( symbol TEXT, date TEXT, open REAL, high REAL, low REAL, close REAL, volume REAL, PRIMARY KEY (symbol, date) );增量更新的思路其实很直接先从数据库查出每个股票本地最大的日期然后只拉取从那个日期到现在的数据最后做一次“INSERT OR REPLACE”写进去。这样做的好处是断点续传哪怕跑一半崩了下次自动接着来。分钟级数据量更大我一般会把不同股票拆成单独的表或者直接用parquet文件按股票存。全市场所有股票的1分钟数据加起来非常庞大如果全塞到一个SQLite里查询速度会肉眼可见地下降。实际上个人回测很少需要全市场分钟级数据按策略需要分批拉取更合理。5.3 常见误区别用实时快照攒历史K线有个想法我经常见到能不能每隔几分钟调一次实时接口把数据存下来慢慢攒出自己的历史K线理论上可行但实际上极其不靠谱。实时接口返回的是某个瞬间的快照不是K线聚合结果你自己攒出来的1分钟K线缺少开盘、最高、最低这些聚合字段而且一旦程序在交易时段中途断个几秒那根K线就是残缺的。要历史数据老老实实用历史行情API这是两码事。如果你非要自己造轮子也一定要基于Level-2逐笔成交去合成而不是基于每秒快照。个人做这套东西的成本非常高远不如直接买个现成的分钟数据包。记住一个原则专业的事情交给专业接口你的时间应该花在策略和系统上而不是造数据。我自己在做A股数据这块时踩过不少坑从第一次拿到“最新价”KeyError到发现Baostock分钟线代码前缀写错再到用免费接口刷太快被封IP每一个问题都很低端但都真实浪费过时间。现在的习惯是实时数据用AkShare全市场快照历史回测用Baostock或者Tushare分钟线必须分段拉取数据落地统一放SQLite。这套组合拳不算最完美但胜在稳定、免费、可复制。如果你是刚开始接触A股行情API建议先照着我这份代码把日线跑通再加上分钟线和实时快照一步步来别一上来就全市场批量下载。数据源永远在变代码里的防御和兜底逻辑比一次性的正确结果重要得多。