見出し画像

ADM-EAのソースコード全文(MQL4によるMT4用EAのサンプルコード)

この記事では、MT4用のEA(FX自動売買ツール)を作成するためのサンプルコードを掲載しています。
内容をコピペするだけで、MT4用のEAをMQL4で作成することが可能です。なお、それぞれのコードの説明も可能な限り細かく記載していますで、これをベースにご自身でロジックをお好きなようにカスタマイズしていただくこともできます。
また、最後にこの記事で紹介したコードをまとめたmq4ファイルをダウンロード可能にしております。このファイルをただコンパイルするだけでも、ex4ファイル(EAの実行ファイル)を作成することができますので、EAとしてそのまま使用していただくことも可能です。


ADM-EA(分解モンテカルロ法)とは

以下のページでロジックを詳細解説しているEAです。

このADM-EAについて、「MQL4でこの売買戦略を実装する方法」を以下ではご紹介していきます。
MQL4でロジック実装する上で必要なコードを詳細まで掲載していますので是非ご確認ください。少し複雑な処理も組み合わせたロジックになっていますので、MT4で他のEAを開発する際にも参考になるコードがあったりすると思います。

バックテストおよびフォワードテスト

ほぼ同等のロジックのADM-EAを以下のサイトで販売中です。バックテストやフォワードテストも掲載されていますので参考にしてください。

"ほぼ同等"というのは、販売用に変数を制限していたりするためです。当記事においては、ソースコード全文を提供していますので、上記で購入したEAをただ使用するよりも自由度が高く、ご自身でカスタマイズも可能ですのでオススメです。

よりシンプルなロジックのEA

以下の記事では、最もシンプルなナンピンマーチン式のFX自動売買ツールの全コードをご紹介しています。初心者向けになるべくコードの意味まで丁寧に解説するように心がけていますので、この記事に書いてある内容が難しい場合等は、まずはこちらのロジックを実装してみることをオススメします。初心者の方も是非、MQL4でのEA作成にチャレンジしてみてください。

プロパティ設定や変数の宣言と初期化

ここからコードの解説に入ります。まずは、プログラム全体の前提となる初期設定部分です。

プロパティ設定

#property copyright   "Copyright 2023, nanpin-martin.com"
#property link        "https://nanpin-martin.com/"
#property version   "ver1.00"
#property strict

例えば、上記のように記載するとEAの表示が以下のような画面になります。

プロパティ設定後

ちなみに何も入力しないと以下のようになります。これは、EAの動作には影響ありませんので、個人利用するだけの方は省略しても特に問題ありません。

プロパティ設定前

ただし、#property strictだけは入れておくようにしてください。詳しい理由はここでは割愛します。

Input設定

初期設定として、いくつかインプットを定めておきます。

input double first_lot = 0.01;//初期ロット数
input double max_lot = 0.30;//最大ロット数
input int parallel_order = 4; //並行注文数
input double order_interval = 900;//並行注文間隔(秒)
input int first_magic_number = 10001; //マジックナンバー(最初の数値)
input double slippage = 10;//スリッページ

input double tp_factor = 2.4;//利確係数
input double lc_factor = 2.3;//ロスカット係数
input double spread_limit = 300;//許容スプレッド(point)
input double entry_range = 60;//エントリー幅(point)

「input~」と記載することで、EA利用時にユーザーが指定可能な変数になります。また、//の後に日本語を入力するなどして、表示させる変数名を指定することも可能です。
上記の内容であれば、以下のように表示されます。

今回実装するのはADM-EA(分解モンテカルロ法)と同じロジックですので、そのために必要な変数もここで用意してあります。ロジックの詳細は以下の記事をご参照ください。

テクニカル指標のパラメータ設定

その他のインプット項目として、各テクニカル指標の期間を設定しておきます。

int MA_period = 14;
int ATR_period = 14;
int ADX_period = 14;

ここでは「input~」を入れていないので、ユーザーには指定させない内部設定項目になります。ちなみにここで設定しているのは、各テクニカル指標の平均期間です。

その他の初期設定

以下の変数を定義しておきます。いずれも、後述の処理で登場します。

int ticket_number;

datetime current_buy_order_time, current_sell_order_time, buy_flg_time, sell_flg_time;

int L,N;

int magic_number[100];

int current_buy_position[100];
int current_sell_position[100];
int buy_position[100];
int sell_position[100];

double current_buy_price[100];
double current_sell_price[100];
double order_lot[100];

int lot_array[100][100];
int current_lot_array[100];
int x,y,z;

double current_ATR[100];

double pre_bid[50];
double pre_ask[50];

int MA_flg;
int DMI_flg;
int order_flg;

int OnInit()

ここからは、初期化関数であるint OnInit()内に書かれる処理です。

並行売買の初期設定

今回のロジックは、ADM-EAのParallelsバージョンの実装です。並行注文数というパラメータを調整することにより、同じロジックの売買を時間をおいて並行で行うことを可能にします。

   for(N=0; N<parallel_order; N++)
     {
      magic_number[N] = first_magic_number+N;
      lot_array_load(N);
      set_current_lot_array(N);
      order_lot[N] = round(current_lot_array[N]*first_lot*100)/100;
      current_ATR_load(N);
     }

parallel_orderというのが並行注文数を設定する変数で、Nの数だけ各変数を複数持たせるという構成にしています。マジックナンバーもそれぞれ変える設計にしています。

現在時刻の設定

現時点から1ヶ月以内の過去注文を確認するロジックを導入していますので、時間の変数も設定しておく必要があります。

   datetime from = TimeCurrent() - (60 * 60 * 24 * 30);
   datetime to = TimeCurrent();

   datetime maxOpenTime = 0;
   int maxOpenOrderTicket = -1;

ポジションの確認

現在保有しているポジションを確認する処理です。
まず、OrdersHistoryTotal()は、注文(オーダー)の履歴を取得する関数です。

   int total = OrdersHistoryTotal();
   for(int i = 0; i < total; i++)
     {
      if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))
        {
         if(OrderType() == OP_BUY || OrderType() == OP_SELL)
           {
            datetime openTime = OrderOpenTime();
            if(openTime > maxOpenTime && openTime >= from && openTime <= to)
              {
               maxOpenTime = openTime;
               maxOpenOrderTicket = OrderTicket();
              }
           }
        }
     }

続いて、OrdersTotal()は、未決済ポジション(保有ポジション)の数を返す関数です。

   total = OrdersTotal();
   for(int i = 0; i < total; i++)
     {
      if(OrderSelect(i, SELECT_BY_POS))
        {
         if(OrderType() == OP_BUY || OrderType() == OP_SELL)
           {
            datetime openTime = OrderOpenTime();
            if(openTime > maxOpenTime && openTime >= from && openTime <= to)
              {
               maxOpenTime = openTime;
               maxOpenOrderTicket = OrderTicket();
              }
           }
        }
     }

以上の情報から、現在どのラインにいるか(次はどのラインの注文を行うべきか)を判定して取得します。

   if(maxOpenOrderTicket != -1)
     {
      if(OrderSelect(maxOpenOrderTicket, SELECT_BY_TICKET))
        {
         int L_magic = OrderMagicNumber();
         int last_two_digits = (L_magic - first_magic_number) % 100;
         if(last_two_digits < parallel_order-1)
            L = last_two_digits + 1;
         else
            L = 0;
        }
     }
   else
     {
      L = 0;
     }

一連の処理で何を行なっているかを簡単に解説します。
このEAにおける並行注文では、例えばA~Dの4つのラインで稼働している場合、「Aの注文の次はBの注文を行う」というように、A→B→C→Dの順番で注文を行う必要があります。
EAを一度稼働して、永遠に動かし続ける場合はもう少しシンプルなロジックで実装可能なのですが、実際に使用する上では、EAを一度停止したり、PCを再起動したりといったことが想定されます。その際に情報がリセットされてしまうと、またAから注文が始まってしまうといったことになってしまうため、想定通りの動作ができなくなります。
そこで、注文履歴と現保有ポジションの時間を取得して、一番最新の注文の情報を特定しているのがこの処理です。例えば、最新の注文がBであれば、次はCの注文のつもりで再稼働します。

void OnTick()

ここからは、ティックイベント関数であるvoid OnTick()内に書かれる処理です。これは新しいティックが更新されるたびに行われる処理で、何度も繰り返すループ処理に近い動きをします。

現在価格や現在時刻の取得

まずは、現在価格や現在時刻等の情報を取得する処理です。

   int cnt;
   int buy_cnt = 0;
   int sell_cnt = 0;

   double spread = round((Ask - Bid)/Point)*Point;

   datetime current_time = TimeCurrent();

   static int DMI_M1_flg,DMI_M5_flg,DMI_M15_flg,DMI_H1_flg;

MQL4の場合は、AskやBidと入力してそのままAsk値やBid値を取得可能です。AskとBidの差がスプレッドです。合わせて、TimeCurrent()で現在時刻を取得しています。「int current_time」のように、intで定義することで、整数値として扱えます。注文に時間制限を設ける場合などにも応用できます。

コメント表示

続いて、コメント表示です。これはEAのロジックに直接影響するものではないですが、視覚的に使いやすくするために有用です。

   string message = " \n";

   for(N=0; N<parallel_order; N++)
     {
      message += "current_ATR_" + IntegerToString(N+1) + " = ";
      message += DoubleToString(current_ATR[N], 3) + "\n";
     }
   message += " \n";

   for(N=0; N<parallel_order; N++)
     {
      string part_message = "lot_array_" + IntegerToString(N+1) + " = ";
      bool first_zero_encountered = false;
      part_message += IntegerToString(lot_array[N][0]);

      for(int i=1; i<100; i++)
        {
         if(lot_array[N][i] == 0 && !first_zero_encountered)
           {
            first_zero_encountered = true;
           }
         if(lot_array[N][i] == 0 && first_zero_encountered)
           {
            break;
           }
         else
            part_message += ", " + IntegerToString(lot_array[N][i]);
        }
      message += part_message + "\n";
     }

   Comment(message);

この処理を入れておくことで、MT4のチャート画面に以下のようなコメントを表示可能です。

コメント表示

これは同時に4つのラインを稼働させている状態で、それぞれ1~4のcurrent_ATRとlot_arrayを表示させています。
current_ATRというのはポジションを取得した際のATRで、EA内ではこれを基準に利確幅とロスカット幅を設定していますので、それを視覚化しています。
lot_arrayというのは分解モンテカルロ法における現在の数列で、これは稼働したばかりの状態ですので、現在の数列が初期値である[0, 1]ということがわかります。
分解モンテカルロ法の数列について、より詳しくは以下の記事をご参照ください。

総ポジションの確認

このADM-EAは、並列してポジションを持つ仕組みであるため、各ラインのポジション合計を確認する処理も入れておきます。

   for(N=0; N<parallel_order; N++)
     {
      buy_cnt+=buy_position[N];
      sell_cnt+=sell_position[N];
     }

これにより、「どのラインもポジションを持っていない場合」という条件を使うことが可能になります。

テクニカル指標の取得

まずは、四本値(バー)を取得します。ここでは、1分足(
M1)、5分足(M5)、15分足(M15)、1時間足(H1)の4つを使用します。

   static int pre_bars_M1 = 0;
   int current_bars_M1 = iBars(NULL,1);
   int bars_M1_check = current_bars_M1 - pre_bars_M1;
   pre_bars_M1 = current_bars_M1;

   static int pre_bars_M5 = 0;
   int current_bars_M5 = iBars(NULL,5);
   int bars_M5_check = current_bars_M5 - pre_bars_M5;
   pre_bars_M5 = current_bars_M5;

   static int pre_bars_M15 = 0;
   int current_bars_M15 = iBars(NULL,15);
   int bars_M15_check = current_bars_M15 - pre_bars_M15;
   pre_bars_M15 = current_bars_M15;

   static int pre_bars_H1 = 0;
   int current_bars_H1 = iBars(NULL,60);
   int bars_H1_check = current_bars_H1 - pre_bars_H1;
   pre_bars_H1 = current_bars_H1;

続いて、各テクニカル指標を取得します。static~と頭に付けると、最初の1回目だけ行う処理になります。

   static double MA_M1 = iMA(NULL, 1, MA_period, 0, MODE_SMA, PRICE_MEDIAN, 1);

   if(bars_M1_check==1)
     {MA_M1 = iMA(NULL, 1, MA_period, 0, MODE_SMA, PRICE_MEDIAN, 1);}

   static double ATR_H1 = iATR(NULL, 60, ATR_period, 1);

   if(bars_H1_check==1)
      ATR_H1 = iATR(NULL, 60, ATR_period, 1);

   static double pDI_M1 = iADX(NULL, 1, ADX_period, PRICE_CLOSE, MODE_PLUSDI, 1);
   static double pDI_M5 = iADX(NULL, 5, ADX_period, PRICE_CLOSE, MODE_PLUSDI, 1);
   static double pDI_M15 = iADX(NULL, 15, ADX_period, PRICE_CLOSE, MODE_PLUSDI, 1);
   static double pDI_H1 = iADX(NULL, 60, ADX_period, PRICE_CLOSE, MODE_PLUSDI, 1);

   if(bars_M1_check==1)
      pDI_M1 = iADX(NULL, 1, ADX_period, PRICE_CLOSE, MODE_PLUSDI, 1);
   if(bars_M5_check==1)
      pDI_M5 = iADX(NULL, 5, ADX_period, PRICE_CLOSE, MODE_PLUSDI, 1);
   if(bars_M15_check==1)
      pDI_M15 = iADX(NULL, 15, ADX_period, PRICE_CLOSE, MODE_PLUSDI, 1);
   if(bars_H1_check==1)
      pDI_H1 = iADX(NULL, 60, ADX_period, PRICE_CLOSE, MODE_PLUSDI, 1);

   static double mDI_M1 = iADX(NULL, 1, ADX_period, PRICE_CLOSE, MODE_MINUSDI, 1);
   static double mDI_M5 = iADX(NULL, 5, ADX_period, PRICE_CLOSE, MODE_MINUSDI, 1);
   static double mDI_M15 = iADX(NULL, 15, ADX_period, PRICE_CLOSE, MODE_MINUSDI, 1);
   static double mDI_H1 = iADX(NULL, 60, ADX_period, PRICE_CLOSE, MODE_MINUSDI, 1);

   if(bars_M1_check==1)
      mDI_M1 = iADX(NULL, 1, ADX_period, PRICE_CLOSE, MODE_MINUSDI, 1);
   if(bars_M5_check==1)
      mDI_M5 = iADX(NULL, 5, ADX_period, PRICE_CLOSE, MODE_MINUSDI, 1);
   if(bars_M15_check==1)
      mDI_M15 = iADX(NULL, 15, ADX_period, PRICE_CLOSE, MODE_MINUSDI, 1);
   if(bars_H1_check==1)
      mDI_H1 = iADX(NULL, 60, ADX_period, PRICE_CLOSE, MODE_MINUSDI, 1);

   if(pDI_M1>mDI_M1)
      DMI_M1_flg = 1;
   else
      if(pDI_M1<mDI_M1)
         DMI_M1_flg = -1;
      else
         DMI_M1_flg = 0;

   if(pDI_M5>mDI_M5)
      DMI_M5_flg = 1;
   else
      if(pDI_M5<mDI_M5)
         DMI_M5_flg = -1;
      else
         DMI_M5_flg = 0;

   if(pDI_M15>mDI_M15)
      DMI_M15_flg = 1;
   else
      if(pDI_M15<mDI_M15)
         DMI_M15_flg = -1;
      else
         DMI_M15_flg = 0;

   if(pDI_H1>mDI_H1)
      DMI_H1_flg = 1;
   else
      if(pDI_H1<mDI_H1)
         DMI_H1_flg = -1;
      else
         DMI_H1_flg = 0;

iMA(NULL, 1, MA_period, 0, MODE_SMA, PRICE_MEDIAN, 1)と、最後の引数を1とすることで最新の値を取得可能です。厳密には0が最新ですが、それだとまだ確定していない最新の変動し続けている値を取得してしまうので、トレードに使用するようなテクニカル指標を意図しているのであれば、基本的に1を使用します。
具体的には、以下のテクニカル指標をここで取得しています。

  • MA(1分足)

  • ATR(1時間足)

  • +DI(1分足、5分足、15分足、1時間足)

  • -DI(1分足、5分足、15分足、1時間足)

さらに、ティックが動く度にテクニカル指標を毎回取得するのは無駄が多く、EAの動作が遅くなったりしてしまいますので、それを排除するための処理を入れてあります。
例えば、bars_H1_checkというのは1時間足が切り替わったタイミングだけ1になるように設定していて、その場合のみ各数値を更新することになります。

1分以内の価格推移

このADM-EAでは、テクニカル指標に加えて、エントリー条件に直近のAsk値とBid値の推移を使用しています。

   static double min_bid = Bid;
   static double max_ask = Ask;
   static int min_bid_idx,max_ask_idx;

   for(int i=1; i<31; i++)
     {
      pre_bid[31-i] = pre_bid[31-i-1];
      pre_ask[31-i] = pre_ask[31-i-1];
     }

   pre_bid[0] = Bid;
   pre_ask[0] = Ask;

   min_bid_idx = ArrayMinimum(pre_bid,30);
   min_bid =  pre_bid[min_bid_idx];
   max_ask_idx = ArrayMaximum(pre_ask,30);
   max_ask =  pre_ask[max_ask_idx];

具体的には、直近30ティックのうち最小のBid値をmin_bid、最大のAsk値をmax_askとしています。
ここの部分は、アレンジしていただくことでトレードの精度が変わってくる可能性も高いですのでご自身でいろいろ試していただくのも良いかもしれません。1分足よりも短い一時的な価格変動を考慮できるので、より細かいエントリー条件を設定する場合などに応用可能です。

有料部分の内容

以上の準備を踏まえて、続きの処理を掲載しています。具体的には、エントリー条件やクローズ条件を設定して実際に注文を送信する部分、分解モンテカルロ法を用いたロット数調整、EAを再起動してもロジックが継続できるような対応等、一通り必要な処理のコード全文を掲載しています。

エントリー条件

具体的には、以下の条件を考慮しています。

  • 移動平均(1分足)とmax_ask、min_bidの乖離が所定の範囲内か

  • スプレッドが指定範囲内か

  • DMI_flgが1分足〜1時間足の全てで同じ方向を向いているか

最終的には、

  • order_flg = 1:buyエントリー条件を満たす

  • order_flg = -1:sellエントリー条件を満たす

  • order_flg = 0:エントリー条件を満たさない

という設計にしています。

クローズ条件

具体的には、以下の条件です。

  • エントリー時のATR(1時間足)の一定倍を利確幅に設定する

  • エントリー時のATR(1時間足)の一定倍をロスカット幅に設定する

現時点のATRではなく、エントリー時点のATRを保持しておいて、クローズ条件に用います。

分解モンテカルロ法

以下のルール通りに数列処理を行い、エントリー注文のロット数に反映させるように実装しています。

  1. 初めに[0, 1]という数列を準備する。

  2. 数列の左端と右端を足し合わせた数だけ賭ける。(最初は0+1=1)

  3. 負けた場合:賭け金額を数列の右端に加える

  4. 勝った場合:数列の一番右端と一番左端を消す

  5. 数列が完全に消えた場合は、数列をリセット。数列は[0, 1]から再びスタートする。

  6. 数列が1つ残った場合は、数字をなるべく均等に分解する。数列は、分解した2つの数字を並べた数列を使用する。
    例:数字分解時は、小さい数字を左に、大きい数字を右に書く。
    「5→[2, 3]」、「8→[4, 4]」、「11→[5, 6]」

利確した場合を勝ち、ロスカットした場合を負けとして、上記の判定を行います。

テキストファイルの記録と読み込み

このADM-EAのロジックでは、ポジションをエントリーした際のATRの値を基準に利確幅とロスカット幅を決定し、毎回の勝敗(利確 or ロスカット)に応じて次のロット数を決めるという仕組みになっています。
そのため、EAを一度停止したり再起動したりした場合でもロジックを継続可能なようにATRと勝敗履歴で定まる数列を記録しておく必要があります。
そこで、利確幅とロスカット幅を決定する際に使用するcurrent_ATRと、ロット数調整に必要な分解モンテカルロ法で使用する数列(lot_array)について、テキストファイルを作成して記録しておき、EA再起動の際に再び読み込むことで、途中から再開可能になるような仕組みを入れておきます。

なお、ファイルは以下のようにMQL5フォルダに用意されているFilesフォルダに作成されます。

mq4ファイルのダウンロード

有料部分の中では、この記事に掲載しているコードを全て盛り込んだmq4ファイル(MetaTrader4向けに書かれたプログラム)をダウンロード可能にしています。ただコンパイルするだけで、ADM-EA_note.ex4が作成可能ですので、特にいじらずにそのままEAとしても使用可能です。

なお、コンパイル方法やEAの設定方法については以下の記事にまとめていますのでご参照ください。

EAの動作確認用(無料版)

無料版として、デモ口座専用のEAは以下から自由にダウンロード可能ですので、動作確認等はこちらでご確認ください。

ADM-EA_note_demo.ex4

なお、有料部分内のADM-EA_note.mq4のint OnInit()内に以下のコードを加えてコンパイルしただけのものです。

if(IsDemo() == false)
     {
      Alert("Init failed\n\n","認証コードが不正です");
      return(INIT_FAILED);
     }
   Comment("Init succeeded");
   return(INIT_SUCCEEDED);

つまり、ロジックは有料版と全く同等ですので、事前の動作確認等にご利用できます。
なお、デモ口座専用に制限したい場合のコード例として利用できます。是非ご活用ください。

注意点

  • 当記事で掲載しているコードはこちらの記事で紹介しているような環境でMT4のインストールが完了していて、必要な準備が整っている上での実行を想定しています。

  • 記事執筆時点で稼働確認を行なっており、エラーが出ないことを確認しておりますが、その後の環境変化等で想定通りに稼働しない可能性はございます。動作保証等はいたしかねますのでご了承ください。

  • リアル口座にアクセスして取引を行うことも可能なコードになっておりますが、必ずデモ口座で事前に稼働確認をしていただくことを推奨いたします。

  • 当記事や他記事で解説しているロジック通りの動作を保証するものではございません。あくまでEA(FX自動売買ツール)開発のためのサンプルコードとしてご活用ください。

  • この記事はMT4用のEAに関する記事です。MT5用のEAについては以下の別記事がございます。

ここから先は

13,408字

¥ 5,000

よろしければサポートお願いします。いただいたサポートは今後の記事の執筆に活用させていただきます。