Skip to content

Premium collection — free download

Fractal Candle Model

Higher-timeframe candle 2/3 closures confirmed by a lower-timeframe CISD, with protected swings, FVGs, trade levels and a status panel.

PremiumMulti-timeframev1.0TradingView (Pine Script v6) Non-repainting signals Alerts

What it does

The Fractal Candle Model indicator turns a three-timeframe candle-closure model into rules a chart can draw. A swing is only treated as valid when a higher-timeframe candle closure confirms it and a lower-timeframe change in the state of delivery forms inside that candle. The concepts come from the free fractal-model course published by TTrades; this is an independent implementation.

How it works

  • Candle 2 closure: a candle sweeps the previous candle's low (high) at a point of interest and closes back inside its range.
  • Candle 3 closure: Candle 2 failed, and Candle 3 closes through Candle 2's body without taking its extreme.
  • CISD: after a series of down-close candles, a candle closes above the open of the first candle of the series (mirror for bearish).
  • Protected swing: the low (high) of the leg the CISD closed through. It is the stop location and must hold while the move continues.
  • Wick 50% and EQ: Candle 3 should not close beyond the midpoint of Candle 2's sweep wick, and the next candle should hold the 50% level of the closure candle.

The indicator draws bias-candle levels, closures with their CISD check, protected swings, CISD lines and FVGs, plus a trade layer and a panel that warns when the chart timeframe does not match the chosen profile.

How to use it

Pick a profile and open the matching entry chart (15-minute for the default Day profile). Trade only in the direction of the bias closure, let the wick form, and look for the CISD inside the next higher-timeframe candle. Targets are untaken higher-timeframe highs and lows.

Settings

InputDefaultWhat it changes
ProfileDayBias, structure and entry timeframes
Bias point of interestSwing / FVGWhat a sweep must touch to count
Swing lookback5Size of the swing used as a point of interest
Candle 3 closuresOnAdds the second closure type
ThemeAutoLight and dark palettes

Limitations

The model needs several conditions to line up, so it gives few setups and none in quiet markets. Higher-timeframe candles are only confirmed when they close, which can feel late. SMT divergence and other discretionary confirmations from the course are not automated.

Pine Script source

In TradingView open Pine Editor, paste the code, click Save and then Add to chart.

TTFM_v1.0.pine

538 lines

//@version=6
// TTFM v1.0: TTrades Fractal Model, the same rules as the MT5 expert TTFM_EA_v1.0.
// Three aligned timeframes (put the indicator on the entry timeframe):
//   Bias      : the last closed bias candle is a Candle 2 closure (swept the previous candle's high/low and
//               closed back inside) at a point of interest (swing / FVG), a Candle 3 closure (closed through
//               Candle 2's body) or a continuation closure. Structure closes beyond its wick 50% / EQ cancel it.
//   Structure : inside the bias candle, a Candle 2 / 3 closure in the bias direction, validated by an
//               entry-timeframe CISD inside that candle. Its swing becomes the protected swing.
//   Entry     : in the next structure candle (Candle 3), a CISD from a point of interest (FVG or swing sweep)
//               that keeps the protected swing. Stop beyond the CISD leg, target 2R (or HTF liquidity).
// Decisions on bar close; higher-timeframe values come from closed candles only (no repainting).
indicator("TTFM v1.0", "TTFM", overlay = true, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500, max_bars_back = 500)

// ───────────────────────────── inputs
grpTF = "Timeframes"
profile   = input.string("Day · D1 / 4H / 15m", "Profile", options = ["Day · D1 / 4H / 15m", "Intraday · D1 / 1H / 5m", "Scalp · 1H / 15m / 1m", "Custom"], group = grpTF)
biasIn    = input.timeframe("D", "Custom: bias", group = grpTF)
structIn  = input.timeframe("240", "Custom: structure", group = grpTF)

grpB = "Bias"
poiMode   = input.string("Swing / FVG", "Point of interest for Candle 2", options = ["Any", "Swing / FVG / opposing candle", "Swing / FVG"], group = grpB)
swingN    = input.int(5, "Swing lookback (bias candles)", minval = 1, maxval = 20, group = grpB)
useC3     = input.bool(true, "Candle 3 closures", group = grpB)
useCont   = input.bool(true, "Continuation closures keep the bias", group = grpB)

grpSt = "Structure"
needStruct = input.bool(true, "Require a structure Candle 2 / 3 closure", group = grpSt)
needCisd   = input.bool(true, "…validated by an entry CISD inside it", group = grpSt)
structLife = input.int(2, "Structure setup valid for (structure candles)", minval = 1, group = grpSt)

grpE = "Entry and exits"
needPoi    = input.bool(true, "Entry CISD must come from an FVG or a swing sweep", group = grpE)
maxElapsed = input.float(0.75, "Latest entry (fraction of the bias candle)", minval = 0.1, maxval = 1, step = 0.05, group = grpE)
useSess    = input.bool(false, "Only in session", inline = "ss", group = grpE)
sess       = input.session("0200-1600", "", inline = "ss", group = grpE)
tz         = input.string("America/New_York", "Timezone", group = grpE)
maxPerBias = input.int(2, "Max trades per bias candle", minval = 1, group = grpE)
slBufAtr   = input.float(0.1, "Stop buffer (x ATR14)", minval = 0, step = 0.05, group = grpE)
tp1R       = input.float(2.0, "TP1 (R, half)", minval = 0.5, step = 0.5, group = grpE)
tpMode     = input.string("Fixed R", "TP2", options = ["Fixed R", "HTF liquidity"], group = grpE)
tp2R       = input.float(2.0, "TP2 (R)", minval = 0.5, step = 0.5, group = grpE)
trailOn    = input.bool(false, "After TP1, trail to new protected swings", group = grpE)

grpV = "Display"
showBias   = input.bool(true, "Bias candle levels (wick 50% / EQ, invalidation, draw)", group = grpV)
showStruct = input.bool(true, "Structure closures and protected swings", group = grpV)
showCisd   = input.bool(true, "CISD levels in the bias direction", group = grpV)
showFvg    = input.bool(true, "Entry fair value gaps in the bias direction", group = grpV)
showTrades = input.bool(true, "Trades (entry, stop, targets)", group = grpV)
keepN      = input.int(6, "Past trades to keep", minval = 0, maxval = 40, group = grpV)
shadeBias  = input.bool(true, "Shade the chart with the bias", group = grpV)
themeIn    = input.string("Auto", "Theme", options = ["Auto", "Light", "Dark"], group = grpV)
showPanel  = input.bool(true, "Panel", group = grpV)
panelPos   = input.string("Top right", "Panel position", options = ["Top right", "Bottom right", "Top left", "Bottom left"], group = grpV)
panelSize  = input.string("Small", "Panel size", options = ["Tiny", "Small", "Normal"], group = grpV)
alOn       = input.bool(true, "Alerts (create one alert with 'Any alert() function call')", group = "Alerts")

// ───────────────────────────── palette
bgLum   = 0.299 * color.r(chart.bg_color) + 0.587 * color.g(chart.bg_color) + 0.114 * color.b(chart.bg_color)
isDark  = themeIn == "Dark" or (themeIn == "Auto" and bgLum < 128)
cBull   = isDark ? #4C9AFF : #1E63D6
cBear   = isDark ? #FF5C7A : #D02F4A
cWin    = isDark ? #22AB94 : #089981
cLoss   = isDark ? #F7525F : #F23645
cText   = isDark ? #D1D4DC : #131722
cMuted  = isDark ? #787B86 : #8A8E99
cPanel  = isDark ? #1E222D : #FFFFFF
cHead   = isDark ? #2A2E39 : #F0F3FA
cBorder = isDark ? #363A45 : #E0E3EB
cGold   = isDark ? #FFB74D : #C77700
cOnPill = #FFFFFF

// ───────────────────────────── timeframes
bTF = profile == "Day · D1 / 4H / 15m" ? "D" : profile == "Intraday · D1 / 1H / 5m" ? "D" : profile == "Scalp · 1H / 15m / 1m" ? "60" : biasIn
sTF = profile == "Day · D1 / 4H / 15m" ? "240" : profile == "Intraday · D1 / 1H / 5m" ? "60" : profile == "Scalp · 1H / 15m / 1m" ? "15" : structIn
eTF = profile == "Day · D1 / 4H / 15m" ? "15" : profile == "Intraday · D1 / 1H / 5m" ? "5" : profile == "Scalp · 1H / 15m / 1m" ? "1" : timeframe.period

tfName(string tf) =>
    s = timeframe.in_seconds(tf)
    s >= 86400 ? "D1" : s >= 3600 ? str.tostring(math.round(s / 3600)) + "H" : str.tostring(math.round(s / 60)) + "m"

bName = tfName(bTF)
sName = tfName(sTF)
eName = tfName(timeframe.period)
tfOk  = timeframe.in_seconds(timeframe.period) == timeframe.in_seconds(eTF)

atr = ta.atr(14)

// the last closed candle (current bar in the HTF context) traded into an FVG made by earlier candles
fvgTouch(bool bull) =>
    hit = false
    for i = 1 to 10
        if bull and high[i + 2] < low[i] and low <= low[i] and low >= high[i + 2] - (low[i] - high[i + 2])
            hit := true
        if not bull and low[i + 2] > high[i] and high >= high[i] and high <= low[i + 2] + (low[i + 2] - high[i])
            hit := true
    hit

// closed bias candles: 1 = last (C2 / C3 candidate), 2 = before it, 3 = before that
[bo1, bh1, bl1, bc1, bt1, bo2, bh2, bl2, bc2, bh3, bl3, bLowN, bHighN, bFvgB, bFvgS] = request.security(syminfo.tickerid, bTF, [open[1], high[1], low[1], close[1], time[1], open[2], high[2], low[2], close[2], high[3], low[3], ta.lowest(low, swingN)[2], ta.highest(high, swingN)[2], fvgTouch(true)[1], fvgTouch(false)[1]], lookahead = barmerge.lookahead_on)
// closed structure candles
[so1, sh1, sl1, sc1, st1, so2, sh2, sl2, sc2, st2, sh3, sl3] = request.security(syminfo.tickerid, sTF, [open[1], high[1], low[1], close[1], time[1], open[2], high[2], low[2], close[2], time[2], high[3], low[3]], lookahead = barmerge.lookahead_on)
biasStart = time(bTF)
biasEnd   = time_close(bTF)
newBias   = bt1 != bt1[1]
newStruct = st1 != st1[1]
inSess    = not useSess or not na(time(timeframe.period, sess, tz))

// ───────────────────────────── types and state
type Trade
    bool         bull
    int          state             // 1 open, 2 closed
    float        entry
    float        sl
    float        tp1
    float        tp2
    float        risk
    bool         tp1Hit = false
    int          bSig
    label        sig
    label        res
    box          pBox
    box          rBox
    array<line>  lines
    array<label> labels

var Trade cur   = na
var trades      = array.new<Trade>()
var biasObjs    = array.new<line>()
var biasLbls    = array.new<label>()
var structObjs  = array.new<line>()
var structLbls  = array.new<label>()
var cisdObjs    = array.new<line>()
var fvgBoxes    = array.new<box>()

// bias
var int    bDir    = 0
var float  bKey    = na
var float  bInval  = na
var float  bTarget = na
var string bType   = ""
var string bKeyTxt = ""
var int    tradesBias = 0
// structure
var int    sDir   = 0
var int    sLeft  = 0
var float  sInval = na
var int    sFrom  = na
var string sType  = ""
var bool   sCisdOk = false
var bool   structOk = false
// entry CISD engine
var bool  prevDn = false
var bool  prevUp = false
var bool  pendBull = false
var bool  pendBear = false
var float bullLevel = na
var float bearLevel = na
var float legLow  = na
var float legHigh = na
var int   tLow  = na
var int   tHigh = na
var int   bRunDn = na
var int   bRunUp = na
var int   cisdBullT = na
var int   cisdBearT = na
var float cisdBullLow  = na
var float cisdBearHigh = na
// record
var int   nTrades = 0
var int   nWins   = 0
var float netR    = 0.0

// ───────────────────────────── helpers
px(float v) =>
    str.tostring(v, format.mintick)

fmtR(float r) =>
    (r >= 0 ? "+" : "−") + str.tostring(math.abs(r), "0.0") + "R"

notify(string msg) =>
    if alOn
        alert("TTFM · " + syminfo.ticker + " · " + msg, alert.freq_once_per_bar_close)

keep(array<line> ls, array<label> lbs, int n) =>
    while ls.size() > n
        ls.shift().delete()
    while lbs.size() > n
        lbs.shift().delete()

rOf(Trade t, float p) =>
    (t.bull ? p - t.entry : t.entry - p) / t.risk

method wipe(Trade t) =>
    for l in t.lines
        l.delete()
    for lb in t.labels
        lb.delete()
    t.lines.clear()
    t.labels.clear()

tag(Trade t, int x, float y, string s, color c) =>
    t.labels.push(label.new(x, y, s, color = #00000000, style = label.style_label_left, textcolor = c, size = size.small))

drawLive(Trade t) =>
    t.wipe()
    xr = bar_index + 6
    t.pBox.set_right(xr)
    t.rBox.set_right(xr)
    t.lines.push(line.new(t.bSig, t.entry, xr, t.entry, color = cText, width = 2))
    t.lines.push(line.new(t.bSig, t.sl, xr, t.sl, color = cLoss, width = 2))
    t.lines.push(line.new(t.bSig, t.tp2, xr, t.tp2, color = cWin, width = 2))
    tag(t, xr, t.entry, "Entry  " + px(t.entry), cText)
    tag(t, xr, t.sl, "Stop  " + px(t.sl) + "   " + fmtR(rOf(t, t.sl)), cLoss)
    tag(t, xr, t.tp2, "TP  " + px(t.tp2) + "   " + fmtR(rOf(t, t.tp2)), cWin)
    if math.abs(t.tp1 - t.tp2) > syminfo.mintick
        t.lines.push(line.new(t.bSig, t.tp1, xr, t.tp1, color = cWin, style = line.style_dotted))
        tag(t, xr, t.tp1, "TP1 ½  " + px(t.tp1) + "   " + fmtR(rOf(t, t.tp1)) + (t.tp1Hit ? "  ✓" : ""), cWin)

newTrade(bool bull, float entry, float sl, float tp1, float tp2, string why) =>
    t = Trade.new(bull, 1, entry, sl, tp1, tp2, math.abs(entry - sl), bSig = bar_index, lines = array.new<line>(), labels = array.new<label>())
    if showTrades
        t.sig  := label.new(bar_index, bull ? low : high, bull ? "BUY" : "SELL", yloc = bull ? yloc.belowbar : yloc.abovebar, style = bull ? label.style_label_up : label.style_label_down, color = bull ? cWin : cLoss, textcolor = cOnPill, size = size.small, tooltip = why)
        t.pBox := box.new(bar_index, math.max(entry, tp2), bar_index + 6, math.min(entry, tp2), border_color = #00000000, bgcolor = color.new(cWin, 86))
        t.rBox := box.new(bar_index, math.max(entry, sl), bar_index + 6, math.min(entry, sl), border_color = #00000000, bgcolor = color.new(cLoss, 86))
        drawLive(t)
    t

finish(Trade t, float exitPx, float r) =>
    t.wipe()
    t.state := 2
    if showTrades
        t.pBox.set_right(bar_index)
        t.rBox.set_right(bar_index)
        t.pBox.set_bgcolor(color.new(cWin, 92))
        t.rBox.set_bgcolor(color.new(cLoss, 92))
        t.res := label.new(bar_index, exitPx, math.abs(r) < 0.05 ? "BE" : fmtR(r), style = label.style_label_left, color = r > 0.05 ? cWin : r < -0.05 ? cLoss : cMuted, textcolor = cOnPill, size = size.tiny)

dropTrade(Trade t) =>
    t.wipe()
    t.sig.delete()
    t.res.delete()
    t.pBox.delete()
    t.rBox.delete()

// the CISD leg extreme swept an entry swing (2 bars each side) or traded into an FVG made before it
entryPoi(bool bull, float ext, int tExt) =>
    ok = not needPoi
    if not ok
        last = 0
        while last < 80 and time[last] > tExt
            last += 1
        for i = last + 3 to last + 78
            if bull and low[i] < low[i - 1] and low[i] < low[i - 2] and low[i] < low[i + 1] and low[i] < low[i + 2] and ext < low[i]
                ok := true
            if not bull and high[i] > high[i - 1] and high[i] > high[i - 2] and high[i] > high[i + 1] and high[i] > high[i + 2] and ext > high[i]
                ok := true
        for i = last + 1 to last + 12
            if bull and high[i + 2] < low[i] and ext <= low[i] and ext >= high[i + 2] - (low[i] - high[i + 2])
                ok := true
            if not bull and low[i + 2] > high[i] and ext >= high[i] and ext <= low[i + 2] + (low[i + 2] - high[i])
                ok := true
    ok

// ───────────────────────────── main (closed bars only)
bool sigBuy  = false
bool sigSell = false

if barstate.isconfirmed and tfOk
    // ---- 1. bias: the bias candle that just closed
    if newBias and not na(bh3)
        poiB = poiMode == "Any" or bl1 < bLowN or bFvgB or (poiMode == "Swing / FVG / opposing candle" and bc2 < bo2)
        poiS = poiMode == "Any" or bh1 > bHighN or bFvgS or (poiMode == "Swing / FVG / opposing candle" and bc2 > bo2)
        bullC2 = bl1 < bl2 and bc1 > bl2 and poiB
        bearC2 = bh1 > bh2 and bc1 < bh2 and poiS
        if bullC2 and bearC2
            up = bc1 > (bh1 + bl1) / 2
            bullC2 := up
            bearC2 := not up
        bullC3 = useC3 and bl2 < bl3 and bc2 < bl3 and bc1 > math.max(bo2, bc2) and bl1 >= bl2
        bearC3 = useC3 and bh2 > bh3 and bc2 > bh3 and bc1 < math.min(bo2, bc2) and bh1 <= bh2
        prevDir = bDir
        if bullC2
            bDir := 1
            bType := "C2"
            bKey := (math.min(bo1, bc1) + bl1) / 2
            bKeyTxt := "wick 50%"
            bInval := bl1
        else if bearC2
            bDir := -1
            bType := "C2"
            bKey := (math.max(bo1, bc1) + bh1) / 2
            bKeyTxt := "wick 50%"
            bInval := bh1
        else if bullC3
            bDir := 1
            bType := "C3"
            bKey := (bh1 + bl1) / 2
            bKeyTxt := "EQ"
            bInval := bl2
        else if bearC3
            bDir := -1
            bType := "C3"
            bKey := (bh1 + bl1) / 2
            bKeyTxt := "EQ"
            bInval := bh2
        else if useCont and prevDir == 1 and bc1 > bh2
            bDir := 1
            bType := "Cont"
            bKey := (bh1 + bl1) / 2
            bKeyTxt := "EQ"
            bInval := math.min(bl1, bl2)
        else if useCont and prevDir == -1 and bc1 < bl2
            bDir := -1
            bType := "Cont"
            bKey := (bh1 + bl1) / 2
            bKeyTxt := "EQ"
            bInval := math.max(bh1, bh2)
        else
            bDir := 0
            bType := ""
        bTarget := bDir == 1 ? math.max(bh1, bh2) : bDir == -1 ? math.min(bl1, bl2) : na
        tradesBias := 0
        sDir := 0
        if bDir != 0
            notify(bName + (bDir == 1 ? " bullish " : " bearish ") + bType + " bias · " + bKeyTxt + " " + px(bKey))
            if showBias
                c   = bDir == 1 ? cBull : cBear
                nb  = math.round(timeframe.in_seconds(bTF) / timeframe.in_seconds())
                xr  = bar_index + nb
                biasObjs.push(line.new(bar_index, bKey, xr, bKey, color = cGold, style = line.style_dashed))
                biasLbls.push(label.new(xr, bKey, bName + " " + bKeyTxt + "  " + px(bKey), color = #00000000, style = label.style_label_left, textcolor = cGold, size = size.tiny))
                biasObjs.push(line.new(bar_index, bInval, xr, bInval, color = color.new(c, 40)))
                biasLbls.push(label.new(xr, bInval, bName + " " + bType + (bDir == 1 ? " low" : " high") + "  " + px(bInval), color = #00000000, style = label.style_label_left, textcolor = c, size = size.tiny))
                biasObjs.push(line.new(bar_index, bTarget, xr, bTarget, color = color.new(c, 40), style = line.style_dotted))
                biasLbls.push(label.new(xr, bTarget, "draw · " + bName + (bDir == 1 ? " high " : " low ") + px(bTarget), color = #00000000, style = label.style_label_left, textcolor = c, size = size.tiny))
                biasLbls.push(label.new(bar_index, bDir == 1 ? low : high, bName + " " + (bDir == 1 ? "▲ " : "▼ ") + bType, yloc = bDir == 1 ? yloc.belowbar : yloc.abovebar, color = color.new(c, 15), style = bDir == 1 ? label.style_label_up : label.style_label_down, textcolor = cOnPill, size = size.tiny))
                keep(biasObjs, biasLbls, 18)

    // ---- 2. structure: the structure candle that just closed
    if newStruct and not na(sh3)
        if sDir != 0
            sLeft -= 1
            if sLeft <= 0
                sDir := 0
        // the bias wick 50% / EQ must hold on structure closes
        if bDir == 1 and sc1 < bKey
            bDir := 0
            notify(bName + " bullish bias cancelled: " + sName + " closed below " + bKeyTxt)
        if bDir == -1 and sc1 > bKey
            bDir := 0
            notify(bName + " bearish bias cancelled: " + sName + " closed above " + bKeyTxt)
        if bDir == 0
            sDir := 0
        else if st1 >= biasStart
            sp  = timeframe.in_seconds(sTF) * 1000
            tol = 0.25 * nz(atr)
            bull = bDir == 1
            c2 = bull ? sl1 < sl2 and sc1 > sl2 : sh1 > sh2 and sc1 < sh2
            c3 = useC3 and (bull ? sl2 < sl3 and sc2 < sl3 and sc1 > math.max(so2, sc2) and sl1 >= sl2 : sh2 > sh3 and sc2 > sh3 and sc1 < math.min(so2, sc2) and sh1 <= sh2)
            if c2 or c3
                swing = bull ? (c2 ? sl1 : sl2) : (c2 ? sh1 : sh2)
                fromT = c2 ? st1 : st2
                cisd  = bull ? (not na(cisdBullT) and cisdBullT >= fromT and cisdBullT < st1 + sp and cisdBullLow <= swing + tol) : (not na(cisdBearT) and cisdBearT >= fromT and cisdBearT < st1 + sp and cisdBearHigh >= swing - tol)
                c = bull ? cBull : cBear
                if not needCisd or cisd
                    sDir    := bDir
                    sInval  := swing
                    sLeft   := structLife
                    sFrom   := st1 + sp
                    sType   := c2 ? "C2" : "C3"
                    sCisdOk := cisd
                    notify(sName + (bull ? " bullish " : " bearish ") + sType + " confirmed · protected " + px(swing))
                if showStruct
                    nb = math.round(timeframe.in_seconds(sTF) / timeframe.in_seconds()) * structLife
                    ok = not needCisd or cisd
                    structObjs.push(line.new(bar_index - math.round(timeframe.in_seconds(sTF) / timeframe.in_seconds()), swing, bar_index + nb, swing, color = ok ? c : cMuted, style = ok ? line.style_solid : line.style_dotted, width = ok ? 2 : 1))
                    structLbls.push(label.new(bar_index, swing, sName + " " + (c2 ? "C2" : "C3") + (cisd ? " · CISD ✓" : " · no CISD"), color = #00000000, style = bull ? label.style_label_up : label.style_label_down, textcolor = ok ? c : cMuted, size = size.tiny))
                    keep(structObjs, structLbls, 16)

    // ---- 3. entry timeframe: CISD engine, POI and entries
    buf     = slBufAtr * nz(atr)
    isDn    = close < open
    isUp    = close > open
    elapsed = (time_close - biasStart) / (timeframe.in_seconds(bTF) * 1000.0)
    timeOk  = elapsed <= maxElapsed and inSess
    structOk := needStruct ? (sDir == bDir and sDir != 0 and time >= sFrom) : bDir != 0

    // manage the open trade (from the bar after the entry)
    if not na(cur) and cur.state == 1
        t = cur
        if t.bull ? low <= t.sl : high >= t.sl
            r = t.tp1Hit ? 0.5 * rOf(t, t.tp1) + 0.5 * rOf(t, t.sl) : rOf(t, t.sl)
            finish(t, t.sl, r)
            nTrades += 1
            netR    += r
            nWins   += r > 0.05 ? 1 : 0
            notify((t.bull ? "BUY" : "SELL") + " closed " + fmtR(r))
        else
            if not t.tp1Hit and (t.bull ? high >= t.tp1 : low <= t.tp1)
                t.tp1Hit := true
            if t.bull ? high >= t.tp2 : low <= t.tp2
                r = 0.5 * rOf(t, t.tp1) + 0.5 * rOf(t, t.tp2)
                finish(t, t.tp2, r)
                nTrades += 1
                netR    += r
                nWins   += 1
                notify((t.bull ? "BUY" : "SELL") + " target hit " + fmtR(r))
        if t.state == 1 and showTrades
            drawLive(t)

    flat = na(cur) or cur.state >= 2

    // bullish CISD
    if pendBull and low < legLow
        legLow := low
        tLow   := time
    if pendBull and close > bullLevel
        pendBull    := false
        cisdBullT   := time
        cisdBullLow := legLow
        if not na(cur) and cur.state == 1 and cur.bull and cur.tp1Hit and trailOn and legLow - buf > cur.sl
            cur.sl := legLow - buf
        if showCisd and bDir == 1
            cisdObjs.push(line.new(bRunDn, bullLevel, bar_index, bullLevel, color = cBull, style = line.style_dashed))
            if cisdObjs.size() > 12
                cisdObjs.shift().delete()
        inval = needStruct ? sInval : bInval
        if flat and bDir == 1 and structOk and timeOk and tradesBias < maxPerBias and legLow > inval and entryPoi(true, legLow, tLow)
            entry = close
            sl    = legLow - buf
            risk  = entry - sl
            tp1   = entry + tp1R * risk
            tp2   = tpMode == "HTF liquidity" and bTarget > tp1 ? bTarget : entry + tp2R * risk
            why   = bName + " " + bType + " · " + sName + " " + sType + " · " + eName + " CISD"
            cur  := newTrade(true, entry, sl, tp1, tp2, why)
            trades.push(cur)
            tradesBias += 1
            sigBuy := true
            notify("BUY " + px(entry) + " · stop " + px(sl) + " · TP " + px(tp2) + " (" + why + ")")
    if isDn and not prevDn
        bullLevel := open
        legLow    := low
        tLow      := time
        bRunDn    := bar_index
        pendBull  := true

    // bearish CISD
    flat := na(cur) or cur.state >= 2
    if pendBear and high > legHigh
        legHigh := high
        tHigh   := time
    if pendBear and close < bearLevel
        pendBear     := false
        cisdBearT    := time
        cisdBearHigh := legHigh
        if not na(cur) and cur.state == 1 and not cur.bull and cur.tp1Hit and trailOn and legHigh + buf < cur.sl
            cur.sl := legHigh + buf
        if showCisd and bDir == -1
            cisdObjs.push(line.new(bRunUp, bearLevel, bar_index, bearLevel, color = cBear, style = line.style_dashed))
            if cisdObjs.size() > 12
                cisdObjs.shift().delete()
        inval = needStruct ? sInval : bInval
        if flat and bDir == -1 and structOk and timeOk and tradesBias < maxPerBias and legHigh < inval and entryPoi(false, legHigh, tHigh)
            entry = close
            sl    = legHigh + buf
            risk  = sl - entry
            tp1   = entry - tp1R * risk
            tp2   = tpMode == "HTF liquidity" and bTarget < tp1 ? bTarget : entry - tp2R * risk
            why   = bName + " " + bType + " · " + sName + " " + sType + " · " + eName + " CISD"
            cur  := newTrade(false, entry, sl, tp1, tp2, why)
            trades.push(cur)
            tradesBias += 1
            sigSell := true
            notify("SELL " + px(entry) + " · stop " + px(sl) + " · TP " + px(tp2) + " (" + why + ")")
    if isUp and not prevUp
        bearLevel := open
        legHigh   := high
        tHigh     := time
        bRunUp    := bar_index
        pendBear  := true
    if isDn or isUp
        prevDn := isDn
        prevUp := isUp

    // entry fair value gaps in the bias direction
    if showFvg and bDir == 1 and high[2] < low
        fvgBoxes.push(box.new(bar_index - 2, low, bar_index + 8, high[2], border_color = #00000000, bgcolor = color.new(cBull, 85)))
    if showFvg and bDir == -1 and low[2] > high
        fvgBoxes.push(box.new(bar_index - 2, low[2], bar_index + 8, high, border_color = #00000000, bgcolor = color.new(cBear, 85)))
    while fvgBoxes.size() > 8
        fvgBoxes.shift().delete()

    while trades.size() > keepN + 1
        dropTrade(trades.shift())

bgcolor(shadeBias and bDir == 1 ? color.new(cBull, 95) : shadeBias and bDir == -1 ? color.new(cBear, 95) : na, title = "Bias")
alertcondition(sigBuy, "TTFM BUY", "TTFM: BUY")
alertcondition(sigSell, "TTFM SELL", "TTFM: SELL")

// ───────────────────────────── panel
tPos = panelPos == "Top right" ? position.top_right : panelPos == "Bottom right" ? position.bottom_right : panelPos == "Top left" ? position.top_left : position.bottom_left
tSz  = panelSize == "Tiny" ? size.tiny : panelSize == "Normal" ? size.normal : size.small

var table panel  = table.new(tPos, 2, 10, bgcolor = cPanel, frame_color = cBorder, frame_width = 1, border_color = cPanel, border_width = 3)

cell(int c, int r, string s, color tc, color bg) =>
    table.cell(panel, c, r, s, text_color = tc, text_size = tSz, text_halign = c == 0 ? text.align_left : text.align_right, bgcolor = bg)

if showPanel and barstate.islast
    t      = cur
    active = not na(t) and t.state == 1
    pill   = not tfOk ? "USE A " + tfName(eTF) + " CHART" : active ? (t.bull ? "BUY · LIVE" : "SELL · LIVE") : bDir == 0 ? "NO BIAS" : structOk ? "WAITING FOR " + eName + " CISD" : "WAITING FOR " + sName + " SWING"
    pillBg = not tfOk ? cGold : active ? (t.bull ? cWin : cLoss) : cHead
    pillTx = not tfOk or active ? cOnPill : bDir == 1 ? cBull : bDir == -1 ? cBear : cMuted
    cell(0, 0, "TTFM · " + bName + " / " + sName + " / " + tfName(eTF), cText, cHead)
    cell(1, 0, pill, pillTx, pillBg)
    cell(0, 1, "Bias " + bName, cMuted, cPanel)
    cell(1, 1, bDir == 0 ? "none" : (bDir == 1 ? "▲ " : "▼ ") + bType + " · " + bKeyTxt + " " + px(bKey), bDir == 1 ? cBull : bDir == -1 ? cBear : cMuted, cPanel)
    cell(0, 2, "Draw on liquidity", cMuted, cPanel)
    cell(1, 2, na(bTarget) or bDir == 0 ? "—" : px(bTarget), cText, cPanel)
    cell(0, 3, "Swing " + sName, cMuted, cPanel)
    cell(1, 3, not needStruct ? "not required" : sDir != 0 and sDir == bDir ? (sDir == 1 ? "▲ " : "▼ ") + sType + (sCisdOk ? " · CISD ✓" : "") + " · protected " + px(sInval) : "waiting for " + sName + " C2 / C3", sDir != 0 and sDir == bDir ? (sDir == 1 ? cBull : cBear) : cMuted, cPanel)
    cell(0, 4, "Time", cMuted, cPanel)
    elapsedNow = (timenow - biasStart) / (timeframe.in_seconds(bTF) * 1000.0)
    cell(1, 4, str.tostring(math.min(elapsedNow, 1) * 100, "0") + "% of " + bName + " candle" + (elapsedNow > maxElapsed ? " · too late" : ""), elapsedNow > maxElapsed ? cMuted : cText, cPanel)
    cell(0, 5, "Entry", cMuted, cPanel)
    cell(1, 5, active ? px(t.entry) : "—", cText, cPanel)
    cell(0, 6, "Stop", cMuted, cPanel)
    cell(1, 6, active ? px(t.sl) + "  " + fmtR(rOf(t, t.sl)) : "—", active ? cLoss : cMuted, cPanel)
    cell(0, 7, "Target", cMuted, cPanel)
    cell(1, 7, active ? px(t.tp2) + "  " + fmtR(rOf(t, t.tp2)) : "—", active ? cWin : cMuted, cPanel)
    wr = nTrades > 0 ? 100.0 * nWins / nTrades : 0.0
    cell(0, 8, "Record", cMuted, cPanel)
    cell(1, 8, str.tostring(nTrades) + " trades · " + str.tostring(wr, "0") + "% · " + fmtR(netR), netR >= 0 ? cWin : cLoss, cPanel)
    cell(0, 9, "Backtest (MT5, XAUUSD, Day)", cMuted, cPanel)
    cell(1, 9, "2024–26: PF 1.38 · +16.9% · DD 7%", cMuted, cPanel)

Key inputs

Key inputs of Fractal Candle Model
InputDefaultDescription
ProfileDay (Daily bias, 4H structure, 15m entry)Timeframe trio. Intraday uses D1/H1/M5, Scalp uses H1/M15/M1.
Candle 3 closuresOnAlso accept failed Candle 2 sweeps followed by a Candle 3 closure.
Swing lookback5Candles used to define a swing point of interest.

Educational tools, not advice. Indicators and Expert Advisors describe price; they do not predict it, and past behaviour does not guarantee future results. Test everything on a demo account first and never risk money you cannot afford to lose. See our disclaimer.

Keep exploring

Related tradingview indicators