Delta turnaround 15m
Mark failed breakouts on the 15 minute BTC chart where volume delta stops pushing, and plan each trade with an ATR stop and a 3R target.
A breakout that fails is one of the cleaner reversal setups: price runs past the recent high or low, the orders waiting there get taken, and the bar closes back inside the range. This script marks those bars when the volume delta agrees, and draws the trade it would take from them: entry, stop and target, each with its price.
The signal rules are tuned for BTC on Binance futures at 15 minutes. A signal needs a failed breakout, a rejection wick, normal volume and delta that stops pushing the breakout. Entry is at the signal bar’s close; the stop sits 4 ATR away and the target at 3R.
script "Delta Turnaround 15m"
// Failed breakouts on the 15 minute chart, confirmed by volume delta. After
// the idea of "Delta Turnaround Optimized 15m" by Coach NecoKronos on the original platform,
// whose code is closed: this is a reconstruction from the signals it shows,
// not the original.
//
// A bar pokes beyond the high (or low) of the bars before it and closes
// back inside: the breakout failed. The bar has to reject that extreme with
// a wick, close in the turnaround's direction, carry normal volume, and its
// delta must not keep pushing the breakout. Then the script marks the bar
// and plans a trade from its close:
//
// entry = close of the signal bar
// stop = entry -/+ 4 x ATR(14) (Wilder ATR)
// target = entry +/- 3 x the stop distance (3R)
//
// The entry, stop and target lines run until price reaches the stop or the
// target, or the next signal replaces the trade. Made for BTC on Binance
// futures at 15m like the original; other charts still run, with a note in
// the legend.
input (
input.tab(title: "Settings")
input.section(title: "Breakout")
lookback = input.int(5, title: "Breakout lookback (bars)", min: 2, max: 50, description: "The bar must poke beyond the highest high (short) or lowest low (long) of this many bars before it and close back inside.")
minPoke = input.float(0.14, title: "Poke beyond the extreme (ATR)", min: 0.0, max: 5.0, description: "How far the wick must reach past the old extreme, in ATRs.")
minWick = input.float(0.39, title: "Rejection wick (ATR)", min: 0.0, max: 5.0, description: "The wick on the breakout side, in ATRs.")
minBody = input.float(0.03, title: "Close in turnaround direction (ATR)", min: -1.0, max: 5.0, description: "The body must point the way of the trade by at least this much: a red bar for shorts, a green bar for longs.")
input.section(title: "Delta and volume")
maxPush = input.float(0.065, title: "Delta still pushing (share of volume)", min: 0.0, max: 1.0, description: "The bar's delta may lean towards the breakout by at most this share of its volume. 0.065 = up to 6.5 % net buying on a short signal.")
minVolume = input.float(0.63, title: "Volume (x 20-bar average)", min: 0.0, max: 10.0, description: "The bar's volume against the average of the 20 bars before it.")
input.section(title: "Trade")
atrLen = input.int(14, title: "ATR length", min: 1, max: 200)
stopAtr = input.float(4.0, title: "Stop (ATR)", min: 0.1, max: 20.0)
reward = input.float(3.0, title: "Target (R)", min: 0.1, max: 20.0)
showTrade = input.bool(true, title: "Show entry, stop and target")
longColor = input.color(#00e676, title: "Long")
shortColor = input.color(#ff1744, title: "Short")
input.tab(title: "About")
input.section(title: "What it does", description: "Marks failed breakouts on 15 minute BTC charts: a bar runs past the recent high or low, gets rejected and closes back inside while its volume delta stops pushing. A green dot under the bar is a long, a red dot over it a short.")
input.section(title: "The trade", description: "Entry is the close of the signal bar. The stop sits 4 ATR away, the target 3 times the stop distance. The lines end where price reaches one of them or where the next signal replaces the trade.")
input.section(title: "Signal rules", description: "Tuned for 15 minute BTC charts. Signals depend on the breakout lookback, rejection wick, volume and delta thresholds; entry, stop and target follow the rules described above.")
)
data chart = subscribe(data.ohlcv)
type Signal {
side: string
entry: float
stop: float
target: float
}
state (
prevHigh: float? = null
prevLow: float? = null
prevAvgVol: float? = null
// The trade being drawn: 1 long, -1 short, 0 none.
dir = 0
entry = 0.0
stop = 0.0
target = 0.0
since: time? = null
nextId = 0
dots = entities.markerPool(max: 500)
lines = entities.linePool(max: 1500)
)
alert longSignal = alert(title: "Delta Turnaround long", onClose: true, payload: Signal)
alert shortSignal = alert(title: "Delta Turnaround short", onClose: true, payload: Signal)
// Draws (or finishes) the current trade's three lines up to `t`.
fn drawTrade(t: time, open: bool) {
if !showTrade || since == null {
return
}
let start = since ?? t
lines.get(nextId * 3).set(start, entry, t, entry, width: 1.0, color: #ffffffb0, style: linestyle.dashed, text: str.format("Entry {0:.1}", entry))
lines.get(nextId * 3 + 1).set(start, stop, t, stop, width: 1.0, color: #ff5252, text: str.format("SL {0:.1}", stop))
lines.get(nextId * 3 + 2).set(start, target, t, target, width: 1.0, color: #26a69a, text: str.format("TP {0:.1}", target))
if !open {
dir = 0
}
}
on chart.close {
let o = chart.open
let h = chart.high
let l = chart.low
let c = chart.close
let t = chart.time
let v = chart.volume
let buy = chart.buyVolume
let sell = chart.sellVolume
// Stateful helpers run on every bar.
let atr = chart.atr(atrLen)
let hiNow = ta.highest(chart.high, lookback)
let loNow = ta.lowest(chart.low, lookback)
let avgNow = ta.sma(chart.volume, 20)
let ph = prevHigh
let pl = prevLow
let avg = prevAvgVol
prevHigh = hiNow
prevLow = loNow
prevAvgVol = avgNow
if o == null || h == null || l == null || c == null || t == null || v == null || atr == null || atr <= 0.0 {
return
}
// 1. The open trade: stop or target reached on this bar ends it.
if dir != 0 {
let stopped = dir > 0 ? l <= stop : h >= stop
let reached = dir > 0 ? h >= target : l <= target
if stopped || reached {
drawTrade(t, false)
} else {
drawTrade(t, true)
}
}
if ph == null || pl == null || avg == null || avg <= 0.0 {
return
}
let delta = (buy ?? 0.0) - (sell ?? 0.0)
let push = v > 0.0 ? delta / v : 0.0
let volOk = v >= avg * minVolume
// 2. Failed breakout above: short. Below: long.
let shortShape = h > ph && c < ph && (h - ph) >= minPoke * atr
let shortCandle = (h - math.max(o, c)) >= minWick * atr && (o - c) >= minBody * atr
let shortSig = shortShape && shortCandle && push <= maxPush && volOk
let longShape = l < pl && c > pl && (pl - l) >= minPoke * atr
let longCandle = (math.min(o, c) - l) >= minWick * atr && (c - o) >= minBody * atr
let longSig = !shortSig && longShape && longCandle && -push <= maxPush && volOk
if shortSig || longSig {
// A new signal replaces the trade on the chart.
if dir != 0 {
drawTrade(t, false)
}
nextId += 1
dir = longSig ? 1 : -1
let sign = longSig ? 1.0 : -1.0
entry = c
stop = c - sign * stopAtr * atr
target = c + sign * stopAtr * atr * reward
since = t
if longSig {
dots.get(nextId).set(t, l - 0.3 * atr, shape: shape.circle, color: longColor, size: 6.0)
longSignal.trigger(Signal { side: "long", entry: entry, stop: stop, target: target })
} else {
dots.get(nextId).set(t, h + 0.3 * atr, shape: shape.circle, color: shortColor, size: 6.0)
shortSignal.trigger(Signal { side: "short", entry: entry, stop: stop, target: target })
}
drawTrade(t, true)
}
}
on chart.update {
// The legend: the market note, and the trade in play.
let tfOk = time.tfSeconds(context.timeframe) == 900
let marketOk = context.exchange == "binancef" && context.base == "BTC"
let note = tfOk && marketOk ? "" : " · made for BTC Binance futures 15m"
let trade = dir > 0 ? str.format(" · long {0:.1}", entry) : dir < 0 ? str.format(" · short {0:.1}", entry) : ""
script.setTitle(str.format("Delta Turnaround 15m{0}{1}", trade, note))
}How it works
The failed breakout
ta.highest(chart.high, lookback) and ta.lowest(chart.low, lookback) are read on every bar and kept in state, so each bar compares itself with the bars before it. A short needs a high above the previous high and a close back below it; a long the mirror image. The poke past the old extreme, the rejection wick and the body are measured in ATRs, so the same settings work in quiet and busy markets.
The delta turnaround
chart.buyVolume - chart.sellVolume is the bar’s volume delta. On a short signal the buyers who drove the breakout must have stopped: the delta may lean to the buy side by at most 6.5 % of the bar’s volume, and anything from neutral to net selling passes. The volume has to be at least about two thirds of the 20-bar average, so empty bars do not count.
The trade
Entry is the close of the signal bar. The stop is 4 ATR(14) away (Wilder’s average, as chart.atr computes it) and the target three times that distance. One trade is drawn at a time: the lines run until a bar reaches the stop or the target, or until the next signal replaces the trade. If a bar touches both, the script counts the stop.
Alerts
longSignal and shortSignal fire on the close of a signal bar with the side, entry, stop and target as payload.
Adapting it
- Fewer signals. Raise Rejection wick or Poke beyond the extreme, or require more volume. The original fires about once every two days; the defaults here fire more often.
- Other markets. The rules are in ATRs and shares of volume, so they carry over to other coins and timeframes, but the defaults were fitted to BTC on Binance futures at 15m. The legend says so on other charts.
- Backtest it. Turn the script into a strategy: replace the lines with
strategy.entryand an exit at the stop and target. See the EMA and ATR strategy for the pattern.
Related
- VD absorption finds bars where a large delta failed to move price.
- Swing levels draws lines from a pool and ends them on a break, as this script does with its trade.