Centred Moving Average
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/

// Edited by m_b_round
// With thanks to ValiantHero for the original idea, and also the explanation that appears below
// which is much better than I could have managed!

//
// ██╗░░░██╗░█████╗░██╗░░░░░██╗░█████╗░███╗░░██╗████████╗ ██╗░░██╗███████╗██████╗░░█████╗░
// ██║░░░██║██╔══██╗██║░░░░░██║██╔══██╗████╗░██║╚══██╔══╝ ██║░░██║██╔════╝██╔══██╗██╔══██╗
// ╚██╗░██╔╝███████║██║░░░░░██║███████║██╔██╗██║░░░██║░░░░███████║█████╗░░██████╔╝██║░░██║
// ░╚████╔╝░██╔══██║██║░░░░░██║██╔══██║██║╚████║░░░██║░░░░██╔══██║██╔══╝░░██╔══██╗██║░░██║
// ░░╚██╔╝░░██║░░██║███████╗██║██║░░██║██║░╚███║░░░██║░░░░██║░░██║███████╗██║░░██║╚█████╔╝
// ░░░╚═╝░░░╚═╝░░╚═╝╚══════╝╚═╝╚═╝░░╚═╝╚═╝░░╚══╝░░░╚═╝░░░░╚═╝░░╚═╝╚══════╝╚═╝░░╚═╝░╚════╝░

// QUICK REFERENCE

// Centered moving averages tries to resolve the problem that simple moving average are still not able to handle significant trends when forecasting
// When computing a running moving average in a centered way, placing the average in the middle time period makes sense
// If we average an even number of terms, we need to smooth the smoothed values
// Try to describe it with an example:
// The following table shows the results using a centered moving average of 4.
// 
// nterim   Steps
// Period	Value	SMA	    Centered
// 1	    9	 	 
// 1.5	 	 	 
// 2	    8	 	 
// 2.5	 	        9.5	 
// 3	    9	 	        9.5
// 3.5	 	        9.5	 
// 4	    12	 	        10.0
// 4.5	 	        10.5	 
// 5	    9	 	        10.750
// 5.5	 	        11.0	 
// 6	    12	 	 
// 6.5	 	 	 
// 7	    11
//
// This is the final table:
// Period	Value	Centered MA
// 1	    9	 
// 2	    8	 
// 3	    9        9.5
// 4	    12	    10.0
// 5	    9   	10.75
// 6	    12	 
// 7	    11
// 
// With this script we are able to process and display the centered moving average as described above.
// In addition to this, however, the script is also able to estimate the potential projection of future data based on the available data
// by replicating where necessary the data of the last bar until the number of data necessary for the calculation of the required centered moving average is reached.
//
// If for example I have 20 daily closings and I look for the moving average centered at 10, I receive the first data on the fifth day
// and the last data on the fourteenth day, so I have 5 days left uncovered, to remedy this I have to give the last value to the uncovered data 
// the closing price of the last day.
// The deviations work like the bollinger bands but must refer to the centered moving average.

//@version=5

// @description This indicator is a centered moving average!
indicator("Centred Moving Average",max_lines_count = 500, overlay=true) 

source = input.source(defval = high,title = 'source', group="Moving Average")
length = input.int(defval = 10,title = 'length', group="Moving Average")
lineSections = input.int(defval=50, title="Line sections", minval=1, maxval=150, tooltip="Steps to split the predicted line into - less steps = faster execution but more jagged line.", group="Moving Average")
dashLine = input.bool(false, "Dashed Line", tooltip="A dashed line will only draw every other section on the chart - again speed / processing savings over and above the white / red combo.", group="Moving Average")
future=input.bool(defval = true,title = 'show future', group="Moving Average")
double=input.bool(defval = false,title = 'use double moving average', group="Moving Average")
maColour = input.color(color.lime, "Line Colour", group="Moving Average")

plotStdDev = input.bool(true, "Plot Standard Deviation Bands", group="Standard Deviation")
stdDev = input.float(2.0, minval=0.001, maxval=50, title="Band multiplier", group="Standard Deviation")
stdDevColour = input.color(color.orange, "Band Colour", group="Standard Deviation")


offset = math.ceil(length/2)
average = ta.sma(source,length)
doubleAverage = ta.sma(ta.sma(source,length), length)

predictedValues = array.new_float()
predictedDSMAValues = array.new_float()

lastVal = source[0]
total = math.sum(source, length)
dsmaTotal = math.sum(average, length)
totalOffset = future ? offset * 2 : offset


if barstate.islast
    for i = 1 to totalOffset
        total := total - source[math.max(length - i, 0)] + lastVal
        avg = total / length
        array.push(predictedValues, avg)
        if double
            dsmaTotal := dsmaTotal - average[math.max(length - i, 0)] + avg
            dsmaAvg = dsmaTotal / length
            array.push(predictedDSMAValues, dsmaAvg)

plotall(data,dataGui,int xstart,float average,float dev)=>   
    ly=average+dev
    step = 1
    if array.size(data) > lineSections
        step := math.ceil(array.size(data) / lineSections)
    colorBool = true
    lineColour = dev == 0 ? maColour : stdDevColour
    for i=0 to array.size(data)-1 by step
        y=array.get(data,i)+dev
        if xstart + i + step < bar_index + 500
            if (dashLine and not colorBool) or not dashLine
                array.push(dataGui,line.new(xstart+i,ly,xstart+i+step,y,width = 1,color=colorBool ? color.white : lineColour))
        ly:=y
        colorBool := not colorBool

redesign(data,dataGui,int xstart,int offset,float average,float dev=0)=>
    for g in dataGui
        line.delete(g)
    plotall(data,dataGui,xstart,average,0)
    if (dev>0) and plotStdDev
        plotall(data,dataGui,xstart,average,dev)
        plotall(data,dataGui,xstart,average,-1*dev)

var data=array.new_float(0)
var dataGui=array.new_line(0)

length_BC_Tminus1_MAX = length
src_BC_Tminus1_MAX = double ? average : source

devMax = stdDev * ta.stdev(src_BC_Tminus1_MAX, length_BC_Tminus1_MAX)
upper = double ? doubleAverage + devMax : average + devMax
lower = double ? doubleAverage - devMax : average - devMax
plot(plotStdDev ? upper : na, "Upper", color=stdDevColour, offset = -offset)
plot(plotStdDev ? lower : na, "Lower", color=stdDevColour, offset = -offset)

// Plot SMA on the chart
plot(series=double ? doubleAverage : average ,offset = -offset, color=maColour, linewidth=2)

if barstate.islast
    redesign(double ? predictedDSMAValues : predictedValues,dataGui,bar_index-offset,offset,double ? doubleAverage : average, devMax)