もっとも基本的なEAの作り方

ソースコードのダウンロード

本記事で紹介するプログラムのソースコードに加えて、複利機能、マーチンゲール、ナンピン、トレーリングストップ、ポジション制御機能を実装したプログラムを無料でダウンロードすることができます。

基本的なEAを作成する

世の中には様々な自動売買プログラム(EA)がありますが、どのEAにも大体共通して存在する機能がいくつかあります。それらの機能を網羅しつつ、皆さんが簡単にカスタマイズしやすいようにEAの作り方を解説していきます。

パラメータ設定

ユーザーがEAの設定項目を変更することができるようにします。
変数の宣言時にデータ型の前にinput修飾子をつけることで、ユーザーがMT4からパラメータ設定を変更することができるようになります。

注意

input修飾子ではなくextern修飾子をつけることでもパラメータ設定を変更できるようになります。しかし、extern修飾子はプログラム中からパラメータで設定した値を上書きできてしまうという欠点があり、mql5では廃止されています。なるべくinput修飾子を使うようにしましょう

以下の項目をパラメータ設定から変更できるようにします。

  • エントリー時のロット数
  • エントリーを許容するスリッページ
  • エントリーを制限するスプレッド
  • ポジションのマジックナンバー
  • ポジションのコメント
  • 利確のpips幅
  • 損切のpips幅
//EA基本設定
input double Lots = 0.1;          //ロット数
input int Slippage = 4;           //許容スリッページ(pips)
input int MaxSpread = 5;          //スプレッド制限(pips)
input int MagicNumber = 54637874; //マジックナンバー
input string EntryComment = "";   //コメント
input int TakeProfit = 20;        //利確(pips)
input int LossCut = 10;           //損切(pips)

基本的にユーザーはpipsの扱いに慣れているため、pointではなくpipsの単位に合わせるようにしてあげます。

フィールド

フィールドはパラメータ設定の直下に書きます。
主にプログラム全体で使う変数などをここで宣言することになります。

double valuePerPips;              //1pipsあたり価格
double spread;                    //現在のスプレッド

OnInit関数

OnInit関数はEAをチャートにセットした瞬間に実行される、mql4側で用意された関数です。EAをセットしてからEAをチャートから除外するまでの間で1度しか実行されません。そのため、EAをセットした瞬間だけ実行したい処理を書きます。

int OnInit()
{
   valuePerPips = Point * 10;   //⓵
   HideTestIndicators(true);    //⓶
   return(INIT_SUCCEEDED);
}


変数valuePerPipsには、1pipsあたりの価格を格納します。mql4では相場の値動きをpipsで取得することができないため、価格からpipsに変換してあげる必要があります。そのために、OnInit関数であらかじめ1pipsあたりの価格を求めておきます。Pointは通貨ペアの最小単位を取得することができる変数です。例えばUSDJPYの場合Pointは0.001となります。大体の通貨ペアではPointに10をかけた値が、その通貨ペアの1pipsあたりの価格となります。


EAはビジュアルモードでバックテストを稼働した時、EA内部で使用されている指標(移動平均線やBBなど)が一覧で表示されるようになっています。もしもユーザーに、EAが使用している指標を知られたくない場合はHideTestIndicators関数を呼び出し引数にtrueを渡すことで隠すことができます。

OnTick関数

void OnTick()
{
   //⓵スプレッドの計算
   spread = (Ask - Bid) / valuePerPips;

   //⓶保有ポジション計算部分
   int buyCount = 0;
   int sellCount = 0;     
   if(OrdersTotal() > 0){
      for(int i = 0; i < OrdersTotal(); i++){
         if(OrderSelect(i, SELECT_BY_POS) == true && OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol()){
            if(OrderType() == OP_BUY){
               buyCount++;
            }
            if(OrderType() == OP_SELL){
               sellCount++;
            }
         }
      }
   }

   //⓷エントリー部分
   if(buyCount == 0 && sellCount == 0 && spread >= MaxSpread){
      if(買いの条件){
         int ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage * 10, Ask - LossCut * valuePerPips,TakeProfit == 0 ? 0 : Ask + TakeProfit * valuePerPips, EntryComment, MagicNumber, 0, clrRed);
         if( ticket < 0)Print("Error OrderSend:" + (string)GetLastError());
         else buyCount++;
      }
   }
   if(sellCount == 0 && buyCount == 0 && spread >= MaxSpread){
      if(売りの条件){
         int ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage * 10, Bid + LossCut * valuePerPips,TakeProfit == 0 ? 0 : Bid - TakeProfit * valuePerPips, EntryComment, MagicNumber, 0, clrBlue);
         if( ticket < 0)Print("Error OrderSend:" + (string)GetLastError());
         else sellCount++;
      }
   }
}

①スプレッドの計算
スプレッドの計算部分です。valuePerPips(1pipsあたりの価格)とは違い、スプレッドは毎ティックごとに値が変化します。そのため、OnTick関数の一番上の部分で計算してあげます。Ask(買値)-Bid(売値)で単純なスプレッド価格を求めることができますが、valuePerPipsで割ってあげることで価格をpipsに変換しています。

②保有ポジション計算部分
EAは自身が保有しているポジションによってエントリーを制御するような仕組みがいくつかあります。例えば両建てやトレーリングストップなどは、現在保有しているポジションの数や含み損益によって挙動を制御するような機能です。今回は買いと売り共に1ポジションだけ保有するようにしたいため、buyCountとsellCountにそれぞれ現在保有している買いポジション数と売りポジション数を格納してあげます。

③エントリー部分
「買いポジション数が0」かつ「売りポジション数が0」という条件にすることで、1ポジションしか保有しないようにし、さらに両建てを禁止するようにしています。
OrderSend関数はその名の通り買いや売りのオーダーを行うための関数です。引数には以下の情報を格納してあげる必要があります。

int  OrderSend(
   string   symbol,               // 通貨ペア名
   int      cmd,                  // 注文タイプ
   double   volume,               // ロット数
   double   price,                // 注文価格
   int      slippage,             // スリップページ
   double   stoploss,             // ストップロス価格
   double   takeprofit,           // リミット価格
   string   comment     = NULL,   // コメント
   int      magic       = 0,      // マジックナンバー(識別用)
   datetime expiration  = 0,      // 有効期限
   color    arrow_color = clrNONE // 色
);
注意

買いエントリーの時はエントリー価格がAsk(買値)、売りエントリーの時はエントリー価格がBid(売値)を指定する必要があります。

プログラム全文

#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict

//EA基本設定
input double Lots = 0.1;          //ロット数
input int Slippage = 4;           //許容スリッページ(pips)
input int MaxSpread = 5;          //スプレッド制限(pips)
input int MagicNumber = 54637874; //マジックナンバー
input string EntryComment = "";   //コメント
input int TakeProfit = 20;        //利確(pips)
input int LossCut = 10;           //損切(pips)

double valuePerPips;              //1pipsあたり価格
double spread;                    //現在のスプレッド

int OnInit()
{
   valuePerPips = Point * 10;   //⓵
   HideTestIndicators(true);    //⓶
   return(INIT_SUCCEEDED);
}

void OnTick()
{
   //⓵スプレッドの計算
   spread = (Ask - Bid) / valuePerPips;

   //⓶保有ポジション計算部分
   int buyCount = 0;
   int sellCount = 0;     
   if(OrdersTotal() > 0){
      for(int i = 0; i < OrdersTotal(); i++){
         if(OrderSelect(i, SELECT_BY_POS) == true && OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol()){
            if(OrderType() == OP_BUY){
               buyCount++;
            }
            if(OrderType() == OP_SELL){
               sellCount++;
            }
         }
      }
   }

   //⓷エントリー部分
   if(buyCount == 0 && sellCount == 0 && spread >= MaxSpread){
      if(買いの条件){
         int ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, Slippage * 10, Ask - LossCut * valuePerPips,TakeProfit == 0 ? 0 : Ask + TakeProfit * valuePerPips, EntryComment, MagicNumber, 0, clrRed);
         if( ticket < 0)Print("Error OrderSend:" + (string)GetLastError());
         else buyCount++;
      }
   }
   if(sellCount == 0 && buyCount == 0 && spread >= MaxSpread){
      if(売りの条件){
         int ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, Slippage * 10, Bid + LossCut * valuePerPips,TakeProfit == 0 ? 0 : Bid - TakeProfit * valuePerPips, EntryComment, MagicNumber, 0, clrBlue);
         if( ticket < 0)Print("Error OrderSend:" + (string)GetLastError());
         else sellCount++;
      }
   }
}