Heikin-Ashi Rolling Time Decay Volume Oscillator
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © tkarolak
//@version=5

// ====================
// ==== Background ====
// ====================

// This script defines a custom Heikin-Ashi Rolling Time Decay Volume Oscillator indicator.
// Users can choose candle types and set the rolling window size and smoothing period.
// The indicator calculates time-decayed moving sums of bullish (green) and bearish (red) volume data.
// It generates a volume share oscillator by smoothing and weighting these volumes relative to total volume.
// Traders can gauge market sentiment from the color-coded area plots and horizontal lines.
// Customizable channel levels and gradient fills in overbought/oversold regions enhance usability.
// By interpreting the indicator's values, traders can make informed decisions about market dynamics.

indicator("Heikin-Ashi Rolling Time Decay Volume Oscillator", shorttitle="HA Time Decay Volume Oscillator", format=format.percent, precision=2)

// Custom types
type SettingsTdVol
    string  source
    int     lookback
    int     smoothing

// Settings Group & Tooltips
string gRollingVolumeTdVol  = "Rolling Volume Oscillator Settings"
string ttSourceTdVol        = "Select the type of candles to use for calculations."
string ttLookbackTdVol      = "Set the rolling window size for volume calculations."
string ttSmoothingTdVol     = "Set the smoothing period for the indicator."
string ttChannelTdVol       = "Set the overbought/oversold channel level."

// Initialize the settings for Rolling Volume
SettingsTdVol settingsTdVol =
 SettingsTdVol.new(
   input.string ("Heikin-Ashi Candles", "Source - candles type",    group = gRollingVolumeTdVol, options=["Heikin-Ashi Candles", "Japanese Candles"], tooltip = ttSourceTdVol),
   input.int    (50,                    "Rolling Window",           group = gRollingVolumeTdVol, minval = 4, step = 1, tooltip = ttLookbackTdVol),
   input.int    (3,                     "Smoothing",                group = gRollingVolumeTdVol, minval = 1, maxval = 10, tooltip = ttSmoothingTdVol)
   )
settingsTdVolChannel = input.float(30, "OB/OS Channel",             group = gRollingVolumeTdVol, minval = 1, maxval = 100, step = 1, tooltip = ttChannelTdVol)

// Runtime error indicating the absence of volume data from the data vendor
if barstate.islast and ta.cum(volume) == 0
    runtime.error("No volume is provided by the data vendor.")

// Heiken Ashi Candles
cHA = ohlc4
oHA = float(na)
oHA := na(oHA[1]) ? (open + close) / 2 : (nz(oHA[1]) + nz(cHA[1])) / 2

// Source selection based on user input
float closeUsed = na
float openUsed  = na

if settingsTdVol.source == "Heikin-Ashi Candles"
    closeUsed   := cHA
    openUsed    := oHA
else
    closeUsed   := close
    openUsed    := open

// Calculate the time decay moving sum for red and green volumes
float decaySumRed = 0.0
for i = 0 to settingsTdVol.lookback - 1
    iterationVolume = closeUsed[settingsTdVol.lookback - 1 - i] < openUsed[settingsTdVol.lookback - 1 - i] ? volume[settingsTdVol.lookback - 1 - i] : 0
    weight = i + 1 / settingsTdVol.lookback
    decaySumRed := decaySumRed + iterationVolume * weight

float decaySumGreen = 0.0
for i = 0 to settingsTdVol.lookback - 1
    iterationVolume = closeUsed[settingsTdVol.lookback - 1 - i] >= openUsed[settingsTdVol.lookback - 1 - i] ? volume[settingsTdVol.lookback - 1 - i] : 0
    weight = i + 1 / settingsTdVol.lookback
    decaySumGreen := decaySumGreen + iterationVolume * weight

// Calculate the final volume indicator with time decay and smoothing
float volIndicatorDecayed = math.round(ta.sma(decaySumGreen / (decaySumGreen + decaySumRed), settingsTdVol.smoothing) * 100 - 50, 2)

////////////////////////////////////////////////////////////////////////////////
//                     ====== DRAWING and PLOTTING ======                     //
////////////////////////////////////////////////////////////////////////////////

// Colors for the volume area plot
colorArea = volIndicatorDecayed > 0 ? color.new(color.green, 60) : color.new(color.red, 60)

// Plot the volume indicator
indicatorPlot = plot(volIndicatorDecayed, "Volume Share", color = colorArea, style = plot.style_area)

// Horizontal lines for OB/OS levels
uppermax    = hline(50, "Max", color.new(color.silver, 60))
upper       = hline(settingsTdVolChannel, "OB Extreme", color.new(color.silver, 60))
median      = hline(0, "Median", color.new(color.orange, 60), hline.style_dotted)
lower       = hline(-settingsTdVolChannel, "OS Extreme", color.new(color.silver, 60))
lowermin    = hline(-50, "Min", color.new(color.silver, 60))

// Gradient Fill for OB/OS regions
midLinePlot = plot(0, color = na, editable = false, display = display.none)
fill(indicatorPlot, midLinePlot, 50, settingsTdVolChannel, top_color = color.new(color.green, 0), bottom_color = color.new(color.green, 100), title = "Overbought Gradient Fill")
fill(indicatorPlot, midLinePlot, -settingsTdVolChannel, -50, top_color = color.new(color.red, 100), bottom_color = color.new(color.red, 0), title = "Oversold Gradient Fill")