下载MetaTrader 5

MQL5 Cookbook: 在EA交易中使用指标来设置交易条件

29 十月 2013, 10:50
Anatoli Kazharski
0
1 330

简介

在本文中,我们将增强EA交易,使用指标值来检查建仓条件。为了增加点乐趣,我们会在外部参数中创建一个下拉列表,可以从三个交易指标中选择一个。
这里提醒一下:我们会继续修改MQL5 Cookbook系列中前文里的EA交易,最新版本的EA交易可以从叫做"MQL5 Cookbook: 交易历史和取得仓位属性的函数库"一文中下载。

另外,本文中我们会特别创建一个函数来检查交易操作是否能够进行,建仓函数将被修改,会让EA交易决定交易模式(即时执行市场执行)。

因为EA交易的代码在前面的文章中经过增强和提高以后已经超过了1500行,每个新功能增加以后都会变得越来越不方便,所以,逻辑上的解决方案是把它分成几类,分别作为独立的库文件。现在,目标已经设定,让我们开始吧。

 

EA 交易开发

我们把前文中EA交易的源代码文件(*.mq5)放到一个独立的文件夹中,TestIndicatorConditions,我们还需要在其中创建Include子文件夹,在这个文件夹中我们会创建包含文件 (*.mqh)。他们可以用MQL5向导来生成(Ctrl+N) 或者人工创建,在所需目录中创建标准文本文件(*.txt) 然后重命名为 *.mqh。

以下是所有创建的包含文件的名称和注释:

  • Enums.mqh 将包含所有的枚举;
  • InfoPanel.mqh 将包含设置信息面板,以及创建和删除图形对象的函数;
  • Errors.mqh 将包含所有返回错误代码和去初始化原因的函数;
  • TradeSignals.mqh 包含用于把价格和指标值复制到数组的函数,以及信号模块;
  • TradeFunctions.mqh 将包含交易函数;
  • ToString.mqh 将包含用于把数字值转换为字符串的函数;
  • Auxiliary.mqh 将用于其它的辅助函数.

为了把这些函数库包含到主文件中,我们使用#include指令。因为EA交易的主文件和包含文件夹 (Include) 在同一个文件夹中,包含文件的代码将如下:

//--- 包含自定义库
#include "Include\Enums.mqh"
#include "Include\InfoPanel.mqh"
#include "Include\Errors.mqh"
#include "Include\TradeSignals.mqh"
#include "Include\TradeFunctions.mqh"
#include "Include\ToString.mqh"
#include "Include\Auxiliary.mqh"

然后,我们就可以打开和修改这些文件,把EA交易主文件的部分代码转移到其中了。

为了正确浏览代码, 需要在每个头文件中增加对邻近头文件以及EA交易的主文件的引用,比如,我们的交易函数库,TradeFunctions.mqh, 看起来是这样的:

//--- 连接EA交易的主文件
#include "..\TestIndicatorConditions.mq5"
//--- 包含自定义库
#include "Enums.mqh"
#include "InfoPanel.mqh"
#include "Errors.mqh"
#include "TradeSignals.mqh"
#include "ToString.mqh"
#include "Auxiliary.mqh"

作为同一层目录中的文件,只需要指定文件名称就足够了,为了到上一层目录,您需要在文件路径的反斜杠之前加上两个点。

让我们在Enums.mqh文件中增加指标的枚举。为了指导的目的,在这个EA交易中我们将使用两个标准指标(移动平均(MV)商品通道指数(CCI)) 和一个自定义指标 (MultiRange_PCH)。枚举定义如下:

//--- 指标
enum ENUM_INDICATORS
  {
   MA       = 0, // 移动平均
   CCI      = 1, // CCI
   PCH      = 2  // 价格通道
  };

外部参数修改如下:

//--- EA交易的外部参数
sinput   long              MagicNumber=777;        // 幻数
sinput   int               Deviation=10;           // 滑点
input    ENUM_INDICATORS   Indicator=MA;           // 指标
input    int               IndicatorPeriod=5;      // 指标周期数
input    int               IndicatorSegments=2;    // 一个方向上的指标段数
input    double            Lot=0.1;                // 手数
input    double            VolumeIncrease=0.1;     // 仓位交易量增加值
input    double            VolumeIncreaseStep=10;  // 交易量增加步长
input    double            StopLoss=50;            // 止损
input    double            TakeProfit=100;         // 获利
input    double            TrailingStop=10;        // 跟踪止损
input    bool              Reverse=true;           // 仓位反转
sinput   bool              ShowInfoPanel=true;     // 是否显示信息面板

如上文所述,您将可以从指标参数的下拉列表中从三个指标中选择一个。

还有一个应用于所有指标的参数 - IndicatorPeriod,它用于设置指标周期数。来自以往版本EA交易的NumberOfBars 参数已经被重命名为IndicatorSegments,现在用于说明指定的指标必须向上/向下以满足建仓条件的柱数。

进而,我们还增加了另外一个外部参数,VolumeIncreaseStep,它可以用于设置交易量增加点数的步长。

AllowedNumberOfBars 变量的值(现在是AllowedNumberOfSegments)将在 GetBarsData()自定义函数中做调整,它现在被放到一个独立的函数中并且只是在初始化时被调用。

因为仓位建仓条件现在是使用指标值做检查,其赋值总是大于2。换句话说,如果IndicatorSegments外部变量被赋值为1,AllowedNumberOfSegments变量将赋值为3,因为为了满足条件 (比如买入) ,在已完成柱的指标值必须比前一柱大。为了这个目的,我们需要读取最后三个指标值。

以下是CorrectInputParameters() 函数的代码:

//+------------------------------------------------------------------+
//| 调整输入参数                                                       |
//+------------------------------------------------------------------+
void CorrectInputParameters()
  {
//--- 调整开启仓位条件中柱的数量
   if(AllowedNumberOfSegments<=0)
     {
      if(IndicatorSegments<=1)
         AllowedNumberOfSegments=3;                     // 至少需要3个柱
      if(IndicatorSegments>=5)
         AllowedNumberOfSegments=5;                     // 但是不能超过7
      else
         AllowedNumberOfSegments=IndicatorSegments+1;   // 总是大于2
     }
  }

在我们处理指标之前,我们创建一个函数来检查是否允许交易 - CheckTradingPermission()。如果因为函数中列出的任何原因不允许交易,将会返回0值,这说明下一次尝试必须在下一个柱进行。

//+------------------------------------------------------------------+
//| 检查是否允许交易                                                    |
//+------------------------------------------------------------------+
bool CheckTradingPermission()
  {
//--- 对于实时模式
   if(IsRealtime())
     {
      //--- 检查服务器连接
      if(!TerminalInfoInteger(TERMINAL_CONNECTED))
         return(1);
      //--- 在运行程序级别上的交易许可
      if(!MQL5InfoInteger(MQL5_TRADE_ALLOWED))
         return(2);
      //--- 终端级别上的交易许可
      if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
         return(3);
      //--- 当前账户的交易许可
      if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))
         return(4);
      //--- 当前账户的自动交易许可
      if(!AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
         return(5);
     }
//---
   return(0);
  }

让我们现在开始文章的重点部分,为了访问指标值,我们需要获得它的句柄,这可以通过使用特别函数做到,它们的名字由指标的短名称和之前的'i'字符组成。

例如,移动平均(MA)指标有对应的iMA()函数,所有MetaTrader 5终端中的标准指标句柄可以通过这些函数获得,完整的列表在MQL5参考的技术指标部分。如果您需要取得一个自定义指标的句柄,使用iCustom()函数.

我们将实现GetIndicatorHandle()函数,根据在指标参数中所选择的指标,对应指标的句柄将被赋予indicator_handle全局变量。此函数的代码可以在我们交易信号函数库(\Include\TradeSignals.mqh文件)中找到,指标句柄的变量位于EA交易的主文件中。

//+------------------------------------------------------------------+
//| 取得指标句柄                                                       |
//+------------------------------------------------------------------+
void GetIndicatorHandle()
  {
//--- 如果选择了移动平均指标
   if(Indicator==MA)
      indicator_handle=iMA(_Symbol,Period(),IndicatorPeriod,0,MODE_SMA,PRICE_CLOSE);
//--- 如果选择了CCI指标
   if(Indicator==CCI)
      indicator_handle=iCCI(_Symbol,Period(),IndicatorPeriod,PRICE_CLOSE);
//--- 如果选择了MultiRange_PCH指标
   if(Indicator==PCH)
      indicator_handle=iCustom(_Symbol,Period(),"MultiRange_PCH",IndicatorPeriod);
//--- 如果指标句柄无法获得
   if(indicator_handle==INVALID_HANDLE)
      Print("获取指标句柄失败!");
  }

下一步,我们创建GetDataIndicators() 函数使用这些获得的指标句柄, 然后我们可以获得它们的值。这可以使用CopyBuffer()函数做到,与文章"MQL5 Cookbook: 在MetaTrader 5策略测试器中分析仓位属性"中使用 CopyTime(), CopyClose(), CopyOpen(), CopyHigh() 和CopyLow() 函数读取柱值方式类似。

因为指标可能含有几个缓冲区(几行数值),缓冲区索引作为第二个参数传给CopyBuffer()函数。标准指标的缓冲区索引可以在MQL5参考中找到。对于自定义指标,如果提供了源代码,缓冲区索引可以在代码中找到。如果没有代码,您需要通过试验,在策略测试器的可视化模式下观察条件如何被满足,然后再找到索引。

在那之前,我们需要在EA交易的主文件中为指标缓冲区值创建动态数组:

//--- 指标值数组
double indicator_buffer1[];
double indicator_buffer2[];

GetIndicatorsData() 的代码在下面提供:

//+------------------------------------------------------------------+
//| 取得指标值                                                         |
//+------------------------------------------------------------------+
bool GetIndicatorsData()
  {
//--- 如果已经获得了指标句柄
   if(indicator_handle!=INVALID_HANDLE)
     {
      //--- 对于移动平均或者CCI指标
      if(Indicator==MA || Indicator==CCI)
        {
         //--- 反转索引顺序 (... 3 2 1 0)
         ArraySetAsSeries(indicator_buffer1,true);
         //--- 取得指标值
         if(CopyBuffer(indicator_handle,0,0,AllowedNumberOfSegments,indicator_buffer1)<AllowedNumberOfSegments)
           {
            Print("复制数值失败 ("+
                  _Symbol+"; "+TimeframeToString(Period())+") (至 indicator_buffer1 数组)错误 ("+
                  IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError()));
            return(false);
           }
        }
      //--- 对于 MultiRange_PCH 指标
      if(Indicator==PCH)
        {
         //--- 反转索引顺序 (... 3 2 1 0)
         ArraySetAsSeries(indicator_buffer1,true);
         ArraySetAsSeries(indicator_buffer2,true);
         //--- 取得指标值
         if(CopyBuffer(indicator_handle,0,0,AllowedNumberOfSegments,indicator_buffer1)<AllowedNumberOfSegments || 
            CopyBuffer(indicator_handle,1,0,AllowedNumberOfSegments,indicator_buffer2)<AllowedNumberOfSegments)
           {
            Print("复制数值失败 ("+
                  _Symbol+"; "+TimeframeToString(Period())+") (至 indicator_buffer1 或 indicator_buffer2 数组)错误 ("+
                  IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError()));
            return(false);
           }
        }
      //---
      return(true);
     }
//--- 如果指标句柄没有获得,重试
   else
      GetIndicatorHandle();
//---
   return(false);
  }

GetTradingSignal()函数已经从本质上改变了,在有无持仓的情况下,条件都已改变。对于移动平均CCI指标,条件是相同的。对于MultiRange_PCH指标,它们被分配到不同的区块中。为了使程序有更好的可读性以及避免重复,我们创建了一个辅助函数,GetSignal(),它返回开启仓位信号,或者在已有持仓并且外部参数允许仓位反转的条件下返回相反信号。

以下是GetSignal() 函数的代码:

//+------------------------------------------------------------------+
//| 检查条件并返回信号                                                  |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE GetSignal()
  {
//--- 检查针对移动平均和CCI指标的条件
   if(Indicator==MA || Indicator==CCI)
     {
      //--- 卖出信号
      if(AllowedNumberOfSegments==3 && 
         indicator_buffer1[1]<indicator_buffer1[2])
         return(ORDER_TYPE_SELL);
      //---
      if(AllowedNumberOfSegments==4 && 
         indicator_buffer1[1]<indicator_buffer1[2] && 
         indicator_buffer1[2]<indicator_buffer1[3])
         return(ORDER_TYPE_SELL);
      //---
      if(AllowedNumberOfSegments==5 && 
         indicator_buffer1[1]<indicator_buffer1[2] && 
         indicator_buffer1[2]<indicator_buffer1[3] && 
         indicator_buffer1[3]<indicator_buffer1[4])
         return(ORDER_TYPE_SELL);
      //---
      if(AllowedNumberOfSegments==6 && 
         indicator_buffer1[1]<indicator_buffer1[2] && 
         indicator_buffer1[2]<indicator_buffer1[3] && 
         indicator_buffer1[3]<indicator_buffer1[4] && 
         indicator_buffer1[4]<indicator_buffer1[5])
         return(ORDER_TYPE_SELL);
      //---
      if(AllowedNumberOfSegments>=7 && 
         indicator_buffer1[1]<indicator_buffer1[2] && 
         indicator_buffer1[2]<indicator_buffer1[3] && 
         indicator_buffer1[3]<indicator_buffer1[4] && 
         indicator_buffer1[4]<indicator_buffer1[5] && 
         indicator_buffer1[5]<indicator_buffer1[6])
         return(ORDER_TYPE_SELL);

      //--- 买入信号
      if(AllowedNumberOfSegments==3 && 
         indicator_buffer1[1]>indicator_buffer1[2])
         return(ORDER_TYPE_BUY);
      //---
      if(AllowedNumberOfSegments==4 && 
         indicator_buffer1[1]>indicator_buffer1[2] && 
         indicator_buffer1[2]>indicator_buffer1[3])
         return(ORDER_TYPE_BUY);
      //---
      if(AllowedNumberOfSegments==5 && 
         indicator_buffer1[1]>indicator_buffer1[2] && 
         indicator_buffer1[2]>indicator_buffer1[3] && 
         indicator_buffer1[3]>indicator_buffer1[4])
         return(ORDER_TYPE_BUY);
      //---
      if(AllowedNumberOfSegments==6 && 
         indicator_buffer1[1]>indicator_buffer1[2] && 
         indicator_buffer1[2]>indicator_buffer1[3] && 
         indicator_buffer1[3]>indicator_buffer1[4] && 
         indicator_buffer1[4]>indicator_buffer1[5])
         return(ORDER_TYPE_BUY);
      //---
      if(AllowedNumberOfSegments>=7 && 
         indicator_buffer1[1]>indicator_buffer1[2] && 
         indicator_buffer1[2]>indicator_buffer1[3] && 
         indicator_buffer1[3]>indicator_buffer1[4] && 
         indicator_buffer1[4]>indicator_buffer1[5] && 
         indicator_buffer1[5]>indicator_buffer1[6])
         return(ORDER_TYPE_BUY);
     }
//--- 用于检查 MultiRange_PCH 指标条件的区块
   if(Indicator==PCH)
     {
      //--- 卖出信号
      if(close_price[1]<indicator_buffer2[1] && 
         open_price[1]>indicator_buffer2[1])
         return(ORDER_TYPE_SELL);
      //--- 买入信号
      if(close_price[1]>indicator_buffer1[1] && 
         open_price[1]<indicator_buffer1[1])
         return(ORDER_TYPE_BUY);
     }
//--- 没有信号
   return(WRONG_VALUE);
  }

GetTradingSignal()函数的代码如下:

//+------------------------------------------------------------------+
//| 判断交易信号                                                       |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE GetTradingSignal()
  {
//--- 如果没有持仓
   if(!pos.exists)
     {
      //--- 卖出信号
      if(GetSignal()==ORDER_TYPE_SELL)
         return(ORDER_TYPE_SELL);
      //--- 买入信号
      if(GetSignal()==ORDER_TYPE_BUY)
         return(ORDER_TYPE_BUY);
     }
//--- 如果有持仓
   if(pos.exists)
     {
      //--- 读取仓位类型
      GetPositionProperties(P_TYPE);
      //--- 取得最后一笔交易的价格
      GetPositionProperties(P_PRICE_LAST_DEAL);
      //--- 检查移动平均和CCI指标条件的区块
      if(Indicator==MA || Indicator==CCI)
        {
         //--- 卖出信号
         if(pos.type==POSITION_TYPE_BUY && 
            GetSignal()==ORDER_TYPE_SELL)
            return(ORDER_TYPE_SELL);
         //---
         if(pos.type==POSITION_TYPE_SELL && 
            GetSignal()==ORDER_TYPE_SELL && 
            close_price[1]<pos.last_deal_price-CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
            return(ORDER_TYPE_SELL);
         //--- 买入信号
         if(pos.type==POSITION_TYPE_SELL && 
            GetSignal()==ORDER_TYPE_BUY)
            return(ORDER_TYPE_BUY);
         //---
         if(pos.type==POSITION_TYPE_BUY && 
            GetSignal()==ORDER_TYPE_BUY && 
            close_price[1]>pos.last_deal_price+CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
            return(ORDER_TYPE_BUY);
        }
      //--- 检查 MultiRange_PCH 指标条件的区块
      if(Indicator==PCH)
        {
         //--- 卖出信号
         if(pos.type==POSITION_TYPE_BUY && 
            close_price[1]<indicator_buffer2[1] && 
            open_price[1]>indicator_buffer2[1])
            return(ORDER_TYPE_SELL);
         //---
         if(pos.type==POSITION_TYPE_SELL && 

            close_price[1]<pos.last_deal_price-CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
            return(ORDER_TYPE_SELL);
         //--- 买入信号
         if(pos.type==POSITION_TYPE_SELL && 
            close_price[1]>indicator_buffer1[1] && 
            open_price[1]<indicator_buffer1[1])
            return(ORDER_TYPE_BUY);
         //---
         if(pos.type==POSITION_TYPE_BUY && 
            close_price[1]>pos.last_deal_price+CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point))
            return(ORDER_TYPE_BUY);
        }
     }
//--- 没有信号
   return(WRONG_VALUE);
  }

现在我们只需要仔细研究交易品种属性部分中的即时执行(Instant Execution)市场执行(Market Execution)模式, 进而修改对应的OpenPosition()开启仓位函数了。它们的意义可以通过自己的名称来解释了,也可以在MQL5 参考中找到:

  • 即时执行
  • 市场执行

请注意,在处理市场执行模式时, 您在开启仓位的时候不能设置止损获利价位:您需要首先开启仓位再修改它,来设置那些价位。

从build 803开始, 可以在市场执行和交换执行模式下在开启仓位的同时设置止损和获利价位了。

让我们在交易品种属性结构中增加执行模式:

//--- 交易品种属性
struct symbol_properties
  {
   int               digits;           // 价格中的小数点位数
   int               spread;           // 点差点数
   int               stops_level;      // 止损级别
   double            point;            // 点值
   double            ask;              // 买价
   double            bid;              // 卖价
   double            volume_min;       // 交易的最小交易量
   double            volume_max;       // 交易的最大交易量
   double            volume_limit;     // 仓位以及单方向订单的最大允许交易量
   double            volume_step;      // 交易中交易量改变的最小步长
   double            offset;           // 事务中价格的最大可能偏移
   double            up_level;         // 止损价位上限
   double            down_level;       // 止损价位下限
   ENUM_SYMBOL_TRADE_EXECUTION execution_mode; // 执行模式
  };

相应地,我们需要修改ENUM_SYMBOL_PROPERTIES枚举

//--- 仓位属性的枚举
enum ENUM_SYMBOL_PROPERTIES
  {
   S_DIGITS          = 0,
   S_SPREAD          = 1,
   S_STOPSLEVEL      = 2,
   S_POINT           = 3,
   S_ASK             = 4,
   S_BID             = 5,
   S_VOLUME_MIN      = 6,
   S_VOLUME_MAX      = 7,
   S_VOLUME_LIMIT    = 8,
   S_VOLUME_STEP     = 9,
   S_FILTER          = 10,
   S_UP_LEVEL        = 11,
   S_DOWN_LEVEL      = 12,
   S_EXECUTION_MODE  = 13,
   S_ALL             = 14
  };

还有GetSymbolProperties() 函数:

case S_EXECUTION_MODE: symb.execution_mode=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE);   break;
      //---
      case S_ALL           :
         symb.digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         symb.spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);
         symb.stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         symb.point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         symb.ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),symb.digits);
         symb.bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),symb.digits);
         symb.volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
         symb.volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
         symb.volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);
         symb.volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
         symb.offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*symb.point),symb.digits);
         symb.up_level=NormalizeDouble(symb.ask+symb.stops_level*symb.point,symb.digits);
         symb.down_level=NormalizeDouble(symb.bid-symb.stops_level*symb.point,symb.digits);
         symb.execution_mode=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE);                       break;
         //---

最终,OpenPosition() 函数的代码现在如下:

//+------------------------------------------------------------------+
//| 开启一个仓位                                                       |
//+------------------------------------------------------------------+
void OpenPosition(double lot,
                  ENUM_ORDER_TYPE order_type,
                  double price,
                  double sl,
                  double tp,
                  string comment)
  {
//--- 设置交易结构的幻数
   trade.SetExpertMagicNumber(MagicNumber);
//--- 设置滑点点数
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
//--- 即时执行模式
//    开启仓位时可以同时设置止损和获利
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      //--- 如果仓位开启失败,打印相关信息
      if(!trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment))
         Print("开启仓位错误: ",GetLastError()," - ",ErrorDescription(GetLastError()));
     }
//--- 市场执行模式 
//    首先开启一个仓位,然后设置止损和获利价位
//    *** 从 build 803 开始,止损和获利可以在仓位开启时设置 ***
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET)
     {
      //--- 如果没有仓位,首先开启仓位,然后设置止损和获利
      if(!pos.exists)
        {
         //--- 如果仓位开启失败,打印相关信息
         if(!trade.PositionOpen(_Symbol,order_type,lot,price,0,0,comment))
            Print("开启仓位错误: ",GetLastError()," - ",ErrorDescription(GetLastError()));
         //--- 取得有无仓位的标志
         pos.exists=PositionSelect(_Symbol);
         //--- 如果有持仓
         if(pos.exists)
           {
            //--- 设置止损和获利
            if(!trade.PositionModify(_Symbol,sl,tp))
               Print("修改仓位出错: ",GetLastError()," - ",ErrorDescription(GetLastError()));
           }
        }
      //--- 如果仓位存在,增加交易量,不改变止损和获利价位
      else
        {
         //--- 如果仓位开启失败,打印相关信息
         if(!trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment))
            Print("开启仓位错误: ",GetLastError()," - ",ErrorDescription(GetLastError()));
        }
     }
  }

我们还必须把最后的非常重要的部分加到事件处理函数中:

  • OnInit
    //+------------------------------------------------------------------+
    //| EA初始化函数                                                       |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- 调整输入参数
       CorrectInputParameters();
    //--- 取得指标句柄
       GetIndicatorHandle();
    //--- 初始化新柱
       CheckNewBar();
    //--- 取得属性
       GetPositionProperties(P_ALL);
    //--- 设置信息面板
       SetInfoPanel();
    //---
       return(0);
      }
    
  • OnDeinit
    //+------------------------------------------------------------------+
    //| EA去初始化函数                                                     |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- 在日志中打印去初始化原因
       Print(GetDeinitReasonText(reason));
    //--- 当从图表上移除时
       if(reason==REASON_REMOVE)
         {
          //--- 从图表上删除所有有关信息面板的对象
          DeleteInfoPanel();
          //--- 删除指标句柄
          IndicatorRelease(indicator_handle);
         }
      }
    
  • OnTick
    //+------------------------------------------------------------------+
    //| EA订单函数                                                        |
    //+------------------------------------------------------------------+
    void OnTick()
      {
    //--- 如果柱不是新的,退出
       if(!CheckNewBar())
         {
          if(IsVisualMode() || IsRealtime())
            {
             //--- 取得属性并更新信息面板上的数值
             GetPositionProperties(P_ALL);
             //--- 设置/更新信息面板
             SetInfoPanel();
            }
          return;
         }
    
    //--- 如果有新柱
       else
         {
          //--- 如果允许交易
          if(CheckTradingPermission()==0)
            {
             if(!GetIndicatorsData())
                return;
             GetBarsData();          // 取得柱数据
             TradingBlock();         // 检查条件和交易
             ModifyTrailingStop();   // 修改跟踪止损价位
            }
         }
    //--- 取得属性
       GetPositionProperties(P_ALL);
    //--- 更新信息面板
       SetInfoPanel();
      }
    

现在所有函数都完成了,我们可以优化参数了。请记住,您需要从主程序文件编译代码。

 

优化参数和测试EA交易

策略测试器需要如下设置:

图 1. 策略测试器设置.

图 1. 策略测试器设置.

进一步,我们设置EA交易用于优化的参数 (参见附件中的 *.set 设置文件):

图 2. EA交易的设置.

图 2. EA交易的设置.

优化在双核处理器上大约需要40分钟。优化图表使您能够基于利润区域的结果部分评估交易系统的质量:

图 3. 优化图表

图 3. 优化图表

最大采收率测试结果如下:

图 4. 最大采收率测试结果

图 4. 最大采收率测试结果

 

结论

文章的附件中是可以下载的EA交易代码存档,展开以后,您需要把\TestIndicatorConditions 文件目录放到<Metatrader 5 terminal>\MQL5\Experts下面,为了保证EA能够正确运行,MultiRange_PCH 指标应该被下载和放置到<Metatrader 5 terminal>\MQL5\Indicators目录下。

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

附加的文件 |
multirange_pch.mq5 (12.06 KB)
MQL5 Cookbook: 交易历史和取得仓位信息的函数库 MQL5 Cookbook: 交易历史和取得仓位信息的函数库

现在是时候简单总结一下之前关于仓位属性文章的内容了,在本文中,我们会额外创建几个函数来取得只能通过访问交易历史才能获得的属性,我们也会对数据结构更加熟悉,这使我们可以用更加方便的方法访问仓位和交易品种属性。

初学者快速入门或简明指南 初学者快速入门或简明指南

亲爱的读者,您好!本文中,我们会试着为您解释并向您呈现可以如何轻松快速地掌握创建EA交易、使用指标等等原则的要领。本文面向初学者,所以不会包含任何难懂或晦涩的示例。

订单策略。多目标 EA 交易 订单策略。多目标 EA 交易

本文主要介绍一些主动使用挂单的策略、用来描述这些策略的元语言,以及如何使用一种以这些描述为基础运行的多目标 EA 交易。

MQL5 Cookbook: 基于三重滤网策略开发交易系统框架 MQL5 Cookbook: 基于三重滤网策略开发交易系统框架

在本文中,我们将基于三重滤网(Triple Screen)策略,使用MQL5开发一个交易系统的框架。EA交易不会从头开始开发,我们会简单地修改前一篇文章, 即"MQL5 Cookbook: 在EA交易中使用指标设置交易条件"中的程序,它已经基本上满足了我们的目标。所以这篇文章也会向您展示如何简单地修改已经完成的程序的模式。