Trailing stop
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © mickes

//@version=5
indicator("Trailing stop", overlay = true)

_entryDefaultTime = 2147483647000 // max unix time
_entryTime = input.time(_entryDefaultTime, "Time", group = "Entry")

_stopSource = input.string("ATR", "Source", ["ATR", "EMA", "SMA", "Source"], group = "Stop")
_atrFactor = input.float(3.5, "ATR factor", group = "ATR")
_emaLength = input.int(21, "Length", group = "EMA")
_smaLength = input.int(50, "Length", group = "SMA")
_source = input.source(close, "Source", tooltip = "An input source can be used to trail the stop. When the signal returns a value (not na), the stop will happen.", group = "Source")

type TrailStop
    float Entry
    float Limit = -1
    int HitBarIndex

var _trailingStop = TrailStop.new()

enter() =>
    if time == _entryTime
        _trailingStop.Entry := close
trailStop() =>
    float limit = na
    switch _stopSource
        "ATR" => limit := low - (ta.atr(14) * _atrFactor)
        "EMA" => limit := ta.ema(close, _emaLength)
        "SMA" => limit := ta.sma(close, _smaLength)
        "Source" => limit := not na(_source) ? 2147483647 : na

    if not na(_trailingStop.Entry)
      and limit > _trailingStop.Limit
        _trailingStop.Limit := limit
trailStopHit() =>
    if time != _entryTime
      and low < _trailingStop.Limit
      and na(_trailingStop.HitBarIndex)
        description = ""
        switch _stopSource
            "ATR" => description := str.format("ATR with factor {0}", _atrFactor)
            "EMA" => description := str.format("EMA with length {0}", _emaLength)
            "SMA" => description := str.format("SMA with length {0}", _smaLength)
            "Source" =>  description := "Source"
        message = str.format("Trailing stop hit ({0})", description)
        alert(message, alert.freq_once_per_bar)
        label.new(bar_index, high, str.format("Alert:\n{0}", message), color = color.new(color.red, 30))
        _trailingStop.HitBarIndex := bar_index
verifyStopSource() =>
    if barstate.isfirst
        switch _stopSource
            "Source" =>
                if _source == close
                    runtime.error("Input source is default close")

enter()

verifyStopSource()
trailStop()
trailStopHit()

middle = plot(hl2, "Middle", display = display.none)
stop = plot(na(_trailingStop.HitBarIndex) and not na(_trailingStop.Entry) and _trailingStop.Limit != -1 ? _trailingStop.Limit : na, "Trailing stop", color.red, linewidth = 2)

fill(middle, stop, color.new(color.red, 90))
