CVD Ichimoku(s)
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © TradingView

//@version=5
indicator("CVD Ichimoku(s)", "CVD Ichimoku(s)", format = format.volume)

// CVD - Cumulative Volume Delta Candles
// v3 2022.07.11

// This code was written using the recommendations from the Pine Script™ User Manual's Style Guide:
//   https://www.tradingview.com/pine-script-docs/en/v5/writing/Style_guide.html

import PineCoders/Time/2 as timeLib



// ———————————————————— Constants and Inputs {


// ————— Constants
int     MS_IN_MIN   = 60 * 1000
int     MS_IN_HOUR  = MS_IN_MIN  * 60
int     MS_IN_DAY   = MS_IN_HOUR * 24

color   WHITE        = #ffffffff
color   GRAY        = #808080ff
color   LIME        = #00FF00ff
color   MAROON      = #800000ff
color   ORANGE      = #FF8000ff
color   PINK        = #FF0080ff
color   TEAL        = #008080ff
color   BG_DIV      = color.new(ORANGE, 90)
color   BG_RESETS   = color.new(GRAY, 90)
color   GRIS        = color.new(GRAY, 50)
color   BLANC       = color.new(WHITE, 50)

string  RST1        = "None"
string  RST2        = "On a stepped higher timeframe"
string  RST3        = "On a fixed higher timeframe"
string  RST4        = "At a fixed time"
string  RST5        = "At the beginning of the regular session"
string  RST6        = "At the first visible chart bar"

string  LTF1        = "Least precise, covering many chart bars"
string  LTF2        = "Less precise, covering some chart bars"
string  LTF3        = "More precise, covering less chart bars"
string  LTF4        = "Most precise, 1min intrabars"

string  TT_TOTVOL   = "The 'Bodies' value is the transparency of the total volume candle bodies. Zero is opaque, 100 is transparent."
string  TT_RST_HTF  = "This value only matters when '" + RST3 +"' is selected."
string  TT_RST_TIME = "Hour: 0-23\nMinute: 0-59\nThese values only matter when '" + RST4 +"' is selected.
  A reset will occur when the time is greater or equal to the bar's open time, and less than its close time."

// ————— Inputs

string  GI = "Ichimokus"
showSAnow = input(true, title='Senko A now', group = GI)
show1r = input(true, title='Current TF - Ichimoku', group = GI)
show2nd = input(true, title='Bigger TF - Ichimoku', group = GI)
coef = input(6, title='Bigger TF Multiplier', group = GI)

string  GCVD = "Inputs CVD"
string  resetInput              = input.string(RST1,        "CVD Resets",                       inline = "00", options = [RST1, RST2, RST3, RST4, RST5, RST6], group = GCVD)
string  fixedTfInput            = input.timeframe("D",      "  Fixed higher timeframe:",        tooltip = TT_RST_HTF, group = GCVD)
int     hourInput               = input.int(9,              "  Fixed time:  Hour",              inline = "01", minval = 0, maxval = 23, group = GCVD)
int     minuteInput             = input.int(30,             "Minute",                           inline = "01", minval = 0, maxval = 59, tooltip = TT_RST_TIME, group = GCVD)
string  ltfModeInput            = input.string(LTF2,        "Intrabar precision",               inline = "02", options = [LTF1, LTF2, LTF3, LTF4], group = GCVD)

string  GRP1 = "Visuals"
color   upColorInput            = input.color(GRIS,         "CVD candles 🡑",                   inline = "11", group = GRP1)
color   dnColorInput            = input.color(BLANC,         "🡓",                               inline = "11", group = GRP1)
bool    colorDivBodiesInput     = input.bool(false,          "Color CVD bodies on divergences ", inline = "12", group = GRP1)
color   upDivColorInput         = input.color(LIME,         "🡑",                               inline = "12", group = GRP1)
color   dnDivColorInput         = input.color(PINK,       "🡓",                               inline = "12", group = GRP1)
bool    showTotVolInput         = input.bool(false,         "Total volume candles borders",     inline = "13", group = GRP1)
color   upTotVolColorInput      = input.color(TEAL,         "🡑",                               inline = "13", group = GRP1)
color   dnTotVolColorInput      = input.color(MAROON,       "🡓",                               inline = "13", group = GRP1)
int     totVolBodyTranspInput   = input.int(80,             "bodies",                           inline = "13", group = GRP1, minval = 0, maxval = 100, tooltip = TT_TOTVOL)
bool    bgDivInput              = input.bool(false,         "Color background on divergences ", inline = "14", group = GRP1)
color   bgDivColorInput         = input.color(BG_DIV,       "",                                 inline = "14", group = GRP1)
bool    bgResetInput            = input.bool(true,          "Color background on resets ",      inline = "15", group = GRP1)
color   bgResetColorInput       = input.color(BG_RESETS,    "",                                 inline = "15", group = GRP1)
bool    showInfoBoxInput        = input.bool(false,          "Show information box ",            group = GRP1)
string  infoBoxSizeInput        = input.string("small",     "Size ",                            inline = "16", group = GRP1, options = ["tiny", "small", "normal", "large", "huge", "auto"])
string  infoBoxYPosInput        = input.string("bottom",    "↕",                                inline = "16", group = GRP1, options = ["top", "middle", "bottom"])
string  infoBoxXPosInput        = input.string("left",      "↔",                                inline = "16", group = GRP1, options = ["left", "center", "right"])
color   infoBoxColorInput       = input.color(color.gray,   "",                                 inline = "16", group = GRP1)
color   infoBoxTxtColorInput    = input.color(color.white,  "T",                                inline = "16", group = GRP1)
// }



// ———————————————————— Functions {


// @function    Determines if the volume for an intrabar is up or down.
// @returns     ([float, float]) A tuple of two values, one of which contains the bar's volume. `upVol` is the volume of up bars. `dnVol` is the volume of down bars.
//              Note that when this function is called with `request.security_lower_tf()` a tuple of float[] arrays will be returned by `request.security_lower_tf()`.
upDnIntrabarVolumes() =>
    float upVol = 0.0
    float dnVol = 0.0
    switch
        // Bar polarity can be determined.
        close > open => upVol += volume
        close < open => dnVol -= volume
        // If not, use price movement since last bar.
        close > nz(close[1]) => upVol += volume
        close < nz(close[1]) => dnVol -= volume
        // If not, use previously known polarity.
        nz(upVol[1]) > 0 => upVol += volume
        nz(dnVol[1]) < 0 => dnVol -= volume
    [upVol, dnVol]


// @function    Selects a HTF from the chart's TF.
// @returns     (simple string) A timeframe string.
htfStep() =>
    int tfInMs = timeframe.in_seconds() * 1000
    string result =
      switch
        tfInMs <  MS_IN_MIN       => "60"
        tfInMs <  MS_IN_HOUR * 3  => "D"
        tfInMs <= MS_IN_HOUR * 12 => "W"
        tfInMs <  MS_IN_DAY * 7   => "M"
        => "12M"


// @function            Selects a LTF from the chart's TF.
// @param precision     (simple int) 1, 2, 3 or 4 for increasing quantities of intrabars.
// @returns             (simple string) A timeframe string.
ltfStep(simple int precision = 2) =>
    int tfInMs = timeframe.in_seconds() * 1000
    bool coverAllChartBars  = precision == 1
    bool hiPrecision        = precision == 3
    bool mostIntrabars      = precision == 4
    string result =
      switch
        coverAllChartBars       => timeLib.secondsToTfString(timeframe.in_seconds() / 4)
        mostIntrabars           => "1"
        tfInMs < MS_IN_HOUR     => hiPrecision ? "1"  : "1"
        tfInMs < MS_IN_DAY      => hiPrecision ? "1"  : "15"
        tfInMs < MS_IN_DAY * 7  => hiPrecision ? "30" : "120"
        => hiPrecision ? "60" : "D"


// @function        Determines when a bar opens at a given time.
// @param hours     (series int) "Hour" part of the time we are looking for.
// @param minutes   (series int) "Minute" part of the time we are looking for.
// @returns         (series bool) `true` when the bar opens at `hours`:`minutes`, false otherwise.
timeReset(int hours, int minutes) =>
    int openTime = timestamp(year, month, dayofmonth, hours, minutes, 00)
    bool timeInBar = time <= openTime and time_close > openTime
    bool result = timeframe.isintraday and not timeInBar[1] and timeInBar
// }



// ———————————————————— Calculations {


// Lower timeframe (LTF) used to mine intrabars.
var int ltfPrecision = ltfModeInput == LTF1 ? 1 : ltfModeInput == LTF2 ? 2 : ltfModeInput == LTF3 ? 3 : 4
var string optimalLtf = ltfStep(ltfPrecision)

// Get two arrays, one each for up and dn volumes.
[upVolumes, dnVolumes] = request.security_lower_tf(syminfo.tickerid, optimalLtf, upDnIntrabarVolumes())

// Total up/dn volumes for intrabars.
float totalUpVolume = nz(array.sum(upVolumes))
float totalDnVolume = nz(array.sum(dnVolumes))
float delta = totalUpVolume + totalDnVolume

// Largest up/dn values in intrabars.
float maxUpVolume = nz(array.max(upVolumes))
float maxDnVolume = nz(array.min(dnVolumes))

// Track cumulative volume.
var float cvd = 0.0
[reset, resetDescription] = 
  switch resetInput
    RST1 => [false, "No resets"]
    RST2 => [timeframe.change(htfStep()), "Resets every " + htfStep()]
    RST3 => [timeframe.change(fixedTfInput), "Resets every " + fixedTfInput]
    RST4 => [timeReset(hourInput, minuteInput), str.format("Resets at {0,number,00}:{1,number,00}", hourInput, minuteInput)]
    RST5 => [session.isfirstbar_regular, "Resets at the beginning of the session"]
    RST6 => [time == chart.left_visible_bar_time, "Resets at the beginning of visible bars"]
if reset
    cvd := 0

// Build OHLC values for CVD candles.
float cvdO = cvd
float cvdH = cvdO + maxUpVolume
float cvdL = cvdO + maxDnVolume
float cvdC = cvdO + delta
cvd += delta

// Total volume level relative to CVD.
float intrabarVolume = totalUpVolume - totalDnVolume
float totalVolumeLevel = cvdO + (intrabarVolume * math.sign(delta))

// Qty of intrabars per chart bar and its average.
int intrabars = array.size(upVolumes)
int chartBarsCovered = int(ta.cum(math.sign(intrabars)))
float avgIntrabars = ta.cum(intrabars) / chartBarsCovered

// Detect errors.
if resetInput == RST3 and timeframe.in_seconds(fixedTfInput) <= timeframe.in_seconds()
    runtime.error("The higher timeframe for resets must be greater than the chart's timeframe.")
else if resetInput == RST4 and not timeframe.isintraday
    runtime.error("Resets at a fixed time work on intraday charts only.")
else if ta.cum(intrabarVolume) == 0 and barstate.islast
    runtime.error("No volume is provided by the data vendor.")
else if ta.cum(intrabars) == 0 and barstate.islast
    runtime.error("No intrabar information exists at the '" + optimalLtf + "' timeframe.")

// Detect divergences between volume delta and the bar's polarity.
bool divergence = delta != 0 and math.sign(delta) != math.sign(close - open)
// }


// ———————————————————— Visuals {


color candleColor = delta > 0 ? colorDivBodiesInput and divergence ? upDivColorInput : upColorInput : colorDivBodiesInput and divergence ? dnDivColorInput : dnColorInput
color totVolCandleColor = delta > 0 ? upTotVolColorInput : dnTotVolColorInput

// Display key values in indicator values and Data Window.
plotchar(delta,             "Volume delta for the bar",     "", location.top, candleColor, display=display.none)
plotchar(totalUpVolume,     "Up volume for the bar",        "", location.top, upColorInput, display=display.none)
plotchar(totalDnVolume,     "Dn volume for the bar",        "", location.top, dnColorInput, display=display.none)
plotchar(intrabarVolume,    "Total intrabar volume",        "", location.top, display=display.none)
plotchar(na,                "═════════════════",            "", location.top, display=display.none)
plotchar(cvdO,              "CVD before this bar",          "", location.top, display=display.none)
plotchar(cvdC,              "CVD after this bar",           "", location.top, display=display.none)
plotchar(maxUpVolume,       "Max intrabar up volume",       "", location.top, upColorInput, display=display.none)
plotchar(maxDnVolume,       "Max intrabar dn volume",       "", location.top, dnColorInput, display=display.none)
plotchar(intrabars,         "Intrabars in this bar",        "", location.top, display=display.none)
plotchar(avgIntrabars,      "Average intrabars",            "", location.top, display=display.none)
plotchar(chartBarsCovered,  "Chart bars covered",           "", location.top, display=display.none)
plotchar(bar_index + 1,     "Chart bars",                   "", location.top, size = size.tiny, display=display.none)
plotchar(na,                "═════════════════",            "", location.top, display=display.none)

// Total volume boxes.
plotcandle(showTotVolInput ? cvdO : na, math.max(cvdO, totalVolumeLevel), math.min(cvdO, totalVolumeLevel), totalVolumeLevel, "CVD", color = color.new(totVolCandleColor, totVolBodyTranspInput), wickcolor = totVolCandleColor, bordercolor = totVolCandleColor)

// CVD candles.
plotcandle(cvdO, cvdH, cvdL, cvdC, "CVD", color = candleColor, wickcolor = candleColor, bordercolor = candleColor)

// Zero line.
hline(0, "Zero", GRAY, hline.style_dotted, display=display.none)

// Background on resets and divergences.
bgcolor(bgResetInput and reset ? bgResetColorInput : bgDivInput and divergence ? bgDivColorInput : na)
    
// Display information box only once on the last historical bar, instead of on all realtime updates, as when `barstate.islast` is used.
if showInfoBoxInput and barstate.islastconfirmedhistory
    var table infoBox = table.new(infoBoxYPosInput + "_" + infoBoxXPosInput, 1, 1)
    string txt = resetDescription + 
      "\nUses intrabars at " + timeLib.formattedNoOfPeriods(timeframe.in_seconds(optimalLtf) * 1000) + 
      "\nAvg intrabars per chart bar: " + str.tostring(avgIntrabars, "#.#")
    table.cell(infoBox, 0, 0, txt, text_color = infoBoxTxtColorInput, text_size = infoBoxSizeInput, bgcolor = infoBoxColorInput)
// }

//CVD Ichimoku cloud actual TF

conversionPeriods = input.int(9, minval=1, title="Conversion Line Length")
basePeriods = input.int(26, minval=1, title="Base Line Length")
laggingSpan2Periods = input.int(52, minval=1, title="Leading Span B Length")
displacement = input.int(26, minval=1, title="Displacement")
donchian(len) => math.avg(ta.lowest(cvd, len), ta.highest(cvd, len))
conversionLine = donchian(conversionPeriods)
baseLine = donchian(basePeriods)
leadLine1 = math.avg(conversionLine, baseLine)
leadLine2 = donchian(laggingSpan2Periods)
plot(show1r?conversionLine:na, color=color.new(color.blue, 0), title="Conversion Line", display=display.none)
plot(show1r?baseLine:na, color=color.new(color.purple, 0), title="Base Line", display=display.none)
//plot(ctl, offset = -displacement + 1, color=color.new(color.maroon, 100), title="Lagging Span")
p0 = plot(showSAnow?leadLine1:na, color= conversionLine > baseLine ? color.new(color.orange, 51) : color.new(color.orange, 49), linewidth=1,
	 title="Now Span A")
p1 = plot(show1r?leadLine1:na, offset = displacement - 1, color=color.new(color.white, 75), linewidth=2, display=display.none,
	 title="Leading Span A")
p2 = plot(show1r?leadLine2:na, offset = displacement - 1, color=color.new(color.white, 75), linewidth=2, display=display.none,
	 title="Leading Span B")
fill(p1, p2, color = leadLine1 > leadLine2 ? color.new(color.aqua, 80) : color.new(color.yellow, 80))

//CVD Ichimoku cloud bigger TF

conversionPeriods2 = coef * conversionPeriods
basePeriods2 = coef * basePeriods
laggingSpan2Periods2 = coef * laggingSpan2Periods
displacement2 = coef * displacement

conversionLine2 = donchian(conversionPeriods2)
baseLine2 = donchian(basePeriods2)
leadLine12 = math.avg(conversionLine2, baseLine2)
leadLine22 = donchian(laggingSpan2Periods2)

plot(show2nd?conversionLine2:na, color=color.gray, linewidth=1, display=display.none, title="BTF Tenkan")
plot(show2nd?baseLine2:na, color=color.gray, linewidth=1, display=display.none, title="BTF Kijun")
plot(show2nd?close:na, offset = -displacement2 + 1, color=color.white, linewidth=1, display=display.none, title="BTF Chikou")

p12 = plot(show2nd?leadLine12:na, offset = displacement2 - 1, color=color.new(color.gray, 75),linewidth=2, display=display.none, title="BTF Senko A")
p22 = plot(show2nd?leadLine22:na, offset = displacement2 - 1, color=color.new(color.gray, 75),  linewidth=2, display=display.none, title="BTF Senko B")
fill(p12, p22, color = leadLine12 > leadLine22 ? color.new(color.gray, 85) : color.new(color.gray, 85))

//Hololooo