首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >上交所逐笔数据“隐藏”了什么?一文讲清沪深口径差异与还原方案

上交所逐笔数据“隐藏”了什么?一文讲清沪深口径差异与还原方案

原创
作者头像
DolphinDB
修改2026-08-13 11:57:44
修改2026-08-13 11:57:44
320
举报

导语: 在高频交易和因子研究中,逐笔数据的完整性直接影响策略结果的准确性。但在沪深两市中,上交所逐笔委托与深交所的披露方式并不一致,尤其是即时全部成交或部分成交的委托,往往会在原始数据中出现“缺失”或“缩量”现象,导致同一套统计逻辑在不同交易所上算出不同结果。针对这一问题,DolphinDB 提供了委托订单还原引擎(createOrderReconstituteEngine),通过还原上交所缺失的原始委托,统一沪深逐笔数据口径,让快照合成、订单流因子计算等上层应用可以复用同一套代码。

一个让人困惑的现象

做高频策略的团队也许会遇到一件怪事:同一套统计逻辑,比如“过去 60 秒新增了多少买单”放到深交所的数据上算出来的结果是对的,放到上交所却总是偏小。

排查了半天,代码没有问题,问题出在数据本身——两个交易所对外发布逐笔数据的方式不一样。

沪深逐笔的差异从哪来

深交所的逐笔委托是完整披露的:只要一笔委托进入撮合系统,无论最终是否成交,都会先发一条委托记录。上交所则做了精简,对能够立即撮合的委托区别处理:

  • 立即全部成交:不再单独发原始委托,逐笔数据里只留得下成交记录。比如一笔 3700 股的买单被 4 笔卖单瞬间吃掉,你能看到 4 条成交,却找不到那条 3700 股的委托。
  • 立即部分成交:先推成交,再补一条委托,但委托量是“剩余未成交量”,不是原始委托量。一笔 2000 股的卖单,531 股当场成交,你在数据里只能看到 1469 股的委托。

结果就是:只要统计口径是“市场究竟来了多少委托”,而不是“最终成交了多少”,上交所的原始数据就会系统性漏记一部分。

于是开发者不得不为沪深两市各维护一套预处理逻辑——上交所这边还要额外关联逐笔成交、反推缺失的委托量——策略迁移和维护成本因此上升。

委托订单还原引擎:把缺的那条委托补回来

针对上述场景,DolphinDB 开发了委托订单还原引擎(createOrderReconstituteEngine)专门解决这个问题:根据逐笔成交与逐笔委托之间的委托号关联关系,实时还原上交所缺失的原始委托,让输出数据和深交所逐笔委托具有一致的语义。

还原逻辑对应前面两种情形:

  • 全部即时成交:引擎把关联的几笔成交量加总,反向推算出那条“消失”的原始委托,插回数据流中。
  • 部分即时成交:引擎把已经收到的成交量和剩余委托量相加,还原出原始委托量,并重新排好先后顺序,让“先有委托、后有成交“的真实逻辑得以恢复。

为了便于后续做因子计算、快照重建等场景,引擎在输出结果里维护了两个标记:一个用来说明这条记录是原始数据还是引擎补出来的,另一个记录了还原后的正确先后顺序。

一套接口适配实盘和回测两种计算场景

无论是拿历史数据回测因子,还是接入实时行情跑线上策略,都可以通过委托订单还原引擎实现委托补齐。

具体使用逻辑只需要三步:

Step1. 准备数据。

把逐笔委托和逐笔成交合并成一张统一格式的表,核心信息包括证券代码、时间、价格、数量、买卖方向和委托编号。合并表通过 SourceType 字段区分消息来源:0 表示逐笔委托,1 表示逐笔成交。

Hint:为了便于统一表结构,可以在委托数据时,将其 BuyNo 和 SellNo 均置为 0。

批计算场景下,这一步可以通过 SQL 直接处理合并;流计算场景下,则有两种方式处理:

  • 部分行情插件,如 INSIGHT,支持直接订阅逐笔合并流表
  • 单独订阅上游的委托表和成交表,并分别在 handler 回调函数中将两者预处理为统一表结构,再注入合并后的流表

合并后的 OrderTrans 表结构如下图所示:

Step2. 创建引擎

DolphinDB 以 createOrderReconstituteEngine 函数接口的形式提供委托订单还原引擎。引擎的输入表为标准化后的逐笔委托与逐笔成交合并表,输出表在原始字段基础上增加 orderMarkorderIndex 两列,用于标识还原结果来源以及还原后在通道内的顺序。

参考代码如下:

代码语言:javascript
复制
engine = createOrderReconstituteEngine(
                    name="OrderReconstitute", 
                    dummyTable=objByName(inputTbName), 
                    outputTable=objByName(inputTbName+"OrderReconstitute"), 
                    inputColMap=inputColMap)

其中,inputColMap 是一个字典,负责把合并表里的列,对应到引擎能识别的几类信息:

字段

说明

codeColumn

标的股票代码

typeColumn

区分市价、限价、撤单等委托类型,以及成交、撤单等成交类型

priceColumn / qtyColumn

委托或成交对应的价格和股数

sideColumn

买卖方向:买单还是卖单

buyOrderColumn / sellOrderColumn

用于把一笔成交关联回它对应的委托,是还原逻辑的核心依据

msgTypeColumn

区分这条记录是委托、成交,还是市场状态消息

Step3. 注入数据

引擎创建好之后,只需要把数据喂进去就能拿到还原结果。

需要注意:委托还原引擎会维护每个通道内的订单状态,因此同一个引擎一次只能处理同一天、同一个行情通道(ChannelNo)的数据,且必须按原始发布顺序(ApplSeqNum 顺序)接入,这是引擎能正确判断“这笔委托是否被还原过”的前提。

历史计算场景下,可以直接通过 append! 接口向引擎注入数据:

代码语言:javascript
复制
// 选取一天一个通道的数据
testData = select * from SHOrderTrans
           where TradeDate=2026.04.29 and ChannelNo=1
           order by ApplSeqNum

// 将历史数据追加到委托还原引擎
getStreamEngine("OrderReconstitute").append!(testData)

流计算场景下,则通过订阅函数的 handler 接口注入引擎:

代码语言:javascript
复制
subscribeTable(tableName="OrderTrans", actionName="orderTrans_to_reconstitute", offset=-1, handler=getStreamEngine("OrderReconstitute"), msgAsTable=true)

注意:若多通道并行接入,建议按 ChannelNo 拆分输入流或为每个通道创建独立任务。

案例实战

场景一:对接快照合成引擎

在实际的高频数据处理链路里,逐笔委托和逐笔成交通常不是终点,而是中间产物:拿到干净、口径一致的逐笔数据后,下一步往往是合成 Orderbook 快照,把某个时间点的完整盘口(各档位的买卖价格、数量、委托笔数)重建出来,供后续因子计算、策略回测直接使用。

这一步对数据的要求很高——快照合成天然依赖“先有委托、后有成交”的真实时间顺序,任何一笔委托的缺失或顺序错乱,都会直接反映在盘口的挂单量和委托笔数上。

委托订单还原引擎的输出,可以在流程上直接对接 DolphinDB 快照合成引擎——只需要把用于指定数据顺序的字段,从原始委托编号切换为还原引擎生成的顺序标记(orderIndex)即可。

代码语言:javascript
复制
inputColMap = dict(`codeColumn`timeColumn`typeColumn`priceColumn`qtyColumn
          `buyOrderColumn`sellOrderColumn`sideColumn`msgTypeColumn`seqColumn,
          `SecurityID`Time`Type`Price`Qty`BuyNo`SellNo`BSFlag`SourceType`orderIndex)

场景二:让订单流因子沪深两市共用一套代码

除了快照合成,高频因子研究里也有一类指标绕不开委托数据的完整性问题——订单流因子,比如过去 60 秒新增买/卖单委托笔数和委托量、过去 5 分钟买/卖单委托量的波动率。这些指标统计的是“市场上到达了多少委托”,而不是“最终成交了多少”,如果直接在上交所原始数据上计算,会因为漏掉立即成交的委托而系统性偏低,进而影响净买入委托量、委托量不平衡率等衍生因子的准确性。

在该场景下,上交所和深交所可以复用同一套因子计算逻辑。两市在计算逻辑上的差异只保留在过滤条件中:

  • 深交所:逐笔委托数据本身完整,新增委托直接用 SourceType == 0 筛选;
  • 上交所:还原输出表中既包含新增委托,也可能包含撤单等消息;如果只统计新增限价委托,需要使用 SourceType == 0 and Type == 2 筛选。

因子逻辑定义参考脚本如下:

代码语言:javascript
复制
/**
 * A26/A27:
 * 过去 5 分钟买/卖单单笔委托量的波动率(5 分钟窗口)
 */
def calcA26A27(orderTb, exchange){
    if(exchange == "SZ"){
        return select
            std(iif(BSFlag==1, Qty, NULL)) as A26,
            std(iif(BSFlag==2, Qty, NULL)) as A27
        from orderTb
        where SourceType == 0
        group by TradeDate, SecurityID, bar(Time, 5m) as Time
    } else if(exchange == "SH"){
        return select
            std(iif(BSFlag==1, Qty, NULL)) as A26,
            std(iif(BSFlag==2, Qty, NULL)) as A27
        from orderTb
        where SourceType == 0 and Type == 2
        group by TradeDate, SecurityID, bar(Time, 5m) as Time
    } else {
        throw "Unsupported exchange: " + exchange
    }

定义了计算所用到的所有因子后,将其封装在一个通用订单流因子计算函数 calcMarketFactors 中,最终的上交所和深交所因子计算脚本可以参考:

代码语言:javascript
复制
// 1. 上交所:使用委托还原后的逐笔数据
shOrderTb = select * from objByName("orderTrans2OrderReconstitute")
            order by orderIndex

// 2. 深交所:直接使用标准化后的逐笔合并数据
szOrderTb = select * from SZOrderTrans
            order by ApplSeqNum

// 3. 分别调用统一计算函数
shFactorRes = calcMarketFactors(shOrderTb, "SH")
szFactorRes = calcMarketFactors(szOrderTb, "SZ")

其中,上交所基于委托订单还原后的 orderTrans2OrderReconstitute 计算,深交所基于标准化后的逐笔合并表 SZOrderTrans 计算。

两市算出来的结果字段结构完全一致,可以直接合并进同一张因子表,不用再为两地行情各写一套代码、各查一次数——这也是委托订单还原引擎的核心价值所在:把交易所层面的数据差异解决在最底层,上层的因子逻辑、快照合成逻辑都可以按同一套框架复用。

上交所因子计算结果示例
上交所因子计算结果示例
深交所因子计算结果示例
深交所因子计算结果示例

结语

上交所逐笔数据的“缺失”看似是一个细节问题,实际却会让同一套统计逻辑在沪深两市算出不一致的结果,逼着开发者反复为交易所差异打补丁。与其在每个应用场景里各自处理,不如把这类问题一次性解决在数据基础设施层——委托订单还原引擎正是这个思路的体现:把交易所层面的口径差异消化在数据层,上层不管接入多少种计算逻辑,都可以直接复用,不必为每一次新需求重新踩一遍坑。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一个让人困惑的现象
  • 沪深逐笔的差异从哪来
  • 委托订单还原引擎:把缺的那条委托补回来
  • 一套接口适配实盘和回测两种计算场景
  • 案例实战
    • 场景一:对接快照合成引擎
    • 场景二:让订单流因子沪深两市共用一套代码
  • 结语
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档