Attachments forums

Re: Ehlers Indicators for TradeStation

Banzai, Mon Mar 16, 2026 5:08 am

A Synthetic Oscillator

Links: |TradingView|TradeStation|NinjaTrader|MT4|MT5|Python|

TradeStation: April 2026
In “A Synthetic Oscillator” in this issue, John Ehlers introduces a nonlinear oscillator designed to reduce lag while maintaining smooth, responsive trading signals. The indicator adapts to changing market conditions by measuring the instantaneous dominant cycle and generating signals through a phase-based sine waveform.

EasyLanguage code for the indicator is shown here and a sample chart plotting the indicator is shown in the code below.

Code: Select all

Function: $HighPass

{
	$HighPass Function
 	(C) 2004-2024 John F. Ehlers
}

inputs:
	Price(numericseries),
	Period(numericsimple);
	
variables:
	a1( 0 ),
	b1( 0 ),
	c1( 0 ),
	c2( 0 ),
	c3( 0 );

a1 = ExpValue(-1.414 * 3.14159 / Period);
b1 = 2 * a1 * Cosine(1.414 * 180 / Period);
c2 = b1;
c3 = -a1 * a1;
c1 = (1 + c2 - c3) / 4;

if CurrentBar >= 4 then 
 	$HighPass = c1*(Price - 2 * Price[1] + Price[2]) +
	 c2 * $HighPass[1] + c3 * $HighPass[2];
if Currentbar < 4 then 
	$HighPass = 0;


Function: $RMS

{
 $RMS Function
 (C) 2025 John F. Ehlers
}

inputs:
	Price( numericseries ),
	Length( numericsimple );

variable:
	SumSq( 0 ),
	Count( 0 );

SumSq = 0;

for Count = 0 to Length - 1
begin
	SumSq = SumSq + Price[count]*Price[count];
end;

if SumSq <> 0 then 
	$RMS = SquareRoot(SumSq / Length);


Function: $SuperSmoother

{
	$SuperSmoother Function
	(C) 2025 John F. Ehlers
}
inputs:
	Price( numericseries ),
	Period( numericsimple );

variables:
	A0( 0 ),
	Q( 0 ),
	C1( 0 ),
	C2( 0 );
	
Q = ExpValue( -1.414*3.14159 / Period );
C1 = 2 * Q * Cosine(1.414*180 / Period);
C2 = Q*Q;
A0 = (1 - c1 + c2) / 2;

if CurrentBar >= 4 Then $SuperSmoother = A0*( Price +
 Price[1]) + C1 * $SuperSmoother[1] - C2 * $SuperSmoother[2];

if Currentbar < 4 then 
	$SuperSmoother = Price;


Function: $Hann

{
	$Hann Windowed Lowpass FIR Filter Function
	(c) 2025 John F. Ehlers
}
inputs:
	Price( numericseries ),
	Length( numericsimple );

variables:
	count(0),
	coef(0),
	Filt(0);
	Filt = 0;
	coef = 0;

for count = 1 to Length 
begin
	Filt = Filt + (1 - Cosine(360*count / (Length +
	 1)))*Price[count - 1];
	coef = coef + (1 - Cosine(360*count / (Length + 1)));
end;

if coef <> 0 then 
	$Hann = Filt / coef;


Function $UltimateSmoother

{
	UltimateSmoother Function
	(C) 2004-2024 John F. Ehlers
}

inputs:
	Price( numericseries ),
	Period( numericsimple );
	
variables:
	a1( 0 ),
	b1( 0 ),
	c1( 0 ),
	c2( 0 ),
	c3( 0 ),
	US( 0 );
	
a1 = ExpValue(-1.414*3.14159 / Period);
b1 = 2 * a1 * Cosine(1.414*180 / Period);
c2 = b1;
c3 = -a1 * a1;
c1 = (1 + c2 - c3) / 4;

if CurrentBar >= 4 then 
 US = (1 - c1)*Price + (2 * c1 - c2) * Price[1] 
 - (c1 + c3) * Price[2] + c2*US[1] + c3 * US[2];
 
if CurrentBar < 4 then 
	US = Price;

$UltimateSmoother = US;


Indicator: Synthetic Oscillator

{
	TASC APR 2026
	Synthetic Oscillator Indicator
	(C) 2025 John F. Ehlers
}

inputs:
	LowerBound( 15 ),
	UpperBound( 25 ),
	Length( 4 );

variables:
	Price( 0 ),
	HP( 0 ),
	LP( 0 ),
	RMS( 0 ),
	Real( 0 ),
	ROC( 0 ),
	QRMS( 0 ),
	Imag( 0 ),
	Denom( 0 ),
	DC( 0 ),
	Count( 0 ),
	Mid( 0 ),
	HP2( 0 ),
	BP( 0 ),
	Phase( 0 ),
	Synth( 0 ),
	Synth2( 0 ),
	ROC2( 0 );

Price = $Hann( Close, 12 );

{ Real component is bandpass filtered and normalized }
HP = $HighPass( Price, UpperBound );
LP = $SuperSmoother( HP, LowerBound );

RMS = $RMS( LP, 100 );
if RMS <> 0 then
	Real = LP / RMS;

{ Imaginary component is rate of change normalized }
ROC = Real - Real[ 1 ];

QRMS = $RMS( ROC, 100 );
if QRMS <> 0 then
	Imag = ROC / QRMS;

{ Solve rate of change of arctangent }
Denom = ( ( Real - Real[ 1 ] ) * Imag ) 
 - ( ( Imag - Imag[ 1 ] ) * Real );
 
if Denom <> 0 then
	DC = 6.28 * ( ( Real * Real ) 
	 + ( Imag * Imag ) ) / Denom;

{ Limit range of measured values }
if DC < LowerBound then
	DC = LowerBound;

if DC > UpperBound then
	DC = UpperBound;

Mid = SquareRoot( LowerBound * UpperBound );

{ Create a bandpass filter at the average dominant cycle period }
HP2 = $HighPass( Close, Mid );
BP  = $UltimateSmoother( HP2, Mid );

{ Cumulate phase and force reset at 0 and 180 degrees }
Phase = Phase + ( 360 / DC );

if BP crosses over 0 then
	Phase = 180 / DC;

if BP crosses under 0 then
	Phase = 180 + ( 180 / DC );

{ Synthetic oscillator is the sine of the cumulative phase angle }
Synth = Sine( Phase );

{ Remove reset glitch if continuity falls in the same quadrant }
if Phase > 0 and Phase < 90 and Synth < Synth[ 1 ] then
	Synth = Synth[ 1 ];

if Phase > 180 and Phase < 270 and Synth > Synth[ 1 ] then
	Synth = Synth[ 1 ];

plot1( Synth );
plot2( 0 );
FIGURE 1: TRADESTATION. This demonstrates a daily chart of the S&P 500 ETF SPY with the indicator applied, showing a portion of 2025 and 2026.

This article is for informational purposes. No type of trading or investment recommendation, advice, or strategy is being made, given, or in any manner provided by TradeStation Securities or its affiliates.
All files in topic