ドテンイメージ

ここでは、前回記事のコメントでご質問頂いた移動平均線と価格のクロスによるドテン取引をするEAの作成方法について解説します。

にゃんたにゃんた

ドテンとは、保有ポジションを決済してすぐに反対のポジションをエントリーすることにゃ。

新MQL4で書き直したEAコードの解説をします。

今回完成EA【test10_2019.mq4】をダウンロード

EAコードの解説

Dr.EADr.EA

最初にEAのコードを見るぞい

// 外部パラメーター
extern double              Lots              = 0.01;
extern double              TakeProfit        = 0.0;
extern double              StopLoss          = 0.0;
extern double              Slippage          = 1.0;
extern string              EA_Comment        = "MA Doten EA";
extern int                 MagicNumber       = 20191122;

extern int                 MA_Period         = 21;
extern int                 MA_Shift          = 0;
extern ENUM_MA_METHOD      MA_Method         = MODE_SMA;
extern ENUM_APPLIED_PRICE  MA_Applied        = PRICE_CLOSE;


// グローバル変数
double _point;       // Pips入力値用の変数

int   entry_bar_buy; // 買い注文した時のバーの数
int   entry_bar_sell;// 売り注文した時のバーの数


//+------------------------------------------------------------------+
//| EA稼働開始時に実行される関数                                     |
//+------------------------------------------------------------------+
int OnInit()
{
   // パラメーターをPips入力にする為の処理
   _point = Point;
   if (Digits % 2 == 1)
   {
      _point *= 10;
      Slippage *= 10;
   }
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| EA稼働終了時に実行される関数                                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{

   
}

//+------------------------------------------------------------------+
//| ティック毎に実行される関数                                       |
//+------------------------------------------------------------------+
void OnTick()
{
   int i;
   
   // 保有ポジションを確認 ------------------------------------------
   int pos_ticket_buy = 0; // 買いポジション保有時はここに注文番号が格納される
   int pos_ticket_sell = 0;// 売りポジション保有時はここに注文番号が格納される
   
   for (i=0; i<OrdersTotal(); i++)
   {
      if (OrderSelect(i, SELECT_BY_POS) == false) return;
      if (OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue;
      
      if (OrderType() == OP_BUY) pos_ticket_buy = OrderTicket();
      if (OrderType() == OP_SELL) pos_ticket_sell = OrderTicket();
   }
   
   // シグナル判定 -------------------------------------------------
   
   // MAの値取得
   double ma_1 = iMA(NULL, 0, MA_Period, MA_Shift, MA_Method, MA_Applied, 1);
   double ma_2 = iMA(NULL, 0, MA_Period, MA_Shift, MA_Method, MA_Applied, 2);
   
   // シグナル判定
   int sign = 0;
   
   if (ma_2 >= Close[2] && ma_1 < Close[1]) sign = 1; // 買いシグナル
   if (ma_2 <= Close[2] && ma_1 > Close[1]) sign = -1;// 売りシグナル
   
   
   // ポジション保有時の処理 --------------------------------------
   
   // 買いポジション保有時
   if (pos_ticket_buy > 0)
   {
      if (sign == -1)   // 売りシグナル発生
         if (OrderClose(pos_ticket_buy, Lots, Bid, (int)Slippage, clrYellow) == true)
            pos_ticket_buy = 0;
   }
   // 売りポジション保有時
   if (pos_ticket_sell > 0)
   {
      if (sign == 1)    // 買いシグナル発生
         if (OrderClose(pos_ticket_sell, Lots, Ask, (int)Slippage, clrYellow) == true)
            pos_ticket_sell = 0;
   }
   
   
   // ポジション無しの時の処理 -----------------------------------
   int ticket;
   double sl = 0, tp = 0;
   
   if (pos_ticket_buy + pos_ticket_sell == 0)
   {
      // 買い注文処理
      if (sign == 1 && entry_bar_buy != Bars)
      {
         if (StopLoss > 0) sl = Ask - StopLoss * _point;
         if (TakeProfit > 0) tp = Ask + TakeProfit * _point;
         
         ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, (int)Slippage, sl, tp, EA_Comment, MagicNumber, 0, clrBlue);
         if (ticket > 0) entry_bar_buy = Bars;
      }
      
      // 売り注文処理
      if (sign == -1 && entry_bar_sell != Bars)
      {
         if (StopLoss > 0) sl = Bid + StopLoss * _point;
         if (TakeProfit > 0) tp = Bid - TakeProfit * _point;
         
         ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, (int)Slippage, sl, tp, EA_Comment, MagicNumber, 0, clrRed);
         if (ticket > 0) entry_bar_sell = Bars;
      }
   }
}

外部パラメーター

最初に、外部パラメーターを次のように設置します。

extern double              Lots              = 0.01;
extern double              TakeProfit        = 0.0;
extern double              StopLoss          = 0.0;
extern double              Slippage          = 1.0;
extern string              EA_Comment        = "MA Doten EA";
extern int                 MagicNumber       = 20191122;

extern int                 MA_Period         = 21;
extern int                 MA_Shift          = 0;
extern ENUM_MA_METHOD      MA_Method         = MODE_SMA;
extern ENUM_APPLIED_PRICE  MA_Applied        = PRICE_CLOSE;

ロット数やTP/SL幅などの注文時に指定するパラメーターと、移動平均線用のパラメーターです。
今回はドテン売買なので、TakeProfit/StopLossは初期値0にしてTP/SLを設定しない初期値設定にしています。

グローバル変数

次にグローバル変数を宣言します。グローバル変数は、どの関数からもアクセス可能で、関数処理終了後にも格納されている値がリセットされないという特徴があります。

double _point;       // Pips入力値用の変数
int   entry_bar_buy; // 買い注文した時のバーの数
int   entry_bar_sell;// 売り注文した時のバーの数

OnInit()関数内の処理

OnInit()関数は、EA稼働開始時に実行される関数です。
OnInit()関数内で、パラメーターTakeProfit, StopLoss, SlippageをPips入力にする為のコードを追加します。

   _point = Point;
   if (Digits % 2 == 1)
   {
      _point *= 10;
      Slippage *= 10;
   }
_point に Pointの値を代入。
もし、Digitsを2で割った余りが1なら、
   _point に10を掛ける
   Slippage に10を掛ける

Pointには、価格表示の最小単位の値が入っています。例えば、レート表示が123.456のように小数点以下3桁までの表示であればPointには0.001が格納されています。

Digitsには、Pointの小数点以下桁数が入っています。例えば、Pointが0.001なら小数点以下3桁なのでDigitsには3が格納されています。

上記のコードの処理をすることで、例えばレート表示が123.456のように小数点以下3桁の場合、_pointには0.01が格納されていることになります。この場合の0.01が現在一般的に使用されているPips表示でいう1.0pipsとなります。
仮にTakeProfit = 10 の場合、TakeProfit * _point の値は、0.100で10.0Pipsの値となります。

Slippageに関しても、0.3 Pips と入力した場合、レート表示小数以下3桁5桁の場合に 3 Point になるように10倍しています。

OnTick()関数内の構成

OnTick()関数内では次の順に書いています。

  1. 保有ポジションを確認

  2. MAと終値によるシグナル判定

  3. ポジション保有時の処理

  4. ポジション無しの時の処理

保有ポジションを確認

稼働EAでのポジションの有無を確認するために次のコードを追加します。(変数iは予め宣言しておきます。)

   int pos_ticket_buy = 0; // 買いポジション保有時はここに注文番号が格納される
   int pos_ticket_sell = 0;// 売りポジション保有時はここに注文番号が格納される
   
   for (i=0; i<OrdersTotal(); i++)
   {
      if (OrderSelect(i, SELECT_BY_POS) == false) return;
      if (OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue;
      
      if (OrderType() == OP_BUY) pos_ticket_buy = OrderTicket();
      if (OrderType() == OP_SELL) pos_ticket_sell = OrderTicket();
   }
買いポジション注文番号を0にしておく
売りポジション注文番号を0にしておく

i を 0 から順に保有ポジション数分増やしながら{}内の繰り返しをする
{
   i 段目のポジション(注文)を選択する
   それの通貨ペアやマジックナンバーがこのEAのものではない場合はスキップ

   (以下、スキップしなかった場合の処理)
   買いポジションの場合、pos_ticket_buy にその注文番号を格納する
   売りポジションの場合、pos_ticket_sell にその注文番号を格納する
}

この処理を実行してpos_ticket_buyとpos_ticket_sellのどちらも0の場合は、ポジションを保有していないことになります。

なぜ、注文番号を格納するのかというと、ポジションを決済する際に注文番号が必要になり、その際のポジションの選択を省略してコードをシンプルにしたいためです。解説用EAでもありますし。。
(手抜きではないですよ。。。手抜きでは。)

MAと終値によるシグナル判定

次のコードを追加して、MAの値の取得と、変数signにシグナル判定結果を格納する処理をします。

   // MAの値取得
   double ma_1 = iMA(NULL, 0, MA_Period, MA_Shift, MA_Method, MA_Applied, 1);
   double ma_2 = iMA(NULL, 0, MA_Period, MA_Shift, MA_Method, MA_Applied, 2);
   
   // シグナル判定
   int sign = 0;
   
   if (ma_2 >= Close[2] && ma_1 < Close[1]) sign = 1; // 買いシグナル
   if (ma_2 <= Close[2] && ma_1 > Close[1]) sign = -1;// 売りシグナル

iMA()関数でパラメーターで指定した移動平均線の1つ前と2つ前の値を取得しています。

シグナル判定では、2つ前のローソク足の終値がMA以下で、1つ前のローソク足の終値がMAより上ならsignに1を代入して買いシグナルとしています。売りシグナルはその反対で、signに-1を代入します。シグナルが発生しないときは、signは0のままです。

ポジション保有時の処理

ポジション保有時の処理として、次のように買いポジション決済と売りポジション決済に分けて書いています。

   // 買いポジション保有時
   if (pos_ticket_buy > 0)
   {
      if (sign == -1)   // 売りシグナル発生
         if (OrderClose(pos_ticket_buy, Lots, Bid, (int)Slippage, clrYellow) == true)
            pos_ticket_buy = 0;
   }
   // 売りポジション保有時
   if (pos_ticket_sell > 0)
   {
      if (sign == 1)    // 買いシグナル発生
         if (OrderClose(pos_ticket_sell, Lots, Ask, (int)Slippage, clrYellow) == true)
            pos_ticket_sell = 0;
   }
買いポジション保有中で
   売りシグナル発生なら、
      買いポジション決済注文して決済されたら、
         pos_ticket_buy に0を代入

売りポジション保有中で
   (以下省略)

ここで注文番号が格納されているpos_ticket_buyやpos_ticket_sellを活用しています。決済された場合に0を代入するのは、後程説明します。

パラメーターSlippageは実数型にしてありますので、(int)を付けて整数に型変換(キャスト)しています。

決済注文のロット数にパラメーターのLotsを指定しているので、ポジション保有中にLotsを変更してEAを再稼働すると、決済エラーになるか部分決済になってしまいますので、注意しましょう。
そうならないためにも、決済前に保有ポジションを選択してOrderLots()関数でそのポジションのロット数を取得して決済する方がよいです。
今回のは手抜きではないっス(汗)

ポジション無しの時の処理

ポジション無しの時の処理コードは次のとおりです。

   int ticket;
   double sl = 0, tp = 0;
   
   if (pos_ticket_buy + pos_ticket_sell == 0)
   {
      // 買い注文処理
      if (sign == 1 && entry_bar_buy != Bars)
      {
         if (StopLoss > 0) sl = Ask - StopLoss * _point;
         if (TakeProfit > 0) tp = Ask + TakeProfit * _point;
         
         ticket = OrderSend(Symbol(), OP_BUY, Lots, Ask, (int)Slippage, sl, tp, EA_Comment, MagicNumber, 0, clrBlue);
         if (ticket > 0) entry_bar_buy = Bars;
      }
      
      // 売り注文処理
      if (sign == -1 && entry_bar_sell != Bars)
      {
         if (StopLoss > 0) sl = Bid + StopLoss * _point;
         if (TakeProfit > 0) tp = Bid - TakeProfit * _point;
         
         ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, (int)Slippage, sl, tp, EA_Comment, MagicNumber, 0, clrRed);
         if (ticket > 0) entry_bar_sell = Bars;
      }
   }
整数変数 ticket を宣言(エントリー注文時に注文番号を格納します)
実数変数 sl と tp を宣言してそれぞれ初期値は0
   
買い・売りの注文番号がともに0の場合で、

   もし、買いシグナル発生中で買いエントリーしたローソク足ではない場合
      StopLoss が0より大きいとき sl に Ask - StopLoss ピプス を代入
      TakeProfit が0より大きいとき tp に Ask + TakeProfit ピプス を代入
      買い注文実行して注文番号をticketに代入
      注文番号が0より大きい場合 entry_bar_buy にバーの合計数を代入

   もし、買いシグナル発生中で買いエントリーしたローソク足ではない場合
      (以下、売り注文処理 省略)

決済注文した後に、pos_ticket_buyやpos_ticket_sellを0にしたのは、そのティック内で最初のエントリー条件の「買い・売りの注文番号がともに0の場合で」を満たすための処理です。
もう一度保有ポジションを確認するという方法もありますが、その分必要な処理数が多くなってしまいます。

OrderSend()関数でエントリー注文して、注文が通ったら注文番号が返ってきて、注文に失敗したら-1が返ってきます。その値を変数ticketに代入して、注文が通った場合のみentry_bar_buy(またはentry_bar_sell)にバーの数を代入します。

あとがき

シンプルなEAにしたつもりですが、説明不足の部分もあるかと思います。疑問に思ったところがありましたらお気軽にコメントで質問してくださいね!

では、今回はこの辺で。