AI SLOP! was: John Ehlers - Synthetic Oscillator conversion

Hello. I tried to convert this ehlers indicator via claude 4.6. I’m not sure if the conversion is correct. If anyone with expertise could kindly help/confirm this.
The formula was derived in 10 steps, which are explained here.
The Hann FIR low-pass filter is a non-recursive FIR filter: each tap depends only on the previous taps, not on its own past output. For this reason, it can be implemented within an AFL function using Ref() — there is no need for an indexed loop. Reduces high-frequency noise prior to IIR filtering.
The RMS (Root Mean Square) FUNCTION calculates the root mean square over Len bars. Used to normalise Real and Imag so that they have unit amplitude regardless of volatility. This is also non-recursive and has been implemented using Ref().
First step, pre-smoothing with a Hann filter (Length=12) attenuates noise prior to high-pass filtering. The ‘+ 0’ forces AFL to treat the result as a local array rather than as a reference to the function.
Second step, a 2-pole high-pass filter on HannPrice (Period=UpperBound) removes the trend components (low frequencies). It only passes cycles with a period <= UpperBound.
AFL NOTE: IIR (recursive) filters CANNOT be implemented within an AFL function because their output depends on previous values of the same output (HP[i-1], // HP[i-2]).
AFL functions do not maintain state between bars. For this reason, all IIR filters are implemented using for loops in the main body, with arrays initialised to Close*0.
NOTE on coefficients: the original Ehlers formula uses c1 = (1 + c2 - c3) / 4, but in AFL, with c3 negative, this results in hc1 > 1, making the filter unstable (it diverges). The correct formula for stability is: hc1 = (1 - hc2/2 + hc3) / 4
STEP 3 — 2-pole SuperSmoother on HP (Period=LowerBound) IIR low-pass filter that eliminates cycles with period < LowerBound. Combined with HighPass, it creates a bandpass filter: it only passes cycles between LowerBound and UpperBound.
STEP 4 — Real component normalised by RMS LP contains the bandpass signal. Dividing it by its RMS normalises it to an amplitude of ~1, which is necessary to correctly calculate the phase angle in the complex plane.
STEP 5 — IMAGINARY component normalised by RMS In signal analysis, the imaginary component of a sinusoidal signal is its derivative (rate of change). This is also normalised by its own RMS.
STEP 6 — Calculation of the Dominant Cycle (DC) Ehlers uses the derivative of the arctangent of (Imag/Real) to measure the instantaneous frequency of the signal. The formula avoids the discontinuity of the arctangent by working directly with the derivatives of Real and Imag. DC is then restricted to the interval [LowerBound, UpperBound].
STEP 7 — HighPass on Close (Period=Mid) Second high-pass filter applied directly to Close (not to HannPrice) to construct the BP signal.
STEP 8 — UltimateSmoother on HP2 (Period=Mid) Ehlers’ IIR filter with virtually zero latency. Combined with HP2, it creates a bandpass filter centred on Mid. BP is used to detect zero crossings and reset the phase of the synthetic oscillator. NOTE: for UltimateSmoother, hc1 uses the original formula (1 + uc2 - uc3) / 4 — this is correct here because the filter structure differs from HighPass. STEP 9 — Phase Accumulation The phase is incremented by 360/DC degrees per bar, simulating the rotation of a phasor at the DC period. At BP zero crossings, the phase is reset: - upward crossing: Phase = 180/DC (start of positive half-cycle) - downward crossing: Phase = 180 + 180/DC (start of negative half-cycle) warmup=100 ignores the first bars where the filters are not yet stabilised. Phase is kept within [0,360] using the modulus to prevent unlimited accumulation, which would cause an incorrect sin() in AFL. AFL NOTE: in EasyLanguage, ‘Phase = Phase + 360/DC’ is automatically recursive (it uses the value from the previous bar). In AFL, the explicit loop Phase[i-1] is required.
STEP 10 — Synthetic Oscillator Synth is the sine of the accumulated phase, so it oscillates between -1 and +1 at the dominant cycle’s frequency. Glitch correction eliminates visual discontinuities that occur when the phase is reset but the signal is still in the same quadrant.

SetBarsRequired( 500, 0 );

LowerBound = Param( "LowerBound", 15, 5, 50, 1 );
UpperBound = Param( "UpperBound", 25, 5, 100, 1 );

function Hann_func( Price, Len )
{
    Filt = 0;
    coef = 0;
    for( count = 1; count <= Len; count++ )
    {
        w    = 1 - cos( 360 * count / ( Len + 1 ) * 3.14159265 / 180 );
        Filt = Filt + w * Ref( Price, -( count - 1 ) );
        coef = coef + w;
    }
    return IIf( coef != 0, Filt / coef, Price );
}

function RMS_func( Price, Len )
{
    SumSq = 0;
    for( count = 0; count < Len; count++ )
        SumSq = SumSq + Ref( Price, -count ) * Ref( Price, -count );
    return IIf( SumSq != 0, sqrt( SumSq / Len ), 0.0001 );
}

HannPrice = Hann_func( Close, 12 ) + 0;

_a1 = exp( -1.414 * 3.14159265 / UpperBound );
hc2 = 2 * _a1 * cos( 1.414 * 180 / UpperBound * 3.14159265 / 180 );
hc3 = -_a1 * _a1;
hc1 = ( 1 - hc2/2 + hc3 ) / 4;

HP = Close * 0;
for( i = 2; i < BarCount; i++ )
    HP[i] = hc1 * ( HannPrice[i] - 2*HannPrice[i-1] + HannPrice[i-2] )
           + hc2 * HP[i-1] + hc3 * HP[i-2];

_Q    = exp( -1.414 * 3.14159265 / LowerBound );
ss_C1 = 2 * _Q * cos( 1.414 * 180 / LowerBound * 3.14159265 / 180 );
ss_C2 = _Q * _Q;
ss_A0 = ( 1 - ss_C1 + ss_C2 ) / 2;

LP = Close * 0;
for( i = 2; i < BarCount; i++ )
    LP[i] = ss_A0 * ( HP[i] + HP[i-1] )
           + ss_C1 * LP[i-1] - ss_C2 * LP[i-2];

RMSval = RMS_func( LP, 100 );
Real   = IIf( RMSval != 0, LP / RMSval, 0 );

RealROC = Real - Ref( Real, -1 );
QRMSval = RMS_func( RealROC, 100 );
Imag    = IIf( QRMSval != 0, RealROC / QRMSval, 0 );

Denom = ( Real - Ref(Real,-1) ) * Imag
      - ( Imag - Ref(Imag,-1) ) * Real;
DC    = IIf( Denom != 0,
             6.28 * ( Real*Real + Imag*Imag ) / Denom,
             ( LowerBound + UpperBound ) / 2 );
DC    = Max( LowerBound, Min( UpperBound, DC ) );

Mid = sqrt( LowerBound * UpperBound );

_a1m = exp( -1.414 * 3.14159265 / Mid );
hc2m = 2 * _a1m * cos( 1.414 * 180 / Mid * 3.14159265 / 180 );
hc3m = -_a1m * _a1m;
hc1m = ( 1 - hc2m/2 + hc3m ) / 4;

HP2 = Close * 0;
for( i = 2; i < BarCount; i++ )
    HP2[i] = hc1m * ( Close[i] - 2*Close[i-1] + Close[i-2] )
            + hc2m * HP2[i-1] + hc3m * HP2[i-2];

_a1u = exp( -1.414 * 3.14159265 / Mid );
uc2  = 2 * _a1u * cos( 1.414 * 180 / Mid * 3.14159265 / 180 );
uc3  = -_a1u * _a1u;
uc1  = ( 1 + uc2 - uc3 ) / 4;

BP = Close * 0;
for( i = 2; i < BarCount; i++ )
    BP[i] = ( 1 - uc1 ) * HP2[i]
           + ( 2*uc1 - uc2 ) * HP2[i-1]
           - ( uc1 + uc3 )   * HP2[i-2]
           + uc2 * BP[i-1]
           + uc3 * BP[i-2];

// -- Fase: proteggi DC e ignora barre iniziali instabili --
warmup = 100;
Phase = Close * 0;
for( i = warmup; i < BarCount; i++ )
{
    safeDC = Max( LowerBound, Min( UpperBound, DC[i] ) );
    Phase[i] = Phase[i-1] + 360 / safeDC;

    if( BP[i] > 0 AND BP[i-1] <= 0 )
        Phase[i] = 180 / safeDC;

    if( BP[i] < 0 AND BP[i-1] >= 0 )
        Phase[i] = 180 + ( 180 / safeDC );

    // Evita accumulo illimitato — tieni Phase in [0, 360]
    Phase[i] = Phase[i] - 360 * int( Phase[i] / 360 );
}

Synth = sin( Phase * 3.14159265 / 180 );

for( i = warmup+1; i < BarCount; i++ )
{
    if( Phase[i] > 0   AND Phase[i] < 90  AND Synth[i] < Synth[i-1] )
        Synth[i] = Synth[i-1];
    if( Phase[i] > 180 AND Phase[i] < 270 AND Synth[i] > Synth[i-1] )
        Synth[i] = Synth[i-1];
}

Plot( Synth, "Synth Osc", colorBlue,   styleLine | styleThick );
Plot( 0,     "Zero",      colorGrey40, styleLine );

PlotOHLC( 0, IIf( Synth > 0, Synth, 0 ), 0, IIf( Synth > 0, Synth, 0 ),
          "", colorGreen, styleCloud | styleNoLabel, Null, Null, Null, -1 );
PlotOHLC( 0, IIf( Synth < 0, Synth, 0 ), 0, IIf( Synth < 0, Synth, 0 ),
          "", colorRed,   styleCloud | styleNoLabel, Null, Null, Null, -1 );

SynthColor = IIf( LastValue(Synth) > 0, colorGreen, colorRed );
Title = EncodeColor( colorWhite ) + "Synthetic Oscillator (Ehlers)"
      + "  |  LB=" + LowerBound + "  UB=" + UpperBound
      + "  |  DC=" + WriteVal( LastValue(DC), 1.2 )
      + "  Phase=" + WriteVal( LastValue(Phase), 1.2 )
      + "  Synth=" + EncodeColor( SynthColor )
      + WriteVal( LastValue(Synth), 4.3 );

To make it more intuitive, you might want to reverse the (+)Positive and (-)Negative number(s), and the resulting plot in your oscillator.

1 Like
SetBarsRequired( 500, 0 );

LowerBound = Param( "LowerBound", 15, 5, 50, 1 );
UpperBound = Param( "UpperBound", 25, 5, 100, 1 );

function Hann_func( Price, Len )
{
    Filt = 0;
    coef = 0;
    for( count = 1; count <= Len; count++ )
    {
        w    = 1 - cos( 360 * count / ( Len + 1 ) * 3.14159265 / 180 );
        Filt = Filt + w * Ref( Price, -( count - 1 ) );
        coef = coef + w;
    }
    return IIf( coef != 0, Filt / coef, Price );
}

function RMS_func( Price, Len )
{
    SumSq = 0;
    for( count = 0; count < Len; count++ )
        SumSq = SumSq + Ref( Price, -count ) * Ref( Price, -count );
    return IIf( SumSq != 0, sqrt( SumSq / Len ), 0.0001 );
}

HannPrice = Hann_func( Close, 12 ) + 0;

_a1 = exp( -1.414 * 3.14159265 / UpperBound );
hc2 = 2 * _a1 * cos( 1.414 * 180 / UpperBound * 3.14159265 / 180 );
hc3 = -_a1 * _a1;
hc1 = ( 1 - hc2/2 + hc3 ) / 4;

HP = Close * 0;
for( i = 2; i < BarCount; i++ )
    HP[i] = hc1 * ( HannPrice[i] - 2*HannPrice[i-1] + HannPrice[i-2] )
           + hc2 * HP[i-1] + hc3 * HP[i-2];

_Q    = exp( -1.414 * 3.14159265 / LowerBound );
ss_C1 = 2 * _Q * cos( 1.414 * 180 / LowerBound * 3.14159265 / 180 );
ss_C2 = _Q * _Q;
ss_A0 = ( 1 - ss_C1 + ss_C2 ) / 2;

LP = Close * 0;
for( i = 2; i < BarCount; i++ )
    LP[i] = ss_A0 * ( HP[i] + HP[i-1] )
           + ss_C1 * LP[i-1] - ss_C2 * LP[i-2];

RMSval = RMS_func( LP, 100 );
Real   = IIf( RMSval != 0, LP / RMSval, 0 );

RealROC = Real - Ref( Real, -1 );
QRMSval = RMS_func( RealROC, 100 );
Imag    = IIf( QRMSval != 0, RealROC / QRMSval, 0 );

Denom = ( Real - Ref(Real,-1) ) * Imag
      - ( Imag - Ref(Imag,-1) ) * Real;
DC    = IIf( Denom != 0,
             6.28 * ( Real*Real + Imag*Imag ) / Denom,
             ( LowerBound + UpperBound ) / 2 );
DC    = Max( LowerBound, Min( UpperBound, DC ) );

Mid = sqrt( LowerBound * UpperBound );

_a1m = exp( -1.414 * 3.14159265 / Mid );
hc2m = 2 * _a1m * cos( 1.414 * 180 / Mid * 3.14159265 / 180 );
hc3m = -_a1m * _a1m;
hc1m = ( 1 - hc2m/2 + hc3m ) / 4;

HP2 = Close * 0;
for( i = 2; i < BarCount; i++ )
    HP2[i] = hc1m * ( Close[i] - 2*Close[i-1] + Close[i-2] )
            + hc2m * HP2[i-1] + hc3m * HP2[i-2];

_a1u = exp( -1.414 * 3.14159265 / Mid );
uc2  = 2 * _a1u * cos( 1.414 * 180 / Mid * 3.14159265 / 180 );
uc3  = -_a1u * _a1u;
uc1  = ( 1 + uc2 - uc3 ) / 4;

BP = Close * 0;
for( i = 2; i < BarCount; i++ )
    BP[i] = ( 1 - uc1 ) * HP2[i]
           + ( 2*uc1 - uc2 ) * HP2[i-1]
           - ( uc1 + uc3 )   * HP2[i-2]
           + uc2 * BP[i-1]
           + uc3 * BP[i-2];

warmup = 100;
Phase = Close * 0;
for( i = warmup; i < BarCount; i++ )
{
    safeDC = Max( LowerBound, Min( UpperBound, DC[i] ) );
    Phase[i] = Phase[i-1] + 360 / safeDC;

    if( BP[i] > 0 AND BP[i-1] <= 0 )
        Phase[i] = 180 / safeDC;

    if( BP[i] < 0 AND BP[i-1] >= 0 )
        Phase[i] = 180 + ( 180 / safeDC );

    Phase[i] = Phase[i] - 360 * int( Phase[i] / 360 );
}

Synth = sin( Phase * 3.14159265 / 180 );

for( i = warmup+1; i < BarCount; i++ )
{
    if( Phase[i] > 0   AND Phase[i] < 90  AND Synth[i] < Synth[i-1] )
        Synth[i] = Synth[i-1];
    if( Phase[i] > 180 AND Phase[i] < 270 AND Synth[i] > Synth[i-1] )
        Synth[i] = Synth[i-1];
}

// Inverti il segnale per visualizzazione intuitiva
Synth = Synth * -1;

Plot( Synth, "Synth Osc", colorBlue,   styleLine | styleThick );
Plot( 0,     "Zero",      colorGrey40, styleLine );

PlotOHLC( 0, IIf( Synth > 0, Synth, 0 ), 0, IIf( Synth > 0, Synth, 0 ),
          "", colorGreen, styleCloud | styleNoLabel, Null, Null, Null, -1 );
PlotOHLC( 0, IIf( Synth < 0, Synth, 0 ), 0, IIf( Synth < 0, Synth, 0 ),
          "", colorRed,   styleCloud | styleNoLabel, Null, Null, Null, -1 );

SynthColor = IIf( LastValue(Synth) > 0, colorGreen, colorRed );
Title = EncodeColor( colorWhite ) + "Synthetic Oscillator (Ehlers)"
      + "  |  LB=" + LowerBound + "  UB=" + UpperBound
      + "  |  DC=" + WriteVal( LastValue(DC), 1.2 )
      + "  Phase=" + WriteVal( LastValue(Phase), 1.2 )
      + "  Synth=" + EncodeColor( SynthColor )
      + WriteVal( LastValue(Synth), 4.3 );

Thanks Sean, you're right. Happy Easter!

Thank you, you as well!

As always this is typical AI slop that should be in fact prohibited. Don't use Claude. It is over hyped beyond measures.

This is what kind of pure nonsense this shitty AI produces:

AFL NOTE: IIR (recursive) filters CANNOT be implemented within an AFL function because their output depends on previous values of the same output (HP[i-1], // HP[i-2]).

Infinite impulse response filter IIR not only can be implemented in AFL but is actually is a built in function

So stop this bullshit and don't post AI generated slop.

If this "AI" actually did a SIMPLE WEB SEARCH, it would find that SuperSmoother was WRITTEN BY ME and it is present right in the User's Manual and it is just few lines of code, not novel-size slop written by AI (link is: AFL Function Reference - IIR )

Also nonsensical statements like this don't work:

Real  = IIf( RMSval != 0, LP / RMSval, 0 ); // WRONG AI SLOP!

This code DOES NOT prevent division when RMSval is zero because IIF is a function (evaluates all arguments), not control flow statement.

Correct code is just:

Real   = SafeDivide( LP, RMSval ); // actual safe division that handles case when RMSval is zero

Again, Claude knows NOTHING about proper AFL programming.

If you must use AI, use Gemini and feed it with Users' Manual before asking questions. I explained it here that the only way to get AI not saying nonsense is to feed it with grounding knowledge by uploading PDF with Users' Guide before asking any questions

3 Likes

Thanks for clarifying that, Tomasz. I’d uploaded the manual, but it seems Claude didn’t process it correctly. I won’t be using that IA anymore, as you suggested. I can see that SafeDivide is an excellent solution for avoiding division-by-zero issues – something my current version of Amibroker 6.20 doesn’t have. I’ll upgrade my licence as soon as possible, then. Thanks, and Happy Easter