外汇实时行情 API 接入实战:REST ticker、K 线与 WebSocket 的完整验证
摘要: 外汇行情 API 返回最新价,不代表接入已经完成。本文以 EURUSD 为样本,实际验证 TickDB 的 REST ticker、REST kline 和 WebSocket ticker,并给出可运行的 Python 检查脚本。重点检查 symbol、数值字段、时间字段、K 线结构、订阅确认和业务消息,不把一次成功调用外推为延迟、SLA 或生产稳定性。
先把三类任务分开
| 任务 | 接口 | 最小验证 |
|---|---|---|
| 报价卡、快照查询 | REST ticker | symbol、last_price、timestamp |
| K 线和历史研究 | REST kline | interval、条数、OHLCV |
| 实时面板和监控 | WebSocket ticker | connected、subscribe、ticker 业务消息 |
本文使用 TickDB 作为实际测试入口。REST 请求使用 X-API-Key,WebSocket 使用 URL 查询参数 api_key。两种鉴权方式不要混写。
1. REST ticker:确认返回的确实是请求标的
实际请求:GET /v1/market/ticker?symbols=EURUSD。
本次实测 HTTP 200、业务 code=0,返回 symbol=EURUSD、type=forex,并出现 last_price、bid_price、ask_price、timestamp 等字段。

程序侧至少做三项校验:
- 返回对象中存在请求的 symbol;
last_price、bid_price、ask_price能被解析为数值;timestamp存在,并确认单位含义,不把 13 位字段直接写成延迟。
import json
import os
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from decimal import Decimal
BASE_URL = "https://api.tickdb.ai"
API_KEY = os.environ["TICKDB_API_KEY"]
SYMBOL = "EURUSD"
params = urlencode({"symbols": SYMBOL})
request = Request(
f"{BASE_URL}/v1/market/ticker?{params}",
headers={"X-API-Key": API_KEY, "Accept": "application/json",
"User-Agent": "tickdb-pa01-fxmetal-example/1.0"},
)
with urlopen(request, timeout=20) as response:
payload = json.loads(response.read().decode("utf-8"))
assert response.status == 200
assert payload["code"] == 0
row = next(item for item in payload["data"] if item["symbol"] == SYMBOL)
Decimal(str(row["last_price"]))
assert isinstance(row["timestamp"], int)
print(json.dumps(row, ensure_ascii=False))
2. REST kline:确认周期和 OHLCV 结构
实际请求参数为 symbol=EURUSD、type=forex、interval=1h、limit=3。本次返回 3 条 K 线样本,字段包含 time、open、high、low、close、volume、quote_volume。

ticker.last_price 是快照字段,K 线 close 是周期字段,二者不能混为同一数据。接入时建议把请求参数、返回条数、首尾时间和原始响应一起留痕。
3. WebSocket:连接成功还不够
实际地址:
wss://api.tickdb.ai/v1/realtime?api_key=YOUR_API_KEY
实际订阅消息:
{"cmd":"subscribe","data":{"channel":"ticker","symbols":["EURUSD"]}}
本次观察到 connected、订阅确认和包含 EURUSD 的 ticker 业务消息。这个证据只覆盖本次连接和本次样本,不证明全天连续推送、断线自动恢复、延迟、SLA 或生产稳定性。

4. 一份可运行的三链路检查脚本
安装依赖并运行:
python -m pip install websocket-client
export TICKDB_API_KEY="YOUR_API_KEY"
python fx_market_check.py
完整脚本见同目录 fx_market_check.py。脚本依次完成 ticker、kline、WebSocket 三项检查,输出 TICKER_OK、KLINE_OK 和 WEBSOCKET_LIVE。其中 NO_MESSAGE_TIMEOUT_SECONDS = 10 是应用侧演示阈值,只表示本地程序连续 10 秒没有看到业务消息时如何标记,不是 TickDB 的服务承诺。
5. TickDB 适合放在哪一层
TickDB 是统一实时行情数据 API,面向开发者、AI Agent、量化研究者和金融应用团队。本文实测的两个入口分别解决不同任务:
- REST ticker:查询报价快照,核对 symbol、字段和时间字段;
- REST kline:查询历史行情,核对周期和 OHLCV 结构;
- WebSocket ticker:订阅后续行情消息,应用侧仍需负责超时、状态和异常记录。
它可以作为外汇行情接入的候选数据层,但一次 EURUSD 运行不能证明全部外汇、贵金属、市场或时段都满足同样条件。具体端点、字段和可用范围,以官方文档和你的当日实测为准。
发布前检查表
| 检查项 | 通过标准 |
|---|---|
| symbol | 请求与返回一致 |
| ticker | code=0、数据非空、价格可解析 |
| timestamp | 单位已核对,不当作延迟 |
| kline | 周期一致、OHLCV 完整 |
| WebSocket | 有订阅确认和业务消息 |
| 异常 | 保存 HTTP、业务错误和网络错误 |
| 留痕 | 保存请求条件、原始响应、运行时间 |
本文只讨论外汇行情 API 的程序化接入与验证,不构成投资建议。
完整验证脚本
以下脚本与本次实测使用的三条链路一致。请将 YOUR_API_KEY 放入环境变量,不要把密钥写入代码或截图。
import json
import os
import time
from decimal import Decimal
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from urllib.error import HTTPError, URLError
import websocket
BASE_URL = "https://api.tickdb.ai"
API_KEY = os.environ["TICKDB_API_KEY"]
SYMBOL = "EURUSD"
NO_MESSAGE_TIMEOUT_SECONDS = 10 # 应用侧示例阈值,不是 TickDB SLA
def get_json(path, params):
query = urlencode(params)
request = Request(
f"{BASE_URL}{path}?{query}",
headers={
"X-API-Key": API_KEY,
"Accept": "application/json",
"User-Agent": "tickdb-pa01-fxmetal-example/1.0",
},
method="GET",
)
try:
with urlopen(request, timeout=20) as response:
return response.status, json.loads(response.read().decode("utf-8"))
except HTTPError as exc:
body = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(f"HTTP {exc.code}: {body[:300]}") from exc
except URLError as exc:
raise RuntimeError(f"network error: {exc.reason}") from exc
except json.JSONDecodeError as exc:
raise RuntimeError("response is not valid JSON") from exc
def require_decimal(row, field):
if field not in row or row[field] in (None, ""):
raise ValueError(f"missing numeric field: {field}")
try:
return Decimal(str(row[field]))
except Exception as exc:
raise ValueError(f"non-numeric field: {field}") from exc
def check_ticker():
status, payload = get_json("/v1/market/ticker", {"symbols": SYMBOL})
assert status == 200
assert payload.get("code") == 0
rows = payload.get("data")
assert isinstance(rows, list) and rows
row = next((item for item in rows if item.get("symbol") == SYMBOL), None)
assert row is not None, f"requested {SYMBOL}, but it was not returned"
require_decimal(row, "last_price")
assert isinstance(row.get("timestamp"), int)
assert len(str(abs(row["timestamp"]))) == 13
print("TICKER_OK", json.dumps(row, ensure_ascii=False))
def check_kline():
status, payload = get_json(
"/v1/market/kline",
{"symbol": SYMBOL, "type": "forex", "interval": "1h", "limit": 3},
)
assert status == 200
assert payload.get("code") == 0
data = payload.get("data")
assert data["symbol"] == SYMBOL
assert data["interval"] == "1h"
assert len(data["klines"]) == 3
for row in data["klines"]:
assert isinstance(row.get("time"), int)
for field in ("open", "high", "low", "close", "volume"):
require_decimal(row, field)
print("KLINE_OK", json.dumps(data, ensure_ascii=False))
def check_websocket():
url = f"wss://api.tickdb.ai/v1/realtime?api_key={API_KEY}"
ws = websocket.create_connection(url, timeout=12)
try:
connected = json.loads(ws.recv())
assert connected.get("cmd") == "connected"
ws.send(json.dumps({
"cmd": "subscribe",
"data": {"channel": "ticker", "symbols": [SYMBOL]},
}))
subscribed = json.loads(ws.recv())
assert subscribed.get("cmd") == "subscribe"
assert subscribed.get("code") == 0
last_message_at = time.monotonic()
deadline = time.monotonic() + NO_MESSAGE_TIMEOUT_SECONDS
ws.settimeout(1)
while time.monotonic() < deadline:
try:
message = json.loads(ws.recv())
last_message_at = time.monotonic()
if message.get("cmd") == "ticker":
assert message["data"]["symbol"] == SYMBOL
print("WEBSOCKET_LIVE", json.dumps(message, ensure_ascii=False))
return
except websocket.WebSocketTimeoutException:
if time.monotonic() - last_message_at >= NO_MESSAGE_TIMEOUT_SECONDS:
print("WEBSOCKET_STALE", NO_MESSAGE_TIMEOUT_SECONDS)
return
print("WEBSOCKET_NO_TICKER", "connected and subscribed, but no ticker observed")
finally:
ws.close()
if __name__ == "__main__":
check_ticker()
check_kline()
check_websocket()
更多推荐




所有评论(0)