Indicator · Pine Script v6 · On the chart

CoilForge Zones [WavesUnchained]

by casoon

This indicator identifies and plots price compression zones directly on the price chart using multi-module scoring.

View on GitHub

GitHub stars9on the repository
Pine versionv6current
Lines of code486
Settings35inputs
LicenceMIT
Last update3 days ago27 Oct 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

486 lines of Pine Script v6 from indicators/market_structure/coilforge_zones/coilforge_zones_v1.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

coilforge_zones_v1.pine
//@version=6
// ============================================================================
// CoilForge Zones v1.2.2 [WavesUnchained]
// Version: 1.3.1
// Author: WavesUnchained
// Build: 2026-10-12
// ============================================================================
// Detects price compression zones using ATR, ADX, volume, impulse, and
// historical S/R context, then tracks confirmed breakouts for a configurable
// window after the zone ends.
//
// Features:
//   ✓ Multi-module compression scoring (ATR, ADX, volume, impulse, historical S/R)
//   ✓ Sensitivity presets (Strict / Balanced / Aggressive / Custom)
//   ✓ Dynamic bias detection via touch pressure and EMA structure (ADX = strength only)
//   ✓ Zone boxes with quality-based color and directional border
//   ✓ Post-zone breakout detection with configurable watch window
//   ✓ Historical pivot level reuse detection
//   ✓ Alert conditions for breakout up / down
//
// Data Contract:
//   Price:     REQUIRED   chart symbol
//   Volume:    OPTIONAL   real trade volume only — Volume Dry-Up module degrades to neutral
//   OI:        NO
//   Reference: NO
//   Verdict:   CFD-degraded
//
// ============================================================================

indicator("CoilForge Zones v1.2.2 [WavesUnchained]",
         shorttitle    = "CFZ v1",
         overlay       = true,
         max_boxes_count  = 100,
         max_labels_count = 100)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Inputs
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
groupCore = "Core"
sensitivity        = input.string("Balanced", "Sensitivity", options = ["Strict", "Balanced", "Aggressive", "Custom"], group = groupCore,
     tooltip = "Preset bundle of range/ATR limit, ADX limit, minimum score and minimum boundary retests. The minimum-retest values of the presets (Strict 3, Balanced 2, Aggressive 2) are a provisional default — not yet validated on data.")
lookback           = input.int(16,  "Zone Lookback",        minval = 8,  maxval = 100,  group = groupCore)
minZoneBars        = input.int(5,   "Minimum Zone Bars",    minval = 3,  maxval = 50,   group = groupCore)
customRangeAtrMult = input.float(4.2, "Custom: Max Range / ATR",  step = 0.1,  group = groupCore)
customAdxMax       = input.float(27.0, "Custom: Max ADX",         step = 0.5,  group = groupCore)
customMinScore     = input.int(35,  "Custom: Minimum Score", minval = 0,  maxval = 100,  group = groupCore)
customMinTouches   = input.int(2,   "Custom: Minimum Boundary Touches", minval = 2, maxval = 20, group = groupCore,
     tooltip = "Minimum retests of the range edges (upper + lower). The bar that sets each range extreme is not counted, and each side needs at least one retest. Provisional default — not yet validated on data.")
minStructEvidence  = input.int(2,   "Minimum Structure Evidence", minval = 1, maxval = 2, group = groupCore,
     tooltip = "How many structural conditions (range compression, touch structure) must agree. Lower = more permissive; neither acts as a lone hard veto.")

groupModules = "Modules"
useAtrCompression = input.bool(true, "Use ATR Compression",     group = groupModules)
useAdxFilter      = input.bool(true, "Use ADX Chop Filter",     group = groupModules)
useTouches        = input.bool(true, "Use Touch Structure",      group = groupModules)
useVolume         = input.bool(true, "Use Volume Dry-Up",        group = groupModules)
useImpulse        = input.bool(true, "Use Impulse-In Context",   group = groupModules)
useHistorical     = input.bool(true, "Use Historical S/R Reuse", group = groupModules)

groupCalc = "Calculation"
atrLen          = input.int(14,   "ATR Length",                      minval = 2,            group = groupCalc)
adxLen          = input.int(14,   "DI Length",                       minval = 2,            group = groupCalc)
adxSmooth       = input.int(14,   "ADX Smoothing",                   minval = 2,            group = groupCalc)
touchTolAtr     = input.float(0.25, "Touch Tolerance / ATR",         step = 0.05,           group = groupCalc)
volLen          = input.int(20,   "Volume MA Length",                 minval = 2,            group = groupCalc)
impulseLookback = input.int(12,   "Impulse Lookback",                 minval = 3, maxval = 100, group = groupCalc)
impulseAtrMult  = input.float(1.8, "Impulse Strength / ATR",         step = 0.1,            group = groupCalc)
histLookback    = input.int(250,  "Historical Pivot Lookback",        minval = 50, maxval = 2000, group = groupCalc)
histTolAtr      = input.float(0.5, "Historical Level Tolerance / ATR", step = 0.1,          group = groupCalc)
pivotLen        = input.int(5,    "Pivot Length",                     minval = 2, maxval = 20,  group = groupCalc)

groupVisual = "Visuals"
showOldZones = input.bool(true,  "Show Old Zones",         group = groupVisual)
oldZoneLife  = input.int(180,    "Old Zone Lifetime Bars", minval = 10, maxval = 2000, group = groupVisual)
maxOldZones  = input.int(8,      "Max Old Zones",          minval = 0,  maxval = 50,   group = groupVisual)
showLabels   = input.bool(true,  "Show Zone Label",        group = groupVisual)
showTable    = input.bool(true,  "Show Info Table",        group = groupVisual)
colorByRead  = input.bool(true,  "Color by Interpretation", group = groupVisual)
tablePos     = input.string("Top Right", "Table Position", options = ["Top Right", "Top Left", "Bottom Right", "Bottom Left"], group = groupVisual,
     tooltip = "Move the table when another indicator's dashboard already occupies the corner.")

groupBreakout = "Breakout"
breakoutWatchBars = input.int(20, "Breakout Watch Bars After Zone", minval = 1, maxval = 200, group = groupBreakout)
breakoutQuality   = input.string("Confirmed", "Breakout Quality", options = ["Early", "Confirmed", "Strict"], group = groupBreakout)
avoidContraryRead = input.bool(true, "Avoid Breakouts Against Box Read", group = groupBreakout)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Sensitivity mapping
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
rangeAtrMult = sensitivity == "Strict" ? 2.4 : sensitivity == "Balanced" ? 4.2 : sensitivity == "Aggressive" ? 5.0 : customRangeAtrMult
adxMax       = sensitivity == "Strict" ? 20.0 : sensitivity == "Balanced" ? 27.0 : sensitivity == "Aggressive" ? 32.0 : customAdxMax
minScore     = sensitivity == "Strict" ? 50 : sensitivity == "Balanced" ? 35 : sensitivity == "Aggressive" ? 25 : customMinScore
minTouches   = sensitivity == "Strict" ? 3 : sensitivity == "Balanced" ? 2 : sensitivity == "Aggressive" ? 2 : customMinTouches

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Base calculations
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
atr               = ta.atr(atrLen)
[plusDI, minusDI, adx] = ta.dmi(adxLen, adxSmooth)
rangeHigh         = ta.highest(high, lookback)
rangeLow          = ta.lowest(low,   lookback)
rangeSize         = rangeHigh - rangeLow
rangeAtrRatio     = atr > 0 ? rangeSize / atr : na
volMa             = ta.sma(volume, volLen)
volumeAvailable   = (ta.cum(nz(volume)) > 0 and syminfo.volumetype != "tick") and
     not na(volume) and volume > 0 and not na(volMa)
volDrying         = volumeAvailable and volume < volMa and ta.sma(volume, math.max(3, math.floor(volLen / 2))) < volMa
priorMove         = close - close[impulseLookback]
impulseBull       = priorMove >  atr * impulseAtrMult
impulseBear       = priorMove < -atr * impulseAtrMult
impulseDir        = impulseBull ? 1 : impulseBear ? -1 : 0
atrMa             = ta.sma(atr, lookback)
rangeOk           = rangeAtrRatio <= rangeAtrMult
adxOk             = adx <= adxMax
touchTol          = atr * touchTolAtr

// Price-structure trend basis (direction source). EMA stack on close: fast vs slow
// gives a structural up/down read independent of DI. ADX/DI is reserved as a
// strength/quality weight only, never the direction decider.
structEmaFast     = ta.ema(close, int(math.max(2, math.round(lookback / 2))))
structEmaSlow     = ta.ema(close, lookback)
structTrendUp     = structEmaFast > structEmaSlow
structTrendDown   = structEmaFast < structEmaSlow
adxStrong         = adx >= 20.0   // ADX as strength confirmation, not direction

// The bar that sets each range extreme is not a retest of it — skip it, so only
// genuine returns to the edge count as touches.
rangeHighOffset   = -ta.highestbars(high, lookback)
rangeLowOffset    = -ta.lowestbars(low, lookback)
rangeUpperTouches = 0
rangeLowerTouches = 0
for i = 0 to lookback - 1
    if i != rangeHighOffset and high[i] >= rangeHigh - touchTol
        rangeUpperTouches += 1
    if i != rangeLowOffset and low[i] <= rangeLow + touchTol
        rangeLowerTouches += 1

touchStructureOk = rangeUpperTouches > 0 and rangeLowerTouches > 0 and rangeUpperTouches + rangeLowerTouches >= minTouches

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Historical levels
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var float[] histLevels = array.new_float()
var int[]   histBars   = array.new_int()

ph = ta.pivothigh(high, pivotLen, pivotLen)
pl = ta.pivotlow(low,  pivotLen, pivotLen)

if useHistorical and not na(ph)
    array.push(histLevels, ph)
    array.push(histBars, bar_index - pivotLen)
if useHistorical and not na(pl)
    array.push(histLevels, pl)
    array.push(histBars, bar_index - pivotLen)

while array.size(histLevels) > 0 and bar_index - array.get(histBars, 0) > histLookback
    array.shift(histLevels)
    array.shift(histBars)

histNearTop    = false
histNearBottom = false
histTol        = atr * histTolAtr

// Only levels from before the current range window count as historical reuse —
// a pivot inside the window is the range's own extreme and would match itself.
histCutoffBar = bar_index - lookback + 1
if useHistorical and array.size(histLevels) > 0
    for i = 0 to array.size(histLevels) - 1
        if array.get(histBars, i) >= histCutoffBar
            continue
        lvl = array.get(histLevels, i)
        if math.abs(lvl - rangeHigh) <= histTol
            histNearTop := true
        if math.abs(lvl - rangeLow) <= histTol
            histNearBottom := true

histReuse = histNearTop or histNearBottom

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Score
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
score = 0.0

if useAtrCompression
    atrScore = rangeAtrRatio <= rangeAtrMult ? 25.0 * (1.0 - math.min(rangeAtrRatio / rangeAtrMult, 1.0)) : 0.0
    score += atrScore
else
    score += 25.0

if useAdxFilter
    adxScore = adx <= adxMax ? 15.0 * (1.0 - math.min(adx / adxMax, 1.0)) : 0.0
    score += adxScore
else
    score += 15.0

if useVolume
    score += not volumeAvailable ? 15.0 : volDrying ? 15.0 : 0.0
else
    score += 15.0

if useImpulse
    score += impulseDir != 0 ? 15.0 : 0.0
else
    score += 15.0

if useHistorical
    score += histReuse ? 10.0 : 0.0
else
    score += 10.0

// Structural evidence: each enabled module contributes a vote rather than acting
// as a lone hard veto. The zone needs enough agreeing structure plus a passing score.
structEvidence = 0
if useAtrCompression and rangeOk
    structEvidence += 1
if useTouches and touchStructureOk
    structEvidence += 1
activeStructModules = (useAtrCompression ? 1 : 0) + (useTouches ? 1 : 0)
structRequired = math.min(minStructEvidence, math.max(activeStructModules, 1))
structureOk = activeStructModules == 0 or structEvidence >= structRequired

candidate = structureOk and score >= minScore

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Active zone state
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
var bool  inZone       = false
var int   zoneStart    = na
var float zoneHigh     = na
var float zoneLow      = na
var int   upperTouches = 0
var int   lowerTouches = 0
var string zoneRead    = "No active zone"
var box   activeBox    = na
var label activeLabel  = na
var box[] oldBoxes     = array.new_box()
var int[] oldBoxEndBars = array.new_int()

var bool  armedZone  = false
var int   armedUntil = na
var float armedHigh  = na
var float armedLow   = na
var string armedRead = "No active zone"

zoneAge      = inZone ? bar_index - zoneStart + 1 : 0
startZone    = candidate and not inZone
continueZone = candidate and inZone
endZone      = not candidate and inZone

if startZone
    inZone       := true
    zoneStart    := bar_index - lookback + 1
    zoneHigh     := rangeHigh
    zoneLow      := rangeLow
    upperTouches := 0
    lowerTouches := 0
    activeBox    := box.new(
         left         = zoneStart,
         top          = zoneHigh,
         right        = bar_index,
         bottom       = zoneLow,
         bgcolor      = color.new(color.gray, 88),
         border_color = color.new(color.gray, 35),
         border_width = 1)

if continueZone
    zoneHigh := math.max(zoneHigh, rangeHigh)
    zoneLow  := math.min(zoneLow,  rangeLow)

if inZone
    if high >= zoneHigh - touchTol
        upperTouches += 1
    if low <= zoneLow + touchTol
        lowerTouches += 1

    pressureRaw = upperTouches - lowerTouches
    // Direction derives from price structure (touch pressure + EMA stack). ADX only
    // tightens/relaxes the pressure needed: a strong trend lets weaker structural
    // pressure confirm a read, but ADX never sets the direction itself.
    pressureNeeded = adxStrong ? 1 : 2
    string bias = "Neutral"
    zoneRead := "Balance / compression"
    if pressureRaw >= 3 or (pressureRaw >= pressureNeeded and structTrendUp)
        bias := "Up"
        zoneRead := "Bullish pressure"
    else if pressureRaw <= -3 or (pressureRaw <= -pressureNeeded and structTrendDown)
        bias := "Down"
        zoneRead := "Bearish pressure"
    else if impulseDir == 1
        bias := "Up / Continuation"
        zoneRead := "Bullish continuation"
    else if impulseDir == -1
        bias := "Down / Continuation"
        zoneRead := "Bearish continuation"

    color readColor = color.gray
    if zoneRead == "Bullish pressure"
        readColor := #00c853
    else if zoneRead == "Bearish pressure"
        readColor := #d50000
    else if zoneRead == "Bullish continuation"
        readColor := #66bb6a
    else if zoneRead == "Bearish continuation"
        readColor := #ef5350

    baseFillTransp = score >= 70 ? 82 : score >= 45 ? 88 : 92
    continuationRead = zoneRead == "Bullish continuation" or zoneRead == "Bearish continuation"
    fillTransp = continuationRead ? math.min(baseFillTransp + 6, 96) : baseFillTransp
    color boxColor = colorByRead ? color.new(readColor, fillTransp) : color.new(color.gray, 94)
    if not colorByRead
        if score >= 70
            boxColor := color.new(color.blue, 82)
        else if score >= 45
            boxColor := color.new(color.gray, 88)

    color borderColor = color.new(color.gray, 30)
    if colorByRead
        borderColor := color.new(readColor, 15)
    else
        if bias == "Up" or bias == "Up / Continuation"
            borderColor := color.new(#00c853, 20)
        else if bias == "Down" or bias == "Down / Continuation"
            borderColor := color.new(#d50000, 20)

    if not na(activeBox)
        box.set_right(activeBox, bar_index)
        box.set_top(activeBox, zoneHigh)
        box.set_bottom(activeBox, zoneLow)
        box.set_bgcolor(activeBox, boxColor)
        box.set_border_color(activeBox, borderColor)
        box.set_border_width(activeBox, score >= 70 ? 2 : 1)

    if showLabels
        labelText =
             "CoilForge Zone\n" +
             "Score: "       + str.tostring(math.round(score)) + "\n" +
             "Read: "        + zoneRead + "\n" +
             "Bias: "        + bias + "\n" +
             "Upper Tests: " + str.tostring(upperTouches) + "\n" +
             "Lower Tests: " + str.tostring(lowerTouches) + "\n" +
             "Hist: "        + (histNearTop ? "R " : "") + (histNearBottom ? "S" : "")
        if na(activeLabel)
            activeLabel := label.new(bar_index, zoneHigh, labelText,
                 style     = label.style_label_down,
                 textcolor = color.white,
                 color     = color.new(readColor, 15))
        else
            label.set_x(activeLabel, bar_index)
            label.set_y(activeLabel, zoneHigh)
            label.set_text(activeLabel, labelText)
            label.set_color(activeLabel, color.new(readColor, 15))

if endZone
    if zoneAge >= minZoneBars
        armedZone  := true
        armedUntil := bar_index + breakoutWatchBars
        armedHigh  := zoneHigh
        armedLow   := zoneLow
        armedRead  := zoneRead

        // Zone edges feed the historical S/R memory regardless of visuals
        if useHistorical
            array.push(histLevels, zoneHigh)
            array.push(histBars, bar_index)
            array.push(histLevels, zoneLow)
            array.push(histBars, bar_index)

        if showOldZones and not na(activeBox)
            array.push(oldBoxes, activeBox)
            array.push(oldBoxEndBars, bar_index)
        else
            if not na(activeBox)
                box.delete(activeBox)
    else
        if not na(activeBox)
            box.delete(activeBox)

    if not na(activeLabel)
        label.delete(activeLabel)

    activeBox    := na
    activeLabel  := na
    inZone       := false
    zoneStart    := na
    zoneHigh     := na
    zoneLow      := na
    upperTouches := 0
    lowerTouches := 0
    zoneRead     := "No active zone"

while array.size(oldBoxes) > maxOldZones
    old = array.shift(oldBoxes)
    array.shift(oldBoxEndBars)
    box.delete(old)

if showOldZones and array.size(oldBoxes) > 0
    for i = array.size(oldBoxes) - 1 to 0
        oldEnd = array.get(oldBoxEndBars, i)
        if bar_index - oldEnd > oldZoneLife
            oldBox = array.get(oldBoxes, i)
            box.delete(oldBox)
            array.remove(oldBoxes, i)
            array.remove(oldBoxEndBars, i)

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Breakout detection (armed zone)
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
if armedZone and bar_index > armedUntil
    armedZone  := false
    armedUntil := na
    armedHigh  := na
    armedLow   := na
    armedRead  := "No active zone"

breakoutVolumeOk = not useVolume or not volumeAvailable or volume > volMa
breakoutBuffer   = atr * (breakoutQuality == "Early" ? 0.0 : breakoutQuality == "Confirmed" ? 0.05 : 0.12)
minBodyAtr       = breakoutQuality == "Early" ? 0.0 : breakoutQuality == "Confirmed" ? 0.12 : 0.25
minClosePosition = breakoutQuality == "Early" ? 0.0 : breakoutQuality == "Confirmed" ? 0.60 : 0.70
barRange         = high - low
bodySize         = math.abs(close - open)
closePosition    = barRange > 0 ? (close - low) / barRange : 0.5
bodyOk           = atr > 0 ? bodySize >= atr * minBodyAtr : true
closeUpOk        = closePosition >= minClosePosition
closeDownOk      = closePosition <= 1.0 - minClosePosition
readUpOk         = not avoidContraryRead or (armedRead != "Bearish pressure" and armedRead != "Bearish continuation")
readDownOk       = not avoidContraryRead or (armedRead != "Bullish pressure" and armedRead != "Bullish continuation")
// Breakout direction is set by price structure (close beyond the zone edge). ADX/DI
// is no longer a direction veto here — it only flavors the read elsewhere.
breakoutUp       = armedZone and close > armedHigh + breakoutBuffer and breakoutVolumeOk and bodyOk and closeUpOk and readUpOk
breakoutDown     = armedZone and close < armedLow - breakoutBuffer and breakoutVolumeOk and bodyOk and closeDownOk and readDownOk

if breakoutUp or breakoutDown
    armedZone  := false
    armedUntil := na
    armedHigh  := na
    armedLow   := na
    armedRead  := "No active zone"

plotshape(breakoutUp,   title = "Breakout Up",   style = shape.triangleup,   location = location.belowbar, size = size.small, color = #00c853, text = "BO↑")
plotshape(breakoutDown, title = "Breakout Down",  style = shape.triangledown, location = location.abovebar, size = size.small, color = #d50000,  text = "BO↓")

bool alertsOnBarClose = input.bool(true, "Alerts only on bar close (confirmed)", group="Alerts")
bool alertOk = not alertsOnBarClose or barstate.isconfirmed
alertcondition(alertOk and (breakoutUp),   "CoilForge Breakout Up",   "CFZ · BREAKOUT UP LONG · {{ticker}} {{interval}}")
alertcondition(alertOk and (breakoutDown), "CoilForge Breakout Down",  "CFZ · BREAKOUT DOWN SHORT · {{ticker}} {{interval}}")

//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Info table
//━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
color tc = color.new(color.gray, 20)
color hd = color.new(color.gray, 90)

var table info = table.new(
     tablePos == "Top Right" ? position.top_right : tablePos == "Top Left" ? position.top_left : tablePos == "Bottom Right" ? position.bottom_right : position.bottom_left,
     2, 9,
     bgcolor      = color.new(color.white, 5),
     border_color = color.new(color.gray, 60),
     border_width = 1,
     frame_color  = color.new(color.gray, 40),
     frame_width  = 1)

if showTable and barstate.islast
    string currentRead = zoneRead
    if not inZone
        currentRead := "No active zone"

    armedStr = not armedZone ? "–" : "Armed (" + str.tostring(armedUntil - bar_index) + ") " + armedRead

    table.cell(info, 0, 0, "CoilForge Zones", text_color = tc, bgcolor = hd, text_size = size.small)
    table.cell(info, 1, 0, "v1.2.2",          text_color = tc, bgcolor = hd, text_size = size.small)
    table.cell(info, 0, 1, "Zone",      text_color = tc, text_size = size.small)
    table.cell(info, 1, 1, inZone ? "Active" : "None", text_color = tc, text_size = size.small)
    table.cell(info, 0, 2, "Score",     text_color = tc, text_size = size.small)
    table.cell(info, 1, 2, str.tostring(math.round(score)), text_color = tc, text_size = size.small)
    table.cell(info, 0, 3, "Read",      text_color = tc, text_size = size.small)
    table.cell(info, 1, 3, currentRead, text_color = tc, text_size = size.small)
    table.cell(info, 0, 4, "Range/ATR", text_color = tc, text_size = size.small)
    table.cell(info, 1, 4, str.tostring(rangeAtrRatio, "#.##"), text_color = tc, text_size = size.small)
    table.cell(info, 0, 5, "ADX",       text_color = tc, text_size = size.small)
    table.cell(info, 1, 5, str.tostring(adx, "#.##"), text_color = tc, text_size = size.small)
    table.cell(info, 0, 6, "Volume",    text_color = tc, text_size = size.small)
    table.cell(info, 1, 6, not volumeAvailable ? "Unavailable" : volDrying ? "Drying" : "Normal/High", text_color = tc, text_size = size.small)
    table.cell(info, 0, 7, "Historical", text_color = tc, text_size = size.small)
    table.cell(info, 1, 7, histReuse ? "Reused Level" : "No", text_color = tc, text_size = size.small)
    table.cell(info, 0, 8, "BO Watch",  text_color = tc, text_size = size.small)
    table.cell(info, 1, 8, armedStr,    text_color = tc, text_size = size.small)

Overview

CoilForge Zones [WavesUnchained] measures ATR compression, ADX chop levels, boundary touches, volume dry-up, and historical pivot levels to identify consolidation areas and signal potential breakouts. Key settings control sensitivity presets, lookback length, minimum touch thresholds, and display elements including boxes and info tables. The volume module degrades to neutral on instruments lacking real trade volume.

Details

Published by

casoon on GitHub

Draws

On the price chart

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

pine-scriptpinescripttechnical-analysistrading-indicatorstrading-strategiestradingview

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
SensitivitystringBalancedsensitivity
Zone Lookbackint16lookback
Minimum Zone Barsint5minZoneBars
Custom: Max Range / ATRfloat4.2customRangeAtrMult
Custom: Max ADXfloat27.0customAdxMax
Custom: Minimum Scoreint35customMinScore
Custom: Minimum Boundary Touchesint2customMinTouches
Minimum Structure Evidenceint2minStructEvidence
Use ATR CompressionbooltrueuseAtrCompression
Use ADX Chop FilterbooltrueuseAdxFilter
Use Touch StructurebooltrueuseTouches
Use Volume Dry-UpbooltrueuseVolume
Show all 35 settings
SettingTypeDefaultVariable
Use Impulse-In ContextbooltrueuseImpulse
Use Historical S/R ReusebooltrueuseHistorical
ATR Lengthint14atrLen
DI Lengthint14adxLen
ADX Smoothingint14adxSmooth
Touch Tolerance / ATRfloat0.25touchTolAtr
Volume MA Lengthint20volLen
Impulse Lookbackint12impulseLookback
Impulse Strength / ATRfloat1.8impulseAtrMult
Historical Pivot Lookbackint250histLookback
Historical Level Tolerance / ATRfloat0.5histTolAtr
Pivot Lengthint5pivotLen
Show Old ZonesbooltrueshowOldZones
Old Zone Lifetime Barsint180oldZoneLife
Max Old Zonesint8maxOldZones
Show Zone LabelbooltrueshowLabels
Show Info TablebooltrueshowTable
Color by InterpretationbooltruecolorByRead
Table PositionstringTop RighttablePos
Breakout Watch Bars After Zoneint20breakoutWatchBars
Breakout QualitystringConfirmedbreakoutQuality
Avoid Breakouts Against Box ReadbooltrueavoidContraryRead
Alerts only on bar close (confirmed)booltruealertsOnBarClose
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

Equity Trading Strategy

by AmjadSaidam
2 stars

This indicator draws a daily-anchored volume-weighted average price on the price chart and marks the first long or short reclaim of that level. A long needs a bar below the average whose high reaches it, a later close above it, and confirmation after at least

Indicator v6 VolumeSupport & resistance Repaints
Updated 5 weeks ago · MIT GitHub repository of Equity Trading Strategy

Snag - VWAP

by dearvn
257 stars

This indicator plots a custom Anchored Volume-Weighted Average Price (VWAP) directly on the price chart along with optional standard deviation bands. Users can anchor the VWAP to standard time periods like sessions, weeks, or months, or to corporate events suc

Indicator v5 Moving averagesVolatility & bands Repaints
Updated 7 weeks ago · None stated GitHub repository of Snag - VWAP

VWAP - Basic

by TradersPost
228 stars

VWAP - Basic plots an anchored Volume Weighted Average Price directly on the main price chart alongside two standard deviation bands. It calculates typical price weighted by volume, resetting the cumulative calculations at user-selected session, weekly, or mon

Indicator v6 VolumeSupport & resistance
Updated 3 months ago · None stated GitHub repository of VWAP - Basic

Donchian Channels (DC)

by mihakralj
99 stars

This indicator calculates and plots Donchian Channels directly on the price chart. It determines the highest high and lowest low over a user-selected lookback period using an optimized monotonic deque algorithm, as well as a middle basis line computed as their

Indicator v6 Volatility & bandsSupport & resistance
Updated 4 weeks ago · Apache-2.0 GitHub repository of Donchian Channels (DC)

D/W/M VWAPs

by cryptorife
75 stars

This indicator plots daily, weekly, and monthly volume-weighted average prices on the price chart, using a selectable source that defaults to hlc3. For each enabled period it also draws two standard-deviation bands around the VWAP, with multipliers defaulting

Indicator v4 VolumeVolatility & bands
Updated 3 years ago · GPL-3.0 GitHub repository of D/W/M VWAPs
47 stars

The Donchian Channels indicator plots the middle basis line as the average of the high and low over the user-defined period. It also draws the upper band as the highest high and the lower band as the lowest low across the same period on the price chart. The ma

Indicator v6 Volatility & bandsSupport & resistance
Updated 8 months ago · MIT GitHub repository of Donchian Channels (DCHANNEL)

More Breakout scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers