// The MIT License (MIT) // © mihakralj //@version=6 indicator("MA Envelope (MAE)", "MAE", overlay=true) //@function Calculates MA Envelope bands using a fixed percentage //@param source Series to calculate moving average from //@param length Lookback period for MA calculation //@param percentage Distance of bands from MA as percentage //@param ma_type Type of moving average (0:SMA, 1:EMA, 2:WMA) //@returns tuple with [middle, upper, lower] band values //@optimized SMA uses circular buffer O(1), EMA uses warmup O(1), WMA is O(n) mae(series float source, simple int length, simple float percentage, simple int ma_type = 1) => if length <= 0 or percentage <= 0.0 runtime.error("Length and percentage must be greater than 0") float middle = na if ma_type == 0 var int head = 0 var int count = 0 var array buffer = array.new_float(length, na) var float sum = 0.0 float oldest = array.get(buffer, head) if not na(oldest) sum -= oldest count -= 1 float current = nz(source) sum += current count += 1 array.set(buffer, head, current) head := (head + 1) % length middle := sum / count else if ma_type == 1 var float alpha = 2.0 / (length + 1) var float sum = 0.0 var float weight = 0.0 if na(sum) sum := source weight := 1.0 sum := sum * (1.0 - alpha) + source * alpha weight := weight * (1.0 - alpha) + alpha middle := sum / weight else if ma_type == 2 float norm = 0.0 float sum = 0.0 for i = 0 to length - 1 float w = float((length - i) * length) norm += w sum += nz(source[i]) * w middle := sum / norm else runtime.error("MA type must be 0 (SMA), 1 (EMA), or 2 (WMA)") float dist = middle * percentage / 100.0 [middle, middle + dist, middle - dist] // ---------- Main loop ---------- // Inputs i_source = input.source(close, "Source") i_length = input.int(20, "Length", minval=1) i_percentage = input.float(1.0, "Percentage", minval=0.001) i_ma_type = input.int(1, "MA Type", minval=0, maxval=2, tooltip="0:SMA, 1:EMA, 2:WMA") // Calculation [middle, upper, lower] = mae(i_source, i_length, i_percentage, i_ma_type) // Plot plot(middle, "Middle", color=color.yellow, linewidth=2) p1 = plot(upper, "Upper", color=color.yellow, linewidth=2) p2 = plot(lower, "Lower", color=color.yellow, linewidth=2) fill(p1, p2, color=color.new(color.blue, 90), title="Band Fill")