← 返回文章列表
August 13, 2026
5 分钟阅读

Updating the Volume Curve Intraday: Does Adaptive Forecasting Actually Help?

Updating the Volume Curve Intraday: Does Adaptive Forecasting Actually Help?
#microstructure
#liquidity
#execution
#prediction
#order-book

TWAP、VWAP 和 POV 中,我们在 90 天的 BTCUSDT L2 重放中对所有三个调度程序进行了正面交锋,并得出了一个故意留下的结论:**在加密货币中,交易量曲线是管道中最薄弱的环节,而不是切片。**那篇文章故意提供了简单版本 - 基于中位数的、周内 × 日内时间曲线,每周重新调整,在交易过程中保持静态一天。研究发现,VWAP 相对于 TWAP 的全部优势在于预测质量:以已实现的成交量曲线误差为条件,VWAP 在最佳预测的三分之一天数上比 TWAP 领先大约 4 个基点,而在最差的三分之一天数上,差距就变成了噪音。

这个结果意味着一个具体的、可证伪的后续行动。如果 VWAP 仅在曲线恰好正确的日子里获胜,那么“随着一天的展开而自我修正”的预测者应该将一些糟糕的 1/3 日子转化为好的日子。或者它不应该——而这篇文章的有趣版本就是它不这样做,因为加密货币中不好的三分位数是级联日和新闻日,而这些天正是观察到的前两个小时的交易量对接下来两个小时的信息最少的日子。

本文实现了日内更新程序,针对相同的工具运行它,并报告 IS 增量与静态曲线的关系 - 包括分布和最终四分位数,而不仅仅是平均值。

测试内容

根据观察到的交易活动预先更新自适应交易量曲线

静态曲线是先验曲线:每个桶的预期体积分数的固定向量 u={u1,,uB}u = \{u_1, \ldots, u_B\},离线估计。自适应版本将其视为更新之前的内容。在 cc 存储桶过去并且您观察到实际卷 V1,,VcV_1, \ldots, V_c 后,静态曲线会丢弃两条新信息:

  1. A 级别估计。 如果前面说桶 1..c1..c 应该携带当天的 icui\sum_{i \le c} u_i 部分,并且它们携带 icVi\sum_{i\le c} V_i 单位,则隐含的全天总数为 V^=icVi/icui\hat{V} = \sum_{i \le c} V_i \big/ \sum_{i \le c} u_i。这是一个今天是沉重的一天/今天是一个死日信号,它可以从第一个桶中获得。
  2. 形状修正。 如果已过去的桶的实际形状系统地偏离先前的形状,则剩余的桶也可能会偏离——但前提是日内成交量曲线误差在当天内自相关,这本身就是一个经验问题,而不是我们可以免费做出的假设。

下面的实现完全使用(1)和根本不使用(2):它将未触及的先前尾部重新规范化为更新的总数。这是保守的版本,也是正确的第一个实验,因为如果纯粹的关卡更新已经捕获了大部分可用的增量,那么形状机制就会变得不合理的复杂性。

import numpy as np
from typing import Optional


class AdaptiveVolumePredictor:
    """
    Bayesian-style adaptive volume profile predictor.

    Combines a prior (the offline day-of-week x time-of-day curve)
    with volume observed so far today to produce an updated forecast
    for the remaining buckets.
    """

    def __init__(self, historical_profiles: np.ndarray):
        """
        Args:
            historical_profiles: shape (n_days, n_buckets), each row sums to 1.0.
                Use the median-based, day-of-week-conditioned curve from the
                TWAP/VWAP/POV article -- a pooled mean curve is misspecified
                and will make the adaptive version look better than it is by
                giving it a weaker baseline to beat.
        """
        self.prior_profile = np.median(historical_profiles, axis=0)
        self.prior_profile /= self.prior_profile.sum()
        self.prior_iqr = np.subtract(*np.percentile(historical_profiles, [75, 25], axis=0))
        self.n_buckets = len(self.prior_profile)

    def predict(
        self,
        observed_volumes: np.ndarray,
        current_bucket: int,
        total_volume_estimate: Optional[float] = None,
    ) -> np.ndarray:
        """
        Predict absolute volume for every bucket: realized values for elapsed
        buckets, forecasts for the remainder.

        Args:
            observed_volumes: actual volumes in buckets 0..current_bucket-1
            current_bucket: index of the current bucket (0-based)
            total_volume_estimate: external ADV estimate (optional prior on level)
        """
        profile = np.zeros(self.n_buckets)

        if current_bucket == 0:
            base = total_volume_estimate if total_volume_estimate else 1.0
            return self.prior_profile * base

        profile[:current_bucket] = observed_volumes[:current_bucket]
        observed_total = observed_volumes[:current_bucket].sum()

        expected_fraction_so_far = self.prior_profile[:current_bucket].sum()
        if expected_fraction_so_far > 0.01:
            implied_total = observed_total / expected_fraction_so_far
        else:
            implied_total = observed_total * self.n_buckets

        if total_volume_estimate:
            w_obs = expected_fraction_so_far
            implied_total = w_obs * implied_total + (1 - w_obs) * total_volume_estimate

        remaining_prior = self.prior_profile[current_bucket:]
        remaining_sum = remaining_prior.sum()
        if remaining_sum > 0:
            remaining_volume = max(0.0, implied_total - observed_total)
            profile[current_bucket:] = remaining_prior / remaining_sum * remaining_volume

        return profile

该代码中的两个细节进行了实验。先验的是以星期几为条件的中值曲线,而不是汇总平均值——为什么这在一个清算级联可以在十分钟内达到一天交易量 15% 的市场中很重要,请参阅 VWAP 文章 ](/en/blog/post/twap-vwap-pov-execution-algorithms) 的[交易量曲线部分。并且级别更新向外部 ADV 估计收缩,权重等于经过的先验分数,因为在第一个存储桶 implied_total 中是一个观察值除以接近零的数字。一个未缩水的更新程序恰恰在剩下最多一天的时间来完成最糟糕的工作。

验证工具

自适应预测研究工具中的并行执行路径

TWAP/VWAP/POV 运行相同,因此这些数字是逐行比较的:

  • 工具/数据: BTCUSDT 永久,90 天的 L2 重放(前 20 个级别,100 毫秒)加上交易磁带。全程采用 UTC 时间;资金时间戳已标记。
  • 母订单: 500,水平 T=4T = 4h,大小为追踪 30 天 ADV 的 0.75%,买方,决策价格 = 开始时的中间价。与已发布的运行相同的随机开始时间。
  • 武器: (A) 静态每周改装曲线上的 VWAP — 已发布的基线; (B) VWAP 与日内更新相同; (C) TWAP 作为底线。
  • 指标: 以基点为单位的决策价格、费用、与到达的 IS — 报告为平均值、中位数、标准差、第 95 个百分位数以及每个家长最后四分位数的 IS。 VWAP 滑点仅作为诊断记录,绝不作为记分板; VWAP 文章的基准部分 显示了一个可行的案例,其中 VWAP 滑点和到达是以相反的顺序对两种算法进行排名。完整的 IS 分解,包括机会成本项,遵循实施不足和 TCA

结果

它是否能在需要的场景中提供帮助?

标题均值是这里最不有趣的数字。已发表的结果表明,VWAP-TWAP 差距在已实现的交易量曲线误差中是单调的,因此自适应臂必须以相同的方式进行评分:根据静态预测与已实现的存储量之间的 L1L_1 距离将 90 天划分为三分位数,然后报告每个三分位数内的自适应负静态 IS 增量。

这两个结果都是可发布的,但它们的含义相反:

  • Delta 集中在良好的三分位。 更新程序正在完善已经很容易的日子。对成本分配的净影响是表面性的;尾部——这是硬视野阿尔法驱动的父母实际支付的费用——没有改变。这意味着日内更新程序并不能解决最薄弱的环节,形状修正和跨资产路线是下一步需要关注的地方。
  • Delta 集中在糟糕的三分位。 更新程序正在执行其构建目的的工作:捕获静态曲线最错误的级联和新闻日。这是证明执行循环中添加状态的合理性的结果。

负面结果是预期的结果,值得明确报告。使加密货币交易量曲线变得困难的机制——级联是“制度突破”,而不是水平转移——正是击败水平更新预测者的机制:当经过的桶告诉你今天很重时,最重要的部分可能已经结束了。请参阅诚实的否定,了解此博客为何发布这些内容。

盘口斜率:它增加了什么?

摘要限价订单簿深度景观和流动性斜率

草案功能集中的一项功能未在本博客的其他地方介绍:书坡度,当您远离触摸时累积深度的累积速度。将 CumVol(d)=α+βd\text{CumVol}(d) = \alpha + \beta d 安装在顶部 KK 水平面上;大 β\beta 意味着深度集中在触摸处,小 β\beta 意味着深度分布在书本上。

def book_slope(prices: np.ndarray, volumes: np.ndarray, mid: float) -> float:
    """Slope of cumulative depth vs. distance from mid. One side only."""
    distances = np.abs(prices - mid)
    return float(np.polyfit(distances, np.cumsum(volumes), 1)[0])

此声明唯一值得提出的版本是经过衡量的版本:将 book_slope 添加到现有功能集是否会使数量预测或生成的 IS 超出 AR/EWMA 基线? 传播建模文章 设定了标准 - 报告技能高于具有规定范围的琐碎基线,因为这些系列以持久性为主,标题 R² 主要测量自相关。

本文故意不重新推导的内容

有界定量管道内的重点研究模块

下面的所有内容已经在博客上进行了更深入的介绍,在这里重新解释它只会创建一个较弱的第二个版本:

  • 流动性测量。 Roll 的估计器(带有符号根修复和价格单位与回报陷阱)和 Kyle 的 lambda:使用机器学习进行传播建模。 Kyle 的 lambda 也在真实的 Binance aggTrades 上进行了校准,作为 Almgren-Chriss.照本宣科扫成本:【滑点和成本模型](/en/blog/post/slippage-cost-models-backtest)。
  • 订单簿功能。 加权中间(不是 Stoikov 的微观价格 - 这是鞅调整的)、多级账簿不平衡、OFI 和完整的 LOB 功能分类:DeepLOB传播建模
  • 日内形态。 对于没有收盘的市场来说,股票 U 形是错误的模型;取代它的加密货币会话/资金/每周结构,加上星期 × 一天中的时间网格,位于 VWAP 文章 .循环时钟编码位于传播文章的 功能表中。
  • 预定事件。 Deribit 08:00 UTC 到期,美国宏观时间 12:30/14:00 UTC,CME 结算 — 将它们视为虚拟货币,而不是让它们污染基线曲线;具体的加密日历在VWAP文章中。上面的自适应更新程序“不是”事件处理程序,并且不应被要求成为事件处理程序。
  • 模型。 使用清除的、禁运的前向 CV 进行梯度提升(普通 TimeSeriesSplit 在重叠的前向窗口目标上泄漏)位于 spread modelling; CNN-LSTM 系列、40 个特征/10 级输入张量、LOBFrame 和复制危机讨论位于 DeepLOB.1] 中。特别注意,级别轴不得在循环层之前折叠。
  • 子订单决策。 被动与激进是计算出的盈亏平衡 p=δ/(Π+δ)p^* = \delta/(\Pi + \delta),而不是硬编码的价差阈值:子订单执行策略。参与率目标是内生的——你自己的填充打印在磁带上——并且修正在VWAP文章中​​。
  • 场地碎片。 crypto 中的智能订单路由,每个场地 TCA 记分卡在 实现不足 ] 中。
  • 生产延迟。 DeepLOB 的生产部分;请注意,传播的文章已经符合推理时间的要求——2000 轮的 LightGBM 从 Python 中可以在“数十微秒”内进行预测,仅使用编译的预测器即可预测个位数。

对抗动态值得用一段而不是一节来描述。显示的深度部分是执行性的,并且检测机制——取消率、墙建立速度、价格接近时的行为、分层——在队列位置和墙分析中使用基于PIQ的具体方法来涵盖。本文可以添加的唯一增量是将它们视为“预测者输入”:取消交易比率、触摸时的平均休息时间以及出现后 100 毫秒内取消的深度分数,与经过的桶信号一起馈送到成交量模型。他们是否在现有功能集上增加了技能是一个开放的衡量标准,而不是一个声明。

## 结论

从不确定性解析到测量执行结果的证据路径

所测试的声明范围很窄,博客已经设定了伪造它的条件:VWAP 文章 证实,VWAP 的优势完全在于预测质量,并且预测在成本最高的日子里失败。日内更新程序要么修复这个问题,要么不修复这个问题,答案是 500 个父级重播的坏三分位数中的一个数字。

本文不会做的是将 bps 数据附加到生产系统,而背后没有重播。 “经过精心调整的流动性预测可以节省 2-5 个基点,夏普值为 1.5 和 2.0 之间的差异”这种形式的主张正是 泄气夏普和多重测试 存在的目的是要拆除的类型:无源、无条件、且从不伴随分散。这里重要的数字是最差三分之一天数的最后四分位数,它要么被测量,要么不被报告。

免责声明:本文提供的信息仅用于教育和参考目的,不构成财务、投资或交易建议。加密货币交易涉及重大损失风险。

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

紧跟市场步伐

订阅我们的时事通讯,获取独家 AI 交易见解、市场分析和平台更新。

我们尊重您的隐私。您可以随时退订。