//@version=6
// Tideline | Trend indicator by Flo Diaconu (fl3ws.xyz)
// Paste into TradingView Pine Editor, Save, then Add to chart.
indicator("Tideline", shorttitle = "Tideline", overlay = true)

// ── Core ────────────────────────────────────────────────
atrLen = input.int(10, "ATR length", minval = 1, group = "Core")
mult   = input.float(3.0, "ATR multiplier", minval = 0.1, step = 0.1, group = "Core",
     tooltip = "Higher = fewer flips and smoother trend. Lower = faster but more whipsaws.")
htf    = input.timeframe("", "Trend timeframe", group = "Core",
     tooltip = "Blank = chart timeframe. Use a timeframe equal to or higher than the chart.")

// ── Signal filters ──────────────────────────────────────
confirmBars = input.int(1, "Confirm bars", minval = 1, group = "Filters",
     tooltip = "Chart bars the new trend must hold before BUY/SELL fires. 1 = fire on the flip bar.")
useAdx = input.bool(true, "Chop filter (ADX)", group = "Filters",
     tooltip = "Suppresses signals while the market is ranging. The line fades while chop is detected.")
adxLen = input.int(14, "ADX length", minval = 1, group = "Filters")
adxMin = input.float(20, "Min ADX to allow signals", minval = 0, group = "Filters")
useMtf = input.bool(false, "Require higher-timeframe agreement", group = "Filters",
     tooltip = "Only BUY when the higher timeframe trend is also bullish, and vice versa.")
mtfTf  = input.timeframe("1W", "Higher timeframe", group = "Filters")

// ── Display ─────────────────────────────────────────────
showLine   = input.bool(true,  "Show line",              group = "Display")
showFill   = input.bool(true,  "Shade price to line",    group = "Display")
showLabels = input.bool(true,  "Show BUY / SELL labels", group = "Display")
colorBars  = input.bool(false, "Color candles by trend", group = "Display")
showTable  = input.bool(true,  "Show trend status box",  group = "Display")
bullCol    = input.color(#00c46a, "Bull color", group = "Display")
bearCol    = input.color(#ff3b5c, "Bear color", group = "Display")

// ── Trend calculation ───────────────────────────────────
[stC, dirC] = ta.supertrend(mult, atrLen)
[stH, dirH] = request.security(syminfo.tickerid, htf, [stC[1], dirC[1]], lookahead = barmerge.lookahead_on)

useHTF = htf != "" and htf != timeframe.period
st     = useHTF ? stH : stC
dir    = useHTF ? dirH : dirC

bull = dir < 0
flip = bull != bull[1]

var int run = 0
run := flip ? 1 : run + 1

// ── Filters ─────────────────────────────────────────────
[diPlus, diMinus, adx] = ta.dmi(adxLen, adxLen)
trending = not useAdx or adx >= adxMin

mtfDir  = request.security(syminfo.tickerid, mtfTf, dirC[1], lookahead = barmerge.lookahead_on)
mtfBull = mtfDir < 0

longOK  = bull     and run >= confirmBars and trending and (not useMtf or mtfBull)
shortOK = not bull and run >= confirmBars and trending and (not useMtf or not mtfBull)

// One signal per trend: fires the first bar all conditions line up
var bool fired = false
if flip
    fired := false
sigUp = longOK and not fired
sigDn = shortOK and not fired
if sigUp or sigDn
    fired := true

// ── Plots ───────────────────────────────────────────────
lineBull = trending ? bullCol : color.new(bullCol, 60)
lineBear = trending ? bearCol : color.new(bearCol, 60)

upPlot    = plot(showLine and bull ? st : na,     "Bull line", lineBull, 2, plot.style_linebr)
dnPlot    = plot(showLine and not bull ? st : na, "Bear line", lineBear, 2, plot.style_linebr)
pricePlot = plot(ohlc4, "Price", display = display.none, editable = false)

fill(pricePlot, upPlot, showFill ? color.new(bullCol, 88) : na, title = "Bull fill")
fill(pricePlot, dnPlot, showFill ? color.new(bearCol, 88) : na, title = "Bear fill")

plotshape(showLabels and sigUp, "Buy",  shape.labelup,   location.belowbar, bullCol, text = "BUY",  textcolor = color.white, size = size.small)
plotshape(showLabels and sigDn, "Sell", shape.labeldown, location.abovebar, bearCol, text = "SELL", textcolor = color.white, size = size.small)

barcolor(colorBars ? (bull ? bullCol : bearCol) : na)

// ── Status box ──────────────────────────────────────────
dist = math.abs(close - st) / close * 100

var table box = table.new(position.top_right, 1, 1)
if showTable and barstate.islast
    txt = (bull ? "BULLISH" : "BEARISH") + " | " + str.tostring(run) + " bars | stop " + str.tostring(dist, "#.##") + "%" + (trending ? "" : " | CHOP")
    table.cell(box, 0, 0, txt, text_color = color.white, bgcolor = bull ? bullCol : bearCol, text_size = size.normal)

// ── Alerts ──────────────────────────────────────────────
alertcondition(sigUp, "Filtered BUY",  "Tideline: {{ticker}} BUY signal on {{interval}} @ {{close}}")
alertcondition(sigDn, "Filtered SELL", "Tideline: {{ticker}} SELL signal on {{interval}} @ {{close}}")
alertcondition(bull and flip,     "Downtrend to Uptrend", "Tideline: {{ticker}} flipped BULLISH on {{interval}} @ {{close}}")
alertcondition(not bull and flip, "Uptrend to Downtrend", "Tideline: {{ticker}} flipped BEARISH on {{interval}} @ {{close}}")
