做A股量化的人大概都经历过这种尴尬:策略在A股跑得好好的,想加上港股和美股做一点全球配置,结果发现行情数据这块先卡住了。国内免费接口对A股支持很完善,但一碰到港股、美股,要么返回字段对不上,要么接口根本覆盖不到;用国外聚合数据服务又得注册、配Key、研究限额,再加上两个市场的交易时间完全错开,数据到底什么时候算"新鲜"都是个问题。上个月我刚好要把手里的策略从A股扩展到港美股,顺手把这条链路完整趟了一遍——从数据源选型、统一封装、交易时段判断到历史K线,最终写了一套同时拉取港股和美股行情的接口。这篇文章就把整个思路和过程中的坑整理出来,给同样在研究股市api接口如何同时获取港股和美股行情的朋友一份可以直接上手的方案。
1. 港股和美股的数据差异:先搞清楚你到底要兼容什么
写代码之前,先把两个市场的"业务规则"梳理清楚,这一节花的半小时能帮你后面少调两天的bug。很多人一上来就找API,结果拿到数据之后发现时间对不上、价格单位不对、盘口字段含义不同,再回头补课就晚了。
1.1 交易时段与时区:两个市场的"时差"决定了数据新鲜度
港股和美股最关键的区别是交易时间。港股是北京时间周一至周五早市9:30到12:00、午市13:00到16:00,中间有一个半小时午休;美股则是美东时间9:30到16:00,换算成北京时间要分夏令时和冬令时两种情况:夏令时是21:30到次日04:00,冬令时是22:30到次日05:00。
这个差异直接影响你写数据拉取任务的方式。如果任务挂在服务器上按"每天固定时间"运行,你就得处理三个不同市场的开关时间。更麻烦的是节假日体系完全不一样:A股休市的时候港股和美股可能正常交易,美股感恩节期间还有提前收盘的特殊安排,港股和A股同时休市的时间较多但也不完全同步。所以在设计接口时,不能默认"现在是交易时间",必须给每个市场单独维护一套交易日历和状态判断。
1.2 代码体系、货币和涨跌规则:数据格式上就分叉了
行情接口要同时兼容两个市场,代码规则是第一道坎。港股通常用5位数字代码,比如腾讯控股是00700;美股用字母代码,苹果是AAPL,带特殊字符的代码如伯克希尔B类股是BRK.B,在URL里这个点号还需要转义或者换成短横线。同一家公司在不同市场还有不同代码,比如阿里巴巴在港股是09988,在美股是BABA。
货币和报价单位也不一样,港股以港币计价,美股以美元计价。如果你的下游需要汇总两个市场的持仓市值,就必须额外引入汇率数据,否则算出来的组合净值在汇率波动大的时候会和真实值差很多。涨跌规则同样不同:港股没有传统意义的涨跌停板,只有针对恒指成分股的市场波动调节机制;美股主要靠个股熔断机制限制极端下跌,1美元以上的股票最小变动价位是0.01美元。这些规则导致如果你用A股的"超过昨收价±10%就告警"逻辑去覆盖港美股,会出现大量误报或漏报。
所以说,同时获取港股和美股行情,本质不是"发起两个HTTP请求再把结果拼在一起",而是要在数据层之上做一次语义统一。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 行情数据源选型:免费接口、数据服务、券商API的取舍
这个标题下的问题,第一个要面对的就是"用谁家的数据"。市面上靠谱的方案有三类:免费网页接口、海外聚合数据服务、券商开放平台。我逐个试过之后,说下它们各自的定位和适用场景。
2.1 腾讯和新浪的免费接口方案
腾讯财经和新浪财经都有公开的行情接口,这应该是个人开发者上手最快的方式。腾讯的实时行情接口https://qt.gtimg.cn/q=...支持一次批量请求多个股票,港股和美股都覆盖,新浪的https://hq.sinajs.cn/list=...同样可以。响应速度比A股一些第三方接口还快,个人做原型验证或者自用工具完全足够。
但免费接口有几个前提要认清楚:第一,接口协议不是正式对外发布的SDK,没有技术支持,字段索引可能随页面改版变化;第二,有隐藏的请求频率限制,并发拉多了会被网关短暂拒绝;第三,返回的字段语义需要自己摸索,不同市场的字段数量会错位。我的定位是:免费接口适合做"实时行情主数据源",但不适合做需要精确回放历史数据的场景。
2.2 海外聚合数据服务的定位
海外服务像Twelve Data、Alpha Vantage、Polygon.io,对美股的覆盖很全面,港股也能拿到日线和分钟线。这类服务的好处是有规范文档、有API Key配额、历史数据完整,缺点是免费额度有限制,比如Twelve Data免费版通常限制每分钟8次请求,Alpha Vantage是每天25次到500次不等,做实时盯盘不够用,做每日收盘后的批量同步倒是很合适。
我目前实践下来的组合是:盘中实时报价走腾讯免费接口,收盘后历史K线和数据校准走Twelve Data或者Polygon.io,两边配合可以做到零成本覆盖两个市场。
2.3 券商开放平台的情况
如果你本来就开了支持港美股的券商账户,富途OpenAPI、盈透证券的IB API这类渠道是最靠谱的选择。它们的好处是数据经过券商合规校验,字段齐全,还能直接关联交易下单;门槛也明显,通常要求入金达到一定金额才能申请行情权限,接口接入也需要审批。对纯做研究不交易的个人来说,为拿行情去开个户然后再入金,成本偏高,我更倾向于在做策略实盘阶段再接入。
2.4 方案对比与选型结论
| 数据源 | 覆盖范围 | 实时性 | 费用 | 接入门槛 | 适合场景 |
|---|---|---|---|---|---|
| 腾讯/新浪免费接口 | 港美股、A股 | 准实时,延迟秒级 | 免费 | 无,直接HTTP调用 | 个人工具、盘中实时监控 |
| Twelve Data等聚合服务 | 全球市场 | 分钟级到日级 | 免费额度有限,付费按量 | 注册拿API Key | 历史K线、数据校准 |
| 券商OpenAPI | 港美股为主 | 实时 | 免费但有入金门槛 | 开户、申请权限 | 实盘策略、下单联动 |
如果你只是想把"同时获取港股和美股行情"这问题快速跑通,建议第一步直接选腾讯接口,后面需要历史数据再补一个海外数据服务,不要上来就迷信付费方案。
3. 统一行情封装:一次请求同时拿到两个市场的数据
数据源确定之后,核心就是把接口调用封装成一套统一逻辑。这一节我以腾讯接口为例,讲清楚请求规则、返回结构,以及怎么把两个市场的字段拉齐。
3.1 腾讯接口的请求规则与返回结构
腾讯实时行情接口的地址非常短:https://qt.gtimg.cn/q=代码1,代码2。关键点在于代码前缀:港股加上hk前缀再接5位数字,比如腾讯是hk00700;美股加上us前缀再接字母代码,比如苹果是usAAPL。一次请求可以传多个代码,用英文逗号分隔,这正好满足"同时获取"的需求。
返回内容的编码是GBK而不是UTF-8,这是第一个容易翻车的地方。请求时直接把响应内容按gbk解码,再用split("~")切分字段,就能拿到类似下面的数据:
code复制v_hk00700="200~腾讯控股~00700~380.200~375.000~376.000~1234567~..."
字段里比较稳定的位置:索引1是股票名称,索引2是代码,索引3是当前价,索引4是昨收盘价,索引5是今开盘价,索引30附近是行情时间,后面的字段包含最高价、最低价、成交量、成交额、换手率、市盈率等。需要注意,这个字段位置不是官方文档承诺的,实际开发中我踩到过字段漂移的情况,所以写代码时不要把所有索引写死,至少要留一个"字段解析失败时打印原文做人工核对"的兜底。
3.2 统一Schema设计:把两个市场的含义拉齐
港股和美股的字段语义存在细微差别,直接透传给下游会让策略代码写得很痛苦。我在封装层做了一次映射,把两个市场统一成下面的数据结构:
json复制{
"symbol": "00700",
"market": "HK",
"name": "腾讯控股",
"currency": "HKD",
"price": 380.2,
"prev_close": 375.0,
"open": 376.0,
"high": 382.0,
"low": 378.0,
"volume": 12345678,
"timestamp": "2025-01-15 15:59:00",
"market_status": "OPEN"
}
market字段用来标记数据来自哪个市场,currency给下游做汇率换算留好扩展点,market_status则直接输出你判断得到的市场状态:OPEN、LUNCH_BREAK、CLOSED、PRE_MARKET、POST_MARKET。这样策略层、展示层都不需要关心"现在是港股午休还是美股盘前",只需要根据状态决定要不要把数据当作实时行情使用。
3.3 可直接运行的示例代码
下面这个类可以一次请求同时获取港股和美股的多只股票行情,并且把原始字段映射成统一结构。代码里我故意保留了两个市场字段位置不一致的处理逻辑,因为港股和美股返回的字段数量确实有差异。
python复制import requests
class QuoteClient:
QT_URL = "https://qt.gtimg.cn/q="
@staticmethod
def _build_market_symbol(symbol: str, market: str) -> str:
if market == "HK":
return "hk" + symbol
if market == "US":
return "us" + symbol
raise ValueError(f"Unsupported market: {market}")
def fetch_quotes(self, symbols):
# symbols: [("00700", "HK"), ("AAPL", "US")]
req_codes = [self._build_market_symbol(s, m) for s, m in symbols]
url = self.QT_URL + ",".join(req_codes)
headers = {"Referer": "https://gu.qq.com/"}
resp = requests.get(url, headers=headers, timeout=10)
resp.encoding = "gbk"
result = {}
for line in resp.text.strip().split(";"):
if "=" not in line:
continue
payload = line.split("=", 1)[1].strip().strip('"')
fields = payload.split("~")
if len(fields) < 40:
continue
code = fields[2]
market = "HK" if code.lower().startswith("hk") else "US"
result[code] = {
"symbol": fields[2],
"market": market,
"name": fields[1],
"price": float(fields[3]),
"prev_close": float(fields[4]),
"open": float(fields[5]),
"high": float(fields[33]),
"low": float(fields[34]),
"volume": int(fields[36]),
"timestamp": fields[30],
}
return result
用的时候传[("00700", "HK"), ("AAPL", "US")],就会返回一个以请求代码为key的字典,里面两个市场的应对齐字段都已经统一好了。实测来看,批量请求10只以内的股票,响应时间基本在200毫秒上下。
4. 交易状态与陈旧数据:拉到了不等于能用
实时行情接口最让人迷惑的一点是:休市期间你去请求,它也会返回最后一笔成交数据,看起来"一切正常",但其实这数据可能是几小时甚至十几小时前的。如果不判断市场状态就喂给策略,回测和实盘都会出偏差。这节专门讲怎么处理市场状态和真假实时的问题。
4.1 市场状态判断逻辑
判断市场状态,最直接的方式是把当前时间转成对应市场的本地时间,再和交易时段做比较。我建议用Python标准库的zoneinfo模块,而不是手写固定offset,因为夏令时会自动切换,手写很容易错。
python复制from datetime import datetime, time
from zoneinfo import ZoneInfo
def get_hk_market_status(dt=None):
hk_tz = ZoneInfo("Asia/Hong_Kong")
now = (dt or datetime.now()).astimezone(hk_tz)
weekday = now.weekday()
if weekday >= 5:
return "CLOSED"
morning = time(9, 30) <= now.time() <= time(12, 0)
afternoon = time(13, 0) <= now.time() <= time(16, 0)
if morning or afternoon:
return "OPEN"
if time(12, 0) < now.time() < time(13, 0):
return "LUNCH_BREAK"
return "CLOSED"
def get_us_market_status(dt=None):
us_tz = ZoneInfo("America/New_York")
now = (dt or datetime.now()).astimezone(us_tz)
weekday = now.weekday()
if weekday >= 5:
return "CLOSED"
if time(9, 30) <= now.time() <= time(16, 0):
return "OPEN"
if time(4, 0) <= now.time() < time(9, 30):
return "PRE_MARKET"
if time(16, 0) < now.time() <= time(20, 0):
return "POST_MARKET"
return "CLOSED"
这里还有一个很多人会漏的点:即便在交易时段内,免费接口的数据也有一定延迟,不能完全当作逐笔行情。腾讯免费接口的推送大约有几秒延迟,做盘中分钟级策略没问题,做高频交易不现实。所以在market_status之外,我还习惯判断接口返回的timestamp字段和本地时间差了多少秒,超过一个阈值就给数据打上STALE标记。
4.2 陈旧数据识别与缓存策略
判断陈旧数据的逻辑其实很简单:把接口返回的行情时间解析成datetime,和当前时间做差。A股和港股的正常延迟应该在几秒内,美股因为跨洋链路会稍高一些,但一般也不会超过30秒。如果差值超过5分钟,基本可以认定接口或网络出问题了。
另一个和"陈旧"紧密相关的问题是缓存。因为免费接口有隐形频率限制,实时行情接口不适合每次都远程调用。我在封装层加了一个TTL缓存,3秒内对同一只股票的重复请求直接返回内存里的旧数据,这样无论下游调用多频繁,实际打到腾讯的请求数量都保持在一个安全水平。实测用300个symbol的池子,3秒TTL,每秒大概只需要十几个远程请求,完全不会触发限流。
5. 踩坑实录:编码、代码格式、时区和限流的完整排查链路
这部分记录我在实际开发中遇到的四个典型问题,每个都说一下"现象到底是什么、按什么思路排查、最后怎么解决",希望能帮你省掉几个小时的试错时间。
5.1 GBK编码导致的乱码
第一次请求腾讯接口时,响应里中文全部乱码,我以为是接口挂了,直接在浏览器里打开URL却显示正常。后来打印了响应头的charset才发现是GBK编码。解决方式很简单,把resp.encoding手动设置成gbk即可。
排查这个问题的经验是:反向接口返回的编码经常和页面编码一致,拿到奇怪的字符先检查编码,不要急着怀疑数据源本身出了问题。新浪接口有的返回是GBK,有的返回是GB2312,统一用resp.apparent_encoding做兜底也行,但手动指定最稳。
5.2 Symbol格式错误,请求返回空
我之前写过一套A股接口,习惯直接传6位股票代码。扩展港美股时,一开始传了0700而不是腾讯的00700,结果接口返回的名称成了乱七八糟的东西,价格字段也不对。另一个案例是美股代码里带点号,BRK.B在URL中如果不处理,服务器会把它当作路径分隔符的一部分,请求直接404。
排查过程:先用浏览器打开https://qt.gtimg.cn/q=hk00700确认正常,再打开hk0700确认异常,缩小范围后发现是代码位数问题。美股的带点号代码,我最后统一在封装层把.替换成-,实测usBRK-B可以正常返回。所以做代码标准化时,港股要补足到5位,美股要处理特殊字符,少一步都拿不到数据。
5.3 日K线时间错位
用历史K线接口拉美股日线时发现一个诡异现象:某天的K线数据总是比预期晚了一个自然日。后来排查到根因是K线接口返回的时间戳和美股交易日期用的是纽约时区,而我在本地存储时直接转成北京时间,导致日期边界判断错位。
比如美东时间2025年1月15日16:00收盘,对应北京时间已经是1月16日05:00。如果按北京自然日划分K线,1月16日可能只有一根收盘K线,1月15日又会缺数据,整个日线序列都是乱的。解决方案是在入库时统一用America/New_York的日期作为美股K线的trade_date,而不是用本地时区的自然日。这个坑非常隐蔽,如果不是逐根核对日期和价格,很难发现。
5.4 请求过频被限流
我一开始写了个监控脚本,每500毫秒循环拉取全部持仓的行情,运行十几分钟后开始收到空的响应体,再往后请求直接超时。一开始怀疑是网络问题,换了网络环境还是一样,最后确认是被网关限流了。
排查链路很简单:看响应状态码有没有异常,看返回体是否为空,再看请求频率是否超过了之前观察到的阈值。解决方式是三层组合:第一,所有请求统一走3秒TTL缓存;第二,请求间隔最低1秒,不在同一秒内发送爆发式请求;第三,增加指数退避重试,比如连续失败后等待2秒、4秒、8秒。这样调整后,我连续跑了近一个月,没有再触发限流。
6. 进阶扩展:历史K线、增量更新与多源降级
实时行情跑通只是第一步,真正做量化策略还绕不开历史K线、增量同步、以及数据源故障时的兜底。这一节把这三个进阶点展开说明。
6.1 历史K线接口的使用
腾讯除了实时行情,还有一个K线接口可以用:https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param=代码,周期,,,数量,复权。比如请求腾讯控股的前复权日K:
code复制https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param=hk00700,day,,,320,qfq
周期参数支持day、week、month,qfq表示前复权,hfq表示后复权。返回JSON里每根K线是一个数组,但要注意元素顺序是[日期, 开盘, 收盘, 最高, 最低, 成交量],中间收盘价和最高最低的顺序和常见的OHLC格式不一样,我第一次解析时当成[open, high, low, close]处理,算出的涨跌幅全反了。老规矩,拿到数据先打印一根K线人工确认字段顺序,再写解析逻辑。
6.2 增量更新与任务调度
历史数据不能每次都全量拉取,否则接口压力和本地存储都是浪费。我的做法是:每天收盘后跑一次全量同步,把最近320根日K拉到本地;盘中则只做增量更新,每5分钟请求一次实时行情,发现有新成交就更新内存里的最新价。这样既不会频繁触发限流,又能保证策略用到的数据足够新。
增量更新的一个关键处理是"首根K线拼接":如果本地5分钟K线还没有今天的数据,就从日K或者实时接口取出今日开盘价作为首根K线的起点;如果已经有数据就直接追加最后一笔成交,不要每次重建整根K线。这个逻辑不处理好,跨天跨周的数据会出现跳空或重复。
6.3 多数据源降级策略
免费接口无论如何都不可能有100%的可用性保证,所以我在架构里加了一个降级开关:实时行情主源是腾讯,当主源连续失败3次后,自动切换到新浪接口;如果两个免费源都不可用,则使用海外数据服务补齐最新的分钟线。这个降级不用做得很复杂,只要在封装层维护一个get_quote()函数,内部按优先级依次调用不同实现,某个实现抛异常就换下一个。
降级策略里最容易忽略的是"降级后数据质量提示":新浪的字段语义和腾讯有细微差别,直接混用可能导致下游算出错误信号。我习惯在返回的统一数据结构里加一个source字段,标明数据来自哪个数据源,这样使用方可以自己决定是否信任当前数据。可靠性和准确性的平衡,在个人项目中靠人工判断就够用了。
最后说一点我自己的体会。这套东西从拆解需求到跑通,前后用了两个周末,回头看单个模块都不难,难的是把两个市场的交易规则、代码格式、字段语义、时区边界这些细节全部想清楚。如果你也准备动手,建议先把港股和美股各自的交易时间、代码规则、K线字段顺序整理成一份笔记,再开始写代码,这样会比边调边查高效很多。另外一个小技巧:我日常会把腾讯接口的批量请求用作持仓页的定时刷新,3秒一次,5分钟K线的刷新单独开任务,实测一个月没有被限流,关键就是别在同一秒里发爆发式请求,留一点缓冲给网关。
