接美股行情 API 第一课:快照是快照,推送是推送,非交易时段是另一回事

摘要:给程序或 Agent 接美股行情,第一个坑往往不是 API Key 配错,而是没分清 REST 快照、WebSocket 推送和交易时段这三个边界。本文以一次盘后告警失效的踩坑经历切入,拆解三种接入方式的适用场景、关键字段差异和常见排错路径,附 REST ticker 最小验证代码和一张可收藏的排错清单。关键词:美股行情 API、REST ticker、WebSocket 推送、交易时段、盘前盘后、工程验证。


一、一个盘后告警失效的踩坑经历

同事在周四晚上十点发来消息:告警脚本跑了一整天没问题,偏偏在收盘后触发了一次误报------last_price 还是下午四点的值,但脚本当成当前价发了告警。

排查过程很短:他用的是 REST ticker 快照,定时五分钟轮询一次。交易时段内一切正常,但收盘后 GET /v1/market/ticker 返回的仍是当日最后一笔成交价。脚本没有检查 timestamp,也没有判断当前是否在交易时段,直接把旧数据当成了实时信号。

修起来也不复杂------加了时间戳新鲜度检查和交易时段判断。但这个问题本身暴露了一个更深层的认知缺口:能拿到数据,和能拿到"对当前决策有效的数据",不是一回事。

美股行情的三种数据获取方式------REST 快照、WebSocket 推送、历史 K 线------对应的是三种完全不同的工程边界。换一个场景,边界就变了,代码也得跟着变。


二、三个边界:一张表说清楚

别急着选接口,先看清你的需求和接口之间的边界在哪。

你的需求 接入方式 关键字段 最常见三个坑
页面加载时显示一次最新价 REST ticker last_pricetimestamp ① 没检查 timestamp 新鲜度,收盘后拿旧数据当实时价;② 盘前盘后 last_price 可能是上一交易日收盘价,此时应关注 pre_market_quotepost_market_quote 等嵌套字段;③ symbol 格式写错(AAPL 而非 AAPL.US
盘中持续监控价格变化 WebSocket 推送 推送中的 last_pricetimestamp ① 连接断开后恢复时订阅丢失;② 心跳只发 ping 不检查 pong;③ 推送 symbol 格式可能与 REST 不同
回测或趋势分析 REST kline closetimevolume ① 用 ticker 的 volume_24h 替代 kline 的 volume;② timetimestamp 字段名混淆;③ 周期参数写错(不同接口对 1w 的定义可能不同)

核心区别一句话

  • REST ticker 是一次查询,返回请求时刻的快照------你不知道这个价格是 1 秒前还是 20 分钟前的。
  • WebSocket 推送是持续连接,服务端在价格变化时主动推送------你能知道价格变动的时间点
  • 交易时段定义了快照是否还在更新 。美股盘后(16:00-20:00 ET)和盘前(04:00-09:30 ET)虽然有交易,但流动性稀疏,last_price 的参考价值不同于盘中。此时 ticker 响应中若有 pre_market_quotepost_market_quote 嵌套字段,应优先用这些字段获取盘前盘后的报价信息,而不是只盯着主字段的 last_price

三、REST ticker 最小验证

下面这段代码只做一件事:验证你能拿到 AAPL.US 的最新快照,并把时间戳转换成可读时间,便于判断数据新鲜度。

参数说明:/v1/market/ticker 接受 symbols(复数),多个品种用逗号分隔,如 symbols=AAPL.US,TSLA.US

环境准备

bash 复制代码
pip install requests python-dotenv

.env 文件(不要提交到版本控制):

复制代码
TICKDB_API_KEY=你的API Key

代码

python 复制代码
import os
from datetime import datetime, timezone
import requests
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.getenv("TICKDB_API_KEY")
BASE_URL = "https://api.tickdb.ai"

def get_ticker(symbols: str):
    """
    获取品种最新行情快照。

    参数:
        symbols: 逗号分隔的品种代码,如 "AAPL.US,TSLA.US"

    返回:
        dict: API 原始响应

    注意:
        这是快照,不是实时推送。timestamp 表示快照生成时间,
        不代表当前仍在交易。调用方需自行判断数据新鲜度。
    """
    resp = requests.get(
        f"{BASE_URL}/v1/market/ticker",
        headers={"X-API-Key": API_KEY},
        params={"symbols": symbols},
        timeout=10
    )
    resp.raise_for_status()
    data = resp.json()

    if data.get("code") != 0:
        raise Exception(f"API 错误: code={data.get('code')}, message={data.get('message')}")

    return data

def check_timestamp_freshness(timestamp_ms: int, max_age_seconds: int = 60):
    """
    检查时间戳新鲜度。

    参数:
        timestamp_ms: 毫秒 UTC 时间戳
        max_age_seconds: 最大允许数据年龄(秒),即快照生成至今的时间上限

    返回:
        tuple: (是否新鲜, 时间差秒数, 可读UTC时间字符串)
    """
    now_ms = int(datetime.now(timezone.utc).timestamp() * 1000)
    age_seconds = (now_ms - timestamp_ms) / 1000
    ts_readable = datetime.fromtimestamp(timestamp_ms / 1000, tz=timezone.utc)
    return age_seconds <= max_age_seconds, age_seconds, str(ts_readable)

if __name__ == "__main__":
    try:
        result = get_ticker("AAPL.US")
        for item in result.get("data", []):
            symbol = item["symbol"]
            price = item["last_price"]
            ts = item["timestamp"]
            fresh, age, ts_str = check_timestamp_freshness(ts)

            status = "✓ 新鲜" if fresh else f"✗ 已过期 ({age:.0f} 秒前)"
            print(f"{symbol}: price={price}, timestamp={ts_str} [{status}]")

            if not fresh:
                print(f"  ⚠ 数据年龄已超过新鲜度阈值,如需持续更新请评估 WebSocket 推送")
    except Exception as e:
        print(f"调用失败: {e}")

这段代码验证了什么

运行后你会看到类似输出:

复制代码
AAPL.US: price=308.33, timestamp=2026-05-28 20:00:05+00:00 [✗ 已过期 (452 秒前)]
  ⚠ 数据年龄已超过新鲜度阈值,如需持续更新请评估 WebSocket 推送

这里 timestamp 显示的是 UTC 时间 20:00:05,转换到美东夏令时(ET)是下午 16:00:05,正是当日美股收盘附近。如果当前时间是晚上十点,那这个 last_price 就是收盘价------它可以用于展示上一交易日的收盘参考,但不能用于触发盘中交易决策。

至此,你已经完成了接入美股行情的第一道验证:不是"能不能拿到数据",而是"拿到的数据对当前决策有没有用"。


四、排错清单:快照查到了,然后呢?

上面代码跑通后,别急着往生产环境推。按下面四步逐项核对:

检查项 怎么验证 不通过时意味着什么
symbol 格式 确认使用了含后缀格式,如 AAPL.US 而非 AAPL;多个品种用逗号分隔,参数名为 symbols(复数) 短格式可能返回错误,或与其他市场同号品种混淆
timestamp 新鲜度 对比返回的 timestamp(毫秒 UTC)和 datetime.now(UTC),计算数据年龄 年龄过大说明拿到的可能是收盘快照,不能当实时价用
交易时段判断 确认 timestamp 转换到美东时间后处于哪个时段(盘前/盘中/盘后/休市) 盘后拿到的 last_price 可能是收盘价或稀疏成交价;此时应检查 ticker 中是否有 post_market_quote 等盘后字段
字段语义 last_price 是快照时刻最新成交价,volume_24h 是 24 小时累计成交量 不要把 volume_24h 当单日成交量或 K 线周期成交量

排错优先级:先查 symbol 格式(一票否决),再查 timestamp 新鲜度(决定数据能否用),最后查字段语义(决定用得对不对)。


五、什么时候该从 REST 切到 WebSocket

REST ticker 适合的场景有边界。一旦越过这些边界,就该评估 WebSocket:

REST ticker 适合 应该评估 WebSocket 的场景
页面加载时查一次价格 需要持续监控价格变化
定时轮询(间隔 ≥1 分钟) 需要更频繁的行情推送需求
非交易时段查询收盘参考价 盘中需要知道价格变动的时间点
首次接入的连通性验证 生产环境对断线恢复有要求

切到 WebSocket 后,需要额外处理心跳、断线重连、订阅恢复和推送格式兼容------这些是连接生命周期管理的问题,另文展开。


六、本文不能保证什么

  • 不能保证 REST 快照的 last_price 一定反映当前最新成交。数据新鲜度取决于请求时刻与最近一笔成交的时间差,非交易时段可能长达数小时。
  • 不能保证所有 timestamp 字段统一为毫秒。不同接口、不同品种下的时间戳单位可能存在差异,接入前需逐接口核验。
  • 不能保证美股盘前盘后交易的完整覆盖 。盘前盘后时段流动性稀疏,某些品种可能长时间无成交,快照返回的是当日最后一笔价。盘前盘后报价信息可能出现在 pre_market_quotepost_market_quote 嵌套字段中,具体以接口响应结构为准。
  • 不能从"能拿到数据"推出"可以做交易决策"。行情数据接入和交易执行是两个独立问题,本文仅涉及前者。

你用 REST ticker 做价格监控时,有没有被收盘后的旧数据坑过?你是怎么判断数据新鲜度的------检查 timestamp,还是额外拉取交易时段日历?评论区聊聊你的做法。

你接下来卡住的问题 值得继续看的方向
REST 数据不够实时 WebSocket 订阅、心跳和重连的工程实现
想用历史数据做回测 K 线接口的字段差异和复权概念
数据拿到了但不敢用 symbol 格式、时间戳单位、字段语义的系统梳理

参考文献:TickDB API 文档,可搜索查阅 https://docs.tickdb.ai

📡 数据由 TickDB.ai 提供

本文不构成任何投资建议。

工程笔记


标签:美股行情 API / REST ticker / 交易时段 / 盘前盘后 / 工程验证 / TickDB