I took the liberty of modifying the popular True Momentum Oscillator by Mobius by making it adaptive to the dominant market cycle. The dominant cycle in this variant is obtained using the band-pass filter method per Chapter 5 of Dr Ehlers' book, "Cycle Analytics for Traders". Here is a screen shot showing the adaptive TMO as the bottom indicator immediately above the volume indicator. Another modification involves replacing the EMA used to smooth the main and signal lines with EhlersSuperSmootherFilter. The SmootherLength is currently set to 6, but can be changed in the properties dialog. Hopefully this adaptive variant will be the game changer you are looking for. Enjoy!
PS - This variant has a property that lets you set the aggregation to a higher time frame. You might consider placing two instances of the indicator on top of each other and setting one to a higher time frame in order to see confluence in the True Momentum Oscillator between the time frames.
Here is the script for the adaptive True Momentum Oscillator (ATMO):
PS - This variant has a property that lets you set the aggregation to a higher time frame. You might consider placing two instances of the indicator on top of each other and setting one to a higher time frame in order to see confluence in the True Momentum Oscillator between the time frames.
Here is the script for the adaptive True Momentum Oscillator (ATMO):
CSS:
# TMO ((T)rue (M)omentum (O)scilator)
# Mobius
# V01.05.2018
#
# [18OCT2025]: Modified by Sesqui to make it adaptive to Market Dominant Cycle via BandpassFilter Filter method per CH 5 Dr Ehlers' book, "Cycle Analytics for Traders"
# The band-pass filter method obtained precise dominant cycle values when tested
# against sinewaves across a1 wide range of period values.
# hint: TMO calculates momentum using the delta of price. Giving a much better picture of trend, tend reversals and divergence than momentum oscillators using price.
declare lower;
input SuperSmootherLength = 6;
input BandPassSource = ohlc4;
input BandPassPeriod = 20;
input BandPassBandwidth = 0.70;
#===================================================================================
#*** Script to measure dominant cycle length using band-pass
#*** filter zero crossings method per Dr Ehlers
#*** Chapter 5 of "Cycle Analytics for Traders".
script GetCycleViaBandPassFilter {
input source = ohlc4;
input Period = 20;
input Bandwidth = 0.70;
def cosPart = Cos(0.25 * Bandwidth * 2.0 * Double.Pi / Period);
def sinPart = Sin(0.25 * Bandwidth * 2.0 * Double.Pi / Period);
def alpha2 = (cosPart + sinPart - 1) / cosPart;
def HP = (1 + alpha2 / 2) * (source - source[1]) + (1 - alpha2) * HP[1];
def beta1 = Cos(2.0 * Double.Pi / Period);
def gamma1 = 1 / Cos(2.0 * Double.Pi * Bandwidth / Period);
def alpha1 = gamma1 - Sqrt(gamma1 * gamma1 - 1);
def BP = if BarNumber() == 1 or BarNumber() == 2 then 0 else 0.5 * (1 - alpha1) * (HP - HP[2]) + beta1 * (1 + alpha1) * BP[1] - alpha1 * BP[2];
def Peak = if AbsValue(BP) > 0.991 * Peak[1] then AbsValue(BP) else 0.991 * Peak[1];
def Real = if Peak <> 0 then BP / Peak else Double.NaN;
def crossAbove = Real crosses above 0;
def crossBelow = Real crosses below 0;
def zeroCrossing = crossAbove or crossBelow;
# Uses a recursive variable to count the bars between zero-crossings
# If a zero-crossing happens, resets the count to 0.
# Otherwise, increments the count by 1.
def barsSinceCross = if zeroCrossing then 0 else barsSinceCross[1] + 1;
def barsBetweenCrossings = barsSinceCross + 1;
def val = if barsBetweenCrossings < barsBetweenCrossings[1] then 2 * barsBetweenCrossings[1] else val[1];
# The following line is a port of Dr Ehlers EasyLanguage program, kept here for reference
#def DC = If barsBetweenCrossings < 3 then 3 else If 2*(barsBetweenCrossings) > 1.25*DC[1] then 1.25*DC[1] else if 2*(barsBetweenCrossings) < 0.8*DC[1] then 0.8*DC[1] else DC[1];
plot DC = val;
DC.HideBubble();
} # End script GetCycleViaBandPassFilter{}
#----------------------------------------------------
script SuperSmoother {
input price = close;
input Cycle = 10;
def CutOffLength = if IsNaN(Floor(Cycle)) then CutOffLength[1] else Floor(Cycle);
def a1 = Exp(-Double.Pi * Sqrt(2) / cutoffLength);
def coeff2 = 2 * a1 * Cos(Sqrt(2) * Double.Pi / cutoffLength);
def coeff3 = - Sqr(a1);
def coeff1 = 1 - coeff2 - coeff3;
def filt = if IsNaN(filt[1]) or IsNaN(filt[2]) then 0 else if IsNaN(price + price[1]) then filt[1] else coeff1 * (price + price[1]) / 2 + coeff2 * filt[1] + coeff3 * filt[2];
plot SuperSmootherFilter = if !IsNaN(price) then filt else Double.NaN;
SuperSmootherFilter.HideBubble();
} # End Script SuperSmoother{}
#=============================================================================
# This section computes the indicator
def Cycle = GetCycleViaBandPassFilter(BandPassSource, BandPassPeriod, BandPassBandwidth).DC;
def length = if IsNaN(Floor(Cycle)) then length[1] else Floor(Cycle);
input calcLength = 5;
input smoothLength = 3;
input agg = AggregationPeriod.MIN;
def o = open(period = agg);
def c = close(period = agg);
def data = fold i = 0 to length
with s
do s + (if c > GetValue(o, i)
then 1
else if c < GetValue(o, i)
then - 1
else 0);
plot Main = SuperSmoother(data, SuperSmootherLength);
plot Signal = SuperSmoother(Main, SuperSmootherLength);
Main.AssignValueColor(if Main > Signal
then Color.GREEN
else Color.RED);
Signal.AssignValueColor(if Main > Signal
then Color.GREEN
else Color.RED);
Signal.HideBubble();
Signal.HideTitle();
AddCloud(Main, Signal, Color.GREEN, Color.RED);
plot zero = if IsNaN(c) then Double.NaN else 0;
zero.SetDefaultColor(Color.GRAY);
zero.HideBubble();
zero.HideTitle();
plot ob = if IsNaN(c) then Double.NaN else Round(length * .7);
ob.SetDefaultColor(Color.GRAY);
ob.HideBubble();
ob.HideTitle();
plot os = if IsNaN(c) then Double.NaN else -Round(length * .7);
os.SetDefaultColor(Color.GRAY);
os.HideBubble();
os.HideTitle();
def b1 = 0.6 * length;
def b2 = -0.8 * length;
AddCloud(ob, b1, Color.LIGHT_RED, Color.LIGHT_RED, no);
AddCloud(b2, os, Color.LIGHT_GREEN, Color.LIGHT_GREEN);
# End Code TMO
#---------------RSI to help gage OB and OS levels---------------------------
input RSIlength = 14;
input over_Bought = 70;
input over_Sold = 30;
input RSIprice = close;
input averageType = AverageType.WILDERS;
def NetChgAvg = MovingAverage(averageType, RSIprice - RSIprice[1], RSIlength);
def TotChgAvg = MovingAverage(averageType, AbsValue(RSIprice - RSIprice[1]), RSIlength);
def ChgRatio = if TotChgAvg != 0 then NetChgAvg / TotChgAvg else 0;
def RSI = 50 * (ChgRatio + 1);
AddLabel(1, if RSI > over_Bought then "RSI OB: " + Round(RSI, 1) else if RSI < over_Sold then "RSI OS: " + Round(RSI, 1) else "RSI: " + Round(RSI, 1), if RSI > over_Bought then Color.RED else if Round(RSI, 2) < over_Sold then Color.GREEN else Color.WHITE , location = Location.TOP_RIGHT, size = FontSize.SMALL);