In the April 2024 issue of TASC, John Ehler's introduces The Ultimate Smoother.
The UltimateSmoother is designed to retain low-frequency swings while reducing high-frequency noise in the input time series. Its critical period parameter determines the shortest wavelength that remains in the filter's pass band. This means it suppresses oscillations shorter than the critical period.
Ehlers highlights that the UltimateSmoother offers zero lag in its pass band and minimal lag in its transition band, setting it apart from traditional digital filters like moving averages. It's versatile and can be applied to different input data series, including other indicators.
Code for various platforms was posted on their website here however it did not have a thinkscript version. I always enjoy examining Ehler's math so I took the opportunity to create a thinkscript version, posted below.
Update 4/13/2024:
Version 3.0
- Added Ehlers Ultimate Bands and Ultimate Channels
Update 3/31/2024:
Version 2.0
- Added option to displace
- Added option to show signals on concavity or direction change
- Added option to color bars based on concavity or direction
- Reworked ultimate smoother into a script
- Changed CompoundValue to default to src instead of Double.NaN
Here is code for Ultimate Smoother Convergence Divergence (USCD)
The UltimateSmoother is designed to retain low-frequency swings while reducing high-frequency noise in the input time series. Its critical period parameter determines the shortest wavelength that remains in the filter's pass band. This means it suppresses oscillations shorter than the critical period.
Ehlers highlights that the UltimateSmoother offers zero lag in its pass band and minimal lag in its transition band, setting it apart from traditional digital filters like moving averages. It's versatile and can be applied to different input data series, including other indicators.
Code for various platforms was posted on their website here however it did not have a thinkscript version. I always enjoy examining Ehler's math so I took the opportunity to create a thinkscript version, posted below.
Update 4/13/2024:
Version 3.0
- Added Ehlers Ultimate Bands and Ultimate Channels
Update 3/31/2024:
Version 2.0
- Added option to displace
- Added option to show signals on concavity or direction change
- Added option to color bars based on concavity or direction
- Reworked ultimate smoother into a script
- Changed CompoundValue to default to src instead of Double.NaN
Code:
# FOXHOUND
# Ehler's Ultimate Smoother, Ultimate Channels, and Ultimate Bands
# by bigboss
#
# https://www.traders.com/Documentation/FEEDbk_docs/2024/04/TradersTips.html
# https://www.traders.com/Documentation/FEEDbk_docs/2024/05/TradersTips.html
#
# Version 3.0
# - Added Ehlers Ultimate Bands and Ultimate Channels
#
# Version 2.0
# - Added option to displace
# - Added option to show signals on concavity or direction change
# - Added option to color bars based on concavity or direction
# - Reworked ultimate smoother into a script
# - Changed CompoundValue to default to src instead of Double.NaN
#
# Version 1.0
# - Initial Release
input src = close;
input length = 20;
input displace = 0;
input showSignals = no;
input signalSource = {default direction, concavity};
input paintbars = no;
input bandMode = {default off, bands, channel};
input bandLength = 20;
input bandWidth = 1.0;
input bandCloud = yes;
script us{
input src = close;
input length = 20;
input displace = 0;
def a1 = Exp(-Sqrt(2) * Double.Pi / length);
def c2 = 2.0 * a1 * Cos(Sqrt(2) * Double.Pi / length);
def c3 = -a1 * a1;
def c1 = (1.0 + c2 - c3) / 4.0;
def us =
(1.0 - c1) * src +
(2.0 * c1 - c2) * src[1] -
(c1 + c3) * src[2] +
c2 * us[1] + c3 * us[2];
plot ultimate = CompoundValue(4,us[-displace],src);
}
plot ultimate = us(src,length,displace);
ultimate.SetLineWeight(2);
def width =
if bandMode == bandMode.Channel then
us(TrueRange(high,close,low),bandLength,displace)
else if bandMode == bandMode.Bands then
StDev(src-ultimate,bandLength)
else
0;
plot upperBand = if bandMode != bandMode.off then ultimate + width * bandwidth else Double.NaN;
upperBand.SetDefaultColor(Color.LIGHT_GRAY);
plot lowerBand = if bandMode != bandMode.off then ultimate - width * bandwidth else Double.NaN;
lowerBand.SetDefaultColor(Color.LIGHT_GRAY);
AddCloud( if bandCloud then upperBand else Double.NaN, if bandCloud then lowerBand else Double.NaN,Color.Dark_Gray);
def direction = if ultimate[1] < ultimate then 1 else -1;
def slope = reference LinearRegressionSlope(ultimate, 2);
def concavity = if slope > slope [1] then 1 else -1;
ultimate.AssignValueColor(
if signalSource == signalSource.concavity then
if concavity > 0 and direction > 0 then Color.GREEN else
if concavity > 0 and direction < 0 then Color.DARK_GREEN else
if concavity < 0 and direction < 0 then Color.RED else
Color.DARK_ORANGE
else
if direction > 0 then Color.LIGHT_GREEN
else Color.LIGHT_RED
);
def ss = if signalSource == signalSource.concavity then concavity else direction;
AssignPriceColor(if !paintbars then Color.CURRENT else if ss > 0 then Color.GREEN else Color.RED);
plot buy = if ss[1] < 0 and ss > 0 and showSignals then ultimate else Double.NaN;
buy.SetPaintingStrategy(PaintingStrategy.ARROW_UP);
buy.HideBubble();
buy.HideTitle();
buy.SetDefaultColor(Color.GREEN);
buy.SetLineWeight(3);
plot sell = if ss[1] > 0 and ss < 0 and showSignals then ultimate else Double.NaN;
sell.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN);
sell.HideBubble();
sell.HideTitle();
sell.SetDefaultColor(Color.RED);
sell.SetLineWeight(3);
Here is code for Ultimate Smoother Convergence Divergence (USCD)
Code:
# FOXHOUND
# Ehler's Ultimate Smoother Convergence Divergence (USCD)
# by bigboss
# https://www.traders.com/Documentation/FEEDbk_docs/2024/04/TradersTips.html
declare lower;
input fastLength = 12;
input slowLength = 26;
input USCDLength = 9;
input src = close;
input showBreakoutSignals = no;
script us{
input src = close;
input length = 20;
def a1 = Exp(-Sqrt(2) * Double.Pi / length);
def c2 = 2.0 * a1 * Cos(Sqrt(2) * Double.Pi / length);
def c3 = -a1 * a1;
def c1 = (1.0 + c2 - c3) / 4.0;
def us =
(1.0 - c1) * src +
(2.0 * c1 - c2) * src[1] -
(c1 + c3) * src[2] +
c2 * us[1] + c3 * us[2];
plot ultimate = CompoundValue(4,us,src);
}
plot Value = us(close, fastLength) - us(close, slowLength);
plot Avg = us(Value, USCDLength);
plot Diff = Value - Avg;
plot ZeroLine = 0;
plot UpSignal = if Diff crosses above ZeroLine then ZeroLine else Double.NaN;
plot DownSignal = if Diff crosses below ZeroLine then ZeroLine else Double.NaN;
UpSignal.SetHiding(!showBreakoutSignals);
DownSignal.SetHiding(!showBreakoutSignals);
Value.SetDefaultColor(GetColor(1));
Avg.SetDefaultColor(GetColor(8));
Diff.SetDefaultColor(GetColor(5));
Diff.SetPaintingStrategy(PaintingStrategy.HISTOGRAM);
Diff.SetLineWeight(3);
Diff.DefineColor("Positive and Up", Color.GREEN);
Diff.DefineColor("Positive and Down", Color.DARK_GREEN);
Diff.DefineColor("Negative and Down", Color.RED);
Diff.DefineColor("Negative and Up", Color.DARK_RED);
Diff.AssignValueColor(if Diff >= 0 then if Diff > Diff[1] then Diff.color("Positive and Up") else Diff.color("Positive and Down") else if Diff < Diff[1] then Diff.color("Negative and Down") else Diff.color("Negative and Up"));
ZeroLine.SetDefaultColor(GetColor(0));
UpSignal.SetDefaultColor(Color.UPTICK);
UpSignal.SetPaintingStrategy(PaintingStrategy.ARROW_UP);
DownSignal.SetDefaultColor(Color.DOWNTICK);
DownSignal.SetPaintingStrategy(PaintingStrategy.ARROW_DOWN);
Last edited: