Indicator · Pine Script v6 · On the chart

Open Source Fractal

by cantolab

This indicator implements the Fractal Model concept to plot market structure shifts, liquidity sweeps, and Change in State of Delivery (CISD) levels.

View on GitHub

GitHub stars183on the repository
Pine versionv6current
Lines of code915
Settings40inputs
LicenceMIT
Last update2 months ago25 Aug 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

915 lines of Pine Script v6 from openSourceFractal.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

openSourceFractal.pine
//@version=6
indicator("openSourceFractal", overlay = true, max_boxes_count = 500, max_lines_count = 500, max_labels_count = 500)

//#region [INPUTS]

grpModel   = 'General Settings'
showAlerts = input.bool(true, 'Alerts?', group = grpModel, inline = 'alerts', tooltip = "To turn on the alerts, follow these steps:\n\n1) Make sure that the 'Alerts?' toggle is ON.\n2) Find, and click on, the three dots next to the indicator title on the top left of your chart, or right-click on the indicator on the chart.\n3) Click 'Add Alert on Fractal Model [Pro+]'\n4) Choose the Alert Name, if you wish, and confirm by clicking 'Create'")
showEarlyCisdAlerts = input.bool(false, 'Early CISD Alerts?', group = grpModel, inline = 'alerts', tooltip = "The global 'Alerts?' toggle above acts as a master switch. If it is OFF, Early CISD alerts will not fire even if this toggle is ON.")
showHistory = input.int(1, 'History', minval = 0, maxval = 40, group = grpModel, tooltip = 'Number of past setups (valid or invalidated) to keep drawn on the chart. 0 = unlimited.')
biasFilter = input.string('Neutral', 'Bias', options = ['Neutral', 'Bullish', 'Bearish'], group = grpModel)
modelType  = input.string('Auto', 'Fractal', options = ['Custom', 'Auto', '1m-15m', '3m-30m', '5m-1H', '15m-4H', '1H-1D', '4H-1W', '1D-1W'], group = grpModel, inline = 'fractal')
showEarlyCisd = input.bool(true, 'Early CISD', group = grpModel, inline = 'fractal')
customLTF  = input.timeframe('5',  'Custom LTF                      ', group = grpModel, inline = 'custom', active = modelType == 'Custom')
customHTF  = input.timeframe('60', '', group = grpModel, inline = 'custom', tooltip = 'Only active when Alignment is set to Custom', active = modelType == 'Custom')

grpCandles     = 'HTF Candles'
htfCandleCount = input.int(4, 'HTF Candles', minval = 1, maxval = 10, group = grpCandles, inline = 'count')
hideHtf        = input.bool(false, 'Hide?', group = grpCandles, inline = 'count', tooltip = 'Hide HTF candles preview')
candleOffsetIn = input.int(0, 'Offset', minval = 0, group = grpCandles)

bullBody   = input.color(color.new(color.green, 0), 'Body', group = grpCandles, inline = 'body')
bearBody   = input.color(color.new(#000000, 0), '', group = grpCandles, inline = 'body')
borderBull = input.color(color.new(#000000, 0), 'Borders', group = grpCandles, inline = 'border')
borderBear = input.color(color.new(#000000, 0), '', group = grpCandles, inline = 'border')
wickBull   = input.color(color.new(#000000, 0), 'Wick', group = grpCandles, inline = 'wick')
wickBear   = input.color(color.new(#000000, 0), '', group = grpCandles, inline = 'wick')

showHtfOpen  = input.bool(true, 'HTF Open', group = grpCandles, inline = 'htfopen')
htfOpenColor = input.color(color.new(#1a1a1a, 30), '', group = grpCandles, inline = 'htfopen')
htfOpenStyle = input.string('Solid', '', options = ['Solid', 'Dashed', 'Dotted'], group = grpCandles, inline = 'htfopen')
htfOpenWidth = input.int(1, '', minval = 1, group = grpCandles, inline = 'htfopen')

showVlines = input.bool(true, 'O/C Time', group = grpCandles, inline = 'vline')
lineColor  = input.color(color.new(color.gray, 60), '', group = grpCandles, inline = 'vline')
lineStyle  = input.string('Solid', '', options = ['Solid', 'Dashed', 'Dotted'], group = grpCandles, inline = 'vline')
lineWidth  = input.int(1, '', minval = 1, group = grpCandles, inline = 'vline')

showLH  = input.bool(false, 'L/H Lines', group = grpCandles, inline = 'lh')
lhColor = input.color(color.new(#1a1a1a, 30), '', group = grpCandles, inline = 'lh')
lhStyle = input.string('Solid', '', options = ['Solid', 'Dashed', 'Dotted'], group = grpCandles, inline = 'lh')
lhWidth = input.int(1, '', minval = 1, group = grpCandles, inline = 'lh')

grpVisuals = 'Model Style'
showLabels = input.bool(true, 'Labels?', group = grpVisuals, inline = 'lbl')
labelSize  = input.string('Small', '', group = grpVisuals, inline = 'lbl', options = ['Tiny', 'Small', 'Normal', 'Large', 'Huge'], active = showLabels)
showSweep  = input.bool(false, 'Candle 1 Sweep', group = grpVisuals, inline = 'sweep')
sweepColor = input.color(color.new(#000000, 0), '', group = grpVisuals, inline = 'sweep')
sweepStyle = input.string('Solid', '', options = ['Solid', 'Dashed', 'Dotted'], group = grpVisuals, inline = 'sweep')
sweepWidth = input.int(1, '', minval = 1, group = grpVisuals, inline = 'sweep')

earlyCisdColor = input.color(color.new(color.blue, 0), 'Early CISD', group = grpVisuals, inline = 'earlycisd', active = showEarlyCisd)
earlyCisdStyle = input.string('Dashed', '', options = ['Solid', 'Dashed', 'Dotted'], group = grpVisuals, inline = 'earlycisd', active = showEarlyCisd)
earlyCisdWidth = input.int(1, '', minval = 1, group = grpVisuals, inline = 'earlycisd', active = showEarlyCisd)

showBullCisd  = input.bool(true, 'Bullish CISD', group = grpVisuals, inline = 'bullcisd')
cisdBullColor = input.color(color.new(color.blue, 0), '', group = grpVisuals, inline = 'bullcisd')
cisdBullStyle = input.string('Solid', '', options = ['Solid', 'Dashed', 'Dotted'], group = grpVisuals, inline = 'bullcisd', active = showBullCisd)
cisdBullWidth = input.int(1, '', minval = 1, group = grpVisuals, inline = 'bullcisd', active = showBullCisd)

showBearCisd  = input.bool(true, 'Bearish CISD', group = grpVisuals, inline = 'bearcisd')
cisdBearColor = input.color(color.new(color.blue, 0), '', group = grpVisuals, inline = 'bearcisd')
cisdBearStyle = input.string('Solid', '', options = ['Solid', 'Dashed', 'Dotted'], group = grpVisuals, inline = 'bearcisd', active = showBearCisd)
cisdBearWidth = input.int(1, '', minval = 1, group = grpVisuals, inline = 'bearcisd', active = showBearCisd)

showEQ  = input.bool(true, 'Candle Equilibrium', group = grpVisuals, inline = 'eq')
eqColor = input.color(color.new(#000000, 0), '', group = grpVisuals, inline = 'eq')
eqStyle = input.string('Dotted', '', options = ['Solid', 'Dashed', 'Dotted'], group = grpVisuals, inline = 'eq')
eqWidth = input.int(1, '', minval = 1, group = grpVisuals, inline = 'eq')

showTSpot      = input.bool(true, 'T-Spot Box', group = grpVisuals, inline = 'tspot')
tSpotBullColor = input.color(color.new(color.green, 80), '', group = grpVisuals, inline = 'tspot')
tSpotBearColor = input.color(color.new(color.gray, 80), '', group = grpVisuals, inline = 'tspot')

grpStdDev   = 'Deviations'
showStdDev  = input.bool(false, 'Enable Projections?', group = grpStdDev, inline = 'stdb')
stdDevInput = input.string('-1,-2,-3', '', group = grpStdDev, inline = 'stdb', active = showStdDev)
stdDevCalc  = input.string('Wick', 'Method', options = ['Wick', 'Body'], group = grpStdDev, inline = 'stdb', active = showStdDev)
stdDevColor = input.color(color.new(#000000, 0), '', group = grpStdDev, inline = 'stdc', active = showStdDev)
stdDevStyle = input.string('Solid', '', options = ['Solid', 'Dashed', 'Dotted'], group = grpStdDev, inline = 'stdc', active = showStdDev)
stdDevWidth = input.int(1, '', minval = 1, group = grpStdDev, inline = 'stdc', active = showStdDev)

grpDash  = 'Dashboard'
boolTable = input.bool(true, 'Show Dashboard', group = grpDash)
tableVert = input.string('Top', '', options = ['Top', 'Middle', 'Bottom'], inline = 'loc', group = grpDash)
tableHorz = input.string('Right', '', options = ['Left', 'Center', 'Right'], inline = 'loc', group = grpDash)
dashTextColor = input.color(color.new(#131722, 0), 'Text', group = grpDash, inline = 'color')
dashBgColor   = input.color(color.new(#ffffff, 0), 'Background', group = grpDash, inline = 'color')

//#endregion

//#region [CONSTANTS AND STATE]

requireCisdToShow = true
candleSpacing = 3
noColor       = color.new(#000000, 100)

type HtfCandle
    float o
    float h
    float l
    float c
    int   hBar
    int   lBar
    box   body
    line  wickUp
    line  wickDown
    line  eqLine
    int   midX
    line  sweepLine   = na
    bool  triggerBuy  = false
    bool  triggerSell = false

type PeriodState
    float prevHigh        = na
    float prevLow         = na
    int   prevHighBar     = na
    int   prevLowBar      = na
    float prevPrevHigh    = na
    float prevPrevLow     = na
    int   prevPrevHighBar = na
    int   prevPrevLowBar  = na
    bool  prevC2Buy       = false
    bool  prevC2Sell      = false

type StdDevData
    array<line>  lines
    array<label> labels

type C2Setup
    bool  isBull
    float extreme
    int   phase     = 1
    bool  dead      = false
    label c2Lbl     = na
    box   c3Box     = na
    line  c3Dash    = na
    float cisdLevel     = na
    int   cisdIndex     = na
    bool  cisdConfirmed = false
    line  cisdLine      = na
    label cisdLabel     = na
    StdDevData stdDevData = na
    box   c4Box     = na
    line  c4Dash    = na
    label c4Lbl     = na
    float c4Top     = na
    float c4Bottom  = na
    bool  c4Invalid = false
    // --- gating / deferred-draw bookkeeping ---
    bool  revealed          = false   // true once C2/C3/(C4) have actually been drawn
    int   c2Bar             = na
    float c2Price           = na
    float sweepFromPrice    = na
    int   sweepFromBar      = na
    int   c3StartBar        = na
    int   c4StartBar        = na
    float c3Top             = na
    float c3Bottom          = na
    bool  cisdConfirmedInC4 = false   // true if CISD confirmed while already in phase 2 (C4)

type SetupRecord
    C2Setup setup
    int     creationBar

type EarlyCisdRecord
    line  ln
    label lbl
    int   barIndex

var array<HtfCandle>        htfCandles           = array.new<HtfCandle>()
var array<line>             vLines               = array.new<line>()
var array<line>             hLines               = array.new<line>()
var array<line>             lLines               = array.new<line>()
var PeriodState              state                = PeriodState.new()
var C2Setup                  buySetup             = na
var C2Setup                  sellSetup            = na
var array<SetupRecord>       setupRecords         = array.new<SetupRecord>()
var array<EarlyCisdRecord>   bullEarlyCisdRecords = array.new<EarlyCisdRecord>()
var array<EarlyCisdRecord>   bearEarlyCisdRecords = array.new<EarlyCisdRecord>()
var int                      htfOpenBar           = na
var line                     htfOpenLn            = na

allowBuy  = biasFilter != 'Bearish'
allowSell = biasFilter != 'Bullish'

//#endregion

//#region [FUNCTIONS]

tfLabel(string tf) =>
    switch tf
        '1'   => '1m'
        '3'   => '3m'
        '5'   => '5m'
        '15'  => '15m'
        '60'  => '1h'
        '240' => '4h'
        '1D'  => '1D'
        'D'   => '1D'
        '1W'  => '1W'
        'W'   => '1W'
        '1M'  => '1M'
        'M'   => '1M'
        =>       tf + 'm'

tfMins(string tf) =>
    switch tf
        '1D' => 1440
        'D'  => 1440
        '1W' => 10080
        'W'  => 10080
        '1M' => 43200
        'M'  => 43200
        =>      int(str.tonumber(tf))

curTfCode() =>
    timeframe.isdaily ? '1D' : timeframe.isweekly ? '1W' : timeframe.ismonthly ? '1M' : timeframe.period

tablePosition(string vert, string horz) =>
    switch vert + '-' + horz
        'Top-Left'      => position.top_left
        'Top-Center'    => position.top_center
        'Top-Right'     => position.top_right
        'Middle-Left'   => position.middle_left
        'Middle-Center' => position.middle_center
        'Middle-Right'  => position.middle_right
        'Bottom-Left'   => position.bottom_left
        'Bottom-Center' => position.bottom_center
        'Bottom-Right'  => position.bottom_right
        =>                 position.top_right

lineStyleFromStr(string s) =>
    switch s
        'Solid'  => line.style_solid
        'Dashed' => line.style_dashed
        'Dotted' => line.style_dotted
        =>          line.style_dashed

textSize(string s) =>
    switch s
        'Tiny'   => size.tiny
        'Small'  => size.small
        'Normal' => size.normal
        'Large'  => size.large
        =>          size.huge

directionalLabel(int xBar, float yPrice, string txt, bool bullish) =>
    label.new(xBar, yPrice, text = txt, xloc = xloc.bar_index, yloc = yloc.price, style = bullish ? label.style_label_up : label.style_label_down, color = na, textcolor = color.gray, size = textSize(labelSize), text_font_family = font.family_monospace)

eqLineColor() => showEQ ? eqColor : noColor

drawSweepLine(int fromBar, float fromPrice, int toBar) =>
    line ln = showSweep ? line.new(fromBar, fromPrice, toBar, fromPrice, color = sweepColor, style = lineStyleFromStr(sweepStyle), width = sweepWidth) : na
    ln

newLHLine(float price) =>
    line.new(bar_index, price, bar_index, price, xloc.bar_index, extend.none, lhColor, lineStyleFromStr(lhStyle), lhWidth)

updateLHLine(line ln, float price) =>
    line.set_xy1(ln, line.get_x1(ln), price)
    line.set_xy2(ln, bar_index, price)
    line.set_color(ln, lhColor)
    line.set_style(ln, lineStyleFromStr(lhStyle))
    line.set_width(ln, lhWidth)

autoHTF() =>
    switch timeframe.period
        '1'   => '15'
        '3'   => '30'
        '5'   => '60'
        '15'  => '240'
        '60'  => '1D'
        '240' => '1W'
        '1D'  => '1M'
        =>       '1M'

ltfCode() =>
    switch modelType
        '1m-15m' => '1'
        '3m-30m' => '3'
        '5m-1H'  => '5'
        '15m-4H' => '15'
        '1H-1D'  => '60'
        '4H-1W'  => '240'
        '1D-1W'  => '1D'
        'Custom' => customLTF
        =>          curTfCode()

isAutoValidTf(int mins) => mins == 1 or mins == 3 or mins == 5 or mins == 15 or mins == 60 or mins == 240 or mins == 1440

method delete(HtfCandle c) =>
    box.delete(c.body)
    line.delete(c.wickUp)
    line.delete(c.wickDown)
    line.delete(c.eqLine)
    line.delete(c.sweepLine)

method update(HtfCandle c, float curH, float curL, float curC, int curBar) =>
    if curH > c.h
        c.h    := curH
        c.hBar := curBar
    if curL < c.l
        c.l    := curL
        c.lBar := curBar
    c.c := curC
    c

method draw(HtfCandle c, int leftBar) =>
    bool  isBull = c.c >= c.o
    int   midX   = leftBar + 1
    float eq     = (c.h + c.l) / 2
    box.set_left(c.body, leftBar)
    box.set_right(c.body, leftBar + 2)
    box.set_top(c.body, math.max(c.o, c.c))
    box.set_bottom(c.body, math.min(c.o, c.c))
    box.set_bgcolor(c.body, isBull ? bullBody : bearBody)
    box.set_border_color(c.body, isBull ? borderBull : borderBear)
    line.set_x1(c.wickUp, midX)
    line.set_x2(c.wickUp, midX)
    line.set_y1(c.wickUp, c.h)
    line.set_y2(c.wickUp, math.max(c.o, c.c))
    line.set_color(c.wickUp, isBull ? wickBull : wickBear)
    line.set_x1(c.wickDown, midX)
    line.set_x2(c.wickDown, midX)
    line.set_y1(c.wickDown, c.l)
    line.set_y2(c.wickDown, math.min(c.o, c.c))
    line.set_color(c.wickDown, isBull ? wickBull : wickBear)
    line.set_x1(c.eqLine, midX)
    line.set_x2(c.eqLine, midX)
    line.set_y1(c.eqLine, eq)
    line.set_y2(c.eqLine, eq)
    line.set_color(c.eqLine, eqLineColor())
    line.set_style(c.eqLine, lineStyleFromStr(eqStyle))
    line.set_width(c.eqLine, eqWidth)
    c.midX := midX

newHtfCandle() =>
    HtfCandle.new(open, high, low, close, bar_index, bar_index,
         box.new(bar_index, high, bar_index, low, border_color = borderBull, border_width = 1),
         line.new(bar_index, high, bar_index, high, color = wickBull),
         line.new(bar_index, low, bar_index, low, color = wickBull),
         line.new(bar_index, high, bar_index, high, color = noColor),
         na)

drawEarlyCisd(bool isBull, float level, int idx, int confirmBar) =>
    array<EarlyCisdRecord> target = isBull ? bullEarlyCisdRecords : bearEarlyCisdRecords
    if showEarlyCisd
        ln = line.new(idx, level, confirmBar + 3, level, color = earlyCisdColor, style = lineStyleFromStr(earlyCisdStyle), width = earlyCisdWidth)
        label lbl = na
        if showLabels
            lbl := label.new(confirmBar + 3, level, isBull ? '+CISD' : '-CISD', xloc = xloc.bar_index, style = label.style_label_left, color = na, textcolor = earlyCisdColor, size = textSize(labelSize), text_font_family = font.family_monospace)
        target.push(EarlyCisdRecord.new(ln, lbl, confirmBar))
    if showAlerts and showEarlyCisdAlerts
        alert((isBull ? 'Bullish' : 'Bearish') + ' Early CISD confirmed on ' + syminfo.ticker, alert.freq_once_per_bar)

pruneEarlyCisd(array<EarlyCisdRecord> recs, int thresholdBar) =>
    if not na(thresholdBar)
        while recs.size() > 0 and recs.first().barIndex < thresholdBar
            evicted = recs.shift()
            line.delete(evicted.ln)
            if not na(evicted.lbl)
                label.delete(evicted.lbl)

consumeLastEarlyCisd(array<EarlyCisdRecord> recs) =>
    if recs.size() > 0
        evicted = recs.pop()
        line.delete(evicted.ln)
        if not na(evicted.lbl)
            label.delete(evicted.lbl)

parseFloats(string src) =>
    var out = array.new<float>()
    array.clear(out)
    for part in str.split(str.replace_all(src, ' ', ''), ',')
        v = str.tonumber(part)
        if not na(v)
            out.push(v)
    out

createStdDevProjections(C2Setup s, int confirmBar) =>
    if showStdDev
        devs = parseFloats(stdDevInput)
        if devs.size() > 0
            off     = bar_index - s.cisdIndex
            useWick = stdDevCalc == 'Wick'

            level0 = s.cisdLevel
            level1 = s.extreme
            if not useWick
                for j = 0 to 50
                    if s.isBull ? low[j] == s.extreme : high[j] == s.extreme
                        level1 := s.isBull ? math.min(open[j], close[j]) : math.max(open[j], close[j])
                        break

            leftBar = s.cisdIndex
            if s.cisdIndex < bar_index
                prevBetter = s.isBull
                     ? (useWick ? high[off + 1] > high[off] : math.max(open[off + 1], close[off + 1]) > math.max(open[off], close[off]))
                     : (useWick ? low[off + 1]  < low[off]  : math.min(open[off + 1], close[off + 1]) < math.min(open[off], close[off]))
                if prevBetter
                    leftBar := s.cisdIndex - 1

            rightBar   = confirmBar + 3
            priceRange = s.isBull ? level0 - level1 : level1 - level0
            lns        = array.new<line>()
            lbs        = array.new<label>()

            for dev in devs
                devPrice = s.isBull ? level0 - priceRange * dev : level0 + priceRange * dev
                lns.push(line.new(leftBar, devPrice, rightBar, devPrice, xloc = xloc.bar_index, color = stdDevColor, width = stdDevWidth, style = lineStyleFromStr(stdDevStyle)))
                if showLabels
                    lbs.push(label.new(rightBar, devPrice, str.tostring(dev), xloc = xloc.bar_index, style = label.style_label_left, color = na, textcolor = stdDevColor, size = textSize(labelSize), text_font_family = font.family_monospace))

            s.stdDevData := StdDevData.new(lns, lbs)

confirmCisd(C2Setup s, int confirmBar) =>
    s.cisdConfirmed := true
    showLine = s.isBull ? showBullCisd : showBearCisd
    clr      = s.isBull ? cisdBullColor : cisdBearColor
    lnStyle  = s.isBull ? cisdBullStyle : cisdBearStyle
    lnWidth  = s.isBull ? cisdBullWidth : cisdBearWidth
    if showLine
        s.cisdLine := line.new(s.cisdIndex, s.cisdLevel, confirmBar + 3, s.cisdLevel, color = clr, style = lineStyleFromStr(lnStyle), width = lnWidth)
        if showLabels
            s.cisdLabel := label.new(confirmBar + 3, s.cisdLevel, s.isBull ? '+CISD' : '-CISD', xloc = xloc.bar_index, style = label.style_label_left, color = na, textcolor = clr, size = textSize(labelSize), text_font_family = font.family_monospace)
    createStdDevProjections(s, confirmBar)

deleteSetupVisuals(C2Setup s) =>
    if not na(s)
        if not na(s.c2Lbl)
            label.delete(s.c2Lbl)
        if not na(s.cisdLine)
            line.delete(s.cisdLine)
        if not na(s.cisdLabel)
            label.delete(s.cisdLabel)
        if not na(s.c3Box)
            box.delete(s.c3Box)
        if not na(s.c3Dash)
            line.delete(s.c3Dash)
        if not na(s.c4Box)
            box.delete(s.c4Box)
        if not na(s.c4Dash)
            line.delete(s.c4Dash)
        if not na(s.c4Lbl)
            label.delete(s.c4Lbl)
        if not na(s.stdDevData)
            if not na(s.stdDevData.lines)
                for ln in s.stdDevData.lines
                    line.delete(ln)
            if not na(s.stdDevData.labels)
                for lb in s.stdDevData.labels
                    label.delete(lb)

registerRecord(C2Setup s) =>
    setupRecords.push(SetupRecord.new(s, bar_index))
    while showHistory > 0 and setupRecords.size() > showHistory
        evicted = setupRecords.shift()
        deleteSetupVisuals(evicted.setup)
    earliestVisibleBar = setupRecords.size() > 0 ? setupRecords.first().creationBar : na
    pruneEarlyCisd(bullEarlyCisdRecords, earliestVisibleBar)
    pruneEarlyCisd(bearEarlyCisdRecords, earliestVisibleBar)

autoTf = switch modelType
    '1m-15m' => '15'
    '3m-30m' => '30'
    '5m-1H'  => '60'
    '15m-4H' => '240'
    '1H-1D'  => '1D'
    '4H-1W'  => '1W'
    '1D-1W'  => '1W'
    'Custom' => customHTF
    =>          autoHTF()

validTf = modelType == 'Auto' ? isAutoValidTf(tfMins(curTfCode())) : tfMins(curTfCode()) <= tfMins(ltfCode())

[currentO, currentH, currentL, currentC, prevH1, prevL1] = request.security(syminfo.tickerid, autoTf, [open[0], high[0], low[0], close[0], high[1], low[1]], lookahead = barmerge.lookahead_on)

c2Buy         = currentL < prevL1 and currentC > prevL1
c2Sell        = currentH > prevH1 and currentC < prevH1
periodChanged = time(autoTf) != time(autoTf)[1]

// Computes the C3/C4 "T-Spot" top/bottom for the CURRENT bar (always runs,
// regardless of whether the setup is currently being drawn on the chart).
computeTopBot() =>
    mid = (state.prevHigh + state.prevLow) / 2
    top = math.max(open, mid)
    bot = math.min(open, mid)
    [top, bot]

// Actually draws the T-Spot box/dash/(C4 label) using an already-computed
// top/bot and a given left-edge bar (which may be in the past, i.e. a
// "backdated" / retroactive draw once CISD finally confirms).
drawPhaseVisual(bool isBull, bool isC4, int startBar, float top, float bot) =>
    yDash = isBull ? bot : top
    bxCol = isBull ? tSpotBullColor : tSpotBearColor
    box   bx   = showTSpot ? box.new(startBar, top, bar_index, bot, border_color = na, bgcolor = bxCol) : na
    line  dash = showEQ ? line.new(startBar, yDash, bar_index, yDash, color = eqColor, width = eqWidth, style = lineStyleFromStr(eqStyle)) : na
    label lbl  = na
    if isC4 and showTSpot and showLabels and validTf
        lbl := directionalLabel(bar_index + 2, isBull ? bot : top, 'C4', isBull)
    [bx, dash, lbl]

// Draws everything that has accumulated so far for a setup (C2 label, C3
// box/dash backdated to when C3 actually started, and — if already in C4 —
// the C4 box/dash/label backdated to when C4 actually started). Called the
// moment CISD confirms. No-ops if already revealed, or if the gate is off
// (in which case reveal happens immediately at C2, same as legacy behavior).
method reveal(C2Setup s) =>
    if not s.revealed
        s.revealed := true
        if validTf and showLabels and not na(s.c2Bar)
            s.c2Lbl := directionalLabel(s.c2Bar, s.c2Price, 'C2', s.isBull)
        if not na(s.sweepFromBar)
            drawSweepLine(s.sweepFromBar, s.sweepFromPrice, bar_index)
        [bx, dash, _] = drawPhaseVisual(s.isBull, false, s.c3StartBar, s.c3Top, s.c3Bottom)
        s.c3Box  := bx
        s.c3Dash := dash
        if s.phase == 2 and not na(s.c4StartBar)
            [bx4, dash4, lbl4] = drawPhaseVisual(s.isBull, true, s.c4StartBar, s.c4Top, s.c4Bottom)
            s.c4Box  := bx4
            s.c4Dash := dash4
            s.c4Lbl  := lbl4
    s

newSetup(bool isBull, float extreme, int extremeBar, float extremePrice, float sweepFromPrice, int sweepFromBar, float cisdLevel, int cisdIndex, bool cisdAlreadyConfirmed, int cisdConfirmBar) =>
    s = C2Setup.new(isBull, extreme, 1, false)
    s.c2Bar          := extremeBar
    s.c2Price        := extremePrice
    s.sweepFromPrice := sweepFromPrice
    s.sweepFromBar   := sweepFromBar
    s.c3StartBar     := bar_index
    [top, bot] = computeTopBot()
    s.c3Top      := top
    s.c3Bottom   := bot
    s.cisdLevel  := cisdLevel
    s.cisdIndex  := cisdIndex
    cisdReady = cisdAlreadyConfirmed and not na(cisdLevel) and not na(cisdIndex) and not na(cisdConfirmBar)
    if not requireCisdToShow
        // legacy behavior: always visible immediately
        s.reveal()
    if cisdReady
        if requireCisdToShow
            s.reveal()
        confirmCisd(s, cisdConfirmBar)
    registerRecord(s)
    s

method advanceToC4(C2Setup s) =>
    if not na(s.c3Box)
        box.set_right(s.c3Box, bar_index)
        line.set_x2(s.c3Dash, bar_index)
    s.c4StartBar := bar_index
    [top, bot] = computeTopBot()
    s.c4Top    := top
    s.c4Bottom := bot
    if s.revealed
        [bx, dash, lbl] = drawPhaseVisual(s.isBull, true, s.c4StartBar, top, bot)
        s.c4Box  := bx
        s.c4Dash := dash
        s.c4Lbl  := lbl
    s.phase     := 2
    s.c4Invalid := false
    s

method finish(C2Setup s) =>
    if not na(s.c4Box)
        box.set_right(s.c4Box, bar_index)
        line.set_x2(s.c4Dash, bar_index)
    s.phase := 0
    s

method deleteStdDev(C2Setup s) =>
    if not na(s.stdDevData)
        if not na(s.stdDevData.lines)
            for ln in s.stdDevData.lines
                line.delete(ln)
        if not na(s.stdDevData.labels)
            for lb in s.stdDevData.labels
                label.delete(lb)
        s.stdDevData := na
    s

method extendAndCheck(C2Setup s) =>
    if not na(s) and not s.dead and s.phase != 0
        box  extBox  = s.phase == 1 ? s.c3Box : s.c4Box
        line extDash = s.phase == 1 ? s.c3Dash : s.c4Dash
        if not na(extBox)
            box.set_right(extBox, bar_index)
            line.set_x2(extDash, bar_index)

        c2Breach = s.isBull ? low < s.extreme : high > s.extreme
        if c2Breach
            if not na(s.c2Lbl)
                label.set_text(s.c2Lbl, 'XC2')
                label.set_textcolor(s.c2Lbl, s.phase == 2 ? color.orange : color.red)
            if not na(s.c3Box)
                box.delete(s.c3Box)
                s.c3Box := na
            if not na(s.c3Dash)
                line.delete(s.c3Dash)
                s.c3Dash := na
            if not na(s.c4Box)
                box.delete(s.c4Box)
                s.c4Box := na
            if not na(s.c4Dash)
                line.delete(s.c4Dash)
                s.c4Dash := na
            if not na(s.c4Lbl)
                label.delete(s.c4Lbl)
                s.c4Lbl := na
            s.deleteStdDev()
            s.dead  := true
            s.phase := 0
    s

// If CISD only ever confirmed while in C4 (i.e. it was never valid back in
// C2/C3), and C4 subsequently gets invalidated, the whole setup — including
// the CISD confirmation itself — is discarded retroactively.
method checkC4Invalid(C2Setup s) =>
    if not na(s) and not s.dead and s.phase == 2 and not s.c4Invalid
        invalid = s.isBull ? close[1] < s.c4Bottom : close[1] > s.c4Top
        if invalid
            s.c4Invalid := true
            if requireCisdToShow and s.cisdConfirmedInC4
                deleteSetupVisuals(s)
                s.c2Lbl      := na
                s.c3Box      := na
                s.c3Dash     := na
                s.c4Box      := na
                s.c4Dash     := na
                s.c4Lbl      := na
                s.cisdLine   := na
                s.cisdLabel  := na
                s.stdDevData := na
                s.cisdConfirmed := false
                s.dead  := true
                s.phase := 0
            else
                if not na(s.c4Lbl)
                    label.set_text(s.c4Lbl, 'XC4')
                    label.set_textcolor(s.c4Lbl, color.red)
                if not na(s.c4Dash)
                    line.set_color(s.c4Dash, color.red)
                if not na(s.c4Box)
                    box.delete(s.c4Box)
                    s.c4Box := na
    s

method checkCisdConfirm(C2Setup s) =>
    if not na(s) and not s.dead and not s.cisdConfirmed and s.phase != 0 and not na(s.cisdLevel) and not na(s.cisdIndex)
        confirmed = s.isBull ? close[1] > s.cisdLevel : close[1] < s.cisdLevel
        if confirmed
            if s.phase == 2
                s.cisdConfirmedInC4 := true
            if requireCisdToShow
                s.reveal()
            confirmCisd(s, bar_index)
    s

processC2(bool trigger, bool allow, bool isBull, float extremePrice, int extremeBar, float sweepFromPrice, int sweepFromBar, float cisdLevel, int cisdIndex, bool cisdAlreadyConfirmed, int cisdConfirmBar, C2Setup existing) =>
    C2Setup result = existing
    if trigger and allow
        result := newSetup(isBull, extremePrice, extremeBar, extremePrice, sweepFromPrice, sweepFromBar, cisdLevel, cisdIndex, cisdAlreadyConfirmed, cisdConfirmBar)
    else if periodChanged and not na(existing) and not existing.dead
        result := existing.phase == 1 ? existing.advanceToC4() : existing.phase == 2 ? existing.finish() : existing
    result

markTrigger(bool trigger, bool allow, int idx, bool isBuy) =>
    if trigger and allow and htfCandles.size() > idx
        c = htfCandles.get(idx)
        if isBuy
            c.triggerBuy := true
        else
            c.triggerSell := true

//#endregion

//#region [LOGIC]

var float o0 = open
var float h0 = high
var float l0 = low
var float o1 = open
var float h1 = high
var float l1 = low

if periodChanged
    o1 := o0[1]
    h1 := h0[1]
    l1 := l0[1]
    o0 := open
    h0 := high
    l0 := low

if high >= h0
    h0 := high
if low <= l0
    l0 := low

bool up = close > open
bool dw = close < open
bool eq = close == open

var float bullLevelFixed = na
var int   bullIndexFixed = na
var float bearLevelFixed = na
var int   bearIndexFixed = na

if low == l0 and low < l1
    if (dw[0] or eq[0]) and (up[1] or eq[1]) and not (eq[0] and eq[1])
        bullLevelFixed := open
        bullIndexFixed := bar_index
    else
        for i = 2 to 10
            if low[i] < low
                break
            if (up[i] or eq[i]) and dw[i - 1]
                b = i - 1
                bullLevelFixed := open[b]
                bullIndexFixed := bar_index - b
                for j = b to 0
                    if open[j] > bullLevelFixed and dw[j]
                        bullLevelFixed := open[j]
                        bullIndexFixed := bar_index - j
                if bullLevelFixed < open
                    bullLevelFixed := close > open ? high : open
                    bullIndexFixed := bar_index
                break

if high == h0 and high > h1
    if (up[0] or eq[0]) and (dw[1] or eq[1]) and not (eq[0] and eq[1])
        bearLevelFixed := open
        bearIndexFixed := bar_index
    else
        for i = 2 to 10
            if high[i] > high
                break
            if (dw[i] or eq[i]) and up[i - 1]
                yb = i - 1
                bearLevelFixed := open[yb]
                bearIndexFixed := bar_index - yb
                for j = yb to 0
                    if open[j] < bearLevelFixed and up[j]
                        bearLevelFixed := open[j]
                        bearIndexFixed := bar_index - j
                if bearLevelFixed > open
                    bearLevelFixed := close < open ? low : open
                    bearIndexFixed := bar_index
                break

var bool bullReclaimed  = false
var int  bullReclaimBar = na
var bool bearReclaimed  = false
var int  bearReclaimBar = na

bullPeriodSweep = l0 < l1
bearPeriodSweep = h0 > h1

newBullLevel = not na(bullIndexFixed) and (na(bullIndexFixed[1]) or bullIndexFixed != bullIndexFixed[1])
if newBullLevel
    bullReclaimed  := false
    bullReclaimBar := na
if not na(bullLevelFixed) and not bullReclaimed and close[1] > bullLevelFixed
    bullReclaimed  := true
    bullReclaimBar := bar_index
    if bullPeriodSweep
        drawEarlyCisd(true, bullLevelFixed, bullIndexFixed, bar_index)

newBearLevel = not na(bearIndexFixed) and (na(bearIndexFixed[1]) or bearIndexFixed != bearIndexFixed[1])
if newBearLevel
    bearReclaimed  := false
    bearReclaimBar := na
if not na(bearLevelFixed) and not bearReclaimed and close[1] < bearLevelFixed
    bearReclaimed  := true
    bearReclaimBar := bar_index
    if bearPeriodSweep
        drawEarlyCisd(false, bearLevelFixed, bearIndexFixed, bar_index)

candleOffset = candleOffsetIn + 10

if periodChanged
    if htfCandles.size() > 0
        newest             = htfCandles.first()
        state.prevHigh    := newest.h
        state.prevLow     := newest.l
        state.prevHighBar := newest.hBar
        state.prevLowBar  := newest.lBar
    if htfCandles.size() > 1
        prior                  = htfCandles.get(1)
        state.prevPrevHigh    := prior.h
        state.prevPrevLow     := prior.l
        state.prevPrevHighBar := prior.hBar
        state.prevPrevLowBar  := prior.lBar
    htfCandles.unshift(newHtfCandle())
    while htfCandles.size() > htfCandleCount
        htfCandles.pop().delete()
    htfOpenBar := bar_index
    if showLH
        if hLines.size() > 0
            line.set_x2(hLines.first(), bar_index)
            line.set_x2(lLines.first(), bar_index)
        hLines.unshift(newLHLine(high))
        lLines.unshift(newLHLine(low))
        while hLines.size() > htfCandleCount
            line.delete(hLines.pop())
        while lLines.size() > htfCandleCount
            line.delete(lLines.pop())
else if htfCandles.size() > 0
    htfCandles.set(0, htfCandles.first().update(high, low, close, bar_index))

if showLH and htfCandles.size() > 0 and hLines.size() > 0
    curCandle = htfCandles.first()
    updateLHLine(hLines.first(), curCandle.h)
    updateLHLine(lLines.first(), curCandle.l)

if periodChanged
    state.prevC2Buy  := c2Buy[1]
    state.prevC2Sell := c2Sell[1]

c2BuyTrigger  = periodChanged and state.prevC2Buy
c2SellTrigger = periodChanged and state.prevC2Sell

if c2BuyTrigger and allowBuy and bullReclaimed[1]
    consumeLastEarlyCisd(bullEarlyCisdRecords)

if c2SellTrigger and allowSell and bearReclaimed[1]
    consumeLastEarlyCisd(bearEarlyCisdRecords)

buySetup  := processC2(c2BuyTrigger, allowBuy, true, state.prevLow, state.prevLowBar, state.prevPrevLow, state.prevPrevLowBar, bullLevelFixed[1], bullIndexFixed[1], bullReclaimed[1], bullReclaimBar[1], buySetup)
sellSetup := processC2(c2SellTrigger, allowSell, false, state.prevHigh, state.prevHighBar, state.prevPrevHigh, state.prevPrevHighBar, bearLevelFixed[1], bearIndexFixed[1], bearReclaimed[1], bearReclaimBar[1], sellSetup)

markTrigger(c2BuyTrigger, allowBuy, 2, true)
markTrigger(c2SellTrigger, allowSell, 2, false)

buySetup  := buySetup.extendAndCheck()
sellSetup := sellSetup.extendAndCheck()

buySetup  := buySetup.checkC4Invalid()
sellSetup := sellSetup.checkC4Invalid()

buySetup  := buySetup.checkCisdConfirm()
sellSetup := sellSetup.checkCisdConfirm()

if showAlerts
    if c2BuyTrigger and allowBuy
        alert('Bullish C2 formed on ' + syminfo.ticker, alert.freq_once_per_bar)
    if c2SellTrigger and allowSell
        alert('Bearish C2 formed on ' + syminfo.ticker, alert.freq_once_per_bar)

if showVlines and periodChanged
    vLines.unshift(line.new(bar_index, low - ta.tr, bar_index, high + ta.tr, xloc.bar_index, extend.both, lineColor, lineStyleFromStr(lineStyle), lineWidth))
    while vLines.size() > htfCandleCount
        line.delete(vLines.pop())

//#endregion

//#region [PLOT]

if (barstate.islast or barstate.isrealtime) and validTf and htfCandles.size() > 0 and not hideHtf
    n = htfCandles.size()
    for i = n - 1 to 0
        htfCandles.get(i).draw(bar_index + candleOffset + candleSpacing * (n - 1 - i))
    if showHtfOpen and not na(htfOpenBar)
        curOpenX2 = box.get_right(htfCandles.first().body)
        if na(htfOpenLn)
            htfOpenLn := line.new(htfOpenBar, currentO, curOpenX2, currentO, xloc.bar_index, extend.none, htfOpenColor, lineStyleFromStr(htfOpenStyle), htfOpenWidth)
        else
            line.set_xy1(htfOpenLn, htfOpenBar, currentO)
            line.set_xy2(htfOpenLn, curOpenX2, currentO)
            line.set_color(htfOpenLn, htfOpenColor)
            line.set_style(htfOpenLn, lineStyleFromStr(htfOpenStyle))
            line.set_width(htfOpenLn, htfOpenWidth)
    for i = n - 1 to 0
        c = htfCandles.get(i)
        if i > 0
            nextC = htfCandles.get(i - 1)
            line.set_x2(c.eqLine, nextC.midX)
        if showSweep and (c.triggerBuy or c.triggerSell) and i > 0
            nextC = htfCandles.get(i - 1)
            price = c.triggerBuy ? c.l : c.h
            if na(c.sweepLine)
                c.sweepLine := line.new(c.midX, price, nextC.midX, price, color = sweepColor, style = lineStyleFromStr(sweepStyle), width = sweepWidth)
            else
                line.set_xy1(c.sweepLine, c.midX, price)
                line.set_xy2(c.sweepLine, nextC.midX, price)
                line.set_color(c.sweepLine, sweepColor)
                line.set_style(c.sweepLine, lineStyleFromStr(sweepStyle))
                line.set_width(c.sweepLine, sweepWidth)

if barstate.islast and boolTable
    var table wm = table.new(tablePosition(tableVert, tableHorz), 1, 4,
         bgcolor      = color.new(#000000, 100),
         border_color = dashTextColor,
         border_width = 1,
         frame_color  = dashTextColor,
         frame_width  = 1)

    ltfDisp = tfLabel(ltfCode())
    htfDisp = tfLabel(autoTf)

    table.cell(wm, 0, 0, 'Open Source Fractal', text_color = dashBgColor, bgcolor = dashTextColor, text_size = size.normal, text_halign = text.align_center, text_font_family = font.family_default)

    alignBiasStr = 'Alignment : ' + ltfDisp + '-' + htfDisp + '\n\nBias : ' + biasFilter
    table.cell(wm, 0, 1, alignBiasStr, text_color = dashTextColor, bgcolor = dashBgColor, text_size = size.small, text_halign = text.align_center, text_font_family = font.family_default)

    dateStr     = str.format_time(time_close, 'dd MMM yyyy', syminfo.timezone)
    pairDateStr = syminfo.ticker + '  |  ' + dateStr
    table.cell(wm, 0, 2, pairDateStr, text_color = dashTextColor, bgcolor = dashBgColor, text_size = size.small, text_halign = text.align_center, text_font_family = font.family_default)

    if not validTf
        curTfStr = tfLabel(curTfCode())
        warnMsg  = '⚠ Switch to ' + ltfDisp + ' or lower (now: ' + curTfStr + ')'
        table.cell(wm, 0, 3, warnMsg, text_color = dashBgColor, bgcolor = color.new(#cc0000, 0), text_size = size.small, text_halign = text.align_center, text_font_family = font.family_default)

//#endregion

Overview

Open Source Fractal overlays custom higher-timeframe candle previews, open prices, and time markers directly on the price chart. Users can filter setups using a directional bias, select preset or custom lower-timeframe to higher-timeframe pairings, toggle early CISD alerts, and control how many historical setups remain drawn on the screen.

Details

Published by

cantolab on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

SMC / ICT / price action, Support & resistance, Trend

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.

Settings

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

SettingTypeDefaultVariable
Alerts?booltrueshowAlerts
Early CISD Alerts?boolfalseshowEarlyCisdAlerts
Historyint1showHistory
BiasstringNeutralbiasFilter
FractalstringAutomodelType
Early CISDbooltrueshowEarlyCisd
Custom LTF timeframe5customLTF
customHTFtimeframe60customHTF
HTF Candlesint4htfCandleCount
Hide?boolfalsehideHtf
Offsetint0candleOffsetIn
Bodycolorcolor.new(color.green, 0)bullBody
Show all 40 settings
SettingTypeDefaultVariable
bearBodycolorcolor.new(#000000, 0)bearBody
Borderscolorcolor.new(#000000, 0)borderBull
borderBearcolorcolor.new(#000000, 0)borderBear
Wickcolorcolor.new(#000000, 0)wickBull
wickBearcolorcolor.new(#000000, 0)wickBear
HTF OpenbooltrueshowHtfOpen
htfOpenColorcolorcolor.new(#1a1a1a, 30)htfOpenColor
htfOpenStylestringSolidhtfOpenStyle
htfOpenWidthint1htfOpenWidth
O/C TimebooltrueshowVlines
lineColorcolorcolor.new(color.gray, 60)lineColor
lineStylestringSolidlineStyle
lineWidthint1lineWidth
L/H LinesboolfalseshowLH
lhColorcolorcolor.new(#1a1a1a, 30)lhColor
lhStylestringSolidlhStyle
lhWidthint1lhWidth
Labels?booltrueshowLabels
labelSizestringSmalllabelSize
Candle 1 SweepboolfalseshowSweep
sweepColorcolorcolor.new(#000000, 0)sweepColor
sweepStylestringSolidsweepStyle
sweepWidthint1sweepWidth
Early CISDcolorcolor.new(color.blue, 0)earlyCisdColor
earlyCisdStylestringDashedearlyCisdStyle
earlyCisdWidthint1earlyCisdWidth
Bullish CISDbooltrueshowBullCisd
cisdBullColorcolorcolor.new(color.blue, 0)cisdBullColor
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

575 stars

This indicator detects equal highs and lows by comparing prices within user-defined left and right bars using a tolerance percentage. It plots horizontal lines on the price chart for equal highs in one color and lows in another. A screener allows input of mult

Indicator v5 Support & resistanceTrend
Updated 1 year ago · MPL-2.0 GitHub repository of ICT Equal Highs and Lows with Screener

This Pine Script indicator plots a ZigZag on the price chart. It identifies reversal points when price moves more than a set percentage from the last pivot. The script calculates forecasts using zigzag data and labels detected geometric patterns including harm

Indicator v4 TrendSupport & resistance
218 stars

This indicator plots market structure on the price chart by identifying swing highs and lows. It connects these points with lines to visualize trend direction and detect Break of Structure (BOS) when price breaks a prior swing in the current trend. Change of C

Indicator v6 TrendSupport & resistance
Updated 3 months ago · None stated GitHub repository of Market Structure - BOS & CHoCH
56 stars

BigBeluga Smart Money Concepts identifies and overlays market structure and volumetric order blocks directly on the price chart. It analyzes market swings, trend sweeps, and mapping structure using customizable pivot lengths and calculation modes. The indicato

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · None stated GitHub repository of BigBeluga - Smart Money Concepts
53 stars

Apex Trend & Liquidity Master calculates a trend cloud using customizable moving averages expanded by a volatility multiplier directly on the main price chart. It identifies dynamic support and resistance liquidity zones from swing high and low pivots, extendi

Indicator v6 TrendSupport & resistance
Updated 2 months ago · MPL-2.0 GitHub repository of Apex Trend & Liquidity Master

Copilot

by milocaetano
48 stars

Copilot is an order flow range-reversal indicator designed to overlay trading setups directly on the price chart. It evaluates four criteria: market context (sideways vs trending regime based on ATR height), structure (double top or double bottom retests), abs

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated yesterday · MIT GitHub repository of Copilot

More indicators

Trade Your Charts With a Regulated Broker

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

Compare brokers