TRADERS’ TIPS
For this month’s Traders’ Tips, the focus is John F. Ehlers’ article in this issue, “Linear Predictive Filters And Instantaneous Frequency.” Here, we present the January 2025 Traders’ Tips code with possible implementations in various software.
You can right-click on any chart to open it in a new tab or window and view it at it’s originally supplied size, often much larger than the version printed in the magazine.
The Traders’ Tips section is provided to help the reader implement a selected technique from an article in this issue or another recent issue. The entries here are contributed by software developers or programmers for software that is capable of customization.
In “Linear Predictive Filters And Instantaneous Frequency,” John Ehlers explores the use of linear predictive filters, applying the Griffiths approach and key digital signal processing principles to tackle the challenge of adaptively tuning indicators for evolving market conditions. He discusses how this approach can identify and adjust to the dominant cycle within market data.
Code in EasyLanguage for the Ehlers’ approach can be found in Ehlers article in this issue, and is also shown below.
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: $SuperSmoother { SuperSmoother 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; if CurrentBar >= 4 then $SuperSmoother = c1*(Price + Price[1]) / 2 + c2 * $SuperSmoother[1] + c3 * $SuperSmoother[2]; if CurrentBar < 4 then $SuperSmoother = Price; Indicator: Griffiths Predictor { TASC JAN 2025 Griffiths Predictor Indicator (C) 2024 John F. Ehlers From "Rapid Measurement of Digital Instantaneous Frequency", IEEE Transactions ASSP-23 } inputs: LowerBound( 18 ), UpperBound( 40 ), Length( 18 ), BarsFwd( 2 ); variables: Mu( 0 ), HP( 0 ), LP( 0 ), HH( 0 ), LL( 0 ), Signal( 0 ), Peak( .1 ), XBar( 0 ), count( 0 ), XPred( 0 ), Advance( 0 ); arrays: XX[200](0), Coef[200](0), Pwr[200,2](0); Mu = 1 / Length; HP = $HighPass(Close, UpperBound); LP = $SuperSmoother(HP, LowerBound); Peak = .991 * Peak[1]; if AbsValue(LP) > Peak then Peak = AbsValue(LP); if Peak <> 0 then Signal = LP / Peak; //Perfect cycle test signal //Signal = Sine(360*currentbar / 30); for Count = 1 to Length begin XX[count] = Signal[Length - count]; end; XBar = 0; for Count = 1 to Length begin XBar = XBar + XX[Length - count] * Coef[count]; end; for count = 1 to Length begin coef[count] = coef[count] + Mu*(XX[Length] - XBar)*XX[Length - count]; end; //Prediction for Advance = 1 to BarsFwd begin XPred = 0; for count = 1 to Length begin XPred = XPred + XX[Length + 1 - count]*coef[count]; end; for count = advance to Length - advance begin XX[count] = XX[count + 1]; end; for count = 1 to Length - 1 begin XX[count] = XX[count + 1]; end; XX[Length] = XPred; end; Plot1( Signal, "Signal" ); Plot2( 0, "Zero Line" ); Plot3( XPred, "XPred" ); Indicator: Griffiths Dominant Cycle { TASC JAN 2025 Griffiths Dominant Cycle Indicator (C) 2024 John F. Ehlers from "Rapid Measurement of Digital Instantaneous Frequency", IEEE Transactions ASSP-23 } inputs: LowerBound( 18 ), UpperBound( 40 ), Length( 40 ); variables: Mu( 0 ), HP( 0 ), LP( 0 ), HH( 0 ), LL( 0 ), Signal( 0 ), Peak( .1 ), XBar( 0 ), Count( 0 ), Advance( 0 ), Period( 0 ), Real( 0 ), Imag( 0 ), Denom( 0 ), MaxPwr( 0 ), Cycle( 0 ); arrays: XX[200]( 0 ), coef[200]( 0 ), Pwr[200,2]( 0 ); Mu = 1 / Length; HP = $HighPass(Close, UpperBound); LP = $SuperSmoother(HP, LowerBound); Peak = .991*Peak[1]; if AbsValue(LP) > Peak then Peak = AbsValue(LP); if Peak <> 0 then Signal = LP / Peak; //Signal = Sine(360*currentbar / 30); for Count = 1 to Length begin XX[count] = Signal[Length - count]; end; XBar = 0; for Count = 1 to Length begin XBar = XBar + XX[Length - Count] * coef[count]; end; For count = 1 to Length begin coef[count] = coef[count] + Mu*(XX[Length] - XBar) * XX[Length - count]; end; //Instantaneous Frequency for Period = LowerBound to UpperBound begin Real = 0; Imag = 0; for count = 1 to Length begin Real = Real + coef[count] * Cosine(360*count / Period); Imag = Imag + coef[count] * Sine(360*count / Period); end; Denom = (1 - Real)*(1 - Real) + Imag*Imag; Pwr[Period, 1] = .1 / Denom; end; MaxPwr = 0; for Period = LowerBound to UpperBound begin If Pwr[Period, 1] > MaxPwr then begin MaxPwr = Pwr[Period, 1]; Cycle = Period; end; end; if Cycle > Cycle[1] + 2 then Cycle = Cycle[1] + 2; if Cycle < Cycle[1] - 2 then Cycle = Cycle[1] - 2; Plot1(Cycle, "Cycle" ); Indicator: Griffiths Spectrum { TASC JAN 2025 Griffiths Spectrum Indicator (C) 2024 John F. Ehlers From "Rapid Measurement of Digitial Instantaneous Frequency", IEEE Transactions ASSP-23 } inputs: LowerBound( 10 ), UpperBound( 40 ), Length( 40 ); variables: Mu( 0 ) , HP( 0 ), LP( 0 ), HH( 0 ), LL( 0 ), Signal( 0 ), Peak( .1 ), XBar( 0 ), Count( 0 ), advance( 0 ), Period( 0 ), Real( 0 ), Imag( 0 ), Denom( 0 ), MaxPwr( 0 ), Color1( 0 ), Color2( 0 ), PlotColor( 0 ); arrays: XX[100]( 0 ), coef[100]( 0 ), Pwr[100, 2]( 0 ); Mu = 1 / Length; HP = $HighPass(Close, UpperBound); LP = $SuperSmoother(HP, LowerBound); Peak = .991 * Peak[1]; if AbsValue(LP) > Peak then Peak = AbsValue(LP); if Peak <> 0 then Signal = LP / Peak; for count = 1 to Length begin XX[count] = Signal[Length - count]; end; XBar = 0; for count = 1 to Length begin XBar = XBar + XX[Length - count]*coef[count]; end; for count = 1 to Length begin coef[count] = coef[count] + Mu*(XX[Length] - XBar)*XX[Length - count]; end; //Instantaneous Frequency For Period = LowerBound to UpperBound begin Pwr[Period, 2] = Pwr[Period, 1]; Real = 0; Imag = 0; for count = 1 to Length begin Real = Real + coef[count] * Cosine(360 * Count / Period); Imag = Imag + coef[count] * Sine(360 * Count / Period); end; Denom = (1 - Real)*(1 - Real) + Imag*Imag; Pwr[Period, 1] = .1 / Denom + .9*Pwr[Period, 2]; end; MaxPwr = 0; For Period = LowerBound to UpperBound begin if Pwr[Period, 1] > MaxPwr then MaxPwr = Pwr[Period, 1]; end; for Period = LowerBound to UpperBound begin if MaxPwr <> 0 then Pwr[Period, 1] = Pwr[Period, 1] / MaxPwr; end; //Plot the Spectrum as a Heatmap for Period = LowerBound to UpperBound begin //Convert Power to RGB Color for Display if Pwr[Period, 1] >= .5 then begin Color1 = 255; Color2 = 255*(2*Pwr[Period, 1] - 1); end else begin Color1 = 255*2*Pwr[Period, 1]; Color2 = 0; End; PlotColor = RGB(Color1, Color2, 0); if period = 3 then Plot3(3, "S5", PlotColor, 0, 4); if period = 4 then Plot4(4, "S4", PlotColor, 0, 4); if period = 5 then Plot5(5, "S5", PlotColor, 0, 4); if period = 6 then Plot6(6, "S6", PlotColor, 0, 4); if period = 7 then Plot7(7, "S7", PlotColor, 0, 4); if period = 8 then Plot8(8, "S8", PlotColor, 0, 4); if period = 9 then Plot9(9, "S9", PlotColor, 0, 4); if period = 10 then Plot10(10, "S10", PlotColor, 0, 4); if period = 11 then Plot11(11, "S11", PlotColor, 0, 4); if period = 12 then Plot12(12, "S12", PlotColor, 0, 4); if period = 13 then Plot13(13, "S13", PlotColor, 0, 4); if period = 14 then Plot14(14, "S14", PlotColor, 0, 4); if period = 15 then Plot15(15, "S15", PlotColor, 0, 4); if period = 16 then Plot16(16, "S16", PlotColor, 0, 4); if period = 17 then Plot17(17, "S17", PlotColor, 0, 4); if period = 18 then Plot18(18, "S18", PlotColor, 0, 4); if period = 19 then Plot19(19, "S19", PlotColor, 0, 4); if period = 20 then Plot20(20, "S20", PlotColor, 0, 4); if period = 21 then Plot21(21, "S21", PlotColor, 0, 4); if period = 22 then Plot22(22, "S22", PlotColor, 0, 4); if period = 23 then Plot23(23, "S23", PlotColor, 0, 4); if period = 24 then Plot24(24, "S24", PlotColor, 0, 4); if period = 25 then Plot25(25, "S25", PlotColor, 0, 4); if period = 26 then Plot26(26, "S26", PlotColor, 0, 4); if period = 27 then Plot27(27, "S27", PlotColor, 0, 4); if period = 28 then Plot28(28, "S28", PlotColor, 0, 4); if period = 29 then Plot29(29, "S29", PlotColor, 0, 4); if period = 30 then Plot30(30, "S30", PlotColor, 0, 4); if period = 31 then Plot31(31, "S31", PlotColor, 0, 4); if period = 32 then Plot32(32, "S32", PlotColor, 0, 4); if period = 33 then Plot33(33, "S33", PlotColor, 0, 4); if period = 34 then Plot34(34, "S34", PlotColor, 0, 4); if period = 35 then Plot35(35, "S35", PlotColor, 0, 4); if period = 36 then Plot36(36, "S36", PlotColor, 0, 4); if period = 37 then Plot37(37, "S37", PlotColor, 0, 4); if period = 38 then Plot38(38, "S38", PlotColor, 0, 4); if period = 39 then Plot39(39, "S39", PlotColor, 0, 4); if period = 40 then Plot40(40, "S40", PlotColor, 0, 4); if period = 41 then Plot41(41, "S41", PlotColor, 0, 4); if period = 42 then Plot42(42, "S42", PlotColor, 0, 4); if period = 43 then Plot43(43, "S43", PlotColor, 0, 4); if period = 44 then Plot44(44, "S44", PlotColor, 0, 4); if period = 45 then Plot45(45, "S45", PlotColor, 0, 4); if period = 46 then Plot46(46, "S46", PlotColor, 0, 4); if period = 47 then Plot47(47, "S47", PlotColor, 0, 4); if period = 48 then Plot48(48, "S48", PlotColor, 0, 4); if period = 49 then Plot49(49, "S49", PlotColor, 0, 4); if period = 50 then Plot50(50, "S50", PlotColor, 0, 4); if period = 51 then Plot51(51, "S51", PlotColor, 0, 4); if period = 52 then Plot52(52, "S52", PlotColor, 0, 4); if period = 53 then Plot53(53, "S53", PlotColor, 0, 4); if period = 54 then Plot54(54, "S54", PlotColor, 0, 4); if period = 55 then Plot55(55, "S55", PlotColor, 0, 4); if period = 56 then Plot56(56, "S56", PlotColor, 0, 4); if period = 57 then Plot57(57, "S57", PlotColor, 0, 4); if period = 58 then Plot58(58, "S58", PlotColor, 0, 4); if period = 59 then Plot59(59, "S59", PlotColor, 0, 4); if period = 60 then Plot60(60, "S60", PlotColor, 0, 4); if period = 61 then Plot61(61, "S61", PlotColor, 0, 4); if period = 62 then Plot62(62, "S62", PlotColor, 0, 4); if period = 63 then Plot63(63, "S63", PlotColor, 0, 4); if period = 64 then Plot64(64, "S64", PlotColor, 0, 4); if period = 65 then Plot65(65, "S65", PlotColor, 0, 4); if period = 66 then Plot66(66, "S66", PlotColor, 0, 4); if period = 67 then Plot67(67, "S67", PlotColor, 0, 4); if period = 68 then Plot68(68, "S68", PlotColor, 0, 4); if period = 69 then Plot69(69, "S69", PlotColor, 0, 4); if period = 70 then Plot70(70, "S70", PlotColor, 0, 4); if period = 71 then Plot71(71, "S71", PlotColor, 0, 4); if period = 72 then Plot72(72, "S72", PlotColor, 0, 4); if period = 73 then Plot73(73, "S73", PlotColor, 0, 4); if period = 74 then Plot74(74, "S74", PlotColor, 0, 4); if period = 75 then Plot75(75, "S75", PlotColor, 0, 4); if period = 76 then Plot76(76, "S76", PlotColor, 0, 4); if period = 77 then Plot77(77, "S77", PlotColor, 0, 4); if period = 78 then Plot78(78, "S78", PlotColor, 0, 4); if period = 79 then Plot79(79, "S79", PlotColor, 0, 4); if period = 80 then Plot80(80, "S80", PlotColor, 0, 4); if period = 81 then Plot81(81, "S81", PlotColor, 0, 4); if period = 82 then Plot82(82, "S82", PlotColor, 0, 4); if period = 83 then Plot83(83, "S83", PlotColor, 0, 4); if period = 84 then Plot84(84, "S84", PlotColor, 0, 4); if period = 85 then Plot85(85, "S85", PlotColor, 0, 4); if period = 86 then Plot86(86, "S86", PlotColor, 0, 4); if period = 87 then Plot87(87, "S87", PlotColor, 0, 4); if period = 88 then Plot88(88, "S88", PlotColor, 0, 4); if period = 89 then Plot89(89, "S89", PlotColor, 0, 4); if period = 90 then Plot90(90, "S90", PlotColor, 0, 4); if period = 91 then Plot91(91, "S91", PlotColor, 0, 4); if period = 92 then Plot92(92, "S92", PlotColor, 0, 4); if period = 93 then Plot93(93, "S93", PlotColor, 0, 4); if period = 94 then Plot94(94, "S94", PlotColor, 0, 4); if period = 95 then Plot95(95, "S95", PlotColor, 0, 4); if period = 96 then Plot96(96, "S96", PlotColor, 0, 4); if period = 97 then Plot97(97, "S97", PlotColor, 0, 4); if period = 98 then Plot98(98, "S98", PlotColor, 0, 4); if period = 99 then Plot99(99, "S99", PlotColor, 0, 4); end;
A sample chart is shown in Figure 1.
FIGURE 1: TRADESTATION. This shows a daily chart of the continuous emini S&P 500 showing a portion of 2024 with all three indicators applied.
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.
Leave it John Ehlers to remind former electrical engineers how much we’ve forgotten! In “Linear Predictive Filters And Instantaneous Frequency” in this issue, Ehlers uses the Griffiths approach with elements of digital signal processing to offer coding for some indicators to help solve the challenge of adaptively tuning indicators and strategy algorithms to the dominant cycle of the data. The article includes several code listings to calculate and plot the indicators on a chart. Just to translate all the code presented in the article took the better part of a day, but the result is worth it.
Atop the Griffiths spectrum in Figure 2, you’ll see the “white hot” dominant cycle (DC) indicator snapping to the frequency with the most energy. Note however that it’s constrained to change only by two cycles per bar to prevent “bouncing.” The heat map is primarily eye candy (useful to impress your spouse and friends), but the Griffith DC indicator is the key to “adaptively tune indicators and strategy algorithms,” as Ehlers states in his article. The GriffithsDC and GriffithsPredictor indicators can now be found in our WealthLab.TASC indicator library.
FIGURE 2: WEALTH-LAB. This example daily chart of the emini S&P 500 futures (ES) displays the Griffiths spectrum with the dominant cycle indicator. The GriffithsDC and GriffithsPredictor indicators can now be found in the WealthLab.TASC indicator library.
using System; using WealthLab.Backtest; using WealthLab.Core; using WealthLab.TASC; namespace WealthScript { public class GriffithSpectrum : UserStrategyBase { Parameter _ub, _lb, _length; public GriffithSpectrum() { _lb = AddParameter("Lowerbound", ParameterType.Int32, 18, 5, 40, 5); _ub = AddParameter("Upperbound", ParameterType.Int32, 40, 20, 125, 5); _length = AddParameter("Length", ParameterType.Int32, 54, 30, 60, 1); } public override void Initialize(BarHistory bars) { TimeSeries ds = bars.Close; int ubound = _ub.AsInt; if (ubound > ds.Count) ubound = ds.Count; //initialize raster series SetPaneDrawingOptions("GrSp", 20); int nser = ubound - _lb.AsInt + 1; TimeSeries[] Raster = new TimeSeries[nser]; for (int n = 0; n < nser; n++) { Raster[n] = new TimeSeries(bars.DateTimes, n + _lb.AsInt); PlotTimeSeriesLine(Raster[n], "", "GrSp", WLColor.Black, 8, suppressLabels:true); } double[] XX = new double[_length.AsInt]; double[] coef = new double[_length.AsInt]; double[,] Pwr = new double[nser, 2]; int L1 = _length.AsInt - 1; double Mu = 1.0 / _length.AsInt; TimeSeries HP = new HighPass(ds, ubound); TimeSeries LP = new SuperSmoother(HP, _lb.AsInt); TimeSeries Peak = new TimeSeries(ds.DateTimes, 0.1); TimeSeries Signal = new TimeSeries(ds.DateTimes, 0); for (int bar = Math.Max(ubound, _length.AsInt); bar < ds.Count; bar++) { Peak[bar] = 0.991 * Peak[bar - 1]; if (Math.Abs(LP[bar]) > Peak[bar]) Peak[bar] = Math.Abs(LP[bar]); Signal[bar] = Peak[bar] != 0 ? LP[bar] / Peak[bar] : Signal[bar - 1]; for (int count = 0; count < _length.AsInt; count++) XX[count] = Signal[bar - (L1 - count)]; double XBar = 0; for (int count = 0; count < _length.AsInt; count++) XBar += XX[L1 - count] * coef[count]; for (int count = 0; count < _length.AsInt; count++) coef[count] += Mu * (XX[L1] - XBar) * XX[L1 - count]; //instantaneous frequency for (int pidx = 0; pidx < nser; pidx++) { double period = pidx + _lb.AsInt; Pwr[pidx, 1] = Pwr[pidx, 0]; double real = 0; double imag = 0; for (int count = 0; count < _length.AsInt; count++) { real += coef[count] * Math.Cos(2 * Math.PI * count / period); imag += coef[count] * Math.Sin(2 * Math.PI * count / period); } double denom = (1 - real) * (1 - real) + imag * imag; Pwr[pidx, 0] = 0.1 / denom + 0.9 * Pwr[pidx, 1]; } double maxPwr = 0; for (int pidx = 0; pidx < nser; pidx++) if (Pwr[pidx, 0] > maxPwr) maxPwr = Pwr[pidx, 0]; for (int pidx = 0; pidx < nser; pidx++) if (maxPwr != 0) Pwr[pidx, 0] = Pwr[pidx, 0] / maxPwr; //convert power to RGB color for (int pidx = 0; pidx < nser; pidx++) { double clr1 = Pwr[pidx, 0] >= 0.5 ? 255 : 255 * 2 * Pwr[pidx, 0]; double clr2 = Pwr[pidx, 0] >= 0.5 ? 255 * (2 * Pwr[pidx, 0] - 1) : 0; SetSeriesBarColor(Raster[pidx], bar, WLColor.FromRgb((byte)clr1, (byte)clr2, 0)); } } //Plot the Dominant Cycle GriffithsDC gdc = GriffithsDC.Series(ds, _lb.AsInt, _ub.AsInt, _length.AsInt); PlotTimeSeriesLine(gdc, gdc.Description, "GrSp", WLColor.WhiteSmoke, 4); } public override void Execute(BarHistory bars, int idx) { } } }
The highpass, SuperSmoother, 2-pole predictor and Griffiths indicators presented in John Ehlers’ article in this issue, In “Linear Predictive Filters And Instantaneous Frequency,” can be easily implemented in NeuroShell Trader using NeuroShell Trader’s ability to call external dynamic linked libraries (DLLs). Dynamic linked libraries can be written in C, C++ and Power Basic.
After moving the code given in the article to your preferred compiler and creating a DLL, you can insert the resulting indicator as follows:
FIGURE 3: NEUROSHELL TRADER. This NeuroShell Trader chart shows the highpass, SuperSmoother, 2-pole predictor, Griffiths predictor, and Griffiths dominant cyle on a chart of the S&P Emini futures (ES).
Users of NeuroShell Trader can go to the Stocks & Commodities section of the NeuroShell Trader free technical support website to download a copy of this or any previous Traders’ Tips.
The TradingView Pine Script code presented here implements the Griffiths predictor, Griffiths dominant cycle indicator, and Griffiths spectrum, as discussed in John Ehlers’ article in this issue, “Linear Predictive Filters And Instantaneous Frequency.”
// TASC Issue: January 2025 // Article: Linear Predictive Filters And // Instantaneous Frequency // Article By: John F. Ehlers // Language: TradingView's Pine Scriptâ„¢ v5 // Provided By: PineCoders, for tradingview.com //@version=5 title ='TASC 2025.01 Linear Predictive Filters' stitle = 'LPF' indicator(title, stitle, false) //#region Inputs and Constants: // @variable Overload for plot `display.all`. DSPA = display.all // @variable Overload for plot `display.none`. DSPN = display.none // @variable Overload for plot style `plot.style_columns`. PSH = plot.style_columns // @variable Full Angle Rotation `360`. float FROT = 2.0 * math.pi // @variable Square Root of 2 `1.414`. float SQRT2 = math.sqrt(2.0) // @variable Perfect Cycle Test signal. // `Signal = math.sin(360.0 * bar_index / 30.0)`. float TS = math.sin(FROT * bar_index / 30.0) // @enum Indicator Display Choice Enumerator. enum eID SMP = 'Simple 2-Pole Predictor' GP = 'Griffiths Predictor' GS = 'Griffiths Spectrum' GD = 'Griffiths Dominant Cycle' GSD = 'Griffiths Spectrum and Dominant Cycle' // @variable Indicator Display Choice. eID iChoice = input.enum(eID.SMP, 'Select Indicator:') // @variable Use Test Signal. bool iTest = input.bool(false, 'Use Test Signal:') // Parameters: float Src = input.source(close, 'Source:') int LBound = input.int(18, 'Lower Bound:') int UBound = input.int(40, 'Upper Bound:') int Length = input.int(40, 'Length:') float Q = input.float(0.35, 'Simple Predictor Q:') int BarsF = input.int(2,'Griffiths Predictor Bars Forward:') //#endregion //#region Filter functions // @function High Pass Filter. HP (float Source, int Period) => float a0 = math.pi * math.sqrt(2.0) / Period float a1 = math.exp(-a0) float c2 = 2.0 * a1 * math.cos(a0) float c3 = -a1 * a1 float c1 = (1.0 + c2 - c3) * 0.25 float hp = 0.0 if bar_index >= 4 hp := c1 * (Source - 2.0 * Source[1] + Source[2]) + c2 * nz(hp[1]) + c3 * nz(hp[2]) hp // @function Super Smoother SS (float Source, int Period) => float a0 = math.pi * math.sqrt(2.0) / Period float a1 = math.exp(-a0) float c2 = 2.0 * a1 * math.cos(a0) float c3 = -a1 * a1 float c1 = 1.0 - c2 - c3 float ss = Source if bar_index >= 4 ss := c1 * ((Source + Source[1]) / 2.0) + c2 * nz(ss[1]) + c3 * nz(ss[2]) ss // @function Common function. CF (float Source=close, int LowerB=18, int UpperB=40, bool Test=false ) => float HP = HP(Source, UpperB) float LP = SS(HP, LowerB) float Peak = 0.1 Peak := .991 * nz(Peak[1]) if math.abs(LP) > Peak Peak := math.abs(LP) if Test TS else float Signal = 0.0 if Peak != 0.0 Signal := LP / Peak Signal //#endregion //#region Simple 2 Pole Predictor SP (float Source=close, int lengthHP=15, int lengthLP=30, float Q=0.35, bool Test=false) => float HP = HP(Source, lengthHP) float LP = SS(HP, lengthLP) float Signal = Test ? TS : LP float c0 = 1.0 , float c1 = 1.8 * Q , float c2 = -Q * Q float sum = 1.0 - c1 - c2 c0 := (c0 / sum) * Signal c1 := (c1 / sum) * Signal[1] c2 := (c2 / sum) * Signal[2] float Predict = c0 - c1 - c2 [Signal, Predict] //#endregion //#region Griffiths Predictor GP (float source=close, int lowerB=18, int upperB=40, int length=18, int barsF=2, bool Test=false) => float MU = 1.0 / length float Signal = CF(source, lowerB, upperB, Test) float[] XX = array.new<float>(length+1, 0.0) var float[] Coef = array.new<float>(length+1, 0.0) float XBar = 0.0 XX.set(length, Signal) for count = 1 to length - 1 XX.set(count, nz(Signal[length - count])) for count = 1 to length XBar += XX.get(length-count) * Coef.get(count) for count = 1 to length Coef.set(count, Coef.get(count) + MU * (XX.get(length) - XBar) * XX.get(length-count)) // Prediction float XPred = 0.0 for advance = 1 to barsF XPred := 0.0 for count = 1 to length XPred += XX.get(length+1-count)*Coef.get(count) for count = advance to length - advance XX.set(count, XX.get(count + 1)) for count = 1 to length - 1 XX.set(count, XX.get(count + 1)) XX.set(length, XPred) [Signal, XPred] //#endregion //#region Griffiths Spectrum GS (float source=close, int lowerB=10, int upperB=40, int length=40, bool Test=false) => int LP1 = length + 1 , float MU = 1.0 / length float Signal = CF(source, lowerB, upperB, Test) float[] XX = array.new<float>(LP1, 0.0) var float[] Coef = array.new<float>(LP1, 0.0) var matrix<float> Pwr = matrix.new<float>(LP1, 2, 0.0) float XBar = 0.0 XX.set(length, Signal) for count = 1 to length - 1 XX.set(count, nz(Signal[length - count])) for count = 1 to length XBar += XX.get(length-count) * Coef.get(count) for count = 1 to length Coef.set(count, Coef.get(count) + MU * (XX.get(length) - XBar) * XX.get(length-count)) // Instantaneous Frequency for period = lowerB to upperB Pwr.set(period, 1, Pwr.get(period, 0)) float re = 0.0 , float im = 0.0 for count = 1 to length float a0 = FROT * count / period re += Coef.get(count) * math.cos(a0) im += Coef.get(count) * math.sin(a0) denom = math.pow(1.0 - re, 2.0) + math.pow(im, 2.0) Pwr.set(period, 0, 0.1 / denom) float MaxPwr = Pwr.col(0).max() if MaxPwr != 0 for period = lowerB to upperB Pwr.set(period, 0, Pwr.get(period, 0) / MaxPwr) // Plot the Spectrum Colors color[] Spectrum = array.new<color>(100, #000000) for period = lowerB to upperB // Convert Power to RGB Color for display float p0 = Pwr.get(period, 0) float r = p0 >= 0.5 ? 255.0 : 255.0 * 2.0 * p0 float g = p0 >= 0.5 ? 255.0 * (2.0 * p0 - 1.0) : 0.0 Spectrum.set(period, color.rgb(r, g, 0.0)) Spectrum //#endregion //#region Griffiths Dominant Cycle GD (float source=close, int lowerB=18, int upperB=40, int length=40, bool Test=false) => int LP1 = length + 1 , float MU = 1.0 / length float Signal = CF(source, lowerB, upperB, Test) float[] XX = array.new<float>(LP1, 0.0) var float[] Coef = array.new<float>(LP1, 0.0) var matrix<float> Pwr = matrix.new<float>(LP1, 2, 0.0) float XBar = 0.0 XX.set(length, Signal) for count = 1 to length - 1 XX.set(count, nz(Signal[length - count])) for count = 1 to length XBar += XX.get(length-count) * Coef.get(count) for count = 1 to length Coef.set(count, Coef.get(count) + MU * (XX.get(length) - XBar) * XX.get(length-count)) // Instantaneous Frequency for period = lowerB to upperB Pwr.set(period, 1, Pwr.get(period, 0)) float re = 0.0 , float im = 0.0 for count = 1 to length float a0 = FROT * count / period re += Coef.get(count) * math.cos(a0) im += Coef.get(count) * math.sin(a0) denom = math.pow(1.0 - re, 2.0) + math.pow(im, 2.0) // float _p1 = Pwr.get(period, 1) Pwr.set(period, 0, 0.1 / denom)// + 0.9 * _p1) float MaxPwr = Pwr.col(0).max() float cycle = Pwr.col(0).indexof(MaxPwr) cycle := switch cycle >= cycle[1] + 2.0 => cycle[1] + 2.0 cycle <= cycle[1] - 2.0 => cycle[1] - 2.0 => cycle cycle //#endregion //#region Plots: // Indicator IO Conditionals: D0 = iChoice == eID.SMP ? DSPA : DSPN D1 = iChoice == eID.GP ? DSPA : DSPN D2 = iChoice == eID.GS or iChoice == eID.GSD ? DSPA : DSPN D3 = iChoice == eID.GD or iChoice == eID.GSD ? DSPA : DSPN // Simple 2-Pole Predictor [s1, p1] = SP(Src, LBound, UBound, 0.35, iTest) plot(s1, 'Signal', color.blue, display=D0) hline(0, display=D0) plot(p1, 'Predict', color.red, display=D0) // Griffiths Predictor [s2, p2] = GP(Src, LBound, UBound, Length, 2, iTest) plot(s2, 'Signal', color.blue, display=D1) hline(0, display=D1) plot(p2, 'Predict', color.red, display=D1) // Griffiths Spectrum SP = GS(Src, LBound, UBound, Length, iTest) plot(19, '', SP.get(18), 1, PSH, false, 18, display=D2) plot(20, '', SP.get(19), 1, PSH, false, 19, display=D2) plot(21, '', SP.get(20), 1, PSH, false, 20, display=D2) plot(22, '', SP.get(21), 1, PSH, false, 21, display=D2) plot(23, '', SP.get(22), 1, PSH, false, 22, display=D2) plot(24, '', SP.get(23), 1, PSH, false, 23, display=D2) plot(25, '', SP.get(24), 1, PSH, false, 24, display=D2) plot(26, '', SP.get(25), 1, PSH, false, 25, display=D2) plot(27, '', SP.get(26), 1, PSH, false, 26, display=D2) plot(28, '', SP.get(27), 1, PSH, false, 27, display=D2) plot(29, '', SP.get(28), 1, PSH, false, 28, display=D2) plot(30, '', SP.get(29), 1, PSH, false, 29, display=D2) plot(31, '', SP.get(30), 1, PSH, false, 30, display=D2) plot(32, '', SP.get(31), 1, PSH, false, 31, display=D2) plot(33, '', SP.get(32), 1, PSH, false, 32, display=D2) plot(34, '', SP.get(33), 1, PSH, false, 33, display=D2) plot(35, '', SP.get(34), 1, PSH, false, 34, display=D2) plot(36, '', SP.get(35), 1, PSH, false, 35, display=D2) plot(37, '', SP.get(36), 1, PSH, false, 36, display=D2) plot(38, '', SP.get(37), 1, PSH, false, 37, display=D2) plot(39, '', SP.get(38), 1, PSH, false, 38, display=D2) plot(40, '', SP.get(39), 1, PSH, false, 39, display=D2) // Griffiths Dominant Cycle cycle = GD(Src, LBound, UBound, Length, iTest) plot(cycle, 'Dominant Cycle', color.blue, 3, display=D3) //#endregion
The indicator is available on TradingView from the PineCodersTASC account: https://www.tradingview.com/u/PineCodersTASC/#published-scripts.
An example chart is shown in Figure 4.
FIGURE 4: TRADINGVIEW. Here is an example of the indicator and heatmap on a daily chart of the emini S&P 500 futures (ES).
In the article “Linear Predictive Filters And Instantaneous Frequency” in this issue, John Ehlers discusses some digital signal processing techniques and an approach using Griffiths spectrum. Several of the indicators discussed in the article are available for download at the following link for NinjaTrader 8:
www.ninjatrader.com/SC/January2025SCNT8.zipOnce the file is downloaded, you can import it into NinjaTrader 8 from within the control center by selecting Tools → Import → NinjaScript Add-On and then selecting the downloaded file for NinjaTrader 8.
You can review the source code in NinjaTrader 8 by selecting the menu New → NinjaScript Editor → Indicators folder from within the control center window and selecting the file.
NinjaScript uses compiled DLLs that run native, not interpreted, to provide you with the highest performance possible.