MQL4プログラミング 第8回 インジケーターを作成してみよう(勝率の表示)
今回は前回まで作った勝敗判定プログラムに修正を加えて勝率を表示できるようにしていきます。
この勝率表示が実現できると、今まで何となく「勝率高そうだな」とか感覚でとらえていた事がキッチリと数値化できるので大変便利です。
私もいろんな自作や外部のサインツールのテストをおこなう時に重宝しています。例えばユーチューブなどでバイナリー投資家の○×さんが投稿している手法で「勝率70%越え!爆益必至、口座凍結注意!」と銘打った動画の手法を忠実に作って勝率を出したら50%前後だったとか!曖昧さをなくして計測できるのでとても重要なツールが自作で作れるようになります。
では、今回のキモとなる勝率表示の関数をまず示します。
勝率表示の関数
//+------------------------------------------------------------------+
//| 勝率表示する関数
//+------------------------------------------------------------------+
void dispWinRate() {
int L1_win, L1_los;
double winRatio;
int maxCount = winCountMaxBar;
if (Bars < maxCount) {
maxCount = Bars;
}
L1_win = L1_los = 0;
for (int i = 0; i < maxCount; i++) {
// Low
if (Log1Down[i+1] != EMPTY_VALUE) {
if (OkSign[i] != EMPTY_VALUE) {
L1_win++;
}
else {
L1_los++;
}
}
// High
if (Log1Up[i+1] != EMPTY_VALUE) {
if (OkSign[i] != EMPTY_VALUE) {
L1_win++;
}
else {
L1_los++;
}
}
}
if (L1_win != 0 || L1_los != 0) {
winRatio = (double)L1_win / (L1_win + L1_los) * 100;
dispLabel(StringFormat("%2d勝%2d敗%.1f%%", L1_win, L1_los, winRatio), PREFIX + "lb_1", clrWhite, 10, 80);
}
else {
dispLabel(" 0勝% 0敗 0.0%", PREFIX + "lb_1", clrWhite, 10, 80);
}
}
上記の新しい関数は単純に勝ちを示すOkSign配列の有効数と負けを示す有効数をそれぞれ合計して最後に勝率を算出して画面上に表示する単純な内容です。少し注意する点は37行目で割り算をしますが分母が0になってしまうとエラーとなるのでそれを防ぐために36行目で勝数、負数両方が0でない事を確認したうえで割り算を実行する事です。
あと、38行目で算出した勝率を画面に表示する関数も今回追加したので下に示します。
画面に文字を表示する関数
//+------------------------------------------------------------------+
//| 画面上にラベルを表示する
//| 引数1 表示するテキスト文字列
//| 引数2 作成するオブジェクト名称
//| 引数3 表示するテキストのカラー
//| 引数4 表示する文字列のx位置
//| 引数5 表示する文字列のy位置
//| 引数6 表示する文字列のフォントサイズ(指定なしの場合はデフォルト値=12とする)
//+------------------------------------------------------------------+
void dispLabel(string text, string objName, color clrName, int x, int y, int fontSize = 12) {
ObjectCreate(objName, OBJ_LABEL, 0, 0, 0);
ObjectSetText(objName, text, fontSize, "Meiryo UI",clrName);
ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y);
}
これは画面に表示する文字はオブジェクト(Object)として作成するのでその定義を4行に渡って記述しています。
11行目:引数objNameの名称でラベルオブジェクト(OBJ_LABEL)を作成します。
12行目:作成したobjNameのオブジェクトに表示する文字列、フォントサイズ、フォント名、表示する色を指定します。
13行目:表示する文字列のx位置を指定します。
14行目:表示する文字列のy位置を指定します。
余談ですが今回追加した処理で文字列を表示したオブジェクトを見てみましょう。
チャート上でマウス右クリックで「表示中のライン等(b)」を選び該当するラベルをダブルクリックすると以下のようになります。
さて、以上の新規追加の関数について理解したところでソースコード全体を示します。
今回のソースコード全容
// prg-0008.mq4
#property copyright "Copyright 2024, smile-invest-blog.com"
#property link "https://www.smile-invest-blog.com/prg-0008/"
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1 Yellow
#property indicator_color2 Red
#property indicator_color3 Red
#property indicator_color4 White
#property indicator_width1 2
#property indicator_width2 2
#property indicator_width3 1
#property indicator_width4 1
#define PREFIX MQLInfoString(MQL_PROGRAM_NAME) + "_"
double Log1Up[]; // Highサイン用バッファ
double Log1Down[]; // Low サイン用バッファ
double OkSign[];
double NgSign[];
int realBars = 0;
int winCountMaxBar = Bars - 3; // 勝率計算の足本数
extern int shortMaPeriod = 10; // 短期移動平均線の期間
extern int longMaPeriod = 30; // 長期移動平均線の期間
extern int adxPeriod = 14; // ADXの期間
//+------------------------------------------------------------------+
//| initialization function
//+------------------------------------------------------------------+
int init() {
//---- indicators
SetIndexStyle(0, DRAW_ARROW, EMPTY);
SetIndexArrow(0, 233);
SetIndexBuffer(0, Log1Up);
SetIndexStyle(1, DRAW_ARROW, EMPTY);
SetIndexArrow(1, 234);
SetIndexBuffer(1, Log1Down);
SetIndexStyle(2, DRAW_ARROW, EMPTY);
SetIndexArrow(2, 161);
SetIndexBuffer(2, OkSign);
SetIndexStyle(3, DRAW_ARROW, EMPTY);
SetIndexArrow(3, 120);
SetIndexBuffer(3, NgSign);
//----
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| deinitialization function
//+------------------------------------------------------------------+
int deinit() {
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| start function
//+------------------------------------------------------------------+
int start() {
bool ret_hantei; // hantei関数の結果格納用
int counted_bars=IndicatorCounted();
int limit = Bars - counted_bars;
for(int i = limit - 1; i >= 0; i--) {
if(i == 0) {
Log1Down[i] = EMPTY_VALUE;
Log1Up[i] = EMPTY_VALUE;
OkSign[i] = EMPTY_VALUE;
NgSign[i] = EMPTY_VALUE;
ret_hantei = hantei(i); // 上下サイン表示の判定を関数化した
if (ret_hantei == true) { // hantei関数の結果がtrueの時
if (realBars < Bars) { // 1本のローソク足で1回だけ実行するための条件
realBars = Bars; // realBarsをBarsにする事で同じローソク足では1回だけ実行する
if (Log1Up[i] != EMPTY_VALUE) { // hantei関数内で上サイン表示と判定された
Alert(Symbol(), " *** High ***");
}
else if (Log1Down[i] != EMPTY_VALUE) { // hantei関数内で下サイン表示と判定された
Alert(Symbol(), " *** Low ***");
}
}
}
if (TaskPeriod()){
// 勝敗判定
winLossCheck(i);
}
}
if (i > 1) {
// 過去足のサインを表示する
hantei(i);
winLossCheck(i);
}
}
return(0);
}
//+------------------------------------------------------------------+
//| エントリーの条件判定関数
//| int i メイン関数からのiの値
//+------------------------------------------------------------------+
bool hantei(int i) {
double shortMaPrevious, longMaPrevious;
double shortMaCurrent, longMaCurrent;
double adx;
// 前回の移動平均値を取得
shortMaPrevious = iMA(NULL, 0, shortMaPeriod, 0, MODE_SMA, PRICE_CLOSE, i+1);
longMaPrevious = iMA(NULL, 0, longMaPeriod, 0, MODE_SMA, PRICE_CLOSE, i+1);
// 現在の移動平均値を取得
shortMaCurrent = iMA(NULL, 0, shortMaPeriod, 0, MODE_SMA, PRICE_CLOSE, i);
longMaCurrent = iMA(NULL, 0, longMaPeriod, 0, MODE_SMA, PRICE_CLOSE, i);
// 現在のADXベースライン値を取得する
adx = getAdx(MODE_MAIN, i);
if (shortMaPrevious < longMaPrevious && shortMaCurrent > longMaCurrent) {
// // 移動平均線のクロスを確認 → ゴールデンクロス
if (adx > 25) {
// adx値が25より大きい場合 → Highサイン
Log1Up[i] = Low[i] - 10 * Point;
return true;
}
}
else if (shortMaPrevious > longMaPrevious && shortMaCurrent < longMaCurrent) {
// 移動平均線のクロスを確認 → デッドクロス
if (adx > 25) {
// adx値が25より大きい場合 → Lowサイン
Log1Down[i] = High[i] + 10 * Point;
return true;
}
}
return false;
}
//+----------------------------------------------------------------------+
//| ADXの値を取得する
//| 引数1 MODE_MAIN(0):ベースライン,MODE_PLUSDI(1):+DIライン,MODE_MINUSDI(2):-DIライン
//| 引数2 シフト数
//+----------------------------------------------------------------------+
double getAdx(int mode, int i) {
double ret;
ret = iADX(
NULL, // 通貨ペア
0, // 時間軸
adxPeriod, // 平均期間
PRICE_CLOSE, // 適用価格
mode, // ラインインデックス
i // シフト
);
return ret;
}
//+------------------------------------------------------------------+
//| ローソク足確定時の処理
//+------------------------------------------------------------------+
bool TaskPeriod() {
static datetime s_lasttime; // 最後に記録した時間軸時間
// staticはこの関数が終了してもデータは保持される
datetime temptime = iTime( Symbol(), Period() ,0 ); // 現在の時間軸の時間取得
if ( temptime == s_lasttime ) { // 時間に変化が無い場合
return false; // 処理終了
}
s_lasttime = temptime; // 最後に記録した時間軸時間を保存
// ----- 処理はこれ以降に追加 -----------
return true; // 移動平均線のクロス判定
}
//+------------------------------------------------------------------+
//| 勝敗のサインを確定する
//| 引数1 シフト数
//+------------------------------------------------------------------+
void winLossCheck(int i) {
//printf("[%d] winLossCheck(%d) in ", __LINE__, i);
int counter;
counter = winCountMaxBar;
if (i > counter) {
return;
}
if (Log1Down[i+2] != EMPTY_VALUE) {
if (iOpen(NULL, 0, i+1) > iClose(NULL, 0, i+1)) {
OkSign[i+1] = High[i+1] + 5 * Point;
}
else {
NgSign[i+1] = High[i+1] + 5 * Point;
}
}
if (Log1Up[i+2] != EMPTY_VALUE) {
if (iOpen(NULL, 0, i+1) < iClose(NULL, 0, i+1)) {
OkSign[i+1] = Low[i+1] - 5 * Point;
}
else {
NgSign[i+1] = Low[i+1] - 5 * Point;
}
}
if (i == 0) {
dispWinRate();
}
}
//+------------------------------------------------------------------+
//| 勝率表示する関数
//+------------------------------------------------------------------+
void dispWinRate() {
int L1_win, L1_los;
double winRatio;
int maxCount = winCountMaxBar;
if (Bars < maxCount) {
maxCount = Bars;
}
L1_win = L1_los = 0;
for (int i = 0; i < maxCount; i++) {
// Low
if (Log1Down[i+1] != EMPTY_VALUE) {
if (OkSign[i] != EMPTY_VALUE) {
L1_win++;
}
else {
L1_los++;
}
}
// High
if (Log1Up[i+1] != EMPTY_VALUE) {
if (OkSign[i] != EMPTY_VALUE) {
L1_win++;
}
else {
L1_los++;
}
}
}
if (L1_win != 0 || L1_los != 0) {
winRatio = (double)L1_win / (L1_win + L1_los) * 100;
dispLabel(StringFormat("%2d勝%2d敗%.1f%%", L1_win, L1_los, winRatio), PREFIX + "lb_1", clrWhite, 10, 80);
}
else {
dispLabel(" 0勝% 0敗 0.0%", PREFIX + "lb_1", clrWhite, 10, 80);
}
}
//+------------------------------------------------------------------+
//| 画面上にラベルを表示する
//| 引数1 表示するテキスト文字列
//| 引数2 作成するオブジェクト名称
//| 引数3 表示するテキストのカラー
//| 引数4 表示する文字列のx位置
//| 引数5 表示する文字列のy位置
//| 引数6 表示する文字列のフォントサイズ(指定なしの場合はデフォルト値=12とする)
//+------------------------------------------------------------------+
void dispLabel(string text, string objName, color clrName, int x, int y, int fontSize = 12) {
ObjectCreate(objName, OBJ_LABEL, 0, 0, 0);
ObjectSetText(objName, text, fontSize, "Meiryo UI",clrName);
ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y);
}
ここでの追加説明はごく僅かです。それは
210行目:最新のロウソク足の勝ち負け判定の最後に勝率表示の関数を呼ぶ
ところですね。
今回の勝率表示の処理は、これからいろんな自作サインツールや外部のサインツールのテスト、解析に応用できる技なのでしっかりマスターしていただければと思います。