ここでは、前回記事のコメントでご質問頂いた移動平均線と価格のクロスによるドテン取引をするEAの作成方法について解説します。
ドテンとは、保有ポジションを決済してすぐに反対のポジションをエントリーすることにゃ。
新MQL4で書き直したEAコードの解説をします。
今回完成EA【test10_2019.mq4】をダウンロード
EAコードの解説
最初にEAのコードを見るぞい
// 外部パラメーター extern double Lots = 0.01; extern double TakeProfit = 0.0; extern double StopLoss = 0.0; extern double Slippage = 1.0; extern string EA_Comment = "MA Doten EA"; extern int MagicNumber = 20191122; extern int MA_Period = 21; extern int MA_Shift = 0; extern ENUM_MA_METHOD MA_Method = MODE_SMA; extern ENUM_APPLIED_PRICE MA_Applied = PRICE_CLOSE; // グローバル変数 double _point; // Pips入力値用の変数 int entry_bar_buy; // 買い注文した時のバーの数 int entry_bar_sell;// 売り注文した時のバーの数 //+------------------------------------------------------------------+ //| EA稼働開始時に実行される関数 | //+------------------------------------------------------------------+ int OnInit() { // パラメーターをPips入力にする為の処理 _point = Point; if (Digits % 2 == 1) { _point *= 10; Slippage *= 10; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| EA稼働終了時に実行される関数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| ティック毎に実行される関数 | //+------------------------------------------------------------------+ void OnTick() { int i; // 保有ポジションを確認 ------------------------------------------ int pos_ticket_buy = 0; // 買いポジション保有時はここに注文番号が格納される int pos_ticket_sell = 0;// 売りポジション保有時はここに注文番号が格納される for (i=0; i<OrdersTotal(); i++) { if (OrderSelect(i, SELECT_BY_POS) == false) return; if (OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue; if (OrderType() == OP_BUY) pos_ticket_buy = OrderTicket(); if (OrderType() == OP_SELL) pos_ticket_sell = OrderTicket(); } // シグナル判定 ------------------------------------------------- // MAの値取得 double ma_1 = iMA(NULL, 0, MA_Period, MA_Shift, MA_Method, MA_Applied, 1); double ma_2 = iMA(NULL, 0, MA_Period, MA_Shift, MA_Method, MA_Applied, 2); // シグナル判定 int sign = 0; if (ma_2 >= Close[2] && ma_1 < Close[1]) sign = 1; // 買いシグナル if (ma_2 <= Close[2] && ma_1 > Close[1]) sign = -1;// 売りシグナル // ポジション保有時の処理 -------------------------------------- // 買いポジション保有時 if (pos_ticket_buy > 0) { if (sign == -1) // 売りシグナル発生 if (OrderClose(pos_ticket_buy, Lots, Bid, (int)Slippage, clrYellow) == true) pos_ticket_buy = 0; } // 売りポジション保有時 if (pos_ticket_sell > 0) { if (sign == 1) // 買いシグナル発生 if (OrderClose(pos_ticket_sell, Lots, Ask, (int)Slippage, clrYellow) == true) pos_ticket_sell = 0; } // ポジション無しの時の処理 ----------------------------------- int ticket; double sl = 0, tp = 0; if (pos_ticket_buy + pos_ticket_sell == 0) { // 買い注文処理 if (sign == 1 && entry_bar_buy != Bars) { if (StopLoss > 0) sl = Ask - StopLoss * _point; if (TakeProfit > 0) tp = Ask + TakeProfit * _point; ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, (int)Slippage, sl, tp, EA_Comment, MagicNumber, 0, clrBlue); if (ticket > 0) entry_bar_buy = Bars; } // 売り注文処理 if (sign == -1 && entry_bar_sell != Bars) { if (StopLoss > 0) sl = Bid + StopLoss * _point; if (TakeProfit > 0) tp = Bid - TakeProfit * _point; ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, (int)Slippage, sl, tp, EA_Comment, MagicNumber, 0, clrRed); if (ticket > 0) entry_bar_sell = Bars; } } }
外部パラメーター
最初に、外部パラメーターを次のように設置します。
extern double Lots = 0.01; extern double TakeProfit = 0.0; extern double StopLoss = 0.0; extern double Slippage = 1.0; extern string EA_Comment = "MA Doten EA"; extern int MagicNumber = 20191122; extern int MA_Period = 21; extern int MA_Shift = 0; extern ENUM_MA_METHOD MA_Method = MODE_SMA; extern ENUM_APPLIED_PRICE MA_Applied = PRICE_CLOSE;
ロット数やTP/SL幅などの注文時に指定するパラメーターと、移動平均線用のパラメーターです。
今回はドテン売買なので、TakeProfit/StopLossは初期値0にしてTP/SLを設定しない初期値設定にしています。
グローバル変数
次にグローバル変数を宣言します。グローバル変数は、どの関数からもアクセス可能で、関数処理終了後にも格納されている値がリセットされないという特徴があります。
double _point; // Pips入力値用の変数 int entry_bar_buy; // 買い注文した時のバーの数 int entry_bar_sell;// 売り注文した時のバーの数
OnInit()関数内の処理
OnInit()関数は、EA稼働開始時に実行される関数です。
OnInit()関数内で、パラメーターTakeProfit, StopLoss, SlippageをPips入力にする為のコードを追加します。
_point = Point; if (Digits % 2 == 1) { _point *= 10; Slippage *= 10; }
_point に Pointの値を代入。 もし、Digitsを2で割った余りが1なら、 _point に10を掛ける Slippage に10を掛ける
Pointには、価格表示の最小単位の値が入っています。例えば、レート表示が123.456のように小数点以下3桁までの表示であればPointには0.001が格納されています。
Digitsには、Pointの小数点以下桁数が入っています。例えば、Pointが0.001なら小数点以下3桁なのでDigitsには3が格納されています。
上記のコードの処理をすることで、例えばレート表示が123.456のように小数点以下3桁の場合、_pointには0.01が格納されていることになります。この場合の0.01が現在一般的に使用されているPips表示でいう1.0pipsとなります。
仮にTakeProfit = 10 の場合、TakeProfit * _point の値は、0.100で10.0Pipsの値となります。
Slippageに関しても、0.3 Pips と入力した場合、レート表示小数以下3桁5桁の場合に 3 Point になるように10倍しています。
OnTick()関数内の構成
OnTick()関数内では次の順に書いています。
-
保有ポジションを確認
-
MAと終値によるシグナル判定
-
ポジション保有時の処理
-
ポジション無しの時の処理
保有ポジションを確認
稼働EAでのポジションの有無を確認するために次のコードを追加します。(変数iは予め宣言しておきます。)
int pos_ticket_buy = 0; // 買いポジション保有時はここに注文番号が格納される int pos_ticket_sell = 0;// 売りポジション保有時はここに注文番号が格納される for (i=0; i<OrdersTotal(); i++) { if (OrderSelect(i, SELECT_BY_POS) == false) return; if (OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue; if (OrderType() == OP_BUY) pos_ticket_buy = OrderTicket(); if (OrderType() == OP_SELL) pos_ticket_sell = OrderTicket(); }
買いポジション注文番号を0にしておく 売りポジション注文番号を0にしておく i を 0 から順に保有ポジション数分増やしながら{}内の繰り返しをする { i 段目のポジション(注文)を選択する それの通貨ペアやマジックナンバーがこのEAのものではない場合はスキップ (以下、スキップしなかった場合の処理) 買いポジションの場合、pos_ticket_buy にその注文番号を格納する 売りポジションの場合、pos_ticket_sell にその注文番号を格納する }
この処理を実行してpos_ticket_buyとpos_ticket_sellのどちらも0の場合は、ポジションを保有していないことになります。
なぜ、注文番号を格納するのかというと、ポジションを決済する際に注文番号が必要になり、その際のポジションの選択を省略してコードをシンプルにしたいためです。解説用EAでもありますし。。
(手抜きではないですよ。。。手抜きでは。)
MAと終値によるシグナル判定
次のコードを追加して、MAの値の取得と、変数signにシグナル判定結果を格納する処理をします。
// MAの値取得 double ma_1 = iMA(NULL, 0, MA_Period, MA_Shift, MA_Method, MA_Applied, 1); double ma_2 = iMA(NULL, 0, MA_Period, MA_Shift, MA_Method, MA_Applied, 2); // シグナル判定 int sign = 0; if (ma_2 >= Close[2] && ma_1 < Close[1]) sign = 1; // 買いシグナル if (ma_2 <= Close[2] && ma_1 > Close[1]) sign = -1;// 売りシグナル
iMA()関数でパラメーターで指定した移動平均線の1つ前と2つ前の値を取得しています。
シグナル判定では、2つ前のローソク足の終値がMA以下で、1つ前のローソク足の終値がMAより上ならsignに1を代入して買いシグナルとしています。売りシグナルはその反対で、signに-1を代入します。シグナルが発生しないときは、signは0のままです。
ポジション保有時の処理
ポジション保有時の処理として、次のように買いポジション決済と売りポジション決済に分けて書いています。
// 買いポジション保有時 if (pos_ticket_buy > 0) { if (sign == -1) // 売りシグナル発生 if (OrderClose(pos_ticket_buy, Lots, Bid, (int)Slippage, clrYellow) == true) pos_ticket_buy = 0; } // 売りポジション保有時 if (pos_ticket_sell > 0) { if (sign == 1) // 買いシグナル発生 if (OrderClose(pos_ticket_sell, Lots, Ask, (int)Slippage, clrYellow) == true) pos_ticket_sell = 0; }
買いポジション保有中で
売りシグナル発生なら、
買いポジション決済注文して決済されたら、
pos_ticket_buy に0を代入
売りポジション保有中で
(以下省略)
ここで注文番号が格納されているpos_ticket_buyやpos_ticket_sellを活用しています。決済された場合に0を代入するのは、後程説明します。
パラメーターSlippageは実数型にしてありますので、(int)を付けて整数に型変換(キャスト)しています。
決済注文のロット数にパラメーターのLotsを指定しているので、ポジション保有中にLotsを変更してEAを再稼働すると、決済エラーになるか部分決済になってしまいますので、注意しましょう。
そうならないためにも、決済前に保有ポジションを選択してOrderLots()関数でそのポジションのロット数を取得して決済する方がよいです。
今回のは手抜きではないっス(汗)
ポジション無しの時の処理
ポジション無しの時の処理コードは次のとおりです。
int ticket; double sl = 0, tp = 0; if (pos_ticket_buy + pos_ticket_sell == 0) { // 買い注文処理 if (sign == 1 && entry_bar_buy != Bars) { if (StopLoss > 0) sl = Ask - StopLoss * _point; if (TakeProfit > 0) tp = Ask + TakeProfit * _point; ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, (int)Slippage, sl, tp, EA_Comment, MagicNumber, 0, clrBlue); if (ticket > 0) entry_bar_buy = Bars; } // 売り注文処理 if (sign == -1 && entry_bar_sell != Bars) { if (StopLoss > 0) sl = Bid + StopLoss * _point; if (TakeProfit > 0) tp = Bid - TakeProfit * _point; ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, (int)Slippage, sl, tp, EA_Comment, MagicNumber, 0, clrRed); if (ticket > 0) entry_bar_sell = Bars; } }
整数変数 ticket を宣言(エントリー注文時に注文番号を格納します)
実数変数 sl と tp を宣言してそれぞれ初期値は0
買い・売りの注文番号がともに0の場合で、
もし、買いシグナル発生中で買いエントリーしたローソク足ではない場合
StopLoss が0より大きいとき sl に Ask - StopLoss ピプス を代入
TakeProfit が0より大きいとき tp に Ask + TakeProfit ピプス を代入
買い注文実行して注文番号をticketに代入
注文番号が0より大きい場合 entry_bar_buy にバーの合計数を代入
もし、買いシグナル発生中で買いエントリーしたローソク足ではない場合
(以下、売り注文処理 省略)
決済注文した後に、pos_ticket_buyやpos_ticket_sellを0にしたのは、そのティック内で最初のエントリー条件の「買い・売りの注文番号がともに0の場合で」を満たすための処理です。
もう一度保有ポジションを確認するという方法もありますが、その分必要な処理数が多くなってしまいます。
OrderSend()関数でエントリー注文して、注文が通ったら注文番号が返ってきて、注文に失敗したら-1が返ってきます。その値を変数ticketに代入して、注文が通った場合のみentry_bar_buy(またはentry_bar_sell)にバーの数を代入します。
あとがき
シンプルなEAにしたつもりですが、説明不足の部分もあるかと思います。疑問に思ったところがありましたらお気軽にコメントで質問してくださいね!
では、今回はこの辺で。
こんばんは。
昨日初めてのEAが完成したので時間足や通貨ペアを色々変えてバックテストしまくってたらメタトレーダーがやたらと重くなったんでファイルを見たら6G以上になってました。
それで色々なファイルを削除したら新規チャートで選択できるチャートの種類が増えました。なぜかそれまではなかったgoldやoilやZARまで選べるようになっちゃいました。
サーバは変わってないみたいなんですけどね。まぁ、それはいい事なんでいいんですけど…
削除してもいいファイルといけないファイルってあるんでしょうか?
特にOptimizationをすると2G以上のファイルができたりしますもんね。
それとMismatched charts errorsとModelling qualityのn/aって何でしょうか?
Modelling qualityはEvery tickでやると90%になると何かで読んだんですが数値はでません。ご存じでしたら教えてください。
長々と質問ばかりですみません。
記事にまでしていただき感激です!!
じっくりと勉強させていただきます。
本当にありがとうございます。
>red sauceさん
こんにちわ。
>新規チャートで選択できるチャートの種類が増えました
なぜでしょうね?私の場合、ODLはもともと選べてましたが^^;
>削除してもいいファイルといけないファイルってあるんでしょうか?
テスターで生成されたファイルは消しても大丈夫そうですが、他は、履歴や設定が保存されたりしているので、そのままにしています。
>Mismatched charts errorsとModelling qualityのn/aって何でしょうか?
Mismatched charts errorsは、別々の時間足で異なるデータがあるときにカウントされます。
n/aはnot allでしょうか^^;完全ではないということだと思います。
>Every tickでやると90%になる
1分足のデータがある期間でやると90%になりますよ。
>xyz__さん
こんにちわ。
がんばりましょうね!