FlowscopeDocs

VD absorption

Detect bars where an outsized volume delta failed to move price, using thresholds that scale with recent delta and volatility.

Absorption is what happens when aggressive orders hit a wall of passive liquidity. Buyers lift the offer bar after bar, the delta is strongly positive, and yet price barely rises, because a large seller keeps refilling the ask. The same happens in reverse at a bid that will not break.

This script flags those bars. It compares each bar’s volume delta with the typical delta of recent bars, and the bar’s price move with the current ATR. When the delta is extreme but the move is small or even against the delta, the bar gets a marker and a short zone that shows where the passive side defended. A sub-pane shows the delta as a multiple of its recent average, with the threshold drawn in.

vd-absorption.fs
script "VD absorption"

input (
  avgLen = input.int(50, title: "Delta average length", min: 5, max: 500)
  deltaMult = input.float(2.5, title: "Delta threshold (x average)", min: 1.0, max: 10.0)
  atrLen = input.int(14, title: "ATR length", min: 2, max: 100)
  maxMoveAtr = input.float(0.25, title: "Max move with delta (x ATR)", min: 0.0, max: 2.0)
  zoneBars = input.int(20, title: "Zone length (bars)", min: 1, max: 200)
)

data (
  chart = subscribe(data.ohlcv)
  vd = subscribe(data.vd)
)

type AbsorptionPayload {
  side: string
  delta: float
  price: float
}

state (
  zoneCount = 0
  zones = entities.boxPool(max: 300)
)

pane ratioPane = pane(title: "Delta / average", height: 0.2)

plot (
  ratio = plot.histogram(title: "Delta ratio", on: ratioPane)
  upper = plot.line(title: "Upper threshold", color: color.gray, style: linestyle.dashed, on: ratioPane)
  lower = plot.line(title: "Lower threshold", color: color.gray, style: linestyle.dashed, on: ratioPane)
  buyAbsorbed = plot.marker(title: "Buying absorbed", color: color.red, shape: shape.down, size: 4.0)
  sellAbsorbed = plot.marker(title: "Selling absorbed", color: color.green, shape: shape.up, size: 4.0)
)

alert absorbed = alert(title: "Absorption", onClose: true, payload: AbsorptionPayload)

on chart.close {
  let d = vd.close
  let absDelta = d != null ? math.abs(d) : null
  let avgAbs = ta.sma(absDelta, avgLen)
  let atr = chart.atr(atrLen)

  let o = chart.open
  let h = chart.high
  let l = chart.low
  let c = chart.close
  let t = chart.time
  upper.plot(deltaMult)
  lower.plot(0.0 - deltaMult)
  if d != null && avgAbs != null && atr != null && o != null &&
    h != null && l != null && c != null && t != null && avgAbs > 0.0 {
    let r = d / avgAbs
    let move = c - o
    let limit = maxMoveAtr * atr

    // Heavy buying, but the close did not get far above the open.
    let buysAbsorbed = r >= deltaMult && move <= limit
    // Heavy selling, but the close did not get far below the open.
    let sellsAbsorbed = r <= -deltaMult && move >= -limit

    let shade = buysAbsorbed || sellsAbsorbed ? color.amber : (d >= 0.0 ? color.green : color.red)
    ratio.plot(r, color: shade)
    buyAbsorbed.plot(buysAbsorbed ? h + atr * 0.3 : null)
    sellAbsorbed.plot(sellsAbsorbed ? l - atr * 0.3 : null)

    if buysAbsorbed || sellsAbsorbed {
      zoneCount += 1
      let tone = buysAbsorbed ? color.red : color.green
      // The defended side: the upper half for absorbed buying, the lower half for absorbed selling.
      let mid = (h + l) / 2.0
      let top = buysAbsorbed ? h : mid
      let bottom = buysAbsorbed ? mid : l
      zones.get(zoneCount).set(t, top, t + time.bars(zoneBars), bottom, color: color.withAlpha(tone, 40), borderWidth: 1.0, borderColor: color.withAlpha(tone, 160))
      absorbed.trigger(AbsorptionPayload {
        side: buysAbsorbed ? "buying absorbed" : "selling absorbed",
        delta: d,
        price: c,
      })
    }
  }
}

How it works

Delta from the intrabar path

data.vd describes how the bar’s delta travelled from zero to its final value: vd.high is the most positive delta reached inside the bar, vd.low the most negative, and vd.close the delta at the close. Absorption is about the net result, so the script reads vd.close. Because the subscription follows the chart timeframe, vd.close in on chart.close belongs to the same confirmed bar as the candle.

Scale-free thresholds

A fixed delta threshold would fire constantly on BTC during the US open and never on a quiet weekend. The script measures delta against its own recent average and price moves against ATR:

Flowscope Script
let r = d / avgAbs
let move = c - o
let limit = maxMoveAtr * atr

r reads as “this bar’s delta was 3.1 times a normal bar”. limit is the largest move in the direction of the delta that still counts as “did not move”. Both ta.sma and chart.atr are called before any branch, on every bar, so they warm up and stay in step. chart.atr is a method of the candle subscription, because it needs high, low and close.

Classifying the bar

Buying is absorbed when the ratio is above the threshold and the close stayed within limit above the open, or fell. Selling is absorbed in the mirror case. A bar that matches is coloured amber in the ratio pane, so you can scan the pane for clusters.

Zones

Each absorption bar gets a box that starts at the bar and extends zoneBars bars to the right: time.bars(n) is the duration of n chart bars.

The box covers the half of the bar where the passive side defended: the upper half for absorbed buying (a seller sat above), the lower half for absorbed selling. Boxes are keyed by a counter. The pool keeps up to 300 boxes, and the oldest are recycled after that.

Adapting it

  • Use the intrabar extreme. Replace vd.close with vd.high for buying and vd.low for selling to catch bars where a burst of aggression was absorbed and then faded before the close.
  • Big traders only. Sum the delta of large prints from a trades subscription, as in size-band delta, and use it instead of vd.close. Absorption of large market orders is usually the more meaningful signal.
  • Confirm with the book. Add book = subscribe(data.book) and require book.depth(0.25) to show more resting value on the defended side (askValue above bidValue for absorbed buying). The book is there for the time the chart has recorded it.
  • Aggregated market. On an aggregated market, delta from every venue is summed, so one venue’s taker flow cannot hide absorption on another.