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