2. 涨停板数据获取与清洗

封板稳定性因子的历史检验,第一步不是算因子,而是把“涨停”这件事从行情数据里准确地还原出来。如果原始数据里混入了非涨停样本、封单量单位错乱、首次封板时间缺失,那么后面无论用多精细的因子公式,检验结论都会被污染。本节聚焦一件事:如何从行情源提取涨停事件,并整理出封单量、封单额、开板次数、首次封板时间、最后封板时间等字段,完成缺失值与异常值处理,形成一张可以直接进入因子计算的干净事件表。

一、原始字段从哪里来、长什么样

涨停事件表的字段来源通常分三类。第一类是日频行情快照,包括证券代码、交易日期、前收盘价、收盘价、最高价、最低价、涨跌幅、成交额、换手率,用于判定“是否涨停”以及计算涨停幅度。第二类是盘口或逐笔衍生数据,包括封单量(涨停价上未成交的买入委托量)、封单额(封单量×涨停价)、开板次数、首次封板时间、最后封板时间,这类字段往往来自行情商的 Level-2 或专门的涨停板统计接口。第三类是基础信息,包括板块(主板、创业板、科创板、北交所)、是否 ST、上市天数,用于确定涨停幅度阈值和样本过滤规则。

不同板块的涨停幅度不同:主板普通股一般为 10%,ST 股为 5%,创业板与科创板为 20%,北交所为 30%。因此判定涨停不能只看“涨跌幅≥9.9%”这种拍脑袋阈值,而应结合板块与 ST 状态计算理论涨停价,再用收盘价或最高价与之比对。一个稳妥的做法是先由前收盘价算出涨停价(按交易所规则四舍五入到分),再判断当日最高价是否触及涨停价、收盘价是否等于涨停价,从而区分“盘中触板”与“收盘封板”。

二、涨停事件的判定与字段提取

判定逻辑建议写成可复现的规则,而不是凭经验筛选。下面是一段示意性的 Python 处理骨架,参数与字段名可按实际数据源调整:

import pandas as pd

def limit_rate(code, is_st):
    if code.startswith(('300', '301', '688')):   # 创业板、科创板
        return 0.20
    if code.startswith(('8', '4')):              # 北交所
        return 0.30
    return 0.05 if is_st else 0.10

df['rate'] = [limit_rate(c, s) for c, s in zip(df['code'], df['is_st'])]
df['limit_price'] = (df['pre_close'] * (1 + df['rate'])).round(2)
df['is_limit_close'] = (df['close'] - df['limit_price']).abs() < 0.005
df['is_touch'] = df['high'] >= df['limit_price'] - 0.005
df['seal_amount'] = df['seal_volume'] * df['limit_price']

这里有两个容易出错的细节。其一,涨停价必须按“前收盘价×(1+幅度)”后四舍五入到分,直接用涨跌幅反推会因舍入产生一分钱误差,导致收盘价与涨停价对不上。其二,封单额应当用涨停价乘以封单量,而不是用收盘价或成交均价,否则封单额会随价格波动而失真。提取完成后,事件表至少应包含:代码、日期、板块、是否 ST、涨停价、封单量、封单额、开板次数、首次封板时间、最后封板时间、当日成交额、换手率。

涨停事件:从原始行情到可用事件表 原始数据源 日频行情快照 Level-2 盘口/逐笔 板块与 ST 基础信息 涨停判定 按板块/ST 定幅度 算涨停价(四舍五入到分) 区分触板 / 收盘封板 字段提取 封单量 / 封单额 开板次数 首次 / 最后封板时间 清洗 缺失值 异常值 单位统一 清洗规则(示例) ① 封单量为 0 或空 → 视为非封板样本,剔除或标记 ② 封单额 / 封单量 与涨停价偏离 > 1% → 单位或价格异常,回查源数据 ③ 首次封板时间缺失但开板次数 > 0 → 逻辑矛盾,标记待核 ④ 最后封板时间早于首次封板时间 → 时间倒挂,剔除 ⑤ 开板次数为负或非整数 → 置空并记录 ⑥ 上市不足 60 日 / 停牌日 → 按研究口径过滤 输出:涨停事件表(一行 = 一只股票一个交易日) 代码 · 日期 · 涨停价 · 封单量 · 封单额 开板次数 · 首次封板时间 · 最后封板时间 · 换手率

三、缺失值处理:先分清“没有”和“不知道”

缺失值在涨停数据里往往带有业务含义,不能一律删除或一律填充。封单量为空,可能是当日根本没有封板(非涨停样本),也可能是数据源未覆盖该股票,这两者处理方式完全不同。前者应当剔除出涨停事件表,后者应当标记为“数据缺失”并单独统计覆盖率。首次封板时间缺失但开板次数大于 0,说明数据内部矛盾,属于需要回查源数据的可疑记录,而不是简单填一个均值。

对于确实需要保留的缺失,处理原则是:时间类字段(首次、最后封板时间)不宜用均值填充,可保留为空并在因子计算时按“缺失即不参与”处理;数值类字段如封单额,若封单量存在而封单额缺失,可用封单量×涨停价重新计算补齐,这属于可推导的补全而非猜测。建议在事件表中增加一列“数据质量标记”,记录每条记录的缺失与修补情况,便于后续按质量分层做稳健性检验。

四、异常值识别:三类高频问题

第一类是单位错乱。有的数据源封单量以“手”为单位,有的以“股”为单位,混用会让封单额相差 100 倍。识别方法是抽查若干记录,用封单额除以封单量,结果应接近涨停价;若系统性偏离 100 倍,就是单位问题。第二类是时间倒挂与越界。首次封板时间应落在 09:30 至 15:00 之间,最后封板时间不应早于首次封板时间,开板次数应为非负整数。第三类是极端值。封单额相对流通市值或当日成交额的比例若出现离谱数值(例如封单额超过流通市值),基本可以判定为源数据错误,应剔除或回查。

建议在清洗阶段同时输出一份“数据质量摘要”:涨停样本数、各字段缺失率、被剔除记录数及原因分布。这份摘要既是数据可信度的证据,也是后续因子检验结论的边界说明。

五、形成可复现的事件表

完成上述步骤后,事件表应满足几个基本约束:每行唯一对应“代码+日期”;涨停判定字段与涨停价可复算;封单额与封单量、涨停价三者自洽;时间字段在合法区间内且顺序正确;每条被剔除或修补的记录都有原因标记。做到这些,封板稳定性因子的历史检验才有一个可靠的起点——因子公式再精巧,也无法弥补事件定义层面的错误。实际工作中,建议把判定、提取、清洗写成一条可重复执行的流水线,每次更换数据源或调整板块规则时重跑一遍,并对比前后事件表差异,确保样本口径的变化是可见、可控的。