English Русский Español Deutsch 日本語 Português
preview
如何开发各种类型的追踪止损并将其加入到EA中

如何开发各种类型的追踪止损并将其加入到EA中

MetaTrader 5示例 | 25 十一月 2024, 13:21
509 0
Artyom Trishkin
Artyom Trishkin

内容


引言

延续 之前文章中提及的追踪止损(Trailing Stop)的话题,这里我们将讨论追踪类,以便于创建各种追踪止损算法。基于所创建的类,我们可以创建任何用于改变止损位置的算法:根据当前价格的止损移动、根据指标、根据指定的止损价格等等。阅读完本文后,我们将能够创建并将任何移动止损算法添加到任意EA上。同时,追踪止损的嵌入和使用将变得方便且清晰。

让我们简要地回顾一下追踪止损操作的算法。让我们定义每个追踪止损可采用以下三个运行条件:

  • 触发追踪起始点 — 达到该盈利点数时触发追踪止损;
  • 追踪步长 — 价格朝盈利方向移动多少点后,下一个止损位置应相应移动;
  • 追踪距离 — 当前价格与止损位置之间的距离。

这三个参数可以应用于任何追踪止损策略。在追踪设置中,这些参数可能全部需要。但如果某个参数不需要或已被算法中的某个值替代,那么也可以只需要部分参数。若将止损位置设置在某个指标值处,那么它可以代替“追踪距离”参数。在这种情况下,如果使用该参数,则止损值不会直接使用指标指示的价格,而是根据距离指示价格的点数来设置。

总体而言,上述三个参数在各类追踪止损策略中使用最为广泛,我们将在创建追踪类时予以考虑。

在将止损位置移动到所需价格时,应执行以下检查:

  • 止损价格不应比当前价格更接近交易品种的止损级别值(SYMBOL_TRADE_STOPS_LEVEL — 设置止损订单时,从当前收盘价的最小点数偏移);
  • 止损价格不应与已设置的价格相同,对于多头仓位应高于当前止损价格,对于空头仓位则应低于当前止损价格;
  • 如果追踪算法使用上述参数,则还需要检查这些参数设定的条件。

这些是基本检查。任何追踪算法的工作原理都相同:输入设置止损所需的价格,检查所有必要的限制条件,如果所有检查都通过,则将止损位置移动到指定位置。

以下是简单追踪止损的工作原理:

  1. 在“触发追踪起始点”参数中,指示达到多少盈利点数时开始追踪,如果不使用该参数,则设为零;
  2. “追踪步长”参数指定盈利多少点后,应跟随价格上拉止损级别,如果不使用该参数,则设为零;
  3. “追踪距离”参数设置当前价格与止损位置之间的距离,如果不使用该参数,则设为零;
  4. 此外,我们可以设置要追踪的仓位的品种和magic编号,或分别设为NULL和-1,以追踪所有品种的任何magic编号的仓位。

当持仓盈利达到指定点数时,便开始使用跟踪止损,并将持仓止损设置在距离当前价格指定的距离处。接下来,当价格朝着持仓盈利方向移动了指定点数(即跟踪步长)后,持仓止损会再次根据价格移动,以保持与价格之间的指定距离。这一过程会一直持续到价格与持仓方向相反变动为止。在这种情况下,止损将保持在已设定的位置。一旦价格达到该止损位置,持仓将因“止损”而获利平仓。因此,追踪止损允许我们在价格反转时通过平仓来保留利润,同时允许价格在指定止损偏移范围内“游走”,如果价格朝着持仓方向移动,则逐渐将止损点跟随价格移动。

我们还可以使用一个从指标中获取的价格来代替“追踪距离”参数。在这种情况下,我们将得到一个基于指标的追踪算法。我们可以传递前一个K线图的某个值(例如,最高价、最低价)——然后我们将得到基于K线图价格的追踪,等等。但是基本的追踪止损算法保持不变。

因此,为了创建追踪止损算法,首先需要创建一个简单的跟踪算法,该算法允许我们向其传递止损头寸所需的价格,并在执行所有必要检查后,将头寸的止损移动到该位置。

在之前的文章中,我们已经探讨了所含的细节,其中包括简单的追踪以及基于各种指标的追踪。这种方法允许我们将追踪止损嵌入到EA中,以追踪当前交易品种的头寸。但它也有其局限性:我们需要在EA中创建一个用于追踪的指标,并将每个指标的句柄发送给追踪函数。追踪函数本身只能处理当前交易品种的头寸。

如果我们使用追踪类,我们可以创建一个追踪类的多个实例,每个实例具有不同的设置,然后这样创建的所有追踪止损都可以根据程序员指定的特定算法在一个EA中同时工作。换句话说,我们创建了一个自定义追踪,用几行代码设置一些列参数后,它便能根据自身的算法进行工作。每个追踪算法都可以在EA中在满足特定条件时启动,从而为追踪止损头寸创建复杂算法。

即使我们为不同的交易品种创建了两个相同的追踪止损,那么对于每个交易品种,都会创建一个单独的追踪,它将根据创建时指定的交易品种的数据运行。这大大简化了追踪算法在程序中的使用:我们只需要用所需的参数创建一个追踪功能块,并在EA中调用它。公平地说,应该指出,在某些算法中,这种方法可能不是最优的,因为每种类型的追踪都会在终端中启动对现有头寸的检索。如果我们制作一个真正通用的算法,该算法为每个追踪止损函数使用相同的终端头寸列表,那么可能会发现其设计和开发的成本对比其需求来说远远不值当。此外,这个话题超出了本文的范围。

因此,首先我们需要编写基础部分:一个简单的跟踪止损类,该类将头寸的止损移动到指定价格,同时执行所有必要的检查。

在之前的文章中,这个简单的基本追踪函数被称为TrailingStopByValue()。在这里,我们将它称为SimpleTrailing。 它将在自定义程序中被充分使用。

与之前的文章一样,我们将所有跟踪类放在一个文件中。然后只需将这个文件连接到EA中。通常,包含文件存储在\MQL5\Include\文件夹或其子文件夹中。


基本追踪止损类

在MetaTrader 5(MQL5)终端的\MQL5\Include\文件夹中,创建一个名为Trailings\的新子文件夹,并在该子文件夹中创建一个名为Trailings.mqh的新类文件。



类名应为CSimpleTrailing,已创建的文件名为Trailings.mqh,同时该类应继承自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并不是必要的。但是,如果我们想从这些类中构建出功能完备的集合,并利用标准库的所有功能,那么这些类就应该从CObject基类派生出来:

CObject类是MQL5标准库构建的基础类,并为它的所有派生类提供了成为链表元素的能力。
此外,还为在派生类中进一步实现定义了一系列虚拟方法。

接下来,声明三个类的私有方法:

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: 
   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:

在这里声明的所有变量中,只有“点差乘数(spread multiplier)”需要解释。交易品种的StopLevel值是当前价格到某个距离,在该距离内不能设置止损。如果服务器上StopLevel的值被设置为零,这并不意味着没有StopLevel。相反,这表示止损级别是浮动的,并且通常等于两个点的点差,或者有时是三个点。这都取决于服务器的设置。因此,在这里我们可以自行设置这个乘数。如果StopLevel值使用的是双倍点差,那么m_spread_mlt变量的值就设置为2(默认值)。如果是三倍点差,那么就设置为3,以此类推。

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


这个类有两个构造函数:

  • 第一个(默认)构造函数使用当前交易品种,并将magic编号设置为-1(表示当前交易品种的所有持仓,无一例外),同时将追踪参数设置为零
  • 第二个构造函数将是参数化的,其形式参数会将交易品种名称、持仓magic编号以及三个追踪参数(起始值、步长和偏移量)传递给构造函数。

让我们来考虑参数化构造函数的实现:

//+------------------------------------------------------------------+
//| 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;
  }

如果传递给方法的止损(StopLoss)值满足所有过滤器的所有标准,该方法返回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() 方法获取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);
  }

由于止损价位不是一个固定值,而是可以动态变化的,因此每次我们调用该方法时,都会从交易品种属性中请求所需的数值。如果某个交易品种的止损价位被设置为零,那么我们使用交易品种点差乘以2的数值。这是默认值。

在之前的文章中,我们已经将上述讨论的所有三个方法作为独立的函数实现了。现在,这些方法被隐藏在类的私有部分,并在没有外部访问的情况下执行其功能。

主要的追踪方法是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;
  }

该方法会遍历终端活跃持仓列表中的持仓,按照交易品种和magic编号对它们进行排序,并将持仓的止损价位移动到与当前价格计算得出的距离上。每个持仓止损价的修改结果会被添加到res变量中。在所有持仓止损价循环修改结束时,我们要么设置true(如果所有符合交易品种/magic编号筛选条件的持仓止损价都成功修改),要么(如果至少有一个持仓没有成功修改)设置标志位为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的时间框架上,使用策略测试器在去年时间段上进行一次测试。所有价格变动(ticks)均不考虑交易延迟。

参数设置如下(未启用追踪):


让我们不使用追踪止损来进行测试并看下结果:



现在启用追踪止损:


然后运行相同的测试。

测试结果如下:



结果稍稍好些。很明显,在这些设置下,追踪止损功能轻微地改善了统计数据。

现在,我们可以基于已创建的简单追踪功能来制作任何其他类型的追踪止损了。

让我们实现几个基于指标的追踪类,包括抛物线转向(Parabolic 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;
                          }
                       }
  };

该类继承自简单的追踪类,因此它拥有该类的所有功能,以及通过句柄访问指标数据的方法。

在类的受保护部分中,声明了类变量、一个通过句柄从指标接收数据的方法,以及一个用于计算止损(StopLoss)价格的虚方法,该方法重写了父类中同名的方法。

类的公共部分包含了设置和获取指标属性的方法、构造函数和析构函数。

在默认构造函数的初始化字符串中,向父类传递了一个值为-1(表示所有仓位)的magic编号和零个追踪参数。用于计算指标的时间框架是当前图表的时间框架。

在参数化构造函数中,通过构造函数的形参传递了用于计算指标的图表货币对、图表周期以及所有追踪参数。
在类的析构函数中,释放了指标的运算部分,并将存储句柄的变量值设置为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)不允许我们将止损设置在当前价格。

因此,这个类实现了通过句柄访问指标数据,并根据指标值计算仓位止损价格的功能,并且它的所有方法都将在子类中可用。

现在我们可以基于创建的类来实现基于指标的追踪止损类。


抛物线指标追踪止损类

让我们继续在相同的文件\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(){}
  };

在类的私有部分,声明用于存储抛物线SAR指标参数值的变量。

公共部分包含用于设置和返回指标属性值的方法。我们声明了用于创建指标的方法,以及两个构造函数——默认构造函数和参数化构造函数。

在默认构造函数中,默认在当前交易品种和图表周期上创建指标,同时将追踪参数值设置为零。

在参数化构造函数中,所有参数都通过构造函数的输入传递,然后根据传递给构造函数的参数创建指标:

//+------------------------------------------------------------------+
//| 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() 方法用于创建抛物线转向(Parabolic 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);
  }


让我们测试基于Parabolic 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,并使用默认参数在测试器中运行它。



正如我们所见,持仓的止损价格已经正确地根据抛物线转向(Parabolic SAR)指标第一根柱子的值进行了跟踪。

接下来,我们将为终端中呈现的各种移动平均线创建追踪止损类。


移动平均线追踪止损类

这些移动平均线追踪止损类与抛物线转向追踪类的不同之处仅在于输入参数集和Initialize()方法创建的指标类型。Initialize()方法会为每个类创建一个与其对应的移动平均线指标。

基于这点,我们将仅考虑一个类:基于自适应移动平均线的追踪止损类:

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

该类与上述基于抛物线转向(Parabolic SAR)的追踪类完全相同。每个基于移动平均线(MA)的追踪类都具备其特有的一组用于存储指标参数的变量,以及用于设置和返回与这些变量相对应值的方法。所有基于移动平均线的追踪类都是相同的。你可以通过查看下面附带的Trailings.mqh文件中的代码来研究它们。你可以通过加载文末附件ExpertMACDWithTrailingByMA.mq5文件中的测试EA,来测试基于移动平均线的追踪类。

我们已经创建了基于简单追踪方式的类,以及基于终端内提供的标准指标的追踪止损和适用于设置头寸止损位的类。

但这些并不是用我们所呈现的类可以创建的全部追踪止损类型。还存在一些追踪止损类型,它们可以针对持有的多头和空头头寸,分别将其止损移动到特定的价格位置。这种追踪止损的一个例子可以是基于K线最高/最低价格的追踪,或者是基于分形指标(fractal indicator)的追踪。

为了实现这种追踪,我们应该能够为每种持仓类型指定一个单独的止损价。让我们创建这样一个类。


追踪指定的止损价格

要创建一个基于指定值的追踪止损,只需定义变量来代替指标参数,以便为多单和空单设置止损值。并且,我们不再需要从指标中获取数据,而只需根据这些变量中指定的值来计算止损价。在调用追踪的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连接起来。现在,让我们来考虑如何根据传递给追踪止损的值(即,K线的最高价和最低价)来连接和启动追踪止损。

将追踪止损功能添加到来自标准指标目录\MQL5\Experts\Advisors\中的ExpertMACD.mq5的EA中。将其保存为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;

InpTimeframe输入参数是图表的时间框架,从中获取最高价作为空头头寸的止损价,和最低价作为多头头寸的止损价。

InpDataRatesIndex输入参数是由InpTimeframe确定的时间框架图表上的柱形索引,从中获取要用于设置头寸止损的最高价和最低价。


在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

只有其magic编号与EA相匹配的头寸的止损位才会被追踪。换句话说,我们只追踪由此EA开立的头寸。


在EA的OnTick()程序中,使用在InpDataRatesIndex输入中指定的索引获取柱形数据,并在确认多头和空头头寸的止损价格(柱形的最高价和最低价)时启动追踪

//+------------------------------------------------------------------+
//| 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中索引对应的K线的最高价和最低价。

这种追踪止损能在多大程度上提高交易系统的胜率,需要通过独立测试来确定。

此外,每个人都可以使用本文中介绍的类,根据自己的算法创建自定义的追踪止损。这里的研究空间很大,几乎不受限制。


结论

在本文中,我们创建了各种类型的追踪止损类,使我们能够轻松地将追踪止损添加到任何EA中。创建的类也是使用自定义算法实现追踪止损的良好工具集。

示例中仅涵盖了一种处理类对象的方法——创建追踪类对象的实例。这种方法使得程序能够预先确定需要哪种类型的追踪止损以及应该具有哪些参数。对于每个追踪止损,都会在全局区域中创建一个对象。这种方法是可行的——它简单明了。但是,为了在程序执行期间使用“new”对象创建运算符动态创建追踪对象,最好利用标准库提供的创建链接对象列表的功能。为此,所有追踪类都派生自“CObject标准库基对象”。这种方法可以在评论中讨论,因为它超出了当前主题的范围。

文章中介绍的所有类都可以“原封不动”地用于您自己的开发中,或者您可以根据自己的需求和任务进行修改。


本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/14862

带有预测性的三角套利 带有预测性的三角套利
本文简化了三角套利的过程,向您展示如何利用预测和专业软件更明智地进行货币交易,即使您是新手也能轻松入门。准备好凭借专业知识进行交易了吗?
神经网络变得简单(第 79 部分):在状态上下文中的特征聚合查询(FAQ) 神经网络变得简单(第 79 部分):在状态上下文中的特征聚合查询(FAQ)
在上一篇文章中,我们领略了一种从图像中检测对象的方法。不过,处理静态图像与处理动态时间序列(例如我们所分析的价格动态)有些不同。在本文中,我们将研究检测视频中对象的方法,其可在某种程度上更接近我们正在解决的问题。
开发多币种 EA 交易(第 7 部分):根据前向时间段选择组 开发多币种 EA 交易(第 7 部分):根据前向时间段选择组
在此之前,我们曾对一组交易策略实例的选择进行过评估,目的是改进它们的联合运行结果,但这只是在对单个实例进行优化的同一时间段进行的。让我们拭目以待在前向时间段会发生什么。
矩阵分解:更实用的建模 矩阵分解:更实用的建模
您可能没有注意到,矩阵建模有点奇怪,因为只指定了列,而不是行和列。在阅读执行矩阵分解的代码时,这看起来非常奇怪。如果您希望看到列出的行和列,那么在尝试分解时可能会感到困惑。此外,这种矩阵建模方法并不是最好的。这是因为当我们以这种方式对矩阵建模时,会遇到一些限制,迫使我们使用其他方法或函数,而如果以更合适的方式建模,这些方法或函数是不必要的。