// © FXPIPS ONLINE — https://fxpips.online/tradingview-indicators/auto-fibonacci/
// License: MIT. Educational tool — not financial advice. Trading involves substantial risk.
//@version=6
// Draws Fibonacci retracement and extension levels between the latest significant swing
// high and swing low. Swings come from confirmed pivots, alternate high/low (a zigzag),
// and must be at least a set number of ATRs apart to count as significant.
indicator("FXP Auto Fibonacci", shorttitle = "FXP Auto Fib", overlay = true, max_lines_count = 50, max_labels_count = 50)

// ── Inputs ─────────────────────────────────────────────────────────────
grpSw      = "Swings"
swingLen   = input.int(10, "Swing length (bars each side)", minval = 2, maxval = 100, group = grpSw)
minAtr     = input.float(2.0, "Minimum swing size (x ATR)", minval = 0.0, step = 0.5, group = grpSw)
atrLen     = input.int(14, "ATR length", minval = 1, group = grpSw)

grpLv      = "Levels"
retrIn     = input.string("0, 0.236, 0.382, 0.5, 0.618, 0.786, 1", "Retracement levels", group = grpLv, tooltip = "Comma-separated ratios. 0 = swing end, 1 = swing start.")
extIn      = input.string("1.272, 1.618", "Extension levels", group = grpLv, tooltip = "Comma-separated ratios projected beyond the swing end, in the direction of the swing.")
showExt    = input.bool(true, "Show extensions", group = grpLv)

grpDisp    = "Display"
extendBars = input.int(15, "Extend lines right (bars)", minval = 0, maxval = 200, group = grpDisp)
showPrices = input.bool(true, "Show prices", group = grpDisp)
showSwing  = input.bool(true, "Draw the swing line", group = grpDisp)
colRetr    = input.color(#2962ff, "Retracement levels", group = grpDisp)
colKey     = input.color(#ff9800, "0.5 - 0.618 zone", group = grpDisp)
colExt     = input.color(#9c27b0, "Extension levels", group = grpDisp)

// ── Zigzag of significant swings ───────────────────────────────────────
float atr = ta.atr(atrLen)
ph = ta.pivothigh(high, swingLen, swingLen)
pl = ta.pivotlow(low, swingLen, swingLen)

var float zzHigh    = na
var int   zzHighBar = na
var float zzLow     = na
var int   zzLowBar  = na
var int   lastType  = 0    // 1 = last swing was a high, -1 = a low
bool anchorsChanged = false

if not na(ph)
    if lastType == 1
        if ph > zzHigh
            zzHigh    := ph
            zzHighBar := bar_index - swingLen
            anchorsChanged := true
    else if na(zzLow) or ph - zzLow >= minAtr * nz(atr)
        zzHigh    := ph
        zzHighBar := bar_index - swingLen
        lastType  := 1
        anchorsChanged := true

if not na(pl)
    if lastType == -1
        if pl < zzLow
            zzLow    := pl
            zzLowBar := bar_index - swingLen
            anchorsChanged := true
    else if na(zzHigh) or zzHigh - pl >= minAtr * nz(atr)
        zzLow    := pl
        zzLowBar := bar_index - swingLen
        lastType := -1
        anchorsChanged := true

bool  valid    = not na(zzHigh) and not na(zzLow) and lastType != 0
bool  upSwing  = lastType == 1    // low -> high, retracements measured down from the high
float swStart  = upSwing ? zzLow : zzHigh
float swEnd    = upSwing ? zzHigh : zzLow
int   startBar = upSwing ? zzLowBar : zzHighBar
int   endBar   = upSwing ? zzHighBar : zzLowBar
float swRange  = swEnd - swStart

levelPrice(float r) =>
    swEnd - swRange * r

extPrice(float e) =>
    swStart + swRange * e

// ── Drawing (last bar only) ────────────────────────────────────────────
var array<line>  fibLines  = array.new<line>()
var array<label> fibLabels = array.new<label>()
var line swingLine = na

parseLevels(string s) =>
    array<string> parts = str.split(str.replace_all(s, " ", ""), ",")
    array<float> out = array.new<float>()
    for p in parts
        float v = str.tonumber(p)
        if not na(v)
            out.push(v)
    out

drawLevel(float price, string txt, color col, int left, int right) =>
    fibLines.push(line.new(left, price, right, price, color = col, width = 1))
    string full = showPrices ? txt + " (" + str.tostring(price, format.mintick) + ")" : txt
    fibLabels.push(label.new(right, price, full, style = label.style_label_left, color = color.new(color.black, 100), textcolor = col, size = size.small))
    true

if barstate.islast
    while fibLines.size() > 0
        line.delete(fibLines.pop())
    while fibLabels.size() > 0
        label.delete(fibLabels.pop())
    line.delete(swingLine)
    if valid
        int left  = math.min(startBar, endBar)
        int right = bar_index + extendBars
        array<float> retr = parseLevels(retrIn)
        for r in retr
            color c = r >= 0.5 and r <= 0.618 ? colKey : colRetr
            drawLevel(levelPrice(r), str.tostring(r), c, left, right)
        if showExt
            array<float> ext = parseLevels(extIn)
            for e in ext
                drawLevel(extPrice(e), str.tostring(e), colExt, left, right)
        if showSwing
            swingLine := line.new(startBar, swStart, endBar, swEnd, color = color.new(color.gray, 40), style = line.style_dashed, width = 1)

// ── Alerts ─────────────────────────────────────────────────────────────
float lvl50  = levelPrice(0.5)
float lvl618 = levelPrice(0.618)
float zoneHi = math.max(lvl50, lvl618)
float zoneLo = math.min(lvl50, lvl618)
bool  inZone = valid and close <= zoneHi and close >= zoneLo
bool  enterZone = barstate.isconfirmed and inZone and not inZone[1] and not anchorsChanged

alertcondition(enterZone, "Price entered 0.5 - 0.618 zone", "{{ticker}} {{interval}}: price closed inside the 0.5 - 0.618 retracement zone at {{close}}")
alertcondition(anchorsChanged, "New Fibonacci swing", "{{ticker}} {{interval}}: Fibonacci anchors moved to a new swing")
