早期做A股策略,我用Python拉通了日K线,回测曲线挺好看,以为数据层搞定了。实盘上线后问题一个接一个:除权日跳空被策略当成下跌信号,两天后才反应过来是K线没做复权;想估滑点,发现手里只有K线,没有盘口,只能拍一个固定值;想做多因子,打开数据源一看,基本面覆盖哪些标的、不覆盖哪些,谁也说不清。
这些坑不是因为接口调不通,而是因为一开始就没看清A股数据到底有几层。
A股数据远不止"行情"两个字。至少分四层:行情、参考数据、基本面、接入方式。缺一层,你的策略就会在某个节点翻车。
这篇文章要做两件事:第一,把A股数据的四层结构摊开,让你在写第一行接入代码前就能画出数据架构图;第二,以一套统一API服务为具体参考,把每一层的实际能力、字段结构、边界条件讲清楚,并给出可直接运行的Python代码。
一、A股数据能力全景图
先看整体结构。从数据到用户,是一条四层的链路:
┌─────────────────────────────────────────────────────────────┐
│ 接入层(怎么拿) │
│ REST │ WebSocket │ AI 原生(Skill / MCP / CLI) │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ 基本面层(公司是什么) │
│ 公司档案 │ 财务三表 │ 估值 │ 行业 │ 股东 │ 事件日历 │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ 参考数据层(什么时候、对什么) │
│ 交易日历 │ 交易时段 │ 标的信息 │ 指数计算 │ 符号目录 │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ 行情层(市场发生了什么) │
│ 快照 │ K线 │ 复权因子 │ 分时 │ 盘口 │ 逐笔 │ 资金流 │
└─────────────────────────────────────────────────────────────┘
行情是地基,参考数据是框架,基本面是纵深,接入层是出口。四层缺一层,你的数据管道就会在某个节点断掉。
下面的内容以 TickDB 为参考实现。它的A股能力覆盖了这四层,并且提供REST、WebSocket、AI原生三种接入方式。先建立结构,再看细节和代码。
覆盖范围速查
| 维度 | 能力 |
|---|---|
| 交易所 | 上交所 SSE + 深交所 SZSE |
| 标的 | 全部A股标的 + 主要指数 |
| 数据品类 | Tick / Trade / Depth (L2)、K线、分时、复权因子、资金流、基本面 |
| 实时性 | 实时快照与逐笔推送,具体更新频率以官方文档和实测为准 |
| 历史深度 | 产品支持最长10年K线 |
| 接入方式 | REST + WebSocket + AI原生(Skill / MCP / CLI) |
| 基本面包围 | 沪市 + 深市个股,不含北交所 / ETF |
统一响应结构与鉴权
在写第一行代码前,先把两件事固定下来,后面所有端点都复用。
REST 响应统一为 {code, message, data} 包装。 code=0 表示成功,data 内是该端点的业务数据。不同端点的 data 结构不同:有的是数组,有的是对象,有的还有二级嵌套。不能假设 data 就是行情字段本身。
REST 认证 Header 是 X-API-Key。 实测 X-TickDB-Key 返回 401,业务码 1002,提示须通过 X-API-Key 提供。
先把这两个基础封装写好,后面所有代码块直接复用:
python
# 测试环境:API 行为实测于 Python 3.14.2 / macOS 15.5 / 2026-09-16
# 发布机兼容性:Python 3.11 / Ubuntu 22.04 需补跑
# 以下代码按实测响应结构编写
import os
import requests
BASE_URL = "https://api.tickdb.ai/v1"
API_KEY = os.getenv("TICKDB_API_KEY")
def _headers() -> dict:
"""统一认证Header,不硬编码密钥"""
if not API_KEY:
raise RuntimeError("请先设置环境变量 TICKDB_API_KEY")
return {"X-API-Key": API_KEY}
def unwrap(resp: requests.Response) -> dict:
"""
统一解包 {code, message, data} 包装
成功返回 data;业务错误抛出 RuntimeError。
注意:HTTP 认证失败时 code 可能是字符串,不能直接和 0 比较,
所以先 raise_for_status() 再解析业务码。
"""
resp.raise_for_status()
payload = resp.json()
if payload.get("code") != 0:
raise RuntimeError(f"API业务错误: code={payload.get('code')} message={payload.get('message')}")
return payload["data"]
这段封装解决了三个坑 :密钥不硬编码、认证错误先于业务错误被捕获、data 解包统一。
二、行情层:Python接入实时快照与K线
行情层是地基。地基不稳,上面全塌。
| 品类 | 能力覆盖 | 解决什么问题 |
|---|---|---|
| 实时快照 | 单标的/批量/全市场;open缺失表示集合竞价进行中 | 盯盘、开盘信号、下单前校验 |
| K线与历史 | 1分钟到月线多个周期;A股5种复权 | 回测、因子、图表 |
| 复权因子 | 每笔公司行动对应前复权/后复权两条记录 | 自建复权逻辑、跨数据源校验 |
| 分时 | 分钟级价格路径 | 日内研究、开盘冲击 |
| 盘口深度 | 多档买卖盘 | 滑点估算、流动性分析 |
| 逐笔成交 | 逐笔明细、VWAP、聚合成交 | 订单流研究、执行系统 |
| 资金流 | 主力净流入、主力买卖量、散户净流入 | 情绪因子、资金监控 |
2.1 REST接入实时快照
参数用 symbols,返回 data 是数组。 单数 symbol 实测也被接受,但统一写复数,避免单复数歧义。
python
def get_ticker(symbol: str) -> dict:
"""
获取单标的实时快照
Args:
symbol: 标的代码,如 "688256.SH"(沪市)或 "000001.SZ"(深市)
Returns:
data 数组中的第一条记录;空数组时抛出未找到
"""
url = f"{BASE_URL}/market/ticker"
params = {"symbols": symbol}
try:
resp = requests.get(url, headers=_headers(), params=params, timeout=10)
data = unwrap(resp)
if not data:
raise RuntimeError(f"未找到标的: {symbol}")
# last_price: 最新价,open: 开盘价,prev_close: 昨收价
# volume_24h: 成交量,high_24h/low_24h: 24小时最高/最低
# timestamp: Unix毫秒时间戳
first = data[0]
print(f"symbol={first.get('symbol')} last={first.get('last_price')} open={first.get('open')}")
return first
except requests.RequestException as e:
print(f"请求失败: {e}")
raise
if __name__ == "__main__":
result = get_ticker("688256.SH")
print(result)
data 数组内单条记录的字段如下(字段级示意,非原始响应全文):
json
{
"symbol": "688256.SH",
"name": "寒武纪",
"type": "stock",
"category": "CN",
"last_price": "1086.58",
"open": "1055.00",
"prev_close": "1053.98",
"volume_24h": "9102334",
"high_24h": "1092.50",
"low_24h": "1048.00",
"timestamp": 1754118000000
}
A股集合竞价期间,open字段缺失是合法状态,不是接口出错。 用open是否存在判断是否进入正式交易,比比较当前时间更稳健。这个判断决定了你的开盘信号是在正式交易后触发,还是在集合竞价期间就误触发。
另一个坑:不存在标的返回 HTTP 200,data 是空数组。 不能把 HTTP 200 直接当成有行情。上面代码里 if not data 就是为了处理这种情况。
2.2 REST接入K线
K线的时间参数是 start_time/end_time(Unix 毫秒),不是 beg_day/end_day。
我一开始把交易日历的参数习惯带到K线,用 beg_day/end_day 请求。返回是200,看着像成功,但拉回来的数据范围根本没被限制在指定窗口------200不等于参数生效 。改成 start_time/end_time 毫秒值后,时间窗才真正生效。
python
from datetime import datetime, timezone
def to_ms(date_str: str) -> int:
"""把 'YYYY-MM-DD' 转为 Unix 毫秒时间戳(UTC)"""
dt = datetime.strptime(date_str, "%Y-%m-%d").replace(tzinfo=timezone.utc)
return int(dt.timestamp() * 1000)
def get_kline(symbol: str, interval: str = "1d", adjust: str = "forward",
beg_day: str = "2026-08-01", end_day: str = "2026-08-31",
limit: int = 100) -> list:
"""
获取历史K线
Args:
symbol: 标的代码,如 "688256.SH"
interval: K线周期,支持 1m/5m/15m/30m/1h/4h/1d/1w/1M
adjust: 复权方式,A股支持 none/forward/backward/forward_add/backward_add
beg_day / end_day: 日期字符串,内部转为毫秒时间戳
limit: 返回条数,最大1000
Returns:
klines 列表
"""
url = f"{BASE_URL}/market/kline"
params = {
"symbol": symbol,
"interval": interval,
"adjust": adjust,
"start_time": to_ms(beg_day),
"end_time": to_ms(end_day),
"limit": limit,
}
try:
resp = requests.get(url, headers=_headers(), params=params, timeout=10)
data = unwrap(resp)
# K线数组在 data.klines,不是顶层 klines
klines = data.get("klines", [])
# open/high/low/close: OHLC价格,volume: 成交量
# timestamp: 周期起始时间,UTC格式
print(f"获取到 {len(klines)} 条K线,最新一条: {klines[-1] if klines else '无'}")
return klines
except requests.RequestException as e:
print(f"请求失败: {e}")
raise
if __name__ == "__main__":
klines = get_kline("688256.SH")
print(klines)
data.klines 数组内单条记录(字段级示意):
json
{
"timestamp": "2026-08-01T00:00:00Z",
"open": 1040.00,
"high": 1068.88,
"low": 1030.01,
"close": 1053.98,
"volume": 8234521
}
接口返回已结束周期的历史K线;当前形成中的周期用另一个接口(/market/kline/latest)。这个区分很重要------把形成中的bar当成已收盘bar用,会产生前视式信号。
2.3 复权因子
复权因子不是比例,是仿射变换。
每一笔公司行动对应一条前复权、一条后复权记录,公式是:
adjusted_price = raw_price × factor_a + factor_b
A股市场的factor_b恒为0,所以实际就是 raw_price × factor_a。它的价值在于:你可以自己控制复权逻辑,而不是依赖数据源替你算。
前复权适合实时信号和最新价格对齐,后复权适合长期历史比较。混用会产生系统性误差------等价于在同一个价格序列里插入两套时间基准。
复权因子的响应路径比较深:data.data[symbol]。 这是实测发现的,不能凭直觉猜。
python
def get_ex_factors(symbol: str, beg_day: str = "2026-01-01",
end_day: str = "2026-09-16") -> list:
"""获取复权因子,返回该标的的因子记录列表"""
url = f"{BASE_URL}/market/kline/ex-factors"
params = {
"symbol": symbol,
"start_time": to_ms(beg_day),
"end_time": to_ms(end_day),
}
try:
resp = requests.get(url, headers=_headers(), params=params, timeout=10)
data = unwrap(resp)
# 实测路径:data.data[symbol],每个元素含 timestamp/adjust/factor_a/factor_b
factors = data.get("data", {}).get(symbol, [])
print(f"复权因子记录数: {len(factors)}")
if factors:
print(f"样例: adjust={factors[0].get('adjust')} factor_a={factors[0].get('factor_a')} factor_b={factors[0].get('factor_b')}")
return factors
except requests.RequestException as e:
print(f"请求失败: {e}")
raise
2.4 盘口、逐笔、资金流
盘口 返回多档买卖盘。data 内单条记录(字段级示意):
json
{
"symbol": "688256.SH",
"bids": [
{"price": 1086.00, "size": 200},
{"price": 1085.50, "size": 300}
],
"asks": [
{"price": 1086.50, "size": 100},
{"price": 1087.00, "size": 350}
]
}
盘口对时延极度敏感。用之前必须验证目标市场的实际可用性,不能从接口存在推断全市场覆盖。
逐笔成交返回逐笔明细,含价格、数量、时间、方向。逐笔是判断真实买卖力量的原始材料,比K线更早反映市场意图。
资金流 返回主力净流入、主力买卖量、散户净流入等字段。A股资金流有三种常见口径------成交方向归因、大单资金流、主力资金流。这三个定义在不同数据源之间完全不兼容。用两个来源的资金流数据做比较,等于用两把不同刻度的尺子量同一个东西。
2.5 行情层的关键认知
如果你只接了K线和ticker,你的策略在除权日、集合竞价、停牌、换月这四个节点上,一定会有一次莫名其妙的回撤。
三、参考数据层:交易日历与交易时段
参考数据层是框架。没有框架,行情数据就是一堆不知道能不能用的数字。
| 品类 | 能力覆盖 | 解决什么问题 |
|---|---|---|
| 交易日历 | 按市场维护的交易日列表,含节假日 | 回测时间序列、缺口识别 |
| 交易时段 | 集合竞价、连续竞价、午休、收盘的完整分段 | 实时策略状态机 |
| 标的基础信息 | 名称、交易所、币种、股本、EPS、BPS、股息率 | 估值计算、身份确认 |
| 指数计算 | 市场宽度、涨跌家数等汇总指标 | 大盘分析、状态识别 |
| 符号目录 | 全市场可查询标的清单 | 选股候选池、UI适配 |
3.1 交易日历
这个端点的参数和K线完全不一样:必须带 market=CN,日期格式是 YYYYMMDD 紧凑格式,不是 YYYY-MM-DD。
我一开始用 from/to 请求,返回400。后来加 market=CN 但日期带连字符,还是400。改成 20260901 这种紧凑格式才成功。
同一个API体系内,不同端点的参数命名和格式不统一,这是接入时最容易踩的坑。 不要假设K线用什么,交易日历就用什么。
python
def get_trade_days(market: str = "CN",
beg_day: str = "20260901",
end_day: str = "20260930") -> dict:
"""
获取交易日历
注意:日期格式必须是 YYYYMMDD,不是 YYYY-MM-DD;
且必须带 market 参数,否则返回400。
"""
url = f"{BASE_URL}/market/trade-days"
params = {"market": market, "beg_day": beg_day, "end_day": end_day}
try:
resp = requests.get(url, headers=_headers(), params=params, timeout=10)
data = unwrap(resp)
days = data.get("trade_days", [])
half_days = data.get("half_trade_days", [])
print(f"交易日数量: {len(days)},半日市数量: {len(half_days)}")
if days:
print(f"首日: {days[0]},末日: {days[-1]}")
return data
except requests.RequestException as e:
print(f"请求失败: {e}")
raise
A股、港股、美股、期货的休市安排不同,多市场研究必须按市场维护独立的交易日历。 "默认按工作日"是回测最常见的陷阱------如果回测框架里的交易日历和真实市场不一致,信号触发时间会系统性偏移。
3.2 交易时段
返回的 data 是数组,不是对象。 每个元素含 market 和 trading_sessions,每段含 begin_time 和 end_time。
python
def get_trading_sessions(market: str = "CN") -> list:
"""获取交易时段,返回 data 数组"""
url = f"{BASE_URL}/market/trading-sessions"
params = {"market": market}
try:
resp = requests.get(url, headers=_headers(), params=params, timeout=10)
data = unwrap(resp)
# data 是数组,遍历取 trading_sessions
for item in data:
sessions = item.get("trading_sessions", [])
print(f"market={item.get('market')} 时段数={len(sessions)}")
for s in sessions:
print(f" {s.get('begin_time')} - {s.get('end_time')}")
return data
except requests.RequestException as e:
print(f"请求失败: {e}")
raise
返回市场当天的开收市时段。A股集合竞价(9:15--9:25、14:57--15:00)、港股午休(12:00--13:00)、期货夜盘,这些是"数据有但不能按普通逻辑使用"的时段。不处理会导致策略在错误状态下运行。
3.3 标的基础信息
返回名称、交易所、币种、股本、EPS、BPS、股息率。估值计算的起点:当前价除以BPS就是PB,当前价除以EPS就是PE。
3.4 参考数据层的关键认知
交易日历和交易时段不是可选知识,是必须在数据层明确处理的事实。 否则你的日内数据里会有一段"空洞",或者策略在非交易时段收到错误信号。
四、基本面层:覆盖边界比覆盖数量更重要
基本面层是纵深。行情告诉你价格怎么走,基本面告诉你这家公司值多少。
覆盖边界先说清楚:本文使用的数据服务,基本面接口覆盖沪市和深市个股,不覆盖北交所个股,不覆盖沪市和深市的ETF。
| 品类 | 能力覆盖 | 解决什么问题 |
|---|---|---|
| 公司档案与高管 | 公司信息、业务简介、管理层名单 | 公司研究起点、治理分析 |
| 财务三表 | 利润表/资产负债表/现金流量表;最新期、年度、TTM | 多因子、盈利质量 |
| 业务与地区分部 | 按业务线或地区拆分的收入结构 | 收入结构分析、地缘风险 |
| 估值指标 | PE/PB/PS/股息率;快照+时序+一年高低位 | 估值分位、风格轮动 |
| 行业分析 | 同业公司、行业估值分布、行业排行、分类树 | 横向比较、行业轮动 |
| 资本行动 | 分红(含TTM)、回购、公司行动 | 事件驱动、回测复权 |
| 股东与基金 | 最新股东结构、前十大股东、基金持仓 | 筹码分析、机构行为 |
| 新闻与事件日历 | 个股新闻+全市场日历 | 事件驱动、风控禁入日 |
4.1 财务三表与估值
TTM口径只支持利润表和现金流量表,资产负债表不适用------这是财务分析的基本常识,但很多数据源不会主动告诉你。
估值字段有两个实测坑:字段名是大写 PE/PB/PS/DvdYld,且嵌在 data.metrics 下,每项还有 value 字段。 不是直接的小写 pe/pb/ps/dividend_yield。
python
def get_valuation_latest(symbol: str) -> dict:
"""获取估值快照,返回 metrics 字典"""
url = f"{BASE_URL}/fundamentals/valuation/latest"
params = {"symbol": symbol}
try:
resp = requests.get(url, headers=_headers(), params=params, timeout=10)
data = unwrap(resp)
# 实测路径:data.metrics["PE"]["value"]
metrics = data.get("metrics", {})
pe = metrics.get("PE", {}).get("value")
pb = metrics.get("PB", {}).get("value")
ps = metrics.get("PS", {}).get("value")
dvd = metrics.get("DvdYld", {}).get("value")
print(f"PE={pe} PB={pb} PS={ps} DvdYld={dvd}")
return metrics
except requests.RequestException as e:
print(f"请求失败: {e}")
raise
估值字段必须注明价格基准日,否则数字没有可比意义。 跨公司比较时必须确认币种一致,跨期比较时必须确认报告期对齐。
4.2 资本行动与股东
资本行动包含分红历史(含TTM)、回购记录、公司行动(拆股、合股、配股、代码变更)。分红除权日是A股价格出现向下跳空的触发日,回测时必须处理,否则系统会把除权跳空误判为价格下跌信号。
股东与基金包含最新股东结构、前十大股东、单股东明细、基金持仓。股东数据有披露延迟,不能当成实时信号使用。
4.3 基本面层的关键认知
知道不覆盖什么,比知道覆盖什么更重要。 沪市深市个股覆盖,北交所和ETF不覆盖------这个边界决定了你的多因子选股范围。
五、接入层:REST / WebSocket / AI原生
接入层是出口。同一套数据,三种接入路径,对应三种使用场景。
| 接入方式 | 认证 | 适用场景 | 关键特性 |
|---|---|---|---|
| REST | X-API-Key Header | 研究、回测、批量拉取 | 字段结构固定,有错误码 |
| WebSocket | api_key Query | 实时看板、盘中止损 | 密钥过期返回1008 |
| AI原生 | X-TickDB-Key Header | Agent工作流、自然语言取数 | 先取结构化事实,再分析 |
5.1 WebSocket接入实时行情
WebSocket 订阅消息格式和 REST 完全不同。 我一开始按直觉写 {"action":"subscribe","channel":"ticker","symbol":"..."},服务端返回 code=2001 Unknown command。
实测成功的格式是 {"cmd":"subscribe","data":{"channel":"ticker","symbols":["..."]}}。用 cmd 不是 action,用数组 symbols 不是单数 symbol,外层套 data。
先安装依赖:
shell
pip install requests websockets certifi
python
# 测试环境:API 行为实测于 Python 3.14.2 / macOS 15.5 / 2026-09-16
# 发布机兼容性:Python 3.11 / Ubuntu 22.04 需补跑
import os
import ssl
import json
import asyncio
import certifi
import websockets
api_key = os.getenv("TICKDB_API_KEY")
async def subscribe_realtime(symbol: str = "688256.SH"):
"""
订阅实时行情
频道说明:
- ticker: 实时快照
- depth: 盘口深度
- trade: 逐笔成交
订阅消息格式实测为 cmd/data/symbols,不是 action/channel/symbol。
密钥过期时,服务端通过 close code 1008 关闭连接,
同时发送JSON消息说明原因。重连逻辑必须区分正常断线和密钥过期。
"""
url = f"wss://api.tickdb.ai/v1/realtime?api_key={api_key}"
ssl_context = ssl.create_default_context(cafile=certifi.where())
try:
async with websockets.connect(url, ssl=ssl_context) as ws:
# 实测成功格式:cmd + data + symbols 数组
sub_msg = {
"cmd": "subscribe",
"data": {"channel": "ticker", "symbols": [symbol]},
}
await ws.send(json.dumps(sub_msg))
print(f"已发送订阅: {sub_msg}")
async for message in ws:
# 行情推送字段待交易时段补测,这里原样打印
print(message)
except websockets.exceptions.ConnectionClosedError as e:
if e.code == 1008:
print("密钥过期,需要刷新API Key后重连")
else:
print(f"连接关闭: code={e.code} reason={e.reason}")
except Exception as e:
print(f"连接异常: {e}")
raise
if __name__ == "__main__":
asyncio.run(subscribe_realtime())
关于 TLS :实测在 macOS 上用默认系统证书链连接失败,改用 certifi 提供的 CA 后连接成功。这不是服务端问题,是本地证书链问题。参考代码里显式使用 certifi,能提高跨环境运行稳定性。
关于 ticker 推送字段 :本次实测连接成功、订阅确认成功,但等待窗口内未收到 ticker 行情消息。因此上面的代码只做原样 print(message),不解析字段。交易时段补测后再添加字段访问示例。
5.2 AI原生接入
Skill、MCP、CLI三档,通过 X-TickDB-Key 认证,让Agent直接调用结构化市场数据。
- Skill:"对话即用·零配置",适合PM/研究员/轻度用户
- MCP:"一个托管HTTPS端点,让你的AI编码助手一次连接即获得行情数据工具"
- CLI:"终端直查实时行情,JSON/表格双输出,为脚本和自主Agent设计"
AI原生接入的意义不是"让AI帮你查价格",而是让模型先取得带标的、字段和时间戳的事实,再进行分析。 顺序反了,分析结论就没有可追溯的数据基础。
5.3 接入层的关键认知
AI工作流中的行情数据,必须在模型推理之前到位。 让模型用记忆猜价格,是把分析过程变成幻觉生成过程。
六、四类读者的最小可用组合
不同角色的人,不需要接同一套数据。
个人量化开发者
最小组合:K线(含复权)+ 实时快照 + 交易日历 + 标的基础信息
起步路径:先用K线搭回测框架,确认复权口径统一;再加上实时快照做信号触发;交易日历处理非交易日空值;标的基础信息用于估值筛选。
最容易踩的坑:K线没做复权,除权日跳空被策略当成下跌信号。
小团队数据工程师
最小组合:交易日历 + 交易时段 + 复权因子 + 标的信息 + 基本面
起步路径:先把参考数据层建好,再做行情管道;复权因子用于自建复权逻辑;基本面用于多因子研究。
最容易踩的坑:交易日历没按市场分开维护,多市场回测的时间轴对不齐。
AI工具使用者
最小组合:MCP/Skill/CLI + 实时快照 + 事件日历
起步路径:先用MCP或CLI接入实时快照,让Agent能取到带时间戳的事实;再加上事件日历,让Agent知道今天有什么值得关注的事件。
最容易踩的坑:让模型用训练数据里的历史价格回答"现在多少钱",而不是先调接口取最新价格。
金融应用团队
最小组合:REST + WebSocket + 多市场统一接入 + 断线重连 + POC验收维度
起步路径:先做POC验收,确认目标市场的字段结构、实时性覆盖、边界处理;再做生产接入,实现断线重连和状态恢复。
最容易踩的坑:没有区分正常断线和密钥过期(1008),重连逻辑一刀切。
七、错误码与常见问题
7.1 错误码速查
| HTTP | 业务码 | 含义 |
|---|---|---|
| 400 | 2001 | 港股/美股不支持加法复权 |
| 401 | 1005 | API Key过期 |
| 403 | 3009 | 接口未开放 |
| 403 | 3010 | 市场未开放 |
| 404 | 40404 | 上游无数据 |
| 404 | 40405 | 查询条件无有效业务数据 |
| 422 | 5006 | 复权基础数据不可用 |
| 429 | --- | 请求频率超限 |
| 503 | 5005 | 复权因子不可用 |
| WS close 1008 | --- | 密钥过期 |
实测补充 :错误 API Key 返回 HTTP 401,body 里 code 是字符串 "Invalid or expired token",不是整数。不要假设业务码永远是整数 ,先 raise_for_status(),再按需解析 body。
7.2 常见问题
Q:A股集合竞价期间为什么没有开盘价?
A:集合竞价期间(9:15--9:25、14:57--15:00)open字段缺失是合法状态。只有9:25统一撮合之后,open才会出现。
Q:基本面数据覆盖北交所吗?
A:本文使用的数据服务,基本面接口覆盖沪市和深市个股,不覆盖北交所个股,也不覆盖沪市和深市的ETF。
Q:资金流数据可以直接当买卖信号吗?
A:不能。资金流是市场参与者行为的量化描述,不是买卖信号。而且不同数据源的资金流口径不兼容,引用前必须先冻结口径定义。
Q:WebSocket断线后怎么处理?
A:要区分正常断线和密钥过期。密钥过期时服务端返回close code 1008并发送JSON消息说明原因,两种情况的处理方式不同。
Q:TradingView图表可以直接用这套数据吗?
A:REST API可以作为TradingView UDF后端的数据源层,但需要开发者自己实现UDF协议的包装层。
Q:为什么K线和交易日历的参数格式不一样?
A:实测发现K线用 start_time/end_time 毫秒值,交易日历用 market=CN + YYYYMMDD 紧凑格式。同一API体系内不同端点参数不统一,接入时必须逐个端点看文档和实测,不能凭直觉类推。
八、A股数据接入检查清单
把下面这张表填完,你的数据架构图就出来了。
| 能力层 | 品类 | 是否需要 | 是否已接入 |
|---|---|---|---|
| 行情 | 实时快照 | ||
| 行情 | K线(含复权) | ||
| 行情 | 复权因子 | ||
| 行情 | 分时 | ||
| 行情 | 盘口深度 | ||
| 行情 | 逐笔成交 | ||
| 行情 | 资金流 | ||
| 参考 | 交易日历 | ||
| 参考 | 交易时段 | ||
| 参考 | 标的信息 | ||
| 参考 | 指数计算 | ||
| 参考 | 符号目录 | ||
| 基本面 | 公司档案 | ||
| 基本面 | 财务三表 | ||
| 基本面 | 估值指标 | ||
| 基本面 | 行业分析 | ||
| 基本面 | 资本行动 | ||
| 基本面 | 股东与基金 | ||
| 基本面 | 新闻与日历 | ||
| 接入 | REST | ||
| 接入 | WebSocket | ||
| 接入 | AI原生 |
九、关于本文参考的统一数据服务
本文全程以 TickDB 作为参考实现,把A股数据的四层能力落到具体的接口、字段和边界。官网截至2026-09-15描述的核心能力如下:
| 维度 | 能力 |
|---|---|
| 覆盖范围 | 上交所、深交所全部A股标的;主要指数;另有美股、港股、期货、外汇 |
| 数据品类 | Tick / Trade / Depth (L2)、K线、分时、复权因子、资金流、基本面 |
| 实时性 | 实时快照与逐笔推送;WebSocket支持ticker、depth、trade三个A股频道 |
| 历史深度 | 产品支持最长10年K线 |
| 接入方式 | REST + WebSocket + AI原生(Skill / MCP / CLI),统一X-API-Key认证 |
| 基本面包围 | 沪市 + 深市个股;不覆盖北交所个股,不覆盖沪市和深市ETF |
TickDB 是面向开发者、量化研究和 AI 应用的统一实时市场数据服务。
如果决定试一下,怎么开始?
先按文末检查清单,列出你自己策略实际需要的数据层。然后从最小可用组合入手------多数个人量化开发者只需要K线(含复权)+ 实时快照 + 交易日历 + 标的基础信息这四类。跑通之后再决定是否扩展到盘口、逐笔、基本面。
十、总结
A股行情数据不是"拿到K线就够了"------至少有行情、参考数据、基本面、接入方式四层能力,每层解决不同的问题。
在项目初期就看清完整的数据全景图,比在回测失败后逐个排查遗漏的成本低得多。
三层立意链收束:
- 工程代价:字段schema不对导致parser重写,交易日历没分开维护导致多市场回测时间轴对不齐,工期以天计;参数名不统一导致200返回但过滤不生效,排查半天才发现时间窗根本没起作用。
- 决策风险:OHLCV对不齐,回测结果偏移方向不可预测;复权口径混用,等价于在同一个价格序列里插入两套时间基准。
- 可证明机制:文末检查清单 + 完整可运行代码 + 错误码速查表,可以直接带进POC验收。
我这轮踩坑最深的三个点,你可以直接避开:
- K线时间参数不是
beg_day/end_day,是start_time/end_time毫秒值。 - 交易日历必须带
market=CN,日期格式是YYYYMMDD。 - WebSocket订阅用
cmd/data/symbols,不是action/channel/symbol。
附录A:常见接口示例
| 端点 | 功能 |
|---|---|
/market/ticker |
单标的/批量行情快照 |
/market/ticker/cn-stock |
A股全市场快照 |
/market/kline |
历史K线 |
/market/kline/latest |
最新K线 |
/market/kline/ex-factors |
复权因子 |
/market/intraday |
分时数据 |
/market/depth |
盘口深度 |
/market/trades |
逐笔成交 |
/market/trades/vwap |
VWAP |
/market/capital-flow |
资金流 |
/market/trade-days |
交易日历 |
/market/trading-sessions |
交易时段 |
/market/stock-info |
标的基础信息 |
/market/calc-index |
指数计算指标 |
/market/intervals/kline |
可用K线周期 |
/symbols/available |
符号目录 |
/fundamentals/profile |
公司档案 |
/fundamentals/financials/latest |
最新财务 |
/fundamentals/financials/ttm |
TTM财务 |
/fundamentals/valuation/latest |
估值快照 |
/fundamentals/valuation/ts |
估值时序 |
/fundamentals/dividends |
分红历史 |
/fundamentals/corp-actions |
公司行动 |
/fundamentals/shareholders/top |
前十大股东 |
/fundamentals/calendar |
财经日历 |
/fundamentals/news |
个股新闻 |
/realtime |
WebSocket实时订阅 |
文中实测数据来自2026-09-15的API调用,样本标的为688256.SH(寒武纪)和600519.SH(贵州茅台)。API行为实测环境:Python 3.14.2 / macOS 15.5 / 2026-09-16。Python 3.11 / Ubuntu 22.04 兼容性待发布机补跑确认。动态数值以官网和官方文档当前口径为准。样本标的仅用于技术演示,不构成投资建议。