摘要: 外汇行情 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=EURUSDtype=forex,并出现 last_pricebid_priceask_pricetimestamp 等字段。

在这里插入图片描述

程序侧至少做三项校验:

  1. 返回对象中存在请求的 symbol;
  2. last_pricebid_priceask_price 能被解析为数值;
  3. 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=EURUSDtype=forexinterval=1hlimit=3。本次返回 3 条 K 线样本,字段包含 timeopenhighlowclosevolumequote_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、订阅确认和包含 EURUSDticker 业务消息。这个证据只覆盖本次连接和本次样本,不证明全天连续推送、断线自动恢复、延迟、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_OKKLINE_OKWEBSOCKET_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()
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐