RedK DIY ZLMA: Customizable Zero-Lag MA (Educational / Utility)
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © RedKTrader

//@version=5
// customize your own ZLMA using 3-pass MAs and get the 1-line formula to use in other scripts
// Some common MA's use this technique to decreae the lagging effect (for example, Hull MA)
// Though HMA uses 2 MA's of different lengths of the same source data series
// Also TEMA (triple EMA) works slightly differently
// So you can't get this ZLMA to become exaclt equivalent to an HMA or a TEMA

// To-do List for next updates:
// add offest
// add 2 colors
// add alerts

// can't use timeframe = "" here to support MTF due to the existance of a table - unless we commented the table section below
indicator("Customizable ZLMA v1.0", shorttitle="DIY_ZLMA v1.0", overlay = true) //, timeframe = "", timeframe_gaps = false)

//======================================================================================================================
// Prep Section
//======================================================================================================================

// ***********************************************************************************************************
// This function (method) calcualtes a user-defined moving average type
method GetAverage(float _data, string MAOption, int _len) =>
    float value = 0.0
    SMA = ta.sma(_data, _len) 
    EMA = ta.ema(_data, _len) 
    HMA = ta.hma(_data, _len) 
    RMA = ta.rma(_data, _len)
    WMA = ta.wma(_data, _len) 
       
    value := switch MAOption
        'SMA' => SMA 
        'EMA' => EMA
        'HMA' => HMA
        'RMA' => RMA
        => WMA
// ***********************************************************************************************************

// This is a generic MovingAverage UDT - used as a template for the 3 MA's the user will customize 
// and we will use later in the My_ZLMA calculation
type MovingAverage
    float Source
    string Type
    float Value

//======================================================================================================================
// Input Section
//======================================================================================================================

// first moving average is the Base MA 
BaseMA        = MovingAverage.new()
grp_1         = "1. Base MA"
line_1        = "Base MA"

BaseMA.Source   := input.source(close, "Source",      inline = line_1, group = grp_1)
var int Length1 = input.int(10, 'Length', minval = 1, inline = line_1, group = grp_1)
BaseMA.Type     := input.string( 'WMA', title = 'MA type', 
  options = ['WMA', 'EMA', 'SMA', 'HMA', 'RMA'],      inline = line_1, group = grp_1)

// second moving average calulcates an MA of the BaseMA, the displacement between the 2 MAs (delta) is then used to create a "de-lag" effect 
// we will call this the "de-lagging MA"
// we can additionally manipulate delta with a magnification factor (1 = no magnification) to increase/decrease the de-lagging effect  
// too much de-lagging (usually > 2.0) will cause an overshoot of the MA - so we'll restrict it to 2.0 - acts more like "fine-tuning"
// magnification of 0 means that we're not using any de-lagging .. so it's like completely skipping this MA and only using smoothing
MA2           = MovingAverage.new()
grp_2         = "2. De-lagging"
line_2        = "De-lagging MA"

var int Length2 =  input.int(5, 'Length', minval = 1, inline = line_2, group = grp_2)
MA2.Type        := input.string( 'SMA', title = 'MA type', 
  options = ['WMA', 'EMA', 'SMA', 'HMA', 'RMA'],      inline = line_2, group = grp_2)
delta_mag       = input.float(1.40, "Magnification", minval = 0.0, maxval = 2.0, step = 0.05, group = grp_2)

// the third MA is used for smoothing the final ZLMA - it's simply an MA of the resulting MA from prior phase
MA3           = MovingAverage.new()
grp_3         = "3. Smoothing" 
line_3        = "Smoothing"

UseSmoothing    = input.bool(true, "Use Smoothing ?",       inline = line_3, group = grp_3)
var int Length3 =  input.int(3, 'Length', minval = 1,       inline = line_3, group = grp_3)
MA3.Type        := input.string( 'WMA', title = 'MA type', 
  options = ['WMA', 'EMA', 'SMA', 'HMA', 'RMA'],            inline = line_3, group = grp_3)

grp_4         = "4. Other Settings"
line_4        = "ZMLA Formula"

DualColor       = input.bool(true, "Dual Color ZLMA ?", group = grp_4)
ShowMAString    = input.bool(false, 'Show My_ZLMA Formula ?',    inline = line_4, group = grp_4)
c_ZLMA_String   = input.color(color.yellow, "Color",      inline = line_4, group = grp_4)
var int offset  = input.int(0, "Offset",  group = grp_4)

//======================================================================================================================
// Calculations
//======================================================================================================================

// we use the GetAverage() method to calculate 1st (base) MA, using the source selected by user
BaseMA.Value  := BaseMA.Source.GetAverage(BaseMA.Type, Length1)

//  we use the GetAverage() method again to calculate 2nd MA, using the value of the BaseMA as a source
MA2.Value     := BaseMA.Value.GetAverage(MA2.Type, Length2)

// Calculate the intermediate ZLMA by using the "magnified" displacement to "de-lag" the BaseMA
ZLMA_2        = BaseMA.Value + delta_mag * (BaseMA.Value - MA2.Value)

//  we use the GetAverage() method to calculate the final MA using the value of the intermediate ZLMA
MyZLMA        = UseSmoothing ? 
  ZLMA_2.GetAverage(MA3.Type, Length3)
  : ZLMA_2

//======================================================================================================================
// Colors & Plots
//======================================================================================================================
up          = ta.change(MyZLMA) > 0 
c_ZLMA      = DualColor ? up ? #2196f3 : #f57f17 : color.blue

plot(MyZLMA, "My ZLMA", color = c_ZLMA, linewidth =3, offset = offset )

//======================================================================================================================
// Compile and show the 1-line MyZLMA Formula
//======================================================================================================================

//for example: this is the 1-line My_ZLMA formula, assuming all MA's are WMA's
//string = ta.wma(ta.wma(src, length_1) + delta * (ta.wma(src, length_1) - ta.wma(ta.wma(src, length_1), length_2)), length_3) 
//plot(string, 'string', color.blue)


//************************************************************************
// here we compile the 1-line MyZLMA formula based on the customizations selected by user
// first MA pass, the BaseMA
_MA_str1        = 'ta.' + BaseMA.Type + '(src, ' + str.tostring(Length1) + ')'

// adding the 2nd pass which includes the delta magnification part
_MA_str2        = _MA_str1 + ' + ' + str.tostring(delta_mag) + ' * (' + _MA_str1 + ' - ' + 
  'ta.' + MA2.Type + '(' + _MA_str1 + ', ' + str.tostring(Length2) + '))'

// if no smoothing is applied, the 3rd MA pass is excluded from the MyZLMA formula line
_MA_str3        = UseSmoothing ? 
  'ta.' + MA3.Type + '(' + _MA_str2 + ', ' + str.tostring(Length3) + ')'
  : _MA_str2

// convert all functions to lower case and compile final 1-line format
_MyZLMA_string  = 'My_ZLMA = ' + str.lower(_MA_str3) 

//************************************************************************
// show the 1-line MyZLMA formula using a quick table cell. 
// Comment this part if MTF support is needed - then uncomment the timeframe = "" in script declaration 
var table ShowMAFormula = table.new(position.top_right, 1, 1)

if barstate.islast and ShowMAString
    table.cell(ShowMAFormula, 0, 0, _MyZLMA_string, text_color =c_ZLMA_String, 
      text_size = size.large, text_font_family = font.family_monospace)  