oscillator-workbench-chart-lucf.pine
Copy
// 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("Oscillator Workbench — Chart [LucF]", "OWC", true, precision = 4, max_bars_back = 1000)
// Oscillator Workbench — Chart [LucF]
// v3, 2022.10.27 17:58 — LucF
// 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 TradingView/ta/4 as TvTa
import LucF/ta/2 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
// 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"
// Oscillators
string OS00 = "None"
string OS01 = "RSI (SL)"
string OS02 = "MACD (S)"
string OS03 = "CCI (SL)"
string OS04 = "Chande Momentum Oscillator (SL)"
string OS05 = "Stochastic (SL)"
string OS06 = "Stochastic RSI (SL)"
string OS09 = "MFI (L)"
string OS10 = "True Strength Index (SL)"
string OS11 = "Williams % Range (L)"
string OS12 = "Ultimate Oscillator (L)"
string OS13 = "Klinger Volume Oscillator (L)"
string OS14 = "Schaff Trend Cycle (SL)"
string OS15 = "Volume Zone Oscillator (L)"
string OS16 = "Price Zone Oscillator (L)"
string OS17 = "Sentiment Zone Oscillator (SL)"
string OS18 = "Wave Period Oscillator (L)"
string OS19 = "Volume Buoyancy (L)"
string OS20 = "Coppock Curve (SL)"
string OS21 = "Aroon (L)"
string OS22 = "DMI (L)"
string OS23 = "TRIX (SL)"
string OS24 = "Ease of Movement (L)"
string OS25 = "Fisher Transform (SL)"
string OS26 = "Signs of the Times"
string OS27 = "Hilbert Transform (S)"
string OS28 = "Intraday Intensity Index"
string OS52 = "True Range"
string OS54 = "Momentum (SL)"
string OS55 = "Rate of Change (SL)"
string OS90 = "Efficient Work (L)"
// Oscillator Channel capping mode
string CAP1 = "Standard deviations"
string CAP2 = "Multiples of ATR"
// Divergence detection mode
string DIV1 = "Opposite directions of the weighted and reference lines"
string DIV2 = "Opposite directions of the oscillator and the polarity of close to close"
// Line styles
string STL1 = "Line"
string STL2 = "Circles"
string STL3 = "Crosses"
// Marker oscillator channel transitions
string ST0 = "None"
string ST1 = "Oscillator channel strong bull state"
string ST2 = "Oscillator channel bull or strong bull state"
string ST3 = "Oscillator channel strong bear state"
string ST4 = "Oscillator 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 oscillator 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, close and open (ohlc4)"
// Tooltips
string TT_OSC1 = "To use one of the oscillators provided in the Workbench, select it here.
\n\nThe 'S' or 'L' in parentheses indicates when the 'Source' or 'Length' fields of this line are taken into account when calculating the oscillator's value.
\n\nNote that for some of the oscillators, assumptions are made concerning their different parameters when they are more complex than just a source and length.
See the `oscCalc()` function in this indicator's code for all the details, and ask me in a comment if you can't find the information you need."
string TT_OSC2 = "To use a second oscillator provided in the Workbench, select it here.
If you select two oscillators, each of their weight will count for an equal part in the aggregate oscillator weight."
string TT_OSCX = "To use an external oscillator, check the checkbox, select the external indicator you want to use, and specify its center value.
\n\nIf you want only the external oscillator to be used, remember to select 'None' for the 'Oscillator 1' and 'Oscillator 2' choices above.
If you do have oscillators selected for 'Oscillator 1' and/or 'Oscillator 2', their weight will be combined with that of the external oscillator.
This way you can have up to three oscillator weights composing the aggregate oscillator weight."
string TT_RVOL = "When checked, the aggregate weight of the oscillator(s) will be multiplied by the weight of the relative volume for the bar.
This weight is determined using the percentile rank of the bar's volume in the number of bars you specify. The range of the weight is 0 to 1."
string TT_REF = "Your choices here determine the reference that will be used as the oscillator channel's baseline.
The MA type and length defined here are also used to calculate the MA of the weighted version of the reference's source."
string TT_CAP = "This allows you to control the Oscillator channel's maximum height by limiting how far the weighted line can extend from the reference line.
It is useful to keep the chart's vertical scale in check. The lower you set it, the less contours you will get in the weighted line.\n\n
Keep in mind that this setting can also impact where divergences will occur on the chart.\n\n
Using '" + CAP2 + "' will restrict the height more agressively. The ATR unit value used is its average over 20 bars."
string TT_LTF = "Your selection here controls how many intrabars will be analyzed for each chart bar.
The more intrabars you analyze per chart bar, the less chart bars will be covered by the indicator's calculations
because a maximum of 100K intrabars can be analyzed.\n\n
With the first four choices the quantity of intrabars is determined from the type of chart bar coverage you want.
The last four choices allow you to select approximately how many intrabars you want analyzed per chart bar."
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_DIV = "Using the second option here will produce less divergences with oscillators such as RSI, which closely follow price movements.
It will also produce vastly different behaviors between oscillators."
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_FILTERS = "The filters are additional conditions that must be true for a marker to appear.
\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'Rising volume' means the volume of the bar must be higher than that of the previous one. 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\n'Bull/bear Oscillator' means that the value of the oscillator you select must be above/below its centerline.
\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 GRP1 = "Oscillators and relative volume"
string osc1TypeInput = input.string(OS01, "Oscillator 1", group = GRP1, inline = "osc1", options = [OS00, OS21, OS03, OS04, OS20, OS22, OS24, OS90, OS25, OS27, OS28, OS13, OS02, OS09, OS54, OS16, OS55, OS01, OS14, OS17, OS26, OS05, OS06, OS23, OS52, OS10, OS12, OS19, OS15, OS18, OS11])
string osc1SourceInput = input.string("close", "", group = GRP1, inline = "osc1", options = ["open", "high", "low", "close", "hl2", "hlc3", "ohlc4", "hlcc4", "volume"])
int osc1LengthInput = input.int(20, " Length", group = GRP1, inline = "osc1", minval = 2, tooltip = TT_OSC1)
string osc2TypeInput = input.string(OS00, "Oscillator 2", group = GRP1, inline = "osc2", options = [OS00, OS21, OS03, OS04, OS20, OS22, OS24, OS90, OS25, OS27, OS28, OS13, OS02, OS09, OS54, OS16, OS55, OS01, OS14, OS17, OS26, OS05, OS06, OS23, OS52, OS10, OS12, OS19, OS15, OS18, OS11])
string osc2SourceInput = input.string("close", "", group = GRP1, inline = "osc2", options = ["open", "high", "low", "close", "hl2", "hlc3", "ohlc4", "hlcc4", "volume"])
int osc2LengthInput = input.int(20, " Length", group = GRP1, inline = "osc2", minval = 2, tooltip = TT_OSC2)
bool oscXInput = input.bool(false, "Oscillator 3 (external)", group = GRP1, inline = "oscX")
float oscXSourceInput = input.source(close, "", group = GRP1, inline = "oscX")
float oscXCenterInput = input.int(0, "Center", group = GRP1, inline = "oscX", tooltip = TT_OSCX)
bool useRelVolWeightInput = input.bool(false, "Use weight of relative volume over n bars", group = GRP1, inline = "RelVolW", tooltip = TT_RVOL)
int relVolLookbackInput = input.int(100, "", group = GRP1, inline = "RelVolW", minval = 2)
string GRP2 = "Oscillator channel"
bool reflLineShowInput = input.bool(false, "Reference line ", group = GRP2, inline = "refLine")
int refLineWidthInput = input.int(1, " Width", group = GRP2, inline = "refLine", minval = 1)
string refLineStyleInput = input.string(STL1, "", group = GRP2, inline = "refLine", options = [STL1, STL2, STL3])
color refLineUpUpColorInput = input.color(LIME, " 🡑🡑", group = GRP2, inline = "refLineColors")
color refLineDnDnColorInput = input.color(PINK, "🡓🡓", group = GRP2, inline = "refLineColors")
color refLineUpColorInput = input.color(TEAL, " 🡑", group = GRP2, inline = "refLineColors")
color refLineDnColorInput = input.color(MAROON, "🡓", group = GRP2, inline = "refLineColors", tooltip = TT_COLORS)
string refTypeInput = input.string(MA06, " ", group = GRP2, inline = "ref", options = [MA01, MA02, MA03, MA04, MA05, MA06, MA07, MA08], tooltip = TT_REF)
string refSourceInput = input.string("close", "", group = GRP2, inline = "ref", options = ["open", "high", "low", "close", "hl2", "hlc3", "ohlc4", "hlcc4", "volume"])
int refLengthInput = input.int(20, " Length", group = GRP2, inline = "ref", minval = 2)
bool osclLineShowInput = input.bool(false, "Weighted line", group = GRP2, inline = "oscLine")
int oscLineWidthInput = input.int(2, " Width", group = GRP2, inline = "oscLine", minval = 1)
string oscLineStyleInput = input.string(STL1, "", group = GRP2, inline = "oscLine", options = [STL1, STL2, STL3])
color oscLineUpUpColorInput = input.color(LIME, " 🡑🡑", group = GRP2, inline = "oscLineColors")
color oscLineDnDnColorInput = input.color(PINK, "🡓🡓", group = GRP2, inline = "oscLineColors")
color oscLineUpColorInput = input.color(TEAL, " 🡑", group = GRP2, inline = "oscLineColors")
color oscLineDnColorInput = input.color(MAROON, "🡓", group = GRP2, inline = "oscLineColors")
float oscChannelCapInput = input.float(3.0, " Cap the channel's height to", group = GRP2, inline = "cap", minval = 1, step = 0.5)
string oscChannelCapModeInput = input.string(CAP1, "", group = GRP2, inline = "cap", options = [CAP1, CAP2], tooltip = TT_CAP)
bool oscChannelShowInput = input.bool(false, "Oscillator channel", group = GRP2, inline = "oscChannel")
color oscChannelUpUpColorInput= input.color(LIME_MD, " 🡑🡑", group = GRP2, inline = "oscChannelColors")
color oscChannelDnDnColorInput= input.color(PINK_MD, "🡓🡓", group = GRP2, inline = "oscChannelColors")
color oscChannelUpColorInput = input.color(TEAL_MD, " 🡑", group = GRP2, inline = "oscChannelColors")
color oscChannelDnColorInput = input.color(MAROON_MD, "🡓", group = GRP2, inline = "oscChannelColors")
string GRP3 = "Divergence channel"
bool divLinesShowInput = input.bool(true, "Divergence levels", group = GRP3, inline = "divLines")
int divLinesWidthInput = input.int(1, " Width", group = GRP3, inline = "divLines", minval = 1)
string divLinesStyleInput = input.string(STL1, "", group = GRP3, inline = "divLines", options = [STL1, STL2, STL3])
color divLinesUpUpColorInput = input.color(LIME, " 🡑🡑", group = GRP3, inline = "divLinesColors")
color divLinesDnDnColorInput = input.color(PINK, "🡓🡓", group = GRP3, inline = "divLinesColors")
color divLinesUpColorInput = input.color(TEAL, " 🡑", group = GRP3, inline = "divLinesColors")
color divLinesDnColorInput = input.color(MAROON, "🡓", group = GRP3, inline = "divLinesColors")
color divLinesNtColorInput = input.color(GRAY, "N", group = GRP3, inline = "divLinesColors")
bool divChannelShowInput = input.bool(false, "Divergence channel", group = GRP3, inline = "divChannel")
color divChannelUpUpColorInput= input.color(LIME_MD, " 🡑🡑", group = GRP3, inline = "divChannelColors")
color divChannelDnDnColorInput= input.color(PINK_MD, "🡓🡓", group = GRP3, inline = "divChannelColors")
color divChannelUpColorInput = input.color(TEAL_MD, " 🡑", group = GRP3, inline = "divChannelColors")
color divChannelDnColorInput = input.color(MAROON_MD, "🡓", group = GRP3, inline = "divChannelColors")
color divChannelNtColorInput = input.color(GRAY_MD, "N", group = GRP3, inline = "divChannelColors")
string divChannelLevelsInput = input.string(CH1, " Levels are defined using", group = GRP3, options = [CH1, CH2])
string divChannelBreachesInput = input.string(BR1, " Breaches are determined using", group = GRP3, options = [BR1, BR2, BR3, BR4, BR5, BR6, BR7, BR8])
string divDetectionModeInput = input.string(DIV1, " Detect divergences on", group = GRP3, inline = "divDetection", options = [DIV1, DIV2], tooltip = TT_DIV)
bool divChannelBiasInput = input.string("Off", " Estimate unbreached channel bias", group = GRP3, options = ["On", "Off"], tooltip = TT_BIAS) == "On"
bool charDivShowInput = input.bool(false, "Divergence mark", group = GRP3, inline = "divChar")
string charDivInput = input.string("•", "", group = GRP3, inline = "divChar")
color charDivColorInput = input.color(ORANGE, "", group = GRP3, inline = "divChar")
bool charDivAboveInput = input.bool(true, "Above bar", group = GRP3, inline = "divChar")
string GRP4 = "Bar coloring"
bool barColorsShowInput = input.bool(true, "Color bars", group = GRP4, inline = "barMode")
string colorBarModeInput = input.string(CB2, "", group = GRP4, inline = "barMode", options = [CB1, CB2, CB3, CB4])
bool barsEmptyOnDecVolInput = input.bool(false, "Don't color falling volume bars", group = GRP4, inline = "barMode", tooltip = TT_BARS)
color barsUpUpColorInput = input.color(LIME, " 🡑🡑", group = GRP4, inline = "barColors")
color barsDnDnColorInput = input.color(PINK, "🡓🡓", group = GRP4, inline = "barColors")
color barsUpColorInput = input.color(TEAL, "🡑", group = GRP4, inline = "barColors")
color barsDnColorInput = input.color(MAROON, "🡓", group = GRP4, inline = "barColors")
color barsNtColorInput = input.color(GRAY, "N", group = GRP4, inline = "barColors")
color barsDivColorInput = input.color(ORANGE, "D", group = GRP4, inline = "barColors")
string GRP5 = "Markers & Alert conditions"
string markerUpOscModeInput = input.string(ST0, "Up markers on transitions to ", group = GRP5, inline = "upMarker", options = [ST0, ST1, ST2])
string markerUpDivModeInput = input.string(SV0, "", group = GRP5, inline = "upMarker", options = [SV0, SV1, SV2])
color markerUpColorInput = input.color(ORANGE_BR, "", group = GRP5, inline = "upMarker", tooltip = TT_MARKERS)
string markerDnOscModeInput = input.string(ST0, "Down markers on transitions to", group = GRP5, inline = "dnMarker", options = [ST0, ST3, ST4])
string markerDnDivModeInput = input.string(SV0, "", group = GRP5, inline = "dnMarker", options = [SV0, SV3, SV4])
color markerDnColorInput = input.color(ORANGE_BR, "", group = GRP5, inline = "dnMarker")
bool markerClosePolarityInput= input.bool(false, "Filter on close to close polarity ", group = GRP5, inline = "Filters1", tooltip = TT_FILTERS)
bool markerRisingVolInput = input.bool(false, "Filter on rising volume", group = GRP5, inline = "Filters1")
bool markerDivInput = input.bool(false, "Filter on divergence in last n bars", group = GRP5, inline = "Filters2")
int markerDivBarsInput = input.int(5, "", group = GRP5, inline = "Filters2", minval = 1)
bool markerOscInput = input.bool(false, "Filter on bull/bear", group = GRP5, inline = "Filters3")
string markerOscTypeInput = input.string(OS01, "", group = GRP5, inline = "Filters3", options = [OS21, OS03, OS04, OS20, OS22, OS24, OS90, OS25, OS27, OS28, OS13, OS02, OS09, OS54, OS16, OS55, OS01, OS14, OS17, OS05, OS06, OS23, OS52, OS10, OS12, OS19, OS15, OS18, OS11])
string markerOscSourceInput = input.string("close", "", group = GRP5, inline = "Filters3", options = ["open", "high", "low", "close", "hl2", "hlc3", "ohlc4", "hlcc4", "volume"])
int markerOscLengthInput = input.int(20, "", group = GRP5, inline = "Filters3", minval = 2)
string alertUpMsgInput = input.text_area("▲", "Up alert text", group = GRP5)
string alertDnMsgInput = input.text_area("▼", "Down alert text", group = GRP5)
//#endregion
//#region ———————————————————— Functions
//@function Returns the centerline value for the `type` oscillator.
//@param type (simple string) The type of oscillator (uses constants that must be defined earlier in the script).
//@returns (series float) The centerline's value.
oscCenter(simple string type) =>
float result =
switch type
OS01 => 50.
OS05 => 50.
OS06 => 50.
OS09 => 50.
OS11 => -50.
OS12 => 50.
OS14 => 50.
=> 0.
//@function Returns the calculation of the `type` oscillator.
//@param type (simple string) The type of oscillator (uses constants that must be defined earlier in the script).
//@param src (series float) The source value used to calculate the oscillator, when one is needed.
//@param length (simple int) The length value used to calculate the oscillator, when one is needed.
//@returns (series float) The oscillator's value.
oscCalc(simple string type, series float src, simple int length) =>
float result =
switch type
OS00 => na
OS01 => ta.rsi(src, length)
OS02 =>
[_, _, histLine] = ta.macd(src, 12, 26, 9)
histLine
OS03 => ta.cci(src, length)
OS04 => ta.cmo(src, length)
OS05 => ta.stoch(src, high, low, length)
OS06 =>
float rsi = ta.rsi(src, length)
ta.sma(ta.stoch(rsi, rsi, rsi, length), 3)
OS09 => ta.mfi(hlc3, length)
OS10 => ta.tsi(src, length, length * 2)
OS11 => ta.wpr(length)
OS12 => TvTa.uo(length / 2, length, length * 2)
OS13 =>
[kvo, _] = TvTa.kvo(length, length * 2, length / 2)
kvo
OS14 => TvTa.stc(src, length, length * 2, length / 2, 3, 3)
OS15 => TvTa.vzo(length)
OS16 => TvTa.pzo(length)
OS17 => TvTa.szo(src, length)
OS18 => TvTa.wpo(length)
OS19 => LucfTa.buoyancy(volume, length, 1000)
OS20 => TvTa.coppock(src, length, length / 2, length / 2)
OS21 =>
[up, dn] = TvTa.aroon(length)
up - dn
OS22 =>
[up, dn, _] = ta.dmi(length, length)
up - dn
OS23 =>
[trix, signal, hist] = TvTa.trix(src, length, int(length * 0.66))
trix
OS24 => TvTa.eom(length)
OS25 => TvTa.ft(src, length)
OS26 => LucfTa.sott()
OS27 => TvTa.ht(src)
OS28 => ta.iii
OS52 => ta.tr * math.sign(ta.change(close))
OS54 => ta.mom(src, length)
OS55 => ta.roc(src, length)
OS90 => LucfTa.efficientWork(length)
=> na
//@function Returns the weight of an oscillator's value.
//@param osc (series float) The oscillator's value.
//@param center (simple float) The oscillator's centerline value.
//@returns (series float) The weight (-1 to +1) of the oscillator's value.
oscWeight(series float osc, simple float center = 0.0) =>
float normalizedOsc = osc - center
float historicalMax = math.abs(ta.max(normalizedOsc))
float historicalMin = math.abs(ta.min(normalizedOsc))
float result =
switch
osc > center => normalizedOsc / historicalMax
osc < center => - math.abs(normalizedOsc / historicalMin)
=> 0.
//@function Throws a runtime error if the oscillator requires volume and none is available.
//@param type (simple string) The type of oscillator (uses constants that must be defined earlier in the script).
//@returns (void) Nothing.
checkForNoVolume(simple string type) =>
bool oscUsesVolume = type == OS09 or type == OS15 or type == OS19 or type == OS24
if barstate.islast and ta.cum(nz(volume)) == 0 and oscUsesVolume
runtime.error("No volume is provided by the data vendor.")
//@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
// Stop the indicator when the oscillator requires volume to calculate.
checkForNoVolume(osc1TypeInput)
checkForNoVolume(osc2TypeInput)
// Oscillator value and weight.
var float osc1Center = oscCenter(osc1TypeInput)
var float osc2Center = oscCenter(osc2TypeInput)
var float oscXCenter = oscXCenterInput
float osc1 = oscCalc(osc1TypeInput, LucfTa.sourceStrToFloat(osc1SourceInput), osc1LengthInput)
float osc2 = oscCalc(osc2TypeInput, LucfTa.sourceStrToFloat(osc2SourceInput), osc2LengthInput)
float oscX = oscXInput ? oscXSourceInput : na
int qtyOfOscillators = (not na(osc1) ? 1 : 0) + (not na(osc2) ? 1 : 0) + (not na(oscX) ? 1 : 0)
float weight1 = oscWeight(osc1, osc1Center)
float weight2 = oscWeight(osc2, osc2Center)
float weightX = oscWeight(oscX, oscXCenter)
float weight = (weight1 + weight2 + weightX) / qtyOfOscillators
// Relative volume weight
float relVolPctRank = ta.percentrank(volume, relVolLookbackInput) / 100.
float relVolumeWeight = useRelVolWeightInput and not na(volume) ? relVolPctRank : 1.
// Combined weight
float combinedWeight = weight * relVolumeWeight
// MAs of reference source and capped weighted source.
float refSource = LucfTa.sourceStrToFloat(refSourceInput)
float capUnits = oscChannelCapModeInput == CAP1 ? ta.stdev(refSource, refLengthInput) : ta.atr(20)
float weightedSource = refSource + (math.sign(combinedWeight) * math.min(refSource * math.abs(combinedWeight), capUnits * oscChannelCapInput))
float reference = LucfTa.ma(refTypeInput, refSource, refLengthInput)
float weightedRef = LucfTa.ma(refTypeInput, weightedSource, refLengthInput)
// Determine bull/bear and strong bull/bear states of the oscillator channel.
bool oscChannelBull = weightedRef > reference
bool oscChannelBear = not oscChannelBull
bool oscChannelBullStrong = oscChannelBull and close > reference and ta.rising(reference, 1) and ta.rising(weightedRef, 1)
bool oscChannelBearStrong = oscChannelBear and close < reference and ta.falling(reference, 1) and ta.falling(weightedRef, 1)
// ————— Divergence channel
// Detect divergences between the slope of the reference line and that of the weighted line.
bool divergence = divDetectionModeInput == DIV1 ? math.sign(ta.change(reference)) != math.sign(ta.change(weightedRef)) : math.sign(ta.change(combinedWeight)) != math.sign(ta.change(close))
// Level sources
float divChannelHiSrc = divChannelLevelsInput == CH1 ? high : math.max(open, close)
float divChannelLoSrc = divChannelLevelsInput == CH1 ? low : math.min(open, close)
// Breach sources
float divBreachHiSrc = na
float divBreachLoSrc = na
switch divChannelBreachesInput
BR1 =>
divBreachHiSrc := low
divBreachLoSrc := high
BR2 =>
divBreachHiSrc := high
divBreachLoSrc := low
BR3 =>
divBreachHiSrc := close
divBreachLoSrc := close
BR4 =>
divBreachHiSrc := open
divBreachLoSrc := open
BR5 =>
divBreachHiSrc := hl2
divBreachLoSrc := hl2
BR6 =>
divBreachHiSrc := hlc3
divBreachLoSrc := hlc3
BR7 =>
divBreachHiSrc := hlcc4
divBreachLoSrc := hlcc4
BR8 =>
divBreachHiSrc := ohlc4
divBreachLoSrc := ohlc4
// 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 oscillator channel.
bool divChannelBullStrong = divChannelBull and oscChannelBullStrong
bool divChannelBearStrong = divChannelBear and oscChannelBearStrong
// ————— Marker filters and triggers
// Close-to-close polarity
bool closeToCloseUp = ta.change(close) > 0
bool closeToCloseDn = ta.change(close) < 0
// Oscillator is bull/bear
var float markerOscCenter = oscCenter(markerOscTypeInput)
float markerOsc = oscCalc(markerOscTypeInput, LucfTa.sourceStrToFloat(markerOscSourceInput), markerOscLengthInput)
bool oscBull = markerOsc > markerOscCenter
bool oscBear = markerOsc < markerOscCenter
// RIsing volume
bool risingVolume = ta.change(volume) > 0
// Divergence in last n bars
bool divPresent = ta.barssince(divergence) <= markerDivBarsInput
// Base conditions for markers to appear.
bool upMarkerOscCondition =
switch markerUpOscModeInput
ST1 => transitionTo(oscChannelBullStrong)
ST2 => transitionTo(oscChannelBull) or transitionTo(oscChannelBullStrong)
=> false
bool upMarkerDivCondition =
switch markerUpDivModeInput
SV1 => transitionTo(divChannelBullStrong)
SV2 => transitionTo(divChannelBull) or transitionTo(divChannelBullStrong)
=> false
bool dnMarkerOscCondition =
switch markerDnOscModeInput
ST3 => transitionTo(oscChannelBearStrong)
ST4 => transitionTo(oscChannelBear) or transitionTo(oscChannelBearStrong)
=> false
bool dnMarkerDivCondition =
switch markerDnDivModeInput
SV3 => transitionTo(divChannelBearStrong)
SV4 => transitionTo(divChannelBear) or transitionTo(divChannelBearStrong)
=> false
// Apply filters to base conditions.
bool upMarker = upMarkerOscCondition or upMarkerDivCondition
bool dnMarker = dnMarkerOscCondition or dnMarkerDivCondition
upMarker :=
(markerUpOscModeInput != ST0 or markerUpDivModeInput != SV0) and upMarker and barstate.isconfirmed and
(not markerClosePolarityInput or closeToCloseUp) and
(not markerOscInput or oscBull) and
(not markerRisingVolInput or risingVolume) and
(not markerDivInput or divPresent)
dnMarker :=
(markerDnOscModeInput != ST0 or markerDnDivModeInput != SV0) and dnMarker and barstate.isconfirmed and
(not markerClosePolarityInput or closeToCloseDn) and
(not markerOscInput or oscBear) and
(not markerRisingVolInput or risingVolume) and
(not markerDivInput or divPresent)
//#endregion
//#region ———————————————————— Visuals
// ————— Oscillator Channel lines and fill.
// Determine colors.
color refLineColor = na
color oscLineColor = na
color oscChannelColor = na
switch
oscChannelBullStrong =>
refLineColor := refLineUpUpColorInput
oscLineColor := oscLineUpUpColorInput
oscChannelColor := oscChannelUpUpColorInput
oscChannelBearStrong =>
refLineColor := refLineDnDnColorInput
oscLineColor := oscLineDnDnColorInput
oscChannelColor := oscChannelDnDnColorInput
oscChannelBull =>
refLineColor := refLineUpColorInput
oscLineColor := oscLineUpColorInput
oscChannelColor := oscChannelUpColorInput
oscChannelBear =>
refLineColor := refLineDnColorInput
oscLineColor := oscLineDnColorInput
oscChannelColor := oscChannelDnColorInput
color oscColor = combinedWeight > 0 ? oscLineUpUpColorInput : combinedWeight < 0 ? oscLineDnDnColorInput : color.silver
// Styles for lines.
var refLineStyle = lineStyleFromUserInput(refLineStyleInput)
var oscLineStyle = lineStyleFromUserInput(oscLineStyleInput)
// Plot lines and fill them.
var bool plotOscLineValues = reflLineShowInput or osclLineShowInput or oscChannelShowInput
weightedPlot = plot(plotOscLineValues ? weightedRef : na, "Weighted Reference", osclLineShowInput ? oscLineColor : na, oscLineWidthInput, oscLineStyle)
refPlot = plot(plotOscLineValues and not na(combinedWeight) ? reference : na, "Reference", reflLineShowInput ? refLineColor : na, refLineWidthInput, refLineStyle)
fill(weightedPlot, refPlot, reference, weightedRef, oscChannelShowInput ? oscChannelColor : na, oscChannelShowInput ? color.new(oscChannelColor, 90) : na, "Fill")
// ————— Divergence channel lines and fill.
// Determine colors.
color divLinesColor = na
color divChannelColor = na
if divChannelBreached
switch
divChannelBullStrong =>
divLinesColor := divLinesUpUpColorInput
divChannelColor := divChannelUpUpColorInput
divChannelBearStrong =>
divLinesColor := divLinesDnDnColorInput
divChannelColor := divChannelDnDnColorInput
divChannelBull =>
divLinesColor := divLinesUpColorInput
divChannelColor := divChannelUpColorInput
divChannelBear =>
divLinesColor := divLinesDnColorInput
divChannelColor := divChannelDnColorInput
=>
divLinesColor := divLinesNtColorInput
divChannelColor := divChannelNtColorInput
else
switch
divChannelBiasInput and preBreachBiasBull =>
divLinesColor := divLinesUpColorInput
divChannelColor := divChannelUpColorInput
divChannelBiasInput and preBreachBiasBear =>
divLinesColor := divLinesDnColorInput
divChannelColor := divChannelDnColorInput
=>
divLinesColor := divLinesNtColorInput
divChannelColor := divChannelNtColorInput
// Plot the channel levels and fill.
var bool plotDivLineValues = divLinesShowInput or divChannelShowInput
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 divChannelShowInput ? divChannelColor : na, not newDivChannel and divChannelShowInput ? color.new(divChannelColor, 99) : na)
fill(divChannelMidPlot, divChannelLoPlot, divChannelMid, divChannelLo, not newDivChannel and divChannelShowInput ? color.new(divChannelColor, 99) : na, not newDivChannel and divChannelShowInput ? divChannelColor : na)
// ————— Display key values in indicator values and Data Window.
displayLocations = display.status_line + display.data_window
color osc1Color = weight1 > 0 ? color.lime : color.fuchsia
color osc2Color = weight2 > 0 ? color.lime : color.fuchsia
color oscXColor = weightX > 0 ? color.lime : color.fuchsia
color weightColor = weight > 0 ? color.lime : color.fuchsia
plot(refSource, "Reference source", display = displayLocations, color = refLineColor)
plot(weightedSource, "Weighted source", display = displayLocations, color = oscLineColor)
plot(na, "═════════════════", display = displayLocations)
plot(osc1, "Oscillator 1", display = displayLocations, color = osc1Color)
plot(osc2, "Oscillator 2", display = displayLocations, color = osc2Color)
plot(oscX, "Oscillator X", display = displayLocations, color = oscXColor)
plot(weight1, "Weight 1", display = displayLocations, color = osc1Color)
plot(weight2, "Weight 2", display = displayLocations, color = osc2Color)
plot(weightX, "Weight X", display = displayLocations, color = oscXColor)
plot(relVolumeWeight, "Relative Volume weight", display = displayLocations)
plot(combinedWeight, "Combined weight", display = displayLocations, color = weightColor)
plot(na, "═════════════════", 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 bar coloring
// Color
color barColor = if barColorsShowInput
switch colorBarModeInput
CB1 =>
switch
divergence => barsDivColorInput
CB2 =>
switch
divergence => barsDivColorInput
oscChannelBullStrong => barsUpUpColorInput
oscChannelBearStrong => barsDnDnColorInput
oscChannelBull => barsUpColorInput
oscChannelBear => barsDnColorInput
=> barsNtColorInput
CB3 =>
switch
divergence => barsDivColorInput
divChannelBullStrong => barsUpUpColorInput
divChannelBearStrong => barsDnDnColorInput
divChannelBull => barsUpColorInput
divChannelBear => barsDnColorInput
=> barsNtColorInput
CB4 =>
switch
divergence => barsDivColorInput
oscChannelBullStrong and divChannelBullStrong => barsUpUpColorInput
oscChannelBearStrong and divChannelBearStrong => barsDnDnColorInput
(oscChannelBull or oscChannelBullStrong) and (divChannelBull or divChannelBullStrong) => barsUpColorInput
(oscChannelBear or oscChannelBearStrong) and (divChannelBear or divChannelBearStrong) => barsDnColorInput
=> barsNtColorInput
=> na
else
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(charDivShowInput ? divergence : na, "Divergence character", charDivInput, charDivAboveInput ? location.abovebar : location.belowbar, charDivColorInput, size = size.tiny)
//#endregion
Show all 717 lines