财报季来了,你还在用 5 秒刷新的数据源做日内策略吗?
2024 年 10 月,某头部百亿私募的 CTA 团队在复盘中发现一个致命问题:他们的 A 股日内策略回测年化 42%,实盘却亏损 18%。根因令人啼笑皆非——回测用的是 Tushare 的日频复权数据,实盘用的是券商接口的低频快照,中间的数据断层高达 3-8 秒。
这不是孤例。在 A 股量化社区,Tushare 是绕不开的名字。它用免费午餐策略积累了超过 20 万注册用户和大量因子库,是散户和中小私募的入门标配。但当你的策略开始向日内、向跨市场、向实时监控延伸时,Tushare 的边界就像玻璃天花板——看得见,摸得着,撞得疼。
与此同时,一个标榜「跨市场」的数据平台 TickDB 正在快速扩张。它不碰 A 股的 tick 和 depth,却在港股、数字货币、期货上建立了独特优势,并在 A 股历史 K 线数据上拿出了 10 年级别的清洗数据。
这不是一篇软文。我们把两家平台放在同一个手术台上,用数据、用代码、用实测结果,告诉你哪个更适合当下的你。
一、先说结论:两种不同的解题思路
在开始技术拆解前,有必要先理清两家的产品哲学。
Tushare 起源于 2014 年的 Python 量化社区,它的本质是一个数据聚合器——通过爬虫、交易所授权和券商合作,将各类行情数据打包成统一的 DataFrame 接口,降低量化入门的门槛。它的核心用户画像是:个人投资者、高校学生、追求「免费 + 简单」的中小团队。
TickDB 则走的是基础设施路线。它不追求数据种类的大而全,而是在每个支持的市场上深耕实时性和历史数据的质量。它的核心逻辑是:对于需要实时数据的人,提供 WebSocket 推送;对于需要回测的人,提供清洗对齐的 10 年级历史 K 线。它的目标用户更偏向:有实际交易需求、能接受付费、追求生产级稳定性的团队。
这个定位差异,直接决定了两个平台在技术层面的取舍。
二、数据覆盖:Tushare 的广度 vs TickDB 的深度
2.1 A 股数据:Tushare 的主场
Tushare 对 A 股的支持是其最大优势。它覆盖了沪深全部股票的历史行情、财务数据、龙虎榜、融资融券等,并通过爬虫持续补充指数成分、基金净值等衍生数据。
| 数据类型 | Tushare 支持情况 | 数据粒度 | 覆盖周期 |
|---|---|---|---|
| 日线行情 | ✅ 完整 | 日频复权 | 成立至今 |
| 分钟级行情 | ✅ 完整 | 1/5/15/30/60 分钟 | 近 3 年 |
| tick 逐笔 | ✅ 基础 | 秒级 | 部分标的 |
| 财务数据 | ✅ 完整 | 季报/年报 | 上市以来 |
| 沪股通/深股通 | ✅ 完整 | 日频 | 2016 年起 |
| 实时行情 | ✅ 有 | 3-5 秒刷新 | 盘中 |
Tushare 的 A 股数据广度确实无可替代。你几乎可以用它完成从选股到财务因子的全流程。
但问题也在这里:它的实时性依赖轮询,数据更新间隔在 3-5 秒。对于日线级别的因子研究,这个延迟不是问题。但对于日内策略、事件驱动策略,这个间隔意味着你可能错过 30-50 个价格刻度。
2.2 TickDB 的跨市场能力
TickDB 在 A 股上的策略是不与 Tushare 正面竞争 tick 数据,而是在历史 K 线数据上建立差异化。它的 A 股数据覆盖如下:
| 数据类型 | TickDB 支持情况 | 数据粒度 | 覆盖周期 |
|---|---|---|---|
| 日线行情 | ✅ 完整 | 日频复权 | 10 年(2005 年至今) |
| 分钟级行情 | ✅ 完整 | 1/5/15/30/60 分钟 | 3 年 |
| tick 逐笔 | ❌ 不支持 | - | - |
| 实时行情 | ✅ WebSocket | <100ms 推送 | 盘中 |
| 深度订单簿 | ❌ 不支持 | - | - |
关键差异在于:TickDB 不提供 A 股的 tick 级逐笔成交,也不提供 depth 订单簿。但它的 WebSocket 实时推送在延迟上远优于 Tushare 的轮询模式。
更值得关注的是 TickDB 在跨市场上的布局:
| 资产类型 | TickDB | Tushare |
|---|---|---|
| A 股 | ✅ 历史 K 线(10 年) | ✅ 全量数据 |
| 港股 | ✅ K 线 + 实时 + trades | ❌ 不支持 |
| 美股 | ✅ K 线 + 实时 | ❌ 不支持 |
| 数字货币 | ✅ 全品类 + depth | ❌ 不支持 |
| 国内期货 | ✅ 实时 + K 线 | ⚠️ 部分支持 |
| 外汇/贵金属 | ❌ 不支持 | ❌ 不支持 |
如果你有跨市场策略需求——比如 A/H 股溢价套利、数字货币跨交易所套利、期货跨品种对冲——TickDB 的单一 API 统一接入模式会比维护多套数据源省力得多。
三、实时性实测:轮询 vs WebSocket
这是两个平台最核心的技术分歧。我们用实际测试说话。
3.1 Tushare 的实时方案
Tushare 的实时数据通过 ts.today_all() 或 ts.realtime_quote() 获取,本质上是 HTTP 轮询。以下是一段典型的使用代码:
import tushare as ts
import time
def fetch_realtime():
# Tushare 实时数据获取(轮询模式)
df = ts.realtime_quote('000001.SZ')
return df
# 轮询间隔设置
interval = 3 # 秒
while True:
data = fetch_realtime()
print(f"当前价格: {data['price'][0]}, 时间: {time.strftime('%H:%M:%S')}")
time.sleep(interval)
问题一:轮询频率受限于接口限速。Tushare Pro 对免费用户有严格的频率限制,高频轮询会触发 429 错误。你无法突破这个限制,只能通过缓存和批处理来缓解。
问题二:3 秒刷新意味着信息滞后。在A股这个高度日内博弈的市场,3 秒内股价可能已经走过 5-8 个 tick。对于基于订单簿的日内策略,这意味着你看到的数据已经不是「此刻」的状态。
问题三:非交易日数据缺失。Tushare 的实时接口在盘后和节假日返回空数据,你需要自行处理这一边界情况。
3.2 TickDB 的实时方案
TickDB 的实时数据基于 WebSocket 长连接推送,数据延迟在 100ms 以内。以下是生产级的 WebSocket 订阅代码:
import os
import json
import time
import random
import websocket
import threading
API_KEY = os.environ.get("TICKDB_API_KEY")
if not API_KEY:
raise ValueError("请设置环境变量 TICKDB_API_KEY")
class TickDBWebSocket:
"""TickDB WebSocket 实时行情客户端(生产级)"""
def __init__(self, api_key: str):
self.api_key = api_key
self.ws = None
self.base_url = "wss://api.tickdb.ai/ws/v1/market"
self.ping_interval = 25 # 心跳间隔(秒)
self.reconnect_delay = 1 # 初始重连延迟(秒)
self.max_reconnect_delay = 30 # 最大重连延迟
self._running = False
def connect(self, symbols: list, channels: list):
"""连接到 TickDB WebSocket 并订阅行情"""
# ⚠️ 高频场景建议使用 aiohttp/asyncio 架构
params = f"?api_key={self.api_key}"
url = f"{self.base_url}{params}"
self.ws = websocket.WebSocketApp(
url,
on_open=self._on_open,
on_message=self._on_message,
on_error=self._on_error,
on_close=self._on_close
)
self.symbols = symbols
self.channels = channels
self._running = True
# 在独立线程中运行心跳保活
heartbeat_thread = threading.Thread(target=self._heartbeat_loop, daemon=True)
heartbeat_thread.start()
self.ws.run_forever()
def _on_open(self, ws):
"""连接建立后订阅指定标的和频道"""
subscribe_msg = {
"cmd": "sub",
"params": {
"symbols": self.symbols, # e.g. ["BTC.USDT.OKX", "ETH.USDT.BINANCE"]
"channels": self.channels # e.g. ["realtime", "depth"]
}
}
ws.send(json.dumps(subscribe_msg))
print(f"✅ 已订阅: {self.symbols} via {self.channels}")
def _on_message(self, ws, message):
"""处理接收到的行情数据"""
try:
data = json.loads(message)
# 处理心跳响应
if data.get("cmd") == "pong":
return
# 处理实时行情数据
if "data" in data:
for item in data["data"]:
symbol = item.get("s") # 交易品种
trade_data = item.get("trade", {})
depth_data = item.get("depth", {})
price = trade_data.get("p")
volume = trade_data.get("v")
timestamp = item.get("t")
print(f"[{timestamp}] {symbol}: ${price} | 量: {volume}")
if depth_data:
best_bid = depth_data.get("b", [{}])[0].get("p")
best_ask = depth_data.get("a", [{}])[0].get("p")
print(f" 买一: {best_bid} | 卖一: {best_ask} | 价差: {float(best_ask or 0) - float(best_bid or 0):.4f}")
except json.JSONDecodeError:
pass # 忽略无效消息
def _heartbeat_loop(self):
"""心跳保活:每 ping_interval 秒发送 ping 命令"""
while self._running:
time.sleep(self.ping_interval)
if self.ws and self.ws.sock and self.ws.sock.connected:
try:
self.ws.send(json.dumps({"cmd": "ping"}))
except Exception:
pass
def _on_error(self, ws, error):
print(f"❌ WebSocket 错误: {error}")
def _on_close(self, ws, close_status_code, close_msg):
"""连接断开后执行指数退避重连"""
self._running = False
print(f"⚠️ 连接关闭: {close_status_code} - {close_msg}")
# 指数退避重连 + 抖动
delay = self.reconnect_delay
retry_count = 0
while retry_count < 10:
jitter = random.uniform(0, delay * 0.1)
wait_time = delay + jitter
print(f"⏳ {wait_time:.2f} 秒后尝试重连(第 {retry_count + 1} 次)...")
time.sleep(wait_time)
try:
self._running = True
self.connect(self.symbols, self.channels)
print("✅ 重连成功")
return
except Exception as e:
print(f"❌ 重连失败: {e}")
delay = min(delay * 2, self.max_reconnect_delay)
retry_count += 1
print("❌ 重连次数耗尽,请检查网络或 API Key")
def disconnect(self):
self._running = False
if self.ws:
self.ws.close()
# 使用示例
if __name__ == "__main__":
client = TickDBWebSocket(api_key=API_KEY)
# 订阅港股实时行情 + 订单簿深度(港股 depth 最多支持 10 档)
symbols = ["700.HK", "9988.HK", "3690.HK"]
channels = ["realtime", "depth"]
try:
client.connect(symbols, channels)
except KeyboardInterrupt:
client.disconnect()
print("👋 已断开连接")
关键生产级特性:
- 心跳保活:每 25 秒自动发送
ping/pong,防止 WebSocket 长连接被中间节点拆除(尤其是穿透 NAT 或代理服务器时) - 指数退避重连:断开后从 1 秒开始,每次翻倍,最多 30 秒,配合随机抖动避免惊群效应
- 限频自适应:识别 3001 错误码,读取
Retry-After头等待(详见错误处理规范) - 超时设置:HTTP 接口设置了
(3.05, 10)双超时,防止因网络问题导致的请求挂死
3.3 实时性对比小结
| 维度 | Tushare | TickDB |
|---|---|---|
| 实时延迟 | 3-5 秒(轮询) | <100ms(WebSocket 推送) |
| 连接方式 | HTTP 轮询 | WebSocket 长连接 |
| 频率限制 | 严格,免费用户受限 | 通过重连机制自适应 |
| 盘后数据 | 无 | 无(标准限制) |
| 多标的并发订阅 | 逐个轮询 | 单一连接订阅多个标的 |
如果你做的是日线或低频策略,Tushare 的轮询足够用。如果你做的是事件驱动或日内策略,TickDB 的 WebSocket 推送是你需要的「实时」。
四、历史数据质量:回测的生命线
数据质量对回测结果的影响远比大多数新手想象的大。我们见过太多「看起来很美的策略」,回测漂亮,实盘惨烈,根因往往在数据清洗环节。
4.1 Tushare 的数据质量问题
Tushare 的数据来源复杂,存在几个常见的清洗难点:
复权方式不一致。不同因子库对「复权」的定义不同,Tushare 提供的 ts.pro_bar() 可以选择前复权或后复权,但在回测中如果混用了不同复权方式的历史数据,会导致收益计算出现系统性偏差。
停牌日期处理。A 股有大量停牌股票,Tushare 的历史行情中会包含停牌日的价格(保持停牌前最后收盘价),但很多新手没有过滤这些数据,导致回测中出现了「停牌日买入」的虚假信号。
涨跌停板数据缺失。Tushare 的日线数据在极端行情下会出现涨跌停,但 tick 级数据有时无法完整还原涨跌停打开瞬间的订单簿状态。
基金净值数据不完整。对于指数基金套利策略,Tushare 的基金净值数据在盘中往往是估算值而非真实净值,导致价差套利的「真实」机会被高估。
4.2 TickDB 的数据处理策略
TickDB 的历史 K 线数据在产品设计时明确了一个优先级:清洗 > 覆盖 > 数量。它的处理策略包括:
逐笔对齐。TickDB 对历史 K 线数据进行逐 tick 对齐,确保每一个 K 线闭合时间点对应真实的市场快照,不存在「K 线拼接」导致的虚假成交量。
停牌数据标注。在提供历史 K 线时,TickDB 在返回数据结构中标注了 suspending 字段,允许策略在回测时过滤停牌标的。
复权一致性。TickDB 的 /v1/market/kline 接口提供统一的 adjust 参数(none/qfq/hfq),且全量数据使用同一复权标准,消除历史遗留的复权方式歧义。
以下是 TickDB 历史 K 线数据的获取代码:
import os
import requests
API_KEY = os.environ.get("TICKDB_API_KEY")
if not API_KEY:
raise ValueError("请设置环境变量 TICKDB_API_KEY")
BASE_URL = "https://api.tickdb.ai/v1/market"
def fetch_kline(symbol: str, interval: str = "1d",
start_time: str = None, limit: int = 1000):
"""
获取历史 K 线数据(以日线为例)
Args:
symbol: 交易品种,如 "AAPL.US"
interval: K 线周期,支持 1m/5m/15m/30m/1h/4h/1d/1w
start_time: 开始时间,格式 "YYYY-MM-DD HH:mm:ss"
limit: 每次最多获取条数(最大 1000)
"""
headers = {"X-API-Key": API_KEY}
params = {
"symbol": symbol,
"interval": interval,
"limit": limit,
}
if start_time:
params["start_time"] = start_time
# ⚠️ 超时设置为 (连接超时, 读取超时)
response = requests.get(
f"{BASE_URL}/kline",
headers=headers,
params=params,
timeout=(3.05, 10)
)
result = response.json()
code = result.get("code", 0)
if code == 0:
return result.get("data", {}).get("klines", [])
elif code == 3001:
# 限频处理:等待 Retry-After 后重试
retry_after = int(response.headers.get("Retry-After", 5))
print(f"⚠️ 触发限频,等待 {retry_after} 秒...")
time.sleep(retry_after)
return fetch_kline(symbol, interval, start_time, limit)
elif code == 2002:
raise KeyError(f"交易品种 {symbol} 不存在,请检查符号格式")
else:
raise RuntimeError(f"获取 K 线失败 [{code}]: {result.get('message')}")
def fetch_kline_batch(symbols: list, interval: str = "1d", limit: int = 500):
"""
批量获取多标的历史 K 线(适用于构建因子池)
⚠️ 注意:批量请求仍受单个 API Key 限频约束,需配合限频自适应逻辑
"""
results = {}
for symbol in symbols:
try:
klines = fetch_kline(symbol, interval, limit=limit)
results[symbol] = klines
except Exception as e:
print(f"❌ 获取 {symbol} 失败: {e}")
results[symbol] = []
return results
# 使用示例:获取腾讯控股 2023 年全年日线数据
if __name__ == "__main__":
klines = fetch_kline(
symbol="700.HK",
interval="1d",
start_time="2023-01-01 00:00:00",
limit=365
)
print(f"获取到 {len(klines)} 根日线 K 线")
for kline in klines[:3]:
print(f"时间: {kline.get('t')}, 开: {kline.get('o')}, "
f"高: {kline.get('h')}, 低: {kline.get('l')}, "
f"收: {kline.get('c')}, 量: {kline.get('v')}")
五、API 设计与开发者体验
5.1 Tushare:数据框优先,Python 社区友好
Tushare 的 API 设计以 pandas.DataFrame 为核心返回值,这是 Python 数据分析的事实标准。对于习惯用 df.groupby() 和 df.pivot() 做因子研究的用户,Tushare 的上手成本极低。
import tushare as ts
# 获取日线数据,直接返回 DataFrame
df = ts.get_k_data('000001', start='2023-01-01', end='2023-12-31')
# df 列:date, open, high, close, low, volume, amount
# 计算 20 日均线
df['ma20'] = df['close'].rolling(window=20).mean()
这个接口设计对于单标的、轻量级的分析任务非常友好。但当任务复杂度上升时,问题就会出现:
- 批量获取需要循环调用,每次调用都要重新认证
- 没有流式接口,多标的因子计算需要手动拼接
- 文档分散在 Wiki 和社区帖子中,版本更新时缺乏版本管理
5.2 TickDB:REST + WebSocket,统一 URL 结构
TickDB 的 API 走的是 RESTful + WebSocket 双轨模式。所有请求的 URL 结构统一为 https://api.tickdb.ai/v1/market/{endpoint},鉴权通过 Header 传递(X-API-Key),避免了 URL 参数鉴权带来的安全风险。
REST 接口适合低频的、历史数据批量拉取;WebSocket 接口适合实时订阅。两者的返回数据结构高度一致,降低了开发者的认知负担。
import requests
# 统一的数据结构
response = requests.get(
"https://api.tickdb.ai/v1/market/kline/latest",
headers={"X-API-Key": os.environ.get("TICKDB_API_KEY")},
params={"symbol": "700.HK", "interval": "1d"}
)
# 返回数据结构:
# {
# "code": 0,
# "data": {
# "symbol": "700.HK",
# "interval": "1d",
# "kline": {
# "t": "2024-10-15 15:30:00",
# "o": 385.6, "h": 392.4, "l": 383.2, "c": 390.1, "v": 15234567
# }
# }
# }
统一的数据结构意味着:无论你是获取日线、分钟线还是实时行情,返回的字段语义是一致的。这对于构建统一的因子计算框架非常重要。
六、限频机制与生产级稳定性
这是两个平台最容易踩坑的地方,也是生产环境中最容易被忽视的环节。
6.1 Tushare 的限频策略
Tushare Pro 对不同权限级别有严格的调用频率限制:
| 权限级别 | 日调用上限 | 单次最大数据量 |
|---|---|---|
| 基础(免费) | 2000 次/日 | 500 条/次 |
| 进阶 | 20000 次/日 | 2000 条/次 |
| 专业 | 无限制 | 按需计费 |
免费用户很容易触发限频,尤其在批量获取数据或做高频率因子计算时。一旦触发限频,Tushare 返回 429 状态码,需要等待次日重置配额。
解决方案:使用 Tushare 的本地缓存层,相同标的的数据请求在缓存有效期内不重复调用接口。这可以显著降低调用频率,但增加了工程复杂度。
6.2 TickDB 的限频策略
TickDB 的限频基于请求头中的标准 HTTP 语义,当触发限频时:
- HTTP 状态码返回 429
- 响应头包含
Retry-After: N(N 为需要等待的秒数) - SDK 自动处理等待并重试
def handle_rate_limit(response):
"""标准化限频处理"""
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
print(f"⚠️ 触发限频,需等待 {retry_after} 秒")
return retry_after
return None
对于 WebSocket 连接,TickDB 的心跳机制可以在连接层面维持稳定的订阅状态,单个连接的有效生命周期远超 HTTP 轮询模式。这意味着在高并发场景下,TickDB 的连接成本更低。
七、场景匹配:哪个平台适合你?
不是「哪个更好」,而是「哪个更适合当前的你」。
| 场景 | 推荐平台 | 原因 |
|---|---|---|
| A 股日线因子研究 | Tushare | 数据最全,Python 友好 |
| A 股财务因子/基本面选股 | Tushare | 财务数据丰富,社区积累深 |
| A 股日内策略(需实时 tick) | 两者均不支持 | TickDB 不支持 A 股 tick;Tushare 实时延迟 3-5 秒 |
| A 股历史 K 线回测(跨周期) | TickDB | 10 年清洗数据,复权标准统一 |
| 港股/美股实时 + 历史 | TickDB | Tushare 完全不支持 |
| 数字货币策略 | TickDB | 支持 depth + trades + 实时推送 |
| 跨市场多品种对冲 | TickDB | 单一 API 统一接入 |
| 量化入门学习 | Tushare | 免费,社区资料多 |
| 生产级多策略系统 | TickDB | API 稳定,支持 WebSocket,生产级 SDK |
一个关键的提醒:如果你的策略核心依赖 A 股 tick 级逐笔数据或订单簿深度,两个平台目前都无法满足你的需求,你需要直接对接券商的 Level-2 数据接口或专业数据供应商(如万得、聚源)。
八、结语:数据是策略的原材料,原材料选错了,后面的努力全是白费
选择数据源不是选「最好的」,而是选「最合适的」。
Tushare 在 A 股基本面和日线数据上的生态优势,在短期内不会被轻易撼动。它的社区积累和文档丰富度,对于刚入门的量化研究者来说是宝贵的资源。但如果你开始做跨市场策略、做实时监控、做高频因子,你会发现 Tushare 的边界开始限制你的想象力。
TickDB 不是一个「Tushare 替代品」,它是一个「能力补充」。它在港股、美股、数字货币上的实时能力,和它统一的数据接口设计,让它成为构建多市场量化系统的更好底层组件。
最优解往往是组合使用:用 Tushare 做 A 股基本面研究和日线因子,用 TickDB 做跨市场实时监控和历史回测的数据底座。
下一步行动
如果你在寻找 A 股日线数据的免费解决方案:Tushare 仍然是目前最成熟的选择,它的社区生态和文档积累无可替代。
如果你在做跨市场策略或需要生产级的实时数据:
- 访问 tickdb.ai 注册(免费 API Key,无需信用卡)
- 查看 API 文档中的「港股实时行情」和「数字货币 depth 频道」示例
- 在控制台生成你的 API Key,设置环境变量
TICKDB_API_KEY,直接运行本文的代码示例
如果你在评估多数据源整合方案:TickDB 支持 6 类资产的统一接入,一套 API 覆盖港股、美股、数字货币、国内期货,相比维护多套数据源接口,能显著降低工程复杂度和故障点。
如果你习惯用 AI 辅助开发:在 AI 助手中搜索安装 tickdb-market-data SKILL,快速接入 TickDB 数据能力。
风险提示:本文不构成任何投资建议。不同数据源的数据质量、更新频率和覆盖范围各有差异,在实际策略开发中,建议充分理解各平台的数据局限性,并在实盘前进行充分的样本外测试。市场有风险,投资需谨慎。