2. 首次封板时间的数据来源与清洗

要统计首次封板时间,第一步不是画图,而是把“涨停”这件事变成一条可计算的时间记录。所谓首次封板时间,指的是某只股票在某个交易日内,价格第一次触及当日涨停价并被买盘封住的时刻。它依赖三类原始信息:当日涨停价、逐笔或分时成交数据、以及交易日历。三者缺一不可,任何一类数据不干净,统计结果都会失真。

一、数据从哪里来

实际工作中,涨停板数据通常有三条获取渠道,各有取舍:

  • 行情终端导出:如同花顺、通达信、Wind 等终端可导出日线、分时和逐笔成交。优点是字段直观、含涨停价与封单量;缺点是逐笔数据量大、导出格式不统一,且不同终端的复权口径可能不同。
  • 交易所公开数据:交易所每日披露的行情文件与龙虎榜数据权威性最高,适合核对涨停价与成交明细,但获取有延迟,且需要自行拼接多日文件。
  • 量化数据接口:如 Tushare、AkShare 等开源或商业接口,能直接返回日线涨跌停标记、分时行情,适合批量处理。需注意接口的字段定义、复权方式和更新频率,避免不同版本口径混用。

无论走哪条渠道,都要先明确一个关键口径:涨停价如何确定。A 股主板涨停幅度一般为 10%,ST 股为 5%,创业板、科创板为 20%,北交所为 30%,且价格需按“四舍五入到分”规则取整。若直接用“昨收 × 1.1”而不做取整,会得到与交易所不一致的涨停价,导致封板判断出现系统性偏差。

二、时间字段的解析

逐笔或分时数据里的时间字段常见两种形态:一是“HH:MM:SS”字符串,二是“HHMMSS”整数,还有的接口返回带日期的时间戳。清洗时要统一转换为当日秒数或标准时间对象,并注意集合竞价时段。以 A 股为例,9:15–9:25 为开盘集合竞价,9:25 产生开盘价,9:30 起连续竞价。若某股在 9:25 即以涨停价开盘,其“首次封板时间”应记为 9:25 而非 9:30,这一点在统计中必须单独约定,否则开盘一字板会被错误地归到 9:30。

下面是一段把时间字段规范化的示例逻辑(示例数据,非实测):

def parse_time(s):
    s = str(s).zfill(6)          # "93000" -> "093000"
    h, m, sec = int(s[0:2]), int(s[2:4]), int(s[4:6])
    return h * 3600 + m * 60 + sec

# 9:25 集合竞价成交视为封板起点
def first_seal_time(ticks, limit_price):
    for t in sorted(ticks, key=lambda x: x["time"]):
        if t["price"] >= limit_price and t["volume"] > 0:
            return t["time"]
    return None

这里有两个细节:一是比较价格时用“大于等于涨停价”,因为部分数据源对涨停价的记录存在一分钱误差;二是要求成交量大于零,避免把只有挂单、没有成交的瞬间误判为封板。

三、停牌与异常值处理

停牌是最容易被忽略的干扰项。一只股票当日全天停牌,行情数据可能为空,也可能返回昨收价重复填充。若不剔除,会被误判为“未封板”或“封板时间为空”,进而污染封板率统计。处理原则是:以交易日历和停牌公告为准,先标记停牌日,再在统计时排除,而不是用价格是否变化来反推。

异常值主要有三类:价格越界(成交价高于涨停价或低于跌停价)、时间越界(时间戳落在非交易时段)、重复记录(同一笔成交被多次推送)。对价格越界,应回查复权口径与涨停价计算;对时间越界,直接丢弃并记录条数;对重复记录,按“时间+价格+成交量”去重。建议每次清洗后输出一份异常统计,例如“越界 12 条、重复 37 条、停牌 5 只”,便于追溯。

四、交易日历对齐

交易日历对齐解决的是“日期连续性”问题。统计首次封板时间的分布时,常需要按交易日聚合,而自然日里包含周末和节假日。若直接用自然日做索引,会出现大量空值日,均值、分位数都会被拉偏。正确做法是维护一张交易日历表,以交易日为唯一索引,把每只股票的封板记录挂到对应交易日上;对缺失交易日,明确区分“停牌”“无数据”“未涨停”三种状态,而不是一律填 0。

建议在清洗流程中固定输出一张“封板事件表”,字段至少包含:交易日期、股票代码、涨停价、首次封板时间(秒)、封板状态(一字板/盘中封板/未封板/停牌)、数据来源。后续所有统计都从这张表出发,口径才稳定。

下图概括了从原始数据到可用封板事件表的处理链路,重点标出了涨停价取整、集合竞价归属、停牌剔除和交易日历对齐四个关键节点:

涨停封板数据清洗链路 原始数据源 终端 / 交易所 / 接口 涨停价计算 昨收×幅度 四舍五入到分 时间字段解析 统一为当日秒数 9:25 竞价单独标记 异常与停牌处理 越界 / 重复 / 停牌剔除 输出异常统计 交易日历对齐 以交易日为唯一索引 · 区分停牌 / 无数据 / 未涨停 封板事件表(统计基表) 交易日期 · 代码 · 涨停价 · 首次封板时间 封板状态 · 数据来源

完成上述清洗后,得到的封板事件表才具备可比性。例如统计“首次封板时间早于 10:00 的个股占比”时,一字板按 9:25 计入,盘中封板按实际成交时间计入,停牌日不进入分母,未涨停日单独归类。这样得到的比例才反映真实的封板节奏,而不是被数据口径差异扭曲的结果。

常见误区:用日线数据的“收盘价是否等于涨停价”来判断封板,会漏掉盘中封板后又打开的个股,也无法得到首次封板时间。首次封板时间必须依赖分时或逐笔数据,日线只能作为辅助核对。

最后提醒一点:不同数据源的复权方式不同,计算涨停价时应使用不复权昨收,否则除权日的涨停价会算错。清洗阶段把口径写进代码注释和字段说明,比事后排查要省力得多。