做量化回测、写行情监控脚本、搭个人数据看板,最让人头疼的往往不是策略本身,而是数据。自己去爬网页,反爬机制分分钟教你做人;买付费终端,一年几千块的费用个人开发者很难扛住。这几年我前前后后把股票、期货、外汇、数字货币四个市场的免费API都接过一遍,踩了不少坑,也整理出一套相对顺手的接入方法。这篇文章不搞“十大免费接口盘点”那种标题党,列个表就完事没有意义。我打算从实际接入的视角,把每个市场的鉴权方式、数据格式、字段陷阱、维护技巧一次说清楚,让你既能跑通,也不容易在后续维护中翻车。
1. 免费金融数据接口怎么选?先看清三类来源和适用场景
1.1 免费接口从哪来:官方直供、聚合平台、开源包装
先梳理一下市场上免费数据的来源,很多刚入门的朋友一上来就搜“免费API”,然后被各种平台的宣传晃花了眼。实际上,免费金融数据无非三类来源。
第一类是官方直供。有些交易所会直接提供公开行情接口,比如部分商品交易所的日行情数据下载,或某些海外交易所的公开行情API。这类接口最大的优点是权威、字段相对标准,但缺点是覆盖面窄,如果既要拿股票又要拿期货数据,往往要对接好几家,各家文档风格还不一样,维护成本直线上升。
第二类是第三方聚合平台。这类平台会把多个市场的行情统一包装成API,免费版通常有调用次数和频率限制,但胜在数据类型多、格式趋近一致。我的经验是,个人开发者和量化入门者做数据源,首选就是这类平台,因为省掉大量字段对齐工作。不过免费档的限制要提前摸清楚,有的平台限制历史数据只能拉最近两年,有的只给日线不给分钟线,注册前先看文档。
第三类是开源社区的包装库和爬虫。社区里有很多现成的开源库,底层可能用爬虫抓取行情页面。这类方案的优势是免费、灵活、接口形式往往很友好,缺点是反爬升级时容易失效,数据延时也看运气,碰上页面改版就只能等社区更新。我建议用它做备用数据源,不要当主源。一旦主源挂了,备用源能让你不至于完全停摆。
这三类来源没有绝对的好坏,关键是匹配你的使用场景。我的选型标准很简单:回测和策略研究优先用聚合平台,实时行情监控找支持WebSocket的官方直供,开源库只做应急备用。下面这张表可以帮你快速判断该往哪个方向找:
| 来源类型 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 官方直供 | 权威、字段标准、数据可靠 | 覆盖面窄、多市场要对接多家 | 单市场实时行情、深度数据 |
| 第三方聚合 | 多市场统一、格式相近、接入快 | 免费限制多、频率低、可能缺历史 | 个人研究、回测、多市场看板 |
| 开源包装 | 免费灵活、社区活跃 | 稳定性差、有反爬风险、需自行维护 | 应急备用、低频数据采集 |
1.2 选型之前先想清楚这四件事
具体选哪家接口之前,别急着注册账号,先把四个问题想明白,这可以省掉很多后面改代码的麻烦。
第一个问题是数据频率够不够。免费接口一般只到分钟级,tick级逐笔数据基本都要收费。对回测来说,日线已经能解决大部分问题,做日内策略才需要分钟线;如果只是搭一个每日收盘后的数据看板,日线绰绰有余。先确认自己的分析粒度,再去挑数据源,避免花大力气接了高频接口却发现用不上。
第二个问题是协议方式适不适合。HTTP轮询简单直接,适合低频任务;WebSocket适合实时高频场景。我自己做实时监控时,数据源优先选支持WebSocket的,因为分钟级K线如果用轮询,每秒都要请求一次,很容易触发限流。反过来,如果你只是每天跑一次定时任务拉数据,完全没必要上WebSocket,徒增连接管理的复杂度。
第三个问题是数据质量能不能接受。免费数据常见的坑有历史数据不全、字段缺失、复权处理粗糙。你要判断的是,这个数据源能不能满足你的准确性要求。做战略研发,数据里多几个空值可能就会导致结果偏差,这块不能马虎。
第四个问题是合规和使用限制。免费接口通常都有服务条款,比如不能商用、不能大量缓存。做个人研究和学习没有问题,打算商业落地前一定要确认使用限制。数据源被封号是小事,因此带来合规风险才是大麻烦。
2. 股票与期货接入实操:字段、复权、合约规则一次说透
2.1 股票行情接口的鉴权与核心字段处理
股票数据是需求量最大的,也是新手最容易踩坑的。先说鉴权,免费接口一般通过token完成认证。注册账号之后,在个人中心生成一个token,然后请求时放到请求头里:
import requests api_url = "https://api.example.com/stock/daily" params = { "symbol": "000001", "start": "20240101", "end": "20241231", "adjust": "qfq" } headers = {"Authorization": "Bearer 你的token"} resp = requests.get(api_url, params=params, headers=headers, timeout=10) if resp.status_code == 200: data = resp.json()["data"] else: print("请求失败:", resp.status_code)这里有几个细节值得多说几句。
为什么请求要带timeout?免费接口稳定性参差不齐,不带超时的话,一旦服务端挂起,你的脚本会一直卡住,影响整个任务链路。我习惯设10秒,超时就记录日志并重试。重试次数和退避策略也要有,连续三次失败就切换备用数据源,而不是无限重试。
为什么复权参数要单独设置?股票分红送股之后,历史价格会出现断层。不复权(adjust=none)数据会让K线出现假跳空,比如一只股价从10元突然变成8元,看起来像暴跌,实际上只是除权除息。前复权(qfq)用当前价格水平回推历史,后复权(hfq)用历史价格水平递推到现在。回测分析我一般用前复权,计算收益率更接近真实情况;做技术指标研究则要看具体场景,如果指标依赖绝对价格,不复权更合适。这些概念听起来抽象,但实际跑一遍数据对比就一目了然。
拿到行情数据后,典型的字段包括时间戳、开盘价、最高价、最低价、收盘价、成交量。不同接口的字段名不一样,有的叫open,有的叫open_price,有的时间返回字符串,有的返回时间戳。所以我强烈建议在接入层做一次统一的字段映射,把所有数据源都转成同一套结构:
STANDARD_COLUMNS = { "open": "open", "high": "high", "low": "low", "close": "close", "volume": "volume" }这样下游不管接哪个数据源,策略代码都不用改。这一层抽象看起来简单,但在多数据源并存时能省下大量调试时间。我甚至会把原始返回的JSON完整保存一份到一个日志目录,出问题时不至于只能对着局部数据结构猜。
股票接入还有一个很容易被忽略的点:交易日历。免费接口多数不会提醒你哪天休市。如果直接按自然日去取数、按行数计算均线,一到长假就会算出错误结果。解决办法是维护一份交易日历表,或者按接口返回的日期过滤非交易日。交易日历在网上能找到现成的数据文件,也可以自己用历史数据推断,但要注意节假日调整安排,比如调休导致的周末开市。
2.2 期货数据里最容易被忽略的连续合约问题
期货比起股票,多了一个“合约”维度,这是新手最常栽跟头的地方。
每个期货品种在不同月份都有对应合约,比如螺纹钢的主力合约是成交量最大的那个月份。直接拿一个月份的合约拉历史数据,会遇到两个问题:一是单个合约的活跃期可能只有几个月,历史深度不够;二是主力合约会换月,如果简单把前后两段拼接,价格会因换月产生跳空,技术指标会算错。
免费接口对期货数据通常有两种提供方式:一种是按具体合约月份取数,一种是直接提供“主力连续合约”。我建议优先用主力连续,因为它按成交量动态切换主力,历史连贯性最好,适合做策略回测。代码示例:
import requests url = "https://api.example.com/future/daily" params = { "symbol": "rb8888", # 某商品期货主力连续合约示意代码 "start": "20240101", "end": "20241231" } resp = requests.get(url, params=params, timeout=10) data = resp.json()["data"]拿到数据后,最好再核对一下持仓量或成交量字段,确认这段数据在换月点是否被接口做了特殊处理。有些接口会标注“换月标记”,有条件的话可以基于这个标记来自动剔除换月当日的异常K线。换月当天的K线往往同时包含旧合约的尾盘和新合约的开盘,成交量会出现明显放大或萎缩,直接用会影响指标计算。
夜盘也需要单独处理。商品期货有夜盘时段,部分品种交易到次日凌晨。如果你拿的是按日期切分的数据,注意接口默认的时间切分是以几点为界。我遇到过把夜盘算进前一天的数据源,也遇到过把夜盘和次日日盘算成同一天的数据源,两种结果对日内指标计算影响很大。处理方法是统一按交易所官方时间规则切分,自己写一个时段映射表,不要依赖接口的默认行为。比如螺纹钢夜盘是21点到23点,那23点之后的数据应该归到下一个交易日,而不是当天。
3. 外汇与数字货币的实时API:两个容易踩坑的市场
3.1 外汇数据:报价源、时区与休市
外汇免费数据比股票期货都要“虚”一些。先解释一下:外汇市场没有统一的交易所,报价来自全球各大银行和做市商。不同数据源拿到的同一货币对价格,可能在最后几位小数上有差异,这很正常。免费接口的报价通常来自某一家或几家聚合报价源,你只能接受这个报价,没有太多选择空间。
接入外汇数据时,第一个要解决的是时区问题。外汇市场24小时交易,但结算时间点不是自然日零点,而是美东时间下午5点,这造成了“一个交易日横跨两个自然日”的情况。如果你用自然日划分数据,日线会错位。我建议设定一个明确的交易时段定义,比如“每天凌晨5点到次日凌晨5点为一个交易日”,并在代码里统一处理:
import pandas as pd df["datetime"] = pd.to_datetime(df["datetime"], utc=True) # 统一转成自定义交易时段的标签 df["trade_date"] = df["datetime"].dt.tz_convert("America/New_York").dt.date第二个问题是“实时”二字要打折扣。很多免费外汇API宣称实时报价,实际上数据源本身可能是15分钟或20分钟快照。我测试过几个平台,网页端看着是跳动的实时价格,接口返回的却是缓存数据。做回测问题不大,但如果你要基于报价做实时交易信号,一定要先做延迟测试。测试方法很简单:连续调用接口取同一个货币对的最新价,同时记录本地时间,然后和网页端报价对比,多测几次就能看出延迟范围。
第三个问题是休市。虽然外汇是24小时交易,但周末会休市,重要节假日流动性极低。免费接口在休市时段返回的数据,可能是最后一条报价的重复值,也可能是空值,这会让你的K线图上出现一段“死线”。处理方法是根据货币对所属市场维护一个开闭市日历,休市时段直接跳过去,不做补数,避免产生虚假交易记录干扰分析。
3.2 数字货币数据:REST与WebSocket双通道怎么用
数字货币市场是免费API最丰富的领域,几乎每家交易所都提供公开行情接口。但丰富也意味着碎片化:同一时刻的BTC价格在不同交易所可以差出几十甚至上百美元,K线的字段命名和返回结构也各不相同。
数字货币的行情数据主要有三类:K线、深度、成交明细。K线适合做技术分析和回测,深度和成交明细适合做实时监控。免费接口里,REST适合低频拉K线和历史数据,WebSocket适合实时订阅。
REST拉K线的典型写法:
import requests url = "https://api.example.com/market/kline" params = { "symbol": "BTC/USDT", "interval": "15m", "limit": 200 } resp = requests.get(url, params=params, timeout=10) data = resp.json()WebSocket订阅就麻烦一点。交易所的WebSocket一般要求先发订阅消息,再收推送数据。我实际用下来,最大的坑有两个:断线不重连、心跳不处理。很多免费接口的WebSocket服务端会定期断开闲置连接,客户端如果不实现心跳和自动重连,过一会儿数据就停了,而你的程序看起来还在跑。我自己踩过一次,监控脚本运行两小时后静默掉线,数据全丢了,日志里没有任何报错。
订阅和重连的框架大概是这样:
import json import websocket def on_message(ws, message): msg = json.loads(message) # 处理行情数据 if msg.get("type") == "ticker": print(msg["data"]) ws = websocket.WebSocketApp( "wss://api.example.com/ws/market", on_message=on_message, on_error=lambda ws, err: print("错误:", err) ) # 启动后先订阅 def on_open(ws): ws.send(json.dumps({ "type": "subscribe", "channel": "kline", "symbol": "BTC/USDT", "interval": "1m" })) ws.on_open = on_open ws.run_forever(ping_interval=30, ping_timeout=10)ping_interval和ping_timeout这两个参数就是心跳机制。我在测试中发现,设置30秒一次的心跳,可以大大降低被服务端主动断开的概率。即使断开了,也要在on_close回调里做自动重连逻辑,而不是让程序默默退出。重连之后要重新发订阅消息,否则看起来连上了,实际上收不到数据。
数字货币数据还有字段对齐的问题。不同交易所的K线返回,时间戳有的用秒、有的用毫秒,有的返回数组,有的返回对象。我统一在接入层做一层转换,时间戳统一转成毫秒整数,价格字段按开高低收统一命名。另外,不同交易所之间的价差有时会成为策略的一部分,如果你做跨交易所监控,记得给每个数据源加上source字段标识。
这里多说一句合规:数字货币交易在不同地区的监管政策差异较大,本文只讨论数据接口的技术接入方式,不构成任何投资建议。实际使用相关数据时,先确认所在地区的合规要求,不要想当然。
4. 跑通一条完整接入链路:从token申请到数据入库
4.1 统一数据模型和缓存策略
前面我反复提到“统一数据模型”,这里单独展开讲一下,因为这是整条接入链路的骨架。
我设计的标准数据结构包含五个核心字段:timestamp、open、high、low、close、volume。外加一组扩展字段:symbol、source、interval。所有市场的数据在进入本地存储之前,都先转成这个结构。
这样做的收益,我在同时维护四类数据源之后体会特别深。如果每个数据源各自为政,下游做策略回测时就要写四套读取代码。统一模型之后,四类市场的数据可以并存于同一张表里,按symbol和interval区分,策略代码写一次就够了。
source字段很多人会忽略。当你同时接了两家股票数据源做交叉验证时,没有这个字段,出了问题都不知道是哪家的数据。我习惯每个数据表都带source字段,后续排查问题时能少走很多弯路。
缓存策略也值得说一下。免费接口往往有频率限制,但历史数据又是低频更新的,完全没必要每次跑脚本都重新拉一遍。我的做法是:历史数据第一次全量拉取后存本地,之后每天只增量拉最近几天的数据,启动脚本时先读缓存,再拉增量缺口。这样既省调用额度,又减少等待时间。
4.2 增量更新的断点续传思路
增量更新的核心是断点续传。简单说就是记录“上次拉到哪里了”,下次从这个位置继续拉。
我用最朴素的方式实现:在存储表里维护一个last_update字段,每次更新时查一下每个标的的最新数据时间,然后只请求这个时间之后的数据。代码逻辑:
def fetch_incremental(symbol, interval, last_ts): params = { "symbol": symbol, "interval": interval, "start": last_ts, "limit": 500 } # 发起请求并解析 new_rows = fetch_and_parse(params) return new_rows注意两个细节:一是免费接口的单次请求通常有限制(比如一次最多返回500根K线),超过要分页拉取;二是分页时注意最后一页和下一页之间的重叠,否则会丢数据或重复插入。我习惯在写完数据后做一次去重,用timestamp加symbol作为唯一约束,重复数据直接跳过。
本地存储的选型,我最常用的是SQLite。免费数据量对个人研究来说完全够用,SQLite单文件、零运维,一条命令就能建库。数据量特别大或需要并发读写时再考虑其他数据库。历史K线导出为Parquet归档也不错,分析和读取都很快,尤其是当你的数据量积累到几百万行之后,CSV的读取速度会明显拖后腿。
4.3 四类市场的接入代码细节
股票、期货、外汇、数字货币的REST接入代码框架大同小异,区别主要在参数和返回结构上。我把四个市场的请求参数各自列一下,你照着改就行。
股票注意复权参数:
params = {"symbol": "600000", "period": "daily", "adjust": "qfq"}期货注意合约代码:
params = {"symbol": "au8888", "period": "daily"} # 贵金属主力连续示意外汇注意货币对代码和报价模式:
params = {"pair": "EUR/USD", "mode": "latest"}数字货币注意交易对和K线周期:
params = {"symbol": "BTC/USDT", "interval": "1h", "limit": 500}返回结果的解析上,各家用JSON的居多,但嵌套层级不一样。我建议先打印一次原始返回值,看清楚结构再写解析代码,不要靠猜。打印原始数据这个动作看起来简单,实际上能省掉大量试错时间。我见过不少朋友对着接口文档猜字段层级,猜了三层还写不对,打印一下一目了然。
另外,务必把请求频率控制好。大部分免费接口的限制在每分钟几十次到几百次不等。我的经验是,按需拉数时每次调用之间至少间隔0.2秒,循环拉大量标的时加一个随机小延时,避免出现节奏性触发限流。限流之后往往要等一分钟甚至更久,反而拖慢整体进度。
5. 免费接口高频故障与排查实录
5.1 高频踩坑速查表
免费接口用久了,总会遇到各种问题。下面这张表是我整理的高频故障速查,出现症状可以直接对照:
| 症状 | 可能原因 | 排查方法 |
|---|---|---|
| 请求返回429 | 调用频率超限 | 加请求间隔、减少并发、换备用token |
| 请求超时 | 接口不稳定或网络问题 | 设置合理timeout并重试,连续失败则切备用源 |
| 数据中间缺一段 | 停牌、除权除息、临时休市 | 核对交易日历,做复权处理 |
| 历史数据拉不全 | 接口分页限制 | 按时间段分批拉取,合并后去重 |
| 时间错乱 | 时区或交易时段划分不一致 | 统一转UTC时间戳存储,展示时再转本地时区 |
| WebSocket静默掉线 | 心跳未处理或连接被断开 | 实现ping心跳和自动重连 |
| 字段名对不上 | 接口版本升级或不同数据源差异 | 接入层维护字段映射表,升级时改映射即可 |
排查的时候,我习惯遵循“先本地后远端”的顺序:先确认本地代码没有改坏、没有限流、没有存错数据,再怀疑接口返回异常。很多时候问题出在本地增量逻辑上,尤其是断点续传写得不严谨,会导致缺数。比如分页拉数据时,每页的参数没有正确接上,就会漏掉中间一段,表现就是数据在某个时间段突然缺失。
5.2 我实际踩过的三个数据坑
第一个坑是股票除权造成了回测收益虚高。有次我拿某只股票做回测,用不复权数据,发现某一天收益率突然出现一个巨大的跳变。一开始以为是策略信号,仔细查了才发现那一天是除权日,股价直接下跳,而我的策略代码按正常涨跌来计算,结果出现了明显异常的“收益”。后来统一改成前复权数据,回测结果才回归正常。这个坑让我明白,免费数据源的“原始数据”不等于“可用数据”,复权、停牌、除权标记这些处理,做分析前必须过一遍。
第二个坑是期货夜盘的K线拼接错乱。我一开始拿到的数据是按“自然日”切的,夜盘归前一天,日盘归当天。我写指标时直接按行号滚动计算日均,结果每天的开盘数据里混着前一天的夜盘尾盘,均线轨迹完全变形。后来我按交易所的交易时间段做重新切分,把夜盘和次日日盘拼成完整交易日,问题才解决。做期货的朋友,接入后第一件事就是确认接口的时间切分规则,不要假设它和你的预期一致。
第三个坑是外汇“实时报价”的延迟。有次我基于某个外汇免费API写了一个实时汇率看板,上线后发现价格和主流行情软件对不上。刚开始以为是数据源价差,后来做了延迟测试,发现API返回的价格实际是15分钟前的快照。看板做展示没问题,但拿它做短线信号就完全不靠谱。这次之后,我对外汇免费API都会先做延迟体检,周期行情用日线没问题,实时性要求高的场景则要用官方直连报价源。
给一个延迟体检的具体做法:每隔几秒轮询一次最新价,记录下来后和行情软件对照,连续测10分钟,就能看出数据延迟的大致区间。这个方法同样适用于数字货币和其他“宣称实时”的接口。测出来的延迟结果记到自己的数据源评估表里,后面选型时会非常有用。
最后几点经验
把四个市场的免费API都接完之后,我最大的感受是:免费接口不是“拿来就能用一辈子”的东西,它是有生命周期的。接口地址会变、字段会升级、频率限制会收紧,你可能今天跑得好好的代码,明天一觉醒来就报错了。
所以我一直建议,数据接入层一定要和业务代码解耦。把“从哪里取数”和“怎么用数”分开,数据源升级了只改接入层,策略代码不用跟着动。统一的字段映射、标准的存储格式、断点续传的增量更新,这三件事做好,即使某天换数据源,迁移成本也很低。
另外,任何免费数据源都要准备一个备用方案。我自己的习惯是主源加备源双跑一段时间,确认主源稳定后再切掉备源。文件存储、代码注释、接口文档里的重要字段说明,都养成随手记录的习惯,这些习惯比接口本身值钱。
既然你已经看到这里,说明大概率准备动手接数据了。我的建议是别一上来就铺四个市场,先选一个你最熟悉的市场,跑通最小闭环:拿到token、拉一次数据、存进本地、画出一张K线图。这一步完成后,再扩展到其他市场会轻松很多。数据链路稳定了,后面无论是做回测、写监控还是搭看板,都是水到渠成的事。