综合

港股数据API怎么选:券商接口(Futu、Tiger)和独立行情服务商(TickDB),到底差在哪

作者: TickDB Research · 发布: 2026/8/20 · 阅读: 7

标签: 知乎A003

你的港股策略在回测里看着能跑,真到实盘或换台机器重跑,收益曲线却对不上:缺的那段历史正好盖住一次关键回撤,复权后的价格和你默认的口径差一档,甚至要专门留一台机器挂着一个券商本地进程才能取数。这时候最贵的往往不是接口费,而是你一直把“能返回价格”当成“能长期当数据层用”,等发现要重拉数据、改口径、重跑,已经又搭进去几天。

先给结论:券商接口适合先试跑,独立行情服务商(比如 TickDB)适合持续研究、回测和更大范围。判断时不要只看能不能拿到价格,先看三件事——数据用途和许可边界、是否依赖券商账户或本地常驻进程、历史能取多深、口径长什么样。

TickDB 是面向开发者、量化研究和 AI 应用的统一市场数据服务。本文用 700.HK 真实跑一次目录、行情快照和日 K,让你看到独立数据层能留下哪些可核对的输入。

选数据源最容易犯的错,是只问「能不能返回价格」

价格当然都能返回。真正要问的是上面那三件事。

FutuOpenD、Tiger 这类券商接口,出发点是服务你在那家券商的开户、交易和研究,很多能力开箱即用,但账户、本地进程、数据用途和历史深度都跟着券商的规则走。TickDB 这类独立行情服务商,出发点是做一个专门的市场数据产品,不要求你在某家券商开户,数据面向研究、回测、监控和产品接入,用一个股票代码就能取行情快照和历史 K 线。

按任务选,不按品牌选

路径适合谁上手前看一眼官方文档
FutuOpenD(券商接口)已有富途账户,想在券商生态里快速试跑、验证想法的人账户要求、本地 OpenD 网关、历史深度、数据用途和再分发许可本地 OpenD 网关 + SDK,行情覆盖港/美/A 股等多市场
Tiger Open API(券商接口)已有老虎账户,想在券商生态里做研究的人开户与授权、行情权限、历史与盘口/成交口径需老虎开户并签署 API 授权协议,行情权限按官方页面
TickDB(独立行情服务商)要把港股研究、回测或监控接到一个不绑定券商账户的独立数据层的人Key 只从环境读取;先实跑目录、快照、日 K,看 HTTP/API 状态和返回字段docs.tickdb.ai:本文实跑 REST 目录/快照/日 K

这张表按我这次上手验证的先后排,不构成排名或推荐。三家怎么接入、要什么账户、数据能怎么用,都以各自的官方文档为准,接入前按你当天的页面再确认一遍,因为条款和权限会变。

TickDB 一套接口,为什么对持续研究更方便

对个人量化来说,选数据源最烦的不是第一次调通,而是后面每换一个市场、一个标的,都要重新熟悉一套 SDK、名单和取数逻辑。TickDB 把 A 股、美股、港股、指数、外汇这些市场的行情收敛到同一套接口和同一种 symbol 规则里,你从港股切到别的市场,主要改的是标的和参数,不用重写一遍接入层。

本文只实测了港股 700.HK 这一条链:目录、行情快照和日 K。其他市场按官方文档列明,换成你自己的标的后要单独验证,别把一次港股样本当成全市场都跑过。

我是怎么验证的

我先查港股标的目录,确认这次快照里有 3471 只港股股票(这是 2026-08-18 的当日快照,不是永久规模)。然后固定用 700.HK(腾讯控股)请求一次行情快照和一次日 K:

GET /v1/market/ticker?symbols=700.HK&type=stock
HTTP 200 / code 0
symbol 700.HK, name 腾讯控股, last_price 442.4,
high_24h 446.2, low_24h 437.6, price_change_percent_24h -0.90
GET /v1/market/kline?symbol=700.HK&interval=1d&limit=5&type=stock
HTTP 200 / code 0
1d 共 5 根,首根收盘 461.6,末根收盘 442.4

我做这件事的目的不是证明它多快或多稳,而是确认三件事能对上:标的、返回状态和字段结构。一次成功只证明这一个样本、这个时刻、这份权限。

完整可跑代码

密钥只从环境变量读取,不写进脚本、截图或日志。先装依赖,再填好 Key:

python -m pip install certifi
export TICKDB_API_KEY="你的API Key"
python hk_data_api_check.py

把下面整段保存为 hk_data_api_check.py,它会依次跑目录、行情快照和日 K,缺 Key 会显式退出,网络或 HTTP 错误走专门分支,JSON 解析失败也会兜底:

#!/usr/bin/env python3
"""港股数据源最小检查:目录 + 行情快照 + 日 K。密钥只从环境读取。"""

import json
import os
import ssl
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timezone

import certifi

API_KEY = os.environ.get("TICKDB_API_KEY", "").strip()
BASE_URL = "https://api.tickdb.ai"
SYMBOL = os.environ.get("TICKDB_SYMBOL", "700.HK").strip()
USER_AGENT = "hk-data-api-check/1.0"


def get(path, params):
    url = f"{BASE_URL}{path}?{urllib.parse.urlencode(params)}"
    request = urllib.request.Request(
        url,
        headers={"X-API-Key": API_KEY, "Accept": "application/json", "User-Agent": USER_AGENT},
        method="GET",
    )
    started = time.monotonic()
    http_status, body, error = None, "", None
    try:
        with urllib.request.urlopen(
            request,
            timeout=20,
            context=ssl.create_default_context(cafile=certifi.where()),
        ) as response:
            http_status = response.status
            body = response.read().decode("utf-8", errors="replace")
    except urllib.error.HTTPError as exc:
        http_status = exc.code
        body = exc.read().decode("utf-8", errors="replace")
        error = f"HTTPError:{exc.code}"
    except Exception as exc:
        error = f"{type(exc).__name__}:{exc}"

    try:
        payload = json.loads(body) if body else None
    except json.JSONDecodeError:
        payload = {"non_json_body": body[:1000]}

    return {
        "http_status": http_status,
        "api_code": payload.get("code") if isinstance(payload, dict) else None,
        "data": payload.get("data") if isinstance(payload, dict) else None,
        "error": error,
        "elapsed_ms": round((time.monotonic() - started) * 1000),
    }


def check_directory():
    result = get("/v1/symbols/available", {"type": "stock", "market": "HK", "page": "1", "page_size": "100"})
    summary = result["data"].get("summary", {}) if isinstance(result["data"], dict) else {}
    hk_count = summary.get("by_market", {}).get("HK")
    ok = result["http_status"] == 200 and result["api_code"] == 0 and hk_count is not None
    return {
        "check": "directory",
        "status": "PASS" if ok else "FAIL",
        "hk_count": hk_count,
        "last_updated": summary.get("last_updated"),
        **result,
    }


def check_ticker():
    result = get("/v1/market/ticker", {"symbols": SYMBOL, "type": "stock"})
    items = result["data"] if isinstance(result["data"], list) else []
    first = items[0] if items else {}
    ok = result["http_status"] == 200 and result["api_code"] == 0 and bool(first)
    return {
        "check": "ticker",
        "status": "PASS" if ok else "FAIL",
        "symbol": first.get("symbol"),
        "name": first.get("name"),
        "last_price": first.get("last_price"),
        **result,
    }


def check_kline():
    result = get("/v1/market/kline", {"symbol": SYMBOL, "interval": "1d", "limit": "5", "type": "stock"})
    data = result["data"] if isinstance(result["data"], dict) else {}
    klines = data.get("klines", []) if isinstance(data, dict) else []
    ok = result["http_status"] == 200 and result["api_code"] == 0 and bool(klines)
    return {
        "check": "kline",
        "status": "PASS" if ok else "FAIL",
        "interval": data.get("interval"),
        "bar_count": len(klines),
        "first_close": klines[0].get("close") if klines else None,
        "last_close": klines[-1].get("close") if klines else None,
        **result,
    }


def main():
    if not API_KEY:
        print(json.dumps({"status": "FAIL", "error": "TICKDB_API_KEY is not set"}, ensure_ascii=False))
        return 2

    started = time.monotonic()
    checks = [check_directory(), check_ticker(), check_kline()]
    all_pass = all(item["status"] == "PASS" for item in checks)

    print(json.dumps({
        "retrieved_at_utc": datetime.now(timezone.utc).isoformat(),
        "symbol": SYMBOL,
        "checks": checks,
        "elapsed_ms": round((time.monotonic() - started) * 1000),
        "status": "PASS" if all_pass else "FAIL",
    }, ensure_ascii=False, indent=2))
    return 0 if all_pass else 1


if __name__ == "__main__":
    sys.exit(main())

跑完别只看 PASS。我一般会再看这四件事:

  1. 标的代码对不对;
  2. HTTP 状态和 API code 是不是都正常;
  3. 账号有没有这三个端点的权限;
  4. 出错时能不能把请求参数和返回内容留下来,方便重跑。

最后这条最容易被忽略:数据一旦异常,能定位、补回、重跑,比“第一次调用成功”更有用。

FAQ

怎么开始? 拿一个你真正关注的港股标的,把脚本里的 700.HK 换成自己的代码,先跑通一次,记录请求、HTTP/API 状态和返回字段。

数据对不上或异常时怎么查? 先核对是不是同一个股票代码和口径,再看请求时间和报价时间是否对得上、有没有错误码或缺口,然后按规则重取或重跑。历史复权、断线补查这些是应用层的工作,不是接口自动完成。

TickDB 还能解决什么下一步问题? 单个港股标的跑通后,可以用同一个数据层扩展更多港股或别的市场;实时推送等其他入口需要在你要用的时候另行验证。本文没有实测 WebSocket,所以不把实时推送写进结论。

最后说一句

别把这篇当结论。拿你自己真正关注的港股标的跑一次,能留下你需要的对象、时间和错误记录,再决定要不要补一个独立行情服务商。

示例标的只用于演示数据请求,不构成投资建议。

通过 TickDB API 获取实时行情数据

一个 API 接入外汇、加密货币、美股、港股、A股、贵金属和全球指数的实时行情。支持 WebSocket 低延迟推送,免费开始使用。

免费领取 API Key查看 API 文档

相关文章