4. 封板前撤单行为的时序特征提取

撤单事件的时间轴建模

封板前的撤单行为不是孤立事件,而是一串带时间戳、档位、数量的离散动作。要判断撤单是否“集中在最后阶段”,需要先把逐笔委托明细还原成一条可计算的时间轴:每条撤单记录包含撤单时刻 t、原挂单档位 L(买一至买五)、撤单量 q。以封板时刻 T 为基准,把 t 映射为相对时间 τ = T − t(单位秒),则 τ 越小表示越接近封板。

本节采用三个统计量刻画撤单结构:

  • 撤单量:某时间窗口内买一至买五撤单量之和 Qw,用于衡量撤单规模。
  • 撤单频率:窗口内撤单笔数 nw 除以窗口时长,得到笔/分钟,用于衡量撤单密集程度。
  • 撤单档位分布:各档撤单量占该窗口总撤单量的比例 pL,用于判断撤单是集中在买一(贴近成交价)还是分散到买五。

把封板前 5 分钟切成 5 个 60 秒窗口:W1 为 τ∈(240,300],W2 为 (180,240],W3 为 (120,180],W4 为 (60,120],W5 为 (0,60]。若 W5 的撤单量占比与撤单频率显著高于前四个窗口,且撤单档位向买一集中,则判定撤单行为集中在封板前最后阶段。

封板前 5 分钟撤单时间轴(τ = T − t,单位:秒) τ=300s τ=0(封板 T) 300 240 180 120 60 0 W1 (240,300] W2 (180,240] W3 (120,180] W4 (60,120] W5 (0,60] ● 每个圆点 = 一笔撤单事件(示例数据) 窗口 撤单笔数 n 撤单量 Q(手) 频率(笔/分) 买一占比 判定 W1 2 1,200 2.0 18% 稀疏 W2 3 2,600 3.0 22% 稀疏 W3 4 4,100 4.0 31% 抬升 W4 6 7,800 6.0 44% 抬升 W5 10 15,300 10.0 63% 集中 W5 撤单量占比 49.2% 频率为 W1 的 5 倍 → 集中

撤单档位分布的判读

仅看总量还不够。若 W5 的撤单量虽大,但主要发生在买四、买五,说明撤单是“远端挂单撤离”,对封板稳定性的冲击有限;若撤单集中在买一、买二,则意味着贴近成交价的承接盘在封板前被抽走,封板质量更脆弱。因此需要按档位拆分:

档位W1–W3 撤单量(手)W4 撤单量(手)W5 撤单量(手)W5 占该档总量
买一1,5003,4009,60066.2%
买二2,1002,0003,20043.8%
买三1,8001,3001,50032.6%
买四1,40070060022.2%
买五1,10040040021.1%

示例数据下,买一档在 W5 的撤单量占该档全窗口撤单量的 66.2%,且 W5 买一撤单量占 W5 总撤单量的 62.7%,说明撤单不仅集中在最后 60 秒,而且集中在最贴近成交价的买一档。这是“封板前最后阶段撤单”的典型形态。

可执行的提取与判定流程

下面给出一个可直接运行的 Python 片段,输入为逐笔委托明细(含撤单记录),输出各窗口的撤单量、频率、档位分布与集中度判定。字段约定:ts 为撤单时间(秒级时间戳),side 为买卖方向,level 为档位 1–5,qty 为撤单量(手),seal_ts 为封板时刻。

import pandas as pd

def extract_cancel_windows(cancel_df, seal_ts, windows=(300, 240, 180, 120, 60, 0)):
    df = cancel_df[cancel_df["side"] == "B"].copy()
    df["tau"] = seal_ts - df["ts"]
    df = df[(df["tau"] > 0) & (df["tau"] <= 300)]
    df["window"] = pd.cut(df["tau"], bins=windows, labels=["W1","W2","W3","W4","W5"], right=True)
    agg = df.groupby("window", observed=True).agg(
        n=("qty", "size"), qty=("qty", "sum")
    ).reindex(["W1","W2","W3","W4","W5"]).fillna(0)
    agg["freq_per_min"] = agg["n"] / 1.0
    agg["qty_share"] = agg["qty"] / agg["qty"].sum()
    level_dist = df.pivot_table(index="window", columns="level", values="qty",
                                aggfunc="sum", fill_value=0).reindex(["W1","W2","W3","W4","W5"]).fillna(0)
    level_share = level_dist.div(level_dist.sum(axis=1), axis=0)
    return agg, level_dist, level_share

def judge_concentration(agg, level_share, qty_thr=0.40, freq_ratio_thr=2.0, l1_thr=0.50):
    w5 = agg.loc["W5"]
    base_freq = agg.loc[["W1","W2","W3","W4"], "freq_per_min"].mean()
    cond_qty = w5["qty_share"] >= qty_thr
    cond_freq = base_freq > 0 and w5["freq_per_min"] / base_freq >= freq_ratio_thr
    cond_level = level_share.loc["W5", 1] >= l1_thr
    return {
        "W5撤单量占比": round(w5["qty_share"], 3),
        "W5频率倍数": round(w5["freq_per_min"] / base_freq, 2) if base_freq else None,
        "W5买一占比": round(level_share.loc["W5", 1], 3),
        "集中判定": cond_qty and cond_freq and cond_level,
    }

判定阈值可按样本池校准:qty_thr=0.40 表示最后 60 秒撤单量占 5 分钟总撤单量 40% 以上;freq_ratio_thr=2.0 表示 W5 频率至少为前四窗口均值的 2 倍;l1_thr=0.50 表示 W5 撤单中买一档占比过半。三个条件同时满足才判为“集中在最后阶段”,避免单一指标误判。

若某只个股 W5 撤单量占比高但买一占比低,更可能是远端挂单批量撤离,应结合封单量变化再判断,不宜直接归为“最后阶段撤单”。
撤单记录需与成交记录区分:部分行情源把“部分成交后撤余量”也记为撤单,提取时应按原始委托号去重,避免同一委托的多次撤单被重复计数。

结果判读与记录表

对每个涨停样本输出一行记录,便于横向比较与复盘。示例数据(假设值)如下:

样本W5 撤单量占比W5 频率倍数W5 买一占比集中判定备注
A49.2%5.062.7%是买一撤单主导
B41.5%2.338.0%否远端撤离为主
C28.7%1.445.2%否撤单分散

样本 A 三项条件均满足,判为集中;样本 B 虽 W5 量占比达标,但买一占比不足 50%,说明撤单主要发生在买三至买五,属于远端撤离;样本 C 频率倍数不足 2,撤单在 5 分钟内分布较均匀。通过这张表可以把“撤单是否集中在最后阶段”从主观印象转为可复核的量化结论。