3. 异常Tick类型

在Tick级数据处理中,“异常”并不是一个笼统的概念。不同字段的异常,其成因、识别手段和修复策略差异极大。本节将异常Tick划分为三大类:价格异常(跳空、零价、负价)、成交量异常(突增、突降、零量)以及时间戳异常。理解这三类异常的本质,是后续编写识别规则与修复逻辑的前提。

核心判断原则:价格异常看“是否落在合理价格区间与相邻跳变幅度”,成交量异常看“是否偏离同标的近期分布”,时间戳异常看“是否违背单调性与交易时段约束”。三者需分别建模,不可用同一套阈值一刀切。

一、价格异常:跳空、零价与负价

价格是Tick最核心的字段,其异常通常表现为三种形态。跳空指相邻两笔Tick价格出现远超正常波动范围的跃迁,例如上一笔10.00元、下一笔10.80元,中间无过渡。它可能源于真实行情(如集合竞价、涨跌停打开),也可能是数据源拼接错误。零价即价格字段为0或极小值,常见于行情源初始化、字段缺失被默认填充。负价在股票中几乎不可能,但在原油期货等品种中曾真实出现,因此不能简单一律判为错误,需结合品种规则判断。

识别价格异常时,建议同时维护两个维度:绝对边界与相对跳变。绝对边界用于拦截零价、负价及超出涨跌停的价格;相对跳变用于捕捉跳空。下面是一段典型的识别伪代码:

def check_price(tick, prev_tick, limit_up, limit_down):
    if tick.price <= 0:
        return "ZERO_OR_NEGATIVE"
    if tick.price > limit_up or tick.price < limit_down:
        return "OUT_OF_LIMIT"
    if prev_tick:
        jump = abs(tick.price - prev_tick.price) / prev_tick.price
        if jump > 0.10:          # 单笔跳变超过10%
            return "PRICE_JUMP"
    return "OK"

二、成交量异常:突增、突降与零量

成交量异常往往比价格异常更隐蔽。突增可能是真实的大单成交,也可能是重复推送或字段错位;突降常出现在数据源切换、撮合延迟场景;零量则需区分“真实无成交”与“字段缺失”。关键思路是:不要用固定阈值,而应基于同标的近N笔Tick的成交量分布建立动态基线,例如使用中位数与MAD(绝对中位差)构造稳健区间。

异常类型典型表现常见成因识别要点
价格跳空相邻Tick价差超阈值拼接错误、集合竞价相对跳变+时段判断
零价/负价price ≤ 0字段缺失、初始化绝对边界拦截
成交量突增volume远超近期基线大单、重复推送MAD稳健区间
成交量突降volume骤降至接近0数据源切换、延迟连续多笔确认
零量volume = 0无成交或字段缺失结合价格是否变化
时间戳异常乱序、重复、越界时钟漂移、多源合并单调性+时段校验

三、时间戳异常:乱序、重复与越界

时间戳异常是三类中最容易被忽视、却对回测影响最大的一类。它主要表现为:乱序(后到的Tick时间戳早于前一笔)、重复(同一时间戳出现多笔相同数据)、越界(时间戳落在非交易时段或未来时间)。乱序会破坏事件驱动回测的因果顺序,重复会导致成交量被重复累计,越界则可能让策略在错误的时间窗口触发。

处理时间戳异常时,应坚持“先校验、后排序、再去重”的流程。校验阶段检查单调性与交易时段;排序阶段对局部乱序窗口重排;去重阶段依据“时间戳+价格+成交量”组合键剔除完全重复记录。需要注意的是,排序窗口不宜过大,否则会引入未来信息,造成回测虚高。

实战建议:对时间戳异常,优先使用交易所原始时间字段而非本地接收时间;若必须使用本地时间,应记录接收延迟分布,并在回测中模拟该延迟,而不是简单忽略。

四、案例与复盘:一次典型的复合异常

某期货主力合约在夜盘开盘瞬间出现连续5笔Tick:价格从4200跳至0,再跳回4210,成交量分别为0、500、0、0、800,时间戳出现两笔完全相同。这是一个典型的复合异常:零价、零量、重复时间戳同时出现。若只按单一规则处理,可能只修复价格而保留重复时间戳,导致后续成交量统计翻倍。

正确的复盘流程是:先按时间戳去重,再对零价Tick做前值填充或标记剔除,最后用MAD基线判断500和800是否为真实大单。复盘时要记录每一步的处理动作与影响范围,形成可追溯的修复日志:

[FIX] ts=1698768000.123 dup_removed=1
[FIX] ts=1698768000.125 price=0 -> filled with prev=4200
[FIX] ts=1698768000.130 volume=500 flagged as OUTLIER, kept
[FIX] ts=1698768000.130 volume=800 flagged as OUTLIER, kept
常见误区:把“跳空”一律当作错误删除。实际上,涨跌停打开、集合竞价、主力合约换月都会产生真实跳空。识别异常的目的是“标记与分类”,而非“无差别清洗”。删除真实跳空会严重扭曲价格序列。

综上,价格、成交量、时间戳三类异常各有其识别逻辑与修复边界。价格异常重在边界与跳变,成交量异常重在分布与基线,时间戳异常重在顺序与时段。只有分别建模、组合校验,才能为后续的Tick修复与回测打下可靠基础。

异常Tick三类识别与修复路径 价格异常 跳空:相邻跳变超阈值 零价:price ≤ 0 负价:结合品种规则 识别:绝对边界 + 相对跳变 修复:前值填充 / 标记剔除 成交量异常 突增:远超近期基线 突降:骤降至接近0 零量:区分无成交/缺失 识别:中位数 + MAD 稳健区间 修复:连续确认 / 保留标记 时间戳异常 乱序:后到早于前笔 重复:同时间戳多笔 越界:非交易时段/未来 识别:单调性 + 时段校验 修复:局部排序 + 组合键去重 统一流程:校验 → 分类标记 → 修复 → 记录日志 → 回测验证