RSIのロジックをそのまま流用した勝率計算機付きインジケーターのサンプルコードをMT4に入れてチャートを動かすと、通常の表示は問題なくても、しばらく相場を見ているうちに「配列の範囲外です」というエラーでインジケーターが止まってしまうことがあります。
原因は、勝敗判定の中で使っている配列インデックスの計算が、最新の数本のローソク足では負の値になってしまうケースを考慮していないことです。この記事では、RSIの逆張りシグナルをもとに勝率を自動集計するインジケーターの作り方を、実際にMT4で長時間動かしても止まらない状態まで直したコードとあわせて解説します。
勝率計算付きのインジケーター
日本でもBinary Option(バイナリーオプション)が流行っていますが、
勝率計算機付きというシステムが多く出回っています。
プログラミング初心者だと勝率計算というだけで難しいと思いがちですが、
実は超簡単にBinary Option(バイナリーオプション)のシステムを勝率計算させることが可能です。
Binary Option(バイナリーオプション)はエントリーの次のローソク足が確定した際に
そのレートよりも上にあるか下にあるかで勝ち負けが決まるシンプルなルールになっています。
このルールに従って勝率計算機を作っていきます。
今回は過去に使用したRSIのロジックを活用して作っていきたいと思います。
メタエディタ(MetaEditor)を立ち上げる
メタエディタ(MetaEditor)を立ち上げましょう。操作は以下に記載しています。
新しいカスタムインジケーターを作るので、メタエディタの「ファイル」→「新規作成」を選び、ウィザードで「Custom Indicator(テンプレートを使用)」を選択して「次へ」を押してください。名前を入力する画面が出るので、今回は名前を「Win-Rate」で作成します。

プロパティを記述する
プロパティを記述しましょう。まず今回用意するバッファは4つです。
そして矢印を青色と赤色の2つを用意して、マルバツ判定は黄色にします。それぞれ幅を2に設定します。
#property indicator_chart_window #property indicator_buffers 4//バッファ4つ #property indicator_color1 clrBlue//青色 #property indicator_color2 clrRed //赤色 #property indicator_color3 clrYellow//黄色 #property indicator_color4 clrYellow//黄色 #property indicator_width1 2//幅2 #property indicator_width2 2//幅2 #property indicator_width3 2 //幅2 #property indicator_width4 2//幅2
バッファを定義する
バッファを定義しましょう。
上矢印を「arrup[]」にして下矢印を「arrDown[]」にします。
また、マルを「maru[]」としてバツを「batu[]」にします。
double arrUp[], arrDown[];//上矢印と下矢印 double maru[],batu[];//マルとバツ
パラメーターを記述する
パラメーターを記述しましょう。
今回はRSIを活用したインジケーターですので、RSIの期間を決めることができる「RSI_Period」と
RSIの上限を「upfilter」でRSIの下限を「downfilter」に設定します。
input int RSI_Period = 14;//RSIの期間 input int upfilter = 70;//フィルター上限 input int downfilter = 30;//フィルター下限
グローバル変数を記述する
グローバル変数を記述しましょう。「totalCnt」でトレードの回数を格納します。
また、それぞれのトレードの勝ち負けの回数を「winCnt」と「loseCnt」にします。
Binary Option(バイナリーオプション)は基本的に終値で終了するので、
終値を判定するために「endPrise」を作ります。エントリーの次の足を判定制御用として「isJudge」にします。
勝利判定を「upEntryFlag」にして敗北判定を「downEntryFlag」にします。
int totalCnt = 0;//トレード回数 int winCnt = 0;//合計勝利回数 int loseCnt = 0;//合計敗北回数 double endPrise = 0;//終値の格納 bool isJudge = false;//次足判定制御用 bool upEntryFlag = false;//勝利判定フラグ bool downEntryFlag = false;//敗北判定フラグ
OnInit()関数を記述する
OnInit()関数を記述しましょう。それぞれのインデックスにバッファを紐付けさせていきます。
今回はすべて矢印なので矢印設定を行い、それぞれの表示形式を設定すれば完了です。
SetIndexBuffer(0, arrUp);//インデックス0にarrupを紐付け SetIndexBuffer(1, arrDown);//インデックス1にarrDownを紐付け SetIndexBuffer(2, maru);//インデックス2にmaruを紐付け SetIndexBuffer(3, batu);//インデックス3にbatuを紐付け SetIndexStyle(0, DRAW_ARROW, STYLE_SOLID);//インデックス0に矢印設定 SetIndexStyle(1, DRAW_ARROW, STYLE_SOLID);//インデックス1に矢印設定 SetIndexStyle(2, DRAW_ARROW, STYLE_SOLID);//インデックス2に矢印設定 SetIndexStyle(3, DRAW_ARROW, STYLE_SOLID);//インデックス3に矢印設定 SetIndexArrow(0, 233);//インデックス0に上矢印 SetIndexArrow(1, 234);//インデックス1に下矢印 SetIndexArrow(2, 161);//インデックス2にマル SetIndexArrow(3, 251);//インデックス3にバツ
OnDeinit()関数を記述する
OnDeinit()関数を記述しましょう。
インジケーターをチャートから削除した場合に勝率計算も消えるように
オブジェクトを全て削除できるように「ObjectsDeleteAll()」を設定しておきましょう。
ObjectsDeleteAll();//オブジェクトを全て削除
OnCalculate()関数を記述する
OnCalculate()関数を記述しましょう。
(元のロジックから2箇所変更しています。勝敗判定の条件に`i-2 >= 0`を追加したことと、カウンタのリセット条件を`prev_calculated == 0`に変更したことです。理由はこのあとの「配列の範囲外アクセスを防ぐ」で説明します。)
double dRsiNow,dRsiPast;//RSI変数
int limit = Bars-IndicatorCounted();
//チャート更新時の勝率計算バグを防ぐ処理
if(prev_calculated == 0)
{
totalCnt = 0;
winCnt = 0;
loseCnt = 0;
}
for(int i=0; i < limit; i++)
{
dRsiNow = iRSI(NULL,0,RSI_Period,PRICE_CLOSE,i);//現在の足のRSI
dRsiPast = iRSI(NULL,0,RSI_Period,PRICE_CLOSE,i+1);//1つ前の足のRSI
arrUp[i] = EMPTY_VALUE;//バッファの値が空
arrDown[i] = EMPTY_VALUE;//バッファの値が空
if(dRsiPast <= downfilter && dRsiNow >downfilter)//1つ前のRSIが30以下で現在のRSIが30を超えている
{
arrUp[i] = Low[i];//シグナルを安値にセットする
upEntryFlag = true;//エントリー確定時フラグを立てる
endPrise = iClose(NULL,0,i);//エントリー確定時の終値を格納
}
else if(dRsiPast >= upfilter && dRsiNow <upfilter)//1つ前のRSIが70以上で現在のRSIが70下回る
{
arrDown[i] = High[i];//シグナルを高値にセットする
downEntryFlag = true;//エントリー確定時フラグを立てる
endPrise = iClose(NULL,0,i);//エントリー確定時の終値を格納
}
//勝敗判定(配列の範囲外アクセスを防ぐため、i-2が0以上の時だけ判定する)
if(upEntryFlag && i-2 >= 0)
{
//isJudgeがfalseの場合、一回処理を飛ばす(次足で判定サインを出したい為)
if(isJudge)
{
//前足の終値よりも次足の終値の方が大きかった場合
if(endPrise < iClose(NULL,0,i-2))
{
maru[i-2] = iLow(NULL,0,i-2)-10*Point;//丸印を安値の1pips下に出す
upEntryFlag = false;
winCnt++;
}
else
{
batu[i-2] = iLow(NULL,0,i-2)-10*Point;//バツ印を安値の1pips下に出す。
upEntryFlag = false;
loseCnt++;
}
isJudge = false;
}
else
{
isJudge = true;
}
}
if(downEntryFlag && i-2 >= 0)
{
//isJudgeがfalseの場合、一回処理を飛ばす(次足で判定サインを出したい為)
if(isJudge)
{
//前足の終値よりも次足の終値の方が小さかった場合
if(endPrise > iClose(NULL,0,i-2))
{
//丸印を高値の1pips上に出す。(1*Point=0.1pips)
maru[i-2] = iHigh(NULL,0,i-2)+10*Point;
downEntryFlag = false;
winCnt++;
}
else
{
//バツ印を高値の1pips上に出す。(1*Point=0.1pips)
batu[i-2] = iHigh(NULL,0,i-2)+10*Point;
downEntryFlag = false;
loseCnt++;
}
isJudge = false;
}
else
{
isJudge = true;
}
}
}
WinRate();//勝率計算の関数
//--- return value of prev_calculated for next call
return(rates_total);
}
配列の範囲外アクセスを防ぐ
上のコードには、そのままだと途中でインジケーターが「配列の範囲外です(array out of range)」というエラーで止まってしまう欠陥がありました。
勝敗判定のあと、`maru[i-2]`や`batu[i-2]`のように、判定した足から2本前の足へ印を出す処理になっています。ここで使っている`i`は、MT4がチャートを開いた直後(過去の全バーをまとめて計算する1回目)は0からある程度大きな値まで動きますが、通常のティック更新(最新の1〜数本だけ再計算する2回目以降)では`i`が0や1にしかならないことがほとんどです。
つまり、`i`が0や1のタイミングで勝敗判定の条件が成立すると、`i-2`は-2や-1というマイナスの値になり、存在しない配列の要素にアクセスしようとしてエラーになります。
これを防ぐため、勝敗判定を行う`if(upEntryFlag)`と`if(downEntryFlag)`の条件に、`i-2 >= 0`という条件を追加しています。こうすることで、`i-2`がマイナスになるタイミングでは判定処理そのものをスキップし、次にプラスの`i`になったタイミングで改めて判定されるため、勝敗のカウント漏れも起きません。
あわせて、勝率カウンタをリセットする条件も直しています。元のコードは`if(limit == Bars – 1)`という条件でしたが、`limit`は`Bars – IndicatorCounted()`で計算されるため、チャートを開いた直後の1回目の計算では`IndicatorCounted()`が0になり、`limit`は`Bars`と一致します。つまり`limit == Bars – 1`は常に成り立たず、コメントの意図(チャートを開き直した時にカウンタをリセットする)が実際には一度も実行されないコードになっていました。MQL4の`OnCalculate()`には、1回目の計算かどうかを直接教えてくれる`prev_calculated`という引数(1回目は必ず0になる)が用意されているので、これを使う形に直しています。
WinRate()関数を記述する
WinRate()関数を記述しましょう。WinRate()関数では勝率計算を行っていきます。
まず、ObjectCreateで勝利計算のオブジェクトを作成していきます。
ObjectCreate("obj_judgeMonitor",OBJ_LABEL,0,0,0);//勝率計算のオブジェクト作成
次にサイズ設定ですが、左上にXサイズ10でYサイズ20で設置します。
ObjectSet("obj_judgeMonitor",OBJPROP_CORNER,CORNER_LEFT_UPPER);//左上に表示
ObjectSet("obj_judgeMonitor",OBJPROP_XDISTANCE,10);//X軸サイズ10
ObjectSet("obj_judgeMonitor",OBJPROP_YDISTANCE,20);//Y軸サイズ20
ここから勝率計算を行っていきます。
まず、totalCntにwinCntとloseCntの合計を代入します。
totalCnt = winCnt + loseCnt;//トータル
そして、勝利/トレード合計*100で勝率を計算していきます。
double rate = MathRound((double)winCnt / totalCnt * 100);//100%の割合を計算
このままだと、まだ一度も判定が行われていない状態(totalCntが0)でゼロ除算が起きて計算結果が不正な値(NaN相当)になってしまうため、totalCntが0の間は計算をスキップするようにしています。
そして、最後に表示させて完了です。
ObjectSetText("obj_judgeMonitor", "判定:"+(string)totalCnt+" 勝ち:"+(string)winCnt+" 勝率:"+(string)rate+"%", 11, "メイリオ", clrWhite);//勝率の表示
//勝率オブジェクト作成
ObjectCreate("obj_judgeMonitor",OBJ_LABEL,0,0,0);//勝率計算のオブジェクト作成
ObjectSet("obj_judgeMonitor",OBJPROP_CORNER,CORNER_LEFT_UPPER);//左上に表示
ObjectSet("obj_judgeMonitor",OBJPROP_XDISTANCE,10);//X軸サイズ10
ObjectSet("obj_judgeMonitor",OBJPROP_YDISTANCE,20);//Y軸サイズ20
//勝率計算
totalCnt = winCnt + loseCnt;//トータル
double rate = 0;
if(totalCnt > 0)//ゼロ除算防止
rate = MathRound((double)winCnt / totalCnt * 100);//100%の割合を計算
ObjectSetText("obj_judgeMonitor", "判定:"+(string)totalCnt+" 勝ち:"+(string)winCnt+" 勝率:"+(string)rate+"%", 11, "メイリオ", clrWhite);//勝率の表示
コンパイルする
コンパイルしましょう。しっかりとロジック通りに動いていれば完成です。

ソースコード全体
ここまでの修正をすべて反映した、実際にMetaEditorでコンパイルしてそのまま動かせる状態のコードです。
//+------------------------------------------------------------------+
//| Win-rate.mq4 |
//| Copyright 2020, FX-EA System Project Creator |
//| https://creator.fx-ea-system-project.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, FX-EA System Project Creator"
#property link "https://creator.fx-ea-system-project.com/"
#property version "1.01"
#property strict
/*プロパティ*/
#property indicator_chart_window
#property indicator_buffers 4//バッファ4つ
#property indicator_color1 clrBlue//青色
#property indicator_color2 clrRed //赤色
#property indicator_color3 clrYellow//黄色
#property indicator_color4 clrYellow//黄色
#property indicator_width1 2//幅2
#property indicator_width2 2//幅2
#property indicator_width3 2 //幅2
#property indicator_width4 2//幅2
/*バッファ*/
double arrUp[], arrDown[];//上矢印と下矢印
double maru[],batu[];//マルとバツ
/*パラメーター*/
input int RSI_Period = 14;//RSIの期間
input int upfilter = 70;//フィルター上限
input int downfilter = 30;//フィルター下限
/*グローバル変数*/
int totalCnt = 0;//トレード回数
int winCnt = 0;//合計勝利回数
int loseCnt = 0;//合計敗北回数
double endPrise = 0;//終値の格納
bool isJudge = false;//次足判定制御用
bool upEntryFlag = false;//勝利判定フラグ
bool downEntryFlag = false;//敗北判定フラグ
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- indicator buffers mapping
SetIndexBuffer(0, arrUp);//インデックス0にarrupを紐付け
SetIndexBuffer(1, arrDown);//インデックス1にarrDownを紐付け
SetIndexBuffer(2, maru);//インデックス2にmaruを紐付け
SetIndexBuffer(3, batu);//インデックス3にbatuを紐付け
SetIndexStyle(0, DRAW_ARROW, STYLE_SOLID);//インデックス0に矢印設定
SetIndexStyle(1, DRAW_ARROW, STYLE_SOLID);//インデックス1に矢印設定
SetIndexStyle(2, DRAW_ARROW, STYLE_SOLID);//インデックス2に矢印設定
SetIndexStyle(3, DRAW_ARROW, STYLE_SOLID);//インデックス3に矢印設定
SetIndexArrow(0, 233);//インデックス0に上矢印
SetIndexArrow(1, 234);//インデックス1に下矢印
SetIndexArrow(2, 161);//インデックス2にマル
SetIndexArrow(3, 251);//インデックス3にバツ
//---
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
ObjectsDeleteAll();//オブジェクトを全て削除
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
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[])
{
//---
double dRsiNow,dRsiPast;//RSI変数
int limit = Bars-IndicatorCounted();
//チャート更新時の勝率計算バグを防ぐ処理
if(prev_calculated == 0)
{
totalCnt = 0;
winCnt = 0;
loseCnt = 0;
}
for(int i=0; i < limit; i++)
{
dRsiNow = iRSI(NULL,0,RSI_Period,PRICE_CLOSE,i);//現在の足のRSI
dRsiPast = iRSI(NULL,0,RSI_Period,PRICE_CLOSE,i+1);//1つ前の足のRSI
arrUp[i] = EMPTY_VALUE;//バッファの値が空
arrDown[i] = EMPTY_VALUE;//バッファの値が空
if(dRsiPast <= downfilter && dRsiNow >downfilter)//1つ前のRSIが30以下で現在のRSIが30を超えている
{
arrUp[i] = Low[i];//シグナルを安値にセットする
upEntryFlag = true;//エントリー確定時フラグを立てる
endPrise = iClose(NULL,0,i);//エントリー確定時の終値を格納
}
else if(dRsiPast >= upfilter && dRsiNow <upfilter)//1つ前のRSIが70以上で現在のRSIが70下回る
{
arrDown[i] = High[i];//シグナルを高値にセットする
downEntryFlag = true;//エントリー確定時フラグを立てる
endPrise = iClose(NULL,0,i);//エントリー確定時の終値を格納
}
//勝敗判定(配列の範囲外アクセスを防ぐため、i-2が0以上の時だけ判定する)
if(upEntryFlag && i-2 >= 0)
{
//isJudgeがfalseの場合、一回処理を飛ばす(次足で判定サインを出したい為)
if(isJudge)
{
//前足の終値よりも次足の終値の方が大きかった場合
if(endPrise < iClose(NULL,0,i-2))
{
maru[i-2] = iLow(NULL,0,i-2)-10*Point;//丸印を安値の1pips下に出す
upEntryFlag = false;
winCnt++;
}
else
{
batu[i-2] = iLow(NULL,0,i-2)-10*Point;//バツ印を安値の1pips下に出す。
upEntryFlag = false;
loseCnt++;
}
isJudge = false;
}
else
{
isJudge = true;
}
}
if(downEntryFlag && i-2 >= 0)
{
//isJudgeがfalseの場合、一回処理を飛ばす(次足で判定サインを出したい為)
if(isJudge)
{
//前足の終値よりも次足の終値の方が小さかった場合
if(endPrise > iClose(NULL,0,i-2))
{
//丸印を高値の1pips上に出す。(1*Point=0.1pips)
maru[i-2] = iHigh(NULL,0,i-2)+10*Point;
downEntryFlag = false;
winCnt++;
}
else
{
//バツ印を高値の1pips上に出す。(1*Point=0.1pips)
batu[i-2] = iHigh(NULL,0,i-2)+10*Point;
downEntryFlag = false;
loseCnt++;
}
isJudge = false;
}
else
{
isJudge = true;
}
}
}
WinRate();//勝率計算の関数
//--- return value of prev_calculated for next call
return(rates_total);
}
//+------------------------------------------------------------------+
//| 勝率計算関数 |
//+------------------------------------------------------------------+
void WinRate()
{
//勝率オブジェクト作成
ObjectCreate("obj_judgeMonitor",OBJ_LABEL,0,0,0);//勝率計算のオブジェクト作成
ObjectSet("obj_judgeMonitor",OBJPROP_CORNER,CORNER_LEFT_UPPER);//左上に表示
ObjectSet("obj_judgeMonitor",OBJPROP_XDISTANCE,10);//X軸サイズ10
ObjectSet("obj_judgeMonitor",OBJPROP_YDISTANCE,20);//Y軸サイズ20
//勝率計算
totalCnt = winCnt + loseCnt;//トータル
double rate = 0;
if(totalCnt > 0)//ゼロ除算防止
rate = MathRound((double)winCnt / totalCnt * 100);//100%の割合を計算
ObjectSetText("obj_judgeMonitor", "判定:"+(string)totalCnt+" 勝ち:"+(string)winCnt+" 勝率:"+(string)rate+"%", 11, "メイリオ", clrWhite);//勝率の表示
}
まとめ
今回のソースコードを改良することで、異なるシステムを勝率計算させることが可能になります。配列の範囲外アクセスとゼロ除算という2つの実行時エラーの原因を直しているので、チャートを開きっぱなしにしても止まらずに使えます。
コメント