ネットで見つけた「ローソク足3本連続でエントリーする」ようなシンプルなEAサンプルコードをそのままMT4に入れて動かしたら、想定より多くのポジションが同時に建っていた、決済されないポジションが残り続けていた——という経験をした人は少なくありません。

原因の多くは、エントリー条件自体はシンプルでも、ポジション管理(重複エントリー防止・複数ポジションの正しい決済・損切り利確)が省略されていることです。この記事では、ローソク足の陽線が3本連続したらロングエントリーするEAを例に、パラメーターの設定からOnTick()関数の中身、そして見落としがちな「ポジションの重複防止」「損切り・利確の設定」「決済ロジックの修正」まで、実際にMetaEditorでコンパイルしてMT4で動かせる状態のコードを最後まで解説します。

ローソク足の陽線が3本でエントリーさせるEA

ローソク足の陽線が3本連続したら自動売買させるEAを作成します。

  • ローソク足の陽線が3本でロングエントリー
  • 決済は6時か18時に行う

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

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

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

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

パラメーターを記述する

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

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

元のコードにあったstatic int TicketNumber;(直近にエントリーしたチケット番号を覚えておくための変数)は、この記事の後半で決済処理を複数ポジション対応に書き換えるため使いません。理由は「決済ロジックを修正する」の項目で説明します。

OnTick()関数を記述する

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

ローソク足陽線3本連続なので、シフトを使いながら始値と終値を見て終値が3本大きいようにif文を作ります。

if(iOpen(Symbol(),0,1) < iClose(Symbol(),0,1) &&
       iOpen(Symbol(),0,2) < iClose(Symbol(),0,2) &&
       iOpen(Symbol(),0,3) < iClose(Symbol(),0,3))

そしてその条件を満たしたらオーダーを送信します。

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

決済は、Hour()で6時または18時のタイミングだった場合に行います。ここは次の「決済ロジックを修正する」で、自分のポジションを漏れなく決済する形に書き直します。

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

上のコードのままOnTick()を動かすと、陽線が3本連続する条件は、その足が確定するまでの間(例えば1時間足なら最大1時間)ずっと真であり続けます。つまり、その間に発生する全てのティックで新規の買い注文が送られ続け、想定よりはるかに多いポジションを一気に抱えることになります。

これを防ぐため、自分(このマジックナンバー)のポジションをすでに持っているかどうかを判定する関数を用意し、エントリー判定の前に確認します。

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

OnTick()のエントリー判定の直前にこの関数の結果をチェックすることで、ポジションを持っている間は新規エントリーの判定自体をスキップできます。

損切り・利確を設定する

元のOrderSend()は、6番目の引数(損切り価格)と7番目の引数(利確価格)がどちらも0になっていました。つまりこのEAは、ポジションを持ってから「6時か18時になる」まで、価格がどれだけ逆行しても決済されずに持ち続ける設計だったということです。

決済タイミングを時間だけに頼らず、損切り・利確ラインも必ず設定しましょう。パラメーターに用意したStopLoss・TakeProfitを使って計算します。

Ask-StopLoss*Point*10,//損切りライン
Ask+TakeProfit*Point*10,//利確ライン

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

なお、「エラーコード130(Invalid stops)」がログに出た場合、原因のほとんどはStopLoss・TakeProfitの値がブローカーの最小ストップ距離より近すぎることです。エラーが出た場合はまず値を大きくしてみてください。

決済ロジックを修正する

少し発展的な内容になりますが、元のコードにはもう1つ見落としやすい問題があります。決済処理がTicketNumberという1つの変数だけを頼りにOrderClose(TicketNumber,...)を呼んでいる点です。

先ほどのポジション重複バグが直っていない状態では、複数のポジションを抱えているのにTicketNumberには最後にエントリーした1件のチケット番号しか残らないため、それ以外のポジションは決済されずに残り続けてしまいます。今回はポジションの重複自体を防いだので通常は1ポジションのみになりますが、より安全にするため、決済処理も「自分のポジションを全て探して決済する」形に書き換えます。

また、元の決済条件if(OrdersTotal() > 0 && ...)は口座全体の注文数を見ているため、他のEAや裁量ポジションを持っていると、それらを自分のポジションと勘違いして誤動作します。これも自分(このマジックナンバー)のポジションだけを対象にするよう修正します。

void CloseAllPositions()
  {
   for(int i=OrdersTotal()-1; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue;
      if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=MagicNumber) continue;
      bool result=OrderClose(OrderTicket(),OrderLots(),Bid,Slippage,Yellow);
      if(!result)
         Print("決済に失敗しました。エラーコード=",GetLastError());
     }
  }
Copy

OnTick()の先頭で時刻を確認し、6時か18時であればこの関数で自分のポジションを全て決済してから、その回の処理を終える形に整理します。

コンパイルする

コンパイルしましょう。「0 error(s), 0 warning(s)」になることを確認し、バックテストで動きを確認できたら完了です。

ソースコード全体

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

//+------------------------------------------------------------------+
//|                                                 3Candle-long.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 double Lots        = 0.1;//ロット数
input int    StopLoss    = 20;//損切り(pips)
input int    TakeProfit  = 20;//利確(pips)
input int    Slippage    = 3;//スリッページ
input int    MagicNumber = 1234;//マジックナンバー

//+------------------------------------------------------------------+
//| この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);
  }

//+------------------------------------------------------------------+
//| このEA(マジックナンバー)のポジションを全て決済する                |
//+------------------------------------------------------------------+
void CloseAllPositions()
  {
   for(int i=OrdersTotal()-1; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES)) continue;
      if(OrderSymbol()!=Symbol() || OrderMagicNumber()!=MagicNumber) continue;
      bool result=OrderClose(OrderTicket(),OrderLots(),Bid,Slippage,Yellow);
      if(!result)
         Print("決済に失敗しました。エラーコード=",GetLastError());
     }
  }

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

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

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   //時刻が6時または18時なら、保有中の自分のポジションを全て決済する
   if(Hour()==6 || Hour()==18)
     {
      CloseAllPositions();
      return;
     }

   //既にポジションを持っている場合は新規エントリーしない
   if(HasPosition())
      return;

   //陽線が3本連続していればロングエントリー
   if(iOpen(Symbol(),0,1) < iClose(Symbol(),0,1) &&//1本前の始値より終値が高い
      iOpen(Symbol(),0,2) < iClose(Symbol(),0,2) &&//2本前の始値より終値が高い
      iOpen(Symbol(),0,3) < iClose(Symbol(),0,3))//3本前の始値より終値が高い
     {
      int ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,Slippage,Ask-StopLoss*Point*10,Ask+TakeProfit*Point*10,"EA",MagicNumber,0,Red);//ロングエントリー
      if(ticket<0)
         Print("買い注文に失敗しました。エラーコード=",GetLastError());
     }
  }
//+------------------------------------------------------------------+
Copy

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

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

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

修正前のコードでバックテストすると、陽線3本の条件が成立している間に同じ方向のポジションが積み上がる様子が結果に表れます。修正後は、ポジションが1つ決済されるまで新規エントリーが発生しないこと、6時・18時に保有ポジションがきちんと決済されることを確認してください。

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

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

  • 陽線が3本連続する場面は、強いトレンドの初動でも、単なるレンジ内の値動きでも起こりうるため、相場環境によっては「ダマシ」に遭いやすい
  • 決済タイミングを6時・18時に固定しているため、その間に大きく逆行した場合はStopLossが実質的な最終防衛ラインになる。値を口座資金に対して過大にならないよう調整する
  • バックテストの結果は過去データに基づくものであり、将来の相場でも同じ成績になるとは限らない

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

まとめ

今回はポジションの取り方やポジションの決済方法を理解できれば完璧です。エントリーのロジックだけでなく、ポジションの重複防止・損切り利確の設定・複数ポジションに対応した決済処理まで含めて初めて、実運用を検討できる形になります。

関連記事