ブログタイトルを「FX自動売買システム開発部」から「autoFX」に変更しました!

信頼できる仕掛け注文関数

本記事で公開しているライブラリーは公開当時のままで、古くなっております。最新版のライブラリーはメルマガ登録いただくことでダウンロードできます。

MQL4標準の仕掛け注文関数は穴だらけ

各種仕掛け注文(成行、指値、逆指値)を出す、MQL4の標準関数にOrderSend()があります。

注文が必ず通るのであれば、OrderSend()を使えば良いわけですが、実際はそうでもありません。

市場の動きが早くて許容スリッページ内で執行できなかったり、ネットワークの調子が悪くて通信エラーが発生したり、FX業者によっては約定拒否されたりして、うまく注文できない場合があります。

その場合、OrderSend()だけでは不十分で、注文失敗時のフォローが必要になります。

というわけで、OrderSend()の注文が失敗した時を考慮した仕掛け注文関数を紹介します。

//+------------------------------------------------------------------+
//|【関数】信頼できる仕掛け注文                                      |
//|                                                                  |
//|【引数】 IN OUT  引数名             説明                          |
//|        --------------------------------------------------------- |
//|         ○      aSymbol            通貨ペア                      |
//|         ○      aCmd               注文種別                      |
//|         ○      aVolume            ロット数                      |
//|         ○      aPrice             仕掛け価格                    |
//|         ○      aSlippage          スリッページ(ポイント)      |
//|         ○      aStoploss          損切り価格                    |
//|         ○      aTakeprofit        利食い価格                    |
//|         △      aComment           コメント                      |
//|         △      aMagic             マジックナンバー              |
//|         △      aExpiration        待機注文の有効期限            |
//|         △      aArrow_color       チャート上の矢印の色          |
//|                                                                  |
//|【戻値】チケット番号(エラーの場合は、-1)                        |
//|                                                                  |
//|【備考】△:既定値あり                                            |
//+------------------------------------------------------------------+
int orderSendReliable(string aSymbol, int aCmd, double aVolume, double aPrice, int aSlippage, double aStoploss, double aTakeprofit, string aComment = NULL, int aMagic = 0, datetime aExpiration = 0, color aArrow_color = CLR_NONE)
{
  int ticket = -1;

  int startTime = GetTickCount();

  Print("Attempted orderSendReliable(" + aSymbol + ", " + orderType2String(aCmd) + ", " + aVolume + "lots, " + aPrice + ", Slippage:" + aSlippage + ", SL:"+ aStoploss + ", TP:" + aTakeprofit + ", Comment:" + aComment + ", Magic:" + aMagic + ", Expiration:" + TimeToStr(aExpiration) + ", ArrowColor:" + aArrow_color + ")");

  double digits = MarketInfo(aSymbol, MODE_DIGITS);

  aStoploss   = NormalizeDouble(aStoploss,   digits);
  aTakeprofit = NormalizeDouble(aTakeprofit, digits);

  double stopLevel   = MarketInfo(aSymbol, MODE_STOPLEVEL) * MarketInfo(aSymbol, MODE_POINT);
  double freezeLevel = MarketInfo(aSymbol, MODE_FREEZELEVEL) * MarketInfo(aSymbol, MODE_POINT);

  while(true){
    if(IsStopped()){
      Print("Trading is stopped!");
      return(-1);
    }

    if(GetTickCount() - startTime > MAX_RETRY_TIME * MILLISEC_2_SEC){
      Print("Retry attempts maxed at " + MAX_RETRY_TIME + "sec");
      return(-1);
    }

    // MarketInfo関数でレートを取得しており、定義済変数であるAskとBidは未使用のため、不要のはずだけど、念のため
    RefreshRates();

    double ask = NormalizeDouble(MarketInfo(aSymbol, MODE_ASK), digits);
    double bid = NormalizeDouble(MarketInfo(aSymbol, MODE_BID), digits);

    if(aCmd == OP_BUY){
      aPrice = ask;
    }else if(aCmd == OP_SELL){
      aPrice = bid;
    }

    // 仕掛け/損切り/利食いがストップレベル未満かフリーズレベル以下の場合、エラー
    if(aCmd == OP_BUY){
      if(MathAbs(bid - aStoploss) < stopLevel){
        Print("StopLevel: SL was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aTakeprofit - bid) < stopLevel){
        Print("StopLevel: TP was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(bid - aStoploss) <= freezeLevel){
        Print("FreezeLevel: SL was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aTakeprofit - bid) <= freezeLevel){
        Print("FreezeLevel: TP was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }
    }else if(aCmd == OP_SELL){
      if(MathAbs(aStoploss - ask) < stopLevel){
        Print("StopLevel: SL was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(ask - aTakeprofit) < stopLevel){
        Print("StopLevel: TP was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aStoploss - ask) <= freezeLevel){
        Print("FreezeLevel: SL was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(ask - aTakeprofit) <= freezeLevel){
        Print("FreezeLevel: TP was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }
    }else if(aCmd == OP_BUYLIMIT){
      if(MathAbs(ask - aPrice) < stopLevel){
        Print("StopLevel: OpenPrice was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aPrice - aStoploss) < stopLevel){
        Print("StopLevel: SL was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aTakeprofit - aPrice) < stopLevel){
        Print("StopLevel: TP was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(ask - aPrice) <= freezeLevel){
        Print("FreezeLevel: OpenPrice was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }
    }else if(aCmd == OP_SELLLIMIT){
      if(MathAbs(aPrice - bid) < stopLevel){
        Print("StopLevel: OpenPrice was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aStoploss - aPrice) < stopLevel){
        Print("StopLevel: SL was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aPrice - aTakeprofit) < stopLevel){
        Print("StopLevel: TP was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aPrice - bid) <= freezeLevel){
        Print("FreezeLevel: OpenPrice was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }
    }else if(aCmd == OP_BUYSTOP){
      if(MathAbs(aPrice - ask) < stopLevel){
        Print("StopLevel: OpenPrice was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aPrice - aStoploss) < stopLevel){
        Print("StopLevel: SL was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aTakeprofit - aPrice) < stopLevel){
        Print("StopLevel: TP was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aPrice - ask) <= freezeLevel){
        Print("FreezeLevel: OpenPrice was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }
    }else if(aCmd == OP_SELLSTOP){
      if(MathAbs(bid - aPrice) < stopLevel){
        Print("StopLevel: OpenPrice was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aStoploss - aPrice) < stopLevel){
        Print("StopLevel: SL was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(aPrice - aTakeprofit) < stopLevel){
        Print("StopLevel: TP was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }else if(MathAbs(bid - aPrice) <= freezeLevel){
        Print("FreezeLevel: OpenPrice was too close to brokers min distance (" + stopLevel + ")");
        return(-1);
      }
    }

    if(IsTradeContextBusy()){
      Print("Must wait for trade context");
    }else{
      ticket = OrderSend(aSymbol, aCmd, aVolume, aPrice, aSlippage, aStoploss, aTakeprofit, aComment, aMagic, aExpiration, aArrow_color);

      if(ticket > 0){
        Print("Success! Ticket #", ticket, " ", orderType2String(aCmd), " order placed, details follow");
        bool selected = OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES);
        OrderPrint();
        return(ticket);
      }

      int err = GetLastError();

      // 一時的エラーの場合はリトライするが、恒常的エラーの場合は処理中断(リトライしてもエラーになるため)
      if(err == ERR_NO_ERROR || 
         err == ERR_COMMON_ERROR ||
         err == ERR_SERVER_BUSY ||
         err == ERR_NO_CONNECTION ||
         err == ERR_TRADE_TIMEOUT ||
         err == ERR_INVALID_PRICE ||
         err == ERR_PRICE_CHANGED ||
         err == ERR_OFF_QUOTES ||
         err == ERR_BROKER_BUSY ||
         err == ERR_REQUOTE ||
         err == ERR_TRADE_CONTEXT_BUSY){
        Print("Temporary Error: " + err + " " + ErrorDescription(err) + ". waiting");
      }else{
        Print("Permanent Error: " + err + " " + ErrorDescription(err) + ". giving up");
        return(-1);
      }

      // 最適化とバックテスト時はリトライは不要
      if(IsOptimization() || IsTesting()){
        return(-1);
      }
    }

    Sleep(SLEEP_TIME * MILLISEC_2_SEC);
  }

  return(-1);
}

//+------------------------------------------------------------------+
//|【関数】注文種別の数値を文字列に変換する                          |
//|                                                                  |
//|【引数】 IN OUT  引数名             説明                          |
//|        --------------------------------------------------------- |
//|         ○      aType              注文種別                      |
//|                                                                  |
//|【戻値】注文種別の数値に対応する文字列                            |
//|                                                                  |
//|【備考】なし                                                      |
//+------------------------------------------------------------------+
string orderType2String(int aType)
{
  if(aType == OP_BUY){
    return("BUY");
  }else if(aType == OP_SELL){
    return("SELL");
  }else if(aType == OP_BUYSTOP){
    return("BUY STOP");
  }else if(aType == OP_SELLSTOP){
    return("SELL STOP");
  }else if(aType == OP_BUYLIMIT){
    return("BUY LIMIT");
  }else if(aType == OP_SELLLIMIT){
    return("SELL LIMIT");
  }else{
    return("None (" + aType + ")");
  }
}

orderSendReliable()の引数をOrderSend()と同じにすることで、導入しやすくしています。OrderSend()の代わりにorderSendReliable()を使うことで、より信頼できる仕掛け注文を行えるようになります。

orderSendReliable()とは別に、orderType2String()という関数を定義しています。これは、指定した注文種別の数値(成行注文の場合、0)を文字列(成行注文の場合、BUY)に変換する関数です。ログを見る際、数値では意味が分かりにくいですからね。

orderSendReliable()は単独では起動しません。定数(MAX_RETRY_TIME、MILLISEC_2_SEC)の利用を前提にしているためです。プログラム全体で使い回す値は定数化なりグローバル変数化しておくと便利です。MAX_RETRY_TIMEは注文失敗時の最大リトライ時間を表す定数です。MILLISEC_2_SECはミリ秒を秒の単位に変換するための定数です。

orderSendReliable()のポイントは、利食い/損切り価格が現在価格に近すぎないか、MarketInfo(aSymbol, MODE_STOPLEVEL)とMarketInfo(aSymbol, MODE_FREEZELEVEL)で注文前に確認している点と、一時的エラーによる注文失敗時はif(GetTickCount() – startTime > MAX_RETRY_TIME * MILLISEC_2_SEC){で制限時間までリトライする点です。繰り返しリトライできるように、while(true)で注文処理を無限ループさせています。そして、制限時間に到達したら、ループから抜け出すようにしています。

あと、最適化かバックテストの場合は、注文失敗時によるリトライには意味がないので(最適化かバックテストの場合、何度やっても状況は改善されないので)、一回注文失敗した時点で、if(IsOptimization() || IsTesting()){で強制終了させています。

余談ですが、ECN/STPタイプのFX業者の場合、仕掛け注文時に損切りと利食い価格を同時に指定できないため、損切りと利食いを指定せずに注文後、すぐにOrderModify()で注文変更するテクニックが書籍や記事で紹介されていることがありますが、2014/12/17現在のMT4では解消されていることがMQL4フォーラムで確認されています。そのため、orderSendReliable()では、ECN/STP業者かどうかを考慮していません(する必要がありません)。

なお、orderSendReliable()を作成するにあたって、豊嶋久道氏の「FXメタトレーダー実践プログラミング」、7bit氏の「common_function.mqh」及びMatthew Kennel氏らの「LibOrderReliable.mq4」を参考にしました。

サンプルEA

関数だけではイメージが沸きづらいと思いますので、サンプルEAを載せておきます。

「信頼できる仕掛け注文関数」のSample.mq4
サイズ: 4.0KB
バージョン: 1.0
公開:2019年10月16日

MQL4プログラミングの最新記事8件

>完全放ったらかしEA 「AutoEndlessCatchRange」

完全放ったらかしEA 「AutoEndlessCatchRange」

「本業が忙しい!」「でも資産運用したい!」そんなあなたに最適なEAです。兼業トレーダーの方はチャートを毎日みて分析してトレードする時間はなかなか確保できないものです。トレードは本EAに任せて、本業やプライベートの時間をもっと増やしませんか?元々は自分自身のために開発したEAですので、手抜き無しのガチものです。

CTR IMG