50歳手前の糖尿病メタボおっちゃんの色々悪あがき日記

老後の健康とお金のため・仕事をサボるため、あくせく悪あがきして生きています♪

とりあえず使えるようになる!メタトレーダー5でバックテスト~(5)単純な売買ルール「山越えで売り・谷越えで買い」を実装する。

f:id:edger_arkw:20200506100920p:plain

売買ルール

 今回は、エキスパート3号を改造して、過去の値動きをもとに売買するかどうかを判定する機能を追加します。
 売買ルールそのものは非常に簡単なルールとなっております。
 が、今回もプログラミングが・・・。
 がんばってください。
 

 

 

今回のステップ

・MT5を使おう<基礎編>
 □.MT5をインストールする。
    ↓
 □.何もしないEAを作り、動作させる。
    ↓
 □.何か適当なメッセージをログ出力してみる。
    ↓
 □.過去一定期の四本値を取得してみる。
    ↓
 □.単純な売買ルール「山越えで売り・谷越えで買い」を実装する。
    ↓
 □.オーダーを実装する。
    ↓
 □.バックテスト。

 

売買戦略

 実装する売買ルール「山超えで売り・谷越えで買い」について説明します。

 ①.一定期間内で一番最高値を「山」とします。
 ②.逆に同期間内で一番最安値を「谷」とします。
 ③.「山」「谷」間を「幅」とします。
 ④.「幅」が一定値よりも狭い場合はなにしません。
 ⑤.「山」から「幅」×0.1下がったところで「売り注文」します。
 ⑥.「谷」から「幅」×0.1上がったところで「買い注文」します。

 以下の図のようになります。

f:id:edger_arkw:20200510145012p:plain

山を越えて売り、谷を越えて買う


 こちらのルールは「5段階で評価する テクニカル指標の成績表 著者:矢口 新 出版社: パンローリング」を参考にいたしました。

https://www.panrolling.com/books/gr/gr84.html

 

実習

早速実習です。今回は前回作成した「test03.mq5」を使用いたします。

①エディターを起動し「test03.mq5」を開きます。

 

ソースコードに、以下を追加します。
 場所はソースコード先頭部。
 赤字部分をコピーし、ソースコードに貼り付けてください。

//+------------------------------------------------------------------+
//|                                                       test01.mq5 |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

//外部変数
input int Kikan = 60;
input double MinBand = 40;
double Open[]; //始値 double High[]; //高値 double Low[]; //安値 double Close[]; //終値 //配列の時系列化 ArraySetAsSeries(Open,true); ArraySetAsSeries(High,true); ArraySetAsSeries(Low,true); ArraySetAsSeries(Close,true); //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() {

 

 ④ソースコードに、以下の部分を修正します。
 場所は、SetOHLC関数内の赤字部分。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   SetOHLC();
  }
//+------------------------------------------------------------------+
//過去指定期間の四本値の取得
void SetOHLC()
  {
   CopyOpen(_Symbol,0,0,Kikan,Open);
   CopyHigh(_Symbol,0,0,Kikan,High);
   CopyLow(_Symbol,0,0,Kikan,Low);
   CopyClose(_Symbol,0,0,Kikan,Close);
  }

//

 

ソースコードに、以下を追加します。
 場所は、ソースコードの末尾。
 赤字部分をコピーし、ソースコードに貼り付けてください。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   SetOHLC();
  }
//+------------------------------------------------------------------+
//過去指定期間の四本値の取得
void SetOHLC()
  {
   CopyOpen(_Symbol,0,0,kikan,Open);
   CopyHigh(_Symbol,0,0,kikan,High);
   CopyLow(_Symbol,0,0,kikan,Low);
   CopyClose(_Symbol,0,0,kikan,Close);
  }

//
void Entry()
  {
   double MaxClose = Close[ArrayMaximum(Close,0,ArraySize(Close))]; //山
   double MinClose = Close[ArrayMinimum(Close,0,ArraySize(Close))]; //谷
   if ((MaxClose - MinClose) < valPips(MinBand)) {return;}         //バンド幅(山-谷)が狭い場合はエントリーしない
   
   double BuyEntry  = MinClose + ( (MaxClose - MinClose) * 0.1);
   double SellEntry = MaxClose - ( (MaxClose - MinClose) * 0.1);
   
   if(Open[1] < BuyEntry &&  Close[1] > BuyEntry) Ordr("BUY",0.01); //買いエントリー
   if(Open[1] > SellEntry && Close[1] < SellEntry) Ordr("SELL",0.01); //売りエントリー
  }

  
double valPips(double pips)
{
   double price = 0;

   int digits = (int)Digits();

   if(digits == 3 || digits == 5){
     price = pips / MathPow(10, digits) * 10;
   } else {
     price = pips / MathPow(10, digits);
   }
   price = NormalizeDouble(price, digits);
   return(price);
}

void Ordr(string BuySell,double Lots) {
  //工事中
}

 

⑥別名で保存 
 今回は「tes04.mq5」で保存してください。
 次回の実習で使用いたします。

 

コンパイル&動作テスト

 前回同様エラーにならない事を確認してください。

 

 ~ 以上で終了です。今回も結果なしです。以降は、今回の説明となります。


実習の説明

input修飾子

 変数の頭にinput修飾子をつけると「外部変数」となります。

 外部変数の機能は、以下のようになります。
 (1)エキスパート外から、バックテスト時に内容を変更させることができる。
 (2)エキスパート内では内容を変更できない。参照のみ。

 今回は、期間を表す「kikan」と、値幅を表す「MinBand」を定義しました。
 バックテストで最適値を求めていきます。

※MT4ユーザー向け情報

 外部変数の修飾子が「extern」から「input」に変更されてるようです。「extern」でもコンパイルエラーにはなりませんが(←イヤラシイ)、バックテスト時にパラメータとして表示されません。私もハマリました。
 

ArrayMaximum関数とArrayMinimum関数

 今回、一定期間の最大値(山)と最小値(谷)の値段を取得するために使用しています。
 まず「ArrayMaximum関数」は配列内から最大値が格納された添字を整数で返却します。
 その逆の「ArrayMinimum関数」は最小値が格納された添字を整数で返却します。

【書式】

[int] ArrayMaximum(配列名[,開始添字,回数])
[int] ArrayMinimum(配列名[,開始添字,回数])

 

【引数】 

配列名 配列を指定します。
省略することができません。
開始添字 検索範囲の開始位置を指定します。
省略すると先頭値(=0)が指定されます。
検索数 検索する配列数を指定します。
省略すると配列全体が指定されます。

 

【使用例】

//配列
int tbl = {5,3,8,1,6,4,7,0,4}


int Max1 = ArrayMaxinum(Tbl,0,ArraySize(tbl));
int Max2 = ArrayMaxinum(Tbl,0);
int Max3 = ArrayMaxinum(Tbl);


Print("Max1=",Max1);
Print("Max2=",Max2);
Print("Max3=",Max3);

 

【結果】

Max1=2       ←「8」が格納された添字「2」を返却。
Max2=2    配列全体から最大値・最小値を求める場合、
Max3=2    引数を省略しても結果は同じです。

 

 

※MT4ユーザー向け情報

 同名同機能の関数ですが、第2引数と第3引数数の順序が入れ替わっています。
 なんのための仕様変更なのか?単なる嫌がらせか?開発者がテキトーな性格なのか?
 こういったイヤラシイ仕様変更が各所に散らばっていそうです。
 MQL4から5へのエキスパート移植とか考えている方は注意してください。

ArraySize関数

ArraySizeは配列数を整数で返却します。

【書式】

[int] ArraySize(配列名)

 

【引数】 

配列名 配列を指定します。
省略することができません。

 

【使用例】

//配列
int tbl = {5,3,8,1,6,4,7,0,4}
int Size = ArraySize(tbl);

Print("Size=",Size);

 

【結果】

Size=9    ←添字の最大数「8」ではないので注意。

 

if文 条件分岐

 条件によって、処理を分岐させたいときに使用します。

【書式】

if(条件式) [条件を満たした場合の処理] [else 条件を満たさなかった場合の処理]

 

【使用例】

//一行で書く場合
if(a > b) Print("aが大きい");
if(a > b) Print("aが大きい"); else Print("bはa以上");

//複数行で書く場合
if(a > b) {
  Print("aが大きい");
} else {
  Print("bはa以上");
}

 

関数「Entry()」について

ますはこの部分で、終値配列の最大値・最小値を取得し、それぞれ山・谷としています。

double MaxClose = Close[ArrayMaximum(Close,0,ArraySize(Close))]; //山
double MinClose = Close[ArrayMinimum(Close,0,ArraySize(Close))]; //谷

 

 次に、山と谷が狭すぎる場合に無駄な注文が乱発されないよう、処理を中断しています。
 狭すぎるかの判定は、外部変数「MinBand」で指定しています。
 ただし「MinBand」は様々な通貨ペアに対応できるようPipsで指定しているため、実際の値段値に変換する必要があります。
 後述する関数「valPips()」で変換を行っています。

if ((MaxClose - MinClose) < valPips(MinBand)) {return;} //バンド幅(山-谷)が狭い場合はエントリーしない

 

 「BuyEntry」が谷を越えたところの値段(最初の図の青線)、「SelEntry」が山を越えたところの値段(赤線)です。

double BuyEntry = MinClose + ( (MaxClose - MinClose) * 0.1);
double SellEntry = MaxClose - ( (MaxClose - MinClose) * 0.1);

 

 1つ前のローソクの胴体(始値終値)が、「BuyEntry」「SelEntry」を跨いだら発注をします。
 価格が蛇行する場合に備え、「BuyEntry」のときは下から上へ、「SelEntry」は上から下へ、と方向を意識します。
 発注は、後述する関数「Order()」で行います。

if(Open[1] < BuyEntry && Close[1] > BuyEntry) {Ordr("BUY",0.01); }; //買いエントリー
if(Open[1] > SellEntry && Close[1] < SellEntry) {Ordr("SELL",0.01);}; //売りエントリー

 ↓図解すると、こんな感じです。

f:id:edger_arkw:20200511022927p:plain

売りエントリーのタイミング例
関数「valPips()」について

 この関数は「Pips値を価格値に変換」します。
 まずPips値とは、通貨にとらわれない共通の単位となります。
 このエキスパートを、米ドル/円・ユーロ/ドルなど様々な通貨ペアで動作させるため、価格値はPips値を使って指定します。
 米ドル/円・ユーロ/円といった「???/円」の場合「1pips=0.01円(1銭)」となります。
 つまり「20Pips」であれば「0.2(円)」となります。
 ちなみに「???/米ドル」の場合は「1pips=0.0001米ドル」となり「0.002($)」となり数字の桁が違います。
 この関数ですが、使い方や機能だけ理解し、内容は理解せずそのまま使用ください。
 よく使う処理なので、こいつは今後も活躍します。

 

関数「Ordr()」について

 この関数は「発注する」機能を持たせる予定ですが、コメントにもありますよう「工事中」にしてあります。
 この関数の作成は、次回となります。この関数の実装をもって、本エキスパートは完成いたします。
  

おめでとう♪

 エキスパートの骨格はすでにできあがり、あとは発注機能を追加するだけ。
 完成まであと一歩のところですが、いままでと今後の作業ボリュームから、ここを一つの区切りとし「銀トロフィー」を授与したいと思います。

 

f:id:edger_arkw:20200510212413p:plain

メタートレーダー 半人前

~ 今回は以上となります。オツカレサマでした。