4. 委买委卖总量失衡事件识别

指标构造:十档委买委卖总量比

本节任务的核心对象是十档委买总量与十档委卖总量的比值。与例3只看买一卖一量比不同,这里把买盘和卖盘各十档的挂单量全部累加,得到更稳定的盘口深度对比。定义如下(以快照时刻 t 为单位):

  • BidVol10(t) = Σ bid_size[i],i 从 1 到 10,即买一到买十档挂单量之和;
  • AskVol10(t) = Σ ask_size[i],i 从 1 到 10,即卖一到卖十档挂单量之和;
  • IMB(t) = BidVol10(t) / AskVol10(t),即委买委卖总量比(Imbalance Ratio)。

约定:IMB > 1 表示买盘挂单总量占优,IMB < 1 表示卖盘占优。为避免除零,当 AskVol10 = 0 时该快照标记为无效并跳过。单点失衡容易受大单挂撤干扰,因此判定规则必须叠加持续时长约束:只有失衡状态连续维持达到 min_duration 个快照(或秒数)才计为一次事件。

失衡判定规则设计

规则分三层:阈值层、持续层、事件层。阈值层给出方向与强度,持续层过滤瞬时噪声,事件层输出起止时间与峰值。下面给出可直接运行的 Python 实现,输入为包含十档买卖挂单量的分钟级(或秒级)快照 DataFrame。

import pandas as pd
import numpy as np

def build_imbalance(snap: pd.DataFrame) -> pd.DataFrame:
    """snap 需含 bid1..bid10 / ask1..ask10 的挂单量列,以及时间列 ts。"""
    bid_cols = [f"bid{i}" for i in range(1, 11)]
    ask_cols = [f"ask{i}" for i in range(1, 11)]
    df = snap.copy()
    df["bid_vol10"] = df[bid_cols].sum(axis=1)
    df["ask_vol10"] = df[ask_cols].sum(axis=1)
    # 卖盘为 0 时比值无意义,置为 NaN
    df["imb"] = np.where(df["ask_vol10"] > 0,
                         df["bid_vol10"] / df["ask_vol10"], np.nan)
    return df

def detect_imbalance_events(df: pd.DataFrame,
                            upper: float = 2.0,
                            lower: float = 0.5,
                            min_duration: int = 3) -> pd.DataFrame:
    """upper/lower 为失衡阈值,min_duration 为连续快照数下限。"""
    d = df.dropna(subset=["imb"]).reset_index(drop=True)
    # 状态:1 买盘严重失衡,-1 卖盘严重失衡,0 正常
    d["state"] = 0
    d.loc[d["imb"] >= upper, "state"] = 1
    d.loc[d["imb"] <= lower, "state"] = -1

    events = []
    start = None
    cur_state = 0
    for i, s in enumerate(d["state"]):
        if s != cur_state:
            # 结束上一段
            if cur_state != 0 and start is not None:
                length = i - start
                if length >= min_duration:
                    seg = d.iloc[start:i]
                    events.append({
                        "start_ts": seg["ts"].iloc[0],
                        "end_ts": seg["ts"].iloc[-1],
                        "direction": "买盘失衡" if cur_state == 1 else "卖盘失衡",
                        "duration": length,
                        "peak_imb": seg["imb"].max() if cur_state == 1 else seg["imb"].min(),
                    })
            start = i if s != 0 else None
            cur_state = s
    # 收尾
    if cur_state != 0 and start is not None:
        length = len(d) - start
        if length >= min_duration:
            seg = d.iloc[start:]
            events.append({
                "start_ts": seg["ts"].iloc[0],
                "end_ts": seg["ts"].iloc[-1],
                "direction": "买盘失衡" if cur_state == 1 else "卖盘失衡",
                "duration": length,
                "peak_imb": seg["imb"].max() if cur_state == 1 else seg["imb"].min(),
            })
    return pd.DataFrame(events)

参数选择建议:upper=2.0、lower=0.5 对应买卖总量相差一倍,属于较明显的失衡;min_duration=3 在分钟级快照下约等于持续 3 分钟,可过滤单笔大单造成的瞬时跳变。若数据为秒级,应把 min_duration 换算为对应秒数,例如 60 秒。

失衡事件与后续价格波动的对应关系

识别出事件后,需要统计事件发生后的价格变化,验证失衡是否具有方向性信息。做法是:对每个事件,取事件结束时刻的中间价 mid_end,再取事件结束后第 N 个快照的中间价 mid_future,计算收益率 ret = mid_future / mid_end - 1,并按方向分组统计。

def attach_forward_return(df, events, horizon=5):
    d = df.dropna(subset=["imb"]).reset_index(drop=True)
    d["mid"] = (d["bid1_price"] + d["ask1_price"]) / 2
    rows = []
    for _, ev in events.iterrows():
        idx = d.index[d["ts"] == ev["end_ts"]]
        if len(idx) == 0:
            continue
        i = idx[0]
        j = min(i + horizon, len(d) - 1)
        ret = d["mid"].iloc[j] / d["mid"].iloc[i] - 1
        rows.append({**ev.to_dict(), "fwd_ret": ret})
    return pd.DataFrame(rows)

# 示例调用
# snap = pd.read_csv("snapshot.csv", parse_dates=["ts"])
# df = build_imbalance(snap)
# ev = detect_imbalance_events(df, upper=2.0, lower=0.5, min_duration=3)
# res = attach_forward_return(df, ev, horizon=5)
# print(res.groupby("direction")["fwd_ret"].agg(["count", "mean", "std"]))

判读逻辑:若“买盘失衡”事件后的平均收益显著为正、且“卖盘失衡”事件后平均收益显著为负,说明该指标具有方向性预测价值;若两组均值接近 0 或符号相反,则说明当前阈值下失衡事件更多是噪声或已被市场消化。示例数据下的预期现象应明确标注为示例,实际结论需用真实快照验证。

十档委买委卖总量比与持续时长判定 IMB 时间 upper = 2.0 lower = 0.5 IMB = 1 买盘失衡持续 ≥ min_duration 卖盘失衡持续 ≥ min_duration 瞬时尖峰被过滤 峰值 谷值 事件后 horizon 快照收益观察窗 买盘失衡区间 卖盘失衡区间 未达持续时长,不计事件

结果判读与常见陷阱

  • 阈值不能照搬:不同标的的十档深度差异很大,upper/lower 应结合该标的 IMB 的历史分位数确定,例如取 90% 与 10% 分位作为上下阈值。
  • 持续时长与采样频率绑定:分钟级快照的 min_duration=3 与秒级快照的 3 个点含义完全不同,必须换算成统一的时间长度再比较。
  • 方向性验证不可省:识别出事件不等于有预测价值,必须用事件后收益分组统计来检验,避免把噪声当成信号。
  • 大单挂撤干扰:十档总量比单档更稳,但仍可能被单笔巨额挂单拉动,可结合例6的挂撤痕迹做二次过滤。
建议先用一段历史快照跑通 build_imbalance 与 detect_imbalance_events,确认事件数量与人工观察的盘口异动大致吻合,再调整阈值与持续时长,最后用 attach_forward_return 做方向性检验。