下载MetaTrader 5

本文讨论如何在跨平台智能交易系统中设置自定义停止价位。它还讨论了一种紧密相关的方法, 即随着时间的推移, 定义停止位的演化。

24 十月 2017, 16:42
Enrico Lambino
0
4 579

内容目绿

  1. 概述
  2. 自定义停止位
  3. 修改停止价位
  4. 盈亏平衡
  5. 尾随止损
  6. 尾随止盈
  7. 实现
  8. CTrails (容器)
  9. 扩展 CTrail
  10. 示例

概述

在前一篇文章《跨平台智能交易系统: 停止位》中, 已展示了如何使用 CStop 类来为跨平台智能交易系统中的给定交易设置止损和止盈价位。所述价位可以用点值或点数来表达。然而, 在很多现实世界的智能交易系统中, 情况并非总是如此。止损和止损通常不需要依据与入场价格的距离来计算。而是在某些情况下, 以图表价格表达的止损 (和/或) 止盈价位通常出自其它的计算结果。本文将讨论如何利用 CStop 类做到以图表价格指定止损和止盈价位。本文还讨论了如何利用本文中介绍的函数库来修改这些停止位。甚至, 它还引入了一个新类 CTrail, 它非常酷似所实现的自定义停止位。然而, 与自定义停止位不同, CTrail 主要用于定义随时间演变的停止位。

自定义停止位

在前一篇文章中, 已展示了如何在跨平台智能交易系统中实现基于点值或点数的止损和止盈。然而, 并非所有的 EA 总是如此。有些策略需要动态计算止损和止盈价位, 通常基于时间序列 (OHLC) 等数据以及技术指标的输出。

在 CStop 中可以设置动态计算止损和止盈价位的两种方法, StopLossCustom 和 TakeProfitCustom。以下代码片段展示这两种方法:

bool CStopBase::StopLossCustom(void)
  {
   return m_stoploss==0;
  }

bool CStopBase::TakeProfitCustom(void)
  {
   return m_takeprofit==0;
  }

程序员可以自由地扩展这些方法, 以便为任何给定的 CStop 实例达成所需的止损或止盈价位。

上面提到的两种方法实际上是类中的重载方法。以下的替代方法返回 布尔 值:

bool CStopBase::StopLossCustom(void)
  {
   return m_stoploss==0;
  }

bool CStopBase::TakeProfitCustom(void)
  {
   return m_takeprofit==0;
  }

如果特定的类成员 (m_stoploss 或 m_takeprofit) 具有零值, 则两个函数都将返回 true。它们的目的将稍后讨论。

CStop 遵循以下指导原则计算停止位。停止价位可以是一个止损或止盈价位, 在 CStop 中表述为成员 m_stoploss 和 m_takeprofit。在下面的步骤中, 假设只处理止损价位 (m_stoploss):

  1. 如果 m_stoploss 为零, 则在计算止损时使用 StopLossCustom
  2. 如果 m_stoploss 非零, 则在计算交易的真实止损时依据入场价使用 m_stoploss。

使用 TakeProfitCustom 方法和类成员 m_takeprofit 计算止盈时原则相同。

可以在两个主要区域找到这四种方法的实际用法。对于主停止价位, 方法调用可以在订单管理器本身 (COrderManager) 中找到。对于其它停止位, 则在每笔订单实例 (COrder) 之内。

有些实例中, 主要停止价位与初始交易请求一起发送到经纪商, 因此, 智能交易系统仅在开始处理交易请求时才需要这些信息, 而不是在初始交易成功发送之后。在 Metatrader 4 和 Metatrader 5 对冲模式下, 对于经纪商端的止损这是真的。对于这些模式, 需要在 OrderSend (MQL4, MQL5) 函数的交易请求里面包含止损和止盈价位信息, 且这些停止价位仅适用于关注的主要交易 (非全局性)。

在订单管理器的 TradeOpen 方法中, 我们可以找到 CStop 的两种方法的调用。下面显示的是 MQL4 版本的代码:

bool COrderManager::TradeOpen(const string symbol,ENUM_ORDER_TYPE type,double price,bool in_points=true)
  {
   int trades_total = TradesTotal();
   int orders_total = OrdersTotal();
   m_symbol=m_symbol_man.Get(symbol);
   if(!CheckPointer(m_symbol))
      return false;
   if(!IsPositionAllowed(type))
      return true;
   if(m_max_orders>orders_total && (m_max_trades>trades_total || m_max_trades<=0))
     {
      ENUM_ORDER_TYPE ordertype=type;
      if(in_points)
         price=PriceCalculate(type);
      double sl=0,tp=0;
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
        {
         sl = m_main_stop.StopLossCustom()?m_main_stop.StopLossCustom(symbol,type,price):m_main_stop.StopLossCalculate(symbol,type,price);
         tp = m_main_stop.TakeProfitCustom()?m_main_stop.TakeProfitCustom(symbol,type,price):m_main_stop.TakeProfitCalculate(symbol,type,price);
        }
      double lotsize=LotSizeCalculate(price,type,sl);
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
      {
         if (!m_main_stop.Broker())
         {
            sl = 0;
            tp = 0;
         }
      }
      int ticket=(int)SendOrder(type,lotsize,price,sl,tp);
      if(ticket>0)
        {
         if(OrderSelect(ticket,SELECT_BY_TICKET))
         {
            COrder *order = m_orders.NewOrder(OrderTicket(),OrderSymbol(),OrderMagicNumber(),
(ENUM_ORDER_TYPE)::OrderType(),::OrderLots(),::OrderOpenPrice());            
            if (CheckPointer(order))
            {
               LatestOrder(GetPointer(order));
               return true;
            }   
         }         
        }
     }
   return false;
  }

下面显示的是 MQL5 版本的代码:

bool COrderManager::TradeOpen(const string symbol,ENUM_ORDER_TYPE type,double price,bool in_points=true)
  {
   bool ret=false;
   double lotsize=0.0;
   int trades_total =TradesTotal();
   int orders_total = OrdersTotal();
   m_symbol=m_symbol_man.Get(symbol);
   if(!IsPositionAllowed(type))
      return true;
   if(m_max_orders>orders_total && (m_max_trades>trades_total || m_max_trades<=0))
     {
      if(in_points)
         price=PriceCalculate(type);
      double sl=0.0,tp=0.0;
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
        {
         sl = m_main_stop.StopLossCustom()?m_main_stop.StopLossCustom(symbol,type,price):m_main_stop.StopLossCalculate(symbol,type,price);
         tp = m_main_stop.TakeProfitCustom()?m_main_stop.TakeProfitCustom(symbol,type,price):m_main_stop.TakeProfitCalculate(symbol,type,price);
        }
      lotsize=LotSizeCalculate(price,type,m_main_stop==NULL?0:m_main_stop.StopLossCalculate(symbol,type,price));  
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
      {
         if (!m_main_stop.Broker() || !IsHedging())
         {
            sl = 0;
            tp = 0;
         }
      }    
      ret=SendOrder(type,lotsize,price,sl,tp);
      if(ret)
      {
         COrder *order = m_orders.NewOrder((int)m_trade.ResultOrder(),m_trade.RequestSymbol(),(int)m_trade.RequestMagic(),
m_trade.RequestType(),m_trade.ResultVolume(),m_trade.ResultPrice());
         if (CheckPointer(order))
         {
            LatestOrder(GetPointer(order));
            return true;
         }
      }         
     }
   return ret;
  }

虽然 TradeOpen 是一个相互分离的实现, 但是我们可以从两个版本中找到一个共同点, 就是计算主要停止价位的方法。首先, 它计算自主停止价位的止损和止盈 (分别表达为 sl 和 tp)。由于计算手数 (资金管理) 时也可能需要该信息, 所以无论真实的初始交易请求中是否需要止损和止盈, 都已经准备就绪。

在计算手数之后, 重要的是在某些情况下将信息重置为零。在 MQL4 中, 如果主停止位不是经纪商端的停止位, 则将变量 sl 和 tp 设置为零:

if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
{
   if (!m_main_stop.Broker())
   {
      sl = 0;
      tp = 0;
   }
}

在 MQL5 中, 我们试图避免在净持模式下使用止损和止盈, 因为它们会被应用到整体持仓上, 而不只是单笔交易的交易量。因此, 如果不是经纪商端的停止位或平台处于净持模式, 则变量将重置为零:

if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
{
   if (!m_main_stop.Broker() || !IsHedging())
   {
      sl = 0;
      tp = 0;
   }
}  

如前一篇文章 (参见: 跨平台智能交易系统: 停止位) 中所讨论的那样, 智能交易系统将经纪商端的停止位转换为净持模式下的挂单, 因此在此模式下的初始交易请求不再需要发送止损和止损。

对于具有多个止损和止盈价位的交易, 这些价位在主交易成功处理之后立即被处理。交易必须先进场, 因为如果主交易尚未入场, 这些停止位将是无意义的。我们可以在 COrder 实例的初始化中发现这些通过 COrder 的 CreateStops (从 COrderBase 获取, 因为 MQL4 和 MQL5 共享实现) 方法产生的其它停止位:

void COrderBase::CreateStops(CStops *stops)
  {
   if(!CheckPointer(stops))
      return;
   if(stops.Total()>0)
     {
      for(int i=0;i<stops.Total();i++)
        {
         CStop *stop=stops.At(i);
         if(CheckPointer(stop)==POINTER_INVALID)
            continue;
         m_order_stops.NewOrderStop(GetPointer(this),stop);
        }
     }
  }

如上面的代码所示, 智能交易系统为每个发现的 CStop 实例都创建一个 COrderStop 实例。然而, 这些附加的停止位仅在主交易成功入场之后才会创建。

修改停止价位

CStop 有独属的 CTradeManager 实例, 因此 CExpertTradeX (扩展自 MQL5 标准库的 CExpertTrade)。该实例独立于订单管理器 (COrderManager) 中找到的实例, 它专门用于处理主交易的入场。停止价位的修改不是由 COrderManager 管理, 而是 CStop 实例本身。然而, 由于停止价位的修改是按照预订顺序执行的, 所以修改停止价位的调用必须源于交易本身, 即 COrder 实例内表示的交易。

停止价位的监视始自 COrder 的 CheckStops 方法, 其代码如下所示:

void COrderBase::CheckStops(void)
  {
   m_order_stops.Check(m_volume);
  }

此处, 它只是调用类成员之一的 Check 方法, 它是 COrderStops 的一个实例。回想前面的文章, COrderStops 是一个指向 COrderStop 实例的容器。另一方面, COrderStop 是 COrder 的真正实例中表示的 CStop。

现在, 我们来看一下 COrderStops 的 Check 方法。该方法显示在以下代码片段中:

COrderStopsBase::Check(double &volume)
  {
   if(!Active())
      return;
   for(int i=0;i<Total();i++)
     {
      COrderStop *order_stop=(COrderStop *)At(i);
      if(CheckPointer(order_stop))
        {
         if(order_stop.IsClosed())
            continue;
         order_stop.CheckTrailing();
         order_stop.Update();
         order_stop.Check(volume);
         if(!CheckNewTicket(order_stop))
            return;
        }
     }
  }

正如我们从代码中可以看出, 它在 COrderStop 的特定实例上至少执行五个函数

  1. 它检查 COrderStop 的特定实例是否已经平单 (IsClosed 方法)
  2. 它根据分配给它的 trailingstop 实例更新停止价位, 如果有的话 (CheckTrailing 方法)
  3. 它更新停止价位 (Update 方法)
  4. 它检查行情是否触及特定停止价位( Check 方法)
  5. 它检查一个新的逐笔 ID 是否代表一笔交易

在这些任务中, 第二个任务是与尾随止损或止盈相关。第一个任务仅用于查看在停止价位是否需要进一步的操作 (如果已在停止价位平仓, 智能交易系统不需要进一步的检查)。当从智能交易系统的外部修改停止价位 (例如拖动虚拟停止位的指示线) 时, 使用第三种方法。第四个任务是用来查看停止价位 (无论由尾随更新与否)是否被行情触发。第五项任务仅在 Metatrader 4 中使用, 因为只有在该平台中, 智能交易系统在部分平仓时才会经历一笔交易变更其单号。Metatrader 5 实现了更简单的逻辑, 在这种情况下, 它会保持交易进程的清晰足迹。

COrderStop 的实例表述由 CStop 定义的一笔交易的停止价位。因此, 所有的停止价位尾随都将最终导致此类对象实例的变更。以下代码显示其 CheckTrailing 方法:

bool COrderStopBase::CheckTrailing(void)
  {
   if(!CheckPointer(m_stop) || m_order.IsClosed() || m_order.IsSuspended() || 
      (m_stoploss_closed && m_takeprofit_closed))
      return false;
   double stoploss=0,takeprofit=0;
   string symbol=m_order.Symbol();
   ENUM_ORDER_TYPE type=m_order.OrderType();
   double price=m_order.Price();
   double sl = StopLoss();
   double tp = TakeProfit();
   if(!m_stoploss_closed)
      stoploss=m_stop.CheckTrailing(symbol,type,price,sl,TRAIL_TARGET_STOPLOSS);
   if(!m_takeprofit_closed)
      takeprofit=m_stop.CheckTrailing(symbol,type,price,tp,TRAIL_TARGET_TAKEPROFIT);
   if(!IsStopLossValid(stoploss))
      stoploss=0;
   if(!IsTakeProfitValid(takeprofit))
      takeprofit=0;
   return Modify(stoploss,takeprofit);
  }
从这段代码中, 我们看到, 当停止价位已平仓时, 它仍然遵循不修改特定停止价位的一贯原则。如果订单的停止价位符合条件, 则继续调用它所代表的 CStop 实例的 CheckTrailing 方法。现在, 我们来看一下 CStop 的 CheckTrailing 方法:
double CStopBase::CheckTrailing(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!CheckPointer(m_trails))
      return 0;
   return m_trails.Check(symbol,type,entry_price,price,mode);
  }

此处, CStop 调用类成员之一 m_trails 的 Check 方法。m_trails 只是一个指向 trailing 或 trailingstop 对象的指针容器。该方法的代码如下所示:

double CTrailsBase::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;   
   double val=0.0,ret=0.0;
   for(int i=0;i<Total();i++)
     {
      CTrail *trail=At(i);
      if(!CheckPointer(trail))
         continue;
      if(!trail.Active())
         continue;
      int trail_target=trail.TrailTarget();
      if(mode!=trail_target)
         continue;
      val=trail.Check(symbol,type,entry_price,price,mode);
      if((type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val>ret || ret==0.0)
            ret=val;
      }      
      else if((type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val<ret || ret==0.0)
            ret=val;
      }      
     }
   return ret;
  }

在这一点上, 了解 CTrails 容器在其自身的 CTrail 实例上遍历并返回一个最终值就足矣了。该最终值是根据所选品种的图表价格, 因此是 double 类型。这是成功尾随之后的止损或止盈的新数值。现在, 我们回到 COrderStop 的 CheckTrailing 方法, 因为它是从这个方法中实际调用修改停止价位的:

bool COrderStopBase::CheckTrailing(void)
  {
   if(!CheckPointer(m_stop) || m_order.IsClosed() || m_order.IsSuspended() || 
      (m_stoploss_closed && m_takeprofit_closed))
      return false;
   double stoploss=0,takeprofit=0;
   string symbol=m_order.Symbol();
   ENUM_ORDER_TYPE type=m_order.OrderType();
   double price=m_order.Price();
   double sl = StopLoss();
   double tp = TakeProfit();
   if(!m_stoploss_closed)
      stoploss=m_stop.CheckTrailing(symbol,type,price,sl,TRAIL_TARGET_STOPLOSS);
   if(!m_takeprofit_closed)
      takeprofit=m_stop.CheckTrailing(symbol,type,price,tp,TRAIL_TARGET_TAKEPROFIT);
   if(!IsStopLossValid(stoploss))
      stoploss=0;
   if(!IsTakeProfitValid(takeprofit))
      takeprofit=0;
   return Modify(stoploss,takeprofit);
  }

此方法的返回值为布尔类型, 它是 COrderStop 的 Modify 方法修改停止价位的结果 (如果成功则返回 true)。但在发送修改请求之前, 它使用 IsStopLossValid 和 IsTakeProfitValid 方法检查止损和止盈是否有效。如果建议值无效, 则重置为零:

bool COrderStopBase::IsStopLossValid(const double stoploss) const
  {
   return stoploss!=StopLoss();
  }

bool COrderStopBase::IsTakeProfitValid(const double takeprofit) const
  {
   return takeprofit!=TakeProfit();
  }

在以上的代码中, 对于止损和止损, 建议值不应等于当前值。

在止损和止盈评估之后, 调用 COrderStop 的 Modify 方法, 如下所示:

bool COrderStopBase::Modify(const double stoploss,const double takeprofit)
  {
   bool stoploss_modified=false,takeprofit_modified=false;
   if(stoploss>0 && takeprofit>0)
     {
      if(ModifyStops(stoploss,takeprofit))
        {
         stoploss_modified=true;
         takeprofit_modified=true;
        }
     }
   else if(stoploss>0 && takeprofit==0)
      stoploss_modified=ModifyStopLoss(stoploss);
   else if(takeprofit>0 && stoploss==0)
      takeprofit_modified=ModifyTakeProfit(takeprofit);
   return stoploss_modified || takeprofit_modified;
  }

在此刻, 根据止损和止盈的值, 执行三种类型的操作。正常时, 修改可以在一个操作中执行, 就像在经纪商端停止位的情况那样。不过情况并非总是如此。若是基于挂单和虚拟停止位则必须依次单独处理止损和止盈。用于修改这两个值的代码可以在以下方法中找到:

bool COrderStopBase::ModifyStops(const double stoploss,const double takeprofit)
  {
   return ModifyStopLoss(stoploss) && ModifyTakeProfit(takeprofit);
  }


方法 ModifyStops 简单地调用其它两种方法。分离实现始于两个因素: (1) 使用的编译器类型 (MQL4 或 MQL5) 和 (2) 停止类型 (基于经纪商端, 挂单或虚拟)。如果停止位是以经纪商端为基础, 那么会导致交易要求修改主交易。如果停止位是基于挂单, 则智能交易系统必须移动目标挂单的入场价。如果停止位是虚拟的, 则智能交易系统只需更新其关于停止价位的内部数据。

COrderStop 没有一个交易对象 (或指向它的指针) 作为其类成员之一, 因此它本身不能改变自己的停止价位。它仍然需要依靠代表它的 CStop 实例来修改自身的停止价位。因此, 任何修改停止价位都将最终导致在 CStop 中调用某个方法。

盈亏平衡

盈亏平衡, 是盈利收入等于成本的那个点。在交易中, 收入是交易的实际利润, 而成本则是点差 (和/或) 佣金。若是一笔交易在这个点离场, 则利润/亏损均为零。对于不收取佣金的做市商或经纪商来说, 盈亏平衡点通常是交易的入场价格。

在智能交易系统里, 当系统成功地为交易设置的止损等于入场价时, 该笔交易会被告知已处于盈亏平衡。达到这个状态后, 交易的最大风险变为零。然而, 交易仍然可以通过其它手段离场, 例如离场信号, 通常是盈利。

在一笔盈亏平衡的交易中, 智能交易系统至少受到两个限制:

  1. 动态交易止损
  2. 经纪商的最小距离

一笔多头仓位交易的止损应该始终低于目前市场价格, 而空头仓位则应高于市场价格。鉴于这种限制, 智能交易系统只有在交易处于浮盈状态时, 才能将止损移动到盈亏平衡点。

经纪商的最小距离要求在修改交易的止损时仍然适用, 而不仅只在交易初始入场时。因此, 一旦入场, 通常不可能立刻将交易止损设定在盈亏平衡点。行情必须在有利于持仓的方向上移动相当远的距离后, 智能交易系统才可以将止损移动到盈亏平衡点。

鉴于这些限制, 在设计盈亏平衡功能时, 我们需要考虑至少两个因素:

  1. 激活价格
  2. 激活后的新止损

激活价格, 或触发价格, 是行情必须达到的价格, 以便智能交易系统将交易的止损移动到盈亏平衡点。这个激活价格必须在逐笔交易的基础上进行评估, 因为每笔交易预计会有不同的入场价格。

激活后, 智能交易系统强制移动交易的止损至盈亏平衡点。通常, 这是该笔交易的入场价格, 但情况并非总是如此。对于收取佣金的经纪商, 盈亏平衡点高于多头持仓的入场价格, 低于空头持仓的入场价格。

这些数值通常是参照另一个数值计算的, 例如当前的市场价格和交易的入场价格。

以下功能显示了以上述方式计算盈亏平衡的图表。基于这张流程图, 要预先计算三个数值: 激活, 失活和新的停止价位。如果当前价格水平大于或等于初始阶段所需的最低价位 (设定止损到盈亏平衡), 则先前计算出的新止损价位将被暂定用作交易的新止损价位。否则, 输出将为零。下一步是检查新的停止价位是否优于当前停止价位, 如果满足上一个条件, 则应始终返回 true, 并返回计算出的停止价位作为最终输出。


盈亏平衡


尾随止损

尾随停止可看作是一个特殊的盈亏平衡。虽然盈亏平衡通常只会应用一次, 但可以任意次数地使用尾随停止。激活后, 尾随通常会应用于整个交易的剩余存活期。在设计尾随停止时, 至少需要考虑三个因素:

  1. 激活价格
  2. 激活后的新止损
  3. 尾随的频率

尾随停止特征与前两个变量共享。然而, 与盈亏平衡不同的是, 尾随的激活价格有可能是价格图表上的一个点, 此处交易处于浮亏或 "资金超支" 状态。在交易亏损的情况下, 交易的止损也可被修改, 因此也可以尾随止损。当发生行情触及交易的有效止损这种情况时, 则仍将导致亏损, 因为它还没有满足越过盈亏平衡的先决条件。

第三个因素是尾随止损频率或 "步长"。当初始尾随止损开始以后, 这决定了当前止损价位到下一价位的距离 (以点值或点数)。这使得它与第一个变量非常相似, 即为激活价格。然而, 与激活价格不同, 在每个尾随阶段计算的价格也会依据第三个变量的变化而提升。

在某些尾随停止中有第四个因素, 即失活价格。交易在达到的这一点后, 智能交易系统将停止尾随止损。这也需要在每笔交易的基础上进行评估。 

以下示意图展示了交易尾随止损点的流程图。这与以前的示意图 (盈亏平衡) 没有太大的区别。如果价格大于尾随初始阶段, 则很可能停止价位已经超出了初始阶段。否则, 它仍将使用尾随的初始阶段作为当前运行的新停止价位值。如果停止价位还没有超过激活价格, 那么只需遵循计算盈亏平衡的剩余程序 (失活价格不适用于盈亏平衡)。


尾随止损


尾随止盈

尾随或尾随停止通常说的是止损的尾随, 但也可以尾随交易的止盈。在这种情况下, 随着价格走向更接近止盈价位 (与止损相反), 则止盈被调整。理论上, 一个尾随止盈将永远不会被触及。但在某些情况下还是有可能的, 譬如突然的价格暴涨和跳空价格走势 –行情移动快于智能交易系统对此特征做出反应的任何条件。

为了让尾随止盈有意义, 有些事情需要满足:

  1. 激活价格应在当前止盈价位之内
  2. 触发下一阶段尾随的下一个价格应在当前的止盈价位之内
  3. 理想情况下, 尾随不应太频繁

如果激活价格超出了当前的价位, 那么行情将在激活价格之前首先触及止盈价位。而一旦触及止盈, 交易就会在任何尾随止盈开始之前离场。对于多头持仓, 激活价格应低于止盈价位。对于空头持仓, 激活价格应高于止盈价位。对于第二个条件也是如此, 只是在初始激活后才能应用于进一步的阶段。

理想情况下, 尾随不应太频繁。这意味着一个止盈价位到下一个 (步长) 之间的距离应该足够宽裕。尾随止盈频率越高, 行情触及止盈的机会越低, 在尾随的每个阶段, 止盈价位随着当前价格而被推延。

实现

作为 CTrail 基类的 CTrailBase 如下面的代码所示:

class CTrailBase : public CObject
  {
protected:
   bool              m_active;
   ENUM_TRAIL_TARGET m_target;
   double            m_trail;
   double            m_start;
   double            m_end;
   double            m_step;
   CSymbolManager   *m_symbol_man;
   CSymbolInfo      *m_symbol;
   CEventAggregator *m_event_man;
   CObject          *m_container;
public:
                     CTrailBase(void);
                    ~CTrailBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_TRAIL;}
   //--- 初始化                    
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual CObject *GetContainer(void);
   virtual void      SetContainer(CObject*);
   virtual bool      Validate(void) const;
   //--- 取值和赋值    
   bool              Active(void) const;
   void              Active(const bool);
   double            End(void) const;
   void              End(const double);
   void              Set(const double,const double,const double,const double);
   double            Start(void) const;
   void              Start(const double);
   double            Step(void) const;
   void              Step(const double);
   double            Trail(void) const;
   void              Trail(const double);
   int               TrailTarget(void) const;
   void              TrailTarget(const ENUM_TRAIL_TARGET);
   //--- 检查
   virtual double    Check(const string,const ENUM_ORDER_TYPE,const double,const double,const ENUM_TRAIL_TARGET);
protected:
   //--- 价格计算
   virtual double    ActivationPrice(const ENUM_ORDER_TYPE,const double);
   virtual double    DeactivationPrice(const ENUM_ORDER_TYPE,const double);
   virtual double    Price(const ENUM_ORDER_TYPE);
   virtual bool      Refresh(const string);
  };

Set 方法允许我们配置 CTrails 实例的设置。它只能像通常的类构造函数一样工作。如果需要, 还可以声明一个调用此方法的自定义构造函数:

void CTrailBase::Set(const double trail,const double st,const double step=1,const double end=0)
  {
   m_trail=trail;
   m_start=st;
   m_end=end;
   m_step=step;
  }

CTrail 依靠行情数据进行计算。因此, 它有一个品种管理器 (CSymbolManager) 的实例作为其类成员之一。在执行任何进一步的计算之前, 需要刷新品种。

bool CTrailBase::Refresh(const string symbol)
  {
   if(!CheckPointer(m_symbol) || StringCompare(m_symbol.Name(),symbol)!=0)
      m_symbol=m_symbol_man.Get(symbol);
   return CheckPointer(m_symbol);
  }

激活价格是触发止损或止盈的 CTrail 实例进行初始移动的价格。由于 start, step, 和 end 参数是以点数为单位, 所以需要根据图表价格计算激活价格。在计算其它价格时使用相同的方法:

double CTrailBase::ActivationPrice(const ENUM_ORDER_TYPE type,const double entry_price)
  {
   if(type==ORDER_TYPE_BUY)
      return entry_price+m_start*m_symbol.Point();
   else if(type==ORDER_TYPE_SELL)
      return entry_price-m_start*m_symbol.Point();
   return 0;
  }

计算失活价格也遵循相同的例程, 但这次使用 m_end 类成员。

double CTrailBase::DeactivationPrice(const ENUM_ORDER_TYPE type,const double entry_price)
  {
   if(type==ORDER_TYPE_BUY)
      return m_end==0?0:entry_price+m_end*m_symbol.Point();
   else if(type==ORDER_TYPE_SELL)
      return m_end==0?0:entry_price-m_end*m_symbol.Point();
   return 0;
  }

使用零值作为失活价格意味着该功能被禁用。尾随将一直应用到主交易离场。

如果在评估时条件允许移动或尾随止损, 则 Price 方法计算止损或止盈的新值:

double CTrailBase::Price(const ENUM_ORDER_TYPE type)
  {
   if(type==ORDER_TYPE_BUY)
     {
      if(m_target==TRAIL_TARGET_STOPLOSS)
         return m_symbol.Bid()-m_trail*m_symbol.Point();
      else if(m_target==TRAIL_TARGET_TAKEPROFIT)
         return m_symbol.Ask()+m_trail*m_symbol.Point();
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(m_target==TRAIL_TARGET_STOPLOSS)
         return m_symbol.Ask()+m_trail*m_symbol.Point();
      else if(m_target==TRAIL_TARGET_TAKEPROFIT)
         return m_symbol.Bid()-m_trail*m_symbol.Point();
     }
   return 0;
  }

现在, 我们来讨论 Check 方法。CTrail 的一个特定实例既可以尾随止损亦或尾随止盈。因此, 我们只需要有能力为 CTrail 实例指定是瞄准交易的止损亦或止盈。这是通过枚举达成的, ENUM_TRAIL_TARGET。这个枚举的声明可以在下面找到 MQLx\Common\Enum\ENUM_TRAIL_TARGET.mqh。其代码如下所示:

enum ENUM_TRAIL_TARGET
  {
   TRAIL_TARGET_STOPLOSS,
   TRAIL_TARGET_TAKEPROFIT
  };

该类的 Check 方法如下代码所示。与该类目前讨论的其它方法不同, 此方法是一个公有方法。当进行更新时需要检查尾随停止价位则调用此方法。

double CTrailBase::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;
   if(!Refresh(symbol))
      return 0;
   if(m_start==0 || m_trail==0)
      return 0;
   double next_stop=0.0,activation=0.0,deactivation=0.0,new_price=0.0,point=m_symbol.Point();
   activation=ActivationPrice(type,entry_price);
   deactivation=DeactivationPrice(type,entry_price);
   new_price=Price(type);
   if(type==ORDER_TYPE_BUY)
     {
      if (m_target==TRAIL_TARGET_STOPLOSS)
      {
         if(m_step>0 && (activation==0.0 || price>=activation-m_trail*point) && (new_price>price+m_step*point))
            next_stop=new_price;
         else next_stop=activation-m_trail*point;
         if(next_stop<price)
            next_stop=price;
         if((deactivation==0) || (deactivation>0 && next_stop>=deactivation && next_stop>0.0))
            if(next_stop<=new_price)
               return next_stop;
      }
      else if (m_target==TRAIL_TARGET_TAKEPROFIT)
      {
         if(m_step>0 && ( activation==0.0 || price>=activation) && (new_price>price+m_step*point))
            next_stop=new_price;
         else next_stop=activation+m_trail*point;
         if(next_stop<price)
            next_stop=price;
         if((deactivation==0) || (deactivation>0 && next_stop<=deactivation && next_stop>0.0))
            if(next_stop>=new_price)
               return next_stop;
      }
     }
   if(type==ORDER_TYPE_SELL)
     {
      if (m_target==TRAIL_TARGET_STOPLOSS)
      {
         if(m_step>0 && (activation==0.0 || price<=activation+m_trail*point) && (new_price<price-m_step*point))
            next_stop=new_price;
         else next_stop=activation+m_trail*point;
         if(next_stop>price)
            next_stop=price;     
         if((deactivation==0) || (deactivation>0 && next_stop<=deactivation && next_stop>0.0))
            if(next_stop>=new_price)
               return next_stop;
      }
      else if (m_target==TRAIL_TARGET_TAKEPROFIT)
      {
         if(m_step>0 && (activation==0.0 || price<=activation) && (new_price<price-m_step*point))
            next_stop=new_price;
         else next_stop=activation-m_trail*point;
         if(next_stop>price)
            next_stop=price;     
         if((deactivation==0) || (deactivation>0 && next_stop<=deactivation && next_stop>0.0))
            if(next_stop<=new_price)
               return next_stop;
      }
     }
   return 0;
  }

从这里, 我们可以看到, 尾随止损和止盈之间的计算是不同的。对于止损值, 我们希望它能够在每个尾随阶段更贴近行情趋势。对止盈值, 我们的期望恰好相反 – 在每个尾随阶段将其从当前行情价格推离某段距离 (点值或点数)。

CTrails (容器)

CTrail 也将有一个名为 CTrails 的容器。其定义如下代码所示:

class CTrailsBase : public CArrayObj
  {
protected:
   bool              m_active;
   CEventAggregator *m_event_man;
   CStop            *m_stop;
public:
                     CTrailsBase(void);
                    ~CTrailsBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_TRAILS;}
   //--- 初始化
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual CStop    *GetContainer(void);
   virtual void      SetContainer(CStop*stop);
   virtual bool      Validate(void) const;
   //--- 取值和赋值
   bool              Active(void) const;
   void              Active(const bool activate);
   //--- 检查
   virtual double    Check(const string,const ENUM_ORDER_TYPE,const double,const double,const ENUM_TRAIL_TARGET);
  };

容器必须在 CStop 和它引用的 CTrail 对象之间进行连接。它也将有自己的 Check 方法:

double CTrailsBase::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;   
   double val=0.0,ret=0.0;
   for(int i=0;i<Total();i++)
     {
      CTrail *trail=At(i);
      if(!CheckPointer(trail))
         continue;
      if(!trail.Active())
         continue;
      int trail_target=trail.TrailTarget();
      if(mode!=trail_target)
         continue;
      val=trail.Check(symbol,type,entry_price,price,mode);
      if((type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val>ret || ret==0.0)
            ret=val;
      }      
      else if((type==ORDER_TYPE_SELL && trail_target==TRAIL_TARGET_STOPLOSS) || (type==ORDER_TYPE_BUY && trail_target==TRAIL_TARGET_TAKEPROFIT))
      {
         if(val<ret || ret==0.0)
            ret=val;
      }      
     }
   return ret;
  }

与本系列文章中使用的其它容器类似, CTrail 是 CArrayObj 的后代, 它设计用于存储多个 CObject 及其继承者的实例指针。然后, CTrails 可以存储多个 CTrail 实例。在 CTrails 中存在多个 CTrail 实例指针的情况下, 调用 Check 方法时, 将调用所有 CTrail 实例的 Check 方法。但是, 只有最接近当前市场价格的数值才会返回作为最终输出。这种行为可以通过扩展 CTrails 来更改。

类 CTrail 和 CTrails 已降格为纯粹的计算。这意味着所有的方法编码在基类 (CTrailBase) 中, 而非任何特定语言的实现。当 (由 CStop) 调用检查尾随停止的状态时, 它将获得新的停止价位数值, 且 CStop 相应地修改一笔交易的停止价位。

扩展 CTrail

CTrail 类不仅适用于尾随停止和盈亏平衡本身。它还可以用来定义一笔交易的停止价位随时间推移的几乎所有演变。该过程与本文中讨论的自定义停止位方式非常相似。不过, 通过扩展 CTrail 的方法, 令其在交易入场, 应用于修改停止价位。

示例

实例 #1: 自定义停止位

本文的第一个例子就是智能交易系统启动后使用自定义止损和止盈。我们将扩展上一篇文章中的第三个智能交易系统示例 (请参阅《跨平台智能交易系统: 停止位》), 以便创建这个智能交易系统。自定义停止价位将根据之前蜡烛的最高价和最低价计算。作为一个附加的保障措施, 我们将添加一个最低停止价位的条件: 如果计算出的止损或止盈太接近入场价格, 譬如低于 200 点, 我们将止损或止盈点数设置在距入场价格 200 点。这可以帮助确保我们的交易请求被经纪商所接受。

首先, 我们将声明一个 CStop 的子类。在这个例子中, 我们将之称为 CCustomStop。其定义如下所示:

class CCustomStop : public CStop
  {
   public:
                     CCustomStop(const string);
                    ~CCustomStop(void);
   virtual double    StopLossCustom(const string,const ENUM_ORDER_TYPE,const double);
   virtual double    TakeProfitCustom(const string,const ENUM_ORDER_TYPE,const double);
  };

此处, 我们将扩展 StopLossCustom 和 TakeProfitCustom 方法。以下代码示意 StopLossCustom 方法:

double CCustomStop::StopLossCustom(const string symbol,const ENUM_ORDER_TYPE type,const double price)
  {
   double array[1];
   double val=0;
   if(type==ORDER_TYPE_BUY)
     {
      if(CopyHigh(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(CopyLow(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   if(val>0)
     {
      double distance=MathAbs(price-val)/m_symbol.Point();
      if(distance<200)
        {
         if(type==ORDER_TYPE_BUY)
            val = price+200*m_symbol.Point();
         else if(type==ORDER_TYPE_SELL)
            val=price-200*m_symbol.Point();
        }
     }
   return val;
  }

首先, 我们根据交易的类型 (买入或卖出) 使用 CopyLow 和 CopyHigh 函数来计算止损。获得初始值后, 我们获得距入场价格距离的绝对值。如果距离小于最小值, 则将距离设置为最小值。

TakeProfitCustom 方法的过程与之类似, 如下所示:

double CCustomStop::TakeProfitCustom(const string symbol,const ENUM_ORDER_TYPE type,const double price)
  {
   double array[1];
   double val=0;
   m_symbol=m_symbol_man.Get(symbol);
   if(!CheckPointer(m_symbol))
      return 0;
   if(type==ORDER_TYPE_BUY)
     {
      if(CopyLow(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(CopyHigh(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   if(val>0)
     {
      double distance=MathAbs(price-val)/m_symbol.Point();
      if(distance<200)
        {
         if(type==ORDER_TYPE_BUY)
            val = price-200*m_symbol.Point();
         else if(type==ORDER_TYPE_SELL)
            val=price+200*m_symbol.Point();
        }
     }
   return val;
  }

默认情况下, CStop 中负责止损和止盈的类成员 (分别为 m_stoploss 和 m_takeprofit) 将被初始化为零。因此, 我们只需在 OnInit 函数中对以下行进行注释:

//main.StopLoss(stop_loss);
//main.TakeProfit(take_profit);

如果止损和止盈价位的值 (以点数为单位) 设置为零, CStop 实例才会使用自定义计算。

示例 #2: 盈亏平衡

对于盈亏平衡功能, 我们还将修改上一篇文章中第三个例子的主要头文件 (与示例 #1 相同)。但这一次, 我们不会扩展任何类对象。而是, 我们只会声明一个 CTrail 实例以及一个 CTrails 实例:

CTrails *trails = new CTrails();
CTrail *trail = new CTrail();
trail.Set(breakeven_value,breakeven_start,0);  
trails.Add(trail);
main.Add(trails);

最后一行是必不可少的。我们需要在现有的 CStop 实例中加入一个 CTrails 实例。这样, 行为将适用于 CStop 的特定实例。

CTrail 的 Set 方法的第三个参数是步长。其默认值为1 (1 点)。由于我们只使用盈亏平衡功能, 所以我们为其赋值为 0。

上面的代码需要变量 breakeven_start, 这是距入场价格的激活价位 (以点数为单位) 和 breakeven_value, 这是距激活价位开始的新止损距离 (以点数为单位)。我们将声明这两个输入参数:

input int breakeven_value = 200;
input int breakeven_start = 200;

有了这个设定, 一旦行情向有利交易方向移动至少 200 点, 止损将从激活价位移动 200 点。200 - 200 = 0, 所以计算出的新止损将是交易自身的入场价格。

示例 #3: 尾随停止

现在, 我们继续在 EA 中实现一个尾随功能。这个例子与前面的例子非常相似。回想一下, 从前面的例子中, 我们在 OnInit 函数中插入以下代码:

CTrails *trails = new CTrails();
CTrail *trail = new CTrail();
trail.Set(breakeven_value,breakeven_start,0);  
trails.Add(trail);
main.Add(trails);

当使用尾随停止替代时, 进程没有什么不同:

CTrails *trails = new CTrails();
CTrail *trail = new CTrail();
trail.Set(trail_value,trail_start,trail_step);
trails.Add(trail);   
main.Add(trails);

这次, CTrail 的 Set 方法使用三个参数。前两个与上个示例相同。第三个参数是步长, 即激活后尾随的频率。在前面的例子中, 这个方法参数的默认值为 0, 这意味着在初始激活后不会再有额外的尾随。然后, 我们在 EA 中声明输入参数 trail_value, trail_start 和 trail_step。

示例 #4: 自定义尾随

在这个示例中, 我们来实现一个自定义尾随功能。我们将扩展本文第一个示例中使用的智能交易系统。回想第一个智能交易系统示例中, 我们声明了一个自定义停止位, 根据前一根蜡烛的最高价和最低价设置止损和止盈。在本例中, 我们将扩展它, 但不仅仅是设置距入场价的止损和止盈。我们还希望通过前一根蜡烛的最高价或最低价进行交易的尾随止损。作为一项保障措施, 我们还将为每个尾随阶段实现最小止损。

为了创建这个智能交易系统, 我们首先扩展 CTrail。我们将把子类命名为 CCustomTrail。其定义如下所示:

class CCustomTrail : public CTrail
  {
public:
                     CCustomTrail(void);
                    ~CCustomTrail(void);
   virtual double    Check(const string,const ENUM_ORDER_TYPE,const double,const double,const ENUM_TRAIL_TARGET);
  };

现在, 我们来扩展 Check 方法。请注意, 在此情况下, 不检查尾随目标更佳。我们正在尾随止损, 这是 CTrail 默认瞄准的尾随目标:

double CCustomTrail::Check(const string symbol,const ENUM_ORDER_TYPE type,const double entry_price,const double price,const ENUM_TRAIL_TARGET mode)
  {
   if(!Active())
      return 0;
   if(!Refresh(symbol))
      return 0;
   double array[1];
   double val=0;
   if(type==ORDER_TYPE_BUY)
     {
      if(CopyLow(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   else if(type==ORDER_TYPE_SELL)
     {
      if(CopyHigh(symbol,PERIOD_CURRENT,1,1,array))
        {
         val=array[0];
        }
     }
   if(val>0)
     {
      double distance=MathAbs(price-val)/m_symbol.Point();
      if(distance<200)
        {
         if(type==ORDER_TYPE_BUY)
            val = m_symbol.Ask()-200*m_symbol.Point();
         else if(type==ORDER_TYPE_SELL)
            val=m_symbol.Bid()+200*m_symbol.Point();
        }
     }
   if((type==ORDER_TYPE_BUY && val<=price+10*m_symbol.Point()) || (type==ORDER_TYPE_SELL && val>=price-10*m_symbol.Point()))
      val = 0;
   return val;
  }
正如我们所见, 与 CCustomStop 中我们看到的计算方法非常相似。此外, 在代码的后面部分, 我们加入返回值检查, 以便计算超出前值 100 点 (10 点值) 的新止损。这是防止交易的止损在最近的高点或低点范围内浮动。并非基于最高/最低价上抬或下压, 我们会在超出一定数值后再设置新的止损价位 (多头持仓越高, 空头持仓越低)。

结束语

在本文中, 我们已经展示了如何在跨平台的智能交易系统中实现自定义的停止价位。并非以点值和点数来设定止损和止盈价位, 文中提出的方法引入了一种方法, 可按照图表价格表达其中所述价位。本文还展示了一种可以随着时间的推移修改止损和止盈价位的方法。

文章中使用的程序

#
名称
类型
描述
1.
breakeven_ha_ma.mqh
头文件
第一个示例中使用的主要头文件
2.
breakeven_ha_ma.mq4 智能交易系统
第一个例子中使用的主要源文件的 MQL4 版本
3. breakeven_ha_ma.mq5 智能交易系统 第一个例子中使用的主要源文件的 MQL5 版本
4. trail_ha_ma.mqh 头文件
第二个示例中使用的主要头文件
5. trail_ha_ma.mq4 智能交易系统
第二个例子中使用的主要源文件的 MQL4 版本
6. trail_ha_ma.mq5 智能交易系统
第二个例子中使用的主要源文件的 MQL5 版本
7. custom_stop_ha_ma.mqh 头文件 第三个示例中使用的主要头文件
8. custom_stop_ha_ma.mq4 智能交易系统 第三个例子中使用的主要源文件的 MQL4 版本
9. custom_stop_ha_ma.mq5 智能交易系统 第三个例子中使用的主要源文件的 MQL5 版本
10. custom_trail_ha_ma.mqh
头文件 第四个示例中使用的主要头文件
11. custom_trail_ha_ma.mq4 智能交易系统 第四个例子中使用的主要源文件的 MQL4 版本
12.
custom_trail_ha_ma.mq5 智能交易系统 第四个例子中使用的主要源文件的 MQL5 版本

文章中列出的类文件

 # 名称
类型
 描述
1.
MQLx\Base\Stop\StopBase.mqh 头文件 CStop (基类)
2.
MQLx\MQL4\Stop\Stop.mqh 头文件 CStop (MQL4 版本)
3.
MQLx\MQL5\Stop\Stop.mqh 头文件 CStop (MQL5 版本)
4. MQLx\Base\Trail\TrailBase.mqh  头文件 CTrail (基类)
5. MQLx\MQL4\Trail\Trail.mqh  头文件 CTrail (MQL4 版本)
6. MQLx\MQL5\Trail\Trail.mqh  头文件 CTrail (MQL5 版本)
7. MQLx\Base\Trail\TrailsBase.mqh  头文件 CTrails (基类)
8. MQLx\MQL4\Trail\Trails.mqh  头文件 CTrails (MQL4 版本)
9. MQLx\MQL5\Trail\Trails.mqh  头文件 CTrails (MQL5 版本)

由MetaQuotes Software Corp.从英文翻译成
原始文章: https://www.mql5.com/en/articles/3621

附加的文件 |
MQL5.zip (741.35 KB)
通用EA交易: CUnIndicator 和挂单的使用(第9部分) 通用EA交易: CUnIndicator 和挂单的使用(第9部分)

本文讲述的是通过通用的 CUnIndicator 类来操作指标,另外,还探讨了操作挂单的新方法。请注意,从这一点开始,CStrategy 项目的结构开始发生本质改变,现在所有的文件都位于一个目录中以便用户方便使用。

图形界面 XI: 集成标准图形库 (统合构建 16) 图形界面 XI: 集成标准图形库 (统合构建 16)

能够创建科学图表 (CGraphic 类) 的新版本图形库已于最近发布。创建图形界面的开发中函数库在本次更新中将引入创建图表的新版本控件。不同类型数据的可视化现在更加容易了。

自动搜索背离和趋合 自动搜索背离和趋合

本文研究各种类型背离: 简单, 隐藏, 扩展, 三重, 四重, 收敛, 以及 A, B 和 C 种类的背离。还开发了在图表上搜索并显示的通用指标。

自适应行情跟踪方法的实际评估 自适应行情跟踪方法的实际评估

本文所述交易系统的不同寻常之处主要是使用数学工具分析股票报价。系统应用了数字滤波和离散时间序列的频谱估值。策略的理论层面已描述过, 并曾创建了一款测试智能交易系统。