A 股实时行情 API 排错指南:symbols 参数、字段路径与错误码

4 阅读7分钟

你对着文档写好了 requests.get,传了 symbol=600519.SH,返回 code: 0,数据也拿到了。但第二天查两只股票,同样的写法却拿到空列表——不报错,就是没数据。最终发现,是参数名少写了一个 s

一、三个最容易写错的点

查 A 股实时行情,调通第一个请求只需要一分钟。但从调通到写对业务代码,中间隔着三个几乎每个人都会踩的坑。

你写的是什么正确写法后果
?symbol=600519.SH?symbols=600519.SH不报错,但 ticker 接口不认单数参数,可能返回空数据或错误结果
data["last_price"]data["data"][0]["last_price"]取到 None,不报错。ticker 的行情数组在 data.data
float(data["last_price"])Decimal(str(data["last_price"]))浮点累加产生舍入误差,金融计算不可接受
拿到 timestamp 就按毫秒延迟用timestamp 是行情生成时间标签,精度不等于送达速度把字段精度当成性能承诺
time.sleep(3) 写死Retry-After 响应头,按服务端建议退避服务端让你等 30 秒,你 3 秒重试一次,连撞限流墙

这五个问题里,前两个是参数和路径错误,后三个是数值精度、时间语义和错误处理策略。它们有一个共同点:不会报错,但数据会静默丢失或偏差。

本文以 TickDB 的 REST API 作为示例数据后端——因为它在字段命名、错误码语义和 symbol 规范上保持了跨市场的一致性。下面这张表列出了多个行情源常见的差异,以及统一接口应该提供的解法。

多源接入时的典型困境统一接口应该提供什么
查行情的参数叫 symbol 还是 symbols 全靠猜ticker 用 symbols;kline 等单标的查询通常用 symbol,以具体端点文档为准
实时价字段有的叫 price,有的叫 last_price,有的叫 closeticker 用 last_price,kline 用 close,trades 用 price,语义彻底分开
限流了有的返回 429,有的返回自定义 code,有的直接断连客户端应兼容业务码 3001 与 HTTP 429,优先读取 Retry-After 并退避
A 股后缀是 .SH 还是 .SSE?港股有没有前导零?本文展开 A 股 .SH/.SZ/.BJ;其他市场格式以对应文档为准

二、一个最小可运行的 Python 示例

以下代码演示了用 requests 查询 A 股实时行情,并处理最常见的三类错误。北交所品种 920832.BJ 经 TickDB MCP 实测可正常返回。你只需要把 .env 里的 Key 换成你自己的。

环境准备

pip install requests python-dotenv

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

TICKDB_API_KEY=你的TickDB API Key
TICKDB_REST_URL=https://api.tickdb.ai

完整代码

# a_stock_ticker.py
# A 股实时行情查询——最小可运行示例
# Python 3.10+ 可直接运行,替换 .env 中的 Key 即可

import os
import time
import json
import requests
from decimal import Decimal, InvalidOperation
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.getenv("TICKDB_API_KEY")
if not API_KEY:
    raise RuntimeError("请设置环境变量 TICKDB_API_KEY")

BASE_URL = os.getenv("TICKDB_REST_URL", "https://api.tickdb.ai")
MAX_RETRIES = 3


def get_ticker(symbols: str, retry_count: int = 0):
    """
    查询 A 股实时行情快照。
    
    参数:
        symbols: 逗号分隔的品种代码,如 "600519.SH,300750.SZ"
        retry_count: 内部重试计数器,调用方不要传入
    
    返回:
        list[dict]: 每个品种的行情数据,价格字段使用 Decimal 类型
    """
    if retry_count > MAX_RETRIES:
        raise Exception(f"重试 {MAX_RETRIES} 次后仍失败,请稍后重试")

    headers = {"X-API-Key": API_KEY}
    params = {"symbols": symbols}

    try:
        resp = requests.get(
            f"{BASE_URL}/v1/market/ticker",
            headers=headers,
            params=params,
            timeout=10
        )
    except requests.exceptions.Timeout:
        raise Exception("请求超时,请检查网络连接")
    except requests.exceptions.ConnectionError:
        raise Exception("无法连接到行情服务,请检查网络")

    # 非 JSON 响应保护
    content_type = resp.headers.get("Content-Type", "")
    if "application/json" not in content_type:
        raise Exception(f"服务端返回非 JSON 响应: {resp.status_code}")

    try:
        data = resp.json()
    except json.JSONDecodeError:
        raise Exception("服务端返回内容无法解析为 JSON")

    # 限流:兼容业务码 3001 与 HTTP 429,优先读 Retry-After 并退避
    if resp.status_code == 429 or data.get("code") == 3001:
        retry_after = resp.headers.get("Retry-After", "5")
        try:
            wait = float(retry_after)
        except (ValueError, TypeError):
            wait = 5
        print(f"触发限流,等待 {wait} 秒后重试...")
        time.sleep(wait)
        return get_ticker(symbols, retry_count + 1)

    # 鉴权与权限错误——不重试,直接阻断
    if data.get("code") == 1001:
        raise Exception("API Key 无效 (1001),请检查 .env 中的 TICKDB_API_KEY")
    if data.get("code") == 1004:
        raise Exception("API Key 权限不足 (1004),请确认账户权限范围")

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

    # 解析行情数据:数据在 data["data"] 路径下
    ticker_list = data.get("data", [])
    if not ticker_list:
        raise Exception(
            "未获取到行情数据,请检查 symbol 是否正确或市场是否休市"
        )

    results = []
    for item in ticker_list:
        symbol = item.get("symbol", "未知")

        # 价格和成交量字段为字符串,缺失则跳过并说明
        raw_price = item.get("last_price")
        raw_vol = item.get("volume_24h")
        if raw_price is None:
            print(f"警告: {symbol} 缺少 last_price 字段,跳过")
            continue

        try:
            last_price = Decimal(str(raw_price))
            volume_24h = Decimal(str(raw_vol if raw_vol is not None else "0"))
        except (InvalidOperation, ValueError):
            print(f"警告: {symbol} 的价格或成交量字段无法解析为 Decimal,跳过")
            continue

        results.append({
            "symbol": symbol,
            "last_price": last_price,
            "volume_24h": volume_24h,
            # ticker 接口的 timestamp 为毫秒 UTC 时间戳
            # 字段精度不等于毫秒级新鲜度或数据送达速度
            "timestamp_ms": item.get("timestamp"),
        })

    return results


if __name__ == "__main__":
    # 查询三只 A 股:贵州茅台(沪)、宁德时代(深)、北交所示例
    # 920832.BJ 经 TickDB MCP 实测可正常返回
    try:
        result = get_ticker("600519.SH,300750.SZ,920832.BJ")
        for row in result:
            print(
                f"{row['symbol']}: 最新价={row['last_price']}, "
                f"24h成交量={row['volume_24h']}, "
                f"时间戳={row['timestamp_ms']}"
            )
    except Exception as e:
        print(f"查询失败: {e}")

代码背后最重要的四件事

  1. 参数是 symbols(复数):ticker 接口用 symbols。kline 等单标的查询通常用 symbol,以具体端点文档为准。写错不报错,但数据出不来。
  2. 数据路径是 data["data"]:API 返回的 JSON 结构是 {"code":0,"data":[...]},行情数组在 data 字段里。写成 data["last_price"] 会拿到 None,排查时很难发现。
  3. 金融数值用 Decimal,不是 floatlast_pricevolume_24h 是字符串。float("308.33") 看似没问题,但回测中成千上万次累加后,舍入误差会累积到不可忽略的量级。Decimal(str(...)) 从源头保护精度。缺失价格字段的品种会被跳过并记录警告,不会静默变成 0。
  4. 错误码分层处理,不要一视同仁:客户端应兼容业务码 3001 与 HTTP 429,优先读取 Retry-After 并按建议等待后重试。1001 Key 无效、1004 权限不足,这两种情况重试不会恢复,必须直接阻断并提示用户检查配置。

三、错误码速查

错误码含义正确动作
0成功读取 data 数组
3001 / HTTP 429请求频率超限优先读响应头 Retry-After,等待后重试
1001API Key 无效或过期检查 Key 是否正确,不要重试
1004API Key 权限不足确认账户权限,不要重试
data 数组symbol 不存在或市场休市核对 symbol 格式(A 股 .SH/.SZ/.BJ

四、本文不能说明什么

  • 不能说明毫秒级新鲜度timestamp 是 13 位毫秒 UTC,这是字段精度,不等于数据从产生到送达的延迟承诺。
  • 不能说明高频交易可用:本文示例是同步 HTTP 请求,仅用于查询演示,不代表低延迟或高频能力。
  • 不能替代交易系统:本文讨论行情数据接入的技术实现,不是交易执行系统,不构成任何投资建议。
  • 不能证明任何投资结论:文中所有价格和成交量均为示例数据,不构成买卖建议、荐股或收益暗示。