Files

71 lines
2.6 KiB
Plaintext

// Licensed under the Apache License, Version 2.0
// © 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<float> 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")