3. 盘口量比突变的滚动检测

为什么量比突变不能直接等同于异常

买一量与卖一量的比值(下称“一档量比”)在盘口中会因大单挂出、撤单、成交消耗而快速跳动。若用固定阈值(如量比 > 3 即报警),正常的大额挂单和撤单会被大量误报。本例采用滚动窗口 + 稳健Z分数:用中位数与MAD估计局部基线,对量比的对数做标准化,只有当突变幅度显著偏离近期波动水平、且盘口深度结构同步变化时才标记为候选异常。

盘口快照 bid1_vol / ask1_vol 一档量比 r r = Vb1 / Va1 对数化 x = ln(r) 滚动窗口 W=30 中位数 med / MAD 稳健Z分数 z = 0.6745(x−med)/MAD |z| ≥ 3.5 突变候选点 深度联动校验 Δ深度 / 撤单占比 确认异常 量比突变 + 深度异动 正常大单挂撤 深度平稳,仅单侧变动 量比对数序列与滚动基线(示意) t−30 t t+10 med ± 3.5·MAD 带 z≈+4.1 候选突变

指标构造与稳健Z分数计算

设第 t 个快照的买一量为 Vb1、卖一量为 Va1,一档量比定义为 r_t = Vb1 / Va1。量比是右偏的正值,直接做Z分数会被极端值拉偏,因此先取对数 x_t = ln(r_t),再在长度为 W 的滚动窗口内计算中位数 med 与中位绝对偏差 MAD = median(|x_i − med|),稳健Z分数为:

z_t = 0.6745 · (x_t − med_t) / MAD_t

常数 0.6745 使 MAD 在正态假设下与标准差一致。当 MAD_t 过小(盘口长期静止)时需设下限 MAD_min,否则微小波动会被放大成巨大Z值。判定候选突变的条件为 |z_t| ≥ Z_th(本例取 3.5),并要求该点前后各 1 个快照不连续触发,以抑制单点毛刺。

可运行的检测实现

import math
from statistics import median

W = 30            # 滚动窗口长度(快照数)
Z_TH = 3.5        # 稳健Z分数阈值
MAD_MIN = 1e-3    # MAD下限,防止静止盘口放大噪声

def robust_z_series(ratios, w=W, z_th=Z_TH, mad_min=MAD_MIN):
    """ratios: 一档量比序列 r_t。返回每个点的 (z, 是否候选突变)。"""
    xs = [math.log(max(r, 1e-9)) for r in ratios]
    out = []
    for t in range(len(xs)):
        lo = max(0, t - w + 1)
        win = xs[lo:t + 1]
        if len(win) < 5:                 # 窗口不足,暂不判定
            out.append((0.0, False))
            continue
        med = median(win)
        mad = median([abs(v - med) for v in win])
        mad = max(mad, mad_min)
        z = 0.6745 * (xs[t] - med) / mad
        out.append((z, abs(z) >= z_th))
    # 抑制单点毛刺:要求前后各一点也超阈
    flags = [f for _, f in out]
    confirmed = []
    for t, (z, f) in enumerate(out):
        prev = flags[t - 1] if t > 0 else False
        nxt = flags[t + 1] if t + 1 < len(flags) else False
        confirmed.append((z, f and (prev or nxt)))
    return confirmed

# 教学示例:前段平稳,第 20 点买一量骤增(大单挂出)
ratios = [1.0, 1.1, 0.9, 1.05, 0.95, 1.0, 1.08, 0.92, 1.02, 0.98,
          1.0, 1.03, 0.97, 1.01, 0.99, 1.0, 1.04, 0.96, 1.02, 1.0,
          6.5, 6.2, 1.1, 1.0, 0.98, 1.02, 1.0, 0.99, 1.01, 1.0]
res = robust_z_series(ratios)
for i, (z, hit) in enumerate(res):
    if hit:
        print(f"t={i:2d}  z={z:+.2f}  候选突变")

示例数据中第 20、21 点量比跳到 6.5 与 6.2,其余在 1.0 附近。运行后这两点会因 |z| 远超 3.5 且相邻点同时超阈而被标记为候选突变。注意这只是候选——它可能只是买一挂出大单,需要下一步的深度校验来区分。

用盘口深度变化区分“真异常”与“正常大单挂撤”

候选点出现后,比较突变前后各 3 个快照的盘口深度特征:

  • 单侧深度变化率:ΔVb1 / Vb1_prev 与 ΔVa1 / Va1_prev。正常大单挂出通常只改变单侧,另一侧几乎不动。
  • 撤单占比:若买一量骤降且伴随买一价上移,多为撤单;若买一量骤增且价格未动,多为新挂单。
  • 多档深度一致性:真异常(如流动性抽离)往往二至五档同步变薄,而单档大单只影响一档。

判读规则:候选突变点若仅单侧一档变化、多档深度基本稳定,判为“正常大单挂撤”,不报警;若多档深度同步异动或双侧同时收缩,判为确认异常。这样可把大量正常大单从报警中剔除。

滚动窗口 W 与阈值 Z_th 需按快照频率调整:3 秒快照可用 W=30(约 90 秒基线),1 分钟快照则 W 取 20~30 更合适。MAD_min 建议用历史 MAD 的 5% 分位数设定,避免静止盘口误报。
稳健Z分数对“持续单边趋势”不敏感:若量比连续多点在窗口内缓慢抬升,中位数会跟随移动,Z 值可能始终不高。此类缓慢漂移应交给趋势类指标,而非突变检测。

结果验证与判读清单

检查项通过条件不通过时的处理
候选点数量单只股票单日候选点占比 < 2%提高 Z_th 或增大 MAD_min
毛刺抑制无孤立单点触发检查前后点确认逻辑是否生效
深度联动确认异常均伴随多档深度异动复核深度校验阈值
误报来源正常大单挂撤被正确排除补充撤单占比字段

验证时建议抽取 20 个候选点人工回看原始快照:记录突变方向、单双侧、涉及档位数与后续 1 分钟内价格变动,形成误报归因表,再据此微调 Z_th 与深度校验条件。