外汇EA编写教程:通道突破形态

概述

全球市场是卖家和买家之间的经久斗争。卖家想以更高的价格抛售以便获得更多收益, 而买家不愿意将他们辛苦赚来的钱轻易花销, 所以总想以更低的价格支付。根据经济学原理, 真正的价格要从供需平衡的角度来发现。这似乎是真的。然而, 问题在于市场是动态的, 因为供需双方的交易量不断变化。

斗争的结果导致价格波动。这些波动形成了通道, 交易者通过分析从中发现市场趋势。反过来说, 这些走势形成更高阶的波动。趋势变化的首要迹象之一是突破已形成的价格通道。

1. 策略的理论方面

沿趋势线的价格通道参照主要的图形分析形状。价格通道显示出当前趋势, 以及趋势内价格波动的幅度。根据当前的趋势, 通道可以是上升, 下降或横向 (横盘)。

MetaTrader 5 终端支持四种类型的通道。

  1. 等距通道
  2. 标准偏差通道
  3. 回归通道
  4. 安德鲁斯草叉
有关通道构造原理及其差异的更多详情, 请参阅终端帮助。在本文中, 我们仅研究通道构造的一般方面。

作为示例, 我们将分析 EURUSD M30 图表及其价格波动。

EURUSD M30 图表

将上图划分趋势, 我们可以标出三个价格通道。等距通道如下图所示。下行通道标为红线, 上行通道显示为蓝色。下行通道的绘制始于通道上边界, 基于价格波动的高点来判定趋势。下边界由平行于上边界的价格低点构建。下边界可依据最大或平均偏差绘制。上行通道的构造与此相反: 先绘制下边界, 然后是上边界。当绘制横向通道时, 我们应该注意以前的趋势, 因为横向价格波动往往是对前期走势的修正, 而在横盘期之后可能会延续。

带有价格通道的 EURUSD M30 图表。

通道交易通常采用两种策略: 通道内交易 (趋势策略) 和通道突破交易 (逆势策略)。在本文中, 我们处理通道突破策略, 这表明趋势发生了变化。

当趋势发生变化时, 价格会以从当前趋势相反的方向离开通道。如果一根蜡烛条收盘后超出其极限, 则认为通道被突破。

考虑到通道突破后, 价格会返回到边界, 之后才会朝着新的趋势方向发展。这种情形通常会导致在价格走势之前触发交易者设置的止损。为避免这种情况, 我们将在价格返回被突破的通道边界之后再入场。 

2. 自动搜索形态

为了创建一个查找形态的算法, 我们将采用 Dmitry Fedoseev 在其文章 [1] 中提出的方法。我们所采用的横向形式定义来自该文章中描述的指标。其代码应该被添加到 CChannel 类中。

所以, 我们决定在价格返回到通道边界后开仓, 而非在突破后立即开仓。在这种情况下可能会发生这种情形, 即我们等待价格返回通道时, EA 却已经在搜索新的通道。若要启用多通道的并行操作, 所创建的类将只搜索并处理一个形态。我们将所有类联合到一个数组。一旦形态进入处理, 且相应开单, 该类将被删除。因此, 通过初始化类中的之字折线指标, 我们需要调用每个类的指标。为了避免这种情况, 我们将在主程序中初始化指标, 只有指标的句柄会被传递给类。

另外, 为了避免通道重叠, 我们将在初始化期间把前一个通道突破时间传递给类。这将确保下一个类实例会在前一个通道突破之后搜索一个通道。

这个类如下所示。

class CChannel : public CObject
  {
private:
   string            s_Symbol;      // 品名
   ENUM_TIMEFRAMES   e_Timeframe;   // 时间帧
   int               i_Handle;      // 指标句柄
   datetime          dt_LastCalc;   // 最后计算的柱线
   SPeackTrough      PeackTrough[]; // 之字折线峰值数组
   int               CurCount;      // 峰值计数
   int               PreDir;        // 前一个之字折线的腿方向
   int               CurDir;        // 当前之字折线的腿方向
   int               RequiredCount; // 通道内的最小峰值数量
   double            d_Diff;        
   bool              b_FoundChannel;
   bool              b_Breaked;
   datetime          dt_Breaked;
   double            d_BreakedPrice;                     

   void              RefreshLast(datetime time,double v);
   void              AddNew(datetime time,double v,int d);
   bool              CheckForm(double base);
   double            GetRessistPrice(SPeackTrough &start_peack, datetime time);
   double            GetSupportPrice(SPeackTrough &start_peack, datetime time);
   bool              DrawChannel(MqlRates &break_bar);
   bool              DrawChannel(void);
   bool              UnDrawChannel(void);

public:
                     CChannel(int handle,datetime start_time,string symbol,ENUM_TIMEFRAMES timeframe);
                    ~CChannel();
   bool              Calculate(ENUM_ORDER_TYPE &type,double &stop_loss,datetime &deal_time,bool &breaked,datetime &breaked_time);
  };

在类的初始化函数中将传递以下信息参数: 指标句柄, 通道搜索开始时间, 品名和工作时间帧。在函数实体中, 传递的数据被保在相应的变量, 并为其它变量分配初始值。

CChannel::CChannel(int handle,datetime start_time,string symbol,ENUM_TIMEFRAMES timeframe) : RequiredCount(4),
                                                                                             CurCount(0),
                                                                                             CurDir(0),
                                                                                             PreDir(0),
                                                                                             d_Diff(0.1),
                                                                                             b_Breaked(false),
                                                                                             dt_Breaked(0),
                                                                                             b_FoundChannel(false)
  {
   i_Handle=handle;
   dt_LastCalc=fmax(start_time-1,0);
   s_Symbol=symbol;
   e_Timeframe=timeframe;
  }

UnDrawChannel 函数在类的 deinitialization 函数中调用。它从图表中删除先前添加的图形对象。

主要操作在 Calculate 函数中执行。其参数包括用来写入有关通道突破和由该形态开单交易信息的变量引用。在参数中使用引用, 能够从函数返回多个变量的值。

在函数开始时加载峰值数组, 品种的报价始自于最后保存的峰值。如果加载所需的报价失败, 则该函数返回 false。

bool CChannel::Calculate(ENUM_ORDER_TYPE &type,double &stop_loss,datetime &deal_time, bool &breaked,datetime &breaked_time)
  {
   MqlRates rates[];
   CurCount=ArraySize(PeackTrough);
   if(CurCount>0)
     {
      dt_LastCalc=PeackTrough[CurCount-1].Bar;
      CurDir=PeackTrough[CurCount-1].Dir;
     }
   int total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc-PeriodSeconds(e_Timeframe),0),TimeCurrent(),rates);
   if(total<=0)
      return false;

 之后我们初始化返回变量。

   stop_loss=-1;
   breaked=b_Breaked;
   breaked_time=dt_Breaked;
   deal_time=0;

之后, 开始循环处理每根柱线的数据。首先检查之字折线出现的新峰值。如果出现新的峰值或前一个峰值被重绘, 则使用 RefreshLast 和 AddNew 函数将数据保存到数组中。

   for(int i=0;i<total;i++)
     {
      if(rates[i].time>dt_LastCalc)
        {
         dt_LastCalc=rates[i].time;
         PreDir=CurDir;
        }
      else
         continue;

      // 新的最大值      

      double lhb[2];
      if(CopyBuffer(i_Handle,4,total-i-1,2,lhb)<=0)
         return false;

      if(lhb[0]!=lhb[1])
        {
         if(CurDir==1)
            RefreshLast(rates[i].time,rates[i].high);
         else
            AddNew(rates[i].time,rates[i].high,1);
        }

      // 新的最小值

      double llb[2];
      if(CopyBuffer(i_Handle,5,total-i-1,2,llb)<=0)
         return false;

      if(llb[0]!=llb[1])
        {
         if(CurDir==-1)
            RefreshLast(rates[i].time,rates[i].low);
         else
            AddNew(rates[i].time,rates[i].low,-1);
        }

下一步是检查识别通道所需的最小峰值数额是否已经形成了。如果是, 那么我们检查当前价格走势是否与通道形式相对应。该检查在 CheckForm 函数中执行。

如果它们相对应, 则给 b_FoundChannel 变量分配 true。否则, 从峰值列表中舍弃最旧的峰值, 给变量分配初始值, 且操作返回到循环的开始处。

      double base=(CurCount>=2 ? MathAbs(PeackTrough[1].Val-PeackTrough[0].Val) : 0);
   
      if(CurCount>=RequiredCount && !b_FoundChannel)
        {
         if(CurDir!=PreDir)
           {
            if(CheckForm(base))
              {
               b_FoundChannel=true;
              }
            else
              {
               UnDrawChannel();
               dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe);
               ArrayFree(PeackTrough);
               CurCount=0;
               CurDir=0;
               PreDir=0;
               b_Breaked=false;
               dt_Breaked=0;
               b_FoundChannel=false;
               deal_time=0;
               total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates);
               i=-1;
               continue;
              }
           }
        }

发现通道后, 搜索突破。如果通道突破, 给变量 b_Breaked 和 breaked 分配 true 值。突破蜡烛条的开盘时间保存到变量 dt_Breaked 和 breaked_time, 蜡烛条的极值保存到 d_BreakedPrice。然后调用 DrawChannel 函数在图表上绘制通道和突破点。请注意, 该函数将搜索与当前趋势方向相反的突破。如果趋势加剧且价格沿当前趋势方向离开通道, 将创建该类的新实例并初始化, 以便搜索通道 (请参阅下面的全局 SearchNewChannel 函数)。

一旦发现突破, 我们继续搜索入场形态。如果价格突破通道并返回到边界, 则生成入场信号。另外一个进场信号是蜡烛条收盘价高于突破蜡烛条极值为买入交易, 低于则为卖出交易。如果价格突破强走势通道并且在没有任何修正的情况更进一步移动, 则该形态用于进场。

当信号产生时, 我们将所需的订单类型写入 ‘type’ 变量, 计算止损位并将其保存到相应的变量。出现信号的柱线其开始时间将会写入 deal_time 变量。

      if(b_FoundChannel)
        {
         if(PeackTrough[0].Dir==1)
           {
            if(PeackTrough[0].Val>PeackTrough[2].Val)
              {
               if(!b_Breaked)
                 {
                  if((rates[i].close-GetRessistPrice(PeackTrough[0],rates[i].time))>=(d_Diff*base))
                    {
                     b_Breaked=breaked=true;
                     dt_Breaked=breaked_time=rates[i].time;
                     d_BreakedPrice=rates[i].high;
                     DrawChannel(rates[i]);
                     continue;
                    }
                  if(CurCount>4 && PeackTrough[CurCount-1].Dir==1 && (GetRessistPrice(PeackTrough[1],rates[i].time)-PeackTrough[CurCount-1].Val)>0)
                    {
                     int channels=ArraySize(ar_Channels);
                     if(ar_Channels[channels-1]==GetPointer(this))
                       {
                        SearchNewChannel(PeackTrough[CurCount-3].Bar-PeriodSeconds(e_Timeframe));
                       }
                    }
                 }
               else
                 {
                  if(rates[i].time<=dt_Breaked)
                     continue;
                  //---
                  double res_price=GetRessistPrice(PeackTrough[0],rates[i].time);
                  if(((rates[i].low-res_price)<=0 && (rates[i].close-res_price)>0 && (rates[i].close-res_price)<=(d_Diff*base)) || rates[i].close>d_BreakedPrice)
                    {
                     type=ORDER_TYPE_BUY;
                     stop_loss=res_price-base*(1+d_Diff);
                     deal_time=rates[i].time;
                     return true;
                    }
                 }
              }
            else
              {
               UnDrawChannel();
               dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe);
               ArrayFree(PeackTrough);
               CurCount=0;
               CurDir=0;
               PreDir=0;
               b_Breaked=false;
               dt_Breaked=0;
               b_FoundChannel=false;
               deal_time=0;
               total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates);
               i=-1;
               continue;
              }
           }
         else
           {
            if(PeackTrough[0].Val<PeackTrough[2].Val)
              {
               if(!b_Breaked)
                 {
                  if((GetSupportPrice(PeackTrough[0],rates[i].time)-rates[i].close)>=(d_Diff*base))
                    {
                     b_Breaked=breaked=true;
                     dt_Breaked=breaked_time=rates[i].time;
                     d_BreakedPrice=rates[i].low;
                     DrawChannel(rates[i]);
                     continue;
                    }
                  if(CurCount>4 && PeackTrough[CurCount-1].Dir==-1 && (PeackTrough[CurCount-1].Val-GetSupportPrice(PeackTrough[1],rates[i].time))>0)
                    {
                     int channels=ArraySize(ar_Channels);
                     if(ar_Channels[channels-1]==GetPointer(this))
                       {
                        SearchNewChannel(PeackTrough[CurCount-3].Bar-PeriodSeconds(e_Timeframe));
                       }
                    }
                 }
               else
                 {
                  if(rates[i].time<=dt_Breaked)
                     continue;
                  double sup_price=GetSupportPrice(PeackTrough[0],rates[i].time);
                  if(((sup_price-rates[i].high)<=0 && (sup_price-rates[i].close)>0 && (sup_price-rates[i].close)<=(d_Diff*base)) || rates[i].close<d_BreakedPrice)
                    {
                     type=ORDER_TYPE_SELL;
                     stop_loss=sup_price+base*(1+d_Diff);
                     deal_time=rates[i].time;
                     return true;
                    }
                 }
              }
            else
              {
               UnDrawChannel();
               dt_LastCalc=PeackTrough[0].Bar+PeriodSeconds(e_Timeframe);
               ArrayFree(PeackTrough);
               CurCount=0;
               CurDir=0;
               PreDir=0;
               b_Breaked=false;
               dt_Breaked=0;
               b_FoundChannel=false;
               deal_time=0;
               total=CopyRates(s_Symbol,e_Timeframe,fmax(dt_LastCalc,0),TimeCurrent(),rates);
               i=-1;
               continue;
              }
           }
        }
     }
   return b_Breaked;
  }

CChannel 类的完整代码及其函数如下所示。

3. 为策略测试创建智能交易系统

现在我们已经创建了一个通道搜索类, 我们需要测试我们的策略。我们创建一款智能交易系统来测试策略。我们使用通用之字折线指标搜索通道, 该指标在相关文章中进行了描述 [3]。此即为什么我们需要下载并重新编译这个指标。为了方便起见, 我已将它添加到资源列表中。这种方法可以在终端之间转移智能交易系统, 而无需转移指标。我还在 EA 中包含了 CChannel 类和一个执行交易操作的标准类 – CTrade。

#resource "//Indicators//ZigZags//iUniZigZagSW.ex5"
#include <//Break_of_channel_DNG//Channel.mqh>
#include <Trade//Trade.mqh>

智能交易系统参数将与指标的参数相同。

input ESorce               SrcSelect      =  Src_HighLow;
input EDirection           DirSelect      =  Dir_NBars;
input int                  RSIPeriod      =  14;
input ENUM_APPLIED_PRICE   RSIPrice       =  PRICE_CLOSE;
input int                  MAPeriod       =  14;
input int                  MAShift        =  0;
input ENUM_MA_METHOD       MAMethod       =  MODE_SMA;
input ENUM_APPLIED_PRICE   MAPrice        =  PRICE_CLOSE;
input int                  CCIPeriod      =  14;
input ENUM_APPLIED_PRICE   CCIPrice       =  PRICE_TYPICAL;
input int                  ZZPeriod       =  50;

EA 有四个全局变量。在这些变量中写入以下内容:

  • 指标句柄,
  • 指向通道的指针数组 (CChannel 类的对象),
  • 一个指向 CTrade 类的指针 (它用于执行交易操作),
  • 柱线的开盘时间, 最后一次突破发生的时间。
int         zz_handle;
CChannel   *ar_Channels[];
CTrade     *Trade;
datetime    dt_last_break;

在 EA 的 OnInit 函数中, 我们调用指标并初始化所需的类。如果发生错误, 函数应返回 INIT_FAILED。

int OnInit()
  {
//---
   zz_handle=iCustom(Symbol(),Period(),"::Indicators//ZigZags//iUniZigZagSW",SrcSelect,
                                             DirSelect,
                                             RSIPeriod,
                                             RSIPrice,
                                             MAPeriod,
                                             MAShift,
                                             MAMethod,
                                             MAPrice,
                                             CCIPeriod,
                                             CCIPrice,
                                             ZZPeriod);
                                             
   if(zz_handle==INVALID_HANDLE){
      Alert("加载指标出错");
      return(INIT_FAILED);
   }  
//---
   Trade=new CTrade();
   if(CheckPointer(Trade)==POINTER_INVALID)
      return INIT_FAILED;
//---
   dt_last_break=0;
//---
   return(INIT_SUCCEEDED);
  }

为了清理内存, 我们删除 OnDeinit 函数中所有用到的类实例。

void OnDeinit(const int reason)
  {
//---
   int total=ArraySize(ar_Channels);
   for(int i=0;i<total;i++)
     {
      if(CheckPointer(ar_Channels[i])!=POINTER_INVALID)
         delete ar_Channels[i];
     }
   ArrayFree(ar_Channels);
   if(CheckPointer(Trade)!=POINTER_INVALID)
      delete Trade;
  }

主要工作在 OnTick 函数中执行。

我们已经决定, 如果蜡烛条收盘价超出限制, 则应认为通道被突破。通道将根据完整形成的之字折线峰值绘制。所以, EA 不必在每次逐笔报价上执行操作。因此, 该函数首先要检查一根新柱线开盘。

void OnTick()
  {
//---
   static datetime last_bar=0;
   if(last_bar>=SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE))
      return;
   last_bar=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);

请注意, last_bar 变量仅用于此代码块, 这就是为什么不声明为全局的原因。如您所知, 相应函数每次启动后都会执行所有局部变量的初始化。这就是为什么保存在变量中的数据在下一次 OnTick 启动时丢失。为了避免数据丢失, 使用静态修饰符声明该变量。该变量将在之后的函数启动过程中保留其值。

下一步是确定数组中存储了多少个通道。如果没有通道, 则从最后保存的突破开始搜索。

   int total=ArraySize(ar_Channels);
   if(total==0)
      if(SearchNewChannel(dt_last_break))
         total++;

之后, 我们在循环中处理每个保存的通道。首先, 检查指向类对象的指针。如果指针不正确, 我们将它从数组中删除并移到下一个。

   for(int i=0;i<total;i++)
     {
      if(CheckPointer(ar_Channels[i])==POINTER_INVALID)
        {
         DeleteChannel(i);
         i--;
         total--;
         continue;
        }

然后调用该类的 Calculate 函数。它的参数是变量引用, 函数将返回有关操作执行结果的信息。我们需要在函数调用之前声明这些变量。此外, 该函数返回一个布尔值。因此, 我们可以将该函数作为 ‘if’ 语句的逻辑表达式来调用, 并且只有在函数成功时才会执行进一步的操作。

      ENUM_ORDER_TYPE type;
      double stop_loss=-1;
      bool breaked=false;
      datetime breaked_time=0;
      datetime deal_time=0;
      if(ar_Channels[i].Calculate(type,stop_loss,deal_time,breaked,breaked_time))
        {

函数成功执行之后, 重新保存最后一次通道突破发生时的柱线开盘时间。

         dt_last_break=fmax(dt_last_break,breaked_time);

如果上次保存的通道已突破, 初始化并搜索最后一次突破后形成的新通道。

         if(breaked && i==(total-1))
            if(SearchNewChannel(breaked_time))
              { 
               if(total>=5)
                  i--;
               else
                  total++;
              }

请注意, SearchNewChannel 函数存储最后五个通道。因此, 如果数组中少于 5 个通道, 则 ‘total’ 变量的值只会增加。否则, 变量减少, 它表示正在处理的通道索引。

然后, 我们检查信号的出现以便开仓, 并在需要时发送相应的订单。智能交易系统仅用于测试目的。这就是为什么它没有资金管理模块, 所有的交易均以最小手数开仓。发送订单后, 应该删除已处理的通道。

         if(deal_time>=0 && stop_loss>=0)
           {
            int bars=Bars(_Symbol,PERIOD_CURRENT,deal_time,TimeCurrent());
            double lot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
            switch(type)
              {
               case ORDER_TYPE_BUY:
                 if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
                    Trade.PositionClose(_Symbol);
                 if(bars<=2)
                    Trade.Buy(lot,_Symbol,0,fmax(stop_loss,0));
                 break;
               case ORDER_TYPE_SELL:
                 if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
                    Trade.PositionClose(_Symbol);
                 if(bars<=2)
                    Trade.Sell(lot,_Symbol,0,fmax(stop_loss,0));
                 break;
              }
            DeleteChannel(i);
            i--;
            total--;
           }
        }
     }
  }


请注意这个程序代码块中的两个重点。

1. 仅当信号出现的时间不早于前一根蜡烛时才会开单。由于智能交易系统可以处理历史数据 (例如, 在初始化期间或终端与服务器断开连接之后), 因此添加了此限制。在这种情况下, 信号可能会出现延迟, 新的交易可能导致不可控的损失。

2. 智能交易系统开单时会带止损位, 但并未指定止盈位。所以, 当信号出现时, 若有必要, 请将反向持仓平仓。

代码中还使用了两个辅助函数 SearchNewChannel 和 DeleteChannel。

SearchNewChannel 函数在通道数组中初始化一个新的 CChannel 类实例。在函数的开始处, 我们检查指标句柄。如果句柄不正确, 则以 ‘false’ 结果退出该函数。

bool SearchNewChannel(datetime time)
  {
   if(zz_handle==INVALID_HANDLE)
      return false;

在创建智能交易系统时, 我决定使用最后五个通道。此即为什么下一步要检查存储在数组中的通道数量, 并在必要时删除最旧的通道。剩下的四个通道被移到数组的开始位置。

   int total=ArraySize(ar_Channels);
   if(total>4)
     {
      for(int i=0;i<total-4;i++)
        {
         if(CheckPointer(ar_Channels[i])!=POINTER_INVALID)
            delete ar_Channels[i];
        }
      for(int i=0;i<4;i++)
         ar_Channels[i]=ar_Channels[total-4+i];
      if(total>5)
        {
         if(ArrayResize(ar_Channels,5)>0)
            total=5;
         else
            return false;
        }
     }

如果少于五个通道, 则数组增加。

   else
     {
      if(ArrayResize(ar_Channels,total+1)>0)
         total++;
      else
         return false;
     }

在函数的最后, 我们在数组的最后一个单元中初始化一个新的 CChannel 类实例。

   ar_Channels[total-1]=new CChannel(zz_handle,time,_Symbol,PERIOD_CURRENT);
   return (CheckPointer(ar_Channels[total-1])!=POINTER_INVALID);
  }

DeleteChannel 函数从数组中删除指定索引处的 CChannel 类实例。在函数的开始处, 我们检查索引是否在现有数组中。如果不是, 则以 ‘false’ 结果退出函数。

bool DeleteChannel(int pos)
  {
   int total=ArraySize(ar_Channels);
   if(pos<0 || pos>=total)
      return false;

然后删除指定的对象, 将其余对象移动到下面的一个单元格中。

   delete ar_Channels[pos];
   for(int i=pos;i<total-1;i++)
      ar_Channels[i]=ar_Channels[i+1];

如果该数组在函数开始前只有一个对象, 则数组将被释放。否则它会减少一个元素。

   if(total==1)
     {
      ArrayFree(ar_Channels);
      return true;
     }
   return (ArrayResize(ar_Channels,total-1)>0);
  }

下面附有智能交易系统的完整代码。

4. 测试智能交易系统

4.1. 时间帧为 H1

相信此策略在较高的时间帧下效果更好, 因为在这些时间帧内走势更加静态, 且较少受到意外噪音的影响。因此, 第一次测试是在 H1 时间帧上进行的。测试针对 2017 年 EURUSD 数据进行, 未对参数进行初步优化。

智能交易系统在 H1 时间帧上进行测试。智能交易系统的测试参数。

第一次测试显示该策略能够获利。EA 在测试期间仅开仓 10 次, 进行了 26 笔交易。80% 的仓位平仓时有盈利。余额增长很平稳。根据测试结果, 盈利因子为 4.06。这是一个良好的结果。

在 Н1 时间帧上的测试结果。

但每年仅有 10 次开仓是不够的。为了增加交易数量, 我决定在较小的时间帧内测试 EA, 而不改变其参数。

4.2. M15 时间帧

第二次测试是在 M15 时间帧上用相同的参数进行的。

智能交易系统在 M15 时间帧上进行测试。智能交易系统的测试参数。

交易数量有所增加。EA 在测试期间进行了 63 笔交易。但是这种增加并没有产生定性结果。所有操作的总利润为 130.60 美元, 而 H1 为 133.46 美元。盈利交易的份额几乎减少了两倍, 达到 41.27%。由此产生的余额图表更加破碎, 盈利因子为 1.44, 与前一次测试相比几乎减少三倍。

在 M15 时间帧上的测试结果。

4.3. 在其它品种上测试

测试结果显示该策略在 H1 时间帧表现更好。为了评估其它时间帧下可能的策略运用情况, 我另外进行了三项测试。我使用了 H1 时间帧, 相同的参数和测试区间。完整的测试结果可在附件中找到, 主要数据如下表所示。

品种 交易数量 成交数量 盈利交易, % 盈利因子 恢复因子 平均持仓时间, 小时 
EURUSD 10 26 80 4.06 1.78 552 
GBPUSD 2 8 50 1.47 0.23 2072 
EURGBP 5 14 0 0.0 -0.71 976 
USDJPY 6 17 83 0.72 -0.19 875 

EURGBP 货币对得到的结果最糟糕。5 笔交易当中没有一笔盈利平仓。但如果我们分析价格图表, 我们可以看到入场后的潜在盈亏与策略相一致。从以下屏幕截图可以看出, 通道突破策略会生成很好的入场信号。但它需要一个相应的离场策略才能实现更稳定的操作。这可通过持仓时间来确认。测试显示, 平均持仓时间为 550 至 2100 小时, 具体还要取决于品种。市场趋势在如此长的时期内可能会发生多次变化。

EA 在 EURGBP 图表上进行的交易示例。

结束语

智能交易系统依据本文介绍的通道突破形态进行交易的一个示例。测试结果表明, 该策略可以用作入场信号的生成器。此外, 测试证实, 策略在较高的时间帧内效果更佳。然而, 为了增加策略成功率, 应该添加持仓离场信号。该策略产生准确但罕见的入场信号, 只是这些信号不足以及时获取利润。这往往会导致浮盈甚至本金转为亏损。

该智能交易系统没有资金管理模块或检查错误, 而在计算和交易操作中这很可能发生。因此, 该 EA 不建议用于实盘账户。不过, 任何人都可以为其添加必要的功能。

参考

  1. 旗形形态
  2. HTML 格式的图形和图解
  3. 通用之字折线

本文中使用的程序:

#  名称 类型  描述 
1 Break_of_channel_DNG.mq5  智能交易系统  用于测试策略的智能交易系统
 2 Channel.mqh  类库  搜索价格通道和开仓信号的类
 3 Break_of_channel_DNG.mqproj    项目描述文件
 4 iUniZigZagSW.ex5  指标  通用之字折线
 5 Reports.zip Zip 存档  智能交易系统测试报告

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/4267

附加的文件 |

MQL5.zip
(204.37 KB)
Reports.zip
(241.95 KB)

 

 


MyFxtop迈投(www.myfxtop.com)-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投