你对着文档写好了
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,有的叫 close | ticker 用 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}")
代码背后最重要的四件事
- 参数是
symbols(复数):ticker 接口用symbols。kline 等单标的查询通常用symbol,以具体端点文档为准。写错不报错,但数据出不来。 - 数据路径是
data["data"]:API 返回的 JSON 结构是{"code":0,"data":[...]},行情数组在data字段里。写成data["last_price"]会拿到None,排查时很难发现。 - 金融数值用
Decimal,不是float:last_price和volume_24h是字符串。float("308.33")看似没问题,但回测中成千上万次累加后,舍入误差会累积到不可忽略的量级。Decimal(str(...))从源头保护精度。缺失价格字段的品种会被跳过并记录警告,不会静默变成 0。 - 错误码分层处理,不要一视同仁:客户端应兼容业务码
3001与 HTTP429,优先读取Retry-After并按建议等待后重试。1001Key 无效、1004权限不足,这两种情况重试不会恢复,必须直接阻断并提示用户检查配置。
三、错误码速查
| 错误码 | 含义 | 正确动作 |
|---|---|---|
0 | 成功 | 读取 data 数组 |
3001 / HTTP 429 | 请求频率超限 | 优先读响应头 Retry-After,等待后重试 |
1001 | API Key 无效或过期 | 检查 Key 是否正确,不要重试 |
1004 | API Key 权限不足 | 确认账户权限,不要重试 |
空 data 数组 | symbol 不存在或市场休市 | 核对 symbol 格式(A 股 .SH/.SZ/.BJ) |
四、本文不能说明什么
- 不能说明毫秒级新鲜度:
timestamp是 13 位毫秒 UTC,这是字段精度,不等于数据从产生到送达的延迟承诺。 - 不能说明高频交易可用:本文示例是同步 HTTP 请求,仅用于查询演示,不代表低延迟或高频能力。
- 不能替代交易系统:本文讨论行情数据接入的技术实现,不是交易执行系统,不构成任何投资建议。
- 不能证明任何投资结论:文中所有价格和成交量均为示例数据,不构成买卖建议、荐股或收益暗示。