mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-27 17:27:43 +00:00
86 lines
3.8 KiB
Plaintext
86 lines
3.8 KiB
Plaintext
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0
|
|
// https://mozilla.org/MPL/2.0/
|
|
// © QuanTAlib
|
|
|
|
//@version=6
|
|
indicator("SAM: Smoothed Adaptive Momentum", shorttitle="SAM", overlay=false)
|
|
|
|
// @function Calculates the Ehlers Smoothed Adaptive Momentum.
|
|
// Measures the Dominant Cycle period via Homodyne Discriminator,
|
|
// then computes one-cycle momentum (close - close[DC]) and applies
|
|
// a 2-pole Super Smoother filter for final output.
|
|
// Source: John F. Ehlers, "Cybernetic Analysis for Stocks and Futures" (2004),
|
|
// Chapter 12: "Adapting to the Trend," p.166.
|
|
// @param src Series to analyze.
|
|
// @param alpha Smoothing factor for cycle measurement. Default 0.07.
|
|
// @param cutoff Super Smoother cutoff period. Default 8.
|
|
// @returns The smoothed adaptive momentum oscillator value.
|
|
sam(series float src, simple float alpha, simple int cutoff) =>
|
|
// ── 4-bar FIR smoother ──
|
|
float smooth = (src + 2.0 * nz(src[1]) + 2.0 * nz(src[2]) + nz(src[3])) / 6.0
|
|
|
|
// ── Hilbert Transform via Ehlers' detrender/quadrature ──
|
|
float pi = math.pi
|
|
float detrend = 0.0
|
|
detrend := (0.0962 * smooth + 0.5769 * nz(smooth[2]) - 0.5769 * nz(smooth[4]) - 0.0962 * nz(smooth[6])) * (0.075 * nz(detrend[1]) + 0.54)
|
|
|
|
// ── In-phase and Quadrature components ──
|
|
float q1 = 0.0
|
|
q1 := (0.0962 * detrend + 0.5769 * nz(detrend[2]) - 0.5769 * nz(detrend[4]) - 0.0962 * nz(detrend[6])) * (0.075 * nz(q1[1]) + 0.54)
|
|
float i1 = nz(detrend[3])
|
|
|
|
// ── Advance phase by 90 degrees ──
|
|
float ji = (0.0962 * i1 + 0.5769 * nz(i1[2]) - 0.5769 * nz(i1[4]) - 0.0962 * nz(i1[6])) * (0.075 * nz(ji[1]) + 0.54)
|
|
float jq = (0.0962 * q1 + 0.5769 * nz(q1[2]) - 0.5769 * nz(q1[4]) - 0.0962 * nz(q1[6])) * (0.075 * nz(jq[1]) + 0.54)
|
|
|
|
// ── Phasor addition for Homodyne Discriminator ──
|
|
float i2 = 0.0
|
|
float q2 = 0.0
|
|
i2 := i1 - jq
|
|
q2 := q1 + ji
|
|
i2 := alpha * i2 + (1.0 - alpha) * nz(i2[1])
|
|
q2 := alpha * q2 + (1.0 - alpha) * nz(q2[1])
|
|
|
|
// ── Homodyne Discriminator for period ──
|
|
float re = 0.0
|
|
float im = 0.0
|
|
re := i2 * nz(i2[1]) + q2 * nz(q2[1])
|
|
im := i2 * nz(q2[1]) - q2 * nz(i2[1])
|
|
re := alpha * re + (1.0 - alpha) * nz(re[1])
|
|
im := alpha * im + (1.0 - alpha) * nz(im[1])
|
|
|
|
float period = 0.0
|
|
if im != 0.0 and re != 0.0
|
|
period := 2.0 * pi / math.atan(im / re)
|
|
period := math.max(math.min(period, 50.0), 6.0)
|
|
|
|
float instPeriod = 0.0
|
|
instPeriod := 0.33 * period + 0.67 * nz(instPeriod[1])
|
|
float dcPeriod = 0.0
|
|
dcPeriod := 0.15 * instPeriod + 0.85 * nz(dcPeriod[1])
|
|
|
|
// ── Adaptive Momentum: one dominant-cycle lookback ──
|
|
int dcLen = math.max(int(dcPeriod), 1)
|
|
float momentum = src - nz(src[dcLen])
|
|
|
|
// ── 2-pole Super Smoother on momentum ──
|
|
float a1 = math.exp(-math.sqrt(2.0) * pi / cutoff)
|
|
float b1 = 2.0 * a1 * math.cos(math.sqrt(2.0) * pi / cutoff)
|
|
float c2 = b1
|
|
float c3 = -a1 * a1
|
|
float c1 = 1.0 - c2 - c3
|
|
float filt = 0.0
|
|
filt := c1 * (momentum + nz(momentum[1])) / 2.0 + c2 * nz(filt[1]) + c3 * nz(filt[2])
|
|
filt
|
|
|
|
// ── Inputs ──────────────────────────────────────────────
|
|
a = input.float(0.07, "Alpha", minval=0.01, maxval=1.0, step=0.01)
|
|
c = input.int(8, "Cutoff", minval=2)
|
|
|
|
// ── Calculation ─────────────────────────────────────────
|
|
result = sam(close, a, c)
|
|
|
|
// ── Plot ────────────────────────────────────────────────
|
|
plot(result, "SAM", color=color.yellow, linewidth=2)
|
|
hline(0, "Zero", color=color.gray, linestyle=hline.style_dotted)
|