// © FXPIPS ONLINE — https://fxpips.online/tradingview-indicators/volume-delta-approx/
// License: MIT. Educational tool — not financial advice. Trading involves substantial risk.
// NOTE: this is an APPROXIMATION. It does not read real bid/ask trades. It splits each bar's total
// volume into "buy" and "sell" parts based on where the close sits inside the bar's high-low range.
//@version=6
indicator("FXP Volume Delta (Approximation)", shorttitle = "FXP Delta Approx", overlay = false, format = format.volume)

// ── Inputs ─────────────────────────────────────────────────────────────
grpD      = "Delta"
dispMode  = input.string("Cumulative delta", "Display", options = ["Cumulative delta", "Bar delta"], group = grpD)
resetSel  = input.string("Session", "Reset cumulative delta", options = ["Never", "Session", "Week", "Month"], group = grpD)
dojiSplit = input.bool(true, "Split zero-range bars 50/50", group = grpD, tooltip = "When high equals low the close position is undefined. On: half buy, half sell. Off: the whole bar is assigned by close versus previous close.")

grpS      = "Signal"
sigLen    = input.int(21, "Signal EMA length (cumulative mode)", minval = 1, group = grpS)
showSig   = input.bool(true, "Show signal-line crosses", group = grpS)

grpVis    = "Display"
buyCol    = input.color(color.teal, "Buying color", group = grpVis)
sellCol   = input.color(color.red, "Selling color", group = grpVis)

// ── Approximate buy / sell split ──────────────────────────────────────
float vol   = nz(volume)
float barRange = high - low
float buyFrac = barRange > 0 ? (close - low) / barRange : dojiSplit ? 0.5 : close > close[1] ? 1.0 : close < close[1] ? 0.0 : 0.5
float buyVol  = vol * buyFrac
float sellVol = vol - buyVol
float delta   = buyVol - sellVol

// ── Cumulative delta with optional reset ───────────────────────────────
bool chgD = timeframe.change("D")
bool chgW = timeframe.change("W")
bool chgM = timeframe.change("M")
bool doReset = resetSel == "Session" ? chgD : resetSel == "Week" ? chgW : resetSel == "Month" ? chgM : false

var float cumDelta = 0.0
cumDelta := doReset ? delta : cumDelta + delta
float cdSignal = ta.ema(cumDelta, sigLen)

bool isCum   = dispMode == "Cumulative delta"
bool rawUp   = ta.crossover(cumDelta, cdSignal)
bool rawDn   = ta.crossunder(cumDelta, cdSignal)
bool crossUp = isCum and rawUp and barstate.isconfirmed and not doReset
bool crossDn = isCum and rawDn and barstate.isconfirmed and not doReset

float cumVol = ta.cum(vol)
if barstate.islast and cumVol == 0
    runtime.error("This symbol provides no volume data, so delta cannot be approximated.")

// ── Plots ──────────────────────────────────────────────────────────────
plot(isCum ? na : delta, "Bar delta (approx.)", style = plot.style_columns, color = delta >= 0 ? buyCol : sellCol)
plot(isCum ? cumDelta : na, "Cumulative delta (approx.)", color = cumDelta >= cdSignal ? buyCol : sellCol, linewidth = 2, style = plot.style_linebr)
plot(isCum and not doReset ? cdSignal : na, "Signal EMA", color = color.new(color.gray, 10), style = plot.style_linebr)
hline(0, "Zero", color = color.new(color.gray, 60), linestyle = hline.style_dotted)
plot(buyVol, "Approx. buy volume", display = display.data_window)
plot(sellVol, "Approx. sell volume", display = display.data_window)

plotshape(showSig and crossUp ? cumDelta : na, "Cum. delta above signal", shape.circle, location.absolute, buyCol, size = size.tiny)
plotshape(showSig and crossDn ? cumDelta : na, "Cum. delta below signal", shape.circle, location.absolute, sellCol, size = size.tiny)

// ── Alerts ─────────────────────────────────────────────────────────────
alertcondition(crossUp, "Cumulative delta crossed above signal", "FXP Delta Approx: approximate cumulative delta crossed above its EMA on {{ticker}} {{interval}}")
alertcondition(crossDn, "Cumulative delta crossed below signal", "FXP Delta Approx: approximate cumulative delta crossed below its EMA on {{ticker}} {{interval}}")
