【超入門】MQL5 EA講座 第66回「待機注文の注文修正」【EAの作り方】

MQL5でEA作ろう講座

前回は 待機注文の実行記述について解説しました。

改めて前回の内容をおさらいをしておくと、

ということをお伝えしました。

今回は 待機注文の注文修正記述 についてお話ししたいと思います。

今回の記事を読んで理解することによって、EA(自動売買プログラム)開発に待機注文戦略を取り入れる際に、相場状況に応じて修正を加えるなどの打ち手を広げることができるようになります。

スポンサーリンク
スポンサーリンク

待機注文の注文修正にはTRADE_ACTION_MODIFYを使う

待機注文の価格(指値価格やSL,TPなど)修正にはメンバ変数 .actionに設定する定数値をTRADE_ACTION_MODIFYに設定します。

MqlTradeRequest構造体についての記事の時にも言及したことですが、

注意したいのが、第63回「約定したポジションにSLとTPを設定する」で紹介した

TRADE_ACTION_SLTPとの違いです。

TRADE_ACTION_SLTPはすでに保有しているポジションに対してのSLTP変更をしたい時に使うのに対して、今回解説するTRADE_ACTION_MODIFYは、注文は出したものの、まだ指値価格に到達しておらず、ポジション保有に至っていない待機注文の変更をしたい時に使うものです。

そのことを前提に、前回第65回「待機注文の実行」で実行した待機注文の修正をするサンプルコードを作っていきましょう。公式リファレンスのコードを拝借して、要所要所を変えていきたいと思います。

実際に待機注文を修正するスクリプトを書いてみる

#property script_show_inputs
#define EXPERT_MAGIC 123456 // エキスパートアドバイザのMagicNumber

input int offset = 50; //オフセット値
input int StopLoss=1000;//SL
input int TakeProfit=1000;//TP
input int StopLimitPrice=500;//ストップリミット注文の開始価格設定値
input ENUM_ORDER_TYPE_TIME  expirationSet;//有効期限の設定
input datetime DateTime=D'2022';//日時指定指定

冒頭の部分はプログラムの内容的に既出のものばかりです。改めて解説する要素はありません。

input変数で定めた各種パラメーターは、修正しようとしている待機注文の、既に設定されているSLやTP などを改めて設定しなおす為に使います。

今回は有効期限と日時の設定も変更できるよう、メンバ変数 .type_time と .expirationへの値入力用のinput変数にて「expirationSet」「DateTime」とそれぞれ用意しました。

datetime型については→コチラをご覧ください。


//+------------------------------------------------------------------+
//| 未決注文の変更                                                      |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- リクエストと結果の宣言と初期化
  MqlTradeRequest request={};
   MqlTradeResult  result={};
   int total=OrdersTotal(); // 保有未決注文数
//--- 全ての保有未決注文を取捨
  for(int i=0; i<total; i++)
     {

待機注文の修正にあたり、待機注文の数のチェックにはOrdersTotal関数を使います。

OrdersTotal関数以外の記述については、これまでの記事で紹介済みなので問題ないかと思います。

が!復習されたい方は↓のリンクをご参照ください。

さて、OrdersTotal関数に話を戻すと、第64回「ポジションをクローズする」でポジションをクローズする時に使ったのは、ポジション数をチェックするPositionsTotal関数でした。

そこでも言及しましたが、PositionsTotal関数有効化したポジション数を取得する関数です。

待機注文の修正処理は、ポジション化していない注文に対して行うので、待機注文の数を把握するにはOrdersTotal関数を使います。

OrdersTotal関数は、まだポジション化していない待機注文の数を取得する

OrdersTotal関数は、まだポジション化していない待機注文の数を取得する場合に用いる関数です。引数は必要ありません。

オーダー(注文)、ポジション、ディール(約定)の違いについてはMqlTradeRequest構造体MqlTradeResult構造体の記事をご参照ください。

OrdersTotal関数待機注文の数を取得したら、次はfor文を使い、待機注文の数だけ繰り返し処理を行っていきます。

という訳で、for文内の記述に入ります。

<参照>

OrderSelect

OrderGetTicket

OrderGetDouble

OrderGetString

OrderGetInteger

for文内の記述1-各種メンバ変数に代入する為の値を取得する-

   //--- 注文パラメータ
    ulong  order_ticket=OrderGetTicket(i);                             //注文チケット
    string order_symbol=Symbol();                                     // シンボル
    int    digits=(int)SymbolInfoInteger(order_symbol,SYMBOL_DIGITS); // 小数点以下の桁数
    ulong  magic=OrderGetInteger(ORDER_MAGIC);                         // 注文のMagicNumber
     double volume=OrderGetDouble(ORDER_VOLUME_CURRENT);               // 現在の注文量
    double sl=OrderGetDouble(ORDER_SL);                               // 現在の注文のStop Loss
     double tp=OrderGetDouble(ORDER_TP);                               // 現在の注文のTake Profit
     ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); // 注文タイプ
                                                   
    double price;                                                     // 注文発動価格
    double point=SymbolInfoDouble(order_symbol,SYMBOL_POINT);         // ポイントサイズ

この部分も、第64回「ポジションをクローズする」と構造的にはほとんど同じです。違っているのは待機注文に対する処理なので各種値の取得に使う関数

PositionGetTicketOrderGetTicket

PositionGetIntegerOrderGetInteger

PositionGetDoubleOrderGetDouble

に変わっていることぐらいです。

少し注目するところがあるとすれば、Volume(ロット数)の所でしょうか?

OrderGetDouble関数で取得出来る待機注文のロット数定数値には、ORDER_VOLUME_INITIALORDER_VOLUME_CURRENTがあります。

ORDER_VOLUME_INITIAL待機注文実行時の初期ロット数、

ORDER_VOLUME_CURRENTが変更などを経た、最新の文ロット数を返します。

※(ENUM_ORDER_TYPE)OrderGetInteger

(ENUM_ORDER_TYPE_TIME)OrderGetInteger

などの()表記はタイプキャスト(型変換)です。タイプキャストとは、あるデータ型の値を、異なるデータ型に変換するプロセスのことです。

タイプキャストについては→コチラをご覧ください。

enum列挙型については→コチラをご覧ください。

for文内の記述2-マジックナンバーの照合とメンバ変数への代入-

 if(magic==EXPERT_MAGIC)
{
        request.action=TRADE_ACTION_MODIFY;                           // 取引操作タイプ
        request.order = OrderGetTicket(i);                           // 注文チケット
        request.symbol   =Symbol();                                   // シンボル
        request.deviation=5;                                         // 価格からの許容オフセット

}// if(magic==EXPERT_MAGIC)

if文変数magicに格納されているマジックナンバー#define命令 で定義したマジックナンバーの値が一致しているか突合を行い、一致していれば、各メンバ変数に値を代入していきます。ポジションをクローズする時と同じ流れですね。

この先は、待機注文の種類によって条件分岐していきます。ここから先も基本的にはポジションをクローズする時と同じ流れです。

待機注文の種類による条件分岐の記述

待機注文の種類による条件分岐の記述です。

ストップ注文、リミット注文、ストップリミット注文待機注文の種類があるので、どの待機注文をプログラム挿入時選択しているかによって、メンバ変数 .Price代入する計算式が変わってきます。

条件分岐-リミット注文の場合-
else if(type==ORDER_TYPE_BUY_STOP)
           {
           price = SymbolInfoDouble(Symbol(),SYMBOL_BID)+offset*point; 
            request.tp = NormalizeDouble(price+TakeProfit*point,digits);
            request.sl = NormalizeDouble(price-StopLoss*point,digits);
            request.price    =NormalizeDouble(price,digits);                 // 正規化された発注価格
            request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
          }
         else if(type==ORDER_TYPE_SELL_STOP)
           {
           price = SymbolInfoDouble(Symbol(),SYMBOL_ASK)-offset*point; 
            request.tp = NormalizeDouble(price-TakeProfit*point,digits);
            request.sl = NormalizeDouble(price+StopLoss*point,digits);
            request.price    =NormalizeDouble(price,digits);                 // 正規化された発注価格
            request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
          }

ORDER_TYPE_BUY_LIMIT すなわち リミット買い注文の場合ですが、リミット注文はマーケットの現在値よりも、有利な価格でポジションを持つことを目的とした注文形態なので、

現在値=SymbolInfoDouble(Symbol(),SYMBOL_ASK)よりも安い価格↓すなわち

SymbolInfoDouble(Symbol(),SYMBOL_ASK)-offset*point

という記述になります。変数offsetはinput変数でパラメータ化した値です。

この値をメンバ変数 .Price に代入します。

代入前にNormalizeDouble関数という組み込み関数が使われていますが、この関数は第1引数で指定した値を、小数点以下の数を第2引数で指定した桁数で、四捨五入します。

NormalizeDouble関数の第2引数に記述されている変数digitsには、定義済み変数_Digits代入されています。_Digits現在の通貨ペアにおける、価格の小数点以下の桁数を取得する定義済み変数ですから、

ドル円などのクロス円通貨ペアであれば「3」桁、ユーロドルなどのクロスドル通貨ペアであれば、「5」桁に値を調整することになります。

NormalizeDouble関数が行った桁数調整のような、プログラムが取り扱うルールに則って値を整える作業のことを「正規化」と言います。

NormalizeDouble関数については、→コチラをご覧ください。

NormalizeDouble関数による正規化が済んで、メンバ変数 .Priceへの値代入が完了したら

メンバ変数 .sl と .tpにストップロスとテイクプロフィットの値を代入していきます。

.sl には.Priceに格納されている値 マイナス (StopLoss*point)

.tp  には.Priceに格納されている値 プラス (StopLoss*point)

の値がそれぞれ入ります

if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)
request.expiration=DateTime;

↑の記述は、メンバ変数 .type_timeに格納されている値がORDER_TIME_SPECIFIED、 または ORDER_TIME_SPECIFIED_DAYだった場合、すなわちメンバ変数 .expirationに有効期限情報を入力する必要がある場合、DateTimeの値を.expiration代入しようという記述式です。

ORDER_TYPE_SELL_LIMIT すなわちリミット売り注文の場合はORDER_TYPE_BUY_LIMITの細かい記述を逆に書き換えただけなので、詳細な記述は割愛させて頂きます。

条件分岐-ストップ注文の場合-
else if(type==ORDER_TYPE_BUY_STOP)
           {
           price = SymbolInfoDouble(Symbol(),SYMBOL_BID)+offset*point; 
            request.tp = NormalizeDouble(price+TakeProfit*point,digits);
            request.sl = NormalizeDouble(price-StopLoss*point,digits);
            request.price    =NormalizeDouble(price,digits);                 // 正規化された発注価格
            request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
          }
         else if(type==ORDER_TYPE_SELL_STOP)
           {
           price = SymbolInfoDouble(Symbol(),SYMBOL_ASK)-offset*point; 
            request.tp = NormalizeDouble(price-TakeProfit*point,digits);
            request.sl = NormalizeDouble(price+StopLoss*point,digits);
            request.price    =NormalizeDouble(price,digits);                 // 正規化された発注価格
            request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
          }

ORDER_TYPE_BUY_STOP、すなわちストップ買い注文は、現在値よりも不利な価格(=高く)でポジションを持つ要求になるので、

現在値=SymbolInfoDouble(Symbol(),SYMBOL_ASK)よりも高い価格↓すなわち

SymbolInfoDouble(Symbol(),SYMBOL_ASK)+offset*point

という記述になります。他の部分はリミット注文と変わりません。

ORDER_TYPE_SELL_STOP、すなわちストップ売り注文の記述はORDER_TYPE_BUY_STOPの細かい部分を全部逆にしているだけです。

条件分岐-ストップリミット注文の場合-
else
         if(type==ORDER_TYPE_BUY_STOP_LIMIT)
           {
            request.type =ORDER_TYPE_BUY_STOP_LIMIT;                               // 注文タイプ
            price        =SymbolInfoDouble(Symbol(),SYMBOL_ASK)+offset*point; // 発注価格
            request.price=NormalizeDouble(price,digits);   // 正規化された発注価格
            double slprice=price-(StopLimitPrice*point);    //ストップリミットの約定価格       
            request.stoplimit=slprice;//
            request.sl=   slprice-(StopLoss*point);    //SL
            request.tp=   slprice+(TakeProfit*point);    //TP
            request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
           }
         else
            if(type==ORDER_TYPE_SELL_STOP_LIMIT)
              {
               request.type     =ORDER_TYPE_SELL_STOP_LIMIT;                           // 注文タイプ
               price=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-offset*point;         // 発注価格
               double slprice=price+(StopLimitPrice*point);
               request.price    =NormalizeDouble(price,digits);                 // 正規化された発注価格
               request.stoplimit=slprice;//ストップリミットの約定価格
                request.sl=   slprice+(StopLoss*point);    //SL
                request.tp=   slprice-(TakeProfit*point);    //TP
                request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
              }  

ORDER_TYPE_BUY_STOP_LIMIT、すなわち買いのストップリミット注文の時ですが、基本的にブレイクアウトした後、一旦押し目をつくるという想定の元、その押し目で待つような注文方法になります。↓

.Priceにはトリガーとなるストップ価格を、.stoplimitにはポジションを有効化させたいリミット価格を入れます。

成行注文ストップリミット注文以外の待機注文.Priceに格納されている値が、ポジションを有効化させる値になるのですが、ストップリミット注文の場合だけ」は、ポジションを有効化させる値は.stoplimitに格納するという違いに注意が必要です。

slpriceという変数をつくり、.Priceに格納されているブレイクアウトしたストップ値から指定ポイント分下がった価格を格納できるようにしました。

.sl と .tpにストップロスとテイクプロフィットの値を代入していきます。

.sl には.stoplimitに格納されている値 マイナス (StopLoss*point)

.tp  には.stoplimitに格納されている値 プラス (StopLoss*point)

の値がそれぞれ入ります

ORDER_TYPE_SELL_STOP_LIMITは、細かい計算や記述をORDER_TYPE_BUY_STOP_LIMITとは逆にするだけです。

全体のコード

全体のコードは以下のようになっています

#property script_show_inputs
#define EXPERT_MAGIC 123456 // エキスパートアドバイザのMagicNumber

input int offset = 50; //オフセット値
input int StopLoss=1000;//SL
input int TakeProfit=1000;//TP
input int StopLimitPrice=500;//ストップリミット注文の開始価格設定値
input ENUM_ORDER_TYPE_TIME  expirationSet;//有効期限の設定
input datetime DateTime;//日時指定指定
//+------------------------------------------------------------------+
//| 未決注文の変更                                                      |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- リクエストと結果の宣言と初期化
  MqlTradeRequest request={};
   MqlTradeResult  result={};
   int total=OrdersTotal(); // 保有未決注文数
//--- 全ての保有未決注文を取捨
  for(int i=0; i<total; i++)
     {
     //--- 注文パラメータ
    ulong  order_ticket=OrderGetTicket(i);                             //注文チケット
    string order_symbol=Symbol();                                     // シンボル
    int    digits=(int)SymbolInfoInteger(order_symbol,SYMBOL_DIGITS); // 小数点以下の桁数
    ulong  magic=OrderGetInteger(ORDER_MAGIC);                         // 注文のMagicNumber
     double volume=OrderGetDouble(ORDER_VOLUME_CURRENT);               // 現在の注文量
    double sl=OrderGetDouble(ORDER_SL);                               // 現在の注文のStop Loss
     double tp=OrderGetDouble(ORDER_TP);                               // 現在の注文のTake Profit
     ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); // 注文タイプ
     ENUM_ORDER_TYPE_TIME  expiration=(ENUM_ORDER_TYPE_TIME)OrderGetInteger(ORDER_TYPE_TIME);//有効期限の種類
     Print("有効期限の種類=",EnumToString(expiration));
                                                   
    double price;                                                     // 注文発動価格
    double point=SymbolInfoDouble(order_symbol,SYMBOL_POINT);         // ポイントサイズ
  
     //--- MagicNumberが一致し、Stop LossとTake Profitが設定されていない場合
    if(magic==EXPERT_MAGIC /*&& sl==0 && tp==0*/)
        {
         request.action=TRADE_ACTION_MODIFY;                           // 取引操作タイプ
        request.order = OrderGetTicket(i);                           // 注文チケット
        request.symbol   =Symbol();                                   // シンボル
        request.deviation=5;                                         // 価格からの許容オフセット
      //--- 価格レベルの設定、タイプに合わせた注文のTake ProfitとStop Loss 
         if(type==ORDER_TYPE_BUY_LIMIT)
           {
            price = SymbolInfoDouble(Symbol(),SYMBOL_ASK)-offset*point; 
            request.tp = NormalizeDouble(price+TakeProfit*point,digits);
            request.sl = NormalizeDouble(price-StopLoss*point,digits);
            request.price    =NormalizeDouble(price,digits);               // 正規化された発注価格
            request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
          }
         else if(type==ORDER_TYPE_SELL_LIMIT)
           {
           price = SymbolInfoDouble(Symbol(),SYMBOL_BID)+offset*point; 
            request.tp = NormalizeDouble(price-TakeProfit*point,digits);
            request.sl = NormalizeDouble(price+StopLoss*point,digits);
            request.price    =NormalizeDouble(price,digits);                 // 正規化された発注価格
            request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
          }
         else if(type==ORDER_TYPE_BUY_STOP)
           {
           price = SymbolInfoDouble(Symbol(),SYMBOL_BID)+offset*point; 
            request.tp = NormalizeDouble(price+TakeProfit*point,digits);
            request.sl = NormalizeDouble(price-StopLoss*point,digits);
            request.price    =NormalizeDouble(price,digits);                 // 正規化された発注価格
            request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
          }
         else if(type==ORDER_TYPE_SELL_STOP)
           {
           price = SymbolInfoDouble(Symbol(),SYMBOL_ASK)-offset*point; 
            request.tp = NormalizeDouble(price-TakeProfit*point,digits);
            request.sl = NormalizeDouble(price+StopLoss*point,digits);
            request.price    =NormalizeDouble(price,digits);                 // 正規化された発注価格
            request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
          }
           else
         if(type==ORDER_TYPE_BUY_STOP_LIMIT)
           {
            request.type =ORDER_TYPE_BUY_STOP_LIMIT;                               // 注文タイプ
            price        =SymbolInfoDouble(Symbol(),SYMBOL_ASK)+offset*point; // 発注価格
            request.price=NormalizeDouble(price,digits);   // 正規化された発注価格
            double slprice=price-(StopLimitPrice*point);    //ストップリミットの約定価格       
            request.stoplimit=slprice;//
            request.sl=   slprice-(StopLoss*point);    //SL
            request.tp=   slprice+(TakeProfit*point);    //TP
            request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
           }
         else
            if(type==ORDER_TYPE_SELL_STOP_LIMIT)
              {
               request.type     =ORDER_TYPE_SELL_STOP_LIMIT;                           // 注文タイプ
               price=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-offset*point;         // 発注価格
               double slprice=price+(StopLimitPrice*point);
               request.price    =NormalizeDouble(price,digits);                 // 正規化された発注価格
               request.stoplimit=slprice;//ストップリミットの約定価格
                request.sl=   slprice+(StopLoss*point);    //SL
                request.tp=   slprice-(TakeProfit*point);    //TP
                request.type_time=expirationSet;
            if(request.type_time==ORDER_TIME_SPECIFIED||request.type_time==ORDER_TIME_SPECIFIED_DAY)request.expiration=DateTime;
              }  
         //---リクエストの送信
        if(!OrderSend(request,result))
           PrintFormat("OrderSend error %d",GetLastError()); // リクエストの送信に失敗した場合、エラーコードを出力する
        //--- 操作情報   
         PrintFormat("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
         //--- リクエストと結果の値のゼロ化
        ZeroMemory(request);
         ZeroMemory(result);
        }
     }
  }

↑のサンプルコードを実際に実行してみると・・・↓

まとめ

今回は 待機注文の注文修正 について解説しました。

今回の記事では以下のことを学びました

今回は以上になります。

なお、待機注文に関しては、今後の講座でもオリジナル関数クラスを作ったり、実際に待機注文用のEA(自動売買プログラム)を作ったりする記事を用意しておりますので、楽しみにMQL5の勉強を進めていただければと思います。待機注文に関する今後の講座記事は以下のようなものになります。↓

最後までお読みいただきありがとうございました<m(__)m>

【超入門】MQL5 EA講座 第65回「待機注文の実行」【EAの作り方】

        →【超入門】MQL5 EA講座 第67回「待機注文の削除」【EAの作り方】

待機注文を使ったコード記述については、講座記事とは別に以下のような記事もありますので、参考にしてください↓

コメント

タイトルとURLをコピーしました