Ehlers Indicators

All functions · nseries package

AdaptiveSuperSmoother

func (s Series) AdaptiveSuperSmoother(n int, rmsLength ...int) Series

AdaptiveSuperSmoother implements the adaptive SuperSmoother according to the translation of the TradeStation algorithm provided by John Ehlers (in TASC September 2026). Rather than modulating the alpha of an EMA, as KAMA, VIDYA and their relatives do, the critical period of a second-order SuperSmoother is tuned on every bar: the one-bar rate of change of the smoothed output is divided by its root mean square over the trailing “rmsLength” bars (scaling it in standard deviations), clamped at two, and the period is then set to n*(1 - roc/2)*(1 - roc/2) with a floor of two bars. The period computed at the end of one bar is the one used to smooth the next, so the tuned period is never longer than n and the filter speeds up whenever the smoothed output is moving quickly.

Optionally, the length of the RMS window can be defined. If not defined, or not positive, it will default to 81, the value used by Ehlers in the article, who remarks that the length over which the RMS is taken is not particularly critical. The RMS is zero until its window is full, so the period is held at n and the first max(3, rmsLength) bars are identical to SuperSmoother(n). A window longer than the series simply never adapts.

Two deliberate deviations from the published code: the filter constants are math.Sqrt(2)*math.Pi rather than Ehlers’ 1.414 and 3.14159, so that this filter agrees bit for bit with the package’s SuperSmoother; and the rate of change on the first bar is zero rather than the whole first price (an artefact of the EasyLanguage variable initialiser), matching the package’s Delta convention.

The SuperSmoother step is written in the exact form SuperSmoother uses, with its overflow-safe mean and its fallback to the published form, so a constant series gives its level exactly; releases before this change used the published form. Because the tuned period feeds the smoothed output back into the filter, a rounding difference can grow over a long series: in the published form, changing one price of a 2,000-bar random walk by one part in 10^12 moved later outputs of AdaptiveSuperSmoother(40, 81) by about 10%. So the change of form moves outputs by rounding at first, and on long series by more, as a change of platform (arm64 against amd64) can.

A value of n less than one returns an aligned zero-filled series rather than panicking. A value of n of one is floored to a period of two bars on every bar, so its output equals SuperSmoother(2). The first three bars are copied from the input, as in SuperSmoother.

Unlike the older indicators in this package, this one is not verified against a column exported from TradeStation. Its reference values come from an independent translation of the published EasyLanguage into Python, reproduced by a second independent translation, and are anchored to the existing SuperSmoother: until the RMS window fills, the adaptive filter is arithmetically the fixed-period one, and the tests assert that the two agree bit for bit across that region. That identity is what ties these values to the TradeStation-verified ones already in the package, without requiring a further export.

AdaptiveSuperSmootherOsc

func (s Series) AdaptiveSuperSmootherOsc(n int, rmsLength ...int) Series

AdaptiveSuperSmootherOsc implements the oscillator presentation of the adaptive SuperSmoother according to the translation of the TradeStation algorithm provided by John Ehlers (in TASC September 2026). It is the adaptive SuperSmoother minus the fixed-period SuperSmoother(n), the ordinary fast-minus-slow convention, so that a value above the zero reference implies that a long position is preferred and a value below it implies that a short position is preferred. Ehlers notes that the cyclic turning points of the oscillator almost perfectly align with the cyclic price turning points.

Optionally, the length of the RMS window can be defined, as for AdaptiveSuperSmoother. If not defined, or not positive, it will default to 81. The oscillator is exactly zero until the RMS window is full, and on every bar it is exactly AdaptiveSuperSmoother(n, rmsLength) minus SuperSmoother(n).

A value of n less than one returns an aligned zero-filled series rather than panicking.

AdaptiveSuperSmootherPeriod

func (s Series) AdaptiveSuperSmootherPeriod(n int, rmsLength ...int) Series

AdaptiveSuperSmootherPeriod returns the tuned critical period of the adaptive SuperSmoother according to the translation of the TradeStation algorithm provided by John Ehlers (in TASC September 2026). The value at bar i is the period computed at the end of bar i, i.e. the one used to smooth bar i+1. It is always finite and lies within [2, max(2, n)], even when the input contains NaN or infinities: it is exactly n while the RMS window is still filling, and it falls to as little as two bars when the rate of change of the smoothed output reaches two standard deviations.

Note that quiet data does not reliably restore the period to n. The scaled rate of change is only updated on bars where the RMS is positive, so once the RMS window holds nothing but zero changes it keeps its last value, as the published code does; and while the smoothed output is still settling on a flat stretch, its tiny changes are measured against an equally tiny RMS, so the period can still take any value in its range. On a series that is constant from the start the smoothed output is exactly the level, every change is 0, and the period is exactly max(n, 2) on every bar; releases before this change smoothed in the published form, whose rounding residue was amplified into a period anywhere within its range.

Optionally, the length of the RMS window can be defined, as for AdaptiveSuperSmoother. If not defined, or not positive, it will default to 81.

A value of n less than one returns an aligned zero-filled series, not a series of n, rather than panicking.

Angle

func (s Series) Angle() Series

Angle implements the angle function according to the translation of the TradeStation algorithm provided by John Ehlers.

The warm-up reads 0, not NaN: bars 0 and 1 are 0, as the peak trackers start at 0.

AutoTune

func (s Series) AutoTune(window int, bandwidth ...float64) Series

AutoTune implements the AutoTune filter according to the translation of the TradeStation algorithm provided by John Ehlers (in TASC May 2026). The data is detrended with a two-pole highpass filter whose critical period is “window” bars, and a rolling autocorrelation of the filtered data (over “window” bars, for lags of 1 to “window” bars) measures the dominant cycle, which is twice the lag of the most negative autocorrelation. The dominant cycle then tunes a bandpass filter on a bar-by-bar basis. The result is a smooth, nominally zero-mean waveform whose peaks and valleys identify the peaks and valleys in the price data for mean-reversion strategies.

Optionally, the bandwidth of the bandpass filter can be defined (as a fraction of its centre frequency). If not defined, it will default to 0.25, the value used by Ehlers in the article.

A window for which the required history is unavailable, including an extreme window value that would otherwise overflow internal bounds checks, returns an aligned zero-filled series rather than panicking.

bandwidth is a fraction of the centre frequency, defaulting to 0.25 when it is omitted. A given bandwidth must be finite and strictly between 0 and 0.5, below a quarter of the shortest dominant cycle the measurement reports (2 bars): from 0.5 up the bandpass is unstable on any bar whose dominant cycle is below four times the bandwidth. 0, a negative value, NaN, an infinity or 0.5 and above (including a percentage such as 25) is invalid and gives all zeros. The warm-up reads 0, not NaN: bars 0 to max(2, 2*window-1)-1 are 0.

AutoTuneDC

func (s Series) AutoTuneDC(window int) Series

AutoTuneDC returns the dominant cycle period measured by the rolling autocorrelation used in the AutoTune filter (see AutoTune). The dominant cycle is twice the lag at which the autocorrelation of the highpass-filtered data is most negative, and is limited to change by no more than 2 bars from one bar to the next.

The warm-up reads 0, not NaN: bars 0 to 2*window-2 are 0, and the dominant cycle then ramps up from 0 by at most 2 bars per bar, so the first values after the warm-up are not a measured cycle.

The ramp is the source’s own (Ehlers seeds the cycle at 0 and limits each step to 2 bars): it lasts until the cycle reaches the measured value, at most window bars after bar 2*window-1. AutoTune’s band-pass carries its own memory for longer (about 120 to 160 bars to within 1% at window 20).

AutoTuneMinCorr

func (s Series) AutoTuneMinCorr(window int) Series

AutoTuneMinCorr returns the minimum value (across lags of 1 to window bars) of the rolling autocorrelation used in the AutoTune filter (see AutoTune). Values near -1 indicate a strong dominant cycle; Ehlers’ pro forma strategy only takes trades when this value is below a threshold (e.g. -0.22).

The warm-up reads 0, not NaN: bars 0 to 2*window-2 are 0, which is above the -0.22 gate, so no trade fires on them.

BandPass

func (s Series) BandPass(n int, bandwidth float64) Series

BandPass implements the band-pass filter according to the translation of the TradeStation algorithm provided by John Ehlers.

bandwidth is a fraction of the centre frequency 1/n, so 0.3 is 30%, not a percentage. The warm-up reads 0, not NaN: bars 0 and 1 are 0 and the recursion starts from zero history; the output is zero-mean.

ContinuationIndex

func (s Series) ContinuationIndex(n int, gamma float64, order int) Series

ContinuationIndex implements Ehlers’ continuation index from TASC Sep 2025. A good starting point for the length (n) is to use the desired number of bars to be in a trade.

gamma is the Laguerre damping factor between 0 and 1 and is not validated. The warm-up reads 0, not NaN: bars 0 to 2n-3 are 0 and the first valid index is 2n-2, where the Laguerre filter (which starts at bar n-1) has run for a full n-bar averaging window. On a constant series the index is exactly 0 from there. Releases before this change gave about plus or minus 0.964 (tanh(2), with the sign of the price) before bar n-1 and then stayed near plus or minus 0.95 while zero-seeded Laguerre stages caught up (about 300 bars on a flat series at n = 40), which reads as strong continuation.

DSMA

func (s Series) DSMA(n int) Series

DSMA calculates the deviation-scaled moving average according to the translation of the TradeStation algorithm provided by John Ehlers. TODO – Revisit this to deal with de-gapping (which we have left out).

The warm-up reads 0, not NaN: bars 0 to 2n-2 are 0 and the first valid index is 2n-1, the first bar whose n-bar RMS window holds only computed filter values; when 2n-1 is at or past the end, every bar is 0. The average starts there from the price of bar 2n-2, so a constant series gives its level exactly from bar 2n-1. Releases before this change started the average from 0 at bar n, while the RMS window still held n-1 zeros, so the first value was about 5/sqrt(n) times the price (2.24 times at n = 5) and settled over several multiples of n bars, and a constant input gave 0 on every bar. The choice of DSMA edition is still open (RECTIFICATION-PLAN C02).

n must be at least 3. For n of 1 or 2 the band-pass’s g1 = cos(1.4*pi/n) is negative, so its recursion is unstable and the output grows without bound (to about 6e62 on the 2023 AAPL daily bars at n = 2); a smaller n is invalid and gives all zeros.

alpha1 is capped at 1. The RMS window holds the current filter value, so |scaledFilt| is at most sqrt(n) and alpha1 at most 5/sqrt(n): above 1 for n below 25, where the published recurrence places the average beyond the price (a step after a flat stretch overshoots by 12% of the step at n = 20 and by 58% at n = 10), and above 2 for n below 7, where the recursion can diverge (a repeating pattern of eight prices took DSMA(4) to -5.8e14 in 800 bars). With the cap the average always lies between its previous value (at the first valid bar, the price seed) and the price, as an EMA does and as Ehlers describes alpha (“a number that can vary between zero and 1”); MAMA bounds its alpha the same way. For n of 26 or more no bar can reach the cap and the output is unchanged, unless the squares of the filter values underflow (filter values below about 1e-154 in magnitude), which can make the RMS too small; at n = 25 the cap is reached only by an exact impulse and changes nothing beyond rounding. Releases before this change did not cap alpha1.

DSSS

func (s Series) DSSS(n int) Series

DSSS calculates the deviation-scaled super smoother according to the translation of the TradeStation algorithm provided by John Ehlers. TODO – Revisit this to deal with de-gapping (which we have left out).

The warm-up reads 0, not NaN: bars 0 to max(n-1, 49)-1 are 0, the RMS length being fixed at 50. The two-pole recursion starts from the prices of the two bars before its first output, the filter’s steady state (as SuperSmoother is seeded), so a constant series gives its level exactly from bar max(n-1, 49). Releases before this change started it from 0, so a ramp that could overshoot followed, and a constant input gave 0 on every bar.

n must be at least 2. For n = 1 the Hann window has floor(1/1.4) = 0 taps, so the filter is 0 on every bar and the smoother would never adapt; a smaller n is invalid and gives all zeros.

When the deviation-scaled filter is exactly 0 the smoother takes its nominal period n, the period it uses at a deviation of one standard deviation. The published coefficients have a double pole at 1 there (a1 = 1, so c1 = 0, c2 = 2 and c3 = -1) and the recursion r[i] = 2r[i-1] - r[i-2] carries its last slope for ever and ignores the input. The filter is exactly 0 on a stretch that has been flat for n + floor(n/1.4) bars, wherever the n-bar momentum is 0 across the whole Hann window (prices alternating 101 and 99 with n = 2) and, for n of 50 or more, on the first computed bar, whose Hann window holds only the warm-up’s zero momenta. On such a bar the output is r[i-1] + c1*(m - r[i-1]) - c3*(r[i-1] - r[i-2]) with SuperSmoother(n)’s coefficients and m the mean of the current and previous prices, a form that is exact when the three values are equal, so a constant series still gives its level exactly; every other bar is computed as before. Releases before this change kept the last slope on those bars: a ramp from 100 to 119.75 over 80 bars followed by 200 bars at 120 ended at 140.11 for n = 2 and 119.32 for n = 20, still moving, and 100 prices alternating 101 and 99 took DSSS(2) from 103 at bar 49 to 203 at bar 99.

The mean of the two prices is (s[i]+s[i-1])/2, as published, unless that sum overflows, when it is formed as s[i]/2 + s[i-1]/2; so a constant series at the largest finite float64 gives that value rather than NaN. A series that is not constant and lies within a factor of about 2 of the largest float64 still overflows the recursion’s history terms, as SuperSmoother does.

EMAT

func (s Series) EMAT(n int) Series

EMAT calculates the truncated EMA according to the translation of the TradeStation algorithm provided by John Ehlers.

The warm-up reads 0, not NaN: bars 0 to n-2 are 0, a price level at 0.

FRAMA

func (s Series) FRAMA(h, l, c Series, n int) Series

FRAMA calculates the fractal EMA according to the translation of the TradeStation algorithm provided by John Ehlers. The October 2005 TradeStation dimension persists when a range term is not positive. This library uses close prices and seeds pre-window values from c. Source: https://traders.com/documentation/feedbk_docs/2005/10/TradersTips/TradersTips.html

n must be even, as Ehlers’s source requires; an odd n returns a zero-filled series.

Hann

func (s Series) Hann(n int) Series

Hann calculates the Hann windowed low-pass FIR filter according to the translation of the TradeStation algorithm provided by John Ehlers. A value of n less than one returns a zero-filled series.

The warm-up reads 0, not NaN: bars 0 to n-2 are 0, a price level at 0.

HighPass

func (s Series) HighPass(n int) Series

HighPass implements the two-pole high-pass filter according to the translation of the TradeStation (EasyLanguage) function provided by John Ehlers in “The Ultimate Smoother”, TASC April 2024.

The first three outputs (indices 0 to 2) are zero, and the recursion begins at index 3 with zero history, as in the printed function (CurrentBar < 4). This corrects the earlier start at index 2, which carried a decaying start-up artefact into later values.

The coefficients use math.Sqrt(2), math.Pi and radians in place of the printed 1.414, 3.14159 and degrees.

n is the critical period in bars; a value of n less than one returns a zero-filled series.

Laguerre

func (s Series) Laguerre(n int, gamma float64, order int) Series

Laguerre implements Ehlers’ Laguerre filter code from TASC Sep 2025. Note that the first stage’s recursion intentionally uses the previous bar’s values (unlike the classic 2004 Laguerre filter used by LaguerreRSI); this matches Ehlers’ published Sep 2025 code. Values of n or order less than one return a zero-filled series.

gamma is a damping factor between 0 and 1. The warm-up reads 0, not NaN: bars 0 to n-2 are 0 and the first valid index is n-1, where every stage starts at the UltimateSmoother’s value, the filter’s steady state, so a constant series gives its level from bar n-1 (exactly at bar n-1, and to within rounding after it); n longer than the series gives zeros. Releases before this change started the stages at 0, so the output ramped up to the price level over roughly 20 bars or more for order > 1.

Each stage update is written as p + gamma*(q - p), equal to Ehlers’s -gamma*p + p + gamma*q but exact when its inputs are equal.

LaguerreRSI

func (s Series) LaguerreRSI(gamma float64) Series

LaguerreRSI implements John Ehlers’ Laguerre Relative Strength Index.

gamma is a damping factor between 0 and 1 and is not validated. The output is on a 0 to 100 scale (a reading can exceed 100 by one ulp from rounding), where Ehlers publishes it on 0 to 1, so his 0.2 and 0.8 levels are 20 and 80 here. The four stages start at the first price, the filter’s steady state, and stay there exactly while the price stays at its opening value, so those bars read 0 and the first bar that moves reads as bar 1 would: at gamma 0.5, 71.43 after a rise and 28.57 after a fall, whatever their size. Releases before this change started the stages at 0, so the first readings climbed to 70 to 100 for tens of bars whatever the data, which read as overbought. A NaN or infinite first price now makes every bar NaN, as a bad price at any later bar already did from that bar on.

Each stage update is written as v + gamma*(p - v), equal to Ehlers’s (1-gamma)*v + gamma*p but exact when its inputs are equal. So on a flat stretch after the price has moved the stages settle on the level, exactly for gamma below 0.5 (reading 0 within about 30 bars at 0.2 after a 1% move), to within an ulp at 0.5 (reading 0), and a few ulps short for larger gamma, where the reading freezes, usually at 0 or 100 in the direction from which the stages approached, as the exact filter would read. The time this takes grows with the move against the spacing of floating-point values at the level: at a level of exactly 0, where that spacing shrinks with the stages, the reading after a rise stays near 100 until the stages underflow, about 480 bars at 0.2 and 1,100 at 0.5, and for larger gamma it stays there. A NaN or infinite gamma makes every bar NaN.

Phase

func (s Series) Phase(n int) Series

Phase implements the phase function according to the translation of the TradeStation algorithm provided by John Ehlers.

Bars 0 and 1 pass the input through, seeding the recursion as SuperSmoother is seeded, so the output starts at the price level; on a constant series it stays within a few ulps of the level, as the all-pass coefficients are not exact in binary64. An n of 0 or less returns zeros. Releases before this change started from zero history, so bars 0 and 1 were 0 and the output ramped from 0 to the price level over the next few bars.

PMA

func (s Series) PMA(n int) Series

PMA implements John Ehlers’ Projected Moving Average (as described in TASC Mar 2025).

Bars 0 to n-2 pass the input through; the first averaged bar is n-1.

PMAPredict

func (s Series) PMAPredict(n int) Series

PMAPredict implements John Ehlers’ prediction based on the predicted moving average (see TASC Mar 2025, p. 11)

Bars 0 to n pass PMA through (so bars 0 to n-2 are the input itself): the prediction term reads Slope two bars back, which is valid only from bar n+1, the first predicted value. Releases before this change added a prediction term built on Slope’s warm-up 0 at bars n-1 and n.

RocketRSI

func (s Series) RocketRSI(nSmooth, n int) Series

RocketRSI applies a Fisher transform to the normalised rolling up/down sums of smoothed momentum. RocketRSI(1, 1) uses zero-lag momentum and returns zeros. The shared Momentum(0) convention remains a one-bar difference.

The warm-up reads 0, not NaN: bars 0 to 2n-2 are 0 and the first valid index is 2n-1. The momentum s[i] - s[i-n+1] exists from bar n-1, so the SuperSmoother and the n-bar sums of its changes run on it from there. Releases before this change smoothed Momentum’s warm-up zeros as well, so bars n and n+1 read about plus or minus 3.8 (the clamp) for almost any data.

Slope

func (s Series) Slope(n int) Series

Slope calculates the slope over “n” points at each point in the series (see TASC Mar 2025, p. 11). The underlying calculation uses numerically stable, centred coordinates so Slope remains accurate for windows with a large common offset. A flat window of two or more points yields a slope of exactly zero. Slope(1) is the existing singleton case and returns NaN.

The warm-up reads 0, not NaN: bars 0 to n-2 are 0, which reads as flat.

SuperSmoother

func (s Series) SuperSmoother(n int) Series

SuperSmoother implements the super-smoother function according to the translation of the TradeStation algorithm provided by John Ehlers.

Bars 0 to 2 pass the input through, seeding the recursion.

The recursion is written as r[i-1] + c1*(m - r[i-1]) - c3*(r[i-1] - r[i-2]), with m the mean of the current and previous prices. That is Ehlers’ c1m + c2r[i-1] + c3*r[i-2] rearranged with c2 = 1 - c1 - c3, and it gives a constant series its level exactly, which the published form does not: the three coefficients sum to 1 only up to rounding. Releases before this change used the published form, which was off by rounding on most bars of some constants (297 of 300 bars of a constant 120 at n = 10 on arm64); every output moves by rounding only. The mean is formed by halving each price first when their sum would overflow, and a bar whose exact form is not finite (its differences can overflow for prices within a factor of about 2 of the largest float64) takes the published form instead, so no finite published value becomes infinite.

UltimateOscillator

func (s Series) UltimateOscillator(bandEdge int, bandWidth int, rmsLength ...int) Series

UltimateOscillator implements the ultimate-oscillator function according to the translation of the TradeStation algorithm provided by John Ehlers (in TASC April 2025). Despite the shared name, this is Ehlers’s 2025 band-pass filter, not Larry Williams’s Ultimate Oscillator (1985), which averages buying pressure over 7, 14 and 28 bars. The optional RMS length defaults to 100, matching the published version. With RMS length 1, the signal is divided by its absolute value; zero stays zero. Standalone RMS retains its zero output for lengths <= 1.

bandWidth is an integer multiple of bandEdge, the slower high-pass using bandWidth*bandEdge bars, not a fraction. The warm-up reads 0, not NaN: bars 0 to rmsLength-2 are 0 (99 bars at the default 100), from RMS’s zero warm-up.

UltimateSmoother

func (s Series) UltimateSmoother(n int) Series

UltimateSmoother implements the ultimate-smoother function according to the translation of the TradeStation algorithm provided by John Ehlers (in TASC April 2024). Note that the Sep 2025 TASC has different code for the UltimateSmoother, but we have stuck with the previous version here (for the moment, at least).

Bars 0 to 2 pass the input through, seeding the recursion.

The recursion is written as r[i-1] + (1-c1)(s[i] - r[i-1]) + (2c1-c2)(s[i-1] - r[i-1]) - (c1+c3)(s[i-2] - r[i-1]) - c3*(r[i-1] - r[i-2]). That is Ehlers’ form rearranged, using the fact that its five weights sum to 1, and it gives a constant series its level exactly, which the published form does not: the weights sum to 1 only up to rounding. Releases before this change used the published form, which was off by rounding on most bars of some constants (297 of 300 bars of a constant 120 at n = 2 on arm64); every output moves by rounding only. A bar whose exact form is not finite (its differences can overflow for prices within a factor of about 2 of the largest float64) takes the published form instead, so no finite published value becomes infinite.

USI

func (s Series) USI(n int) Series

USI implements John Ehlers’ Ultimate Strength Index (as described in TASC Nov 2024).

The warm-up reads 0, not NaN: bar 0 is 0, and a bar whose smoothed up or down sum is not above 0.01, in price units, holds the previous value, which starts at 0, so on flat data it stays 0.

VIDYA

func (s Series) VIDYA(short int, long int) Series

VIDYA implements Chande/Kroll’s variable dynamic average according to the translation of the TradeStation algorithm provided by John Ehlers. Requires 1 <= short <= long; invalid windows return a zero-filled series.

Bars 0 to long-1 pass the input through, and the recursion is seeded with s[long-1].

Voss

func (s Series) Voss(lead int) Series

Voss implements the Voss predictor function according to the translation of the TradeStation algorithm provided by John Ehlers. A lead of less than one returns a zero-filled series.

The recursion runs from bar 0 with zero history, as Ehlers’s source does (feedback terms before bar 0 read 0), so there are no warm-up zeros; it feeds back on its own early output, so apply it to a band-passed, zero-mean input, whose first values are small. Releases before this change held bars 0 to 3lead-1 at 0 and started the recursion at bar 3lead, where it met a full-size input with no history and gave a spike of about (3+3lead)/2 times the input; the output then equals this one on the input with its first 3lead values set to 0.

ZeroLag

func (s Series) ZeroLag(n int) Series

ZeroLag implements John Ehlers’ zero-lag indicator. A value of n less than one returns a zero-filled series.

The warm-up reads 0, not NaN: bars 0 to F-1 are 0, where F = 2n-1 (F = 2 when n = 1) is the first valid index, the first bar whose Hann window holds only bars where the required price is defined (from bar n, or bar 2 when n = 1). Releases before this change gave values near minus the price on bars n-1 to F-1, because the window averaged the simple average’s pass-through against a required price of 0; every later bar is unchanged.