Intraday Intensity Modes
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © allanster on TradingView

//@version=5
// Original concept by David Bostian, with variations featured in "Bollinger on Bollinger Bands".
indicator("Intraday Intensity Modes", 'Intensity')

toolTipA  = 'III (Individual):\nOsc Length 1\n[   ] Cumulative (Off)\n[   ] Normalized (Off)\n[   ] Inverse Volu' +
 'me (Off)\n[   ] Show Levels (Off)\n\n'
toolTipB  = 'II  (Cumulative):\nOsc Length 1\n[✓] Cumulative (On)\n[   ] Normalized (Off)\n[   ] Inverse Volume ' +
 '(Off)\n[   ] Show Levels (Off)\n\n'
toolTipC  = 'II% (Oscillator):\nOsc Length 21\n[   ] Cumulative (Off)\n[✓] Normalized (On)\n[   ] Inverse Volume' +
 ' (Off)\n[✓] Show Levels (On)'
toolTipD  = 'Enables examination of intensity per bar instead of per day, this is a departure from the original ' +
 'concept. Whenever this setting is enabled the indicator should be regarded as operating in an experimental mode.'
toolTipV  = '[   ] Inverse Volume (Off):\n\n   (2 * close - high - low) * volume\n ────────────────\n           ' +
 '         high - low\n\n\n[✓] Inverse Volume (On):\n\n            2 * close - high - low\n          ───────────' +
 '\n            (high - low) * volume'

i_length  = input.int  (21,         'Osc Length',     minval = 1, tooltip = toolTipA + toolTipB + toolTipC)
i_cumltv  = input.bool (false,      'Cumulative')
i_normal  = input.bool (true,       'Normalized')
i_candle  = input.bool (false,      'Intrabar',                   tooltip = toolTipD)
i_invert  = input.bool (false,      'Inverse Volume',             tooltip = toolTipV)
i_colrUp  = input.color(#00BCD4,  'Colors For Up',                                             inline = 'a')
i_colrDn  = input.color(#E040FB,  'Down',                                                      inline = 'a')
i_styles  = input.string('Columns', 'Style & Width', options = ['Columns', 'Histogram', 'Line'], inline = 'b')
i_widths  = input.int  (1,          '',               minval = 1,                                inline = 'b')
i_shoLvl  = input.bool (true,       '',                                                          inline = 'c')
i_levelH  = input.int  (25,         'Show Levels Above',                                         inline = 'c')
i_levelL  = input.int  (-25,        'Below',                                                     inline = 'c')

id_cum(source) => // perform cumulative sum once per day when using realtime intraday source values
    var carrySum  = float(na)
    var dailySum  = float(na)
    if not timeframe.isintraday
        carrySum := ta.cum(nz(source))
    else
        dailySum := timeframe.change('D') ? nz(carrySum) : nz(dailySum)
        carrySum := nz(dailySum) + nz(source)

altSum(source, length) => normal = math.sum(nz(source), length) // treat na as 0 and return sum

var idRangeH = float(na)
var idRangeL = float(na)
var idVolume = float(na)
startDay  = timeframe.change('D')
idRangeH := not timeframe.isintraday or startDay ?   high : high > nz(idRangeH)        ? high : idRangeH // intraday high
idRangeL := not timeframe.isintraday or startDay ?    low :  low < nz(idRangeL, 10e99) ?  low : idRangeL // intraday low
idVolume := not timeframe.isintraday or startDay ? volume : nz(idVolume) + volume                        // intraday volume
idUseVol  = i_invert ? 1 / idVolume : idVolume
iiiValue  = nz(((2 * close - idRangeH - idRangeL) / (idRangeH - idRangeL)) * idUseVol)                   // intraday intensity

use_iii   = i_invert ?
 (2 * close - high - low) / ((high - low) * volume) :
 ((2 * close - high - low) / (high - low)) * volume

usePrcnt = i_normal ? 100 : 1

iiSource = 
 usePrcnt * altSum(i_cumltv ? i_candle ? ta.cum(nz(use_iii)) : id_cum(iiiValue) : i_candle ? nz(use_iii) : iiiValue, i_length) / 
 (i_normal ? altSum(i_cumltv ? i_candle ? ta.cum(volume) : id_cum(idVolume) : i_candle ? volume : idVolume, i_length) : 1)

colrSign = altSum(i_candle ? use_iii : iiiValue, i_length) / (i_normal ? i_candle ? volume : altSum(idVolume, i_length) : 1)
pltStyle = i_styles == 'Columns' ? plot.style_columns : i_styles == 'Histogram' ? plot.style_histogram : plot.style_line
plot(iiSource, 'III, II, or II%', math.sign(colrSign) != -1 ? i_colrUp : i_colrDn, i_widths, pltStyle)

plot(startDay ? 1 : 0, 'startDay', #ffff00, display = display.data_window)
plot(close,    'close',            #ffff00, display = display.data_window)

plot(high,     'high',             #ffff00, display = display.data_window)
plot(low,      'low',              #ffff00, display = display.data_window)
plot(volume,   'volume',           #ffff00, display = display.data_window)

plot(idRangeH, 'idRangeH',         #ffff00, display = display.data_window)
plot(idRangeL, 'idRangeL',         #ffff00, display = display.data_window)
plot(idVolume, 'idVolume',         #ffff00, display = display.data_window)

plot(iiiValue, 'iiiValue',         #ffff00, display = display.data_window)
plot(iiSource, 'iiSource',         #ffff00, display = display.data_window)

hline(i_shoLvl ? i_levelH : na)
hline(i_shoLvl ? i_levelL : na)

// Reference Equations Used For Normal Volume
// III = (((2 * close) - high - low) / (high - low)) * volume
// II  = ta.cum((((2 * close) - high - low) / ((high - low)) * volume)
// II% = 100 * math.sum((((2 * close) - high - low) / ((high - low)) * volume, 21) / math.sum(volume, 21)

// Reference Equations Used For Inverted Volume
// III = ((2 * close) - high - low) / ((high - low) * volume)
// II  = ta.cum(((2 * close) - high - low) / ((high - low) * volume))
// II% = 100 * math.sum(((2 * close) - high - low) / ((high - low) * volume), 21) / math.sum(volume, 21)