Singular and Cumulative Volume Delta (SVD+CVD)
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/

// Singular Volume Delta and Cumulative Volume Delta.
// 
// Options for MA/SMA smoothing.
// Cumulative vs Singular visual scale control.
// Sustain level to show where current delta must land to for cumulative delta to stay flat.
// Initial levels to show how current buy/sell/delta cumulations compare to values at start of current cumulation.

//@version=5
indicator("Singular and Cumulative Volume Delta (SVD+CVD)", shorttitle="SVD+CVD")

// <Variable Setup>

// Toggle Parts
var RenderDelta_Singular = input.bool(true, "Delta (Singular)", group="Show", inline="Delta")
var RenderDelta_Cumulative = input.bool(true, "Delta (Cumulative)", group="Show", inline="Delta")
var RenderBuySell_Singular = input.bool(true, "Buy/Sell (Singular)", group="Show", inline="Buy/Sell")
var RenderBuySell_Cumulative = input.bool(true, "Buy/Sell (Cumulative)", group="Show", inline="Buy/Sell")

var RenderCumulationLine = input.bool(true, "Cumulation Line", group="Show", inline="Cumulation Line", tooltip="Show where the cumulation window ends.")

var RenderSustainLevels = input.bool(true, "Sustain Level (Singular)", group="Show", inline="Sustain Level", tooltip="Show the singular delta value that would keep the current cumulative delta the same for the next bar.")
var RenderSustainLevelsHistory = input.bool(false, "Sustain Level History", group="Show", inline="Sustain History", tooltip="Show the history of the sustain level. Off for current candle only.")

var RenderInitialLevels = input.bool(true, "Inital Levels (Cumulative)", group="Show", inline="Initial Levels", tooltip="Show the initial cumulation levels from the current cumulation window.")
var RenderInitialLevelsHistory = input.bool(false, "Initial Levels History", group="Show", inline="Initial History", tooltip="Show the history of the initial levels. Off for current candle only.")

// Math Parameters

string cumulation_type = input.string("SUM", "Cumulation Type", group="Parameters", options = ["SUM", "EMA", "SMA"], tooltip="Formula to use to calculation cumulation.")
var cumulation_length = input.int(14, "Cumulation Length", group="Parameters", minval=1, tooltip="Number of periods to include in cumulation.")
var scale_factor = input.float(1.0, "Scale Factor", group="Parameters", tooltip="Scale the cumulation sizes. Sets the visual ratio between singular and cumulative data.", minval=0, step=.125)
var normalize_MAs = input.bool(true, "Normalize MAs", group="Parameters", tooltip="Unshrink the results of MA calculations. Multiplies the output average by the cumulation length.")

var cumulation_line_after_last = input.bool(true, "Cumulation Line After Last Input", group="Cumulation Line", tooltip=" True: Line appears AFTER the last cumulation input (@ last dropped value).\n\n False: Line appears OVER the last value (@ next value to drop).")

// Color Setup

// Pallete
bull_color = input.color(color.green, "Bull Bar Color", group="Colors")
bear_color = input.color(color.red, "Bear Bar Color", group="Colors")
weak_color = input.color(color.gray, "Weak Bar Color", group="Colors")

// Transparency Levels
Transparency_1 = input.int(65, "Cumulative", group="Transparency", minval=0, maxval=100, inline="Transparency Levels")
Transparency_2 = input.int(25, "Singular", group="Transparency", minval=0, maxval=100, inline="Transparency Levels")

// Default all colors to Transparency_1
bull_color := color.new(bull_color, Transparency_1)
bear_color := color.new(bear_color, Transparency_1)
weak_color := color.new(weak_color, Transparency_1)

// <essential calculations>

bullCandle = (open < close)
price_spread = (high - low)

// Establish lengths.

upper_wick_length = bullCandle ? (high-close) : (high-open)
lower_wick_length = bullCandle ? (open-low) : (close-low)
body_length = price_spread - (upper_wick_length + lower_wick_length)

// Convert lengths to Percents
upper_wick_percent = (upper_wick_length / price_spread)
lower_wick_percent = (lower_wick_length / price_spread)
body_percent = (body_length / price_spread)

// Convert percentage lengths into assignment groups.
var effective_wick_divisor = 2 
wicks_percent = (upper_wick_percent + lower_wick_percent)
effective_wick_portion = (wicks_percent / effective_wick_divisor)
dominant_candle_portion = (body_percent + effective_wick_portion)

// Assign to volumes.
volume_buy  = volume * (    bullCandle ? dominant_candle_portion : effective_wick_portion)
volume_sell = volume * (not bullCandle ? dominant_candle_portion : effective_wick_portion)

volume_delta = (volume_buy - volume_sell)

// Give name to direction calculation
volume_isBullish = (volume_delta > 0)
volume_isBearish = (volume_delta < 0)

// <Cumulations>

// Main cumulation Calculations
// Pre-calculating all values ends up being more coherent and forward thinking than complex `switch` calculations.

volume_buy_cumulative_sum = math.sum(volume_buy, cumulation_length)
volume_buy_cumulative_ema = ta.ema(volume_buy, cumulation_length)
volume_buy_cumulative_sma = ta.sma(volume_buy, cumulation_length)

volume_sell_cumulative_sum = math.sum(volume_sell, cumulation_length)
volume_sell_cumulative_ema = ta.ema(volume_sell, cumulation_length)
volume_sell_cumulative_sma = ta.sma(volume_sell, cumulation_length)

volume_delta_cumulative_sum = volume_buy_cumulative_sum - volume_sell_cumulative_sum
volume_delta_cumulative_sma = volume_buy_cumulative_sma - volume_sell_cumulative_sma
volume_delta_cumulative_ema = volume_buy_cumulative_ema - volume_sell_cumulative_ema

// Assign to plot variables / default data history.

volume_buy_cumulative = switch cumulation_type
    "SUM" => volume_buy_cumulative_sum
    "EMA" => volume_buy_cumulative_ema
    "SMA" => volume_buy_cumulative_sma
    
volume_sell_cumulative = switch cumulation_type
    "SUM" => volume_sell_cumulative_sum
    "EMA" => volume_sell_cumulative_ema
    "SMA" => volume_sell_cumulative_sma

// Normalize MAs if applicable.
// Only applies to plot values. Singular sized MA values are retained in their variables for other potential uses..
// @TODO Create separate series variables for normalized results and then switch at plot rendering.

is_MA_cumulation = switch cumulation_type
    "EMA" => true
    "SMA" => true
    => false

normalize_multiplier = (is_MA_cumulation and normalize_MAs) ? cumulation_length : 1

volume_buy_cumulative := volume_buy_cumulative * normalize_multiplier
volume_sell_cumulative := volume_sell_cumulative * normalize_multiplier

// For cumulative scale, do the buy and sell levels. Rest are derived from these anyway. 
volume_buy_cumulative := volume_buy_cumulative * scale_factor
volume_sell_cumulative := volume_sell_cumulative * scale_factor

// This is calculated here, rather than switched from previous inputs, so the scaling is included.
volume_delta_cumulative = (volume_buy_cumulative - volume_sell_cumulative) 

// Give name to direction calculation
volume_cumulative_isBullish = (volume_delta_cumulative > 0) 
volume_cumulative_isBearish = (volume_delta_cumulative < 0)

// <plot: Buy/Sell Levels>

// Use alt transparency for buysell data.
buysell_data_transparency = Transparency_2

plot( not RenderBuySell_Singular ? na :  volume_buy, "Buy Volume", style=plot.style_histogram, color=color.new(bull_color, buysell_data_transparency))
plot( not RenderBuySell_Singular ? na : -volume_sell, "Sell Volume", style=plot.style_histogram, color=color.new(bear_color, buysell_data_transparency))

plot( not RenderBuySell_Cumulative ? na :  volume_buy_cumulative, "Buy Volume (Cumulative)", style=plot.style_stepline, color=color.new(bull_color, buysell_data_transparency))
plot( not RenderBuySell_Cumulative ? na : -volume_sell_cumulative, "Sell Volume (Cumulative)", style=plot.style_stepline, color=color.new(bear_color, buysell_data_transparency))

// <plot: Delta Singular>

volume_delta_color = volume_isBullish ? bull_color : volume_isBearish ? bear_color : weak_color
volume_delta_color := color.new(volume_delta_color, Transparency_2)

plot( not RenderDelta_Singular ? na : volume_delta, "Volume Delta", style=plot.style_columns, linewidth=4, color=volume_delta_color)

// <plot: Delta Cumulative (CVD)>

volume_delta_cumulative_color = (volume_cumulative_isBullish) ? (bull_color) : (volume_cumulative_isBearish) ? (bear_color) : (weak_color)

plot( not RenderDelta_Cumulative ? na : volume_delta_cumulative, "Cumulative Volume Delta", color=volume_delta_cumulative_color, style=plot.style_columns)

// -------------------------------------------------------------------------------------

// <plot: Center Line>
hline(0, "Zero Line", linestyle=hline.style_dotted, color=color.gray)

// -------------------------------------------------------------------------------------

// <plot: Cumulation Line>

// This line shows where the cumulation window ends. The values at this mark are dropped from the cumulation, and will be used for Sustain and Initial Levels.

var cumulation_line_bull = line.new(na,na,na,na, style=line.style_dotted, color=color.new(weak_color, Transparency_2))

cumulation_last_bar_index = bar_index - cumulation_length + (cumulation_line_after_last ? 0 : 1)
line.set_xy1(cumulation_line_bull, cumulation_last_bar_index, volume_buy_cumulative[cumulation_length])
line.set_xy2(cumulation_line_bull, cumulation_last_bar_index, -volume_sell_cumulative[cumulation_length])

// <Cumulation Sustain and Initial Levels>

// # Sustain Levels:
// The last element of the cumulation period gets dropped on each bar progression.
// This affects the update calculation in predictable ways, setting a break even level for the current candle.
// By previewing the change, we can establish if the market is on track to maintain, increase, or reverse the cumulation.
// The break even point is the sustain levels.

// # Initial Levels:
// The cumulation levels at the beginning of the cumulation window, compared to the current, indicate:
// * Relative changes in total number of buyers / sellers.
// * Change in cumulative delta direction and strength.
// * Growth and contraction of overal market volume.

// <calculate: Sustain Level>

// Collect the value to be dropped.
cumulation_last_drop_delta = volume_delta[cumulation_length]

// The SUM replenishment is just the values removed. That's how sums work.
cumulation_sustain_delta_sum = cumulation_last_drop_delta

// The SMA replenishment is the same as for SUM.
// b/c If you remove a value from an average's input pool,
//     Then you need to put the same value back or the calculation changes.
cumulation_sustain_delta_sma = cumulation_last_drop_delta

// For EMA:
//   There is an intuitive solution to this, but the algebra verifies:
//     Let k = (smoothing/(1+cumulation_length)
//     Then:
//       ema_this = ema_last(1-k) + value_this(k)
//       1 = (ema_this)/(ema_last) = (1-k) + (value_this)(k)/(ema_last)
//       1 - (1-k) = (value_this)(k)/(ema_last)
//       (k)(ema_last) = (value_this)(k)
//       ema_last = value_this
//
//   Essentially, the making the current output equal to the last output allows us to cancel out the smoothing
//   Making the target variable equal as well.
float cumulation_sustain_delta_ema = volume_delta_cumulative_ema[1]

cumulation_sustain_delta = switch cumulation_type
    "SUM" => cumulation_sustain_delta_sum
    "SMA" => cumulation_sustain_delta_sma
    "EMA" => cumulation_sustain_delta_ema

// <calculate: Initial Levels>

// Collect the values to be dropped.
cumulation_next_drop_delta_cumulative = volume_delta_cumulative[cumulation_length - 1]
cumulation_next_drop_buy_cumulative   = volume_buy_cumulative[cumulation_length - 1]
cumulation_next_drop_sell_cumulative  = volume_sell_cumulative[cumulation_length - 1]

// The initial levels are just history levels.
cumulation_initial_delta_cumulative = cumulation_next_drop_delta_cumulative
cumulation_initial_buy_cumulative   = cumulation_next_drop_buy_cumulative
cumulation_initial_sell_cumulative  = cumulation_next_drop_sell_cumulative

// <plot: sustain levels>

sustain_level_show_last = (RenderSustainLevelsHistory) ? (na) : (1)

plot( not RenderSustainLevels ? na :  cumulation_sustain_delta, "Delta Sustain", style=plot.style_columns, color=weak_color, offset=0, show_last=sustain_level_show_last)

// <plot: initial levels>

initial_level_show_last = (RenderInitialLevelsHistory) ? (na) : (2)

plot( not RenderInitialLevels ? na :  cumulation_initial_delta_cumulative, "Delta Cumulative Sustain", style=plot.style_stepline, color=weak_color, offset=0, show_last=initial_level_show_last)
plot( not RenderInitialLevels ? na :  cumulation_initial_buy_cumulative, "Buy Cumulative Sustain", style=plot.style_stepline, color=weak_color, offset=0, show_last=initial_level_show_last)
plot( not RenderInitialLevels ? na : -cumulation_initial_sell_cumulative, "Sell Cumulative Sustain", style=plot.style_stepline, color=weak_color, offset=0, show_last=initial_level_show_last)