Files

87 lines
3.6 KiB
Plaintext

// Licensed under the Apache License, Version 2.0
// © mihakralj
//@version=6
// Indicator algorithm (C) 2013 John F. Ehlers
// "Cycle Analytics for Traders", Wiley, 2013
indicator("Ehlers MESA Stochastic (MSTOCH)", "MSTOCH", overlay=false)
//@function Calculates Ehlers MESA Stochastic
//@param src Series to calculate MESA Stochastic from
//@param stochLength Lookback period for the stochastic highest/lowest calculation
//@param hpLength Cutoff period for the Roofing Filter highpass stage (default 48)
//@param ssLength Cutoff period for the Roofing Filter super smoother stage (default 10)
//@returns MESA Stochastic value (0 to 1 range, smoothed)
//@description Applies Ehlers Roofing Filter (HP + Super Smoother) to remove spectral
// dilation, then computes stochastic on the filtered data, then smoothes the
// stochastic with another Super Smoother for a clean oscillator output.
// Reference: John F. Ehlers, "Cycle Analytics for Traders" (2013), Chapter 6
mstoch(series float src, simple int stochLength, simple int hpLength, simple int ssLength) =>
float SQRT2_PI = math.sqrt(2.0) * math.pi
// === Stage 1: Roofing Filter (HP + Super Smoother) ===
// --- Highpass Filter (2-pole Butterworth, removes trend below hpLength) ---
int safe_hp = math.max(hpLength, 1)
float hp_arg = SQRT2_PI / float(safe_hp)
float hp_exp_arg = math.exp(-hp_arg)
float hp_c2 = 2.0 * hp_exp_arg * math.cos(hp_arg)
float hp_c3 = -hp_exp_arg * hp_exp_arg
float hp_c1 = (1.0 + hp_c2 - hp_c3) / 4.0
var float hp = 0.0
float ssrc = nz(src, src[1])
float src1 = nz(src[1], ssrc)
float src2 = nz(src[2], src1)
float hp1 = nz(hp[1], 0.0)
float hp2 = nz(hp[2], 0.0)
hp := hp_c1 * (ssrc - 2.0 * src1 + src2) + hp_c2 * hp1 + hp_c3 * hp2
// --- Super Smoother (removes noise above ssLength) ---
int safe_ss = math.max(ssLength, 1)
float ss_arg = SQRT2_PI / float(safe_ss)
float ss_exp_arg = math.exp(-ss_arg)
float ss_c2 = 2.0 * ss_exp_arg * math.cos(ss_arg)
float ss_c3 = -ss_exp_arg * ss_exp_arg
float ss_c1 = 1.0 - ss_c2 - ss_c3
var float filt = 0.0
filt := ss_c1 * (hp + nz(hp[1], hp)) / 2.0 + ss_c2 * nz(filt[1], hp) + ss_c3 * nz(filt[2], nz(hp[1], hp))
// === Stage 2: Stochastic on roofing-filtered data ===
float highestC = filt
float lowestC = filt
for count = 1 to stochLength - 1
float val = nz(filt[count], filt)
if val > highestC
highestC := val
if val < lowestC
lowestC := val
float range_val = highestC - lowestC
float stoc = range_val > 0.0 ? (filt - lowestC) / range_val : 0.5
// === Stage 3: Smooth stochastic with Super Smoother ===
var float mstoc = 0.0
mstoc := ss_c1 * (stoc + nz(stoc[1], stoc)) / 2.0 + ss_c2 * nz(mstoc[1], stoc) + ss_c3 * nz(mstoc[2], nz(stoc[1], stoc))
// Clamp to valid range
float result = math.max(0.0, math.min(1.0, mstoc))
result
// ---------- Main loop ----------
// Inputs
i_stochLength = input.int(20, "Stochastic Length", minval=2)
i_hpLength = input.int(48, "HP Length", minval=1, tooltip="Highpass cutoff period — removes cycles longer than this")
i_ssLength = input.int(10, "SS Length", minval=1, tooltip="Super Smoother cutoff period — removes cycles shorter than this")
i_source = input.source(close, "Source")
// Calculation
result = mstoch(i_source, i_stochLength, i_hpLength, i_ssLength)
// Plot
plot(result, "MSTOCH", color=color.yellow, linewidth=2)
hline(0.8, "Overbought", color=color.gray, linestyle=hline.style_dotted)
hline(0.2, "Oversold", color=color.gray, linestyle=hline.style_dotted)
hline(0.5, "Midline", color=color.gray, linestyle=hline.style_dotted)