English Русский 中文 Español Deutsch Português
preview
事後取引分析:ストラテジーテスターにおけるトレーリングストップと新しいストップレベルの選択

事後取引分析:ストラテジーテスターにおけるトレーリングストップと新しいストップレベルの選択

MetaTrader 5 |
140 4
Artyom Trishkin
Artyom Trishkin

内容


はじめに

前回の記事では、実際の口座で得られた取引結果をもとに、クライアントターミナルのストラテジーテスターで取引をおこなうEAを作成しました。さらに、異なるストップ注文サイズを使用してテストできるよう、StopLossおよびTakeProfitの値を変更できる機能を追加しました。その結果は意外なものでした。損失ではなく、実際の取引で発生した損失とほぼ同等の利益が得られたのです。つまり、もしテスター上で利益を出せたStopLossおよびTakeProfitの値を実際の取引口座で使用していれば、実際の取引でも同様に利益を上げられたことになります。これは単にストップサイズを変更しただけの結果です。では、ここにトレーリングストップを加えたらどうなるでしょうか。取引結果はどのように変化するでしょうか。

今回は、いくつかの異なるトレーリングストップをEAに組み込み、ストラテジーテスターでテストをおこないます。どのような結果が得られるか確認していきましょう。


ポジショントレーリングストップのクラス選択と改良

ここでは、パラボリックSAR移動平均を利用したトレーリング機能をEAに組み込んでみます。パラボリックSARは、その名称(SAR = Stop And Reverse)と説明からも分かるように、価格変動に応じてストップラインを動かすのに非常に適したインジケーターです。

Parabolic SAR(パラボリックSAR)

パラボリックSARは、トレンド相場を分析するために開発されたインジケーターです。このインジケーターは価格チャートで作成されます。このインジケーターは移動平均に似ています に似ていますが、より高い加速度で移動し、価格の面でその位置が変化する可能性があるという唯一の違いがあります。ブル相場(上昇トレンド)ではインジケーターは価格の下にあります。ベア市場(下降トレンド)の時はインジケーターは価格の上に表示されます。

価格がパラボリックSARのラインを超えると、インジケーターは転換し、さらにその値は価格の反対側に位置します。上記のようなインジケーターの転換が起こった場合、1つ前の期間の高値か安値が起点となります。インジケーターが転換した場合、トレンドの終わり(調整またはフラット)かトレンドの転換の印となります。

パラボリックSARは、決済タイミングを見出すインジケーターとして優秀です。買いポジションは、価格がSARより下がった時に決済するべきです。売りポジションは、価格がSARよりも上がった時に決済するべきです。つまり、パラボリックSARの方向を追跡し、パラボリックSARと同じ方向にあるポジションのみを保有する必要があります。このインジケーターはよくトレール注文として使用されます。

買いポジションを持った場合(つまり、価格がSARよりも上にある場合)、パラボリックSARは価格の方向に関わらず上がります。パラボリックSARラインの移動量は、値動きの大きさによって異なります。

一方、移動平均線も、その性質上、価格にやや遅れて追従するため、ストップラインを価格に追随させる用途にも適しています。ロングポジションでは、移動平均線が価格より下にある場合、その値を基準にストップを価格に追従させることができます。価格が移動平均線を下回ると、ストップが発動します。逆にショートポジションの場合は、価格が移動平均線より下にあるときに、その線を基準としてストップを追従させます。

以前の「あらゆるタイプのトレーリングストップを開発してEAに接続する方法」では、トレーリング機能をクラス化し、それをEAに組み込む手法を紹介しました。

今回もその考え方を活用します。ただし、前回の実装では各銘柄やマジックナンバーごとに独自のポジションループが存在していました。これは改善が必要ですが、修正自体は容易です。

記事に添付されているTrailings.mqhファイルをダウンロードします。これは現在必要な唯一のファイルです。 前回の記事では、取引履歴による取引用のクラスとEAファイルを含むフォルダ(\MQL5\Experts\TradingByHistoryDeals)を作成しました。 ダウンロードしたTrailings.mqhファイルを保存し、MetaEditorで開きます。

プログラムから呼び出される中心的なメソッドは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;
  }

このメソッドを各銘柄の取引オブジェクトごとに呼び出すと(前回の記事を参照)、ループの回数が使用している銘柄数だけ増えてしまいます。これは処理効率の面で望ましくありません。したがって、すべてのポジションを処理するループはプログラム本体に統合し、そこから各トレーリングメソッドを呼び出すように設計するのが理想です。ただし、既存のRun()には内部ループがあるため、ポジションのチケット番号を指定して個別にストップを動かせる新しいRun()メソッドを作成し、それをメインループから呼び出す形にします。

まず、シンプルなトレーリングクラスに、対象ポジションのチケットを受け取り処理できる新しいメソッドを宣言します。

//+------------------------------------------------------------------+
//| Class of the position StopLoss simple trailing                   |
//+------------------------------------------------------------------+
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: 
   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:
//--- 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);
   bool              Run(const ulong pos_ticket);

//--- 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() {}
  };

続いて、この新しいメソッドの実装をクラス本体の外側に記述します。

//+------------------------------------------------------------------+
//| Launch simple trailing with StopLoss offset from the price       |
//+------------------------------------------------------------------+
bool CSimpleTrailing::Run(const ulong pos_ticket)
  {
//--- if trailing is disabled, or the ticket is invalid, we leave
   if(!this.m_active || pos_ticket==0)
      return false;
      
//--- trailing variables
   MqlTick tick = {};   // price structure
   
//--- check the correctness of the data by symbol
   ::ResetLastError();
   if(this.m_point==0)
     {
      //--- let's try to get the data again
      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;
        }
     }
//--- select a position by ticket
   if(!::PositionSelectByTicket(pos_ticket))
     {
      ::PrintFormat("%s: PositionSelectByTicket(%I64u) failed. Error %d",__FUNCTION__, pos_ticket, ::GetLastError());
      return false;
     }
     
//--- if prices could not be obtained, return 'false'
   if(!::SymbolInfoTick(this.m_symbol, tick))
      return false;

//--- 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 the conditions for modifying StopLoss are suitable, return the result of modifying the position stop
   if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick))
      return(this.ModifySL(pos_ticket, value_sl));
//--- conditions for modification are not suitable
   return false;
  }

ここで、このメソッドは、仮パラメータを持たないものの、すべてのポジションを巡回するループが構成されているRun()メソッドから呼び出されるか、必要なチケットが渡されたプログラムから直接呼び出されます。

仮パラメータなしでRun()メソッドを改良してみましょう。ループ内のコードブロックを削除します

   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);
     }

代わりに新しいメソッドの呼び出しを追加します

//+------------------------------------------------------------------+
//| Launch simple trailing with StopLoss offset from the price       |
//+------------------------------------------------------------------+
bool CSimpleTrailing::Run(void)
  {
//--- if disabled, leave
   if(!this.m_active)
      return false;
//--- trailing variables
   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;
      
      res &=this.Run(pos_ticket);
     }
//--- at the end of the loop, return the result of modifying each position that matches the "symbol/magic" filter
   return res;
  }

指定された値を使用するトレーリングクラスでは、ポジションチケットを指定する新しい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);
   bool              Run(const ulong pos_ticket, 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){}
  };

クラス本体の外側で実装します。

//+------------------------------------------------------------------+
//| Launch trailing with the specified StopLoss offset from the price|
//+------------------------------------------------------------------+
bool CTrailingByValue::Run(const ulong pos_ticket,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(pos_ticket);
  }

ここでは、指定されたポジションチケットとともに、シンプルトレーリングクラスに追加されたRun()メソッドを呼び出します

トレーリングストップのファイルを取得した記事で、トレーリングクラスの詳細を確認してください。

前回の記事で作成した銘柄取引用クラスは、取引履歴のリストと、標準ライブラリのCTrade取引クラスを保持しています。このクラスにトレーリングクラスを組み込むために修正を加えるのは避けたいので、新しいクラスをこのクラスを基に作成し、そこにトレーリングクラスの処理を追加します。

ターミナルの\MQL5\Experts\TradingByHistoryDeals\フォルダに、CSymbolTradeExtクラスの新しいファイル「SymbolTradeExt.mqh」を作成してください。 このファイルには、トレーリングクラスのファイルとCSymbolTradeクラスのファイルをインクルードする必要があります新たに作成するクラスは、後者を継承します

//+------------------------------------------------------------------+
//|                                               SymbolTradeExt.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 "Trailings.mqh"
#include "SymbolTrade.mqh"

class CSymbolTradeExt : public CSymbolTrade
  {
  }

記事から取得したトレーリングストップクラスを使用すると、パラボリックSARや、すべての種類の標準移動平均に対応したトレーリングストップを利用することができます。このクラスには、指定した値に基づくトレーリング機能も備わっていますが、今回は使用しません。この機能を使用するには、独自のプログラム内でストップレベルを計算し、それをトレーリングクラスのRun()メソッドのパラメータとして渡す必要があるためです。たとえば、ATRインジケーターを計算し、その値をトレーリングクラスに渡すといった方法が考えられます。

トレーリングモードの列挙型を実装してみましょう。

//+------------------------------------------------------------------+
//|                                               SymbolTradeExt.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 "Trailings.mqh"
#include "SymbolTrade.mqh"

enum ENUM_TRAILING_MODE    // Enumeration of trailing modes
  {
   TRAILING_MODE_SIMPLE=2, // Simple trailing
   TRAILING_MODE_SAR,      // Trailing by Parabolic SAR
   TRAILING_MODE_AMA,      // Trailing by adjustable moving average
   TRAILING_MODE_DEMA,     // Trailing by double exponential moving average
   TRAILING_MODE_FRAMA,    // Trailing by fractal adaptive moving average 
   TRAILING_MODE_MA,       // Trailing by simple moving average
   TRAILING_MODE_TEMA,     // Trailing by triple exponential moving average
   TRAILING_MODE_VIDYA,    // Trailing by moving average with dynamic averaging period
  };

class CSymbolTradeExt : public CSymbolTrade
  {
  }

列挙定数値が0ではなく2で始まるのはなぜでしょうか。これを使用して新たに作成するEAには、次のようなテストモードを列挙した部分もあります。

enum ENUM_TESTING_MODE
  {
   TESTING_MODE_ORIGIN,    /* Original trading                          */ 
   TESTING_MODE_SLTP,      /* Specified StopLoss and TakeProfit values  */ 
  };

ここでは2つの定数、つまり、元の取引「0」と、指定したストップオーダー値を用いた取引「1」が定義されています。後ほど、この部分にトレーリングの列挙型に対応する新しい定数を追加します。そのため、トレーリングの列挙定数の値は2から始まっています。

クラスのprivateセクションでは、トレーリングストップクラスのオブジェクトへのポインタと、トレーリングで使用するインジケーターを計算するためのチャート時間軸を格納する変数を宣言します。publicセクションでは、トレーリングのパラメータを設定するメソッド、ポジションのトレーリングを開始するメソッド、そしてコンストラクタとデストラクタを宣言します。

class CSymbolTradeExt : public CSymbolTrade
  {
private:
   CSimpleTrailing  *m_trailing;    // Trailing class object
   ENUM_TIMEFRAMES   m_timeframe;   // Timeframe for calculating the indicator for trailing
public:
//--- Set trailing and its parameters
   bool              SetTrailing(const ENUM_TRAILING_MODE trailing_mode, const int data_index, const long magic, const int start, const int step, const int offset, const MqlParam &param[]);
//--- Start a trail of the position specified by the ticket
   void              Trailing(const ulong pos_ticket);

//--- Constructor/destructor
                     CSymbolTradeExt() : m_trailing(NULL), m_timeframe(::Period()) { this.SetSymbol(::Symbol()); }
                     CSymbolTradeExt(const string symbol, const ENUM_TIMEFRAMES timeframe);
                    ~CSymbolTradeExt();
  };

クラスコンストラクタでは、初期化リストにおいて銘柄を親クラスのコンストラクタに渡しています。仮引数で渡された値はインジケーター計算用の時間軸として設定され、トレーリングクラスオブジェクトへのポインタはNULLで初期化されます。

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSymbolTradeExt::CSymbolTradeExt(const string symbol, const ENUM_TIMEFRAMES timeframe) : CSymbolTrade(symbol)
  {
   this.m_trailing=NULL;
   this.m_timeframe=timeframe;
  }

作成されたトレーリングオブジェクトが存在する場合、デストラクタ内でそれを削除します。

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CSymbolTradeExt::~CSymbolTradeExt()
  {
//--- delete the created trailing object
   if(this.m_trailing!=NULL)
      delete this.m_trailing;
  }

異なる種類のトレーリングストップは、それぞれ異なるタイプのインジケータを基に動作し、異なる設定パラメータを持ちます。そのため、トレーリングストップのパラメータを設定するメソッドには、これらすべてのパラメータをMqlParam構造体を通じて渡します。各トレーリングでは独自のパラメータセットを持ち、構造体の各フィールドはそのインジケータ固有のパラメータ値を保持します。

//+------------------------------------------------------------------+
//| Set trailing parameters                                          |
//+------------------------------------------------------------------+
bool CSymbolTradeExt::SetTrailing(const ENUM_TRAILING_MODE trailing_mode, const int data_index, const long magic, const int start, const int step, const int offset, const MqlParam &param[])
  {
//--- Set trailing parameters (only necessary structure fields are used for each indicator type)
   int                ma_period  = (int)param[0].integer_value;
   int                ma_shift   = (int)param[1].integer_value;
   ENUM_APPLIED_PRICE ma_price   = (ENUM_APPLIED_PRICE)param[2].integer_value;
   ENUM_MA_METHOD     ma_method  = (ENUM_MA_METHOD)param[3].integer_value;
   int                fast_ema   = (int)param[4].integer_value;
   int                slow_ema   = (int)param[5].integer_value;
   int                period_cmo = (int)param[6].integer_value;
   double             sar_step   = param[0].double_value;
   double             sar_max    = param[1].double_value;
   
//--- depending on the trailing type, we create a trailing object
//--- if the value passed as the calculation period is less than the allowed value, then each indicator is assigned its own default value
   switch(trailing_mode)
     {
      case TRAILING_MODE_SIMPLE  :
        this.m_trailing=new CSimpleTrailing(this.Symbol(), magic, start, step, offset);
        break;
      case TRAILING_MODE_AMA     :
        this.m_trailing=new CTrailingByAMA(this.Symbol(), this.m_timeframe, magic,
                                           (ma_period<1 ?  9 : ma_period), (fast_ema<1  ?  2 : fast_ema), (slow_ema<1  ? 30 : slow_ema), ma_shift, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_DEMA    :
        this.m_trailing=new CTrailingByDEMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_FRAMA   :
        this.m_trailing=new CTrailingByFRAMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_MA      :
        this.m_trailing=new CTrailingByMA(this.Symbol(), this.m_timeframe, magic, ma_period, (ma_period==0 ? 10 : ma_period), ma_method, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_TEMA    :
        this.m_trailing=new CTrailingByTEMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_VIDYA   :
        this.m_trailing=new CTrailingByVIDYA(this.Symbol(), this.m_timeframe, magic, (period_cmo<1 ? 9 : period_cmo), (ma_period==0 ? 12 : ma_period), ma_shift, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_SAR     :
        this.m_trailing=new CTrailingBySAR(this.Symbol(), this.m_timeframe, magic, (sar_step<0.0001 ? 0.02 : sar_step), (sar_max<0.02 ? 0.2 : sar_max), start, step, offset);
        break;
      default :
        break;
     }
//--- something went wrong - return 'false'
   if(this.m_trailing==NULL)
      return false;
      
//--- all is well - make the trail active and return 'true'
   this.m_trailing.SetActive(true);
   return true;
  }

異なるトレーリングタイプでこのメソッドを呼び出す際には、MqlParam構造体を正しく設定することが重要です。これについては、EAをトレーリングに対応させる際に確認します。

以下は、チケットで指定されたポジションのトレーリングを開始するメソッドです。

//+------------------------------------------------------------------+
//| Start trailing of the position specified by the ticket           |
//+------------------------------------------------------------------+
void CSymbolTradeExt::Trailing(const ulong pos_ticket)
  {
    if(this.m_trailing!=NULL)
      this.m_trailing.Run(pos_ticket);
  }

EAの保有ポジションをループ処理する際(ポジションの銘柄に応じて)、リストから銘柄取引オブジェクトを取得します。そのオブジェクトから、チケットを引数として渡すことで、ポジションのストップをトレーリングするメソッドを呼び出します。トレーリングオブジェクトが存在する場合、そのRun()メソッドが指定されたポジションチケットで実行されます。


各種トレーリングストップのテスト

テストを実行するために、前回の記事のTradingByHistoryDeals_SLTP.mq5 EAを使用し、同じフォルダ\MQL5\Experts\TradingByHistoryDeals\にTradingByHistoryDeals_Ext.mq5という名前で保存します。

CSymbolTradeクラスファイルのインクルードは、CSymbolTradeExtクラスファイルのインクルードに置き換えます。また、トレーリングクラスのファイルをインクルードし、テストモードの列挙に必要なトレーリングの選択肢を追加して定数リストを拡張します。

//+------------------------------------------------------------------+
//|                                    TradingByHistoryDeals_Ext.mq5 |
//|                                  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 "SymbolTradeExt.mqh"
#include "Trailings.mqh"

enum ENUM_TESTING_MODE
  {
   TESTING_MODE_ORIGIN,       /* Original trading                          */ 
   TESTING_MODE_SLTP,         /* Specified StopLoss and TakeProfit values  */ 
   TESTING_MODE_TRAIL_SIMPLE, /* Simple Trailing                           */ 
   TESTING_MODE_TRAIL_SAR,    /* Trailing by Parabolic SAR indicator       */ 
   TESTING_MODE_TRAIL_AMA,    /* Trailing by AMA indicator                 */ 
   TESTING_MODE_TRAIL_DEMA,   /* Trailing by DEMA indicator                */ 
   TESTING_MODE_TRAIL_FRAMA,  /* Trailing by FRAMA indicator               */ 
   TESTING_MODE_TRAIL_MA,     /* Trailing by MA indicator                  */ 
   TESTING_MODE_TRAIL_TEMA,   /* Trailing by TEMA indicator                */ 
   TESTING_MODE_TRAIL_VIDYA,  /* Trailing by VIDYA indicator               */ 
  };

//+------------------------------------------------------------------+
//| Expert                                                           |
//+------------------------------------------------------------------+

EAの入力パラメータに、トレーリングパラメータを選択するための変数を追加します

//+------------------------------------------------------------------+
//| Expert                                                           |
//+------------------------------------------------------------------+
//--- input parameters
input    group                " - Strategy parameters - "
input    string               InpTestedSymbol   =  "";                  /* The symbol being tested in the tester        */ 
input    long                 InpTestedMagic    =  -1;                  /* The magic number being tested in the tester  */ 
sinput   bool                 InpShowDataInLog  =  false;               /* Show collected data in the log               */ 

input    group                " - Stops parameters - "
input    ENUM_TESTING_MODE    InpTestingMode    =  TESTING_MODE_ORIGIN; /* Testing Mode                                 */ 
input    int                  InpStopLoss       =  300;                 /* StopLoss in points                           */ 
input    int                  InpTakeProfit     =  500;                 /* TakeProfit in points                         */ 

input    group                " - Trailing Parameters -"
input    bool                 InpSetStopLoss    =  true;                /* Set Initial StopLoss                         */ 
input    bool                 InpSetTakeProfit  =  true;                /* Set Initial TakeProfit                       */ 
input    int                  InpTrailingStart  =  150;                 /* Trailing start                               */ // Profit in points to start trailing
input    int                  InpTrailingStep   =  50;                  /* Trailing step in points                      */ 
input    int                  InpTrailingOffset =  0;                   /* Trailing offset in points                    */ 

input    group                " - Indicator Parameters -"
input    ENUM_TIMEFRAMES      InpIndTimeframe   =  PERIOD_CURRENT;      /* Indicator's timeframe                        */ // Timeframe of the indicator used in trailing calculation
input    int                  InpMAPeriod       =  0;                   /* MA Period                                    */ 
input    int                  InpMAShift        =  0;                   /* MA Shift                                     */ 
input    int                  InpFastEMAPeriod  =  2;                   /* AMA Fast EMA Period                          */ 
input    int                  InpSlowEMAPeriod  =  30;                  /* AMA Slow EMA Period                          */ 
input    int                  InpCMOPeriod      =  9;                   /* VIDYA CMO Period                             */ 
input    double               InpSARStep        =  0.02;                /* Parabolic SAR Step                           */ 
input    double               InpSARMax         =  0.2;                 /* Parabolic SAR Max                            */ 
input    ENUM_APPLIED_PRICE   InpAppliedPrice   =  PRICE_CLOSE;         /* MA Applied Price                             */ 
input    ENUM_MA_METHOD       InpMAMethod       =  MODE_SMA;            /* MA Smoothing Method                          */ 
input    int                  InpDataIndex      =  1;                   /* Indicator data index                         */ // Bar of data received frrom the indicator


ここでInpMAPeriodはデフォルトでゼロに設定されています。その理由は、各種類の移動平均にはそれぞれ独自のデフォルト期間値があるためです。このパラメータがゼロに設定されている場合、インジケーターに設定されたデフォルト値がトレーリングクラスに渡されます。トレーリングで使用されるインジケーターに単純なデフォルト値が必要な場合でも、すべてのデータが正しく処理されます。

コード全体で、すべてのCSymbolTradeをCSymbolTradeExtに置き換えます。これで、新しい銘柄取引オブジェクトクラスCSymbolTradeExtが前回の記事で実装したCSymbolTradeクラスを継承して作成されました。このクラス内では、トレーリングクラスオブジェクトが宣言されています。したがって、以前のクラス型を新しいものに置き換えました。実際のところ、これはすべての箇所でおこなう必要はなく、トレーリングが必要な箇所だけで十分です。ここではクラス継承の詳細には踏み込みませんが、単純に古いクラスを新しいクラスに置き換えます。

トレーリングは、各ポジションのチケットごとに呼び出されます。ポジションのトレーリングにアクセスするには、そのポジションが開かれている銘柄の取引オブジェクトをリストから取得する必要があります。そのため、銘柄名で取引オブジェクトへのポインタをリストから返す関数を実装します。

//+------------------------------------------------------------------+
//| Return the pointer to the symbol trading object by name          |
//+------------------------------------------------------------------+
CSymbolTrade *GetSymbolTrade(const string symbol, CArrayObj *list)
  {
   SymbTradeTmp.SetSymbol(symbol);
   list.Sort();
   int index=list.Search(&SymbTradeTmp);
   return list.At(index);
  }

この関数は、取引オブジェクトを返すべき銘柄名と、銘柄の取引オブジェクトへのポインタを格納したリストへのポインタを受け取ります。関数に渡された銘柄名を一時的な取引オブジェクトに設定し、その銘柄名を持つオブジェクトのインデックスをリスト内で検索します。結果として、目的のオブジェクトへのポインタがインデックスによってリストから返されます。そのようなオブジェクトがリストに存在しない場合、インデックスは-1となり、リストからNULLが返されます。

使用する銘柄の配列を作成する際に、銘柄取引オブジェクトが生成され、入力で指定されたトレーリングストップがそれらに初期化される必要があります。次に、使用する銘柄の配列を作成する関数を改良します。

//+------------------------------------------------------------------+
//| Creates an array of used symbols                                 |
//+------------------------------------------------------------------+
bool CreateListSymbolTrades(SDeal &array_deals[], CArrayObj *list_symbols)
  {
   bool res=true;                      // result
   MqlParam param[7]={};               // trailing parameters
   int total=(int)array_deals.Size();  // total number of deals in the array
   
//--- if the deal array is empty, return 'false'
   if(total==0)
     {
      PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__);
      return false;
     }
   
//--- in a loop through the deal array
   CSymbolTradeExt *SymbolTrade=NULL;
   for(int i=0; i<total; i++)
     {
      //--- get the next deal and, if it is neither buy nor sell, move on to the next one
      SDeal deal_str=array_deals[i];
      if(deal_str.type!=DEAL_TYPE_BUY && deal_str.type!=DEAL_TYPE_SELL)
         continue;
      
      //--- find a trading object in the list whose symbol is equal to the deal symbol
      string symbol=deal_str.Symbol();
      SymbTradeTmp.SetSymbol(symbol);
      list_symbols.Sort();
      int index=list_symbols.Search(&SymbTradeTmp);
      
      //--- if the index of the desired object in the list is -1, there is no such object in the list
      if(index==WRONG_VALUE)
        {
         //--- we create a new trading symbol object and, if creation fails,
         //--- add 'false' to the result and move on to the next deal
         SymbolTrade=new CSymbolTradeExt(symbol, InpIndTimeframe);
         if(SymbolTrade==NULL)
           {
            res &=false;
            continue;
           }
         //--- if failed to add a symbol trading object to the list,
         //--- delete the newly created object, add 'false' to the result
         //--- and we move on to the next deal
         if(!list_symbols.Add(SymbolTrade))
           {
            delete SymbolTrade;
            res &=false;
            continue;
           }
         //--- initialize trailing specified in the settings in the trading object
         ENUM_TRAILING_MODE mode=(ENUM_TRAILING_MODE)InpTestingMode;
         SetTrailingParams(mode, param);
         SymbolTrade.SetTrailing(mode, InpDataIndex, InpTestedMagic, InpTrailingStart, InpTrailingStep, InpTrailingOffset, param);
         
        }
      //--- otherwise, if the trading object already exists in the list, we get it by index
      else
        {
         SymbolTrade=list_symbols.At(index);
         if(SymbolTrade==NULL)
            continue;
        }
         
      //--- if the current deal is not yet in the list of deals of the symbol trading object
      if(SymbolTrade.GetDealByTime(deal_str.time)==NULL)
        {
         //--- create a deal object according to its sample structure
         CDeal *deal=CreateDeal(deal_str);
         if(deal==NULL)
           {
            res &=false;
            continue;
           }
         //--- add the result of adding the deal object to the list of deals of a symbol trading object to the result value
         res &=SymbolTrade.AddDeal(deal);
        }
     }
//--- return the final result of creating trading objects and adding deals to their lists
   return res;
  }

ここでは、インジケーター入力パラメータ構造体の宣言と、取引オブジェクト内でトレーリングを初期化する小さなコードブロックを追加しました。

指定されたトレーリングのパラメータを設定する専用関数を実装します。

//+------------------------------------------------------------------+
//| Set the trailing parameters according to its selected type       |
//+------------------------------------------------------------------+
void SetTrailingParams(const ENUM_TRAILING_MODE mode, MqlParam &param[])
  {
//--- reset all parameters
   ZeroMemory(param);

//--- depending on the selected trailing type, we set the indicator parameters
   switch(mode)
     {
      case TRAILING_MODE_SAR     :
        param[0].type=TYPE_DOUBLE;
        param[0].double_value=InpSARStep;
        
        param[1].type=TYPE_DOUBLE;
        param[1].double_value=InpSARMax;
        break;
      
      case TRAILING_MODE_AMA     :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;

        param[4].type=TYPE_INT;
        param[4].integer_value=InpFastEMAPeriod;

        param[5].type=TYPE_INT;
        param[5].integer_value=InpSlowEMAPeriod;
        break;
      
      case TRAILING_MODE_DEMA    :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;
        break;
      
      case TRAILING_MODE_FRAMA   :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;
        break;
      
      case TRAILING_MODE_MA      :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;

        param[3].type=TYPE_INT;
        param[3].integer_value=InpMAMethod;
        break;
      
      case TRAILING_MODE_TEMA    :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;
        break;
      
      case TRAILING_MODE_VIDYA   :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;

        param[6].type=TYPE_INT;
        param[6].integer_value=InpCMOPeriod;
        break;
         
      case TRAILING_MODE_SIMPLE  :
        break;
      
      default:
        break;
     }
  }

ここでは、各トレーリングタイプに応じて、トレーリングで使用されるインジケーターの値をEA入力で指定されたものに設定しています。 構造体のフィールドに代入された値は、新しい銘柄取引オブジェクトクラス「CSymbolTradeExt」内で検証され、適切に調整されます。

次に、取引履歴に基づいた取引関数を改良します。ここでは、使用しているテストタイプを明確に区別する必要があります。
元の取引をテストする場合、ストップオーダーを設定する必要はありません。
すべてのポジションは、履歴上の決済済み取引に基づいてクローズされます。

異なるストップ注文サイズでのテスト取引をおこなう場合、正しいサイズを取得し、開かれるポジションに対して設定する必要があります。。トレーリングストップをテストする場合、ストップを設定することは必須ではありませんが、大きなドローダウンや長期保有を避けるため、初期ストップを設定することを推奨します。 その後、トレーリングストップが指定されたロジックに従ってそれらを移動します。EAの設定には、初期ストップ注文を設定するかどうかを決定するパラメータがあります。

input    bool                 InpSetStopLoss    =  true;                /* Set Initial StopLoss                         */ 
input    bool                 InpSetTakeProfit  =  true;                /* Set Initial TakeProfit                       */ 

これらを使用する際は、正しいストップサイズを取得し、開かれるポジションに設定する必要があります

//+------------------------------------------------------------------+
//| Trading by history                                               |
//+------------------------------------------------------------------+
void TradeByHistory(const string symbol="", const long magic=-1)
  {
   datetime time=0;
   int total=ExtListSymbols.Total();   // number of trading objects in the list
   
//--- in a loop by all symbol trading objects
   for(int i=0; i<total; i++)
     {
      //--- get another trading object
      CSymbolTradeExt *obj=ExtListSymbols.At(i);
      if(obj==NULL)
         continue;
      
      //--- get the current deal pointed to by the deal list index
      CDeal *deal=obj.GetDealCurrent();
      if(deal==NULL)
         continue;
      
      //--- sort the deal by magic number and symbol
      if((magic>-1 && deal.Magic()!=magic) || (symbol!="" && deal.Symbol()!=symbol))
         continue;
      
      //--- sort the deal by type (only buy/sell deals)
      ENUM_DEAL_TYPE type=deal.TypeDeal();
      if(type!=DEAL_TYPE_BUY && type!=DEAL_TYPE_SELL)
         continue;
      
      //--- if this is a deal already handled in the tester, move on to the next one
      if(deal.TicketTester()>0)
         continue;
      
      //--- if the deal time has not yet arrived, move to the next trading object of the next symbol
      if(!obj.CheckTime(deal.Time()))
         continue;

      //--- in case of a market entry deal
      ENUM_DEAL_ENTRY entry=deal.Entry();
      if(entry==DEAL_ENTRY_IN)
        {
         //--- set the sizes of stop orders depending on the stop setting method
         //--- stop orders are not used initially (for original trading)  
         ENUM_ORDER_TYPE order_type=(deal.TypeDeal()==DEAL_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
         double sl=0;
         double tp=0;
         //--- in case of the mode for setting the specified stop order values 
         if(InpTestingMode==TESTING_MODE_SLTP)
           {
            //--- get correct values for StopLoss and TakeProfit
            sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss);
            tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit);
           }
         //--- otherwise, if testing with trailing stops
         else
           {
            if(InpTestingMode!=TESTING_MODE_ORIGIN)
              {
               //--- if allowed in the settings, we set correct stop orders for the positions being opened
               if(InpSetStopLoss)
                  sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss);
               if(InpSetTakeProfit)
                  tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit);
              }
           }
         
         //--- open a position by deal type
         ulong ticket=(type==DEAL_TYPE_BUY  ? obj.Buy(deal.Volume(), deal.Magic(), sl, tp, deal.Comment()) : 
                       type==DEAL_TYPE_SELL ? obj.Sell(deal.Volume(),deal.Magic(), sl, tp, deal.Comment()) : 0);
         
         //--- if a position is opened (we received its ticket)
         if(ticket>0)
           {
            //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object 
            obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
            deal.SetTicketTester(ticket);
            //--- get the position ID in the tester and write it to the properties of the deal object
            long pos_id_tester=0;
            if(HistoryDealSelect(ticket))
              {
               pos_id_tester=HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
               deal.SetPosIDTester(pos_id_tester);
              }
           }
        }
      
      //--- in case of a market exit deal
      if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT || entry==DEAL_ENTRY_OUT_BY)
        {
         //--- get a deal a newly opened position is based on
         CDeal *deal_in=obj.GetDealInByPosID(deal.PositionID());
         if(deal_in==NULL)
            continue;

         //--- get the position ticket in the tester from the properties of the opening deal
         //--- if the ticket is zero, then most likely the position in the tester is already closed
         ulong ticket_tester=deal_in.TicketTester();
         if(ticket_tester==0)
           {
            PrintFormat("Could not get position ticket, apparently position #%I64d (#%I64d) is already closed \n", deal.PositionID(), deal_in.PosIDTester());
            obj.SetNextDealIndex();
            continue;
           }
         //--- if we reproduce the original trading history in the tester,
         if(InpTestingMode==TESTING_MODE_ORIGIN)
           {
            //--- if the position is closed by ticket
            if(obj.ClosePos(ticket_tester))
              {
               //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object 
               obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
               deal.SetTicketTester(ticket_tester);
              }
           }
         //--- otherwise, in the tester we work with stop orders placed according to different algorithms, and closing deals are skipped;
         //--- accordingly, for the closing deal, we simply increase the number of deals handled by the tester and
         //--- write the deal ticket in the tester to the properties of the deal object
         else
           {
            obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
            deal.SetTicketTester(ticket_tester);
           }
        }
      //--- if a ticket is now set in the deal object, then the deal has been successfully handled -
      //--- set the deal index in the list to the next deal
      if(deal.TicketTester()>0)
        {
         obj.SetNextDealIndex();
        }
     }
  }

次に、ポジションをトレーリングする関数を実装します。

//+------------------------------------------------------------------+
//| Trail positions                                                  |
//+------------------------------------------------------------------+
void Trailing(void)
  {
//--- variables for getting position properties
   long   magic=-1;
   string symbol="";
   
//--- in a loop through all positions
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next position
      ulong ticket=PositionGetTicket(i);
      if(ticket==0)
         continue;

      //--- get the magic number and position symbol
      ResetLastError();
      if(!PositionGetInteger(POSITION_MAGIC, magic))
        {
         Print("PositionGetInteger() failed. Error ", GetLastError());
         continue;
        }
      if(!PositionGetString(POSITION_SYMBOL, symbol))
        {
         Print("PositionGetString() failed. Error ", GetLastError());
         continue;
        }
      
      //--- if the position does not meet the specified conditions of the magic number and symbol, we move to the next one
      if((InpTestedMagic>-1 && magic!=InpTestedMagic) || (InpTestedSymbol!="" && symbol!=InpTestedSymbol))
         continue;
      
      //--- get a trading object by a symbol name and call its method for trailing a position by ticket
      CSymbolTradeExt *obj=GetSymbolTrade(symbol, &ExtListSymbols);
      if(obj!=NULL)
         obj.Trailing(ticket);
     }
  }

ここではすべてがシンプルです。ループで各ポジションを選択し、そのマジックナンバーと銘柄がEA設定で指定されたものと一致するかを確認します。該当するポジションであれば、そのポジションの銘柄に対応する取引オブジェクトを取得し、ポジションチケットを指定してトレーリングを呼び出します。

EAのOnTick()ハンドラ内でこの関数を呼び出す処理を実装します。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- work only in the strategy tester
   if(!MQLInfoInteger(MQL_TESTER))
      return;
      
//--- Trail open positions
   Trailing();
   
//---  Handle the list of deals from the file
   TradeByHistory(InpTestedSymbol, InpTestedMagic);
  }

すべての準備が整いました。EAは完成です。完全なEAコードは、記事に添付されているファイルで確認できます。

次に、異なる種類のトレーリングストップをテストし、元の取引と、異なるアルゴリズムを使用したトレーリングストップ取引の結果を比較してみましょう。

EAをチャート上で起動すると、取引履歴が収集され、ファイルに書き込まれ、テスターの設定(初期テスト日、初期証拠金、レバレッジ)に関するアラートが表示されます。

操作ログには、取引に使用された銘柄と、それぞれの銘柄で実行された取引の数が表示されます。

Alert: Now you can run testing
Interval: 2024.09.13 - current date
Initial deposit: 3000.00, leverage 1:500
Symbols used in trading:
  1. AUDUSD trade object. Total deals: 222
  2. EURJPY trade object. Total deals: 120
  3. EURUSD trade object. Total deals: 526
  4. GBPUSD trade object. Total deals: 352
  5. NZDUSD trade object. Total deals: 182
  6. USDCAD trade object. Total deals: 22
  7. USDCHF trade object. Total deals: 250
  8. USDJPY trade object. Total deals: 150
  9. XAUUSD trade object. Total deals: 118

では、アラートに表示され、銘柄にも重複して記録されている推奨テスター設定を使用して、EAをテスターで実行してみましょう。

元の取引

元の取引では、USD 658の損失が発生しました。

次に、さまざまなトレーリングストップを試してみます。初期ストップを100ポイントに設定し、その他のパラメータを変更せずに、
各トレーリング注文をテスターで順番に実行します。これにより、取引の結果がどのように変化するかを確認します。

シンプルトレーリングの場合

損失はUSD 746.1となり、元の取引よりもさらに悪化しました。

インジケーターを使用したトレーリングを確認します。

パラボリックSARによるトレーリング

利益はUSD 541.8でした。

異なる種類の移動平均を使用したトレーリングストップを見てみましょう。

AMAによるトレーリング

利益はUSD 806.5でした。

DEMAによるトレーリング

利益はUSD 1397.1でした。

FRAMAによるトレーリング

利益はUSD 1291.6でした。

MAによるトレーリング

利益はUSD 563.1でした。

TEMAによるトレーリング

利益はUSD 1355.1でした。

VIDYAによるトレーリング

利益はUSD 283.3でした。

最終的な結果を、元の取引との比較表にまとめると、以下のようになります。

#
トレーリングの種類
ストップロス幅
テイクプロフィット幅
 総利益
1
元の取引
100
500
 - 658.0
2
シンプルトレーリングストップ
初期100、価格から100ポイントで追随
0
 - 746.1
3
パラボリックSARによるトレーリングストップ
初期100、インジケーター値に基づく
0
 + 541.8
4
VIDYAによるトレーリングストップ
初期100、インジケーター値に基づく
0
 -283.3
5
MAによるトレーリングストップ
初期100、インジケーター値に基づく
0
 + 563.1
6
АМАによるトレーリングストップ
初期100、インジケーター値に基づく
0
 + 806.5
7
FRAMAによるトレーリングストップ
初期100、インジケーター値に基づく
0
 + 1291.6
8
TEMAによるトレーリングストップ
初期100、インジケーター値に基づく
0
 + 1355.1
9
DEMAによるトレーリングストップ
初期100、インジケーター値に基づく
0
 + 1397.1

結果として、元の取引が損失で終わった場合、クライアントターミナルの通常のシンプルトレーリングと同等の方法では、損失がさらに拡大することが分かりました。一方で、反転とストップレベルを示すインジケーターとして知られるパラボリックSAR (Stop And Reverse)に基づいたトレーリングでは、インジケーターラインに沿ってストップロスレベルを移動させたことで、USD 540の利益が得られました。

次に、異なるタイプの移動平均の値に基づいてストップを移動させた場合の結果を見てみましょう。VIDYAを使用したトレーリングではUSD 280の損失となりましたが、その他のすべての方法では利益を示しました。特に、単純移動平均(MA)に沿ったトレーリングでは、パラボリックSARよりも大きな利益を得ることができました。最も優れた結果を示したのは、二重指数移動平均(DEMA)によるトレーリングで、最終的な利益はUSD 1397となりました。


結論

ストラテジーテスターで取引を検証し、自分の取引スタイルに最も適したトレーリングストップを選択できるEAを開発しました。なお、トレーリングテストで使用したすべてのインジケーターのパラメータは標準値であり、トレーリングの設定はランダムに選ばれたものです。

最も正しいアプローチは、各通貨ペアごとに個別のテストを実施し、その銘柄の値動きやボラティリティの特性を考慮して適切なトレーリング設定を選択することです。しかし、このEAには各取引銘柄ごとに個別パラメーターを設定する機能は備わっていません。とはいえ、今回の目的は、ストラテジーテスターを使って取引を検証し、分析、改善できる可能性を示すことにあり、その目的は十分に達成されました。つまり、このEAはさらに発展させることで、各銘柄に対して独自のパラメータを設定できるようにすることも可能です。これはそれほど難しいことではありません。

この記事には、すべてのクラスファイルおよびEAが添付されています。また、アーカイブファイルも含まれており、解凍するとすぐに必要なターミナルフォルダ内でテスト用のファイルを使用できます。

以下は本稿で使用されているプログラムです。

#
名前
 種類  詳細
1
Trailings.mqh
クラスライブラリ
トレーリングクラスライブラリ
2
SymbolTrade.mqh
クラスライブラリ
取引構造および銘柄取引クラス
3
SymbolTradeExt.mqh
クラスライブラリ
銘柄およびトレーリング取引クラス
4
TradingByHistoryDeals_Ext.mq5
EA
ストラテジーテスターおよび実際の取引における取引のストップロス、テイクプロフィット、トレーリングストップを表示・変更するEA
5
MQL5.zip
ZIPアーカイブ
上記のファイルのアーカイブは、クライアントターミナルのMQL5ディレクトリに解凍できます。

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

添付されたファイル |
Trailings.mqh (101.41 KB)
SymbolTrade.mqh (53.86 KB)
SymbolTradeExt.mqh (12.95 KB)
MQL5.zip (26.49 KB)
最後のコメント | ディスカッションに移動 (4)
Roman Shiredchenko
Roman Shiredchenko | 30 1月 2025 において 15:38
記事をどうもありがとう。一気に読めて、すべてがクリアになりました。
コードフラグメントとf -- iiを入札ロボットに使ってみます。単純なトロールも。
あるロボットには1つ、別のロボットには別のトロールを。
Yevgeniy Koshtenko
Yevgeniy Koshtenko | 30 1月 2025 において 23:21
TOP記事
den2008224
den2008224 | 31 1月 2025 において 05:16

Трал по VIDYA:

利益 - 283.3ドル

エラー:損失-283.3ドル。

Alexey Viktorov
Alexey Viktorov | 31 1月 2025 において 06:46
den2008224 #:

エラー:損失-283.3ドル。

記事中、利益のマイナス値が書かれている。

しかし、マイナスの後のスペースが誤って挿入された。
強化学習と弱者淘汰を組み合わせた進化型取引アルゴリズム(ETARE) 強化学習と弱者淘汰を組み合わせた進化型取引アルゴリズム(ETARE)
この記事では、進化アルゴリズムと深層強化学習を組み合わせた、外国為替取引のための革新的な取引アルゴリズムを紹介します。このアルゴリズムは、非効率な個体を絶滅させるメカニズムを使用して取引戦略を最適化します。
初級から中級まで:テンプレートとtypename(IV) 初級から中級まで:テンプレートとtypename(IV)
本記事では、前回の記事の最後で提示した問題の解決方法について詳しく解説します。そのために、データunionのテンプレートを作成できるタイプのテンプレートを設計しようという試みがおこなわれました。
時間進化移動アルゴリズム(TETA) 時間進化移動アルゴリズム(TETA)
これは私自身のアルゴリズムです。本記事では、並行宇宙や時間の流れの概念に着想を得た「時間進化移動アルゴリズム(TETA: Time Evolution Travel Algorithm)」を紹介します。本アルゴリズムの基本的な考え方は、従来の意味でのタイムトラベルは不可能であるものの、異なる現実に至る一連の出来事の順序を選択することができるという点にあります。
Pythonの価格変動離散化手法 Pythonの価格変動離散化手法
Python + MQL5を使用した価格離散化手法を見ていきます。本記事では、バー生成に関する幅広い手法を実装したPythonライブラリの開発経験についご紹介します。クラシックなボリュームバーやレンジバーから、よりエキゾチックな練行足やカギ足といった手法までを網羅します。スリーラインブレイクローソク足やレンジバーの統計分析をおこないながら、価格を離散的に表現する新たな方法を探っていきます。