Delta Volume Channels [LucF]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © LucF

//@version=5
indicator("Delta Volume Channels [LucF]", "DV Channels", true, precision = 6, max_labels_count = 500)

// Delta Volume Channels [LucF]
// v5, 2023.04.16 17:55

// 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 PCtime
import PineCoders/lower_tf/4 as PCltf
import LucF/ta/3 as LucfTa



//#region ———————————————————— Constants


// Colors
color  LIME      = #00FF00ff
color  LIME_MD   = #00FF0090
color  LIME_LT   = #00FF0040
color  TEAL      = #008080ff
color  TEAL_MD   = #00808090
color  TEAL_LT   = #00808040
color  PINK      = #FF0080ff
color  PINK_MD   = #FF008090
color  PINK_LT   = #FF008040
color  MAROON    = #800000ff
color  MAROON_MD = #80000090
color  MAROON_LT = #80000040
color  ORANGE    = #c56606ff
color  ORANGE_BR = #FF8000ff
color  GRAY      = #808080ff
color  GRAY_MD   = #80808090
color  GRAY_LT   = #80808030
color  WHITE     = #FFFFFFff
color  BLACK     = #000000ff

// Reference MAs
string MA01 = "Simple MA"
string MA02 = "Exponential MA"
string MA03 = "Wilder MA"
string MA04 = "Weighted MA"
string MA05 = "Volume-weighted MA"
string MA06 = "Arnaud Legoux MA"
string MA07 = "Hull MA"
string MA08 = "Symmetrically-weighted MA"

// Intrabar precisions
string LTF1   = "Covering most chart bars (least precise)"
string LTF2   = "Covering some chart bars (less precise)"
string LTF3   = "Covering less chart bars (more precise)"
string LTF4   = "Covering few chart bars (very precise)"
string LTF5   = "Covering the least chart bars (most precise)"
string LTF6   = "~12 intrabars per chart bar"
string LTF7   = "~24 intrabars per chart bar"
string LTF8   = "~50 intrabars per chart bar"
string LTF9   = "~100 intrabars per chart bar"
string LTF10  = "~250 intrabars per chart bar"

// Line styles
string STL1 = "Line"
string STL2 = "Circles"
string STL3 = "Crosses"

// Marker DV channel transitions
string ST0 = "None"
string ST1 = "DV channel strong bull state"
string ST2 = "DV channel bull or strong bull state"
string ST3 = "DV channel strong bear state"
string ST4 = "DV channel bear or strong bear state"

// Marker Divergence channel transitions
string SV0 = "None"
string SV1 = "Divergence channel strong bull state"
string SV2 = "Divergence channel bull or strong bull state"
string SV3 = "Divergence channel strong bear state"
string SV4 = "Divergence channel bear or strong bear state"

// Bar color choices
string CB0 = "None"
string CB1 = "On divergences only"
string CB2 = "On divergences and on the state of the DV channel"
string CB3 = "On divergences and on the state of the divergence channel"
string CB4 = "On divergences and on the combined state of both channels"

// Channel level sources
string CH1 = "High and Low"
string CH2 = "Open and Close"

// Channel breach sources
string BR1 = "`low` must breach channel's top, `high` must breach channel's bottom"
string BR2 = "`high` must breach channel's top, `low` must breach channel's bottom"
string BR3 = "Close"
string BR4 = "Open"
string BR5 = "The average of high and low (hl2)"
string BR6 = "The average of high, low and close (hlc3)"
string BR7 = "The average of high, low and two times the close (hlcc4)"
string BR8 = "The average of high, low and close and open (ohlc4)"

// Tooltips
string TT_REF       = "Your choices here determine the reference that will be used as the DV channel's baseline.
  The MA type and length defined here are also used to calculate the MA of the DV% weights."
string TT_CAP       = "This is the maximum number of standard deviations away from the reference line that the DV%-weighted line can extend to.
  It limits swings of the DV%-weighted line, keeping the chart's vertical scale within acceptable boundaries."
string TT_RVOL      = "In addition to the weight of DV, use the weight of the relative volume for the bar. 
  This weight is determined using the percentile rank of the bar's volume in the specified number of bars."
string TT_LTF       = "Your selection here controls how many intrabars will be analyzed for each chart bar. 
  The more intrabars you analyze, the more precise the calculations will be,
  but the less chart bars will be covered by the indicator's calculations because a maximum of 100K intrabars can be analyzed.\n\n
  The first five choices determine the lower timeframe used for intrabars using how much chart coverage you want.
  The last five choices allow you to select approximately how many intrabars you want analyzed per chart bar."
string TT_LTF_BOX   = "Displays the LTF used and intrabar statistics in a configurable position and color."
string TT_BIAS      = "This option enables a guess on the bull/bear bias of the channel before it is breached.
  It uses the number of changes of the top/bottom channel levels to determine a bias.
  When more changes of the top level occur, the bias is bullish. When more changes of the bottom level occur, the bias is bearish.
  \n\n Note that enabling this setting will make the channel's states less reliable."
string TT_COLORS    = "'🡑🡑' and '🡓🡓' indicate the colors used for strong bull/bear conditions.
  \n'🡑' and '🡓' indicate bull/bear conditions."
string TT_MARKERS   = "The conditions you use to determine when markers appear will also be used to trigger alerts created from this script.
  \n\nMarkers are non-repainting; they appear on the close of bars."
string TT_BARS      = "If the coloring of bars on divergences is active, their body will always be colored in the divergence color, regardless of this checkbox's state."
string TT_DIV       = "A divergence occurs when the slope of the reference line does not match that of the DV%-weighted line."
string TT_FILTERS   = "The filters are additional conditions that must be true for a marker to appear.
  \n\n'Bar polarity' means that the bar's up/dn polarity must match that of the marker.
  \n\n'Close-to-close polarity' means that the `close` must be higher than the previous one for an up marker, and vice versa.
  \n\n'Bull/bear CCI' means that CCI (using the same source and length as the reference line) must be above/below 0.
  \n\n'Rising volume' means the volume of the bar must be higher than that of the previous bar. This condition is the same for up/dn markers.
  \n\nThe filter on divergences requires a divergence to have occurred in the last number of bars you specify.
  \n\nThe filter on 'Efficient Work' requires its bull/bear state to match the direction of the marker. ('Efficient Work' is one of my indicators).
  \n\nAs markers are non-repainting, keep in mind that marker conditions must be true on the bar's close, which is when the marker will appear."
//#endregion



//#region ———————————————————— Inputs

string  ltfModeInput            = input.string(LTF8,        "Intrabar precision",                   inline = "ltf", options = [LTF1, LTF2, LTF3, LTF4, LTF5, LTF6, LTF7, LTF8, LTF9, LTF10], tooltip = TT_LTF)

string  GRP0 = "DV channel"
bool    reflLineShowInput       = input.bool(false,         "Reference line ",                      group = GRP0, inline = "refLine")
int     refLineWidthInput       = input.int(1,              " Width",                               group = GRP0, inline = "refLine", minval = 1)
string  refLineStyleInput       = input.string(STL1,        "",                                     group = GRP0, inline = "refLine", options = [STL1, STL2, STL3])
color   refLineUpUpColorInput   = input.color(LIME,         "  🡑🡑",                                 group = GRP0, inline = "refLineColors")
color   refLineDnDnColorInput   = input.color(PINK,         "🡓🡓",                                   group = GRP0, inline = "refLineColors")
color   refLineUpColorInput     = input.color(TEAL,         " 🡑",                                   group = GRP0, inline = "refLineColors")
color   refLineDnColorInput     = input.color(MAROON,       "🡓",                                    group = GRP0, inline = "refLineColors", tooltip = TT_COLORS)
string  refTypeInput            = input.string(MA06,        "  ",                                   group = GRP0, inline = "ref", options = [MA01, MA02, MA03, MA04, MA05, MA06, MA07, MA08], tooltip = TT_REF)
float   refSourceInput          = input.source(close,       "",                                     group = GRP0, inline = "ref")
int     refLengthInput          = input.int(20,             " Length",                              group = GRP0, inline = "ref", minval = 2)

bool    dvlLineShowInput        = input.bool(false,         "DV%-weighted line",                    group = GRP0, inline = "dvLine")
int     dvLineWidthInput        = input.int(2,              " Width",                               group = GRP0, inline = "dvLine", minval = 1)
string  dvLineStyleInput        = input.string(STL1,        "",                                     group = GRP0, inline = "dvLine", options = [STL1, STL2, STL3])
color   dvLineUpUpColorInput    = input.color(LIME,         "  🡑🡑",                                 group = GRP0, inline = "dvLineColors")
color   dvLineDnDnColorInput    = input.color(PINK,         "🡓🡓",                                   group = GRP0, inline = "dvLineColors")
color   dvLineUpColorInput      = input.color(TEAL,         " 🡑",                                   group = GRP0, inline = "dvLineColors")
color   dvLineDnColorInput      = input.color(MAROON,       "🡓",                                    group = GRP0, inline = "dvLineColors")
int     sigmaCapInput           = input.int(5,              "   Cap (in standard deviations)",      group = GRP0, inline = "cap", minval = 1, tooltip = TT_CAP)
bool    useRelVolWeightInput    = input.string("Use",       "  ",                                   group = GRP0, inline = "RelVolW", options = ["Use", "Don't use"], tooltip = TT_RVOL) == "Use"
int     relVolLookbackInput     = input.int(100,            "relative volume over n bars",          group = GRP0, inline = "RelVolW", minval = 2)

bool    dvFillShowInput         = input.bool(true,          "DV channel",                           group = GRP0, inline = "dvFill")
color   dvFillUpUpColorInput    = input.color(LIME_MD,      " 🡑🡑",                                  group = GRP0, inline = "dvFill")
color   dvFillDnDnColorInput    = input.color(PINK_MD,      "🡓🡓",                                   group = GRP0, inline = "dvFill")
color   dvFillUpColorInput      = input.color(TEAL_MD,      " 🡑",                                   group = GRP0, inline = "dvFill")
color   dvFillDnColorInput      = input.color(MAROON_MD,    "🡓",                                    group = GRP0, inline = "dvFill")

string  GRP1 = "Divergence channel"
bool    divLinesShowInput       = input.bool(false,         "Divergence levels",                    group = GRP1, inline = "divLines")
int     divLinesWidthInput      = input.int(1,              " Width",                               group = GRP1, inline = "divLines", minval = 1)
string  divLinesStyleInput      = input.string(STL1,        "",                                     group = GRP1, inline = "divLines", options = [STL1, STL2, STL3])
color   divLinesUpUpColorInput  = input.color(LIME,         "  🡑🡑",                                 group = GRP1, inline = "divLinesColors")
color   divLinesDnDnColorInput  = input.color(PINK,         "🡓🡓",                                   group = GRP1, inline = "divLinesColors")
color   divLinesUpColorInput    = input.color(TEAL,         " 🡑",                                   group = GRP1, inline = "divLinesColors")
color   divLinesDnColorInput    = input.color(MAROON,       "🡓",                                    group = GRP1, inline = "divLinesColors")
color   divLinesNtColorInput    = input.color(GRAY,         "N",                                    group = GRP1, inline = "divLinesColors")

bool    divFillShowInput        = input.bool(true,          "Divergence channel",                   group = GRP1, inline = "divFill")
color   divFillUpUpColorInput   = input.color(LIME_MD,      " 🡑🡑",                                  group = GRP1, inline = "divFill")
color   divFillDnDnColorInput   = input.color(PINK_MD,      "🡓🡓",                                   group = GRP1, inline = "divFill")
color   divFillUpColorInput     = input.color(TEAL_MD,      " 🡑",                                   group = GRP1, inline = "divFill")
color   divFillDnColorInput     = input.color(MAROON_MD,    "🡓",                                    group = GRP1, inline = "divFill")
color   divFillNtColorInput     = input.color(GRAY_MD,      "N",                                    group = GRP1, inline = "divFill")

string  divChannelLevelsInput   = input.string(CH1,         "   Levels are defined using",          group = GRP1, options = [CH1, CH2])
string  divChannelBreachesInput = input.string(BR1,         "   Breaches are determined using",     group = GRP1, options = [BR1, BR2, BR3, BR4, BR5, BR6, BR7, BR8])
bool    divChannelBiasInput     = input.string("Off",       "   Estimate unbreached channel bias",  group = GRP1, options = ["On", "Off"], tooltip = TT_BIAS) == "On"

string  GRP2 = "Other visuals"
string  colorBarModeInput       = input.string(CB2,         "Bar colors",                           group = GRP2, inline = "barMode", options = [CB0, CB1, CB2, CB3, CB4])
bool    barsEmptyOnDecVolInput  = input.bool(false,         "Don't color falling volume bars",      group = GRP2, inline = "barMode", tooltip = TT_BARS)
color   barsUpUpColorInput      = input.color(LIME,         "     🡑🡑",                              group = GRP2, inline = "barColors")
color   barsDnDnColorInput      = input.color(PINK,         "🡓🡓",                                   group = GRP2, inline = "barColors")
color   barsUpColorInput        = input.color(TEAL,         "🡑",                                    group = GRP2, inline = "barColors")
color   barsDnColorInput        = input.color(MAROON,       "🡓",                                    group = GRP2, inline = "barColors")
color   barsNtColorInput        = input.color(GRAY,         "N",                                    group = GRP2, inline = "barColors")
color   barsDivColorInput       = input.color(ORANGE,       "D",                                    group = GRP2, inline = "barColors")

bool    showCharDivInput        = input.bool(false,         "Divergence mark",                      group = GRP2, inline = "divChar")
string  charDivInput            = input.string("•",         "",                                     group = GRP2, inline = "divChar")
color   charDivColorInput       = input.color(ORANGE,       "",                                     group = GRP2, inline = "divChar")
bool    charDivAboveInput       = input.bool(true,          "Above bar",                            group = GRP2, inline = "divChar", tooltip = TT_DIV)


bool    showTooltipsInput       = input.bool(false,         "Tooltips of raw values",               group = GRP2)
bool    showInfoBoxInput        = input.bool(true,          "Information box",                      group = GRP2, tooltip = TT_LTF_BOX)
string  infoBoxSizeInput        = input.string("small",     "  ",                                   group = GRP2, inline = "infoBox", options = ["tiny", "small", "normal", "large", "huge", "auto"])
string  infoBoxYPosInput        = input.string("bottom",    "↕",                                    group = GRP2, inline = "infoBox", options = ["top", "middle", "bottom"])
string  infoBoxXPosInput        = input.string("left",      "↔",                                    group = GRP2, inline = "infoBox", options = ["left", "center", "right"])
color   infoBoxColorInput       = input.color(GRAY_MD,      "",                                     group = GRP2, inline = "infoBox")
color   infoBoxTxtColorInput    = input.color(BLACK,        "T",                                    group = GRP2, inline = "infoBox")

string  GRP3 = "Marker/Alert conditions"
string  markerUpDvModeInput     = input.string(ST0,         "Up markers on transitions to  ",       group = GRP3, inline = "upMarker", options = [ST0, ST1, ST2])
string  markerUpDivModeInput    = input.string(SV0,         "",                                     group = GRP3, inline = "upMarker", options = [SV0, SV1, SV2])
color   markerUpColorInput		= input.color(ORANGE_BR,      "🡑",			                               group = GRP3, inline = "upMarker", tooltip = TT_MARKERS)
string  markerDnDvModeInput     = input.string(ST0,         "Down markers on transitions to",       group = GRP3, inline = "dnMarker", options = [ST0, ST3, ST4])
string  markerDnDivModeInput    = input.string(SV0,         "",                                     group = GRP3, inline = "dnMarker", options = [SV0, SV3, SV4])
color   markerDnColorInput		= input.color(ORANGE_BR,      "🡓",                                    group = GRP3, inline = "dnMarker")
bool    markerBarPolarityInput  = input.bool(false,         "Filter on bar polarity  ",             group = GRP3, inline = "Filters1")
bool    markerClosePolarityInput= input.bool(false,         "Filter on close-to-close polarity",    group = GRP3, inline = "Filters1", tooltip = TT_FILTERS)
bool    markerCciStateInput     = input.bool(false,         "Filter on bull/bear CCI     ",         group = GRP3, inline = "Filters2")
bool    markerRisingVolInput    = input.bool(false,         "Filter on rising volume",              group = GRP3, inline = "Filters2")
bool    markerDivInput          = input.bool(false,         "Filter on divergence in last n bars",  group = GRP3, inline = "Filters3")
int     markerDivBarsInput      = input.int(5,              "",                                     group = GRP3, inline = "Filters3", minval = 1)
bool    markerEwInput           = input.bool(false,         "Filter on bull/bear Efficient Work",   group = GRP3, inline = "Filters4")
string  alertUpMsgInput         = input.text_area("▲",      "Up alert text",                        group = GRP3)
string  alertDnMsgInput         = input.text_area("▼",      "Down alert text",					            group = GRP3)
//#endregion



//#region ———————————————————— Functions


//@function     Determines when a state is entered on a bar where the previous state was different.
//@param state  (series bool) The state whose transition into must be identified.
//@returns      (series bool) `true` on the bar where we transition into the state, `false` otherwise.
transitionTo(series bool state) =>
    bool result = (not state[1] and state)


//@function     Determines a "plot_style" to be used from a user's input.
//@param state  (input string) The user selection string of his line style choice (depends on the `STL1`, `STL2` and `STL3` string constants).
//@returns      (plot_style) The `style` named argument required in `plot()`.
lineStyleFromUserInput(userSelection) =>
    result = switch userSelection
        STL1 => plot.style_line
        STL2 => plot.style_circles
        STL3 => plot.style_cross
        => plot.style_line
//#endregion



//#region ———————————————————— Calculations


// Lower timeframe (LTF) used to mine intrabars.
var string intrabarTf = PCltf.ltf(ltfModeInput, LTF1, LTF2, LTF3, LTF4, LTF5, LTF6, LTF7, LTF8, LTF9, LTF10)

// Get two arrays, one each for up and dn volumes of intrabars. `dnVolumes` values are negative.
[upVolumes, dnVolumes] = request.security_lower_tf(syminfo.tickerid, intrabarTf, LucfTa.upDnIntrabarVolumesByPolarity())

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

// Total volume for intrabars.
float intrabarVolume = totalUpVolume - totalDnVolume

// Delta volume
float dv = totalUpVolume + totalDnVolume

// Delta volume percent
float dvPct    = (dv / intrabarVolume) * 100
float dvWeight = math.abs(dvPct / 100)

// Relative volume weight
float relVolPctRank   = ta.percentrank(intrabarVolume, relVolLookbackInput) / 100.
float relVolumeWeight = na(dv) ? na : useRelVolWeightInput ? relVolPctRank : 1

// Combined weight
float combinedWeight  = dvWeight * relVolumeWeight

// MAs of reference source and capped dv%-weighted source.
float weightedSource = refSourceInput + (math.sign(dvPct) * math.min(refSourceInput * combinedWeight, sigmaCapInput * ta.stdev(refSourceInput, refLengthInput)))
float reference      = LucfTa.ma(refTypeInput, refSourceInput, refLengthInput)
float dvWeightedRef  = LucfTa.ma(refTypeInput, weightedSource, refLengthInput)

// Determine bull/bear and strong bull/bear states of the DV channel.
bool dvChannelBull = dvWeightedRef > reference
bool dvChannelBear = not dvChannelBull
bool dvChannelBullStrong = dvChannelBull and close > reference and ta.rising(reference, 1)  and ta.rising(dvWeightedRef, 1)
bool dvChannelBearStrong = dvChannelBear and close < reference and ta.falling(reference, 1) and ta.falling(dvWeightedRef, 1)

// Intrabar stats
[intrabars, chartBarsCovered, avgIntrabars] = PCltf.ltfStats(upVolumes)
float volumeOnAllIntrabars = ta.cum(intrabarVolume)
float allIntrabars = ta.cum(intrabars)

// Error detection
if volumeOnAllIntrabars == 0 and barstate.islast
    runtime.error("No volume is provided by the data vendor.")
else if allIntrabars == 0 and barstate.islast
    runtime.error("No intrabar information exists at the '" + intrabarTf + "' timeframe.")


// ————— Divergence channel

// Detect divergences between the slope of the reference line and that of the DV-weighted line.
bool divergence = dv != 0 and math.sign(ta.change(reference)) != math.sign(ta.change(dvWeightedRef))

// Level sources
float divChannelHiSrc = divChannelLevelsInput == CH1 ? high : math.max(open, close)
float divChannelLoSrc = divChannelLevelsInput == CH1 ? low  : math.min(open, close)

// Breach sources
[divBreachHiSrc, divBreachLoSrc] =
  switch divChannelBreachesInput
    BR1 => [low,    high]
    BR2 => [high,   low]
    BR3 => [close,  close]
    BR4 => [open,   open]
    BR5 => [hl2,    hl2]
    BR6 => [hlc3,   hlc3]
    BR7 => [hlcc4,  hlcc4]
    BR8 => [ohlc4,  ohlc4]
    => [float(na), float(na)]

// Update the divergence channel.
[divChannelHi, divChannelLo, divChannelBull, divChannelBear, divChannelBreached, newDivChannel, preBreachUpChanges, preBreachDnChanges] = 
  LucfTa.divergenceChannel(divergence, divChannelHiSrc, divChannelLoSrc, divBreachHiSrc, divBreachLoSrc)

// If needed, take a guess on the state of the channel when it has not yet been breached.
bool preBreachBiasBull = not divChannelBreached and divChannelBiasInput and preBreachUpChanges > preBreachDnChanges
bool preBreachBiasBear = not divChannelBreached and divChannelBiasInput and preBreachUpChanges < preBreachDnChanges

// Strong bull/bear states occur when the divergence channel's bull/bear state matches that of the DV channel.
bool divChannelBullStrong = divChannelBull and dvChannelBullStrong
bool divChannelBearStrong = divChannelBear and dvChannelBearStrong



// ————— Marker filters and triggers

// Bar polarity
bool barUp = close > open
bool barDn = close < open

// Close-to-close polarity
bool closeToCloseUp = ta.change(close) > 0
bool closeToCloseDn = ta.change(close) < 0

// CCI bull/bear
float cciSignal = ta.cci(close, refLengthInput)
bool cciBull    = cciSignal > 0
bool cciBear    = cciSignal < 0

// RIsing volume
bool risingVolume = ta.change(volume) > 0

// Divergence in last n bars
bool divPresent = ta.barssince(divergence) <= markerDivBarsInput

// Efficient work
float ew = LucfTa.efficientWork(refLengthInput)
bool ewBull = ew > 0
bool ewBear = ew < 0

// Base conditions for markers to appear.
bool upMarkerDvCondition = 
  switch markerUpDvModeInput
    ST1 => transitionTo(dvChannelBullStrong)
    ST2 => transitionTo(dvChannelBull) or transitionTo(dvChannelBullStrong)
    => false
bool upMarkerDivCondition = 
  switch markerUpDivModeInput
    SV1 => transitionTo(divChannelBullStrong)
    SV2 => transitionTo(divChannelBull) or transitionTo(divChannelBullStrong)
    => false
bool dnMarkerDvCondition = 
  switch markerDnDvModeInput
    ST3 => transitionTo(dvChannelBearStrong)
    ST4 => transitionTo(dvChannelBear) or transitionTo(dvChannelBearStrong)
    => false
bool dnMarkerDivCondition = 
  switch markerDnDivModeInput
    SV3 => transitionTo(divChannelBearStrong)
    SV4 => transitionTo(divChannelBear) or transitionTo(divChannelBearStrong)
    => false

// Apply filters to base conditions.
bool upMarker = upMarkerDvCondition or upMarkerDivCondition
bool dnMarker = dnMarkerDvCondition or dnMarkerDivCondition
upMarker := 
  (markerUpDvModeInput != ST0 or markerUpDivModeInput != SV0) and upMarker and barstate.isconfirmed and
  (not markerBarPolarityInput   or barUp)          and 
  (not markerClosePolarityInput or closeToCloseUp) and 
  (not markerCciStateInput      or cciBull)        and
  (not markerRisingVolInput     or risingVolume)   and
  (not markerDivInput           or divPresent)     and
  (not markerEwInput            or ewBull)
dnMarker := 
  (markerDnDvModeInput != ST0 or markerDnDivModeInput != SV0) and dnMarker and barstate.isconfirmed and
  (not markerBarPolarityInput   or barDn)          and 
  (not markerClosePolarityInput or closeToCloseDn) and 
  (not markerCciStateInput      or cciBear)        and
  (not markerRisingVolInput     or risingVolume)   and
  (not markerDivInput           or divPresent)     and
  (not markerEwInput            or ewBear)
//#endregion



//#region ———————————————————— Visuals


// ————— DV Channel lines and fill.

// Determine colors.
[refLineColor, dvLineColor, dvFillColor] =
  switch
    dvChannelBullStrong => [refLineUpUpColorInput, dvLineUpUpColorInput, dvFillUpUpColorInput]
    dvChannelBearStrong => [refLineDnDnColorInput, dvLineDnDnColorInput, dvFillDnDnColorInput]
    dvChannelBull       => [refLineUpColorInput,   dvLineUpColorInput,   dvFillUpColorInput]
    dvChannelBear       => [refLineDnColorInput,   dvLineDnColorInput,   dvFillDnColorInput]
    => [color(na), color(na), color(na)]
color dvColor = dv > 0 ? dvLineUpUpColorInput : dv < 0 ? dvLineDnDnColorInput : color.silver

// Styles for lines.
var refLineStyle = lineStyleFromUserInput(refLineStyleInput)
var dvLineStyle  = lineStyleFromUserInput(dvLineStyleInput)

// Plot lines and fill them.
var bool plotDvLineValues = reflLineShowInput or dvlLineShowInput or dvFillShowInput
dvRefPlot = plot(plotDvLineValues ? dvWeightedRef : na,            "DV-weighted Reference",  dvlLineShowInput  ? dvLineColor  : na, dvLineWidthInput,  dvLineStyle)
refPlot   = plot(plotDvLineValues and not na(dv) ? reference : na, "Reference",              reflLineShowInput ? refLineColor : na, refLineWidthInput, refLineStyle)
fill(dvRefPlot, refPlot, reference, dvWeightedRef, dvFillShowInput ? dvFillColor : na, dvFillShowInput ? color.new(dvFillColor, 90) : na, "Fill")


// ————— Divergence channel lines and fill.

// Determine colors.
[divLinesColor, divFillColor] =
  switch
    divChannelBreached =>
        switch
            divChannelBullStrong => [divLinesUpUpColorInput, divFillUpUpColorInput]
            divChannelBearStrong => [divLinesDnDnColorInput, divFillDnDnColorInput]
            divChannelBull       => [divLinesUpColorInput,   divFillUpColorInput]
            divChannelBear       => [divLinesDnColorInput,   divFillDnColorInput]
            => [divLinesNtColorInput, divFillNtColorInput]
    =>
        switch
            divChannelBiasInput and preBreachBiasBull => [divLinesUpColorInput, divFillUpColorInput]
            divChannelBiasInput and preBreachBiasBear => [divLinesDnColorInput, divFillDnColorInput]
            => [divLinesNtColorInput, divFillNtColorInput]

// Plot the channel levels and fill.
var bool plotDivLineValues = divLinesShowInput or divFillShowInput
var divLineStyle = lineStyleFromUserInput(divLinesStyleInput)
float divChannelMid = math.avg(divChannelHi, divChannelLo)
divChannelHiPlot = plot(plotDivLineValues ? divChannelHi  : na, "Divergence Channel Hi", not newDivChannel and divLinesShowInput ? divLinesColor : na, divLinesWidthInput, divLineStyle)
divChannelLoPlot = plot(plotDivLineValues ? divChannelLo  : na, "Divergence Channel Lo", not newDivChannel and divLinesShowInput ? divLinesColor : na, divLinesWidthInput, divLineStyle)

// This midline is used to start/end the two different gradient fills used to fill the divergence channel.
divChannelMidPlot = plot(plotDivLineValues ? divChannelMid : na, "Divergence Channel Mid", na, display = display.none)

// Fill from the middle going up and down.
fill(divChannelHiPlot, divChannelMidPlot, divChannelHi, divChannelMid, not newDivChannel and divFillShowInput ? divFillColor : na, not newDivChannel and divFillShowInput ? color.new(divFillColor, 99) : na)
fill(divChannelMidPlot, divChannelLoPlot, divChannelMid, divChannelLo, not newDivChannel and divFillShowInput ? color.new(divFillColor, 99) : na, not newDivChannel and divFillShowInput ? divFillColor : na)


// ————— Display key values in indicator values and Data Window.

float signedDvWeight = dvPct / 100
float signedCombinedWeight = math.sign(signedDvWeight) * combinedWeight
displayLocations = display.status_line + display.data_window
plot(na,                    "═════════════════",      display = displayLocations)
plot(signedDvWeight,        "DV% weight (1=100%)",    display = displayLocations, color = dvColor)
plot(relVolumeWeight,       "Relative Volume weight", display = displayLocations)
plot(signedCombinedWeight,  "Combined weight",        display = displayLocations, color = dvColor)
plot(na,                    "═════════════════",      display = displayLocations)
plot(dv,                    "Volume delta",           display = displayLocations, color = dvColor)
plot(totalUpVolume,         "Up volume for the bar",  display = displayLocations, color = dvLineUpUpColorInput)
plot(totalDnVolume,         "Dn volume for the bar",  display = displayLocations, color = dvLineDnDnColorInput)
plot(intrabarVolume,        "Total intrabar volume",  display = displayLocations)
plot(na,                    "═════════════════",      display = displayLocations)
plot(intrabars,             "Intrabars in this bar",  display = displayLocations)
plot(avgIntrabars,          "Average intrabars",      display = displayLocations)
plot(chartBarsCovered,      "Chart bars covered",     display = displayLocations)
plot(bar_index + 1,         "Chart bars",             display = displayLocations)


// ————— Markers

plotchar(upMarker, "Up Marker",   "▲", location.belowbar, markerUpColorInput, size = size.tiny)
plotchar(dnMarker, "Down Marker", "▼", location.abovebar, markerDnColorInput, size = size.tiny)


// ————— Alerts

switch
    upMarker => alert(alertUpMsgInput)
    dnMarker => alert(alertDnMsgInput)


// ————— Chart bars.

// Color
color barColor =
  switch colorBarModeInput
    CB0 =>
        na
    CB1 =>
        switch
            divergence           => barsDivColorInput
    CB2 =>
        switch
            divergence           => barsDivColorInput
            dvChannelBullStrong  => barsUpUpColorInput
            dvChannelBearStrong  => barsDnDnColorInput
            dvChannelBull        => barsUpColorInput
            dvChannelBear        => barsDnColorInput
            => barsNtColorInput
    CB3 =>
        switch
            divergence           => barsDivColorInput
            divChannelBullStrong => barsUpUpColorInput
            divChannelBearStrong => barsDnDnColorInput
            divChannelBull       => barsUpColorInput
            divChannelBear       => barsDnColorInput
            => barsNtColorInput
    CB4 =>
        switch
            divergence => barsDivColorInput
            dvChannelBullStrong and divChannelBullStrong => barsUpUpColorInput
            dvChannelBearStrong and divChannelBearStrong => barsDnDnColorInput
            (dvChannelBull or dvChannelBullStrong) and (divChannelBull or divChannelBullStrong) => barsUpColorInput
            (dvChannelBear or dvChannelBearStrong) and (divChannelBear or divChannelBearStrong) => barsDnColorInput
            => barsNtColorInput
    => na

// Empty bodies on decreasing chart volume.
if barsEmptyOnDecVolInput and ta.falling(volume, 1) and not divergence
    barColor := na

barcolor(barColor)


// ————— Plot character showing divergences. 

plotchar(showCharDivInput ? divergence : na, "Divergence character", charDivInput, charDivAboveInput ? location.abovebar : location.belowbar, charDivColorInput, size = size.tiny)


// ————— Tooltips containing bar stats.
if showTooltipsInput
    string tooltipText = 
      "DV = "    + str.tostring(totalUpVolume, format.volume) +
      " − "      + str.tostring(math.abs(totalDnVolume), format.volume) +
      " = "      + str.tostring(dv, format.volume) +
      "\nDV% = " + str.tostring(dv, format.volume) +
      " / "      + str.tostring(intrabarVolume, format.volume) +
      " = "      + str.tostring(dvPct, format.percent) +
      str.format("\n\nDV weight = {0,number,0.000}\nRelVol weight = {1,number,0.000}\nCombined weight = {2,number,0.000}", signedDvWeight, relVolumeWeight, signedCombinedWeight)
    label.new(bar_index, high, " \n \n \n \n \n \n ", style = label.style_none, color = color(na), tooltip = tooltipText)


// ————— Display information box only once on the last historical bar because it doesn't need to update in real time.
// 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)
    color infoBoxBgColor = infoBoxColorInput
    string txt = str.format(
      "Uses intrabars at {0}\nAvg intrabars per chart bar: {1,number,#.##}\nChart bars covered: {2} / {3} ({4,number,percent})", 
      PCtime.formattedNoOfPeriods(timeframe.in_seconds(intrabarTf) * 1000), 
      avgIntrabars, chartBarsCovered, bar_index + 1, chartBarsCovered / (bar_index + 1))
    if avgIntrabars < 5
        txt += "\nThis quantity of intrabars is dangerously small.\nResults will not be as reliable with so few."
        infoBoxBgColor := color.red
    table.cell(infoBox, 0, 0, txt, text_color = infoBoxTxtColorInput, text_size = infoBoxSizeInput, bgcolor = infoBoxBgColor)
//#endregion
