ネットで見つけたゴールデンクロス・デッドクロスEAのサンプルコードをそのままMT4に入れて動かしたら、想定より大量のポジションが積み上がっていた、損切りが効かず含み損だけが膨らんでいた——MQLでEAを自作し始めた人がよくつまずくポイントです。

原因の多くは、コード自体は動く(コンパイルは通る)のに、「ポジションを重複して持たない処理」や「損切りの設定」が抜けていることです。この記事では、移動平均線のゴールデンクロス・デッドクロスでエントリーするEAを例に、パラメーターの設定からOnTick()関数の中身、そして見落としがちな「ポジションの重複防止」と「損切り設定」まで、実際にMetaEditorでコンパイルしてMT4で動かせる状態のコードを最後まで解説します。

ゴールデンクロスとデッドクロスでエントリーするEA

ゴールデンクロスとデッドクロスでエントリーするEAを作成します。

メタエディタ(MetaEditor)を立ち上げる

MT4を起動し、ツールバーの黄色いボタン(またはツール→MetaQuotes Language Editor)からメタエディタ(MetaEditor)を立ち上げましょう。

新しいEAを作るので、メタエディタの「ファイル」→「新規作成」を選び、ウィザードで「Expert Advisor(テンプレートを使用)」を選択して「次へ」を押します。

名前を入力する画面が出るので、今回は「GC-DC」と入力します(作者名・リンク・バージョンは空欄のままで問題ありません)。あとの画面はイベントハンドラのチェックを何も入れず、そのまま「次へ」を数回押して「完了」まで進めてください。すると、OnInit()・OnDeinit()・OnTick()の3つの関数だけが最初から書かれた空のひな形が開きます。

このひな形に、これから説明するコードを追記していきます。

パラメーターを記述する

パラメーターを記述しましょう。今回は利確・損切り・ロット数・マジックナンバー・スリッページの5つを、あとから値を変更しやすいようにinput変数として用意します。

input int    TakeProfit  = 5;//利確(pips)
input int    StopLoss    = 10;//損切り(pips)
input double Lots        = 0.01;//ロット数
input int    MagicNumber = 12345;//マジックナンバー
input int    Slippage    = 3;//スリッページ
Copy

損切り(StopLoss)とスリッページ(Slippage)は、後述する理由でこの記事から新しく追加したパラメーターです。

MagicNumberは、このEAが出した注文だけを識別するための番号です。適当な数字で構いませんが、他のEAやインジケーターと同じ口座で同時に動かす場合は、それぞれ異なる値にしてください(同じ値だと、後述するポジション管理が別のEAの注文まで自分のものとして数えてしまいます)。

OnTick()関数を記述する

OnTick()関数を記述しましょう。

まず、はじめに移動平均線を格納する変数を作成します。

double kakoMA50,kakoMA100;
double genMA50,genMA100;

次にそれぞれの変数にiMA関数の値を代入します。シフトで1つ前の足かどうかの違いになります。

     kakoMA50 = iMA(NULL,0,50,0,MODE_SMA,PRICE_CLOSE,1);
     kakoMA100 = iMA(NULL,0,100,0,MODE_SMA,PRICE_CLOSE,1);
     genMA50 = iMA(NULL,0,50,0,MODE_SMA,PRICE_CLOSE,0);
     genMA100= iMA(NULL,0,100,0,MODE_SMA,PRICE_CLOSE,0);

ゴールデンクロスしている条件を記載します。

if(kakoMA50 <= kakoMA100 && genMA50 > genMA100)

条件が合えば、ロングのポジションを持ちます。

OrderSend(Symbol(),OP_BUY,Lots,Ask,Slippage,Ask-StopLoss*Point*10,Ask+TakeProfit*Point*10,"Order Long",MagicNumber,0,Red);

ショートエントリーも同様にデッドクロスしているかの条件を記載します。

if(kakoMA50 >= kakoMA100 && genMA50 < genMA100)

条件が満たされていればショートのポジションを持ちます。

OrderSend(Symbol(),OP_SELL,Lots,Bid,Slippage,Bid+StopLoss*Point*10,Bid-TakeProfit*Point*10,"Order Short",MagicNumber,0,Blue);

これでエントリーのロジック自体はできましたが、このまま動かすと2つの問題が残っています。次の2つのセクションで直していきます。

ポジションを重複して持たないようにする

上のコードのままOnTick()を動かすと、ゴールデンクロスが発生した瞬間だけでなく、条件(genMA50 > genMA100)が成立している間、つまり次にデッドクロスするまでのすべてのティックで新規の買い注文を送り続けてしまいます。

移動平均線が同じ位置関係を保つ時間は数時間〜数日に及ぶことも珍しくないため、このまま動かすと証拠金が尽きるまで同じ方向のポジションを増やし続ける結果になります。

これを防ぐため、まず自分(このマジックナンバー)のポジションをすでに持っているかどうかを判定する関数を用意し、OnTick()の先頭でチェックします。

bool HasPosition()
  {
   for(int i=OrdersTotal()-1; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue;
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MagicNumber)
         return(true);
     }
   return(false);
  }
Copy

OrdersTotal()で保有中の注文数だけループし、通貨ペアとマジックナンバーが一致するポジションが1つでもあればtrueを返します。OnTick()の先頭に以下の1行を追加するだけで、ポジションを持っている間は新規エントリーの判定自体をスキップできます。

if(HasPosition())
   return;//既にポジションがあれば新規エントリーしない

損切り(ストップロス)を設定する

元のOrderSend()は、6番目の引数(損切り価格)が0になっていました。0を指定すると損切りは設定されず、デッドクロスが起きてポジションが逆行しても決済されないまま含み損が膨らみ続けます。

利確だけでなく損切りも必ず設定しましょう。パラメーターに追加したStopLossを使って、エントリー方向と逆側に損切りラインを計算します。

//買いの場合
Ask-StopLoss*Point*10

//売りの場合
Bid+StopLoss*Point*10

※Pointは通貨ペアの最小価格単位です。多くのFX業者が採用している5桁表示(例:USDJPYが150.123のように小数点以下3桁)のブローカーでは1pip=10Pointになるため、pips単位で指定したTakeProfit・StopLossに10を掛けています。4桁表示(USDJPYが150.12までの2桁)のブローカーを使う場合は、この「×10」を外してください。

※このEAをそのまま動かして「エラーコード130(Invalid stops)」がログに出た場合、注文が失敗する原因のほとんどはStopLoss・TakeProfitの値が近すぎることです。ブローカーごとに「ここより近い距離には損切り・利確を置けない」という最小ストップ距離(ストップレベル)が決まっており、TakeProfit=5pips・StopLoss=10pipsという初期値はこの距離より狭い業者もあります。エラー130が出た場合は、まずTakeProfit・StopLossの値を大きくしてみてください。

ソースコード全体

ここまでの修正をすべて反映した、実際にMetaEditorでコンパイルしてそのまま動かせる状態のコードです。

//+------------------------------------------------------------------+
//|                                                        GC-DC.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

/*パラメーター*/
input int    TakeProfit  = 5;//利確(pips)
input int    StopLoss    = 10;//損切り(pips)
input double Lots        = 0.01;//ロット数
input int    MagicNumber = 12345;//マジックナンバー
input int    Slippage    = 3;//スリッページ

//+------------------------------------------------------------------+
//| このEA(マジックナンバー)のポジションを保有しているか判定する      |
//+------------------------------------------------------------------+
bool HasPosition()
  {
   for(int i=OrdersTotal()-1; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue;
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MagicNumber)
         return(true);
     }
   return(false);
  }

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   //既にポジションを持っている場合は新規エントリーしない
   if(HasPosition())
      return;

   double kakoMA50,kakoMA100;//過去MA50,過去MA100
   double genMA50,genMA100;//現在MA50,現在MA100

   kakoMA50 = iMA(NULL,0,50,0,MODE_SMA,PRICE_CLOSE,1);//1つ前の50日線
   kakoMA100 = iMA(NULL,0,100,0,MODE_SMA,PRICE_CLOSE,1);//1つ前の100MA
   genMA50 = iMA(NULL,0,50,0,MODE_SMA,PRICE_CLOSE,0);//現在の50MA
   genMA100= iMA(NULL,0,100,0,MODE_SMA,PRICE_CLOSE,0);//現在の100MA

   int ticket;

   if(kakoMA50 <= kakoMA100 && genMA50 > genMA100)//もし50日移動平均線が100日線を下から上にクロスしたら(ゴールデンクロス)
     {
      ticket = OrderSend(Symbol(),OP_BUY,Lots,Ask,Slippage,Ask-StopLoss*Point*10,Ask+TakeProfit*Point*10,"Order Long",MagicNumber,0,Red);//ロングエントリー
      if(ticket < 0)
         Print("買い注文に失敗しました。エラーコード=",GetLastError());
     }

   if(kakoMA50 >= kakoMA100 && genMA50 < genMA100)//もし50日移動平均線が100日線を上から下にクロスしたら(デッドクロス)
     {
      ticket = OrderSend(Symbol(),OP_SELL,Lots,Bid,Slippage,Bid+StopLoss*Point*10,Bid-TakeProfit*Point*10,"Order Short",MagicNumber,0,Blue);//ショートエントリー
      if(ticket < 0)
         Print("売り注文に失敗しました。エラーコード=",GetLastError());
     }
  }
//+------------------------------------------------------------------+
Copy

バックテストで動作を確認する

コードを保存したら、MetaEditorの「コンパイル」(Ctrl+F7)で「0 error(s), 0 warning(s)」になることを確認します。

そのあとMT4の「表示」→「ストラテジーテスター」を開き、Expert Advisorに作成したGC-DCを選択、通貨ペアと期間を指定して、モデルを「全ティック(最も正確な値)」にしてスタートすると、過去データでの動作を確認できます。

修正前のコードでバックテストすると、同じ方向のポジションが大量に積み上がる様子が結果に表れます。修正後は、ポジションが1つ決済されるまで新規エントリーが発生しないことを確認してください。

実運用する前に確認しておきたいこと

このEAは移動平均線のクロスだけで売買する、学習用にシンプルへ作った例です。実際の口座で稼働させる前に、次の点を確認してください。

  • レンジ相場ではゴールデンクロス・デッドクロスが短時間で連続して発生しやすく、損切りが重なりやすい(いわゆる「ダマシ」)
  • ロット数(Lots)は口座残高に対して過大にならないよう調整する
  • バックテストの結果は過去データに基づくものであり、将来の相場でも同じ成績になるとは限らない

まずはデモ口座、または少額のロットでのフォワードテスト(リアルタイムでの動作確認)を行ってから、本番運用を検討してください。

まとめ

移動平均線のゴールデンクロス・デッドクロスでエントリーするEAの作り方を、実際に動くコードとあわせて解説しました。エントリーのロジックだけでなく、ポジションの重複防止と損切り設定まで含めて初めて、実運用を検討できる形になります。

関連記事