Dodge Trend [JacobMagleby]
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © JacobMagleby

//@version=5

// { <DECLARATION STATEMENT>

indicator(title = "Dodge Trend [JacobMagleby]", shorttitle = "Dodge Trend [JacobMagleby] - Version 1.0.0", overlay = true, timeframe = "")

// } <DECLARATION STATEMENT>



// { <CONSTANT_DECLARATIONS>

INDICATOR_GROUP = "Indicator Settings"
COLOR_GROUP     = "Color Settings"
COLOR_INLINE    = "Color Inline"

// } <CONSTANTS>



// { <INPUTS>

lengthInput     = input.int(
 title          = "Length",
  defval        = 20,
   minval       = 1,
     group      = INDICATOR_GROUP,
      tooltip   = "How Much Price Action To Take Into Consideration. Lower Values = More Reactive To Current Market Volatility.")

sizeInput       = input.float(
 title          = "Size",
  defval        = 3.0,
   minval       = 0.1,
     step       = 0.1,
      group     = INDICATOR_GROUP,
       tooltip  = "How Wide The Gap Is From The Current Price To The Line When Flipping.")

sourceInput     = input.source(
 title          = "Source",
  defval        = hl2,
   group        = INDICATOR_GROUP,
     tooltip    = "Source For The Dodge Line")

dodgeIntensity  = input.float(
 title          = "Dodge Intensity",
  defval        = 95.0,
   minval       = 0.0,
     maxval     = 99,
      step      = 0.1,   
       group    = INDICATOR_GROUP,
         tooltip= "How Intense/Aggressive The Dodge Effect Is. Anything Above 96 Will Get Very Intense. 95 Is Recommended Default. 0 Will Yield Almost No Dodge Effect.")

upColorInput    = input.color(
 title          = "",
  defval        = color.green,
   group        = COLOR_GROUP,
     inline     = COLOR_INLINE)

downColorInput  = input.color(
 title          = "",
  defval        = color.red,
   group        = COLOR_GROUP,
     inline     = COLOR_INLINE)

// } <INPUTS>



// { <USER DEFINED TYPES>

type dodgeTrend
    int length
    float size
    float source
    color upColor
    color downColor
    float trueRangeAverage
    float referenceTop
    float referenceBottom
    float lowestReferenceTop
    float highestReferenceBottom
    float activeTop
    float activeBottom
    float combinedValue
    float direction
    color currentColor

method updateDependencies(dodgeTrend self)=>
    trueRangeAverage      = ta.sma(ta.tr, lengthInput)
    self.referenceTop    := sourceInput + (trueRangeAverage * sizeInput)
    self.referenceBottom := sourceInput - (trueRangeAverage * sizeInput)
    if na(self.direction) and not na(self.referenceTop)
        self.direction              := 1
        self.activeTop              := na
        self.activeBottom           := self.referenceBottom
        self.combinedValue          := self.referenceBottom
        self.highestReferenceBottom := self.referenceBottom
        self.currentColor           := self.upColor
    self

method updateActives(dodgeTrend self)=>
    self.lowestReferenceTop     := ta.change(self.direction) ? self.referenceTop :
                                   self.lowestReferenceTop
    self.highestReferenceBottom := ta.change(self.direction) ? self.referenceBottom :
                                   self.highestReferenceBottom
    self.activeTop              := self.direction == -1 and self.direction[1] == 1 ? self.referenceTop :
                                   self.direction == 1 and self.direction[1] == -1 > 0 ? na :
                                   self.activeTop
    self.activeBottom           := self.direction == 1 and self.direction[1] == -1 ? self.referenceBottom :
                                   self.direction == -1 and self.direction[1] == 1 ? na :
                                   self.activeBottom
                                   
    rPrevB   = self.referenceBottom[1]
    rPrevBH  = self.highestReferenceBottom
    rPrevT   = self.referenceTop[1]
    rPrevTH  = self.lowestReferenceTop
    pullback = .60 * (100 - dodgeIntensity)
    recover  = .40 * (100 - dodgeIntensity)
    if self.direction == 1
        self.highestReferenceBottom   := self.referenceBottom > self.highestReferenceBottom ? self.referenceBottom :
                                         self.highestReferenceBottom
        self.activeBottom             := self.highestReferenceBottom > rPrevBH ? self.activeBottom + (self.highestReferenceBottom - rPrevBH) :
                                         self.referenceBottom < rPrevB ? self.activeBottom - ((rPrevB - self.referenceBottom) / pullback) :
                                         self.referenceBottom > rPrevB ? self.activeBottom + ((self.referenceBottom - rPrevB) / recover) :
                                         self.activeBottom
        self.combinedValue            := self.activeBottom
        self.direction                := close < self.activeBottom ? -1 :
                                         self.direction
        self.currentColor             := self.upColor

    else
        self.lowestReferenceTop := self.referenceTop < self.lowestReferenceTop ? self.referenceTop :
                                   self.lowestReferenceTop
        self.activeTop          := self.lowestReferenceTop < rPrevTH ? self.activeTop - (rPrevTH - self.lowestReferenceTop) :
                                   self.referenceTop > rPrevT ? self.activeTop + ((self.referenceTop - rPrevT) / pullback) :
                                   self.referenceTop < rPrevT ? self.activeTop - ((rPrevT - self.referenceTop) / recover) :
                                   self.activeTop
        self.combinedValue      := self.activeTop
        self.direction          := close > self.activeTop ? 1 :
                                   self.direction
        self.currentColor       := self.downColor
    self

method run(dodgeTrend self)=>
    self.updateDependencies()
    self.updateActives()
    self

// } <USER DEFINED TYPES>



// { <CALCULATIONS>

var dodgeTrend = dodgeTrend.new(
 length         = lengthInput,
  size          = sizeInput,
   source       = sourceInput,
     upColor    = upColorInput,
      downColor = downColorInput)

dodgeTrend.run()

// } <CALCULATIONS>



// { <VISUALS>

topBoundry = dodgeTrend.combinedValue + (ta.atr(1000) / 3)
botBoundry = dodgeTrend.combinedValue - (ta.atr(1000) / 3)
invisible  = color.new(color.white, 100)
topBoundryPlot = plot(topBoundry, color = invisible, editable = false)
botBoundryPlot = plot(botBoundry, color = invisible, editable = false)
centerBoundryPlot = plot(dodgeTrend.combinedValue, "Dodge Trend", dodgeTrend.currentColor, 2)
fill(topBoundryPlot, centerBoundryPlot, topBoundry, dodgeTrend.combinedValue, invisible, color.new(dodgeTrend.currentColor, 75))
fill(centerBoundryPlot, botBoundryPlot, dodgeTrend.combinedValue, botBoundry, color.new(dodgeTrend.currentColor, 75), invisible)

// } <VISUALS>



// { <ALERTS>

alertcondition(
 condition = dodgeTrend.direction == 1 and dodgeTrend.direction[1] == -1,
  title = "Dodge Trend Flips Up")

alertcondition(
 condition = dodgeTrend.direction == -1 and dodgeTrend.direction[1] == 1,
  title = "Dodge Trend Flips Down")

// } <ALERTS>



