English Русский 中文 Español Deutsch Português
EAの元のコードを変更することなく、テイクプロフィットの代わりにリミットオーダーを使用する

EAの元のコードを変更することなく、テイクプロフィットの代わりにリミットオーダーを使用する

MetaTrader 5 | 11 12月 2018, 10:42
1 692 0
Dmitriy Gizlyk
Dmitriy Gizlyk

コンテンツ

イントロダクション

様々なフォーラムで、ユーザーは、MT5のテイクプロフィットレベルの仕様について批判しています。 このサイトでも同じような投稿を見つけることができます。 ユーザーは、テイクプロフィットについて、特にスリッページの負の影響について言及しています。 代替法として、標準のテイクプロフィットを使う代わりにリミットオーダーを提案する人もいます。

その一方で、標準的なテイクプロフィットと対照的に、リミットオーダーの使用により、部分的に決済するようなアルゴリズムを構築できます。(指値注文ではポジションの一部のボリュームを指定できます。) この記事でこのようなテイクプロフィット置換の実装に使用できるオプションの 1 つを提供します。

1. 一般的な側面

組み込みテイクプロフィットまたはリミットオーダーの何がベターであるかについての論争は無意味だと考えています。 すべてのトレーダーは、戦略原則と要件に基づいてこの問題を解決する必要があります。 この記事ではあるソリューションを提供します。

リミットオーダーのシステムを開発する前に、アルゴリズムを設計する際に注意する必要がある側面について考えてみましょう。

覚えておくべき主な事柄は、テイクプロフィットがポジションの決済のオーダーであることです。 これは当たり前に思えるかもしれませんが、実際にはターミナルとシステムによってこのタスクが実行されているという事実に慣れています。 テイクプロフィットを設定する場合、その管理に完全な責任を負う必要があります。

これは何の話でしょうか。 決済は、テイクプロフィットだけでなく、ストップロスやトレーダーの裁量で行われる可能性があります。 (多くの場合成行注文で決済されます。) つまり、今回のシステムは相場上のポジションを追跡し、何らかの理由でポジションがない場合にはリミットオーダーを削除する必要があります。 そうしなければ、意図しないポジションを取得することにより損失が発生し、本来のテイクプロフィットのスリッページによる損失を大きく上回る可能性があります。

その上、ポジションが部分的に閉じる可能性もあります。 (ネットアカウントの場合) したがって、ポジションそのものだけでなく、そのボリュームも追跡する必要があります。 ポジションのボリュームが変わった場合、リミットオーダーも即座に変更されるべきです。

別の側面としてヘッジシステムオペレーションがあります。 このシステムは、ポジションの区分経理を実行し、単一のシンボルに複数のポジションを同時に開くことができます。 つまり、リミットオーダーのアクティベートによって既存のポジションが決済されることはありません。 代わりに、新しいポジションを開きます。 したがって、リミットオーダーがトリガーされた後反対のポジションによって決済を実行する必要があります。

もう一つの問題は、予約オーダーのテイクプロフィットです。 この場合、本体のオーダーが処理される前に、テイクプロフィットがトリガーされないようにしなければなりません。 一見すると、ストップ ・ リミットオーダーを使用することが可能です。 たとえば、 同時に売りストップオーダーと買いストップリミットオーダーを配置できます。 しかし、システム上、売りリミットオーダーでは同様の操作を実行することはできません。 これは、リミットのテイクプロフィットの以降の設定で予約オーダーのアクティベートの追跡の問題が生じるところから来ます。 その代わりに、プログラム内の予約オーダーのアクティベートを追跡し、テイクプロフィットなしの保留オーダーは、手に負えないポジションのリスクを伴います。 その結果、価格はテイクプロフィットレベルと逆に達する可能性があります。 プログラムによるコントロールの欠如は、最終的に損失を生み出すポジションを閉じることができません。

個人的な解決策は、標準のテイクプロフィットを指定しつつ、予約オーダー設定することだと思います。 ポジションが開かれた後、テイクプロフィットは、リミットオーダーを配置し、テイクプロフィットフィールドをゼロに設定リミットオーダーに置き換えられます。 このオプションは、コントロールを失った場合の保証になります。 このプログラムは、サーバーへの接続を失った場合、システムによるオーダーテイクプロフィットをアクティブにします。 この場合、マイナススリッページによる損失は、コントロールの欠如によって引き起こされる損失よりも小さくなります。

別の問題は、前のテイクプロフィットの変更です。 多くの場合、異なる戦略を使用して、追跡し、オープン ポジションのテイクプロフィットを調整する必要があります。 ここに2 つの選択肢があります。

  1. このようなEAのコードに変更する場合、コード上でテイクプロフィットを変更する可能なオプションを探さないために、OrderSend関数を単純に我々のクラスのメソッドに置き換え、前に設定されたリミットオーダーの存在をチェックし、新しいレベルに対応するようにします。 必要に応じて、以前に配置したオーダーを変更し、以前に配置したリミットオーダーが新しい要件を満たしている場合は、コマンドを無視します。
  2. 購入したEAを使用すると、 そのコードへのアクセスがないので、このプログラムはポジションを開きません。しかし、そのテイクプロフィットを置き換えます。 この場合、テイクプロフィットがすでにセットされたオーダーに対してセットされる可能性があります。 つまり、既存のリミットオーダーの関連性を再確認し、テイクプロフィットフィールドをゼロに調整します。

また、現在の価格から最小の予約注文の設定距離を追跡すべきです。 ヒストリーの削除、変更はリミットオーダーのポジションを閉じるとき前者同様にテイクプロフィットシステムの同様の設定に適用する場合、距離が裏目に出る可能性があります。 残念ながら、システムのアルゴリズムに依存しないので、このようなリスクはシステムを構築するときだけでなく、使うときにも考慮する必要があります。

2. 実装の原則 - リミットオーダーリンク

すでに言及したように、ポジションの状態の追跡とリミットテイクプロフィットのマッチの探索が必要です。 これを実現する方法を見てみましょう。 まず第一に、 どの時点でターミナルをオーバー ロードするためにコントロールする必要があるかを決定する必要があります。

潜在的に、ポジションは、トレードのセッションが開いている場合、任意の時点で変更できます。 ただし、これはそう頻繁には発生せず、それぞれのティックの確認はこのEAによって実行されるオペレーションを増加させます。 ここでは、イベントを使用できます。 MQL5ドキュメンテーションに従って、トレードサーバーにトレード操作を完了するとき、トレード イベントが生成されます。 OnTrade 関数は、このイベントの結果として起動されます。 したがって、この関数は、オープン ポジションと置かれたリミットテイクプロフィットのチェックができます。 これは毎ティックで一致しているか確認させず、同時にチャンスを逃さないようにします。

次に識別の問題が来ます。 一見、すべてが簡単に見えます。 リミットオーダーを確認し、ポジションを開きます。 しかし、今回は異なる戦略においても、異なるアカウントタイプにおいても十分に稼働する汎用的なアルゴリズムを構築しましょう。 またストラテジ内でリミットオーダーが使われることに留意してください。 したがって、 リミットテイクプロフィット を割り当てる必要があります。 これらを識別するコメントを使用します。 今回のリミットオーダーはテイクプロフィットの代わりになるので、それらを識別するために"TP"という文字をコメントに付け加えます。 次に、多段ポジション決済が適用される場合にに備えて、ステージ番号を追加します。 ネットシステムではこれで十分ですが、ヘッジシステムの場合は同時にポジションを複数開く可能性があります。 したがって、適切なポジション ID をテイクプロフィットコメントリミットに追加する必要があります。

3. リミットテイクプロフィットクラスを作成

上記をまとめてみましょう。 このクラスの機能は、2 つの論理プロセスに分類できます。

  1. サーバーへのリクエスト送信するトレードに変更を加えます。
  2. 監視と修正ポジションを開き、リミットオーダーを配置します。

使いやすさ、CLimitTakeProfit クラスとして のアルゴリズムを設計して、次のすべての関数を静的にみましょう。 これよりプログラム コードでそのインスタンスを宣言しなくても、クラス メソッドを使用できます。

class CLimitTakeProfit : public CObject
  {
private:
   static CSymbolInfo       c_Symbol;
   static CArrayLong        i_TakeProfit; //固定テイクプロフィット
   static CArrayDouble      d_TakeProfit; //テイクプロフィットで閉じるため %
   
public:
                     CLimitTakeProfit();
                    ~CLimitTakeProfit();
//---
   static void       Magic(int value)  {  i_Magic=value; }
   static int        Magic(void)       {  return i_Magic;}
//---
   static void       OnlyOneSymbol(bool value)  {  b_OnlyOneSymbol=value;  }
   static bool       OnlyOneSymbol(void)        {  return b_OnlyOneSymbol; }
//---
   static bool       OrderSend(const MqlTradeRequest &request, MqlTradeResult &result);
   static bool       OnTrade(void);
   static bool       AddTakeProfit(uint point, double percent);
   static bool       DeleteTakeProfit(uint point);
   
protected:
   static int        i_Magic;          //コントロールするマジック ナンバー
   static bool       b_OnlyOneSymbol;  //制御の下で 1 つのシンボルのポジションのみ
//---
   static bool       SetTakeProfits(ulong position_ticket, double new_tp=0);
   static bool       SetTakeProfits(string symbol, double new_tp=0);
   static bool       CheckLimitOrder(MqlTradeRequest &request);
   static void       CheckLimitOrder(void);
   static bool       CheckOrderInHistory(ulong position_id, string comment, ENUM_ORDER_TYPE type, double &volume, ulong call_position=0);
   static double     GetLimitOrderPriceByComment(string comment);
  };

Magic、OnlyOneSymbol、AddTakeProfit、DeleteTakeProfit メソッドは、クラスの動作を構成するためのものです。 Magic - マジック ナンバー (ヘッジ アカウント) のポジションを追跡するために使用します。 -1 の場合、クラスは、すべてのポジションで動作します。 OnlyOneSymbol でEAが起動されたシンボル チャートのポジションでのみ動作するクラスに指示します。 AddTakeProfit と DeteleTakeProfit メソッドは、追加および固定テイクプロフィットレベル初期ポジション量の割合として使用します。

ユーザーは、省略可能な場合、メソッドを適用できます。 デフォルトでは、このメソッドは、すべてのマジックナンバと固定テイクプロフィットの設定がないシンボルで稼働します。 リミットオーダーは、ポジションで指定されたテイクプロフィットの代わりにのみ設定されます。

3.1. トレードオーダーの送信を変更

OrderSend メソッドは、EAによって送信されたオーダーを監視します。 名前とメソッド呼び出しの形式は、MQL5 にオーダーを送信するための標準関数に似ています。 このメソッドで標準的な関数を置き換えることによって以前に書かれたEAのコードがするべきアルゴリズムを埋め込むことになります。

予約オーダーのテイクプロフィットを取り替えることの問題は既に説明しました。 このため、このブロックだけで成行オーダーのテイクプロフィットを置き換えることができます。 ただし、注意してください。サーバーによってオーダーが受理されても執行されるとは限りません。 しかも、オーダーを送信した後、オーダーチケットは受け取りますが、これはポジションIDではありません。 したがって、モニタリングブロックでテイクプロフィットを置き換えられます。 ここで、 以前に設定されたテイクプロフィットが変更された瞬間を追跡します。

メソッドのコードの先頭で、送信リクエストがアルゴリズムの動作の設定フィルタに対応するかどうかを確認します。 さらに、トレードの種類を確認する必要があります。 ポジションのストップの変更リクエストに対応する必要があります。 また、テイクプロフィットがリクエスト内に存在するか確認することを忘れないでください。 リクエストが要件の少なくとも 1 つを満たしていない場合は、すぐにそのままサーバーに送信されます。

要件をチェックすると、このリクエストは、リミットオーダーを配置する場所、SetTakeProfit メソッドに渡されます。 このクラスがポジション チケットとシンボルによってタスクの 2 つのメソッドを備えているに注意してください。 ポジション チケットがリクエストに含まれていない場合、二つ目はネットアカウントに適用されます。 メソッドが成功した場合は、ゼロへのリクエストでテイクプロフィットフィールドを設定します。

このリクエストは、テイクプロフィットとストップロスの両方を変更する可能性があるので、ストップロスやテイクプロフィットのポジション設定が適切かを確認します。 必要に応じて、サーバーにリクエストを送信し、関数を終了します。 完全なメソッドのコードは、下の通りです。

bool CLimitTakeProfit::OrderSend(MqlTradeRequest &request,MqlTradeResult &result)
  {
   if((b_OnlyOneSymbol && request.symbol!=_Symbol) ||
      (i_Magic>=0 && request.magic!=i_Magic) || !(request.action==TRADE_ACTION_SLTP && request.tp>0))
      return(::OrderSend(request,result));
//---
   if(((request.position>0 && SetTakeProfits(request.position,request.tp)) ||
       (request.position<=0 && SetTakeProfits(request.symbol,request.tp))) && request.tp>0)
      request.tp=0;
   if((request.position>0 && PositionSelectByTicket(request.position)) ||
      (request.position<=0 && PositionSelect(request.symbol)))
     {
      if(PositionGetDouble(POSITION_SL)!=request.sl || PositionGetDouble(POSITION_TP)!=request.tp)
         return(::OrderSend(request,result)); 
     }
//---
   return true;
  }

SetTakeProfit メソッドを詳細に分析してみましょう。 メソッドの先頭に、指定したポジションが存在する場合、ポジションシンボルの連携を確認します。 次に、ポジションツールのデータを更新します。 その後、リミットオーダー可能な最も近い価格を計算します。 エラーの場合 'false' の結果を持つメソッドを終了します。

bool CLimitTakeProfit::SetTakeProfits(ulong position_ticket, double new_tp=0)
  {
   if(!PositionSelectByTicket(position_ticket) || (b_OnlyOneSymbol && PositionGetString(POSITION_SYMBOL)!=_Symbol))
      return false;
   if(!c_Symbol.Name(PositionGetString(POSITION_SYMBOL)) || !c_Symbol.Select() || !c_Symbol.Refresh() || !c_Symbol.RefreshRates())
      return false;
//---
   double min_sell_limit=c_Symbol.NormalizePrice(c_Symbol.Ask()+c_Symbol.StopsLevel()*c_Symbol.Point());
   double max_buy_limit=c_Symbol.NormalizePrice(c_Symbol.Bid()-c_Symbol.StopsLevel()*c_Symbol.Point());

その後、リミットオーダーを配置するためのトレードリクエストを送信するための構造のテンプレートを準備します。 計算された距離を超えない固定のテイクプロフィットだけを使うポジションで指定されたテイクプロフィットを計算します。

   MqlTradeRequest tp_request={0};
   MqlTradeResult tp_result={0};
   tp_request.action =  TRADE_ACTION_PENDING;
   tp_request.magic  =  PositionGetInteger(POSITION_MAGIC);
   tp_request.type_filling =  ORDER_FILLING_RETURN;
   tp_request.position=position_ticket;
   tp_request.symbol=c_Symbol.Name();
   int total=i_TakeProfit.Total();
   double tp_price=(new_tp>0 ? new_tp : PositionGetDouble(POSITION_TP));
   if(tp_price<=0)
      tp_price=GetLimitOrderPriceByComment("TPP_"+IntegerToString(position_ticket));
   double open_price=PositionGetDouble(POSITION_PRICE_OPEN);
   int tp_int=(tp_price>0 ? (int)NormalizeDouble(MathAbs(open_price-tp_price)/c_Symbol.Point(),0) : INT_MAX);
   double position_volume=PositionGetDouble(POSITION_VOLUME);
   double closed=0;
   double closed_perc=0;
   double fix_closed_per=0;

次に、固定テイクプロフィットの配置とループを調整します。 最初に、オーダーのコメントを設定します。(コーディングの原理は上で説明) ポジションで指定されたテイクプロフィットまたはリクエストが固定されたものを超えていないかどうかを確認します。 超えている場合、次のテイクプロフィットにします。 また、設定したリミットオーダーのボリュームにポジションが重複しないことを確認します。 リミットオーダーで、ポジションのボリュームを重複する場合は、ループを終了します。

   for(int i=0;i<total;i++)
     {
      tp_request.comment="TP"+IntegerToString(i)+"_"+IntegerToString(position_ticket);
      if(i_TakeProfit.At(i)<tp_int && d_TakeProfit.At(i)>0)
        {
         if(closed>=position_volume || fix_closed_perc>=100)
            break;

次のステップは、トレードリクエスト構造体のかけた要素に対する処理です。 これを行うには、新規リミットオーダー量を計算し、オーダーの種類を指定します。

//---
         double lot=position_volume*MathMin(d_TakeProfit.At(i),100-closed)/(100-fix_closed_perc);
         lot=MathMin(position_volume-closed,lot);
         lot=c_Symbol.LotsMin()+MathMax(0,NormalizeDouble((lot-c_Symbol.LotsMin())/c_Symbol.LotsStep(),0)*c_Symbol.LotsStep());
         lot=NormalizeDouble(lot,2);
         tp_request.volume=lot;
         switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
           {
            case POSITION_TYPE_BUY:
              tp_request.type=ORDER_TYPE_SELL_LIMIT;
              tp_request.price=c_Symbol.NormalizePrice(open_price+i_TakeProfit.At(i)*c_Symbol.Point());
              break;
            case POSITION_TYPE_SELL:
              tp_request.type=ORDER_TYPE_BUY_STOP;
              tp_request.price=c_Symbol.NormalizePrice(open_price-i_TakeProfit.At(i)*c_Symbol.Point());
              break;
           }

トレードリクエストを処理したら、前に同じパラメータでのリミットオーダーが設定されたかどうかを確認します。 これを行うには、リクエスト構造体を渡すことによって CheckLimitOrder メソッド(このメソッドアルゴリズムは下で説明)を使います。 オーダーが前に設定されていない場合、設定オーダーボリュームを設定ボリュームの合計に追加します。 そのポジションを確保するために必要な配置リミットオーダーボリュームは、互いに対応します。

         if(CheckLimitOrder(tp_request))
           {
            if(tp_request.volume>=0)
              {
               closed+=tp_request.volume;
               closed_perc=closed/position_volume*100;
              }
            else
              {
               fix_closed_per-=tp_request.volume/(position_volume-tp_request.volume)*100;
              }
            continue;
           }

オーダーが配置されていない場合、現在の価格に関してブローカーの要件に従ってその価格を調整し、サーバーにリクエストを送信します。 リクエストが正常に送信すると、 ポジションに設定されたボリュームの合計オーダー量を追加します。

         switch(tp_request.type)
           {
            case ORDER_TYPE_BUY_LIMIT:
              tp_request.price=MathMin(tp_request.price,max_buy_limit);
              break;
            case  ORDER_TYPE_SELL_LIMIT:
              tp_request.price=MathMax(tp_request.price,min_sell_limit);
              break;
           }
         if(::OrderSend(tp_request,tp_result))
           {
            closed+=tp_result.volume;
            closed_perc=closed/position_volume*100;
            ZeroMemory(tp_result);
           }
        }
     }

ループを完了すると、変更リクエスト (またはポジション) で指定価格で不足しているボリュームのリミットオーダーを配置するのにのと同じアルゴリズムを使用します。 ボリュームが最小より小さい場合は、'false' の結果を返し、関数を終了します。

   if(tp_price>0 && position_volume>closed)
     {
      tp_request.price=tp_price;
      tp_request.comment="TPP_"+IntegerToString(position_ticket);
      tp_request.volume=position_volume-closed;
      if(tp_request.volume<c_Symbol.LotsMin())
         return false;
      tp_request.volume=c_Symbol.LotsMin()+MathMax(0,NormalizeDouble((tp_request.volume-c_Symbol.LotsMin())/c_Symbol.LotsStep(),0)*c_Symbol.LotsStep());
      tp_request.volume=NormalizeDouble(tp_request.volume,2);
//---
      switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
        {
         case POSITION_TYPE_BUY:
           tp_request.type=ORDER_TYPE_SELL_LIMIT;
           break;
         case POSITION_TYPE_SELL:
           tp_request.type=ORDER_TYPE_BUY_LIMIT;
           break;
        }
      if(CheckLimitOrder(tp_request) && tp_request.volume>=0)
        {
         closed+=tp_request.volume;
         closed_perc=closed/position_volume*100;
        }
      else
        {
         switch(tp_request.type)
           {
            case ORDER_TYPE_BUY_LIMIT:
              tp_request.price=MathMin(tp_request.price,max_buy_limit);
              break;
            case  ORDER_TYPE_SELL_LIMIT:
              tp_request.price=MathMax(tp_request.price,min_sell_limit);
              break;
           }
         if(tp_request.volume<=0)
           {
            tp_request.volume=position_volume-closed;
            tp_request.volume=c_Symbol.LotsMin()+MathMax(0,NormalizeDouble((tp_request.volume-c_Symbol.LotsMin())/c_Symbol.LotsStep(),0)*c_Symbol.LotsStep());
            tp_request.volume=NormalizeDouble(tp_request.volume,2);
           }
         if(::OrderSend(tp_request,tp_result))
           {
            closed+=tp_result.volume;
            closed_perc=closed/position_volume*100;
            ZeroMemory(tp_result);
           }
        }
     }      

メソッドの完了時に配置されたリミットオーダーのボリューム カバー ポジション量であるかどうかを確認します。 もしそうである場合は、ポジションのテイクプロフィットをゼロにし、関数を終了します。

   if(closed>=position_volume && PositionGetDouble(POSITION_TP)>0)
     {
      ZeroMemory(tp_request);
      ZeroMemory(tp_result);
      tp_request.action=TRADE_ACTION_SLTP;
      tp_request.position=position_ticket;
      tp_request.symbol=c_Symbol.Name();
      tp_request.sl=PositionGetDouble(POSITION_SL);
      tp_request.tp=0;
      tp_request.magic=PositionGetInteger(POSITION_MAGIC);
      if(!OrderSend(tp_request,tp_result))
         return false;
     }
   return true;
  }

次に、CheckLimitOrder メソッドのアルゴリズム見てみましょう。 機能的には、このメソッドは準備されたトレードリクエストの前にリミットオーダーの存在をチェックします。 オーダーが既に設定されている場合、'true' を返し、新しいオーダーはセットされません。

メソッドの先頭で、リミットオーダーを配置するための最も近い使用可能なレベルを決定します。 以前に配置したオーダーを変更する必要がある場合、 これが必要です。

bool CLimitTakeProfit::CheckLimitOrder(MqlTradeRequest &request)
  {
   double min_sell_limit=c_Symbol.NormalizePrice(c_Symbol.Ask()+c_Symbol.StopsLevel()*c_Symbol.Point());
   double max_buy_limit=c_Symbol.NormalizePrice(c_Symbol.Bid()-c_Symbol.StopsLevel()*c_Symbol.Point());

次のステップは、開いているすべてのオーダーを繰り返し処理するループを配置することです。 必要なオーダーは、そのコメントによって識別されます。

   for(int i=0;i<total;i++)
     {
      ulong ticket=OrderGetTicket((uint)i);
      if(ticket<=0)
         continue;
      if(OrderGetString(ORDER_COMMENT)!=request.comment)
         continue;

必要なコメントのオーダーを見つけること、そのボリュームとオーダーの種類を確認します。 パラメータのいずれかが一致しない場合、既存の予約オーダーを削除し、'false' の結果を持つ関数を終了します。 オーダー削除エラーの場合、既存のオーダー量がリクエストボリューム] フィールドに表示されます。

      if(OrderGetDouble(ORDER_VOLUME_INITIAL) != request.volume || OrderGetInteger(ORDER_TYPE)!=request.type)
        {
         MqlTradeRequest del_request={0};
         MqlTradeResult del_result={0};
         del_request.action=TRADE_ACTION_REMOVE;
         del_request.order=ticket;
         if(::OrderSend(del_request,del_result))
            return false;
         request.volume=OrderGetDouble(ORDER_VOLUME_INITIAL);
        }

次の段階で検出されたオーダーとパラメータで指定された 1 つのオープンの価格をチェックします。 必要に応じて、現在のオーダーを変更し、'true' の結果を持つメソッドを終了します。

      if(MathAbs(OrderGetDouble(ORDER_PRICE_OPEN)-request.price)>=c_Symbol.Point())
        {
         MqlTradeRequest mod_request={0};
         MqlTradeResult mod_result={0};
         mod_request.action=TRADE_ACTION_MODIFY;
         mod_request.price=request.price;
         mod_request.magic=request.magic;
         mod_request.symbol=request.symbol;
         switch(request.type)
           {
            case ORDER_TYPE_BUY_LIMIT:
              if(mod_request.price>max_buy_limit)
                 return true;
              break;
            case ORDER_TYPE_SELL_LIMIT:
              if(mod_request.price<min_sell_limit)
                 return true;
              break;
           }
         bool mod=::OrderSend(mod_request,mod_result);
        }
      return true;
     }

しかし、リミットオーダーはそのボリュームで既に発動している可能性もあります。 したがって、オープンオーダーの中で必要なオーダーがない場合は、同様に現在のポジションのオーダーヒストリーをチェックします。 この関数は、最後にCheckOrderInHistory メソッドで実装されます。

   if(!PositionSelectByTicket(request.position))
      return true;
//---
   return CheckOrderInHistory(PositionGetInteger(POSITION_IDENTIFIER),request.comment, request.type, request.volume);
  }

アカウントの種類によってリミットオーダーのアクティベートには、2 つのオプションがあります。

  1. (ネットアカウント) ポジションで直接アクティベート。
  2. (ヘッジアカウント)リミットオーダーは、反対のポジションを開き 互いにポジションを閉じます。

そのような可能性を探しているときは、このポジションにこのようなオーダーが関連しないので、情報の検索を行うことに注意してください。

bool CLimitTakeProfit::CheckOrderInHistory(ulong position_id, string comment, ENUM_ORDER_TYPE type, double &volume, ulong call_position=0)
  {
   if(!HistorySelectByPosition(position_id))
      return true;
   int total=HistoryDealsTotal();
   bool hedging=(AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
//---
   for(int i=0;i<total;i++)
     {
      ulong ticket=HistoryDealGetTicket((uint)i);
      ticket=HistoryDealGetInteger(ticket,DEAL_ORDER);
      if(!HistoryOrderSelect(ticket))
         continue;
      if(ticket<=0)
         continue;

ヘッジアカウントについては、オーダーが他のポジションに関係しているかどうかまず確認する必要があります。 別のポジションのオーダーが検出された場合は、そのポジションの必要なコメントのオーダーを詳しく見ます。 これを行うには、CheckOrderInHistory 関数の再帰呼び出しを実行します。 ループを避けるために、メソッドがメソッドを呼び出す前に、このポジションから呼び出されたかどうか確認します。 オーダーが検出された場合は、'true' の結果でメソッドを終了します。 それ以外の場合、ポジションのヒストリーをリロードして、次のトレードに移動します。

      if(hedging && HistoryOrderGetInteger(ticket,ORDER_POSITION_ID)!=position_id && HistoryOrderGetInteger(ticket,ORDER_POSITION_ID)!=call_position)
        {
         if(CheckOrderInHistory(HistoryOrderGetInteger(ticket,ORDER_POSITION_ID),comment,type,volume))
            return true;
         if(!HistorySelectByPosition(position_id))
            continue;
        }

現在のポジションのオーダーのコメントとオーダーの種類を確認します。 オーダーが検出されると、マイナスサインでリクエストにそのボリュームを書き込み、メソッドを終了します。

      if(HistoryOrderGetString(ticket,ORDER_COMMENT)!=comment)
         continue;
      if(HistoryOrderGetInteger(ticket,ORDER_TYPE)!=type)
         continue;
//---
      volume=-OrderGetDouble(ORDER_VOLUME_INITIAL);
      return true;
     }
   return false;
  }

すべてのメソッドおよび関数の完全なコードは、添付ファイルにあります。

3.2. トレードオペレーションプロセス

監視および既存のポジションとオープンのリミットオーダーを修正する提案アルゴリズムの第 2 ブロックを形成します。

アカウント上でこれを行うには、OnTrade関数の実行が原因となりトレードイベントが生成されます。 トレードを処理するクラスに適切なメソッドを追加します。

メソッドアルゴリズムで準備タスクを開始: アカウントのオープン ポジションの数を取得し、オーダータイプを確認します。

bool CLimitTakeProfit::OnTrade(void)
  {
   int total=PositionsTotal();
   bool result=true;
   bool hedhing=AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING;

次に、オープン ポジションを繰り返し処理するループを配置します。 ループの先頭ポジションがシンボルとマジックナンバー (ヘッジのアカウント) に対して並べ替え条件に対応するかどうかを確認します。

   for(int i=0;i<total;i++)
     {
      ulong ticket=PositionGetTicket((uint)i);
      if(ticket<=0 || (b_OnlyOneSymbol && PositionGetString(POSITION_SYMBOL)!=_Symbol))
         continue;
//---
     if(i_Magic>0)
        {
         if(hedhing && PositionGetInteger(POSITION_MAGIC)!=i_Magic)
            continue;
        }

ヘッジアカウントについては、リミットテイクプロフィットを処理するポジションが開かれているか確認してください。 その場合、操作によって、決済を実行します。 ポジションが正常に決済した後は、次のポジションに移動します。

      if(hedhing)
        {
         string comment=PositionGetString(POSITION_COMMENT);
         if(StringFind(comment,"TP")==0)
           {
            int start=StringFind(comment,"_");
            if(start>0)
              {
               long ticket_by=StringToInteger(StringSubstr(comment,start+1));
               long type=PositionGetInteger(POSITION_TYPE);
               if(ticket_by>0 && PositionSelectByTicket(ticket_by) && type!=PositionGetInteger(POSITION_TYPE))
                 {
                  MqlTradeRequest   request  ={0};
                  MqlTradeResult    trade_result   ={0};
                  request.action=TRADE_ACTION_CLOSE_BY;
                  request.position=ticket;
                  request.position_by=ticket_by;
                  if(::OrderSend(request,trade_result))
                     continue;
                 }
              }
           }
        }

ループの最後に、ポジションのリミットオーダーを設定するSetTakeProfits メソッドを呼び出します。 このメソッドアルゴリズムは上で説明しています。

      result=(SetTakeProfits(PositionGetInteger(POSITION_TICKET)) && result);
     }

ポジションをオープン ループのチェックを完了すると、アクティブなリミットオーダーがポジションを開き、必要に応じて、ポジションをクローズした後、残りのリミットオーダーの対応を確認します。 これを行うには、CheckLimitOrder メソッドを呼び出します。 この場合、上記で説明した関数とは異なり、パラメータを指定せず関数を呼びます。 これは、完全に別のメソッドを呼び出し、関数のオーバー ロードプロパティにより発生します。

   CheckLimitOrder();
//---
   return result;
  }

このメソッドのアルゴリズムは、すべてのオーダーを繰り返し処理するのに基づいています。 必要なものは、コメントを使用して選択されます。

void CLimitTakeProfit::CheckLimitOrder(void)
  {
   int total=OrdersTotal();
   bool res=false;
//---
   for(int i=0;(i<total && !res);i++)
     {
      ulong ticket=OrderGetTicket((uint)i);
      if(ticket<=0)
         continue;
      string comment=OrderGetString(ORDER_COMMENT);
      if(StringFind(comment,"TP")!=0)
         continue;
      int pos=StringFind(comment,"_",0);
      if(pos<0)
         continue;

テイクプロフィットの検出リミット後、コメントから反対のポジション ID を取得します。 指定したポジションにアクセスするのに ID を使用します。 そのポジションが存在しない場合は、オーダーを削除します。

      long pos_ticker=StringToInteger(StringSubstr(comment,pos+1));
      if(!PositionSelectByTicket(pos_ticker))
        {
         MqlTradeRequest del_request={0};
         MqlTradeResult del_result={0};
         del_request.action=TRADE_ACTION_REMOVE;
         del_request.order=ticket;
         if(::OrderSend(del_request,del_result))
           {
            i--;
            total--;
           }
         continue;
        }

ポジションのアクセスを管理する場合は、オーダータイプがポジションタイプに対応しているか確認します。 このチェックは、反転ポジションが可能なネットアカウントに必要です。 不一致が検出された場合は、オーダーを取り外して次のいずれかに移動します。

      switch((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE))
        {
         case POSITION_TYPE_BUY:
           if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_LIMIT)
              continue;
           break;
         case POSITION_TYPE_SELL:
           if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_LIMIT)
              continue;
           break;
        }
      MqlTradeRequest del_request={0};
      MqlTradeResult del_result={0};
      del_request.action=TRADE_ACTION_REMOVE;
      del_request.order=ticket;
      if(::OrderSend(del_request,del_result))
        {
         i--;
         total--;
        }
     }
//---
   return;
  }

添付ファイルでクラスのすべてのメソッドのコードを詳しく見ることができます。

4. EAにクラスを統合

このクラスのタスクを完了した後、既に開発したEAに統合する方法について考えてみましょう。

覚えているかもしれませんが、今回のクラスのすべてのメソッドが静的クラスのインスタンスを宣言しなくても使用できます。 このようなアプローチは、既に開発済みのEAへのクラス統合を簡素化する意図があります。 実際には、これはEAにクラスを統合に向けた最初のステップです。

次に、 LimitOrderSend 関数と同様の呼び出しパラメータを持つ、 OrderSend 関数を作成します。 クラスのコードの下に位置する、CLimitTakeProfit::OrderSend メソッドを呼び出します。 次に、 #define ディレクティブを使用して、カスタムの 1 つに元の OrderSend 関数を置き換えます。 このメソッドを適用すると、同時に全体のEAのコードに沿ってそのようなコマンドの検索に時間を無駄にしていないので、トレードのリクエストを送信するすべてのEA関数にコードを埋め込むことができます。

bool LimitOrderSend(const MqlTradeRequest &request, MqlTradeResult &result)
 { return CLimitTakeProfit::OrderSend(request,result); } 
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
#define OrderSend(request,result)      LimitOrderSend(request,result)

多くのEAはOnTrade関数を備えていないので、クラスファイルにインクルードします。 しかし、もし対象のEAにこの関数がある場合、コメントアウトするか削除し、CLimitTakeProfit::OnTradeメソッドを追加する必要があります。

void OnTrade()
  {
   CLimitTakeProfit::OnTrade();
  }

次に、 EAにクラスを統合する#include ディレクティブを使用してクラス ファイルへの参照を追加しなければなりません。 クラスをその他のライブラリやEAのコードを呼び出す前に配置させることを覚えておいてください。 以下はターミナルの標準的な配信から MACD が Sample.mq5EAにクラスを追加する例です。

//+------------------------------------------------------------------+
//|                                          MACD Sample LimitTP.mq5 |
//|                   Copyright 2009-2017, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2009-2017, MetaQuotes Software Corp."
#property link        "http://www.mql5.com"
#property version     "5.50"
#property description "It is important to make sure that the expert works with a normal"
#property description "chart and the user did not make any mistakes setting input"
#property description "variables (Lots, TakeProfit, TrailingStop) in our case,"
#property description "we check TakeProfit on a chart of more than 2*trend_period bars"

#define MACD_MAGIC 1234502
//---
#include <Trade\LimitTakeProfit.mqh>
//---
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
//---

OnInit 関数コードを閉じる部分のポジションを追加できます。 これでEAの準備が整いました。

実際のアカウントに使用する前に、EAをテストすることを忘れないでください。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//---すべての必要なオブジェクトを作成。
   if(!ExtExpert.Init())
      return(INIT_FAILED);
   CLimitTakeProfit::AddTakeProfit(100,50);
//---secceed
   return(INIT_SUCCEEDED);
  }

EA操作

EAのコード全体は添付ファイルにあります。

結論

この記事では、リミットオーダーで決済するポジションのテイクプロフィットに代わるメカニズムを提供しました。 既存EAに可能な限り簡単に導入できる仕様にしました。 この記事がお役に立つことを願っています。また、長所短所を評価することができると思います。

記事で使用したプログラム

#
名前
タイプ
詳細
1 LimitTakeProfit.mqh クラス ライブラリ リミットオーダーでテイクプロフィットのオーダーを交換するためのクラス
2 MACD Sample.mq5 EA MetaTrader5のサンプルのオリジナルEA
3 MACD Sample LimitTP.mq5 EA MT5 の例で使用されているクラスの統合の例


MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/5206

添付されたファイル |
MQL5.zip (184.41 KB)
上位100件の最適化パス(その1)最適化分析器の開発 上位100件の最適化パス(その1)最適化分析器の開発
本稿では、最適な最適化パスを選択するためのアプリケーションの開発について、いくつかのオプションを使用して説明します。 このアプリケーションは、様々な要因によって最適化結果を分類することができます。最適化パスは常にデータベースに書き込まれるため、再び最適化せずに常に新しいロボットパラメータを選択できます。さらに、すべての最適化パスを1つのチャートで表示し、パラメトリックVaR比を計算し、パスの正規分布と特定の比率セットの取引結果のグラフを作成することができます。さらに、いくつかの計算された比率のグラフは、最適化開始から(または選択された日付から別の選択された日付まで)動的に構築されます。
ピボット・パターン:『ダブルトップ・ダブルボトム』パターンのテスト ピボット・パターン:『ダブルトップ・ダブルボトム』パターンのテスト
トレーディングの実践において、トレーダーはしばしば、トレンドの傾向の逆転のポイントを探します。なぜなら、トレンドが生まれたときに、その価格が最も大きな変動の可能性を秘めているからです。そのため、技術分析の実践において、様々な反転パターンが考慮されます。最も有名で頻繁に使用されるパターンの1つはダブルトップ・ダブルボトムです。この記事では、パターンの自動検出の例を提案し、またその履歴データに対する収益性をテストします。
リバーシング: 最大ドローダウンの削減と他の相場のテスト リバーシング: 最大ドローダウンの削減と他の相場のテスト
この記事では、リバーシング(反転)技術について扱います。 以前に考慮されたツールの許容レベルまで最大残高ドローダウンを削減します。 利益を減少させるかどうかを確認します。 また、株式、コモディティ、インデックス、ETF、農業相場など、他の相場でのリバース方式の実行方法も確認します。 注意として、この記事には多くの画像が含まれています!
運動継続モデル-チャート上での検索と実行統計 運動継続モデル-チャート上での検索と実行統計
この記事では、運動継続モデルの1つをプログラムによって定義します。 この主なアイデアは、2つの波の定義です(メインと補正) 極値点については、フラクタルだけでなく、 "潜在的な " フラクタル-まだフラクタルとして形成されていない極値点を適用します。