Page 2 of 2

Re: Tame the Whipsaws: Ridge Regression Regularized Moving Average (RRRMA)

Posted: Sun Sep 27, 2026 6:38 pm
by PTScalper
MetaTrader 5 (MQL5)

In MQL5, custom indicator price arrays default to chronological ordering (0 is the oldest historical bar, rates_total - 1 is the current forming tick). The indexing mirrors the C# logic perfectly.

Code: Select all

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDarkOrange
#property indicator_width1  2
#property indicator_label1  "Ridge MA"

input int    InpPeriod = 14;          // Regression Period (P)
input double InpBlendK = 0.5;         // Ridge Blend 'k' (0.0 to 1.0)

double RidgeBuffer[];
double Sxx;
double HalfP;

int OnInit()
{
    if(InpPeriod < 2) return INIT_PARAMETERS_INCORRECT;
    
    SetIndexBuffer(0, RidgeBuffer, INDICATOR_DATA);
    
    // Calculate sequence variance and the X-axis boundary
    Sxx = (InpPeriod * (MathPow(InpPeriod, 2) - 1)) / 12.0;
    HalfP = (InpPeriod - 1.0) / 2.0;
    
    return INIT_SUCCEEDED;
}

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
    if(rates_total < InpPeriod) return 0;
    
    // Start at the first calculable bar, or recalculate the last forming bar
    int limit = prev_calculated == 0 ? InpPeriod - 1 : prev_calculated - 1;
    
    for(int i = limit; i < rates_total; i++)
    {
        double sum_y = 0;
        double sum_xy = 0;
        
        for(int j = 0; j < InpPeriod; j++)
        {
            // price[i] is the newest bar in the current window iteration
            double y = price[i - j];
            sum_y += y;
            
            // Centered X-coordinate mapping
            double x_centered = HalfP - j;
            sum_xy += x_centered * y;
        }
        
        double sma = sum_y / InpPeriod;
        double slope = sum_xy / Sxx;
        
        // Apply the structural blend directly to the slope
        RidgeBuffer[i] = sma + (InpBlendK * slope * HalfP);
    }
    
    return rates_total;
}

Re: Tame the Whipsaws: Ridge Regression Regularized Moving Average (RRRMA)

Posted: Sun Sep 27, 2026 6:40 pm
by PTScalper
To execute this calculation in $O(1)$ time per bar, we must eliminate the inner loop by using a sliding window technique.

Instead of recalculating the entire regression window on every bar, we can derive the current bar's values by looking at the previous bar's values, adding the newest price, and subtracting the oldest price falling out of the window.

Handling Intra-bar Ticks

A critical trap when using running sums in trading platforms is state corruption during the forming bar. If a live tick updates the running sum, the next live tick will inherit corrupted data.To solve this, we store the $S_y$ and $W$ values in hidden data buffers. This guarantees that every tick on the forming bar correctly references the finalized sums from the previous closed bar.

Re: Tame the Whipsaws: Ridge Regression Regularized Moving Average (RRRMA)

Posted: Sun Sep 27, 2026 6:40 pm
by PTScalper
cTrader (C# cAlgo)

In cAlgo, we use internal IndicatorDataSeries to cache the running sums securely.

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Indicators;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class NormalizedRidgeMA : Indicator
    {
        [Parameter("Period (P)", DefaultValue = 14, MinValue = 2)]
        public int Period { get; set; }

        [Parameter("Ridge Blend (k)", DefaultValue = 0.5, MinValue = 0.0, MaxValue = 1.0)]
        public double K { get; set; }

        [Parameter("Source")]
        public DataSeries Source { get; set; }

        [Output("Ridge MA", LineColor = "Orange", Thickness = 2)]
        public IndicatorDataSeries Result { get; set; }

        // Hidden buffers to store running sums
        private IndicatorDataSeries _sumY;
        private IndicatorDataSeries _sumW;

        private double _sxx;
        private double _halfP;

        protected override void Initialize()
        {
            _sumY = CreateDataSeries();
            _sumW = CreateDataSeries();

            _sxx = (Period * (Math.Pow(Period, 2) - 1)) / 12.0;
            _halfP = (Period - 1.0) / 2.0;
        }

        public override void Calculate(int index)
        {
            if (index < Period - 1) return;

            double sum_y, sum_w;

            // O(P) initialization for the very first calculable bar
            if (index == Period - 1) 
            {
                sum_y = 0;
                sum_w = 0;
                for (int j = 0; j < Period; j++)
                {
                    double y = Source[index - j];
                    sum_y += y;
                    sum_w += j * y;
                }
            }
            // O(1) sliding window for all subsequent bars and ticks
            else 
            {
                double y_in = Source[index];
                double y_out = Source[index - Period];
                
                // Reference the finalized sums from the previous bar
                double prev_sum_y = _sumY[index - 1];
                double prev_sum_w = _sumW[index - 1];

                sum_w = prev_sum_w + prev_sum_y - (Period * y_out);
                sum_y = prev_sum_y + y_in - y_out;
            }

            // Cache the calculated sums for the next bar
            _sumY[index] = sum_y;
            _sumW[index] = sum_w;

            // Compute OLS components
            double sma = sum_y / Period;
            double sum_xy = (_halfP * sum_y) - sum_w;
            double slope = sum_xy / _sxx;

            Result[index] = sma + (K * slope * _halfP);
        }
    }
}

Re: Tame the Whipsaws: Ridge Regression Regularized Moving Average (RRRMA)

Posted: Sun Sep 27, 2026 6:40 pm
by PTScalper
MetaTrader 5 (MQL5)

In MQL5, we expand indicator_buffers to 3, but keep indicator_plots at 1. We map the calculation buffers using the INDICATOR_CALCULATIONS flag, which hides them from the Data Window and chart, but makes them persistent.

Code: Select all

#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots   1

#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDarkOrange
#property indicator_width1  2
#property indicator_label1  "Ridge MA"

input int    InpPeriod = 14;          // Regression Period (P)
input double InpBlendK = 0.5;         // Ridge Blend 'k' (0.0 to 1.0)

double RidgeBuffer[];
double SumYBuffer[];    // Hidden buffer for Y running sum
double SumWBuffer[];    // Hidden buffer for Weighted running sum

double Sxx;
double HalfP;

int OnInit()
{
    if(InpPeriod < 2) return INIT_PARAMETERS_INCORRECT;
    
    SetIndexBuffer(0, RidgeBuffer, INDICATOR_DATA);
    SetIndexBuffer(1, SumYBuffer,  INDICATOR_CALCULATIONS);
    SetIndexBuffer(2, SumWBuffer,  INDICATOR_CALCULATIONS);
    
    Sxx = (InpPeriod * (MathPow(InpPeriod, 2) - 1)) / 12.0;
    HalfP = (InpPeriod - 1.0) / 2.0;
    
    return INIT_SUCCEEDED;
}

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
    if(rates_total < InpPeriod) return 0;
    
    int limit = prev_calculated == 0 ? InpPeriod - 1 : prev_calculated - 1;
    
    for(int i = limit; i < rates_total; i++)
    {
        double sum_y, sum_w;
        
        // O(P) initialization for the very first calculable bar
        if(i == InpPeriod - 1)
        {
            sum_y = 0;
            sum_w = 0;
            for(int j = 0; j < InpPeriod; j++)
            {
                double y = price[i - j];
                sum_y += y;
                sum_w += j * y;
            }
        }
        // O(1) sliding window for all subsequent bars and ticks
        else
        {
            double y_in = price[i];
            double y_out = price[i - InpPeriod];
            
            // Reference the finalized sums from the previous closed bar
            double prev_sum_y = SumYBuffer[i - 1];
            double prev_sum_w = SumWBuffer[i - 1];
            
            sum_w = prev_sum_w + prev_sum_y - (InpPeriod * y_out);
            sum_y = prev_sum_y + y_in - y_out;
        }
        
        // Cache the calculated sums
        SumYBuffer[i] = sum_y;
        SumWBuffer[i] = sum_w;
        
        // Compute OLS components
        double sma = sum_y / InpPeriod;
        double sum_xy = (HalfP * sum_y) - sum_w;
        double slope = sum_xy / Sxx;
        
        RidgeBuffer[i] = sma + (InpBlendK * slope * HalfP);
    }
    
    return rates_total;
}