Advertisement
Optimize MetaTrader 4, MetaTrader 5, cTrader, and TradingView for speed. Discuss Level II Market Depth (DOM), custom hotkeys, and volume indicators.
PTScalper
Site Admin
Posts: 3166 Joined: Mon Jul 20, 2026 1:28 pm
Post
by PTScalper » Sun Sep 27, 2026 6:38 pm
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;
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping
PTScalper
Site Admin
Posts: 3166 Joined: Mon Jul 20, 2026 1:28 pm
Post
by PTScalper » Sun Sep 27, 2026 6:40 pm
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.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3166 Joined: Mon Jul 20, 2026 1:28 pm
Post
by PTScalper » Sun Sep 27, 2026 6:40 pm
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);
}
}
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3166 Joined: Mon Jul 20, 2026 1:28 pm
Post
by PTScalper » Sun Sep 27, 2026 6:40 pm
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;
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.