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