Using a ring buffer in MT5

 

I am currently practicing the implementation of a simple Simple Moving Average (SMA) indicator using a ring buffer in MT5, as part of my effort to better understand and become proficient with the ring buffer method.

The logic is divided into two main parts:

  1. When a new bar appears, and
  2. When processing ticks within the same bar.

These are separated using an if ~ else structure.
In the if branch, the oldest index in the ring buffer is replaced with the newest value only when a new bar is generated, which keeps the code straightforward.

In the else branch, for real-time processing, I use the index where the close value was assigned at the moment the new bar appeared. From that point until the next bar is generated, the close value is continuously updated with each incoming tick, and the calculation is refreshed accordingly.

When I tested this indicator in MT5, the historical part of the data matched perfectly as expected. However, once it entered real-time tick processing (after the red vertical line in the attached figure), the drawing of the line began to diverge. I suspect that the issue lies in the tick calculation within the same bar, i.e., the else part of the code, but I have not been able to identify the exact cause.

I have attached the code file for reference, and I would greatly appreciate it if you could point out any inappropriate sections or areas for improvement. In addition, I would be very grateful for any advice regarding common pitfalls or important considerations when developing real-time indicators (both in the main chart and subcharts) using the ring buffer method.

 
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_label1  "RingBufferSMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrOrange
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- パラメータ
input int maPeriod = 25;  // 移動平均の期間

//--- バッファ
double smaBuffer[];

//--- リングバッファ関連の変数
double ringBuffer[];      // 移動平均の元データを保持するリングバッファ
double runningSum = 0;    // 合計値(高速化のため)
int    ringIndex = 0;     // 現在のリングバッファ書き込み位置
int    ringNewest = 0;    // 同一バーのティックでの計算の時のみ使用する最新のリングバッファのインデックス番号
int    validCount = 0;    // 有効なデータ数(初期期間のため)
int    lastBarIndex = -1; // 最後に処理したバーのインデックス(新バー検出用)

//+------------------------------------------------------------------+
//| 初期化処理                                                       |
//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, smaBuffer, INDICATOR_DATA);
   ArraySetAsSeries(smaBuffer, false);  // 非時系列

   ArrayResize(ringBuffer, maPeriod);
   ArrayInitialize(ringBuffer, 0.0);

   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 start = (prev_calculated == 0) ? 0 : prev_calculated - 1; // パラメータの設定期間を考慮しなくても、リングバッファなので大丈夫。

   for (int i = start; i < rates_total; i++)
   {
      double newClose = close[i];
      int currentBar = i;
      
      if (currentBar != lastBarIndex)  // 新しいバーができた瞬間だけ以下処理を実行
      {
         // 新バー:リングバッファ更新+runningSum更新
         double oldClose = ringBuffer[ringIndex];
         ringBuffer[ringIndex] = newClose;
         runningSum = runningSum - oldClose + newClose;
         ringNewest = ringIndex; // 同一バー内ティック処理用に最新の値が入ったインデックス番号を保存
         
         // 検証用
         printf("新しいバーの格納されたリングバッファのインデックスは = "+ringNewest);

         ringIndex = (ringIndex + 1) % maPeriod;   // 次回新バー出現時新値を入れるリングインデックスを計算
         lastBarIndex = currentBar;

         if (validCount < maPeriod)
         {
            validCount++;
            continue;      // 期間分データ取得まで計算&描画防止措置
          }

         // 通常SMA
         smaBuffer[i] = runningSum / validCount;
      }
      else  // 同一バー内のティック:最新のnewCloseを仮反映し、暫定的なSMAを描画
      {
         // マイナスインデックス対策
         //int lastIndex = (ringIndex - 1 + maPeriod) % maPeriod;    高速化のため計算を省き、"ringNewest"変数を利用する仕様に変更
         
         // 検証用
         printf("ティック変更バーを上書きするリングバッファのインデックスは = "+ringNewest);

         double tempOldClose = ringBuffer[ringNewest];     // 前ティックで格納したCloseの値をtempOldCloseとする
         ringBuffer[ringNewest] = newClose;                // 更新値をリングバッファに上書き
         
         // リングバッファの計算:全合計から古い値を引いて新しい値を足す
         double tempSum = runningSum - tempOldClose + newClose;
         if (validCount < maPeriod)
            continue;      // 期間分データ取得まで計算&描画防止措置
         
         smaBuffer[i] = tempSum / validCount;
         // 検証用
         //printf("リングバッファで計算された合計値は? = "+tempSum);
         
      }
   }

   return rates_total;
}
 
テリー:

In your case, the discrepancy comes from how you detect the new bar and how you use the ring index within the ticks.

The simplest thing to do is: save only confirmed closes in the ring and use time[0] to detect new bars; during the tick, temporarily replace the last closed value with close[0] to calculate the live SMA.

 
Thank you very much for your prompt feedback.
I will first try rewriting the code using Time[] , as you suggested.
I also noticed a few points myself, so once I have revised the code, I will post it here again.
I would greatly appreciate it if you could provide me with further comments at that time as well.
Thank you once again for your kind support.
 

Hello,
Based on the points you kindly pointed out, I have revised the code.
First, I changed the method for detecting new bars into a bool function that uses time[] .

I also found that the reason why the indicator was not updated in real time was due to the use of the tempSum variable in the else part.
In the calculation method of the code I shared previously, the value of runningSum remained unchanged, which caused a discrepancy in the calculation.

After removing tempSum and using only runningSum , the indicator started to update correctly without any issues.

I am attaching the revised code here as well.
If you notice any vulnerabilities or possible improvements, I would greatly appreciate your feedback.

 
//+------------------------------------------------------------------+
//|                                               TEST_SMA_RING5.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|              Ring Buffer Based Simple Moving Average (SMA)       |
//|                 - リングバッファ方式で実装されたSMA                        |
//+------------------------------------------------------------------+
// 新バー判定をtime[]を利用し時系列化に対応。
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_label1  "RingBufferSMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrOrange
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

//--- パラメータ
input int maPeriod = 25;  // 移動平均の期間

//--- バッファ
double smaBuffer[];

//--- リングバッファ関連の変数
double ringBuffer[];      // 移動平均の元データを保持するリングバッファ
double runningSum = 0;    // 合計値(高速化のため)
int    ringIndex = 0;     // 現在のリングバッファ書き込み位置
int    ringNewest = 0;    // 同一バーのティックでの計算の時のみ使用する最新のリングバッファのインデックス番号
int    validCount = 0;    // 有効なデータ数(初期期間のため)
//int    lastBarIndex = -1; // 最後に処理したバーのインデックス(新バー検出用)

//+------------------------------------------------------------------+
//| 初期化処理                                                       |
//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, smaBuffer, INDICATOR_DATA);
   ArraySetAsSeries(smaBuffer, false);  // 非時系列

   ArrayResize(ringBuffer, maPeriod);
   ArrayInitialize(ringBuffer, 0.0);

   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[])
{  
   ArraySetAsSeries(time, false);  // 非時系列
   ArraySetAsSeries(close, false);  // 非時系列
   int start = (prev_calculated == 0) ? 0 : prev_calculated - 1; // パラメータの設定期間を考慮しなくても、リングバッファなので大丈夫。

   for (int i = start; i < rates_total; i++)
   {
      double newClose = close[i];
      //int currentBar = i;
      
      //if (currentBar != lastBarIndex)  // 新しいバーができた瞬間だけ以下処理を実行
      //if(isNewBar(close[i])==true) →timeと入力すべきところを、完全に間違えてcloseにしていた!!だから完全に間違えていた。
      if(isNewBar(time[i])==true)   // 新しいバーができた瞬間だけ以下処理を実行 
      {
         // 新バー:リングバッファ更新+runningSum更新
         double oldClose = ringBuffer[ringIndex];
         ringBuffer[ringIndex] = newClose;
         runningSum = runningSum - oldClose + newClose;
         ringNewest = ringIndex; // 同一バー内ティック処理用に最新の値が入ったインデックス番号を保存
         
         // 検証用
         printf("新しいバーの格納されたリングバッファのインデックスは = "+ringNewest);

         ringIndex = (ringIndex + 1) % maPeriod;   // 次回新バー出現時新値を入れるリングインデックスを計算
         //lastBarIndex = currentBar;

         if (validCount < maPeriod)
         {
            validCount++;
            continue;      // 期間分データ取得まで計算&描画防止措置
          }

         // 通常SMA
         smaBuffer[i] = runningSum / validCount;
      }
      else  // 同一バー内のティック:最新のnewCloseを仮反映し、暫定的なSMAを描画
      {
         // マイナスインデックス対策
         //int lastIndex = (ringIndex - 1 + maPeriod) % maPeriod;  高速化のため計算を省き、"ringNewest"変数を利用する仕様に変更
         
         // 検証用
         printf("ティック変更バーを上書きするリングバッファのインデックスは = "+ringNewest);

         double tempOldClose = ringBuffer[ringNewest];     // 前ティックで格納したCloseの値をtempOldCloseとする
         ringBuffer[ringNewest] = newClose;                // 更新値をリングバッファに上書き
         
         // リングバッファの計算:全合計から古い値を引いて新しい値を足す
         //double tempSum = runningSum - tempOldClose + newClose;   // →ここがNG
         runningSum = runningSum - tempOldClose + newClose;    //
         if (validCount < maPeriod)
            continue;      // 期間分データ取得まで計算&描画防止措置
         
         smaBuffer[i] = runningSum / validCount;
         // 検証用
         //printf("リングバッファで計算された合計値は? = "+runningSum);
         
      }
   }

   return rates_total;
}


//+------------------------------------------------------------------+
//| 利用関数                                                           |
//+------------------------------------------------------------------+

// 新規バーのチェック関数
bool isNewBar(datetime time)
{
 static datetime LastBarTime = 0;
 datetime NewBarTime = time;
 
 if(LastBarTime != NewBarTime)
  {
   LastBarTime = NewBarTime;
   return true;
   }
   
 else return false;
 }
 
 
 /*
 bool isNewBar(int shift)
{
 static datetime LastBarTime = 0;
 datetime NewBarTime = iTime(_Symbol,PERIOD_CURRENT,shift);
 
 if(LastBarTime != NewBarTime)
  {
   LastBarTime = NewBarTime;
   return true;
   }
   
 else return false;
 }
 */