4. 核心字段详解(二)

在逐笔成交数据中,价格和数量回答的是“成交了多少”,而买卖方向、成交编号、委托编号等交易标识字段回答的是“谁在成交、成交属于哪一笔委托、这笔成交与前后成交是什么关系”。这些字段本身数值不大,却构成了逐笔数据从“孤立记录”走向“可关联事件流”的骨架。理解它们的编码方式与关联规则,是后续做委托生命周期还原、大单拆分识别、主力行为分析的前提。

一、买卖方向字段:先明确参照系,再谈含义

逐笔成交中的买卖方向(常见字段名为 BSFlag、Side、TradeBSFlag 等)表示主动成交方的方向,而不是挂单方的方向。其判定依据是:成交时买方报价不低于卖方报价,若成交由买方主动吃单形成,则方向记为买(B);若由卖方主动砸单形成,则记为卖(S)。这一点必须先讲清楚,因为很多初学者会把它误解为“这笔成交里买方是谁”,从而在统计主动买卖净额时把符号搞反。

不同数据源的编码并不统一。上交所 Level-2 逐笔成交常用 B/S 字符;部分券商落地文件用 1/2 或 0/1 表示;还有的数据源用 +1/-1 表示买卖方向,用 0 表示未知或集合竞价成交。工程上必须做一次显式映射,不能依赖“看起来像”的默认值。例如把字符型统一转换为枚举:

# 示例:方向字段标准化(示例映射,实际以数据源文档为准)
DIR_MAP = {
    "B": "BUY", "S": "SELL",
    "1": "BUY", "2": "SELL",
    "0": "UNKNOWN"
}

def normalize_side(raw):
    key = str(raw).strip().upper()
    return DIR_MAP.get(key, "UNKNOWN")

需要特别注意的是,集合竞价阶段(开盘、收盘)产生的成交往往没有明确的主动方,方向字段可能为空、为 0 或直接标记为中性。若在统计中把它们强行归入买或卖,会系统性污染主动买卖差额。合理做法是单独打上“竞价成交”标签,在连续竞价分析中剔除。

二、成交编号与委托编号:两类不同粒度的标识

成交编号(TradeIndex、TradeID、成交序号)标识的是一笔成交事件,通常在同一交易日内按时间顺序递增,具有唯一性。它是最细粒度的“事件身份证”,用于去重、排序和定位。委托编号(OrderID、OrderNo、委托序号)标识的是一笔委托订单,同一笔委托可能对应多笔成交——这正是大单被拆分成多笔撮合的直接体现。

两者的关系可以概括为:一个委托编号对应 1 到 N 个成交编号,而一个成交编号只对应一个买方委托编号和一个卖方委托编号。因此逐笔成交记录中通常同时携带买方委托号和卖方委托号(如 BidOrderID、AskOrderID),通过它们才能把成交事件挂回到具体的委托上。若数据源只提供单一委托号字段,需要先确认它记录的是主动方委托号还是被动方委托号,否则关联方向会错位。

判断字段粒度的一个实用方法:看该字段在同一秒内是否可能重复。成交编号在同一交易日内不应重复;委托编号在同一委托上会重复出现于多笔成交中。若发现“成交编号”出现重复,多半是数据源把委托号误当成了成交号,或存在跨市场合并问题。

三、关联关系的还原逻辑

把上述字段串起来,就能还原出委托与成交的树状结构。以示例数据说明(以下数值为教学构造的示例,非实测):某日 10:00:03,委托号 880021 的买单先后与委托号 990105、990106 两笔卖单成交,成交编号分别为 5001、5002。此时可观察到:同一买方委托号出现两次,成交编号各不相同,方向均为 B。这说明该买单被分两笔撮合完成,属于典型的拆单成交。

还原时建议按以下顺序处理:先按成交编号排序保证事件时序;再以(买方委托号,卖方委托号)为键建立成交到委托的映射;最后按委托号聚合,统计每笔委托的成交笔数、累计成交量与成交均价。若累计成交量小于委托原始数量,说明该委托尚未完全成交或已撤单,需要结合委托表进一步判断。

成交编号与委托编号的关联结构(示例数据) 买方委托 880021 方向 B · 委托量 2000 股 卖方委托 990105 方向 S · 委托量 800 股 卖方委托 990106 方向 S · 委托量 1200 股 成交编号 5001 方向 B · 800 股 · 10.02 成交编号 5002 方向 B · 1200 股 · 10.03 1 对 N 按委托 880021 聚合 成交 2 笔 · 累计 2000 股 均价 10.026 · 已全部成交 买方委托 卖方委托 成交事件 聚合结果 虚线表示同一委托的第二笔成交

四、实操中的常见坑与校验方法

第一类是编号跨日重置。成交编号和委托编号通常只在单个交易日内唯一,跨日直接拼接会导致键冲突。落库时应以“交易日 + 编号”作为联合主键,或在读取时先按日期分区。

第二类是编号类型不一致。有的数据源委托号是字符串(可能带前导零),有的是整型,混用会导致 00123 与 123 被当成两个不同委托。读取阶段应统一为字符串并保留原始长度,避免数值化丢位。

第三类是方向与委托号不匹配。若某笔成交方向为 B,但买方委托号为空或与卖方委托号相同,说明数据存在缺失或错位,应标记为异常记录而非直接参与统计。可以写一个轻量校验:

# 示例:逐笔成交基础校验(示例规则)
def check_trade(row):
    issues = []
    if not row.get("trade_id"):
        issues.append("成交编号缺失")
    if row.get("side") not in ("BUY", "SELL", "UNKNOWN"):
        issues.append("方向编码异常")
    if row.get("side") == "BUY" and not row.get("bid_order_id"):
        issues.append("买方委托号缺失")
    if row.get("bid_order_id") == row.get("ask_order_id"):
        issues.append("买卖委托号相同")
    return issues

校验通过后,再进入聚合环节。一个可复用的判断依据是:对同一委托号,累计成交量不应超过其委托量;若超过,说明委托号被复用或数据重复,需要回查原始文件。这类一致性检查能在分析前拦下大部分脏数据,比事后修正结论要省力得多。

把方向、成交编号、委托编号三者放在一起看,逐笔成交就不再是一张平铺的记录表,而是一棵可以向上追溯到委托、向下拆分到单笔撮合的事件树。后续无论是识别大单拆分、统计主动买卖力量,还是还原撤单行为,都建立在这套标识与关联关系之上。