ロットサイズイメージ

ここでは、MT4のEAによる取引で連勝中はロット数を増やして、連敗中はロット数を減らす機能を追加する方法を解説します。

過去の記事【MT4のEAに複利(MM)機能を搭載する方法】のサンプルEAに、今回の機能を追加していきます。
過去の記事【MT4のEAに複利(MM)機能を搭載する方法】へ

MM機能搭載例のサンプルEA【Sample_MM.mq4】をダウンロード

ロット数増減の計算ルール

ロット数の増減量

連勝中のロット数は、2連勝で10%増し、3連勝で20%増しとしていき、最大100%(2倍)までとします。
連敗中のロット数は、2連敗で1/2(2分の1)、3連敗で1/3(3分の1)としていきます。

勝敗の基準

EA稼働開始または前回勝敗判定以降に決済済みのポジションがあり、現在保有ポジションがない状態に勝敗判定を行い、決済済みのポジションの損益合計がプラスなら勝ち、マイナスなら負け、±0ならノーカウントとします。

追加コードの解説

Dr.EADr.EA

今回完成したEAのコードがこちらじゃ。

extern double  Lots           = 0.1;
extern bool    UseMM          = true;
extern double  BalancePerLots = 100000;

extern int     MagicNumber    = 20191114;

double max_lot;   // 最大ロット数
double min_lot;   // 最小ロット数
int lot_digit;    // ロット小数桁数

int      win_cnt;       // 連勝・連敗数
datetime last_cnt_time; // 最終判定時間

int OnInit()
  {
   max_lot = MarketInfo(Symbol(), MODE_MAXLOT);
   min_lot = MarketInfo(Symbol(), MODE_MINLOT);
   
   double lotstep = MarketInfo(Symbol(), MODE_LOTSTEP);
   if(NormalizeDouble(lotstep, 3) <= 0.001)
      lot_digit = 3;
   else if(NormalizeDouble(lotstep, 2) <= 0.01)
      lot_digit = 2;
   else if(NormalizeDouble(lotstep, 1) <= 0.1)
      lot_digit = 1;
   else
      lot_digit = 0;
   
   win_cnt = 0;
   last_cnt_time = TimeCurrent();
   
   if (IsTesting() == false) return(INIT_FAILED);
   return(INIT_SUCCEEDED);
  }

void OnTick()
  {
   int i;
   
   // ポジションの有無を確認
   int pos_cnt = 0;
   for (i=0; i<OrdersTotal(); i++)
   {
      if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) return;
      if (OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue;
      
      pos_cnt++;
   }
   
   // 連勝・連敗カウント
   static int last_hist_cnt = 0;
   int now_hist_cnt = OrdersHistoryTotal();
   
   if (pos_cnt == 0 && last_hist_cnt != now_hist_cnt)
   {
      last_hist_cnt = now_hist_cnt;
      
      double total_profit = 0;
      
      for (i=0; i<now_hist_cnt; i++)
      {
         if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) == false) continue;
         if (OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue;
         if (OrderCloseTime() <= last_cnt_time) continue;
         
         total_profit += OrderProfit() + OrderSwap() + OrderCommission();
      }
      
      if (total_profit > 0)
      {
         if (win_cnt < 0)
            win_cnt = 1;
         else
            win_cnt++;
      }
      
      if (total_profit < 0)
      {
         if (win_cnt > 0)
            win_cnt = -1;
         else
            win_cnt--;
      }
      
      last_cnt_time = TimeCurrent();
   }
   
   // 適当な取引
   if (pos_cnt == 0)
   {
      // ロット数を算出
      double lots = Lots;
      if (UseMM == true) lots = AccountBalance() / BalancePerLots * Lots;
      
      // 連勝時のロット数
      if (win_cnt > 1) lots += lots * 10 * MathMin(win_cnt-1, 10) * 0.01;
      // 連敗時のロット数
      if (win_cnt < -1) lots *= -1.0 / win_cnt;
      
      lots = MathFloor(lots * MathPow(10, lot_digit)) * MathPow(0.1, lot_digit);
      if (lots > max_lot) lots = max_lot;
      if (lots < min_lot) lots = 0;
      
      if (lots > 0)
         int ticket = OrderSend(Symbol(), OP_BUY, lots, Ask, 0, Ask - 100*Point, Ask + 100*Point, NULL, MagicNumber, 0, clrBlue);
      else
         Print("残高不足です");
   }
  }

今回完成EAファイル【Sample_MM_LotSize.mq4】をダウンロード

当該EAでのポジションかを判別可能にする

サンプルEAでは、テスターでのみの実行を前提としたコードなので、パラメーターMagicNumberを追加して、他のポジションをカウントしないようにします。

extern int     MagicNumber    = 20191114;

OnTick()関数の最初に次のコードを追加します。

   int i;

   // ポジションの有無を確認
   int pos_cnt = 0;
   for (i=0; i<OrdersTotal(); i++)
   {
      if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES) == false) return;
      if (OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue;
      
      pos_cnt++;
   }

ポジションの有無をpos_cntで判定するようにif (pos_cnt == 0)としておき、OrderSend()関数のマジックナンバー部分にMagicNumberを指定します。

         int ticket = OrderSend(Symbol(), OP_BUY, lots, Ask, 0, Ask - 100*Point, Ask + 100*Point, NULL, MagicNumber, 0, clrBlue);

グローバル変数を追加

グローバル変数には、連勝・連敗をカウントしておく整数変数と勝敗判定した時の時間を格納する変数を宣言します。

int      win_cnt;       // 連勝・連敗数
datetime last_cnt_time; // 最終判定時間

連敗の場合は、win_cntにマイナス値を入れていくようにします。

OnInit()関数内でグローバル変数を初期化

今回は、EA再稼働やパラメーター設定変更時に前回稼働時の連勝・連敗データを引き継がず、リセットするようにします。

   win_cnt = 0;
   last_cnt_time = TimeCurrent();

OnTick()関数内で勝敗を判定

OnTick()関数内に次のコードを追加して、勝敗を判定します。

   static int last_hist_cnt = 0;
   int now_hist_cnt = OrdersHistoryTotal();
   
   if (pos_cnt == 0 && last_hist_cnt != now_hist_cnt)
   {
      last_hist_cnt = now_hist_cnt;
      
      double total_profit = 0;
      
      for (i=0; i<now_hist_cnt; i++)
      {
         if (OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) == false) continue;
         if (OrderSymbol() != Symbol() || OrderMagicNumber() != MagicNumber) continue;
         if (OrderCloseTime() <= last_cnt_time) continue;
         
         total_profit += OrderProfit() + OrderSwap() + OrderCommission();
      }
      
      if (total_profit > 0)
      {
         if (win_cnt < 0)
            win_cnt = 1;
         else
            win_cnt++;
      }
      
      if (total_profit < 0)
      {
         if (win_cnt > 0)
            win_cnt = -1;
         else
            win_cnt--;
      }
      
      last_cnt_time = TimeCurrent();
   }
   もしポジションなしで、前回勝敗判定時の取引履歴と異なる場合、
   {
      取引履歴数を記録しておく。
      前回判定時の時間より後に決済されたポジションの損益合計を算出。
      
      もし損益がプラスなら
      {
         もしwin_cnt がマイナスならwin_cntに1を代入。
         そうでないなら、win_cntに1を足す。
      }
      もし損益がマイナスなら
      {
         もしwin_cnt がプラスならwin_cntに-1を代入。
         そうでないなら、win_cntから1を引く。
      }
      判定時間を記録しておく。
   }

エントリー注文前にロット数増減をする

UseMMのロット算出の下に、次のコードを追加します。

      // 連勝時のロット数
      if (win_cnt > 1) lots += lots * 10 * MathMin(win_cnt-1, 10) * 0.01;
      // 連敗時のロット数
      if (win_cnt < -1) lots *= (-1.0 / win_cnt);

UseMMの括弧内の切り捨てやmax_lotなどの補正は、上記コードの下に移動します。

あとがき

最初、連敗時のロット数算出部分を、if (win_cnt < -1) lots *= (-1 / win_cnt);にしていて、ロット数が0になってしまいました。
整数÷整数は切り捨て整数になることを常に意識していないといけないですね^^;

次回は、今回のロット算出法を自作EAに搭載して検証をします。