【超入門】MQL5 EA講座 第64回「ポジションをクローズする」【EAの作り方】

MQL5でEA作ろう講座

前回は発注における、ストップロス(SL:損切り価格)とテイクプロフィット(TP:利益確定価格)を設定するプロセスについて解説しました。

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

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

今回は ポジションをクローズする記述 についてお話ししたいと思います。

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

ポジションをクローズする時は、TRADE_ACTION_DEALを使う

ポジションをクローズする時は、メンバ変数 .actionTRADE_ACTION_DEALを設定します。

新規成行注文の時に使った定数値ですが、ポジションを決済する時にも利用します。

MQL4にはあった、ポジション決済用に使うOrderClose関数MQL5にはありません。同じような外形・仕組みで決済記述をしたい場合は、関数を自作したり標準ライブラリーを利用したりする必要があります。この点については今後の講座記事で解説します。

ネッティングシステムにおけるポジションのクローズについて

ネッティングシステム口座の場合、保有ポジションは1銘柄1つなのでポジションの確認は銘柄名(→.symbol)で確認します。従って、クローズ処理にあたり.positionへの値は入力不要です。

request.action   =TRADE_ACTION_DEAL;       // 取引操作タイプ
request.symbol   =_Symbol;          // シンボル 
request.volume   =0.1;                   // ポジションボリューム
request.type =ORDER_TYPE_SELL;      //オーダータイプ
request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID);//売買価格
request.deviation=5;            //スリッページ

ネッティングシステムの場合、メンバ変数 .Volumeへのロット設定次第では、この時の処理で、ポジションをクローズさせるだけでなく、反対方向にポジションを保有させることもできます。

ヘッジングシステムにおけるポジションのクローズについて

ヘッジングシステム口座の場合、1銘柄に対して複数のポジション保有が可能なので、ポジションクローズの設定にあたっては、ポジション番号を正しく取得し.position に設定する事が必要になります。

ポジション番号の取得は、

などの手段を用います。

request.action   =TRADE_ACTION_DEAL;       // 取引操作タイプ
request.symbol   =_Symbol;          // シンボル 
request.volume   =0.1;                   // ポジションボリューム
request.position=ポジション番号;   // ポジション番号
request.type =ORDER_TYPE_SELL;      //オーダータイプ
request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID);//売買価格
request.deviation=5;            //スリッページ

実際に、ポジションクローズの記述を書いてみる

ネッティングシステムヘッジングシステムそれぞれのクロージングに関する概略を説明したところで、実際にポジションクローズの記述を書いてみましょう。今回は公式リファレンスの決済スクリプトを拝借しつつ、猥雑な所は簡潔な形に変えていきたいと思います。

※ここからは、ヘッジングシステム口座を前提とした記述になります。

マジックナンバーを#define命令で定義する

冒頭のグローバル領域(関数の外の領域)にて、マジックナンバーを設定します。

今回は #define命令 を使ってマジックナンバーを定義します。(実践投入するEA(自動売買プログラム)においてはinput変数を使ってマジックナンバーをパラメータ化するのが一般的です)

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

#define命令定数値を定義する時に用いられる命令記述です。

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

マジックナンバーEA(自動売買プログラム)固有の識別番号です。

1つのメタトレーダー5(MT5)で複数のEA(自動売買プログラム)を運用する時した場合、保有しているポジションやオーダーがどのEAに由来するものかを切り分けるための番号となります。

クローズしようとしているポジションが、現在運用中のEA(自動売買プログラム)から注文したものかどうかを識別する為に今回このマジックナンバーを用います。

今回の決済用サンプルコードはOnStart関数内に記述する、スクリプトなので、新規成行注文スクリプトは別に用意します。

第62回「実際に注文を出すコードを書いてみる」で使ったものを、流用します。このコードに#define命令 を使ってマジックナンバーを追加します。↓

#define EXPERT_MAGIC 123456   // エキスパートアドバイザーを識別するためのユニークな番号、"マジックナンバー"を定義します。

void OnStart()  // スクリプトまたはエキスパートアドバイザが初めて実行されたときに呼び出される特別な関数です。
{
    // 取引リクエストと結果を格納するための構造体を初期化します。
    MqlTradeRequest request={};  // 取引リクエストのための構造体
    MqlTradeResult result={};    // 取引結果のための構造体
    
    // 注文の詳細を設定します。
    request.action=TRADE_ACTION_DEAL;  // 取引アクションとして"取引"を選択します。
    request.type=ORDER_TYPE_BUY;       // 注文タイプとして"買い"を選択します。
    request.symbol=_Symbol;            // 注文するシンボルとして現在のチャートのシンボルを使用します。
    request.volume=0.1;                // 取引量として0.1ロットを指定します。
    request.type_filling=ORDER_FILLING_IOC; // 注文の執行タイプとして"即時またはキャンセル"を指定します。
    request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK); // 注文価格として現在のAsk価格を指定します。
    request.sl=0;                      // ストップロス価格は0(設定なし)です。
    request.tp=0;                      // テイクプロフィット価格も0(設定なし)です。
    request.deviation=3;               // 許容スリッページを3ポイントに設定します。
    request.magic=EXPERT_MAGIC;        // 注文のマジックナンバーを上で定義した値に設定します。
   
    // 注文を送信し、結果を'result'に格納します。
    OrderSend(request, result);
    
    // 注文結果を確認します。
    if(result.retcode == TRADE_RETCODE_PLACED || result.retcode == TRADE_RETCODE_DONE)
    {
        // 注文が正常に配置されたか、完了した場合に実行されるブロックです。
        Print("注文が成功しました。 リターンコード= ", result.retcode);
    }
    else
    {
        // 注文が失敗した場合に実行されるブロックです。
        Print("注文が失敗しました。リターンコード= ", result.retcode);
    }
}

↑のサンプルコードをコンパイルしたMQL5 Program Fileでポジションを持った後、今回の記事でこれから解説する決済用のMQL5 Program Fileでポジションをクローズする、という流れでやりたいと思います。

PositionsTotal関数を使いポジション数を把握する回路を記述する

続いてOnStart関数内の記述に移ります。

MqlTradeRequest構造体及びMqlTradeResult構造体インスタンスを宣言し、PositionsTotal関数戻り値int型変数「total」に格納するようにしました。

void OnStart()
  {
//--- 結果とリクエストの宣言
  MqlTradeRequest request;
   MqlTradeResult  result;
   int total=PositionsTotal(); // 保有ポジション数

}//  void OnStart()

PositionsTotal関数はポジション数を取得し戻り値として返す

PositionsTotal関数はポジション数を取得し、戻り値として返す関数です

引数はありません。

PositionsTotal関数で取得出来るのは、あくまで有効化したポジションの数です。

MQL4経験者の方だと「え!?OrdersTotal関数を使うんじゃないの?」と思われる方も多いと思います。MQL5においてはOrdersTotal関数は、まだポジション化していない待機注文の数を取得する場合に用いる関数です。間違えないようにしましょう。
<参照>

OrderGetString関数について

PositionGetInteger関数について

PositionGetDouble関数について

PositionSelect関数について

PositionSelectByTicket関数について

PositionGetTicket関数について

PositionGetSymbol関数について

MQL5公式リファレンスでも↓のような注意書き文言が頻出します。

現在の未決注文はクライアント端末の「ツールボックス」の「取引」タブに表示されているポジションと混乱されてはなりません。注文とはトランザクションを実行するリクエストです。ポジションは 1 つまたは複数の約定の結果です。

出典:MQL5リファレンス OrdersTotal関数のページより

MQL5にはオーダー(注文)、ポジション、ディール(約定)という概念があるということは繰り返し申し上げてきましたが、改めて注意を払いたいところです。

※オーダー(注文)、ポジション、ディール(約定)の違いについては↓

第60回「OrderSend関数とMqlTradeRequest構造体」

第61回「MqlTradeResult構造体について」

をご参照ください。

for文を使ってポジションの数だけ処理を行う記述を組み込む

ポジションには保有した順にポジションインデックスという番号が割り振られていきます。(ポジション番号とは違うものです)

時系列的に古い方から0、1,2、・・・となり直近になるほど番号が大きくなる仕組みです。

 int total=PositionsTotal(); // 保有ポジション数

for(int i=total-1; i>=0; i--)
     {
       
   }//for

ループの開始点を指定する、for文第1セクションにはtotal-1、すなわち一番最近のポジションから繰り返しをスタートするという意味になります。

ループの終了点を指定するfor文第2セクションでは0、すなわち、一番古いポジションで繰り返しを終了する、という意味になります。

直近のポジション→古いポジションという流れで処理を施そうとしているので、ループごとの処理を規定するfor文第3セクションデクリメント(値をマイナス1させる)になっています。

次はこのfor文{}内に必要な情報を記述していきます。まずはメンバ変数 に代入する各種データの取得です。

クローズに必要な、メンバ変数 に代入する各種データの取得

変数position_ticketにはポジション番号を格納します。後で、メンバ変数 .positionに値を代入する為です。

ポジション番号を取得するには、PositionGetTicket関数を使います。

//--- 注文のパラメータ
    ulong  position_ticket=PositionGetTicket(i);   // ポジション番号
    

PositionGetTicket関数はポジション番号を取得する

PositionGetTicket関数ポジション番号を取得し、戻り値として返します。

引数にはポジションインデックスを記述します。

もしポジションを1つしかもっていない場合、ポジションインデックスは0になります。

待機注文オーダー番号を取得する場合にはOrderGetTicket関数を使う事に注意しましょう。

OrderGetTicket関数に関しては↓の記事をご参照ください

double volume=PositionGetDouble(POSITION_VOLUME); // ポジションボリューム

変数volumeには、後でメンバ変数 .Volumeに格納する決済ロット数を格納します。

決済ロット数の取得にはPositionGetDouble関数を使います。

PositionGetDouble関数引数定数値 「POSITION_VOLUME」を記述します。

ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // ポジションタイプ

変数「type」には、これから決済するポジションのポジションタイプを格納します。

もし変数「type」に格納されている定数値(=決済するポジションのポジションタイプ)が、ORDER_TYPE_BUYであれば、決済注文時メンバ変数 .type に格納する定数値はORDER_TYPE_SELLにしなければいけないし、

逆に、もし変数「type」に格納されている定数値(=決済するポジションのポジションタイプ)が、ORDER_TYPE_SELLであれば、決済注文時メンバ変数 .type に格納する定数値はORDER_TYPE_BUYにするように記述しなければいけない

ということです。要は、現在保有しているポジションとは逆方向のオーダータイプを設定するということです。

ポジションタイプを確認するには、PositionGetInteger関数引数定数POSITION_TYPEを記述します。

サンプルコードを見ると、PositionGetInteger関数の前に(ENUM_POSITION_TYPE)という表記が見られますが、これはタイプキャスト(型変換)です。

変数typeのデータ型が、ENUM_POSITION_TYPEという定義済みenum列挙型であるのに対し、PositionGetInteger関数戻り値として返す値はint型になっているので、データ欠損の可能性がないように行う処理です。

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

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

ulong  magic=PositionGetInteger(POSITION_MAGIC);  // ポジションのMagicNumber

変数magicにはこれから決済するポジションのマジックナンバーを格納します。

決済するポジションのマジックナンバーを取得するにはPositionGetInteger関数引数に、POSITION_MAGICを記述します。

変数「magic」に格納されているマジックナンバーと、#define命令 で定義したマジックナンバーの値が一致していれば、決済するポジションは運用しているEA(自動売買プログラム)から注文されたものである、ということになります。(一致していなければ、裁量トレードによるエントリーか、別のEA(自動売買プログラム)からのエントリーである可能性があります)

引き続きfor文内の記述を続けます。次はマジックナンバーとの照合です。

ポジションに付与されているマジックナンバーを照合する

if文変数「magic」に格納されているマジックナンバー#define命令 で定義したマジックナンバーの値が一致しているか突合を行い、一致していれば、新規成行注文と同じように各メンバ変数に値を代入していきます。

メンバ変数代入する値については、クローズに必要な、メンバ変数 に代入する各種データの取得の項で書いた通りです。

 //--- MagicNumberが一致している場合
    if(magic==EXPERT_MAGIC)
        {
         //--- リクエストと結果の値のゼロ化
        ZeroMemory(request);
         ZeroMemory(result);
         //--- 操作パラメータの設定
         
        
        request.action   =TRADE_ACTION_DEAL;       // 取引操作タイプ
        request.position =position_ticket;         // ポジションチケット
        request.symbol   =_Symbol;                // シンボル 
        request.volume   =volume;                   // ポジションボリューム
        request.deviation=5;                       // 価格からの許容偏差
        request.magic    =EXPERT_MAGIC;             // ポジションのMagicNumber
        request.type_filling=ORDER_FILLING_IOC;   //フィルポリシー

決済するポジションのタイプを照合する

if文変数「type」に格納されている、ポジションタイプを照合し、その結果によって、決済時メンバ変数

 .type ORDER_TYPE_BUY代入するのか、ORDER_TYPE_SELL代入するのかを決定します。 

ここで、決済に必要なメンバ変数への値入力が終わったので、OrderSend関数を最後に記述して完成です。

 //--- ポジションタイプによる注文タイプと価格の設定
        if(type==POSITION_TYPE_BUY)
           {
            request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID);
            request.type =ORDER_TYPE_SELL;
           }
         else
           {
            request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK);
            request.type =ORDER_TYPE_BUY;
           }

      OrderSend(request,result);

全体のコード

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

#define EXPERT_MAGIC 123456   // エキスパートアドバイザのMagicNumber
//+------------------------------------------------------------------+
//| 全てのポジションを決済                                                  |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 結果とリクエストの宣言
   MqlTradeRequest request;
   MqlTradeResult  result;
   int total=PositionsTotal(); // 保有ポジション数
//--- 全ての保有ポジションの取捨
   for(int i=total-1; i>=0; i--)
     {
      //--- 注文のパラメータ
      ulong  position_ticket=PositionGetTicket(i);       // ポジションチケット
               // 小数点以下の桁数
      ulong  magic=PositionGetInteger(POSITION_MAGIC);  // ポジションのMagicNumber
      double volume=PositionGetDouble(POSITION_VOLUME);                                 // ポジションボリューム
      ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);   // ポジションタイプ
      //--- ポジション情報の出力

      //--- MagicNumberが一致している場合
      if(magic==EXPERT_MAGIC)
        {
         //--- リクエストと結果の値のゼロ化
         ZeroMemory(request);
         ZeroMemory(result);
         //--- 操作パラメータの設定


         request.action   =TRADE_ACTION_DEAL;       // 取引操作タイプ
         request.position =position_ticket;         // ポジションチケット
         request.symbol   =_Symbol;                // シンボル
         request.volume   =volume;                   // ポジションボリューム
         request.deviation=5;                       // 価格からの許容偏差
         request.magic    =EXPERT_MAGIC;             // ポジションのMagicNumber
         request.type_filling=ORDER_FILLING_IOC;   //フィルポリシー
         
         //--- ポジションタイプによる注文タイプと価格の設定
         if(type==POSITION_TYPE_BUY)
           {
            request.price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
            request.type =ORDER_TYPE_SELL;
           }
         else
           {
            request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
            request.type =ORDER_TYPE_BUY;
           }

         OrderSend(request,result);
        }
     }
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+

↑のサンプルコードを実行すると・・・↓

新規にポジションをとるスクリプトを挿入した後、今回解説したクロージング用のスクリプトを実行した様子です。問題なく決済がされていますね。

おまけ

新規ポジション発注のサンプルコードもそうだったのですが、2024年2月現在、公式リファレンスのポジションクローズサンプルコードにはフィルポリシーを埋める回路が記述されていません。

従ってmarket執行方式の場合、コピペして実行しても10030エラー(無効なフィルポリシー)で決済されてないので注意が必要です。↓

まとめ

今回は ポジションをクローズする記述 について解説しました。

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

今回は以上になります。

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

【超入門】MQL5 EA講座 第63回「約定したポジションにSLとTPを設定する」【EAの作り方】

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

コメント

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