订单流分析与市场深度预测:逐笔委托重建与DeepSeek因子辅助
发布时间:2026/9/17 14:27:31来源:尧图网络
简介面向量化研究员、证券数据分析师与金融工程方向学习者的一份系统性技术文档围绕证券市场流动性监测与微观结构分析展开将 DeepSeek-R1 引入 tick 级数据处理、交易行为模式识别与订单流分析场景着力破解流动性指标碎片化、微观结构建模难以落地的痛点。全文共 332 页、53 个大章节从流动性核心指标体系构建、预处理层改造、词嵌入层适配、注意力机制优化一路延伸到订单簿快照存储选型、主动买卖单方向判定、订单流不平衡度加权融合与推理加速形成由数据采集到市场深度预测的完整链路其中 9 类离散交易行为标签体系与多尺度时序建模对实盘特征工程有直接参考价值。资源为单个 PDF 文件约 14.81MB支持目录章节跳转与阅读器左侧书签大纲定位条理清晰、查阅方便。目前已有 91 人学习下载适合希望系统补齐微观结构分析与订单流建模能力的中高级读者。1. 盘中流动性抽干前的 40 秒这套方案在算什么14:20:33某只中盘股的卖一挂单从 2800 手掉到 320 手买一价差从 1 个 tick 拉到 4 个 tick随后 90 秒内价格下挫 1.8%。如果只看 1 分钟 K 线的成交量和涨跌幅这个信号最早也要到 14:22 才成立——那时候滑点已经吃掉大半个基点的利润。这份方案要解决的不是涨还是跌而是这个价位的深度还能不能承接住我要下的单。它把逐笔委托、逐笔成交拆成订单流分析指标再用交易行为模式识别把盘中状态切成若干片段最后交给市场深度预测算法输出未来 N 档的挂单量和吃单滑点估计。DeepSeek 在其中的位置是辅助因子解释、代码生成和复盘报告产出而不是替代微观结构建模本身。适合三类人做日内和 T0 的量化研究员、搭行情与风控链路的券商后台开发、需要评估大单执行成本的自营交易工程师。下面按数据底座、算法、落地、排错的顺序展开。2. 微观结构分析的数据底座逐笔委托重建订单簿与订单流指标2.1 逐笔委托与逐笔成交的对齐、去重和异常过滤Level-2 行情里逐笔委托流和逐笔成交流是两条独立的序列。委托流记录挂单、撤单成交流记录撮合结果两者的时间戳精度、序号连续性、字段语义在不同交易所之间有差异。接入前第一件事是做一次对账用当日成交总量分别从两条流聚合差值超过万分之三就说明字段理解有问题先别往下走。常见的坑是把成交明细当成委托流用。成交明细里看不到挂上去又撤掉的那部分量而撤单恰恰是判断做市商是否撤退的关键信号。丢掉它后面所有的深度预测都会系统性偏乐观。清洗步骤我一般按这个顺序走统一时间戳到毫秒并做单调性校验按交易所序号去重剔除集合竞价段和涨跌停封板期间的数据这两段的订单流分布和连续竞价完全不同最后把价格字段转成整数 tick避免浮点比较导致的档位错位。提示涨跌停封板期间的数据不要直接删掉了事单独存一份封板时长和封单量它是后续判断流动性的强特征。2.2 从逐笔流重建限价订单簿拿到真实的盘口深度快照数据只给你 10 档的截面而深度预测需要的是任意时刻的完整簿。重建的做法是维护一个价格到挂单量的字典委托流做增量成交流做减量。from collections import defaultdict import numpy as np class L2Book: 用逐笔委托 逐笔成交重建限价订单簿输出 N 档深度特征 def __init__(self, depth10): self.depth depth self.bid defaultdict(float) # price(tick) - 挂单量 self.ask defaultdict(float) def on_order(self, side, price, qty): # qty 为增量挂单为正撤单为负 book self.bid if side B else self.ask book[price] qty if book[price] 0: book.pop(price, None) def on_trade(self, side, price, qty): # side 为主动方B 表示主动买消耗卖盘 book self.ask if side B else self.bid book[price] - qty if book[price] 0: book.pop(price, None) def snapshot(self): bp np.array(sorted(self.bid.keys(), reverseTrue)[:self.depth]) ap np.array(sorted(self.ask.keys())[:self.depth]) bq np.array([self.bid[p] for p in bp]) aq np.array([self.ask[p] for p in ap]) return bp, bq, ap, aq def depth_features(self): bp, bq, ap, aq self.snapshot() if len(bp) 2 or len(ap) 2: return None mid (bp[0] ap[0]) / 2 # 深度斜率挂单量对档位序号的回归系数越负说明承接越薄 slope_b np.polyfit(np.arange(len(bq)), bq, 1)[0] slope_a np.polyfit(np.arange(len(aq)), aq, 1)[0] return { mid: mid, spread_tick: ap[0] - bp[0], queue_ratio: bq[0] / max(aq[0], 1e-9), depth_bid: bq.sum(), depth_ask: aq.sum(), slope_bid: slope_b, slope_ask: slope_a, }depth参数控制输出档数日内策略一般取 10 就够做市策略要开到 20 档以上才能看到远端的承接结构。on_order里对负增量做归零删除处理是因为交易所的撤单流偶尔会出现超出已挂量的情况容忍一下比抛异常更稳。depth_features返回的slope_*是最容易被忽略但最有效的特征同样 5000 手的总深度集中在买一到买三和摊在买一到买十对价格的支撑力完全不同。注意某一档没有挂单时不要直接填 0 参与斜率回归先做档位压缩再拟合否则会在跳空价位上产生假的陡峭斜率。2.3 订单流不平衡 OFI 与主动买卖量分解订单流分析里最经得起检验的指标是 OFI。它不看成交只看买一卖一的价格和挂单量怎么变本质是把想买的人是否比想卖的人更急量化出来。def ofi(prev, cur): prev/cur 格式(bid_px, bid_qty, ask_px, ask_qty)单位统一为 tick 和手 pb, pbq, pa, paq prev b, bq, a, aq cur e 0.0 e bq if b pb else 0.0 # 买一价上移或持平新增买量计正 e - pbq if b pb else 0.0 # 买一价下移原买量流失计负 e - aq if a pa else 0.0 # 卖一价下移新增卖量计负 e paq if a pa else 0.0 # 卖一价上移原卖量撤走计正 return e这个函数返回的e按时间累加再除以窗口内的平均深度做归一化就得到可直接入模的 OFI 因子。它比单纯的买卖量差更早反应——因为挂单量的变化往往先于成交量出现。围绕微观结构分析还有一组指标值得一起算出来它们从不同侧面描述同一件事指标计算口径反映什么更新频率OFI买一卖一价格与挂单量的变动组合短期价格压力逐笔有效价差2×|成交价−中间价|÷中间价实际成交成本逐笔/秒实现价差有效价差 5 分钟后中间价变动做市商真实盈亏分钟Amihud 非流动性|收益率|÷成交额单位成交额推动价格的能力分钟深度斜率N 档挂单量对档位回归承接厚度秒VPIN按等成交量分桶的买卖不平衡均值知情交易占比成交量桶VPIN 的桶宽参数是关键。分桶太大信号被平滑掉分桶太小桶内样本不足均值噪声压过信号。A 股日内我一般用当日预期的 1/50 日均成交量作为单桶量全天大约产生 50 个桶既能保证统计稳定性又不至于钝化。2.4 交易行为模式识别把连续成交切成可聚类的片段单看一秒钟的 OFI 没有意义真正有用的是当前这段行情像什么。交易行为模式识别的做法是滑窗提取行为向量再聚类成有限个状态。import pandas as pd from sklearn.mixture import GaussianMixture from sklearn.preprocessing import StandardScaler def behavior_features(tick_df, win30, stride5): 按 win 秒滑窗、stride 秒步进输出行为特征矩阵 g tick_df.set_index(ts).resample(1S).agg( vol(qty, sum), cnt(qty, size), buy_vol(qty, lambda s: s[tick_df.loc[s.index, side] B].sum()) ).fillna(0) feats pd.DataFrame({ trade_intensity: g[cnt].rolling(win).sum(), avg_trade_size: g[vol].rolling(win).sum() / g[cnt].rolling(win).sum().clip(lower1), buy_ratio: g[buy_vol].rolling(win).sum() / g[vol].rolling(win).sum().clip(lower1), vol_cv: g[vol].rolling(win).std() / g[vol].rolling(win).mean().clip(lower1), }).iloc[win::stride] return feats.dropna() def fit_states(feats, n_states4): scaler StandardScaler().fit(feats) gm GaussianMixture(n_componentsn_states, covariance_typefull, random_state42, n_init3).fit(scaler.transform(feats)) return scaler, gmwin取 30 秒是经验值太短状态切换频繁、模型抖动太长会把不同状态的边界抹平。n_states从 3 到 5 试通常能稳定收敛到流动性充裕单边趋势知情交易主导做市撤退这四类状态序列本身以及状态的持续时长就是很好的输入特征。covariance_typefull允许各维度相关代价是参数更多样本少于 5 万条时改用diag。3. 市场深度预测算法标签构造、时序切分与 DeepSeek 辅助因子挖掘3.1 预测目标的三种定义方式与取舍深度预测算法最容易做错的地方在标签。同样一套特征标签定义不同模型能力天差地别。标签定义计算式预测难度适用场景中间价方向sign(mid_{th} − mid_t)低但噪声大方向性择时盘口深度变化率(D_{th} − D_t) ÷ D_t中流动性风控、提前撤单吃单滑点(VWAP_fill − mid_t) ÷ mid_t高大单拆单执行做流动性监测我建议主用第二个。它的自相关结构比方向标签稳定得多而且标签本身就是业务关心的量——深度掉一半就该触发风控不需要再翻译。滑点标签适合直接喂给执行算法做成本预估但需要真实成交回报来标注回测阶段只能用模拟撮合近似误差要单独评估。h的选取也要实测A 股日内深度预测的有效窗口通常在 10 到 60 秒之间。低于 10 秒信号还没形成标签就已经实现高于 60 秒微观结构的预测力衰减到接近零。3.2 时序切分与 Purged K-Fold别让未来函数偷走 AUC标准 K-Fold 在时序数据上必然泄漏。相邻样本的标签窗口互相重叠训练集里混进了测试期的未来信息离线 AUC 冲到 0.75、上线腰斩到 0.52 是常态。import numpy as np def purged_kfold(n_samples, n_splits5, embargo600, label_horizon60): 时间序列 Purged K-Fold embargo: 测试集边界外额外剔除的样本数防特征窗口重叠 label_horizon: 标签跨度训练集尾部需再往前推这么多 fold_size n_samples // n_splits for k in range(n_splits): te_lo, te_hi k * fold_size, min((k 1) * fold_size, n_samples) test_idx np.arange(te_lo, te_hi) cut max(embargo, label_horizon) train_idx np.r_[ 0:max(te_lo - cut, 0), min(te_hi embargo, n_samples):n_samples ] yield train_idx, test_idxembargo要设成特征最长回看窗口的长度比如用了 5 分钟滚动 OFI那embargo至少 300。label_horizon等于标签的预测跨度。这两个参数设置不到位离线评估就只是自欺欺人。验收时除了 AUC一定要看分行情状态的分组表现。如果模型只在流动性充裕状态下有效、一到做市撤退就失效那它恰恰在最需要的时候不管用。3.3 梯度提升树与序列模型的选型边界维度LightGBMTCN / Transformer数据量要求十万级即可百万级起步单次推理延迟亚毫秒毫秒到十毫秒特征可解释性强有 gain 和 SHAP弱对特征工程的依赖高中调参成本低高日内深度预测我默认从 LightGBM 起步原因是延迟预算。风控链路里每个 tick 都要重算一次十毫秒的推理延迟已经会拖慢撤单决策。序列模型的优势是能自动学出订单流的长程依赖但那部分信息通过手工构造的滚动统计量OFI 累加、状态持续时长、深度斜率的变化率大部分能被树模型捕获。真要用深度模型放在盘后做因子生成把输出当成特征喂给树模型兼顾表达力和延迟。3.4 用 DeepSeek 辅助因子解释、命名与复盘报告因子挖到几十个之后最大的成本不是计算而是理解——哪些是同一件事的不同说法、哪些在特定行情下会反向。这部分工作可以让 DeepSeek 承担输入只给统计摘要不给原始逐笔数据。from openai import OpenAI client OpenAI(api_keyYOUR_KEY, base_urlhttps://api.deepseek.com/v1) def explain_factor(name, ic, turnover, regime_ic): prompt ( f因子名{name}\n全样本IC{ic:.4f}\n日换手{turnover:.2f}\n f分状态IC{regime_ic}\n\n 请用三条要点说明1) 它最可能捕捉的微观结构机制 2) 在什么行情状态下会失效3) 可能和哪些常见因子高度共线。 只基于上述统计量推断不要编造数据。 ) r client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: prompt}], temperature0.3, max_tokens800, ) return r.choices[0].message.contenttemperature压到 0.3 是为了让解释保持稳定同一份统计量每次问出来的结论不该差别太大。max_tokens限制在 800 以内避免模型展开成长篇论述反而失去重点。接口走的是 OpenAI 兼容协议如果因子公式和逐笔数据不能出内网把模型本地部署后改base_url指向内网端点即可只把字段名和汇总统计送进模型。接入 VS Code 这类编辑器做辅助开发也同理让它读清洗函数和特征构造函数生成单元测试和边界用例比让它直接写交易逻辑安全得多。4. 用 Python 跑通一条订单流分析与市场深度预测流水线4.1 环境、数据契约与目录结构依赖清单很短pandas、numpy、lightgbm、scikit-learn加上行情接入的 SDK。目录按数据层、特征层、模型层分开mkdir -p liqmon/{raw,clean,feature,model,report} # raw 原始逐笔与快照 # clean 对齐去重后的标准表 # feature 秒级特征宽表 # model 训练产物 # report 评估与复盘数据契约先定死ts毫秒整数、symbol、sideB/S、price整数 tick、qty手、seq交易所序号。所有上游数据先映射到这套 schema 再进流程后面换数据源只改适配层。4.2 订单簿重建到 OFI 计算的最小可运行链路def run_pipeline(ticks, depth10, ofi_win60): book L2Book(depthdepth) prev_snap, rows None, [] for t in ticks.itertuples(): if t.kind order: book.on_order(t.side, t.price, t.qty) else: book.on_trade(t.side, t.price, t.qty) feat book.depth_features() if feat is None: continue cur (t.bid_px, t.bid_qty, t.ask_px, t.ask_qty) feat[ofi] ofi(prev_snap, cur) if prev_snap else 0.0 feat[ts] t.ts rows.append(feat) prev_snap cur df pd.DataFrame(rows).set_index(ts) df[ofi_cum] df[ofi].rolling(ofi_win).sum() df[depth_ratio] df[depth_bid] / (df[depth_bid] df[depth_ask]) return dfofi_win取 60 是 60 个 tick 而不是 60 秒因为逐笔事件的到达频率本身就在变化用小的时间窗反而会把流动性差的时段切得太碎。如果数据量到亿级这段循环会成为瓶颈用numba加速或者改成向量化实现。4.3 用 SQL 做秒级聚合避免 Python 端内存爆炸一天的逐笔成交在千万行量级直接在 pandas 里做重采样会吃满内存。把聚合下推到数据库-- 逐笔成交按秒聚合成主动买卖量与成交笔数 SELECT symbol, FLOOR(trade_ts / 1000) AS sec_ts, SUM(CASE WHEN side B THEN qty ELSE 0 END) AS active_buy_qty, SUM(CASE WHEN side S THEN qty ELSE 0 END) AS active_sell_qty, COUNT(*) AS trade_cnt, SUM(price * qty) / NULLIF(SUM(qty), 0) AS vwap_tick FROM tick_trade WHERE trade_ts BETWEEN :start_ms AND :end_ms GROUP BY symbol, FLOOR(trade_ts / 1000)FLOOR(trade_ts / 1000)把毫秒时间戳对齐到秒是后续所有滚动窗口的时间轴。NULLIF防止某秒零成交导致除零。聚合后数据量降到百万行以内再落到feature目录做跨表关联。4.4 训练、评估与滑点回测的三个验收指标模型训练本身没什么特别关键是验收。三个指标缺一不可第一分组 AUC——按前面识别出的行为状态分组最差那组的 AUC 也要在 0.55 以上第二深度骤降事件的召回率——把历史中深度腰斩的时刻标注出来看模型提前 30 秒能否捕捉到七成第三滑点预估误差——把模型输出的滑点预测和回测撮合的实际滑点做回归斜率接近 1 且截距小才算可用。5. 深度预测上线后最容易翻车的四个点与参数调优速查5.1 四个翻车点第一个是时间戳时区和对齐口径不统一。行情源给的是交易所本地时间撮合日志用的是服务器 UTC差 8 小时的表join 出来特征全错位而且 AUC 可能还看得过去很难发现。第二个是除权除息日的 tick 跳变。价格从前收盘跳到除权价整数 tick 的映射整体位移所有基于价格档位的特征当天全部失真。当天直接标记为不可用别硬跑。第三个是午盘和尾盘的分布漂移。开盘前 15 分钟和收盘前 10 分钟的行为模式和其他时段差异极大训练集如果做了统一采样模型在这两段会集体走偏。做法是按时段分桶采样或者把距开盘/收盘秒数直接作为特征。第四个是特征在线计算的实现和离线不一致。离线用 pandas 的rolling在线用自己写的环形缓冲边界处理稍微差一点因子值就飘。上线前用同一份历史数据跑一遍在线代码逐点比对离线结果差异超过千分之一就别上。5.2 关键参数速查表参数作用建议起点调整方向depth订单簿重建档数10做市策略提到 20ofi_winOFI 累加窗口60 tick波动大时降到 30win / stride行为窗口与步进30s / 5s延迟敏感时 stride 调到 10n_states行为状态数4收敛差时减到 3h预测跨度30s10~60s 之间实测embargo切分隔离带特征最长回看窗不小于该窗口vpin_bucketVPIN 单桶量预估日量的 1/50按标的流动性缩放调参的优先顺序建议是先定h和embargo这两个决定评估是否可信再调depth和ofi_win决定特征质量最后才动模型超参。顺序反了调出来的最优参数只是在噪声上过拟合。本文还有配套的精品资源点击获取
网站建设高端定制企业官网