English Русский 中文 Español Deutsch Português
preview
あらゆるタイプのトレーリングストップを開発してEAに接続する方法

あらゆるタイプのトレーリングストップを開発してEAに接続する方法

MetaTrader 5 | 17 10月 2024, 09:23
547 0
Artyom Trishkin
Artyom Trishkin

内容


はじめに

前回の記事に引き続き、今回はトレーリングストップに関する内容をさらに深めます。本稿では、さまざまなストップロスポジションのトレーリングストップを簡単に実装できるトレーリングストップクラスについて考察します。このクラスを用いることで、現在の価格からのストップの移動や、指標値に基づくストップロスレベルの設定など、柔軟なアルゴリズムを作成することが可能になります。この記事を通じて、ストップロス位置を自動で調整するアルゴリズムを作成し、それをEA(エキスパートアドバイザー)に接続する方法を理解できるでしょう。加えて、トレーリングストップの使用がより明確で簡便になることを目指します。

トレーリングストップの操作アルゴリズムを簡単に考えてみましょう。それぞれのトレーリングストップには3つの使用条件があることに同意しましょう。

  • トレーリング開始:トレーリング終了が発動されるポジションの利益ポイント数
  • トレーリングステップ:ストップロスを次に移動するために価格が利益方向に動くべきポイント数
  • トレーリング距離:現在価格からストップロスが配置されるべき距離

これら3つのパラメータは、どのようなトレーリングストップにも適用可能です。設定に応じて必要な場合のみ使用し、不要な場合は省略できます。あるいは、他の値で置き換えることも可能です。例えば、「トレーリング距離」の代わりに、ストップロスを特定の指標値に設定することも可能です。この場合、ストップは指標が示す価格そのものではなく、指標が指示した価格からポイント単位の距離を離れた場所に設定されます。

一般的に、上にリストした3つのパラメータは様々なトレーリングストップで最も使用されるものであり、トレーリングストップクラスを作成する際にはこれらを考慮します。

ストップロス位置を適切な価格に移動する際には、いくつかのチェックが必要です。

  • ストップロス価格が現在価格に近すぎないこと:ストップロス価格は、銘柄のStopLevel (SYMBOL_TRADE_STOPS_LEVEL)値による最小シフトポイントを超える必要があります。これは、現在の終値からの最小ポイント以上離れてストップ注文を設定しなければならないことを意味します。
  • ストップロス価格が、すでに設定されている価格と同一でないこと。また、ロングポジションでは現在のストップロスより高く、ショートポジションでは低く設定されること。
  • トレーリングストップアルゴリズムがパラメータに従っているかを確認すること。

これらは基本的なチェックです。すべてのトレーリングストップアルゴリズムの動作は同じです。ストップを設定するために必要な価格が入力され、必要なすべての制限がチェックされ、すべてのチェックに合格した場合、ストップポジションは指定されたレベルに移動します。

シンプルなトレーリングストップの仕組みは次の通りです。

  1. トレーリング開始:指定された利益ポイントに達するとトレーリングが開始されます(パラメータを使用しない場合はゼロ)。
  2. トレーリングステップ:何ポイント利益が出たらストップレベルを引き上げるかを指定します。
  3. トレーリング距離:現行価格からストップロスの位置までの距離を設定します(パラメータを使用しない場合はゼロ)。
  4. さらに、すべての銘柄の位置を任意のマジックナンバーでトレーリングする必要がある場合は、トレーリングする銘柄とマジックナンバーを設定するか、それぞれNULLと-1を設定することができます。

ポジションの利益が指定されたポイント数に達すると、トレーリングストップが開始され、ストップは現在の価格から一定の距離に設定されます。その後、価格がさらに指定されたポイント数(トレーリングステップ)だけポジションの利益方向に動くと、ストップも指定された距離を保ちながら再度調整され、価格に追随します。このプロセスは、価格が逆方向に動くまで続きます。価格が反転すると、ストップはその時点で固定され、価格がそのレベルに達すると、ストップロスによりポジションが利益確定されます。このように、トレーリングストップは、価格が反転した際にポジションを決済して利益を確保すると同時に、価格がポジションに有利な方向に動く間は、ストップを価格に追随させて徐々に調整します。これにより、利益を保護しながら、さらなる価格上昇に対応することができます。

「トレーリング距離」パラメータの代わりに、指標から得られる価格を使用することも可能です。これは、指標トレーリングです。例えば、前のローソク足のHighやLowなどの値を指定して、指標に基づいたトレーリングストップをおこなうことができます。これは、バー価格によるトレーリングです。ただし、基本的なトレーリングストップアルゴリズム自体は変更されません。

したがって、どのようなトレーリングストップアルゴリズムを作成する際にも、まず基本的なトレーリングストップを構築し、必要な価格をストップロスの位置に渡して、すべての必要なチェックを行った上でストップロスをそのレベルに移動させる必要があります。

前回の記事では、シンプルなトレーリングストップやさまざまな指標に基づいたトレーリングストップ機能について説明しました。この方法を使えば、トレーリングストップをEA(エキスパートアドバイザー)に接続し、現在の銘柄のポジションに対してトレーリングストップをおこなうことが可能です。しかし、これには限界があります。EAで指標を作成し、それぞれの指標に対してトレーリングハンドルをトレーリング関数に送信する必要があります。また、トレーリング関数は、現在の銘柄のポジションにのみ適用されます。

トレーリングストップクラスを使用することで、異なる設定で複数のトレーリングストップクラスのインスタンスを作成でき、作成されたすべてのトレーリングストップは、プログラマーが指定した特定のアルゴリズムに従って1つのEA内で同時に動作させることが可能です。つまり、各パラメータセットごとに独自のアルゴリズムで動作するカスタムトレーリングストップを作成できるのです。このように作成されたトレーリングストップは、特定の条件下でEA内で起動され、ストップロスポジションをトレーリングする複雑なアルゴリズムを実現します。

たとえ異なる銘柄に対して同じトレーリングストップを2つ作成したとしても、それぞれの銘柄に対して別々にトレーリングが作成され、各トレーリングストップは作成時に指定された銘柄のデータに基づいて動作します。これにより、プログラムでのトレーリングの使用が大幅に簡素化されます。トレーリングストップは必要なパラメータを指定して作成し、EAハンドラでそれを呼び出すだけで機能します。公平を期すために、各タイプのトレーリングストップは端末内の既存のポジションの独自の検索を開始するため、このアプローチがすべてのアルゴリズムにとって最適とは限らないことに注意が必要です。真に汎用的なアルゴリズムを作成し、すべてのトレーリングに同じ端末ポジションリストを使用する場合、その設計・開発コストが需要に見合わない可能性があります。この点については本記事の範囲を超える話題です。

要するに、すべての必要なチェックをおこないながら、ポジションのストップロスを指定された価格に移動させるシンプルなトレーリングストップクラスをまず作成します。

前回の記事では、このシンプルで基本的なトレーリングストップをTrailingStopByValue()と呼びましたが、ここではSimpleTrailingと呼ぶことにします。これはカスタムプログラムで活用できます。

前回の記事と同様に、すべてのトレーリングストップクラスを1つのファイルにまとめ、このファイルをEAに接続するだけで済みます。一般的に、インクルードファイルは\MQL5\Include\フォルダやそのサブフォルダに保存されます。


基本トレーリングストップクラス

\MQL5\Include\端末フォルダに、新しいTrailings\サブフォルダを作成し、Trailings.mqhという新しいクラスファイルを配置します。



作成されたTrailings.mqhで、クラス名はCSimpleTrailingとし、CObject標準ライブラリの基本オブジェクトクラスを基本クラスにします。


MQLウィザードで作業を完了すると、 \MQL5\Include\Trailings\Trailings.mqhに以下のクラステンプレートができます。

//+------------------------------------------------------------------+
//|                                                    Trailings.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
class CSimpleTrailing : public CObject
  {
private:

public:
                     CSimpleTrailing();
                    ~CSimpleTrailing();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSimpleTrailing::CSimpleTrailing()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSimpleTrailing::~CSimpleTrailing()
  {
  }
//+------------------------------------------------------------------+


作成したファイルに標準ライブラリ基本オブジェクトのファイルをインクルードします。

//+------------------------------------------------------------------+
//|                                                    Trailings.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Object.mqh>

//+------------------------------------------------------------------+
//| Class of the position StopLoss simple trailing                   |
//+------------------------------------------------------------------+
class CSimpleTrailing : public CObject

なぜ作成したすべてのトレーリングストップクラスを標準ライブラリの基本オブジェクトから継承する必要があるのでしょうか。

これにより、これらのクラスから後続のコレクションを簡単に作成し、リストに配置したり、リスト内で必要なオブジェクトを検索したりすることが可能になります。つまり、もしEAファイル内で単に将来的なクラスのオブジェクトをインスタンスとして使いたいだけであれば、CObjectを継承する必要はありません。しかし、これらのクラスから本格的なコレクションを構築し、MQL5標準ライブラリのあらゆる機能を活用したい場合には、これらのクラスをCObject基本クラスから派生させることが推奨されます。

CObjectクラスはMQL5標準ライブラリの基盤となるクラスであり、すべての子孫クラスに対して、リンクリストの要素として機能するための基本的な機能を提供します。
さらに、多くの仮想メソッドが定義されており、子孫クラスで実装することが可能です。

クラスのprivateセクションで3つのメソッドを宣言します。

class CSimpleTrailing : public CObject
  {
private:
//--- check the criteria for modifying the StopLoss position and return the flag
   bool              CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick);

//--- modify StopLoss of a position by its ticket
   bool              ModifySL(const ulong ticket, const double stop_loss);

//--- return StopLevel in points
   int               StopLevel(void);

protected: 

CheckCriterion()は、ストップロスポジションの変更に必要なすべてのチェックに合格したかどうかのフラグを返します。

ModifySL()は、ポジションのストップロスを指定された値で変更します。

StopLevel()は、銘柄のStopLevelプロパティの値を返します。

前回の記事では、これらのメソッドはすべて別々の関数でした。これらは基となるトレーリングストップクラスの一部として機能するようになり、そこから他のトレーリングのクラスを継承することになります。

クラスのprotectedセクションで、クラスが機能するために必要な変数をすべて宣言します。

protected: 
   string            m_symbol;                        // trading symbol
   long              m_magic;                         // EA ID
   double            m_point;                         // Symbol Point
   int               m_digits;                        // Symbol digits
   int               m_offset;                        // stop distance from price
   int               m_trail_start;                   // profit in points for launching trailing
   uint              m_trail_step;                    // trailing step
   uint              m_spread_mlt;                    // spread multiplier for returning StopLevel value
   bool              m_active;                        // trailing activity flag

//--- calculate and return the StopLoss level of the selected position
   virtual double    GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick);

public:

ここでは、「スプレッド倍率」変数についての説明が必要です。銘柄のStopLevel値は、ストップロスを設定できない価格からの最低距離を示します。サーバーによって設定されているStopLevelがゼロであっても、これはストップレベルがないことを意味するわけではありません。実際には、ストップレベルは変動しており、通常はスプレッドの2倍、または3倍の値になります。これらの値はサーバーの設定によって異なるため、プログラムではこの倍率を任意で設定できるようにしています。たとえば、StopLevelにダブルスプレッドが適用される場合、m_spread_mlt変数は「2」に設定され、トリプルスプレッドなら「3」と設定されます。デフォルトでは「2」に設定されています。

GetStopLossValue()仮想メソッドは、ポジションの損切り価格を計算し返すメソッドです。異なるタイプのトレーリングストップでは、ストップロスレベルの計算方法が異なるため、各トレーリングストップクラスでこのメソッドをオーバーライドして独自の計算ロジックを実装する必要があります。このメソッドが仮想メソッドとして宣言されている理由は、継承されたトレーリングストップクラスがそれぞれ異なるストップロスの計算方法を持つ可能性があるためです。

クラスのpublicセクションには、protectedセクションで宣言された変数に値を設定・取得するメソッド、およびクラスのコンストラクタとデストラクタを記述します。

public:
//--- set trailing parameters
   void              SetSymbol(const string symbol)
                       {
                        this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol);
                        this.m_point  =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT);
                        this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS);
                       }
   void              SetMagicNumber(const long magic)       { this.m_magic = magic;       }
   void              SetStopLossOffset(const int offset)    { this.m_offset = offset;     }
   void              SetTrailingStart(const int start)      { this.m_trail_start = start; }
   void              SetTrailingStep(const uint step)       { this.m_trail_step = step;   }
   void              SetSpreadMultiplier(const uint value)  { this.m_spread_mlt = value;  }
   void              SetActive(const bool flag)             { this.m_active = flag;       }

//--- return trailing parameters
   string            Symbol(void)                     const { return this.m_symbol;       }
   long              MagicNumber(void)                const { return this.m_magic;        }
   int               StopLossOffset(void)             const { return this.m_offset;       }
   int               TrailingStart(void)              const { return this.m_trail_start;  }
   uint              TrailingStep(void)               const { return this.m_trail_step;   }
   uint              SpreadMultiplier(void)           const { return this.m_spread_mlt;   }
   bool              IsActive(void)                   const { return this.m_active;       }

//--- launch trailing with StopLoss offset from the price
   bool              Run(void);

//--- constructors
                     CSimpleTrailing() : m_symbol(::Symbol()), m_point(::Point()), m_digits(::Digits()),
                                         m_magic(-1), m_trail_start(0), m_trail_step(0), m_offset(0), m_spread_mlt(2) {}
                     CSimpleTrailing(const string symbol, const long magic,
                                     const int trailing_start, const uint trailing_step, const int offset);
//--- destructor
                    ~CSimpleTrailing() {}
  };


このクラスは2つのコンストラクタを持ちます。

  • 最初の(デフォルトの)コンストラクタは、現在の銘柄を使用し、マジックナンバーは常に「-1」に設定されます。これは、例外なく現在の銘柄に対するすべてのポジションを対象とすることを意味しています。さらに、トレーリングストップに関するパラメータはすべてゼロに設定されます
  • 2つ目のコンストラクタは、パラメータを受け取る形式で実装され、銘柄名、ポジションのマジックナンバー、および3つのトレーリングパラメータ(スタート、ステップ、シフト)がコンストラクタに渡されます。これにより、ユーザーは特定の銘柄とポジションに対して柔軟にトレーリングストップの動作を設定でき、各パラメータを個別に指定して運用することが可能になります。

パラメトリックコンストラクタの実装を考えてみましょう。

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CSimpleTrailing::CSimpleTrailing(const string symbol, const long magic,
                                 const int trail_start, const uint trail_step, const int offset) : m_spread_mlt(2)
  {
   this.SetSymbol(symbol);
   this.m_magic      = magic;
   this.m_trail_start= trail_start;
   this.m_trail_step = trail_step;
   this.m_offset     = offset;
  }

初期化文字列がすべての変数に現在の銘柄値を設定し、トレーリングのパラメータにゼロ値を設定するデフォルトのコンストラクタとは異なり、ここではコンストラクタは対応する変数に設定された値を取得します。SetSymbol()メソッドは、一度に複数の変数に値を設定します。

   void              SetSymbol(const string symbol)
                       {
                        this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol);
                        this.m_point  =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT);
                        this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS);
                       }

どちらのコンストラクタでも、スプレッドの倍率は2に設定されます。必要であれば、SetSpreadMultiplier()メソッドで変更できます。


以下は、選択されたポジションのストップロスレベルを計算して返す仮想メソッドです。

//+------------------------------------------------------------------+
//| Calculate and return the StopLoss level of the selected position |
//+------------------------------------------------------------------+
double CSimpleTrailing::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick)
  {
//--- calculate and return the StopLoss level depending on the position type
   switch(pos_type)
     {
      case POSITION_TYPE_BUY  :  return(tick.bid - this.m_offset * this.m_point);
      case POSITION_TYPE_SELL :  return(tick.ask + this.m_offset * this.m_point);
      default                 :  return 0;
     }
  }

これはシンプルなトレーリングストップであるため、ポジションのストップロスは常に現在価格から指定された距離に保たれます。

計算されたポジションのストップロス値はメソッドから返され、CheckCriterion()メソッドで検証されます。

//+------------------------------------------------------------------+
//|Check the StopLoss modification criteria and return a flag        |
//+------------------------------------------------------------------+
bool CSimpleTrailing::CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick)
  {
//--- if the stop position and the stop level for modification are equal or a zero StopLoss is passed, return 'false'
   if(::NormalizeDouble(pos_sl - value_sl, this.m_digits) == 0 || value_sl==0)
      return false;
      
//--- trailing variables
   double trailing_step = this.m_trail_step * this.m_point;                      // convert the trailing step into price
   double stop_level    = this.StopLevel() * this.m_point;                       // convert the symbol StopLevel into price
   int    pos_profit_pt = 0;                                                     // position profit in points

//--- depending on the type of position, check the conditions for modifying StopLoss
   switch(pos_type)
     {
      //--- long position
      case POSITION_TYPE_BUY :
         pos_profit_pt = int((tick.bid - pos_open) / this.m_point);              // calculate the position profit in points
         if(tick.bid - stop_level > value_sl                                     // if the StopLoss level is lower than the price with the StopLevel level set down from it (the distance according to StopLevel is maintained) 
            && pos_sl + trailing_step < value_sl                                 // if the StopLoss level exceeds the trailing step set upwards from the current position StopLoss
            && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start)   // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
           )
            return true;
         break;
         
      //--- short position
      case POSITION_TYPE_SELL :
         pos_profit_pt = int((pos_open - tick.ask) / this.m_point);              // calculate the position profit in points
         if(tick.ask + stop_level < value_sl                                     // if the StopLoss level is higher than the price with the StopLevel level set upwards from it (the distance according to StopLevel is maintained)
            && (pos_sl - trailing_step > value_sl || pos_sl == 0)                // if the StopLoss level is below the trailing step set downwards from the current StopLoss or a position has no StopLoss
            && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start)   // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
           )
            return true;
         break;
         
      //--- return 'false' by default
      default:
         break;
     }
     
//--- conditions are not met - return 'false'
   return false;
  }

メソッドに渡されたストップロス値がすべてのフィルタの基準を満たす場合、メソッドはtrueを返し、ポジションのストップロスはModifySL() メソッドを使用して変更されます。

//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool CSimpleTrailing::ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if the EA stop flag is set, report this in the journal and return 'false'
   if(::IsStopped())
     {
      Print("The Expert Advisor is stopped, trading is disabled");
      return false;
     }
//--- if failed to select a position by ticket, report this in the journal and return 'false'
   ::ResetLastError();
   if(!::PositionSelectByTicket(ticket))
     {
      ::PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, ::GetLastError());
      return false;
     }
     
//--- declare the structures of the trade request and the request result
   MqlTradeRequest   request= {};
   MqlTradeResult    result = {};
   
//--- fill in the request structure
   request.action    = TRADE_ACTION_SLTP;
   request.symbol    = ::PositionGetString(POSITION_SYMBOL);
   request.magic     = ::PositionGetInteger(POSITION_MAGIC);
   request.tp        = ::PositionGetDouble(POSITION_TP);
   request.position  = ticket;
   request.sl        = ::NormalizeDouble(stop_loss, this.m_digits);
   
//--- if the trade operation could not be sent, report this to the journal and return 'false'
   if(!::OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, ::GetLastError());
      return false;
     }
     
//--- request to change StopLoss position successfully sent
   return true;
  }


ストップレベルを取得するにはStopLevel()メソッドを使用します。

//+------------------------------------------------------------------+
//| Return StopLevel in points                                       |
//+------------------------------------------------------------------+
int CSimpleTrailing::StopLevel(void)
  {
   int spread     = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_SPREAD);
   int stop_level = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_TRADE_STOPS_LEVEL);
   return int(stop_level == 0 ? spread * this.m_spread_mlt : stop_level);
  }

ストップレベルは一定の値ではなく、動的に変化する可能性があるため、メソッドを呼び出すたびに、銘柄のプロパティから必要な値を要求します。銘柄のStopLevelが0に設定されている場合、銘柄のスプレッドに2を掛けた値を使用します。これはデフォルト値です。

取り上げた3つのメソッドは、前回の記事ですべて別々の関数として実装済みです。これらのメソッドはクラスのprivateセクションに隠され、外部からアクセスされることなく機能を実行します。

主なトレーリングストップメソッドはRun()メソッドであり、ポジションを選択し、そのストップレベルを計算された値に移動させるサイクルを開始します。

//+------------------------------------------------------------------+
//| Launch simple trailing with StopLoss offset from the price       |
//+------------------------------------------------------------------+
bool CSimpleTrailing::Run(void)
  {
//--- if disabled, leave
   if(!this.m_active)
      return false;
//--- trailing variables
   MqlTick tick = {};                           // price structure
   bool    res  = true;                         // result of modification of all positions
//--- check the correctness of the data by symbol
   if(this.m_point==0)
     {
      //--- let's try to get the data again
      ::ResetLastError();
      this.SetSymbol(this.m_symbol);
      if(this.m_point==0)
        {
         ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError());
         return false;
        }
     }
     
//--- in a loop by the total number of open positions
   int total =::PositionsTotal();
   for(int i = total - 1; i >= 0; i--)
     {
      //--- get the ticket of the next position
      ulong  pos_ticket =::PositionGetTicket(i);
      if(pos_ticket == 0)
         continue;
         
      //--- get the symbol and position magic
      string pos_symbol = ::PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = ::PositionGetInteger(POSITION_MAGIC);
      
      //--- if the position does not match the filter by symbol and magic number, leave
      if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol))
         continue;
      
      //--- if failed to get the prices, move on
      if(!::SymbolInfoTick(this.m_symbol, tick))
         continue;

      //--- get the position type, its opening price and StopLoss level
      ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
      double             pos_open =::PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl   =::PositionGetDouble(POSITION_SL);
      
      //--- get the calculated StopLoss level
      double value_sl = this.GetStopLossValue(pos_type, tick);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level and add the result to the res variable
      if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick))
         res &=this.ModifySL(pos_ticket, value_sl);
     }
//--- at the end of the loop, return the result of modifying each position that matches the "symbol/magic" filter
   return res;
  }

このメソッドは、端末のアクティブポジションのリストを調べ、銘柄とマジックナンバーで並び替え、各ポジションのストップを現在の価格から計算された距離に移動させます。各ポジションのストップロスの修正結果はres変数に追加されます。すべてのポジションのストップレベルの変更が終了した時点で、銘柄とマジックフィルターに一致するすべてのポジションのストップレベルが正常に変更された場合はtrueを設定し、少なくとも1つのポジションが正常に変更されなかった場合はfalseを設定します。これは、外部からのストップの変更をさらにコントロールするためにおこなわれます。

では、このクラスをEAに接続して、その機能をテストしてみましょう。

テストをおこなうには、\MQL5\Experts\Examples\端末フォルダにあるMoving Average.mq5を使用します。このファイルをMovingAverageWithSimpleTrail.mq5として保存します。

EAにトレーリングストップクラスファイルをインクルードし、設定に追加パラメータを入力し、シンプルなトレーリングストップクラスのインスタンスを作成します。

//+------------------------------------------------------------------+
//|                                 MovingAverageWithSimpleTrail.mq5 |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade\Trade.mqh>
#include <Trailings\Trailings.mqh>

input group  " - Moving Averages Expert Parameters -"
input double MaximumRisk        = 0.02;    // Maximum Risk in percentage
input double DecreaseFactor     = 3;       // Descrease factor
input int    MovingPeriod       = 12;      // Moving Average period
input int    MovingShift        = 6;       // Moving Average shift

input group  " - Simple Trailing Parameters -"
input int   InpTrailingStart  =  10;      // Trailing start
input int   InpTrailingStep   =  20;      // Trailing step in points
input int   InpTrailingOffset =  30;      // Trailing offset in points
input bool  InpUseTrailing    =  true;    // Use Trailing Stop

//---
int    ExtHandle=0;
bool   ExtHedging=false;
CTrade ExtTrade;

CSimpleTrailing ExtTrailing;              // simple trailing class instance

#define MA_MAGIC 1234501
//+------------------------------------------------------------------+


OnInit()ハンドラで、トレーリングストップのパラメータを設定します

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- prepare trade class to control positions if hedging mode is active
   ExtHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
   ExtTrade.SetExpertMagicNumber(MA_MAGIC);
   ExtTrade.SetMarginMode();
   ExtTrade.SetTypeFillingBySymbol(Symbol());
//--- Moving Average indicator
   ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(ExtHandle==INVALID_HANDLE)
     {
      printf("Error creating MA indicator");
      return(INIT_FAILED);
     }
     
//--- set trailing parameters
   ExtTrailing.SetActive(InpUseTrailing);
   ExtTrailing.SetSymbol(Symbol());
   ExtTrailing.SetMagicNumber(MA_MAGIC);
   ExtTrailing.SetTrailingStart(InpTrailingStart);
   ExtTrailing.SetTrailingStep(InpTrailingStep);
   ExtTrailing.SetStopLossOffset(InpTrailingOffset);
//--- ok
   return(INIT_SUCCEEDED);
  }


OnTick()ハンドラで、トレーリングストップを開始します

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//---
   if(SelectPosition())
      CheckForClose();
   else
      CheckForOpen();
      
//--- launch trailing
   ExtTrailing.Run();
  }


EAファイルに必要な追加入力はこれだけです。コンパイルして、ストラテジーテスターでEURUSD M15で実行してみましょう。実行遅延のないすべてのティックを使用します。

パラメータは以下の通り(トレーリングストップは無効)です。


トレーリングストップを無効にしてテストを実行し、結果を見てみましょう。



次にトレーリングストップを使用します。


まったく同じテストをおこないます。

テスト結果は以下の通りです。



結果は少し良くなりました。これらの設定では、トレーリングストップが統計値をわずかに向上させることは明らかです。

次に、シンプルなトレーリングストップをもとに、他のトレーリングストップを作ることができます。

パラボリックSARやクライアント端末に表示されるトレンドフォロー指標のリストからの様々な移動平均線を含むいくつかの指標のトレーリングストップクラスを実装してみましょう。


指標別トレーリングストップクラス

同じファイルにコードを書き続けます。指標に基づくトレーリングストップの場合、必要な指標を作成する必要があります。そのためには、データシリーズを構築するために使用する銘柄と時間枠を指定し、さらに指標にアクセスするために、作成した指標のハンドルを変数に保存する必要があります。指標からデータを受け取る際は、ハンドルによって指定された指標に依存することはありません。データは、ハンドルを使用して指定した指標からCopyBuffer()メソッドによって取得されます。

指標からデータを取得するためのすべてのアクションは、指標ベースのトレーリングストップクラスに設定する必要があります。この場合、異なる指標で動作する各トレーリングクラスには、これらのアクションをすべて設定することになります。これは可能ですが、最適な方法ではありません。したがって、このクラスでは、ハンドルを使って指標からデータを取得するメソッドを実装し、各指標に対して同じ変数(銘柄、時間枠、データ時系列インデックス、指標ハンドル)を指定します。トレーリングストップクラス自体は、基本クラスから派生したものになります。その後、クラス編成をおこないます。基本クラスは、ハンドルによる指標データへのアクセスを特徴としており、継承されたクラスは、必要な指標の作成とそのデータの処理を含みます。

Trailings.mqhファイルにコードを書き続けます。指標に基づくトレーリングストップの基本クラスを実装してみましょう。

//+------------------------------------------------------------------+
//| Base class of indicator-based trailings                          |
//+------------------------------------------------------------------+
class CTrailingByInd : public CSimpleTrailing
  {
protected:
   ENUM_TIMEFRAMES   m_timeframe;            // indicator timeframe
   int               m_handle;               // indicator handle
   uint              m_data_index;           // indicator data bar
   string            m_timeframe_descr;      // timeframe description

//--- return indicator data
   double            GetDataInd(void)              const { return this.GetDataInd(this.m_data_index); }
   
//--- calculate and return the StopLoss level of the selected position
   virtual double    GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick);

public:
//--- set parameters
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe)
                       {
                        this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
                        this.m_timeframe_descr=::StringSubstr(::EnumToString(this.m_timeframe), 7);
                       }
   void              SetDataIndex(const uint index)                  { this.m_data_index=index;       }
                       
//--- return parameters
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_timeframe;       }
   uint              DataIndex(void)                           const { return this.m_data_index;      }
   string            TimeframeDescription(void)                const { return this.m_timeframe_descr; }
     
//--- return indicator data from the specified timeseries index
   double            GetDataInd(const int index) const;
                      
//--- constructors
                     CTrailingByInd(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_timeframe(::Period()), 
                                            m_handle(INVALID_HANDLE), m_data_index(1), m_timeframe_descr(::StringSubstr(::EnumToString(::Period()), 7)) {}
                     
                     CTrailingByInd(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int trail_start, const uint trail_step, const int trail_offset) :
                                    CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_data_index(1), m_handle(INVALID_HANDLE)
                       {
                        this.SetTimeframe(timeframe);
                       }
//--- destructor
                    ~CTrailingByInd(void)
                       {
                        if(this.m_handle!=INVALID_HANDLE)
                          {
                           ::IndicatorRelease(this.m_handle);
                           this.m_handle=INVALID_HANDLE;
                          }
                       }
  };

このクラスはシンプルなトレーリングストップのクラスから継承されているため、このクラスのすべての機能が利用可能であり、ハンドルによって指標データにアクセスするためのメソッドも用意されています。

クラスのprotectedセクションでは、クラス変数が宣言され、ハンドルを使用して指標からデータを受け取るメソッドと、親クラスの同名のメソッドをオーバーライドしてストップロスレベルを計算する仮想メソッドが宣言されています。

クラスのpublicセクションには、指標のプロパティを設定したり取得したりするメソッド、コンストラクタ、デストラクタが含まれています。

デフォルトのコンストラクタの初期化文字列では、マジックナンバーの値-1(すべてのポジション)と、トレーリングのゼロパラメータが親クラスに渡されます。指標の計算には、現在のチャートの時間枠が使用されます。

パラメトリックコンストラクタでは、指標を計算するためのチャートの銘柄と周期、およびすべてのトレーリングストップパラメータが、コンストラクタの仮パラメータとして渡されます。
クラスのデストラクタでは、指標の計算部分が解放され、ハンドルを格納する変数にはINVALID_HANDLEの値が設定されます。


以下は、指定された時系列インデックスから指標データを返すメソッドです。

//+------------------------------------------------------------------+
//| Return indicator data from the specified timeseries index        |
//+------------------------------------------------------------------+
double CTrailingByInd::GetDataInd(const int index) const
  {
//--- if the handle is invalid, report this and return "empty value"
   if(this.m_handle==INVALID_HANDLE)
     {
      ::PrintFormat("%s: Error. Invalid handle",__FUNCTION__);
      return EMPTY_VALUE;
     }
//--- get the value of the indicator buffer by the specified index
   double array[1];
   ::ResetLastError();
   if(::CopyBuffer(this.m_handle, 0, index, 1, array)!=1)
     {
      ::PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, ::GetLastError());
      return EMPTY_VALUE;
     }
//--- return the received value
   return array[0];
  }

CopyBuffer()を使用して、指定したインデックスで単一の指標バーの値を取得します。正常に受信した場合、その値を返しますが、エラーが発生した場合は操作ログにメッセージを表示し、空の値(EMPTY_VALUE)を返します。

CopyBuffer()関数は、ハンドルによってどの指標からのデータでも動作するため、このメソッドは普遍的であり、各クラスの指標に基づくトレーリングストップで変更することなく使用できます。


以下は、選択されたポジションのストップロスレベルを計算して返す仮想メソッドです。

//+------------------------------------------------------------------+
//| Calculate and return the StopLoss level of the selected position |
//+------------------------------------------------------------------+
double CTrailingByInd::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick)
  {
//--- get the indicator value as a level for StopLoss
   double data=this.GetDataInd();
       
//--- calculate and return the StopLoss level depending on the position type
   switch(pos_type)
     {
      case POSITION_TYPE_BUY  :  return(data!=EMPTY_VALUE ? data - this.m_offset * this.m_point : tick.bid);
      case POSITION_TYPE_SELL :  return(data!=EMPTY_VALUE ? data + this.m_offset * this.m_point : tick.ask);
      default                 :  return 0;
     }
  }

このメソッドでは、親クラスにおいて単純に価格からオフセットされたストップロスレベルを計算します。このメソッドの同じクラスでは、指標からデータを受け取り、それをストップロスレベルとして渡す必要があります。指標からデータを受信していない場合、このメソッドは現在の価格を返します。StopLevelレベルでは、現在の価格でストップレベルを設定することができないため、ポジションのストップロスを設定することができません。

その結果、このクラスはハンドルによる指標データへのアクセスと、指標値によるストップロスポジションのレベル計算を実装し、すべてのメソッドが子クラスで利用できるようになります。

次に、作成したクラスに基づいて、指標ベースのトレーリングストップクラスを実装できます。


パラボリックSARのトレーリングストップクラス

引き続き、同じファイル(\MQL5\Include\Trailings\Trailings.mqh)にコードを書きます。

指標ベースのトレーリングストップの他のすべてのクラスと同様に、パラボリックSAR トレーリングストップクラスは指標ベースのトレーリングストップの基本クラスから派生させる必要があります

//+------------------------------------------------------------------+
//| Parabolic SAR position StopLoss trailing class                   |
//+------------------------------------------------------------------+
class CTrailingBySAR : public CTrailingByInd
  {
private:
   double            m_sar_step;             // Parabolic SAR Step parameter
   double            m_sar_max;              // Parabolic SAR Maximum parameter

public:
//--- set Parabolic SAR parameters
   void              SetSARStep(const double step)                   { this.m_sar_step=(step<0.0001 ? 0.0001 : step);   }
   void              SetSARMaximum(const double max)                 { this.m_sar_max =(max <0.0001 ? 0.0001 : max);    }
   
//--- return Parabolic SAR parameters
   double            SARStep(void)                             const { return this.m_sar_step;                          }
   double            SARMaximum(void)                          const { return this.m_sar_max;                           }
   
//--- create Parabolic SAR indicator and return the result
   bool              Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum);

//--- constructors
                     CTrailingBySAR() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0)
                       {
                        this.Initialize(this.m_symbol, this.m_timeframe, 0.02, 0.2);
                       }
                     
                     CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                                    const double sar_step, const double sar_maximum,
                                    const int trail_start, const uint trail_step, const int trail_offset);
//--- destructor
                    ~CTrailingBySAR(){}
  };

クラスのprivateセクションで、パラボリックSAR指標パラメータの値を格納する変数を宣言します。

publicセクションには、指標プロパティの値を設定したり返したりするメソッドが含まれています。指標を作成するメソッドと、デフォルトとパラメトリックの2つのコンストラクタを宣言しました。

コンストラクタのデフォルトでは、指標は現在の銘柄とチャート期間に作成され、トレーリングストップのパラメータ値はゼロに設定されます。

パラメトリックコンストラクタでは、すべてのパラメータがコンストラクタの入力に渡され、コンストラクタに渡されたパラメータに基づいて指標が作成されます。

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CTrailingBySAR::CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                               const double sar_step, const double sar_maximum,
                               const int trail_start, const uint trail_step, const int trail_offset) :
                               CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset)
  {
   this.Initialize(symbol, timeframe, sar_step, sar_maximum);
  }


Initialize()メソッドは、パラボリックSAR指標を作成し、その作成結果を返します。

//+------------------------------------------------------------------+
//| create Parabolic SAR indicator and return the result             |
//+------------------------------------------------------------------+
bool CTrailingBySAR::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum)
  {
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
   this.SetSARStep(sar_step);
   this.SetSARMaximum(sar_maximum);
   ::ResetLastError();
   this.m_handle=::iSAR(this.m_symbol, this.m_timeframe, this.m_sar_step, this.m_sar_max);
   if(this.m_handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                    this.m_symbol, this.TimeframeDescription(), this.m_sar_step, this.m_sar_max, ::GetLastError());
     }
   return(this.m_handle!=INVALID_HANDLE);
  }


作成したパラボリックSARベースのトレーリングストップクラスをテストしてみましょう。

テストをおこなうために、標準配信のEA \MQL5\Experts\Advisors\ExpertMACD.mq5を使用します。このファイルをExpertMACDWithTrailingBySAR.mq5として保存し、必要なコード文字列を追加します。

トレーリングストップクラスを含むファイルをインクルードし、新しい入力を追加し、トレーリングストップクラスのインスタンスを宣言します。

//+------------------------------------------------------------------+
//|                                                   ExpertMACD.mq5 |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include                                                          |
//+------------------------------------------------------------------+
#include <Expert\Expert.mqh>
#include <Expert\Signal\SignalMACD.mqh>
#include <Expert\Trailing\TrailingNone.mqh>
#include <Expert\Money\MoneyNone.mqh>

#include <Trailings\Trailings.mqh>

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
//--- inputs for expert
input group  " - ExpertMACD Parameters -"
input string Inp_Expert_Title            ="ExpertMACD";
int          Expert_MagicNumber          =10981;
bool         Expert_EveryTick            =false;
//--- inputs for signal
input int    Inp_Signal_MACD_PeriodFast  =12;
input int    Inp_Signal_MACD_PeriodSlow  =24;
input int    Inp_Signal_MACD_PeriodSignal=9;
input int    Inp_Signal_MACD_TakeProfit  =50;
input int    Inp_Signal_MACD_StopLoss    =20;

input group  " - Trailing By SAR Parameters -"
input ENUM_TIMEFRAMES   InpTimeframeSAR   =  PERIOD_CURRENT;   // Parabolic SAR Timeframe
input double            InpStepSAR        =  0.02;             // Parabolic SAR Step
input double            InpMaximumSAR     =  0.2;              // Parabolic SAR Maximum
input int               InpTrailingStart  =  0;                // Trailing Start
input uint              InpTrailingStep   =  0;                // Trailing Step
input int               InpTrailingOffset =  0;                // Trailing Offset
input bool              InpUseTrailing    =  true;             // Use Trailing Stop

//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert;
CTrailingBySAR ExtTrailing;


OnInit()ハンドラで、パラボリックベースのトレーリングストップを初期化します

//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Initializing trailing
   if(ExtTrailing.Initialize(NULL,PERIOD_CURRENT,InpStepSAR,InpMaximumSAR))
     {
      ExtTrailing.SetMagicNumber(Expert_MagicNumber);
      ExtTrailing.SetTrailingStart(InpTrailingStart);
      ExtTrailing.SetTrailingStep(InpTrailingStep);
      ExtTrailing.SetStopLossOffset(InpTrailingOffset);
      ExtTrailing.SetActive(InpUseTrailing);
     }
   
//--- Initializing expert


OnTick()ハンドラで、トレーリングストップを有効にします

//+------------------------------------------------------------------+
//| Function-event handler "tick"                                    |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   ExtExpert.OnTick();
   ExtTrailing.Run();
  }


EAをコンパイルし、デフォルトのパラメータでテスターで実行してみましょう。



ご覧のように、ポジションのストップレベルは、パラボリックSARの最初のバーの値によって正しくトレーリングされています。

それでは、クライアント端末に表示されるさまざまな移動平均のトレーリングストップクラスを作成してみましょう。


移動平均のトレーリングストップクラス

これらのクラスは、入力のセットと、Initialize()メソッドがそのクラスに対応する指標を作成するという点でのみ、パラボリック SAR トレーリングストップクラスと異なります。

これに基づき、適応型移動平均(Adaptive Moving Average)ベースのトレーリングストップクラスのみを検討します。

//+------------------------------------------------------------------+
//| Adaptive Moving Average position StopLoss trailing class         |
//+------------------------------------------------------------------+
class CTrailingByAMA : public CTrailingByInd
  {
private:
   int               m_period;               // Period AMA parameter
   int               m_fast_ema;             // Fast EMA Period parameter
   int               m_slow_ema;             // Slow EMA Period parameter
   int               m_shift;                // Shift AMA parameter
   ENUM_APPLIED_PRICE m_price;               // Applied Price AMA parameter

public:
//--- set AMA parameters
   void              SetPeriod(const uint period)                    { this.m_period=int(period<1 ? 9 : period);     }
   void              SetFastEMAPeriod(const uint period)             { this.m_fast_ema=int(period<1 ? 2 : period);   }
   void              SetSlowEMAPeriod(const uint period)             { this.m_slow_ema=int(period<1 ? 30 : period);  }
   void              SetShift(const int shift)                       { this.m_shift = shift;                         }
   void              SetPrice(const ENUM_APPLIED_PRICE price)        { this.m_price = price;                         }
   
//--- return AMA parameters
   int               Period(void)                              const { return this.m_period;                         }
   int               FastEMAPeriod(void)                       const { return this.m_fast_ema;                       }
   int               SlowEMAPeriod(void)                       const { return this.m_slow_ema;                       }
   int               Shift(void)                               const { return this.m_shift;                          }
   ENUM_APPLIED_PRICE Price(void)                              const { return this.m_price;                          }
   
//--- create AMA indicator and return the result
   bool              Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe,
                                const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price);
   
//--- constructors
                     CTrailingByAMA() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0)
                       {
                        this.Initialize(this.m_symbol, this.m_timeframe, 9, 2, 30, 0, PRICE_CLOSE);
                       }
                     
                     CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                                    const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price,
                                    const int trail_start, const uint trail_step, const int trail_offset);
//--- destructor
                    ~CTrailingByAMA(){}
  };
//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CTrailingByAMA::CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                               const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price,
                               const int trail_start, const uint trail_step, const int trail_offset) :
                               CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset)
  {
   this.Initialize(symbol, timeframe, period, fast_ema, slow_ema, shift, price);
  }
//+------------------------------------------------------------------+
//| create AMA indicator and return the result                       |
//+------------------------------------------------------------------+
bool CTrailingByAMA::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe,
                                const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price)
  {
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
   this.SetPeriod(period);
   this.SetFastEMAPeriod(fast_ema);
   this.SetSlowEMAPeriod(slow_ema);
   this.SetShift(shift);
   this.SetPrice(price);
   ::ResetLastError();
   this.m_handle=::iAMA(this.m_symbol, this.m_timeframe, this.m_period, this.m_fast_ema, this.m_slow_ema, this.m_shift, this.m_price);
   if(this.m_handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d",
                    this.m_symbol, this.TimeframeDescription(), this.m_period, this.m_fast_ema, this.m_slow_ema,
                    ::StringSubstr(::EnumToString(this.m_price),6), ::GetLastError());
     }
   return(this.m_handle!=INVALID_HANDLE);
  }

このクラスは、前述のパラボリックSARに基づいたトレーリングストップクラスとまったく同じです。各MAに基づいたトレーリングストップクラスは、指標パラメータを格納するための独自の変数セットと、これらの変数に対応する値を設定したり返したりするためのメソッドを備えていなければなりません。MAに基づいたトレーリングストップクラスはすべて同じです。以下に添付したTrailings.mqhファイルにあるコードを見て学習することができます。また、以下に添付するExpertMACDWithTrailingByMA.mq5ファイルからテストEAを起動して、MAに基づいたトレーリングストップクラスをテストすることができます。

端末に提供される標準的な指標に基づいて、ポジションストップロスを配置するためのレベルを示すのに適したシンプルなトレーリングストップと他のトレーリングストップのクラスを作成しました。

しかし、紹介したクラスを使って作成できるトレーリングストップの型はこれだけではありません。ロングポジションとショートポジションで別々に指定された特定の価格レベルにポジションのストップレベルを移動させるトレーリングストップもあります。例えば、High/Lowローソク足に基づくトレーリングストップや、フラクタル指標に基づくトレーリングストップなどがその例です。

このようなトレーリングストップを実装するためには、ポジションの種類ごとにストップロスを設定するレベルを個別に指定できるようにする必要があります。そのためのクラスを作成してみましょう。


指定したストップロスレベルのトレーリングストップ

指定された値に基づいてトレーリングストップを作成するためには、指標のパラメータの代わりに変数を定義し、ロングおよびショートポジションのストップロス値を設定する必要があります。指標からデータを取得するのではなく、これらの変数を使用して指定された値からストップロスポジションの値を計算します。この値は、トレーリングのRun()メソッドを呼び出す際に、制御プログラムから直接設定されます。

次のクラスを実装しましょう。

//+------------------------------------------------------------------+
//| Trailing class based on a specified value                        |
//+------------------------------------------------------------------+
class CTrailingByValue : public CSimpleTrailing
  {
protected:
   double            m_value_sl_long;     // StopLoss level for long positions
   double            m_value_sl_short;    // StopLoss level for short positions

//--- calculate and return the StopLoss level of the selected position
   virtual double    GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick);

public:
//--- return StopLoss level for (2) long and (2) short positions
   double            StopLossValueLong(void)    const { return this.m_value_sl_long;   }
   double            StopLossValueShort(void)   const { return this.m_value_sl_short;  }

//--- launch trailing with the specified StopLoss offset from the price
   bool              Run(const double value_sl_long, double value_sl_short);

//--- constructors
                     CTrailingByValue(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_value_sl_long(0), m_value_sl_short(0) {}
                     
                     CTrailingByValue(const string symbol, const long magic, const int trail_start, const uint trail_step, const int trail_offset) :
                                      CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_value_sl_long(0), m_value_sl_short(0) {}
//--- destructor
                    ~CTrailingByValue(void){}
  };

このクラスは、指標ベースのトレーリングストップのすべてのクラスとほぼ同様ですが、指標を作成する必要がないため、Initialize()メソッドは含まれていません。

選択したポジションのストップロスレベルを計算して返す仮想メソッドでは、ストップロスの値がロングポジションとショートポジションのストップレベルから計算されます。

//+------------------------------------------------------------------+
//| Calculate and return the StopLoss level of the selected position |
//+------------------------------------------------------------------+
double CTrailingByValue::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick)
  {
//--- calculate and return the StopLoss level depending on the position type
   switch(pos_type)
     {
      case POSITION_TYPE_BUY  :  return(this.m_value_sl_long  - this.m_offset * this.m_point);
      case POSITION_TYPE_SELL :  return(this.m_value_sl_short + this.m_offset * this.m_point);
      default                 :  return 0;
     }
  }


ロングポジションとショートポジションのストップロス値は、Run() メソッドの仮パラメータを使用してクラスに渡されます。

//+------------------------------------------------------------------+
//| Launch trailing with the specified StopLoss offset from the price|
//+------------------------------------------------------------------+
bool CTrailingByValue::Run(const double value_sl_long, double value_sl_short)
  {
   this.m_value_sl_long =value_sl_long;
   this.m_value_sl_short=value_sl_short;
   return CSimpleTrailing::Run();
  }

まず、仮パラメータで渡された値がクラスのメンバ変数に設定され、それから親クラスのRun()メソッドが呼ばれます。再定義されたGetStopLossValue()仮想メソッドが親クラスから呼び出され、ポジションのストップレベルはその中で計算された値に設定されます。


トレーリングストップをEAに接続する

指標ベースのトレーリングストップをテストする際に、トレーリングストップをEAに接続する方法については既に説明しました。次に、渡された値、すなわちローソク足のHighとLowに基づいて、どのようにトレーリングストップを接続し、起動するかを考えてみましょう。

標準で含まれているEA\MQL5\Experts\Advisors\ExpertMACD.mq5にトレーリングストップを含めます。このファイルをExpertMACDWithTrailingByValue.mq5として保存し、必要な改良を加えます。

EAにトレーリングストップクラスを含むファイルをインクルードし、トレーリングセットアップ入力を追加し、値ベースのトレーリングストップクラスインスタンスを宣言します。

//+------------------------------------------------------------------+
//|                                                   ExpertMACD.mq5 |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include                                                          |
//+------------------------------------------------------------------+
#include <Expert\Expert.mqh>
#include <Expert\Signal\SignalMACD.mqh>
#include <Expert\Trailing\TrailingNone.mqh>
#include <Expert\Money\MoneyNone.mqh>

#include <Trailings\Trailings.mqh>

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
//--- inputs for expert
input group  " - ExpertMACD Parameters -"
input string Inp_Expert_Title            ="ExpertMACD";
int          Expert_MagicNumber          =10981;
bool         Expert_EveryTick            =false;
//--- inputs for signal
input int    Inp_Signal_MACD_PeriodFast  =12;
input int    Inp_Signal_MACD_PeriodSlow  =24;
input int    Inp_Signal_MACD_PeriodSignal=9;
input int    Inp_Signal_MACD_TakeProfit  =50;
input int    Inp_Signal_MACD_StopLoss    =20;

input group  " - Trailing By Value Parameters -"
input ENUM_TIMEFRAMES   InpTimeframe      =  PERIOD_CURRENT;   // Data Rates Timeframe
input uint              InpDataRatesIndex =  2;                // Data Rates Index for StopLoss
input uint              InpTrailingStep   =  0;                // Trailing Step
input int               InpTrailingStart  =  0;                // Trailing Start
input int               InpTrailingOffset =  0;                // Trailing Offset
input bool              InpUseTrailing    =  true;             // Use Trailing Stop

//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert;
CTrailingByValue ExtTrailing;

データレートタイムフレーム inputは、チャートの期間であり、ショートポジションのストップレベルの高値とロングポジションのストップレベルの安値がそこから取られます。

ストップロス用のデータレートインデックスは、データレートの時間枠期間を持つチャート上のバーインデックスであり、そこからストップロスを設定するために高値と安値を取得します。


EAのOnInit()ハンドラで、トレーリングストップのパラメータを初期化します

//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Initializing trailing
   ExtTrailing.SetMagicNumber(Expert_MagicNumber);
   ExtTrailing.SetTrailingStart(InpTrailingStart);
   ExtTrailing.SetTrailingStep(InpTrailingStep);
   ExtTrailing.SetStopLossOffset(InpTrailingOffset);
   ExtTrailing.SetActive(InpUseTrailing);
   
//--- Initializing expert

マジックナンバーがEAのものと一致するポジションのストップレベルのみがトレーリングされます。言い換えれば、EAによって建てられたポジションのみをトレーリングします。


EAのOnTick()ハンドラで、input InpDataRatesIndexで指定されたインデックスでバーデータを取得し、 ロング ポジションとショートポジションの損切り価格(バーのHighとLow)を指定しながらトレーリングストップを開始します。

//+------------------------------------------------------------------+
//| Function-event handler "tick"                                    |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   ExtExpert.OnTick();
   MqlRates rates[1]={};
   if(CopyRates(ExtTrailing.Symbol(),InpTimeframe,InpDataRatesIndex,1,rates))
      ExtTrailing.Run(rates[0].low,rates[0].high);
  }

EAにトレーリングストップを接続するために必要なことはこれだけです。すでにお気づきかもしれませんが、異なるトレーリングストップをEAに接続する際の違いは、異なるトレーリングタイプのインスタンスを宣言することだけです。それ以外のトレーリング接続の手順はほとんど同じであり、特に疑問や疑念を抱く必要はありません。

EAをコンパイルし、デフォルト設定のビジュアルテスターモードで実行してみましょう。

ストップポジションが、時系列2にインデックスを持つローソク足の高値と安値にどのように設定されているかを確認できます。

このようなトレーリングが取引システムでどの程度効果的かは、独自のテストが必要です。

さらに、この記事で紹介されているクラスを使用すれば、誰でも自分のアルゴリズムに基づいてカスタムトレーリングストップを作成できます。ここには、ほとんど無限の研究の余地があります。


結論

この記事では、あらゆるEAに簡単にトレーリングストップを組み込むことができる様々なトレーリングストップのクラスを作成しました。作成されたクラスは、カスタムアルゴリズムを使用してトレーリングストップを実装するための優れたツールセットでもあります。

例では、クラスオブジェクトを扱う方法の一つとして、トレーリングクラスオブジェクトのインスタンスを作成する方法のみを説明しました。この方法により、必要なトレーリングのタイプや、そのパラメータを事前にプログラムで決定することが可能になります。各トレーリングに対して、1つのオブジェクトがグローバルエリアに作成されます。このアプローチはシンプルで明快ですが、プログラム実行中に「new」オブジェクト生成演算子を使用してトレーリングのオブジェクトを動的に生成する場合、リンクされたオブジェクトリストを作成するために標準ライブラリが提供する機能を利用する方が良いでしょう。これらの目的のために、すべてのトレーリングクラスはCObject標準ライブラリの基本オブジェクトから派生しています。このアプローチについては、今回のトピックの範囲外なので、コメントで議論することができます。

この記事で紹介されているすべてのクラスは、開発に「そのまま」使用することもできますし、ニーズやタスクに合わせて変更することも可能です。


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

多通貨エキスパートアドバイザーの開発(第11回):最適化の自動化(最初のステップ) 多通貨エキスパートアドバイザーの開発(第11回):最適化の自動化(最初のステップ)
良いEAを得るためには、取引戦略の複数のインスタンスから優れたパラメータセットを選択する必要があります。これを実現するためには、さまざまな銘柄で最適化を行い、最良の結果を選ぶという手動のプロセスがあります。しかし、この作業をプログラムに任せ、より生産的な活動に専念したほうが効率的です。
アルゴリズム取引のリスクマネージャー アルゴリズム取引のリスクマネージャー
本稿の目的は、リスクマネージャーを利用する必要性を証明し、アルゴリズム取引におけるリスク管理の原則を別クラスで実践することで、金融市場におけるデイ取引と投資におけるリスク標準化アプローチの有効性を誰もが検証できるようにすることです。この記事では、アルゴリズム取引用のリスクマネージャークラスを作成します。これは、手動取引のリスクマネージャーの作成について述べた前回の記事の論理的な続きです。
コードロックアルゴリズム(CLA) コードロックアルゴリズム(CLA)
この記事では、コードロックを単なるセキュリティメカニズムとしてではなく、複雑な最適化問題を解くためのツールとして再考し、新たな視点から捉えます。セキュリティ装置にとどまらず、最適化への革新的アプローチのインスピレーション源となるコードロックの世界をご紹介します。各ロックが特定の問題の解を表す「ロック」の母集団を作り、機械学習や取引システム開発など様々な分野でこれらのロックを「ピッキング」し、最適解を見つけるアルゴリズムを構築します。
彗尾アルゴリズム(CTA) 彗尾アルゴリズム(CTA)
この記事では、ユニークな宇宙物体である彗星と、太陽に接近する際に形成されるその印象的な尾にインスパイアされた「彗尾最適化アルゴリズム(CTA: Comet Tail Algorithm)」について考察します。このアルゴリズムは、彗星とその尾の運動の概念に基づき、最適化問題の最適解を見つけることを目的としています。