「21時から翌6時まで」のように日付をまたぐ時間帯の条件を`&&`(AND)で書いてしまい、意図した時間帯が一度もエントリーしない——複数の時間帯・複数の手法を1つのEAに詰め込むタイプのコードでよく起きるつまずきです。

この記事では、アジア・欧州・アメリカの時間帯ごとに手法を切り替えるEAを例に、時間帯判定の書き方と、見落としがちな「ポジションの重複防止」まで、実際にMetaEditorでコンパイルしてMT4で動かせる状態のコードを解説します。

アジア・欧州・アメリカ時間帯別でトレードするEA

アジア・欧州・アメリカ時間帯別でトレードするEAを作成したいと思います。

  • アジア時間:RSI70以上30以下で逆張り
  • 欧州時間:ゴールデンクロスとデッドクロスで順張り
  • アメリカ時間:RSI80以上20以下で順張り

という形で作成していきます。

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

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

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

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

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

パラメーターを記述する

パラメーターを記述しましょう。

input int TakeProfit = 5;//利確
input int StopLoss = 50;//ストップロス
input double Lots = 0.01;//ロット数
input int MagicNumber = 12345;//マジックナンバー

input int StartTimeA = 21;//アジア開始時間(サーバー時刻)
input int EndTimeA = 6;//アジア終了時間
input int EndTimeE = 12;//欧州終了時間
input int EndTimeU = 21;//アメリカ終了時間

input int RSI_period = 14;//RSI期間
input int MA_Short_period = 50;//短期移動平均線期間
input int MA_Long_period = 100;//長期移動平均線期間

OnTick()関数を記述する

OnTick()関数を記述しましょう。まずは変数をそれぞれ記述していきます。

TimeHour()は指定した日付データの時を返す関数です。

その中にあるTimeCurrent()は、最後に受信したサーバー時間を返す関数です。

つまり、最後に受信したサーバー時間の時間を使います。
JpnTimeは日本時間、EurTimeは欧州時間、UsaTimeは欧米時間になります。

   int NowHour = TimeHour(TimeCurrent());//現在の時間
   int TimeSignal;
   int JpnTime = 0;
   int EurTime = 1;
   int UsaTime = 2;

次にテクニカル指標を格納する変数を用意します。

   double RSI;//RSI
   double genMA_Short,genMA_Long;//現在の短期移動平均線と現在の長期移動平均線
   double kakoMA_Short,kakoMA_Long;//1つ前の短期移動平均線と1つ前の長期移動平均線

それぞれの変数にテクニカル指標を値を代入します。

   RSI = iRSI(Symbol(),0,RSI_period,PRICE_CLOSE,0);//RSI
   kakoMA_Short = iMA(NULL,0,MA_Short_period,0,MODE_SMA,PRICE_CLOSE,1);
   kakoMA_Long = iMA(NULL,0,MA_Long_period,0,MODE_SMA,PRICE_CLOSE,1);
   genMA_Short = iMA(NULL,0,MA_Short_period,0,MODE_SMA,PRICE_CLOSE,0);
   genMA_Long= iMA(NULL,0,MA_Long_period,0,MODE_SMA,PRICE_CLOSE,0);

ではここから時間帯別にロジックでエントリーするプログラムを書いていきます。

日本時間でRSIが30より小さい場合はロングのポジションを取ります。

if(TimeSignal == JpnTime && RSI < 30)
{
 OrderSend(Symbol(),OP_BUY,Lots,Ask,3,Ask-StopLoss*Point*10,Ask+TakeProfit*Point*10,"Order Long",MagicNumber,0,Lime);
}

日本時間でRSIが70より大きい場合はショートのポジションを取ります。欧州時間でゴールデンクロスした場合はロング、デッドクロスした場合はショート、アメリカ時間でRSIが80より大きい場合はロング、RSIが20より小さい場合はショートのポジションを取ります。考え方はすべて同じで、条件式が変わるだけです(残り4パターンの実際のコードは、後述の「ソースコード全体」でまとめて確認できます)。

これでOnTick()関数のエントリー判定は完了ですが、このまま動かすと2つの問題が残っています。次の2つのセクションで直していきます。

時間帯の判定を正しく書く

このEAは「アジア開始時間(StartTimeA=21)からアジア終了時間(EndTimeA=6)まで」というパラメーターを用意しています。これは21時から日付をまたいで翌6時までを意味していますが、元のコードは次のように&&(AND)条件で書かれていました。

if(NowHour >= StartTimeA && NowHour < EndTimeA) TimeSignal = JpnTime;

NowHourは0〜23のどれか1つの値しか取りません。「21以上」であり、かつ「6未満」でもある値は存在しないため、この条件は常にfalseになり、アジア時間帯の判定は永久に成立しません。つまり、日付をまたぐ時間帯の設定自体が機能していませんでした。

日付をまたぐ時間帯を判定するには、&&ではなく||(OR)を使い、「21時以降」または「6時未満」のどちらかに当てはまればアジア時間、という書き方にします。あわせて、3つの判定をelse ifでつなぎ、必ずどれか1つの時間帯に分類されるようにします。元のコードのように独立したif文を3つ並べると、いずれの条件にも一致しない時間帯ができてしまい、TimeSignalが前回のティックの値のまま残ってしまう不具合につながるためです。

//日付をまたぐ時間帯(21時〜翌6時)はOR条件、それ以外はelse ifで確実にどれか1つに分類する
if(NowHour >= StartTimeA || NowHour < EndTimeA)
   TimeSignal = JpnTime;
else if(NowHour >= EndTimeA && NowHour < EndTimeE)
   TimeSignal = EurTime;
else
   TimeSignal = UsaTime;
Copy

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

このEAは6つの条件でエントリーする分岐がありますが、条件を満たしている間(例えば欧州時間でゴールデンクロスした状態が続く間)は毎ティック新規の注文を送り続けてしまいます。時間帯が切り替わるまでの数時間、同じ方向のポジションを増やし続ける結果になるため、自分(このマジックナンバー)のポジションをすでに持っているかどうかを判定する関数を用意し、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

OnTick()の先頭に以下の1行を追加するだけで、ポジションを持っている間は新規エントリーの判定自体をスキップできます。

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

コンパイルする

コンパイルしましょう。実際に動きを確認できたら完了です。

ソースコード全体

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

//+------------------------------------------------------------------+
//|                                                  JP-EU-NY-EA.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;//利確
input int StopLoss = 50;//ストップロス
input double Lots = 0.01;//ロット数
input int MagicNumber = 12345;//マジックナンバー

input int StartTimeA = 21;//アジア開始時間(サーバー時刻)
input int EndTimeA = 6;//アジア終了時間
input int EndTimeE = 12;//欧州終了時間
input int EndTimeU = 21;//アメリカ終了時間

input int RSI_period = 14;//RSI期間
input int MA_Short_period = 50;//短期移動平均線期間
input int MA_Long_period = 100;//長期移動平均線期間

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

   int NowHour = TimeHour(TimeCurrent());//現在の時間
   int TimeSignal;//それぞれの時間を代入する変数
   int JpnTime = 0;//日本時間
   int EurTime = 1;//欧州時間
   int UsaTime = 2;//欧米時間

   double RSI;//RSI
   double genMA_Short,genMA_Long;//現在の短期移動平均線と現在の長期移動平均線
   double kakoMA_Short,kakoMA_Long;//1つ前の短期移動平均線と1つ前の長期移動平均線

   //日付をまたぐ時間帯(21時〜翌6時)はOR条件、それ以外はelse ifで確実にどれか1つに分類する
   if(NowHour >= StartTimeA || NowHour < EndTimeA)
      TimeSignal = JpnTime;
   else if(NowHour >= EndTimeA && NowHour < EndTimeE)
      TimeSignal = EurTime;
   else
      TimeSignal = UsaTime;

   RSI = iRSI(Symbol(),0,RSI_period,PRICE_CLOSE,0);//RSI
   kakoMA_Short = iMA(NULL,0,MA_Short_period,0,MODE_SMA,PRICE_CLOSE,1);//1つ前の短期移動平均線
   kakoMA_Long  = iMA(NULL,0,MA_Long_period,0,MODE_SMA,PRICE_CLOSE,1);//1つ前の長期移動平均線
   genMA_Short  = iMA(NULL,0,MA_Short_period,0,MODE_SMA,PRICE_CLOSE,0);//現在の短期移動平均線
   genMA_Long   = iMA(NULL,0,MA_Long_period,0,MODE_SMA,PRICE_CLOSE,0);//現在の長期移動平均線

   int ticket;

   if(TimeSignal == JpnTime && RSI < 30)//日本時間でRSIが30より小さい場合
     {
      ticket = OrderSend(Symbol(),OP_BUY,Lots,Ask,3,Ask-StopLoss*Point*10,Ask+TakeProfit*Point*10,"Order Long",MagicNumber,0,Lime);
      if(ticket < 0) Print("買い注文に失敗しました。エラーコード=",GetLastError());
     }
   if(TimeSignal == JpnTime && RSI > 70)//日本時間でRSIが70より大きい場合
     {
      ticket = OrderSend(Symbol(),OP_SELL,Lots,Bid,3,Bid+StopLoss*Point*10,Bid-TakeProfit*Point*10,"Order Short",MagicNumber,0,Red);
      if(ticket < 0) Print("売り注文に失敗しました。エラーコード=",GetLastError());
     }
   if(TimeSignal == EurTime && kakoMA_Short < kakoMA_Long && genMA_Short > genMA_Long)//欧州時間でゴールデンクロスした場合
     {
      ticket = OrderSend(Symbol(),OP_BUY,Lots,Ask,3,Ask-StopLoss*Point*10,Ask+TakeProfit*Point*10,"Order Long",MagicNumber,0,Lime);
      if(ticket < 0) Print("買い注文に失敗しました。エラーコード=",GetLastError());
     }
   if(TimeSignal == EurTime && kakoMA_Short > kakoMA_Long && genMA_Short < genMA_Long)//欧州時間でデッドクロスした場合
     {
      ticket = OrderSend(Symbol(),OP_SELL,Lots,Bid,3,Bid+StopLoss*Point*10,Bid-TakeProfit*Point*10,"Order Short",MagicNumber,0,Red);
      if(ticket < 0) Print("売り注文に失敗しました。エラーコード=",GetLastError());
     }
   if(TimeSignal == UsaTime && RSI > 80)//米国時間でRSIが80より大きい場合
     {
      ticket = OrderSend(Symbol(),OP_BUY,Lots,Ask,3,Ask-StopLoss*Point*10,Ask+TakeProfit*Point*10,"Order Long",MagicNumber,0,Lime);
      if(ticket < 0) Print("買い注文に失敗しました。エラーコード=",GetLastError());
     }
   if(TimeSignal == UsaTime && RSI < 20)//米国時間でRSIが20より小さい場合
     {
      ticket = OrderSend(Symbol(),OP_SELL,Lots,Bid,3,Bid+StopLoss*Point*10,Bid-TakeProfit*Point*10,"Order Short",MagicNumber,0,Red);
      if(ticket < 0) Print("売り注文に失敗しました。エラーコード=",GetLastError());
     }
  }
//+------------------------------------------------------------------+
Copy

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

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

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

修正前のコードでバックテストすると、アジア時間帯(21時〜翌6時)でのエントリーが一度も発生しないことが結果から確認できます。修正後は、3つの時間帯すべてでエントリーが発生し、ポジションが1つ決済されるまで新規エントリーが起きないことを確認してください。

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

このEAはアジア・欧州・アメリカという3つの手法を1つのEAに詰め込んだ学習用の例です。実際の口座で稼働させる前に、次の点を確認してください。

  • 3つの手法(逆張り・順張り・逆張り)はそれぞれ得意な相場が異なるため、可能であれば時間帯ごとに個別のバックテストで検証してから、全体を組み合わせて検証する
  • ロット数(Lots)は口座残高に対して過大にならないよう調整する
  • バックテストの結果は過去データに基づくものであり、将来の相場でも同じ成績になるとは限らない

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

まとめ

こうした時間帯別にトレードするプログラムは非常によく活用しますので、ぜひ応用してみてください。日付をまたぐ時間帯の判定とポジションの重複防止さえ押さえておけば、他の時間帯・他の手法にも同じ考え方をそのまま使えます。

関連記事