English Русский Español Deutsch 日本語 Português
preview
事后交易分析:在策略测试器中选择尾随停止和新的止损位

事后交易分析:在策略测试器中选择尾随停止和新的止损位

MetaTrader 5示例 |
374 4
Artyom Trishkin
Artyom Trishkin

内容


概述

上一篇文章中,我们创建了一个 EA,基于真实账户的交易结果在客户端的策略测试器中复现交易。我们添加了设置新的止损和止盈价位的功能,以便我们在测试器中测试采用不同止损大小的交易。结果出乎意料:我们扭亏为盈没,获得了与实际交易中的亏损相称的盈利。这意味着,如果我们在交易账户中使用止损和止盈价位,令我们在测试器中获利,那么在真实账户上也应当可盈利。这只是止损价位的简单更改。我想知道如果我们添加尾随停止会发生什么?事情将会如何变化?

今天,我们将若干不同的尾随停止连接到 EA,并在策略测试器中测试我们的交易。我们看看会得到什么结果。


选择和精炼持仓尾随停止

我们尝试尾随与抛物线转向移动平均线指标连接。抛物线转向指标,从其描述和名称(SAR = 停止和逆转)推断,应当非常适合根据其数值移动停止线:

抛物线转向

抛物线转向技术指标是为分析趋势市场而开发的。该指标基于价格图表构建。该指标类似于移动平均线,唯一的区别是抛物线转向指标以更高的加速度移动,且或许会随价格项改变其位置。该指标在看涨市场低于价格(上升趋势),当市场看跌(下降趋势)时,它在价格上方。

如果价格穿过抛物线转向指标线,则指标转向,其未来数值位于价格的另一侧。当指标发生这样的转向时,前一个周期的最高或最低价格将作为起点。当指标转弯时,它会给出趋势结束(修正阶段或横盘)、或要转向的信号。

抛物线转向指标是一款提供离场点的出色指标。当价格跌破低于 SAR 线时,多头持仓应平仓,当价格升至 SAR 线上方时,空头持仓应平仓。也就是,有必要跟踪抛物线转向指标的方向,并仅保持与抛物线转向指标方向相同的持仓。该指标往往用作尾随停止线。

如果多头开仓(即价格高于 SAR 线),则无论价格朝哪个方向走,抛物线 SAR 线都会上行。抛物线转向指标线的移动量取决于价格走势的数值。

移动平均线由于其性质滞后,也适合跟随价格作为尾随止损线。对于多头持仓,如果指标线低于价格,那么可用移动平均线数值作为尾随停止。价格将穿过移动平均线,当然此刻触发停止。对于空头持仓,如果价格低于移动平均线,停止应跟随移动平均线。

我之前写过一篇关于将连接任意尾随到 EA 的文章:《如何开发任何类型的尾随停止,并将其连接到 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() 方法,但指定所需移动止损的持仓单据,并从主循环中调用它。

在简单的尾随类中,声明一个新方法来获取必要的持仓的单据:

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

当使用取自本文的尾随停止类时,我们会获得抛物线和所有标准移动平均线类型的尾随停止。该类还有一个基于指定数值的尾随函数,但我们不会在此用到它,因为它需要在自己的程序中计算停止价位,并将它们传递到尾随类所调用的 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
  {
  }

为什么枚举常量值以 2 而不是零开头?我们将将要创建的新 EA 还列出了测试模式:

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

此处有两个常量:原始交易(0),以及按指定止损单进行交易(1)。稍后,我们还将在此处添加与尾随枚举对应的新常量。正是出于这个原因,尾随枚举常量的值从数值 2 开始。

在类的私密部分中,声明一个指向尾随停止类对象的指针,和一个变量,用于存储图表周期,供尾随停止中所用指标的计算。在公开部分,我们声明了一个设置尾随参数的方法、一个启动持仓尾随的方法、构造函数、和一个析构函数:

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,并将其保存在与 TradingByHistoryDeals_Ext.mq5 相同的文件夹 \MQL5\Experts\TradingByHistoryDeals\ 之中。

包含 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,并在警报中指定推荐的测试器参数,并在流水账中复现

原始交易:

我们看到原始交易导致亏损 658 美元。

我们尝试不同的尾随停止。允许将初始止损设置为 100 点,并在测试器中运行每笔尾随订单。
这将轮流完成,无需更改其它参数。我们看看交易如何变化。

简单尾随:

我们亏损了 746.1 美元。甚至比原始交易还要多。

我们使用指标检查尾随。

抛物线转向指标尾随:

盈利 — 541.8 美元。

现在我们看看基于不同类型移动平均线的尾随停止。

按 АМА 尾随:

盈利 — 806.5 美元。

按 DEMA 尾随:

盈利 — 1397.1 美元。

按 FRAMA 尾随:

盈利 — 1291.6 美元。

按 MA 尾随:

盈利 — 563.1 美元。

按 TEMA 尾随:

盈利 — 1355.1 美元。

按 VIDYA 尾随:

盈利 — 283.3 美元。

故此,最后一张表显示了使用不同类型的尾随停止相对于原始交易的结果:

#
尾随类型
止损大小
止盈大小
 总盈利
1
原始交易:
100
500
 - 658.0
2
简单尾随停止
最初为 100,然后据价格 100 点处停止
0
 - 746.1
3
抛物线转向指标尾随停止
最初为 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

如是结果,当原始交易无利可图时,与客户端中的尾随停止相当的常规简单尾随会导致更大的亏损。我们看一个基于抛物线转向指标的尾随指标,该指标定位为显示逆转和止损价位(停止并逆转)的指标。事实上,通过遵循第一根柱上指标线后面的止损价位,我们盈利了 540 美元。

现在我们看看基于第一根柱上不同类型移动平均线数值移动持仓止损的结果。按 VIDYA 指标的尾随导致亏损 280 美元,但所有其它指标均显示盈利。甚至,遵循简单移动平均线止损比沿抛物线转向指标尾随产生更大的盈利。改善交易结果的绝对“冠军”是双重指数移动平均线 — +1397 美元的盈利。


结束语

我们开发了一款 EA,允许在策略测试器中测试我们的交易,并为我们的交易风格选择最合适的尾随停止。请注意,尾随测试中用到的所有指标参数都有标准值,尾随设置是随机选择的。

最正确的解决方案是对每个单独的品种进行测试,并根据货币对的变动和波动性特征选择可接受的尾随设置。但是该 EA 没有单独的设置来为每个单独的品种设置自定义参数。但最主要的是利用策略测试器测试,展示分析和改进交易的可能性。这意味着能够深入开发 EA,从而允许我们为每个品种设置自己的参数 — 这并不困难。

所有审查过的类文件和 EA 都附于文后。还包括一个存档。一旦解压,您可立即获取在所需终端文件夹中安装好的文件进行测试。

文章中用到的程序:

#
名称
 类型  描述
1
Trailings.mqh
类库
尾随类库
2
SymbolTrade.mqh
类库
交易结构和类库,品种交易类
3
SymbolTradeExt.mqh
类库
品种和尾随交易类
4
TradingByHistoryDeals_Ext.mq5
智能交易系统
在策略测试器中复现账户上执行的成交,查看和修改成交的止损、止盈和尾随停止的 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。拖网也很简单。
在一些机器人中使用一个,在另一些机器人中使用另一个拖网。
Yevgeniy Koshtenko
Yevgeniy Koshtenko | 30 1月 2025 在 23:21
TOP article!
den2008224
den2008224 | 31 1月 2025 在 05:16

Трал по VIDYA:

利润 - 283.3 美元。

错误:损失 - 283.3 美元。

Alexey Viktorov
Alexey Viktorov | 31 1月 2025 在 06:46
den2008224 #:

错误:损失 283.3 美元。

文章中写的是利润的负值。

但是,减号后面的空格是不小心加上去的。
从基础到中级:模板和类型名称(一) 从基础到中级:模板和类型名称(一)
在本文中,我们开始考虑许多初学者避免的概念之一。这与模板不是一个容易的话题有关,因为许多人不理解模板的基本原理:函数和过程的重载。
价格行为分析工具包开发(第十六部分):引入四分之一理论(2)—— 侵入探测器智能交易系统(EA) 价格行为分析工具包开发(第十六部分):引入四分之一理论(2)—— 侵入探测器智能交易系统(EA)
在前一篇文章中,我们介绍了一个名为“四分位绘图脚本”的简单脚本。现在,我们在此基础上更进一步,创建一个用于监控的智能交易系统(EA),以跟踪这些四分位水平,并对这些价位可能引发的市场反应进行监督。请随我们一同探索在本篇文章中开发区域检测工具的过程。
价格行为分析工具包开发(第 17 部分):TrendLoom EA 工具 价格行为分析工具包开发(第 17 部分):TrendLoom EA 工具
作为一名价格行为的观察者和交易者,我注意到当一个趋势得到多个时间周期的确认时,它通常会朝着该方向延续。可能不同的是趋势持续的时间,而这取决于您是哪种类型的交易者,无论是长期持仓还是从事剥头皮交易。您为确认所选的时间周期起着至关重要的作用。读这篇文章,了解一个快速、自动化的系统,只需点击一下按钮或通过定期更新,就能帮助您分析不同时间周期的整体趋势。
卡尔曼滤波器在外汇均值回归策略中的应用 卡尔曼滤波器在外汇均值回归策略中的应用
卡尔曼滤波器是一种递归算法,在算法交易中用于通过滤除价格走势中的噪声来估计金融时间序列的真实状态。它能够根据新的市场数据动态更新预测,这使得它在均值回归等自适应策略中极具价值。本文首先介绍卡尔曼滤波器,涵盖其计算方法和实现方式。接下来,我们以外汇领域一个经典的均值回归策略为例,应用该滤波器。最后,我们通过将卡尔曼滤波器与移动平均线(MA)在外汇不同货币对上进行比较,开展各种统计分析。