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
| Input | Default | What it changes |
|---|---|---|
| Profile | Day | Bias, structure and entry timeframes |
| Bias point of interest | Swing / FVG | What a sweep must touch to count |
| Swing lookback | 5 | Size of the swing used as a point of interest |
| Candle 3 closures | On | Adds the second closure type |
| Theme | Auto | Light 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)

