Design9 Oct 2026cvd_absorption_study.md

H1 — Price Absorption: Design & Repository Assessment

Status: frozen design — awaiting go-ahead before implementation.

Question owner: (you)

Scope: H1 only (price absorption). H2–H6 are not implemented here.

Related: reports/spot_cvd_divergence_study.md (completed negative result), reports/spot_cvd_divergence_results.md.


Part A — Repository assessment

A.1 What already exists and is reusable

Data layer (src/quant_lab/data/)

Feature layer (src/quant_lab/features/)

Analytics layer (src/quant_lab/analytics/)

Reporting / execution

A.2 What does not exist (must be built)

A.3 Data coverage (the binding constraint)

Measured from data/normalized/binance (this repo):

dataset coverage
perp 1m bars 100 symbols, ~2026-01…2026-09. Only BTCUSDT spans 2024-01…2026-09; ETHUSDT partial
spot 1m bars 100 symbols, 2026; BTCUSDT 2025-01…2026-09
funding 100 symbols (2026)
open interest 20 symbols so far (backfill job running)
1d bars BTC/ETH/ZEC 2025-01…2026-09

Implication. Any H1 test on the current universe is single-regime, in-sample 2026 and must be labelled exploratory — the same limitation called out in the divergence study. The only genuine multi-regime cross-check is BTCUSDT (2024–2026), and to a lesser extent ETHUSDT. This is stated in every pass/fail claim; we will not assert generality across regimes we did not observe.

A.4 Data caveats carried into H1


Part B — Frozen H1 specification

B.1 Hypothesis and economic rationale

Question. Does the effectiveness of aggressive flow in moving price carry information about subsequent price behaviour?

H1. When aggressive selling produces unusually little downward price movement (absorption), price subsequently outperforms comparable strong-selling events. Symmetric: absorbed aggressive buying (little upward movement) precedes underperformance.

Rationale (a proxy, not a mechanism). If large aggressive flow moves price much less than a trailing model predicts, resting/passive liquidity likely opposed it. If that passive side is persistent, price should later move against the aggressor. Alternative explanation: low impact simply means low-information flow, which may mean-revert. H1 tests whether the residual is informative, net of volatility, trend and flow magnitude.

Directional definition (fixed).

B.2 Event definitions and formulas (all causal, trailing only)

Per symbol, on 1h bars (resampled from 1m):

qd_t      = 2 * taker_buy_quote_volume - quote_volume        # signed taker flow, USDT
r_t       = log(close_t / close_{t-1})                        # bar log return
adv_t     = mean(quote_volume, 20)  (trailing, min_periods=20)  # activity scale
f_t       = qd_t / adv_t             (guard: null if adv_t == 0)  # normalized flow

Expected impact (regression-based, not a naive ratio). A rolling OLS of the bar return on normalized flow over a trailing window W (primary W = 720 ≈ 30d), intercept included, using only bars ≤ t:

r_i ≈ alpha_t + beta_t * f_i          for i in (t-W, t]
rhat_t = alpha_t + beta_t * f_t       # expected same-bar impact
s_t    = r_t - rhat_t                 # impact residual (signed surprise)

This replaces the unstable r/qd ratio with a conditional estimate, per the brief. Bars where the denominator would explode are excluded, not divided.

Absorption score (the signal):

A_t = -sign(f_t) * s_t                     # >0 = aggressor absorbed
A_t = A_t / std_W(A)   (trailing std)      # scale-free z-score, per symbol

Strong-flow event: |f_t| ≥ q90(|f|) where q90 is the trailing 90th percentile of |f| over W (self-normalising; no absolute threshold). Primary tests are within strong-flow events; A_t is also tested continuously.

Execution delay. Signals are evaluated at the close of t; all outcomes begin at t+1.

B.3 Required input data

Perp 1m klines for the universe (already on disk for 2026). Columns used: timestamp, open, high, low, close, volume, quote_volume, taker_buy_volume, taker_buy_quote_volume. No spot, funding or OI is required for H1's primary test; funding/OI/regimes are controls, so partial OI coverage (20 symbols) limits only the robustness slice, not the primary test.

B.4 Baselines and matched controls

  1. Price-action-only baseline — strong-flow events with no absorption conditioning: unconditional oriented forward return.
  2. Flow-only baseline — condition only on |f_t| (and sign(f_t)), ignoring the impact residual. If absorption adds nothing over “big flow”, H1 fails.
  3. Matched controls — each absorbed event is compared with non-absorbed strong-flow events matched on trailing buckets of: realized vol, trailing return (trend), |f_t|, and regime label. Pairwise within-cell difference is reported alongside the pooled view.

B.5 Primary outcome and horizon

B.6 Confounders and leakage

B.7 Statistical evaluation & cost assumptions

B.8 Pass/fail criteria (preregistered)

Declare an edge iff ALL hold on the test period:

# criterion threshold
P1 Pooled rank IC(A_t, y_t) > 0.02, 95% block-bootstrap CI excludes 0
P2 Sign-stable design IC and test IC both > 0
P3 Matched-control spread (absorbed − control, 4h) > 12 bps, correct sign, CI excludes 0
P4 Survives controls A_t coefficient CI excludes 0, > 0
P5 Monotone quintiles Spearman(quintile idx, mean y_t) > 0.7
P6 Not single-name / mega-cap holds dropping any one symbol; holds ex-BTC/ETH
P7 Horizon-consistent > 0 at 4h and at ≥1 of {1h, 24h}

Anything short of all seven → H0 (no edge). Thresholds frozen before results. No parameter is chosen on the test period.

B.9 Feasibility with existing data

Yes. 1m perp bars (2026) → 1h via resample_bars; rolling OLS and trailing quantiles in Polars/NumPy; estimators reuse analytics/divergence.py. No new data download and no tick data required. The binding limitation is regime breadth (2026 only), which forces the exploratory label.


Part C — Frozen primary specification

element primary robustness
universe crypto-only top-10 perps (configs/divergence_universe.json) top-30, ex-BTC/ETH
timeframe 1h (from 1m) 15m, 4h
flow scaling f = qd / ADV(quote, 20) ADV 60; base-unit flow
impact model rolling OLS r ~ f, W = 720 W ∈ {240, 720, 1440}; add vol term
event ` f
signal A = −sign(f)·s, z-scored rank-transformed
horizon 4h 15m, 1h, 24h, 3d
design/test design 2026-01…04 / test 2026-05…09 walk-forward months
cost 12 bps round-trip 24 bps

C.1 Implementation plan (after approval)


Part D — Explicit non-goals

← All reports · source (.md)