以下に、コードを追記いたします。ご参照ください。
//+------------------------------------------------------------------+ //| TEST_SMA_RING3.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Ring Buffer Based Simple Moving Average (SMA) | //| - リングバッファ方式で実装されたSMA | //+------------------------------------------------------------------+ // ティックでの描画で数値がずれる誤差対策済み #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; }
英語フォーラムに投稿されたプログラムを確認しました。
「新しいバーができた瞬間だけ」とそれ以外に分ける必要があるのですか?
出来高の計算ではこのようにする場合もありますが、価格の場合は必要ないように思います。
double newClose = close[i];
この場合、新しいティックデータが入って来ても時間内であればclose[i]の値が変化するだけで、i の数値が変わるわけではありません。
ですからrates_totalの値が1つ増えたらringBufferのインデックスも1つ増やせばいいだけのように思うのですが、何か別の思惑があるのですか?
それと、別に間違いではありませんがArraySetAsSeriesはMT5ではfalseがデフォルトですから、いちいち指定する必要はないと思います。
英語フォーラムに投稿されたプログラムを確認しました。
「新しいバーができた瞬間だけ」とそれ以外に分ける必要があるのですか?
出来高の計算ではこのようにする場合もありますが、価格の場合は必要ないように思います。
この場合、新しいティックデータが入って来ても時間内であればclose[i]の値が変化するだけで、i の数値が変わるわけではありません。
ですからrates_totalの値が1つ増えたらringBufferのインデックスも1つ増やせばいいだけのように思うのですが、何か別の思惑があるのですか?
それと、別に間違いではありませんがArraySetAsSeriesはMT5ではfalseがデフォルトですから、いちいち指定する必要はないと思います。
Shino Unada # 様
英語フォーラムの方までご確認いただきありがとうございました。
大変ためになるご指摘をありがとうございます!
リングバッファの計算を ringIndex = (ringIndex + 1) % maPeriod; としていたことがそもそもの元凶でございまして、ご指摘いただくまでこの部分をまったく疑うことなくコード作成を進めておりました。
ringIndex = i % maPeriod; とすることで、新しいバーができた時をわける必要がなくなりました。
かなり簡素なコードだけで、しっかりと動くインジケーターができました。ありがとうございます。
ちなみに、ArraySetAsSeriesの表記は、自分の確認のためです。
新しいコードを貼り付けいたします。
//+------------------------------------------------------------------+ //| TEST_SMA_RING7.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Ring Buffer Based Simple Moving Average (SMA) | //| - リングバッファ方式で実装されたSMA | //+------------------------------------------------------------------+ // 新バー判定は必要なくなったので関数ごと除外 // 計算と変数をできるだけなくしメモリ保護と高速化 #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 validCount = 0; // 有効なデータ数(初期期間のため) //+------------------------------------------------------------------+ //| 初期化処理 | //+------------------------------------------------------------------+ 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++) { //ringIndex = (ringIndex + 1) % maPeriod; ← ※ わざわざ(ringIndex+1)とする必要はなし ringIndex = i % maPeriod; //検証用 printf("新しいバーの格納されたリングバッファのインデックスは = "+ ringIndex); double oldClose = ringBuffer[ringIndex]; //ringBuffer[ringIndex] = newClose; ringBuffer[ringIndex] = close[i]; runningSum = runningSum - oldClose + close[i]; if (validCount < maPeriod) { validCount++; continue; // 期間分データ取得まで計算&描画防止措置 } // 通常SMA smaBuffer[i] = runningSum / validCount; } return rates_total; } //+------------------------------------------------------------------+ //| 利用関数 | //+------------------------------------------------------------------+ // 新規バーのチェック関数 /* bool isNewBar(datetime NewBartime) { static datetime LastBarTime = 0; if(LastBarTime != NewBartime) { LastBarTime = NewBartime; return true; } else return false; } */

- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
私は現在、MT5におけるリングバッファ方式の理解と習熟のため、簡単な単純移動平均(SMA)指標をリングバッファで実装する練習を行っています。
処理の構成は大きく分けて「新しいバーが出現した瞬間」と「同一バー内でのティック処理」の2つであり、それを if ~ else 構文で切り分けています。
if 文では、新しいバーが出現したときのみ、一番古いインデックスに新しい値を代入する、というシンプルな処理を行っています。
一方、else 部分のリアルタイム処理では、「新しいバーができた際に代入された close 値のインデックス」を利用し、次のバーが生成されるまでの間、ティックごとに変化する close 値を上書きしながら計算を更新する仕組みにしています。
このインジケータをMT5に組み込んで確認したところ、過去のデータについては期待通りに両者が一致しました。しかし、リアルタイムでのティック処理(添付図の赤い垂直線以降)に入ると、描画ラインにずれが生じ始めます。
おそらく、同一バー内でのティック計算、すなわち else 部分の処理に誤りがあるのだと思いますが、現状では問題点を特定できずにいます。
コードのファイルを添付いたしますので、もし不適切な箇所や改善点があればご指摘いただけると幸いです。さらに、今後リアルタイムで描画されるインジケータ(メインチャート・サブチャートを問わず)をリングバッファ方式で作成する際に、よく起こりがちな誤りや注意点についてもご教示いただければ大変ありがたく存じます。
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:
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.