3. Tick数据采集:实时行情接口、数据解析、数据清洗基础

Tick数据是金融市场最细粒度的交易记录,每一条Tick都包含时间戳、价格、成交量、买卖盘口等瞬时快照。在坏账率监测与漂移哨兵系统中,Tick数据的采集质量直接决定后续特征计算的准确性和预警的时效性。本章聚焦实时行情接口的接入方式、数据解析的标准化流程以及数据清洗的基础规则,帮助学员建立从原始行情到可用Tick数据集的完整管道。

3.1 实时行情接口类型与选型

不同交易所和行情供应商提供的接口协议差异较大,选型时需综合考虑延迟、吞吐量、数据完整性和成本。下表对比了三种主流实时行情接口的核心特性:

接口类型 传输协议 典型延迟 数据格式 适用场景
WebSocket TCP / WSS < 10 ms JSON / BSON 高频交易、实时监控
FIX/FAST TCP / 组播 < 1 ms 二进制编码 机构级极速交易
REST Polling HTTP/2 50–200 ms JSON / CSV 低频分析、回测数据
选型建议:对于坏账率监测场景,推荐以WebSocket为主通道,FIX/FAST作为备选降级方案。REST Polling仅用于数据校验和补录,不可作为实时哨兵的主数据源。

3.2 Tick数据解析标准化流程

原始行情报文经过网络传输后,需要经过解析层转换为结构化Tick记录。解析流程通常包含以下五个步骤:

  1. 协议解码:根据接口协议(WebSocket帧、FIX消息、HTTP响应)提取有效载荷。
  2. 格式反序列化:将JSON/二进制流转换为内存对象,注意处理嵌套结构和可选字段。
  3. 字段映射:将交易所原始字段名映射为统一内部字段名(如 last_px → trade_price)。
  4. 时间戳归一化:将交易所时间戳(如Unix毫秒、字符串ISO8601)统一转为纳秒级整数,便于排序和计算。
  5. 校验与丢弃:检查序列号连续性、价格非负、成交量大于零等基本规则,丢弃异常报文。
关键指标:解析吞吐量应至少达到行情峰值速率的2倍(例如行情峰值10万条/秒,解析能力需≥20万条/秒),否则会造成数据积压和延迟飙升。

3.3 数据清洗基础规则

即使接口稳定、解析正确,原始Tick数据仍可能包含脏数据。清洗阶段需要识别并处理以下六类常见问题:

异常类型 典型表现 处理策略
重复Tick 相同时间戳+价格+成交量出现多次 基于(时间戳, 价格, 成交量)去重,保留首条
价格异常 价格超出当日涨跌停范围或为负数 标记为无效,用前一条有效Tick填充或丢弃
成交量异常 成交量≤0或超过单笔最大限制 丢弃该条Tick,记录告警日志
时间戳乱序 后到达的Tick时间戳早于前一条 缓存重排序窗口(通常50ms),超时后按时间戳排序输出
字段缺失 必填字段(如价格、成交量)为空 丢弃该条Tick,触发缺失率告警
盘口价差异常 买一价 ≥ 卖一价 或价差超过阈值 标记为可疑,不参与特征计算但保留原始记录

3.4 Tick数据采集管道架构

下图展示了从行情接口到清洗后Tick数据的完整采集管道,包含接入层、解析层、清洗层和存储层四个核心模块:

Tick数据采集管道架构 行情接入层 WebSocket / FIX / REST 数据解析层 反序列化 / 字段映射 / 时间归一 数据清洗层 去重 / 校验 / 排序 / 标记 存储层 时序数据库 / 消息队列 监控与告警 延迟 / 吞吐 / 异常率

3.5 代码示例:WebSocket Tick解析与清洗

以下Python代码演示了从WebSocket接收行情JSON、解析字段、执行基本清洗并输出标准化Tick记录的核心逻辑:

import json
import time
from dataclasses import dataclass
from typing import Optional

@dataclass
class TickRecord:
    symbol: str
    trade_time_ns: int          # 纳秒级时间戳
    trade_price: float
    trade_volume: int
    bid_price: float
    ask_price: float
    is_valid: bool = True

def parse_tick_from_ws(raw_message: str, symbol: str) -> Optional[TickRecord]:
    try:
        data = json.loads(raw_message)
        # 字段映射与时间戳归一化
        trade_time_ns = int(data['timestamp'] * 1_000_000_000)
        trade_price = float(data['price'])
        trade_volume = int(data['size'])
        bid_price = float(data.get('bid', 0.0))
        ask_price = float(data.get('ask', 0.0))

        # 清洗规则:价格/成交量基本校验
        if trade_price <= 0 or trade_volume <= 0:
            return None
        if bid_price > 0 and ask_price > 0 and bid_price >= ask_price:
            # 盘口价差异常,标记为无效但保留
            return TickRecord(symbol, trade_time_ns, trade_price,
                              trade_volume, bid_price, ask_price, is_valid=False)

        return TickRecord(symbol, trade_time_ns, trade_price,
                          trade_volume, bid_price, ask_price)
    except (json.JSONDecodeError, KeyError, ValueError) as e:
        # 解析失败,丢弃并记录日志
        print(f"Parse error: {e}")
        return None

# 使用示例(伪代码)
# ws.on_message = lambda msg: store_tick(parse_tick_from_ws(msg, "BTCUSDT"))
生产环境注意:上述示例为单线程简化版本。在生产部署中,必须使用异步IO(如asyncio + websockets)或多线程消费者池,确保解析速度不成为管道瓶颈。同时建议对清洗后的Tick记录添加数据版本号和源标识,便于问题追溯。

3.6 采集质量监控指标

为了保障Tick数据采集的持续健康,需要建立实时监控看板,重点关注以下四项指标:

  • 数据到达延迟(Latency):从交易所撮合时间到清洗完成时间的差值,P99应小于50ms。
  • 解析成功率(Parse Success Rate):成功解析的报文数 / 总接收报文数,目标≥99.9%。
  • 清洗丢弃率(Drop Rate):被清洗规则丢弃的Tick数 / 总解析成功数,正常应低于0.1%,超过1%触发告警。
  • 序列号连续性(Sequence Gap):检查交易所下发的序列号是否连续,出现跳号表示可能丢包,需触发补录流程。
哨兵联动:当清洗丢弃率或序列号间隙超过阈值时,漂移哨兵系统应自动降低该数据源的权重,并切换至备用行情通道,避免坏账率监测模型使用污染数据。