Advertisement IC Markets

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

Optimize MetaTrader 4, MetaTrader 5, cTrader, and TradingView for speed. Discuss Level II Market Depth (DOM), custom hotkeys, and volume indicators.
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

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

Post by FTtrader »

Hey everyone,

If you scalp on the M1 or M5 timeframes, you already know the ultimate moving average dilemma: you either get crushed by lag (SMA/EMA) or you get chopped to pieces by false signals and noise (LSMA/HMA).

Recently, I’ve been experimenting with applying machine learning regularization to traditional indicators, and I wanted to share a custom tool I coded: the Ridge Regression Regularized Moving Average (RRRMA).

How it Works:
A standard Linear Regression Moving Average (LSMA) calculates a line of best fit and plots the endpoint. It’s incredibly fast and has almost zero lag, but it overreacts to every single micro-spike, causing whipsaws.

RRRMA fixes this by applying an L2 penalty (Tikhonov regularization) to the regression slope. In plain English: we introduce a "Lambda" parameter that mathematically penalizes extreme changes in the MA's direction.

Lambda = 0: You get a standard, hyper-reactive LSMA.

Lambda = High (e.g., 500): The penalty is so high it flattens the slope, turning it into a smooth, traditional SMA.

The Sweet Spot (e.g., 20–100): You get the explosive responsiveness of an LSMA, but the regularization aggressively filters out the random tick-noise that usually triggers false entries.

How to Scalp with It:
I recommend pairing a fast RRRMA (Period 14, Lambda 30) with a slower one (Period 50, Lambda 100). Because the regularization strips out the noise, the crossovers are significantly cleaner than standard EMAs, keeping you on the right side of short-term momentum bursts without getting shaken out by a single erratic 1-minute candle.

Here is the source code for MT4. Just create a new custom indicator, paste this in, and compile.

Code: Select all

//+------------------------------------------------------------------+
//|                                                    Ridge_MA.mq4  |
//|                                      Ridge Regularized LSMA      |
//+------------------------------------------------------------------+
#property strict
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1 clrOrange
#property indicator_width1 2

input int InpPeriod = 14;      // MA Period
input double InpLambda = 50.0; // Ridge Penalty (Lambda)

double MABuffer[];

int OnInit() {
    SetIndexBuffer(0, MABuffer);
    SetIndexStyle(0, DRAW_LINE);
    IndicatorShortName("RRRMA(" + IntegerToString(InpPeriod) + ", " + DoubleToString(InpLambda, 1) + ")");
    return(INIT_SUCCEEDED);
}

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[]) {
                
    int limit = rates_total - prev_calculated;
    if(limit > 1) limit = rates_total - InpPeriod - 1;

    // Calculate constants for the regression denominator
    double x_mean = (InpPeriod - 1) / 2.0;
    
    // Sum of squared differences for X
    double sum_x2 = (InpPeriod * (MathPow(InpPeriod, 2) - 1)) / 12.0;
    
    // Apply L2 Regularization (Ridge) to the denominator
    double ridge_denom = sum_x2 + InpLambda; 

    for(int i = limit; i >= 0; i--) {
        double sum_y = 0;
        
        // Get the average price (Y mean) for the window
        for(int j = 0; j < InpPeriod; j++) {
            sum_y += iClose(Symbol(), 0, i + j);
        }
        double y_mean = sum_y / InpPeriod;

        // Calculate the regularized slope (Beta)
        double num = 0;
        for(int j = 0; j < InpPeriod; j++) {
            double y_val = iClose(Symbol(), 0, i + j);
            num += (j - x_mean) * (y_val - y_mean);
        }
        
        double beta = num / ridge_denom;
        
        // Forecast the current point (where x = 0 in our loop)
        MABuffer[i] = y_mean + beta * (0 - x_mean);
    }
    
    return(rates_total);
}
Play around with the Lambda settings on your favorite pairs and let me know what combinations work best for your sessions. Happy scalping!
Recommended broker for automated trading & scalping IC Markets
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

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

Post by FTtrader »

Here it is for MT5 traders :-)

One of the great things about compiling this in MT5 is that by using the price[] array in the OnCalculate function, you aren't forced to only use the Close price. When you load the indicator on your chart, the MT5 parameters box will let you apply it to the Open, High, Low, Median, or even the data of other indicators.

How to install in MT5:
Open MetaEditor in MT5 (press F4).

Go to File > New > Custom Indicator, and name it Ridge_MA.

Delete all the default template code.

Paste the code below, hit Compile (F7), and attach it to your chart.

Code: Select all

//+------------------------------------------------------------------+
//|                                                    Ridge_MA.mq5  |
//|                                      Ridge Regularized LSMA      |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1

// Plot formatting
#property indicator_label1  "RRRMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrOrange
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

input int    InpPeriod = 14;      // MA Period
input double InpLambda = 50.0;    // Ridge Penalty (Lambda)

// Indicator buffer
double MABuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Map the array to the indicator data buffer
   SetIndexBuffer(0, MABuffer, INDICATOR_DATA);
   
   // Set the name that appears in the top left of the subwindow
   IndicatorSetString(INDICATOR_SHORTNAME, "RRRMA(" + IntegerToString(InpPeriod) + ", " + DoubleToString(InpLambda, 1) + ")");
   
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   // Wait until we have enough candles to calculate the period
   if(rates_total < InpPeriod)
      return(0);

   // MT5 arrays are left-to-right by default. We set them as series 
   // so index [0] is the current active candle, matching MT4 logic.
   ArraySetAsSeries(price, true);
   ArraySetAsSeries(MABuffer, true);

   // Determine how many bars need calculating
   int limit = rates_total - prev_calculated;
   if(limit > rates_total - InpPeriod) 
       limit = rates_total - InpPeriod;

   // Calculate constants for the regression denominator
   double x_mean = (InpPeriod - 1) / 2.0;
   
   // Sum of squared differences for X
   double sum_x2 = (InpPeriod * (MathPow(InpPeriod, 2) - 1)) / 12.0;
   
   // Apply L2 Regularization (Ridge) to the denominator
   double ridge_denom = sum_x2 + InpLambda; 

   // Main calculation loop
   for(int i = limit; i >= 0 && !IsStopped(); i--)
     {
      double sum_y = 0;
      
      // Get the average price (Y mean) for the lookback window
      for(int j = 0; j < InpPeriod; j++)
        {
         sum_y += price[i + j];
        }
      double y_mean = sum_y / InpPeriod;

      // Calculate the regularized slope (Beta)
      double num = 0;
      for(int j = 0; j < InpPeriod; j++)
        {
         double y_val = price[i + j];
         num += (j - x_mean) * (y_val - y_mean);
        }
      
      double beta = num / ridge_denom;
      
      // Forecast the current point (where x = 0 in our loop)
      MABuffer[i] = y_mean + beta * (0 - x_mean);
     }
   
   return(rates_total);
  }
//+------------------------------------------------------------------+
FTtrader
Posts: 954
Joined: Mon Aug 03, 2026 2:43 pm

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

Post by FTtrader »

And finally for IC traders/scalpers in Ctrader:

Moving over to cTrader is a massive step up, especially since the cAlgo environment runs on standard C# and the .NET framework. This gives you a much cleaner, event-driven architecture to work with compared to MQL's procedural loops.

Because cTrader's Calculate method is called on every single tick for the current index, we can heavily optimize this indicator by caching the heavy mathematical constants inside the Initialize method. This keeps the CPU overhead extremely light, which is crucial if you are running this on low-latency ECN feeds for quick scalping.

How to install in cTrader:
1.) Open the Automate tab (cAlgo) in cTrader.

2.) Click New > Indicator and name it RidgeRegressionMA.

3.) Paste the C# code below over the default template.

4.) Click Build (or press Ctrl+B).

Code: Select all

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

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class RidgeRegressionMA : Indicator
    {
        // By using DataSeries, you can apply this to Open, High, Low, Close, or even other indicators
        [Parameter("Source")]
        public DataSeries Source { get; set; }

        [Parameter("Period", DefaultValue = 14, MinValue = 2)]
        public int Period { get; set; }

        [Parameter("Lambda (Penalty)", DefaultValue = 50.0, MinValue = 0.0)]
        public double Lambda { get; set; }

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

        // Cached variables for performance
        private double _xMean;
        private double _ridgeDenom;

        protected override void Initialize()
        {
            // Pre-calculate constants here so we don't waste CPU cycles on every tick
            _xMean = (Period - 1) / 2.0;
            
            // Sum of squared differences for X
            double sumX2 = (Period * (Math.Pow(Period, 2) - 1)) / 12.0;
            
            // Apply L2 Regularization (Ridge) to the denominator
            _ridgeDenom = sumX2 + Lambda;
        }

        public override void Calculate(int index)
        {
            // Wait until we have enough historical data
            if (index < Period)
                return;

            double sumY = 0.0;

            // 1. Get the average price (Y mean) for the lookback window
            for (int j = 0; j < Period; j++)
            {
                // In cTrader, index is the current bar, and we look back 'j' bars
                sumY += Source[index - j];
            }
            
            double yMean = sumY / Period;
            double num = 0.0;

            // 2. Calculate the regularized slope (Beta)
            for (int j = 0; j < Period; j++)
            {
                double yVal = Source[index - j];
                num += (j - _xMean) * (yVal - yMean);
            }

            double beta = num / _ridgeDenom;

            // 3. Forecast the endpoint (where x = 0 in our loop context)
            Result[index] = yMean + beta * (0 - _xMean);
        }
    }
}
Like the MT5 version, utilizing the DataSeries Source parameter natively exposes the drop-down menu in cTrader's UI, allowing you to feed RSI, MACD, or any custom series directly into the regularized calculation without needing to rewrite the logic.

Let me know, if it was usefull for you. Happy trading, scalping :-)
Take a care.
LondonScalper
Posts: 770
Joined: Sat Sep 05, 2026 7:54 am

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

Post by LondonScalper »

FTtrader wrote:you either get crushed by lag (SMA/EMA) or you get chopped to pieces by false signals and noise (LSMA/HMA).
Ridge-style shrinkage on a fast fit is a reasonable idea — same family as “slow down when the fit is unstable.”

I have not run your RRRMA specifically, but the desk problem it aims at is familiar: zero-lag lines that quiver through London’s first hour. Any regularisation that dampens micro-spikes helps as a filter; I would still avoid crossing it for automatic entries without structure or session context.

Test suggestion: measure whipsaw rate and average hold-time against LSMA on the same tickets, costs included. If it only looks prettier on the chart, it is not an edge yet.

Are you using RRRMA as bias only, or as a primary cross signal on M1?
PropScalpDesk
Posts: 364
Joined: Sat Sep 19, 2026 7:50 pm

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

Post by PropScalpDesk »

Worked through the formula, and there's a neat simplification. Because lambda is only added to the denominator, the ridge slope is always the OLS slope times a fixed factor k = Sxx / (Sxx + λ), where Sxx = P(P²−1)/12. The plotted value then comes out as SMA + k × (LSMA − SMA), i.e. a constant blend of the two lines.

That matters for the suggested settings, because Sxx grows with the cube of the period:

P=14, λ=30: k ≈ 0.88, so it's mostly LSMA
P=14, λ=500: k ≈ 0.31, still a third of the way to LSMA, not a flat SMA
P=50, λ=100: Sxx is about 10,400, so k ≈ 0.99 and the slow line is effectively a plain LSMA

So the 14/30 against 50/100 cross is closer to an LSMA crossover than the description implies. If you want lambda to feel the same across periods, parameterise it as k directly (for example 0.5) and derive λ = Sxx(1−k)/k inside OnInit.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

PropScalpDesk wrote: Sun Sep 27, 2026 12:20 pm Worked through the formula, and there's a neat simplification. Because lambda is only added to the denominator, the ridge slope is always the OLS slope times a fixed factor k = Sxx / (Sxx + λ), where Sxx = P(P²−1)/12. The plotted value then comes out as SMA + k × (LSMA − SMA), i.e. a constant blend of the two lines.

That matters for the suggested settings, because Sxx grows with the cube of the period:

P=14, λ=30: k ≈ 0.88, so it's mostly LSMA
P=14, λ=500: k ≈ 0.31, still a third of the way to LSMA, not a flat SMA
P=50, λ=100: Sxx is about 10,400, so k ≈ 0.99 and the slow line is effectively a plain LSMA

So the 14/30 against 50/100 cross is closer to an LSMA crossover than the description implies. If you want lambda to feel the same across periods, parameterise it as k directly (for example 0.5) and derive λ = Sxx(1−k)/k inside OnInit.
Hi PropScalpDesk,

This is a mathematically precise and highly consequential observation. You have accurately identified a classic flaw in applying machine learning regularization directly to time-series indicators without normalizing for the window size: scale invariance.

Because the independent variable in a moving average regression is simply an integer sequence of time $x \in \{1, 2, \dots, P\}$, its variance is not static. As you noted, the sum of squared deviations $S_{xx}$ for a uniform discrete distribution grows cubically at $O(P^3)$.

Your arithmetic is flawless, and it exposes exactly why a fixed $\lambda$ parameter breaks down across multiple timeframes.Here is an extended breakdown of why this parametrization matters for algorithmic design and how to effectively implement your solution.The Interpolation MechanicBy expressing the Ridge slope as a scaled version of the OLS slope, you have proven that a Ridge Regression Moving Average is just a linear interpolation between a Simple Moving Average and a Least Squares Moving Average.

The Crossover Distortion

This structural flaw creates dangerous distortions in moving average crossover strategies. The fundamental premise of a fast/slow crossover is that the fast MA carries less lag than the slow MA.
However, under a fixed-$\lambda$ Ridge MA, the lag reduction is unevenly applied:

The fast line ($P=14, \lambda=30 \implies k=0.88$) still experiences a 12% penalty to its slope, anchoring it slightly toward the lagging SMA.
The slow line ($P=50, \lambda=100 \implies k=0.99$) experiences effectively zero penalty, behaving almost exactly like an ultra-responsive LSMA.

In environments with sudden volatility spikes, this distortion can cause the "slow" line to react to new price data faster than the "fast" line, resulting in inverted logic and false crossover signals.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

Implementing the $k$-Parameterization

Your solution to parameterize the indicator by the structural blend $k$ rather than the raw penalty $\lambda$ is exactly how this should be engineered in MQL or C#.

By exposing $k$ to the user, you ensure that the behavior of the indicator remains identical regardless of the period $P$. A $k=0.5$ setting will always yield an indicator perfectly bisecting the SMA and LSMA, whether $P=10$ or $P=200$.

Here is how this architecture looks when translated into OnInit() for an MQL5 indicator or a cAlgo bot:

Code: Select all

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

double Sxx;
double Lambda;

int OnInit()
{
    if(InpPeriod < 2) return INIT_PARAMETERS_INCORRECT;
    
    // Cap k to prevent division by zero or negative logic
    double k = MathMax(0.0001, MathMin(1.0, InpBlendK));
    
    // Calculate the inherent variance of the time sequence
    Sxx = (InpPeriod * (MathPow(InpPeriod, 2) - 1)) / 12.0;
    
    // Reverse-engineer the required penalty to achieve the desired blend
    if (k >= 1.0) {
        Lambda = 0; // Pure LSMA
    } else {
        Lambda = Sxx * (1.0 - k) / k; 
    }
    
    return INIT_SUCCEEDED;
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

This approach not only stabilizes the indicator's behavior across multiple chart timeframes, but it makes the inputs much easier to optimize in a strategy tester. An optimizer iterating a normalized $k$ variable between 0.1 and 1.0 will map a clean, linear behavioral space, whereas iterating a raw $\lambda$ from 0 to 1000 will result in a highly skewed, nonlinear optimization surface that is heavily dependent on the chosen period.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

By parameterizing the indicator directly with $k$, you can bypass computing $\lambda$ entirely. Because the Ridge slope is mathematically identical to the Ordinary Least Squares (OLS) slope multiplied by $k$, the indicator logic simplifies to three efficient steps per bar:

1.) Calculate the Simple Moving Average (SMA).

2.) Calculate the unpenalized OLS slope using a centered X-axis (which eliminates intercept calculations).

3.) Apply the structural blend: Ridge MA = SMA + k * Slope * ((P-1)/2).

This elegant math handles $k=0$ (pure SMA) and $k=1$ (pure LSMA) flawlessly without division-by-zero risks. Below are the complete, compilable implementations for both C# (cAlgo) and MQL5.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post by PTScalper »

cTrader (C# cAlgo)

In cAlgo, data series are chronologically indexed, making the look-back loop straightforward.

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; }

        private double _sxx;
        private double _halfP;

        protected override void Initialize()
        {
            // Calculate sequence variance and the X-axis boundary
            _sxx = (Period * (Math.Pow(Period, 2) - 1)) / 12.0;
            _halfP = (Period - 1.0) / 2.0;
        }

        public override void Calculate(int index)
        {
            // Ensure enough history exists
            if (index < Period - 1) return;

            double sumY = 0;
            double sumXY = 0;

            for (int j = 0; j < Period; j++)
            {
                // Source[index] is the newest bar in the window
                double y = Source[index - j];
                sumY += y;
                
                // Centered X-coordinate mapping: 
                // j=0 (newest) becomes +HalfP, j=P-1 (oldest) becomes -HalfP
                double xCentered = _halfP - j;
                sumXY += xCentered * y;
            }

            double sma = sumY / Period;
            double slope = sumXY / _sxx;

            // Apply the structural blend directly to the slope
            Result[index] = sma + (K * slope * _halfP);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply