English Русский Español Deutsch 日本語 Português
preview
开发 EA 构造函数的一次尝试

开发 EA 构造函数的一次尝试

MetaTrader 5 — 示例 |
4 131 41
Vladimir Karputov
Vladimir Karputov

内容


概述

从一开始,我的预定目标就是使用标准库。 我的首要任务是实现最简单的功能:包括 CTrade 交易类,及执行买入或卖出的方法。 我之所以选择标准库,是因为它可以生成更简洁的代码。以下以脚本形式执行的短代码以交易量 1.0 手执行开多头仓位:

//+------------------------------------------------------------------+
//|                                                     Open Buy.mq5 |
//|                         Copyright © 2018-2021, Vladimir Karputov |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018-2021, Vladimir Karputov"
#property version   "1.001"
//---
#include <Trade\Trade.mqh>
CTrade         m_trade;                      // trading object
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   m_trade.Buy(1.0); // open Buy position, volume 1.0 lot
  }

渐渐地,需求变得越来越复杂,我几乎每次在编写新的智能交易系统(EA)时都会遇到交易错误。 故此,我对编写正确代码的渴望变得越来越强烈。 最终,一篇非常重要的文章《交易机器人在市场发布前必须通过的检查》问世了。 当这篇文章发表时,我已经意识到需要针对交易指令的执行进行可靠控制的函数。 从那一刻起,我开始逐渐积攒了大量经过验证的函数,这些函数可以通过复制->粘贴,轻松添加到 EA 之中。

由于 EA 的操作几乎总是涉及到使用指标,因此我开始积攒创建正确指标句柄,以及接收指标数据的函数。

NB: MQL5 样式意味着指标句柄只能创建一次。 作为一条规定,这是在 OnInit 中完成的。

自 2.XXX, 版起,我开始维护两个开发分支 — 正常的面向过程代码,和类形式的代码(类的主要目标是实现多币种 EA)。

在我的工作动向中,构造函数逐渐接受了最流行的设置:

  • 止损和止盈,
  • 尾随,
  • 依据风险百分比、或恒定/最小手数计算手数,
  • 控制交易运作内的时间间隔,
  • 场内只存在一笔持仓,
  • 反转交易信号,
  • 在出现翻转信号的情况下强制平仓...

每个输入都牵扯到创建代码模块和新函数。

对于日常使用,我决定在 Trading engine 3.mq5 EA 当中收集所有最流行的函数和完整的输入集合。事实上,这是一个成品 EA,它能把我们从大量日常工作中解脱出来。 我们所要做的就是在每种特定情况下添加/删除函数,或更改代码模块之间的交互。


1. 构造函数之后的 EA 功能

由构造函数创建的 EA 立即拥有多个设置,可以组合这些设置来创建独特的策略。 版本 4.XXX 应用了以下规则: 

  • 使用当前品种符号(EA 启动时所在图表的品种符号)
  • 止盈、止损和尾随都在输入中设定。 Points — 依据报价货币计量的当前品种符号点数大小,例如 “EURSD” 1.00055-1.00045=10 个点。

通过拖动十字线工具,始终可以在品种符号图表上看到 “点数”:

点数

图例 1. 点数

以下是使用构造函数创建的 EA 输入:

  • 交易设置
    • Working timeframe — 操作时间帧。操作时间帧也许不同于 EA 启动所在的图表时间帧。 这是所创建指标的默认时间帧(如果指标中未明确指定其它时间帧)。 它还用于跟踪新柱线创建的时刻(如果只有在新柱线出现时才能检测出交易信号,或者只有在新柱线出现时才启动跟踪信号)。
    • Stop Loss — 止损。(0 – 禁用)。
    • Take Profit — 止盈。(0 – 禁用)。
    • Trailing on ... — 尾随。在每次即时报价(柱线#0(每次即时报价))或仅在出现新柱线时检查尾随能力 (柱线 #1 (出现一根新柱线))。
    • Search signals on ... — 信号搜索位柱线。在每次即时报价(柱线#0(每次即时报价))或仅在出现新柱线时搜索交易信号 (柱线 #1 (出现一根新柱线))。
    • Trailing Stop (min distance from price to Stop Loss) — 尾随停止,价格和持仓止损之间的最小距离。 只有在持仓已有盈利、且价格远离开盘价超过尾随停止+尾随步长时,尾随才被激活。尾随操作显示在 TrailingStop 代码图片之中。
    • Trailing Step — 尾随步长。 
  • 仓位大小管理(手数计算)。
    • Money management lot: Lot OR Risk — 资金管理手数:手数或风险。手数计算系统。 手数可以是固定的(资金管理=固定手数,手数大小可设置在 资金管理的数值),也可以是动态的 — 每笔交易的风险 %(资金管理=每笔交易的风险百分比,风险百分比设置在货币管理数值)。 您还可以将固定手数为最小手数(资金管理=最小手数)。
    • The value for "Money management" — 资金管理数值 
  • 交易模式
    • Trade mode: 仅允许多头仓位, 仅允许空头仓位 以及 允许多头和空头仓位
  • DEMA — 自定义指标参数。 这是您最终设置指标及其参数的地方
    • DEMA: 均化周期
    • DEMA: 水平偏移
    • DEMA: 价格类型
  • 时间控制 — 操作时间周期 该时间周期表示在其内允许搜索交易信号
    • Use time control — 标志,启用/禁用 时间控制
    • Start Hour — 周期开始钟点
    • Start Minute — 周期开始分钟
    • End Hour — 周期结束钟点
    • End Minute — 周期结束分钟
  • 挂单参数 — 与挂单相关的参数
    • Pending: Expiration, in minutes (0 -> OFF) — 挂单生存期 (0 — 禁用)。 
    • Pending: Indent — 挂单距当前价格的间距(未明确设置挂单价格时)
    • Pending: Maximum spread (0 -> OFF) — 最大点差 (0 — 禁用)。 如果当前点差超过指定值,则不会放置挂单(EA 等待点差缩小)
    • Pending: Only one pending — 启用/禁用标志。 场内上只允许有一笔挂单
    • Pending: Reverse pending type — 启用/禁用标志。 挂单逆向
    • Pending: New pending -> delete previous ones — 如果要设置挂单,则所有前期的其它挂单均被删除
  • 附加功能
    • Positions: Only one — 启用/禁用标志。 场内上只允许有一笔持仓
    • Positions: Reverse — 启用/禁用标志。 交易订单逆向
    • Positions: Close opposite — 启用/禁用标志。 如果有交易订单,则所有前期持仓都被平仓,以便执行该订单
    • Print log — 启用/禁用标志。 显示有关操作和错误的扩展信息
    • Coefficient (if Freeze==0 Or StopsLevels==0) — 考虑到停止级别的比率 
    • Deviation — 指定滑点
    • Magic number — EA 独有 ID

2. 构造函数通用算法

SPosition数组在全局程序级别声明(在 EA 头部)。 它由 STRUCT_POSITION 结构组成。 在启动期间,数组的大小为零。 处理交易信号完毕后,数组也会重置到零。

从 OnTick 中调用 SearchTradingSignals 函数。 如果存在信号(场内没有持仓),该函数将形成一笔交易订单(在数组中为每笔交易订单创建一个单独的 STRUCT_POSITION 结构)。在 OnTick 中检查是否有交易订单存在 — 检查 SPosition 数组大小:如果超过零,则存在一笔交易订单 ,该交易订单会被发送给 OpenBuy 或 OpenSell。 在 OnTradeTransaction 中完成对交易请求执行的控制:

通用算法(简单)

图例 2. 通用算法(简单)

通常假设 EA 针对当前品种符号上操作,即 EA 所在图表的品种符号。 例如: 如果 EA 置于 USDPLN 上,则它针对 USDPLN 操作。

2.1. STRUCT_POSITION 结构

该结构是 EA 的核心。 它同时执行两个角色:该结构为交易订单提供设置字段(在搜索交易信号中执行设置)。该结构还拥有管理交易订单执行的字段(控制在 OnTradeTransaction 中完成)。 

//+------------------------------------------------------------------+
//| Structure Positions                                              |
//+------------------------------------------------------------------+
struct STRUCT_POSITION
  {
   ENUM_POSITION_TYPE pos_type;              // position type
   double            volume;                 // position volume (if "0.0" -> the lot is "Money management")
   double            lot_coefficient;        // lot coefficient
   bool              waiting_transaction;    // waiting transaction, "true" -> it's forbidden to trade, we expect a transaction
   ulong             waiting_order_ticket;   // waiting order ticket, ticket of the expected order
   bool              transaction_confirmed;  // transaction confirmed, "true" -> transaction confirmed
   //--- Constructor
                     STRUCT_POSITION()
     {
      pos_type                   = WRONG_VALUE;
      volume                     = 0.0;
      lot_coefficient            = 0.0;
      waiting_transaction        = false;
      waiting_order_ticket       = 0;
      transaction_confirmed      = false;
     }
  };

一些字段负责交易订单本身,而另一些处理其执行。该结构包含构造函数 — STRUCT_POSITION() 特殊函数。 它在创建结构对象时调用,用于初始化结构元素。

交易订单字段:

  • pos_type — 开仓类型 (可以是 POSITION_TYPE_BUY 或 POSITION_TYPE_SELL)
  • volume — 开仓量。 若交易量 0.0,则从 'Position size management (lot calculation)' 输入组中获取
  • lot_coefficient — 如果该比率超过 0.0,则交易量乘以该比率

控制交易订单执行的字段

  • waiting_transaction — 指示交易订单已成功执行,且需要等待确认的标志
  • waiting_order_ticket — 执行交易订单时获取的订单索引
  • transaction_confirmed — 指示交易订单执行已确认的标志 

启用 transaction_confirmed 中的标志后,一笔交易订单将从结构中删除。 因此,如果 EA 操作不涉及交易订单,则结构的大小为零。 在“我们来捕获事务(简化代码)”章节中了解更多有关结构字段和控制的信息。

为什么我要采用这样的算法?

首先,检查 Buy 方法是否为 true 或 false 似乎就足矣了,且在为 true 情况下,则假设交易订单已经执行。 在许多情况下,这种方法实际上是可行的。 但有时即使返回 true 也不能保证结果。 这在有关 Buy 和 Sell 方法的文档中曾经提过:

注意

方法成功完成并不总是意味着交易操作成功执行。 有必要利用 ResultRetcode() 和 ResultDeal() 返回的值来检查交易请求的结果(交易服务器返回代码)。

交易历史记录中存在的记录项可作为交易操作执行的最终和精准确认。 因此,选择了以下算法:如果方法成功执行,则检查 ResultDeal(成交票据),检查 ResultRetcode(请求执行结果代码),并保存 ResultOrder(订单票据)。 订单票据可在 OnTradeTransaction 中找到。


3. 添加标准指标 — 处理 Indicators Code.mq5 文件

为了更加便捷,成品代码模块(声明存储句柄、输入、创建句柄的变量)被收集在 Indicators Code.mq5 EA 里。指标输入和存储句柄的变量位于 EA “头部”里,句柄则在 OnInit 中创建并设置。 请记住,存储句柄的变量名称格式如下:“handle_” + “指标名”,例如 “handle_iStdDev”。 整个 Indicators Code.mq5 的处理归结为复制-粘贴操作。 

NB: MQL5 样式意味着指标句柄只能创建一次。 作为一条规定,这是在 OnInit 中完成的。

3.1. 添加 iRVI(相对活力指数,RVI)指标的示例

创建 Add indicator.mq5 EA。在 MetaEditor 中,运行 MQL 向导,例如,点击 新建并选择智能交易系统(模板)

智能交易系统(模板)

图例 3. MQL 向导 -> 智能交易系统(模板)

我强烈建议在下一步至少添加一个输入 

智能交易系统(模板)

图例 4. 智能交易系统(模板) -> 加入参数

这能够在代码在输入模块里自动添加字符串:

//--- input parameters
input int      Input1=9;

MQL 向导已创建一个空 EA。 现在,我们来添加 iRVI(相对活力指数,RVI)指标。 在 Indicators Code.mq5 里,搜索 handle_iRVI (按下 ctrl + F)。 搜索会定位存储句柄的变量:

iRVI

图例 5. handle RVI

复制新找到的字符串,并将其插入“Add indicator” EA 的头部:

//--- input parameters
input int      Input1=9;
//---
int      handle_iRVI;                           // variable for storing the handle of the iRVI indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()

继续搜索并找到句柄创建模块:

iRVI

图例 6. handle iRVI

复制新找到的字符串,并将其插入到 OnInit 中的 “Add indicator” EA 当中:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iRVI
   handle_iRVI=iRVI(m_symbol.Name(),Inp_RVI_period,Inp_RVI_ma_period);
//--- if the handle is not created
   if(handle_iRVI==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iRVI indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(Inp_RVI_period),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }

现在,我们来添加指标输入。 在 Indicators Code.mq5,中键点击,指定 Inp_RVI_period,进入输入模块:

iRVI

图例 7. handle iRVI

复制字符串,并将其插入到输入中:

#property version   "1.00"
//--- input parameters
input group             "RVI"
input ENUM_TIMEFRAMES      Inp_RVI_period                = PERIOD_D1;      // RVI: timeframe
input int                  Inp_RVI_ma_period             = 15;             // RVI: averaging period
//---
int      handle_iRVI;                           // variable for storing the handle of the iRVI indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

编译会产生以下错误:m_symbol 和 m_init_error。 这是一种预期行为,因为在代码中存在的这些变量,可于构造函数操作后获得,而创建 “Add indicator” EA 只是出于演示如何处理 Indicators Code.mq5 文件。


4. 添加自定义指标

我们来添加 MA on DeMarker 自定义指标。 首先,它是一个自定义指标,其次,它用到组。 与上一章节类似,创建 “Add custom indicator'” EA。 在这之后,复制自定义指标的输入,并将其插入到 EA:

#property version   "1.00"
//--- input parameters
input group             "DeMarker"
input int                  Inp_DeM_ma_period    = 14;             // DeM: averaging period
input double               Inp_DeM_LevelUP      = 0.7;            // DeM: Level UP
input double               Inp_DeM_LevelDOWN    = 0.3;            // DeM: Level DOWN
input group             "MA"
input int                  Inp_MA_ma_period     = 6;              // MA: averaging period
input ENUM_MA_METHOD       Inp_MA_ma_method     = MODE_EMA;       // MA: smoothing type
//---

在 Indicators Code.mq5 里,找到 handle_iCustom 变量,它存储自定义指标的句柄,将其插入到 EA:

//+------------------------------------------------------------------+
//|                                         Add custom indicator.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.00"
//--- input parameters
input group             "DeMarker"
input int                  Inp_DeM_ma_period    = 14;             // DeM: averaging period
input double               Inp_DeM_LevelUP      = 0.7;            // DeM: Level UP
input double               Inp_DeM_LevelDOWN    = 0.3;            // DeM: Level DOWN
input group             "MA"
input int                  Inp_MA_ma_period     = 6;              // MA: averaging period
input ENUM_MA_METHOD       Inp_MA_ma_method     = MODE_EMA;       // MA: smoothing type
//---
int      handle_iCustom;                        // variable for storing the handle of the iCustom indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()

在 Indicators Code.mq5 的 OnInit() 中找到创建自定义句柄的模块   ,并将其插入到 EA:  

int      handle_iCustom;                        // variable for storing the handle of the iCustom indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iCustom
   handle_iCustom=iCustom(m_symbol.Name(),Inp_DEMA_period,"Examples\\DEMA",
                          Inp_DEMA_ma_period,
                          Inp_DEMA_ma_shift,
                          Inp_DEMA_applied_price);
//--- if the handle is not created
   if(handle_iCustom==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(Inp_DEMA_period),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |

这里我们尚有一些工作要做。 我们需要设置时间帧(InpWorkingPeriod)、指标的路径(我们假设它存储在 Indicators 文件夹的根目录中)、和输入:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iCustom
   handle_iCustom=iCustom(m_symbol.Name(),InpWorkingPeriod,"MA on DeMarker",
                          "DeMarker",
                          Inp_DeM_ma_period,
                          Inp_DeM_LevelUP,
                          Inp_DeM_LevelDOWN,
                          "MA",
                          Inp_MA_ma_period,
                          Inp_MA_ma_method);
//--- if the handle is not created
   if(handle_iCustom==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code
      PrintFormat("Failed to create handle of the iCustom indicator for the symbol %s/%s, error code %d",
                  m_symbol.Name(),
                  EnumToString(InpWorkingPeriod),
                  GetLastError());
      //--- the indicator is stopped early
      m_init_error=true;
      return(INIT_SUCCEEDED);
     }
//---
   return(INIT_SUCCEEDED);
  }

5. 我们来捕获事务(简化代码)

注:这只是一个 EA 的简化版本。 与成熟的构造函数相比,许多函数的代码更短洁。

如果场内没有该 EA 开立的持仓,则将交易请求设置为开立 BUY 仓位。 从 OnTradeTransaction 和 OnTick 的打印输出里可确认开仓。 在 SearchTradingSignals 函数中搜索和编写交易订单:

//+------------------------------------------------------------------+
//| Search trading signals                                           |
//+------------------------------------------------------------------+
bool SearchTradingSignals(void)
  {
   if(IsPositionExists())
      return(true);
//---
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
      return(true);
   ArrayResize(SPosition,size_need_position+1);
   SPosition[size_need_position].pos_type=POSITION_TYPE_BUY;
   if(InpPrintLog)
      Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY");
   return(true);
  }

如果场内没有该 EA 开立的持仓,且 SPosition 数组大小为零,则将数组大小增加 1。 如此,我们创建了单个 STRUCT_POSITION 结构对象。 这样,按顺序,调用 STRUCT_POSITION() 构造函数。 调用构造函数之后,结构元素被初始化 (例如,volume — 开仓量设置为 0.0,因此,它取自输入组 “Position size management (lot calculation)”)。 现在,只剩下在结构中设置交易订单类型。 在此情况下,它可以解释为:“开立多头仓位”。

交易订单设置完毕后,SPosition 数组由单个结构组成,且结构元素具有以下值:

元素 值 注意 
 pos_type  POSITION_TYPE_BUY  在 SearchTradingSignals 里设置
 volume  0.0  在结构构造函数中初始化
 lot_coefficient  0.0  在结构构造函数中初始化
 waiting_transaction  false  在结构构造函数中初始化
 waiting_order_ticket  0  在结构构造函数中初始化
 transaction_confirmed  false  在结构构造函数中初始化

5.1. 我们在一个新的即时报价来临时进入 OnTick

OnTick 一般原则:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
     {
      for(int i=size_need_position-1; i>=0; i--)
        {
         if(SPosition[i].waiting_transaction)
           {
            if(!SPosition[i].transaction_confirmed)
              {
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", OK: ","transaction_confirmed: ",SPosition[i].transaction_confirmed);
               return;
              }
            else
               if(SPosition[i].transaction_confirmed)
                 {
                  ArrayRemove(SPosition,i,1);
                  return;
                 }
           }
         if(SPosition[i].pos_type==POSITION_TYPE_BUY)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }
         if(SPosition[i].pos_type==POSITION_TYPE_SELL)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }
        }
     }
//--- search for trading signals only at the time of the birth of new bar
   if(!RefreshRates())
      return;
//--- search for trading signals
   if(!SearchTradingSignals())
      return;
//---
  }

在 OnTick 的开头处点击,检查 SPosition 数组的大小(这是 STRUCT_POSITION 结构数组)。 如果数组大小超过零,开始遍历到零,有两种情况存在: 

  • 如果 waiting_transaction 结构标志为 true (交易订单已准备就绪,需要等待确认), 检查 transaction_confirmed 标志 
    • 如果为 false,该笔业务尚未得到确认(例如,如果交易订单已发送,新的即时报价已到达,而 OnTradeTransaction 仍未确认,就可能发生这种情况)。 打印相应的消息并由 return 指令退出 — 等待新的即时报价,希望信息在新的即时报价上更新
    • 如果为 true,该笔业务已经确认。 从数组中删除结构,并由 return 指令退出
  • 如果 waiting_transaction 结构标志为 false (交易订单刚刚指定完毕,尚未执行),启用 waiting_transaction 标志,并把订单重定向至 OpenPosition。 代码块可以简化为

             SPosition[i].waiting_transaction=true;
             OpenPosition(i);
             return;
    但为了更容易理解 EA 构造函数的完整形式,我仍将其保留为这种形式:
             if(SPosition[i].pos_type==POSITION_TYPE_BUY)
               {
                if(InpCloseOpposite)
                  {
                   if(count_sells>0)
                     {
                      ClosePositions(POSITION_TYPE_SELL);
                      return;
                     }
                  }
                if(InpOnlyOne)
                  {
                   if(count_buys+count_sells==0)
                     {
                      SPosition[i].waiting_transaction=true;
                      OpenPosition(i);
                      return;
                     }
                   else
                     {
                      ArrayRemove(SPosition,i,1);
                      return;
                     }
                  }
                SPosition[i].waiting_transaction=true;
                OpenPosition(i);
                return;
               }
             if(SPosition[i].pos_type==POSITION_TYPE_SELL)
               {
                if(InpCloseOpposite)
                  {
                   if(count_buys>0)
                     {
                      ClosePositions(POSITION_TYPE_BUY);
                      return;
                     }
                  }
                if(InpOnlyOne)
                  {
                   if(count_buys+count_sells==0)
                     {
                      SPosition[i].waiting_transaction=true;
                      OpenPosition(i);
                      return;
                     }
                   else
                     {
                      ArrayRemove(SPosition,i,1);
                      return;
                     }
                  }
                SPosition[i].waiting_transaction=true;
                OpenPosition(i);
                return;
               }

我们继续追踪。 我要提醒您这段代码块:

         if(SPosition[i].pos_type==POSITION_TYPE_BUY)
           {
            SPosition[i].waiting_transaction=true;
            OpenPosition(i);
            return;
           }

交易订单刚在结构中设定 (在 SearchTradingSignals 函数里),且 waiting_transaction 标志设为 false。如此,设置 waiting_transaction 标志为 true,并将其作为参数传递至 OpenPosition 函数。 这是 OpenPosition 数组中的结构序列编号。 由于交易订单类型为 POSITION_type_BUY,因此应将结构序列号传递给 OpenBuy 函数。

5.2. OpenBuy 函数

函数目标是通过初步测试,发送开立多头仓位交易请求,并立即跟踪结果。

首先检查 - SYMBOL_VOLUME_LIMIT

 SYMBOL_VOLUME_LIMIT 针对此品种符号单一方向上(买入或卖出)开仓和挂单的最大允许总交易量。 例如,如果限额为 5 手,您也许已有 5 手的持仓,并放置了 5 手的卖出限价挂单。 但在这种情况下,您不能再放置一笔买入或卖出的限价挂单(因为一个方向上的总交易量将超过限制)。

如果检查未通过,从 SPosition 数组中删除结构元素。

第二个检查 - 保证金

获取交易操作后剩余的自由保证金(FreeMarginCheck),及交易操作所需的保证金(MarginCheck)。 之后,为安全起见,需确保总和至少等于 FreeMarginCheck:

   if(free_margin_check>margin_check)

如果检查未通过,打印 free_margin_check 变量,并从 SPosition 数组里删除结构。

第三个检查 - Buy 操作的二元布尔结果

如果 Buy 方法返回 false,打印错误,并设置 waiting_transaction 为 false (最常见的原因是错误 10004,requote - 重报价)。 故此,将在新即时报价上尝试重新开立多头仓位。 如果结果为 true (下面是代码块,其中 Buy 方法返回 true)

         if(m_trade.ResultDeal()==0)
           {
            if(m_trade.ResultRetcode()==10009) // trade order went to the exchange
              {
               SPosition[index].waiting_transaction=true;
               SPosition[index].waiting_order_ticket=m_trade.ResultOrder();
              }
            else
              {
               SPosition[index].waiting_transaction=false;
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", ERROR: ","#1 Buy -> false. Result Retcode: ",m_trade.ResultRetcode(),
                        ", description of result: ",m_trade.ResultRetcodeDescription());
              }
            if(InpPrintLog)
               PrintResultTrade(m_trade,m_symbol);
           }
         else
           {
            if(m_trade.ResultRetcode()==10009)
              {
               SPosition[index].waiting_transaction=true;
               SPosition[index].waiting_order_ticket=m_trade.ResultOrder();
              }
            else
              {
               SPosition[index].waiting_transaction=false;
               if(InpPrintLog)
                  Print(__FILE__," ",__FUNCTION__,", OK: ","#2 Buy -> true. Result Retcode: ",m_trade.ResultRetcode(),
                        ", description of result: ",m_trade.ResultRetcodeDescription());
              }
            if(InpPrintLog)
               PrintResultTrade(m_trade,m_symbol);
           }

之后检查 ResultDeal (成交票据)。

如果成交票据为零,检查 ResultRetcode (请求执行结果码)。 结果返回码为 10009 (例如,交易订单已发送至外部交易系统,譬如交易所,如此成交票据为零)。 设置 waiting_transaction 为 true,而 waiting_order_ticket 包含 ResultOrder (订单票据)。 否则 (返回码不为 10009), waiting_transaction 设为 false,并打印错误消息。

如果成交票据不为零(例如,在同一交易服务器上执行),则执行类似的返回码检查,并将值写入 waiting_transaction 及 waiting_order_ticket。

5.3. OnTradeTransaction

如果交易订单已成功发送,请等待成交已执行的确认,并记录在交易历史记录当中。 在 OnTradeTransaction 里,我们操控 trans 变量 (其为 MqlTradeTransaction 类型的结构)。 该结构有两个有趣的字段 — deal 和 type:

struct MqlTradeTransaction
  {
   ulong                         deal;             // Deal ticket
   ulong                         order;            // Order ticket
   string                        symbol;           // Symbol name
   ENUM_TRADE_TRANSACTION_TYPE   type;             // Trading transaction type
   ENUM_ORDER_TYPE               order_type;       // Order type
   ENUM_ORDER_STATE              order_state;      // Order state
   ENUM_DEAL_TYPE                deal_type;        // Deal type
   ENUM_ORDER_TYPE_TIME          time_type;        // Order type by lifetime
   datetime                      time_expiration;  // Order expiration time
   double                        price;            // Price 
   double                        price_trigger;    // Stop Limit order trigger price
   double                        price_sl;         // Stop Loss level
   double                        price_tp;         // Take Profit level
   double                        volume;           // Volume in lots
   ulong                         position;         // Position ticket
   ulong                         position_by;      // Opposite position ticket
  };


一旦 OnTradeTransaction 得到 TRADE_TRANSACTION_DEAL_ADD (在历史记录里添上一笔成交),则执行检查: 调用 HistoryDealSelect 尝试从历史记录中选择一笔成交。如果选择失败,则打印错误。 如果交易历史记录中存在成交,则循环遍历 SPosition 数组。 在循环里,仅查看字段 waiting_transaction 被设为 true 的结构,而 waiting_order_ticket 等于所选出成交订单的票据。 如果找到匹配项,则将 transaction_confirmed 设为 true。 这意味着交易订单已被执行和确认。

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
//--- get transaction type as enumeration value
   ENUM_TRADE_TRANSACTION_TYPE type=trans.type;
//--- if transaction is result of addition of the transaction in history
   if(type==TRADE_TRANSACTION_DEAL_ADD)
     {
      ResetLastError();
      if(HistoryDealSelect(trans.deal))
         m_deal.Ticket(trans.deal);
      else
        {
         Print(__FILE__," ",__FUNCTION__,", ERROR: ","HistoryDealSelect(",trans.deal,") error: ",GetLastError());
         return;
        }
      if(m_deal.Symbol()==m_symbol.Name() && m_deal.Magic()==InpMagic)
        {
         if(m_deal.DealType()==DEAL_TYPE_BUY || m_deal.DealType()==DEAL_TYPE_SELL)
           {
            int size_need_position=ArraySize(SPosition);
            if(size_need_position>0)
              {
               for(int i=0; i<size_need_position; i++)
                 {
                  if(SPosition[i].waiting_transaction)
                     if(SPosition[i].waiting_order_ticket==m_deal.Order())
                       {
                        Print(__FUNCTION__," Transaction confirmed");
                        SPosition[i].transaction_confirmed=true;
                        break;
                       }
                 }
              }
           }
        }
     }
  }

新即时报价来临时进入 OnTick。 从 SPosition 数组中删除 transaction_confirmed 字段为 true 的结构。 因此,已被发出的交易订单,直至出现在交易历史之前,一直会被跟踪。


6. 使用构造函数创建 EA(开仓信号)

在开发 EA 之前,我们应该思考它的交易策略。 我们来研究一个简单的策略,基于 iDEMA(双重指数移动平均,DEMA)指标。 这是构造函数中设置的默认策略。 只有当新柱线出现时,才会尝试寻找开单信号,而交易信号本身是一个依序上升或下降的指标:

DEMA 策略

图例 8. DEMA 策略

请记住,任何策略都可以通过调整参数进行大幅修改。 例如,我们可以在保留止盈和止损不变的情况下禁用尾随。 反之亦然:禁用止盈和止损,同时保留尾随。 我们也许会只允许买入或卖出来限制交易方向。 还可以启用 Time control,限制夜间交易,或相反,仅设置在夜间交易。 通过调整 Additional features 组中的参数,交易系统也可以发生重大变化。

一般来说,交易策略的主干是构建在 SearchTradingSignals 函数中,而所有其它参数都是“探测”行情,从而寻找最佳方法的。

故此,我们创建一个新文件 EA(执行图例 3 和图例 4 所示的步骤)。 在步骤 4,为 EA 指定一个独有的名称。其为 iDEMA Full EA.mq5。 结果则是,我们得到以下工件:

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.00"
//--- input parameters
input int      Input1=9;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

现在从 Trading engine 3.mq5 复制整段代码,并插入它来替代原来的字符串。 现在是编辑 “EA 头部” 的时候了。 生成的“头部”如下所示:

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//+------------------------------------------------------------------+
//|                                             Trading engine 3.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "4.003"
#property description "barabashkakvn Trading engine 4.003"
#property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)"
/*
   barabashkakvn Trading engine 4.003
*/
#include <Trade\PositionInfo.mqh>

令“头部”看起来如下所示:

//+------------------------------------------------------------------+
//|                                                iDEMA Full EA.mq5 |
//|                              Copyright © 2021, Vladimir Karputov |
//|                      https://www.mql5.com/en/users/barabashkakvn |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2021, Vladimir Karputov"
#property link      "https://www.mql5.com/en/users/barabashkakvn"
#property version   "1.001"
#property description "iDEMA EA"
#property description "Take Profit, Stop Loss and Trailing - in Points (1.00055-1.00045=10 points)"
/*
   barabashkakvn Trading engine 4.003
*/
#include <Trade\PositionInfo.mqh>

现在,如果我们编译它,将不会有错误。 得到的 EA 甚至可以进行交易。

6.1. SearchTradingSignals 函数

这是负责检查交易订单可用性的主要函数。 我们来逐模块研究这个函数。

每根柱线不超过一笔开仓:

   if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal
      return(true);

检查交易时间范围:

   if(!TimeControlHourMinute())
      return(true);

从指标接收数据。 指标数据被传递到 dema 数组。 借助于 ArraySetAsSeries,可以为其设置反向索引顺序(将 [0] 数组元素对应于图表上最右边的柱线)。 通过 iGetArray 自定义函数接收数据:

   double dema[];
   ArraySetAsSeries(dema,true);
   int start_pos=0,count=6;
   if(!iGetArray(handle_iCustom,0,start_pos,count,dema))
     {
      return(false);
     }
   int size_need_position=ArraySize(SPosition);
   if(size_need_position>0)
      return(true);

开立多头仓位信号。 如有必要(InpReverse 变量存储 Positions: Reverse 输入值),可以逆转交易信号。 如果交易方向有限制(InpTradeMode 变量存储 Trade mode: 输入值),也会作为参考:

//--- BUY Signal
   if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPosition,size_need_position+1);
            SPosition[size_need_position].pos_type=POSITION_TYPE_BUY;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPosition,size_need_position+1);
            SPosition[size_need_position].pos_type=POSITION_TYPE_SELL;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL");
            return(true);
           }
        }
     }

做空信号的代码模块与其类似。 


7. 使用构造函数创建 EA(挂单信号)

EA 的名称为 iDEMA Full EA Pending.mq5。 打开 iDEMA Full EA.mq5,并以新名称保存。

总是应该先制定交易策略,然后才是编写代码。 我们稍微更改一下 第 6 节 使用构造函数创建 EA(开仓信号) 中的策略。做多信号替换为 BuyStop 挂单,而做空信号替换为 SellStop 挂单。 以下参数用于挂单:

  • Pending: Expiration, in minutes (0 -> OFF)  -> 600
  • Pending: Indent — 挂单据当前价格的间隔 (若挂单价格未明确设定) -> 50
  • Pending: Maximum spread (0 -> OFF). 如果当前点差超过指定值,则不设置挂单 (EA 等待点差缩小) -> 12
  • Pending: Only one pending — 启用/禁用标志。 场内上只允许有一笔挂单 -> true
  • Pending: Reverse pending type — 启用/禁用标志。 挂单逆向 -> false
  • Pending: New pending -> 删除以前挂单 — 如果要放置挂单,则会删除前期所有其它挂单 -> true

SearchTradingSignals 函数如下所示:

//+------------------------------------------------------------------+
//| Search trading signals                                           |
//+------------------------------------------------------------------+
bool SearchTradingSignals(void)
  {
   if(iTime(m_symbol.Name(),InpWorkingPeriod,0)==m_last_deal_in) // on one bar - only one deal
      return(true);
   if(!TimeControlHourMinute())
      return(true);
   double dema[];
   ArraySetAsSeries(dema,true);
   int start_pos=0,count=6;
   if(!iGetArray(handle_iCustom,0,start_pos,count,dema))
     {
      return(false);
     }
   int size_need_pending=ArraySize(SPending);
   if(size_need_pending>0)
      return(true);
//---
   if(InpPendingOnlyOne)
      if(IsPendingOrdersExists())
         return(true);
   if(InpPendingClosePrevious)
      m_need_delete_all=true;
//--- BUY Signal
   if(dema[m_bar_current]>dema[m_bar_current+1] && dema[m_bar_current+1]>dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP");
            return(true);
           }
        }
     }
//--- SELL Signal
   if(dema[m_bar_current]<dema[m_bar_current+1] && dema[m_bar_current+1]<dema[m_bar_current+3])
     {
      if(!InpReverse)
        {
         if(InpTradeMode!=buy)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal SELL STOP");
            return(true);
           }
        }
      else
        {
         if(InpTradeMode!=sell)
           {
            ArrayResize(SPending,size_need_pending+1);
            SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
            if(InpPrintLog)
               Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");
            return(true);
           }
        }
     }
//---
   /*if(InpPendingOnlyOne)
      if(IsPendingOrdersExists())
         return(true);
   if(InpPendingClosePrevious)
      m_need_delete_all=true;
   int size_need_pending=ArraySize(SPending);
   ArrayResize(SPending,size_need_pending+1);
   if(!InpPendingReverse)
      SPending[size_need_pending].pending_type=ORDER_TYPE_BUY_STOP;
   else
      SPending[size_need_pending].pending_type=ORDER_TYPE_SELL_STOP;
   SPending[size_need_pending].indent=m_pending_indent;
   if(InpPendingExpiration>0)
      SPending[size_need_pending].expiration=(long)(InpPendingExpiration*60);
   if(InpPrintLog)
      Print(__FILE__," ",__FUNCTION__,", OK: ","Signal BUY STOP");*/
//---
   return(true);
  }

请注意,我们没有在 SPending 结构中设置挂单价格 。 这意味着将用当前价格叠加间隔。

我们收到一个交易信号,该信号仅在点差低于指定值时触发(放置一笔挂单):

iDEMA Full EA Pending

图例 9. iDEMA Full EA Pending


文后所附文件:

名称 文件类型 说明
Indicators Code EA 存储句柄、指标输入、指标创建块的变量
Add indicator.mq5 EA 使用 Add indicator.mq5 的示例 — 添加标准指标
Add custom indicator.mq5 EA
加入自定义指标的示例
Trading engine 4.mq5 EA 构造器
iDEMA Full EA.mq5
EA
在构造器的帮助下创建的 EA — 开仓信号
iDEMA Full EA Pending.mq5
EA
在构造器的帮助下创建的 EA — 放置挂单信号

结束语

我希望这套交易函数能帮助您创建更可靠的智能交易系统,为适应不断变化的市场交易条件做好准备。 尝试这些参数时无需犹豫。 您也可以通过启用某些参数、同时禁用其它参数,来大幅更改策略。


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

附加的文件 |
Indicators_Code.mq5 (88.76 KB)
Add_indicator.mq5 (4.55 KB)
Trading_engine_4.mq5 (177.98 KB)
iDEMA_Full_EA.mq5 (177.93 KB)
最近评论 | 前往讨论 (41)
good_boy86
good_boy86 | 29 3月 2022 在 16:29

您好,我正在运行 iDEMA_Full_EA_Pending,设置中的Positions: Only one 参数设置为 true( 市场中只 允许一个 仓位),但在EURUSD、m5、模拟账户、对冲中,却打开了多个仓位。也许我设置的参数有误?

Moon Domain - Unipessoal Lda
Dmitri Diall | 8 6月 2022 在 22:09

您好@Vladimir Karputov-- 这是一个非常好的最佳实践库,可提高 EA 处理市场订单和挂单的可靠性...它是一个很好的灵感来源,我一定会借鉴您在这里实现的一些好点子!

尽管如此,我还是觉得代码库有点过于 "单一",不符合我的个人口味,因为我通常更喜欢使用模块化方法。无论如何,您的EA 框架 肯定会 非常 有用,可以快速创建原型来测试新的策略想法,而无需在多个文件等方面投入太多时间......这对于提高工作效率来说真是太棒了。

Since about version 2.XXX, I started maintaining two development branches —  the normal procedural code and the code in the form of a class (the main objective of the class is implementing multicurrency EAs).

您提到您的框架有一个基于类的版本,但我在所附代码中找不到--我是不是漏掉了什么,或者在哪里可以找到?在测试一个新想法时,我更喜欢同时操作多个符号,所以如果你能告诉我下载这些文件的正确方向,我会非常感激。

zhurs
zhurs | 3 7月 2022 在 14:21

下午好,弗拉基米尔。我正在饶有兴趣地学习这篇文章。我对您文章中的以下一段话很感兴趣:

"我开始领导构造函数开发的两个分支 -- 通常的程序代码和类形式的代码(类的主要任务是实现多货币专家)"。

在所附文件中,我看到了标准类的使用,但我没有找到用于多币种 EA 的 "类形式代码"。它是否位于另一篇文章中?也许,作为一个初学者,经验不足,我还没有弄明白。您能告诉我在哪里可以看到基于类的 多货币智能交易程序的结构吗?我在文章中只找到了程序方法。

Ivan Titov
Ivan Titov | 7 12月 2022 在 06:38

我的理解是否正确,这里的构建是指添加/修改/删除代码(即不是从零开始,而是从头开始)?

另外,信号通常需要多个指标。有没有借助该构造函数创建的类似示例?

AK377MC
AK377MC | 2 3月 2024 在 20:59

你好,弗拉基米尔、


首先感谢您的伟大工作,这对我学习和理解 MQL 编码 "有一点 "帮助很大。

现在我测试了一些东西,发现当我排除买入或卖出条件行:1312、1313 和 1338 时,EA 仍然继续买入! 这是为什么?

但只是买入。

我是在测试不同的公式时发现这一点的,但 EA 从未达到我的要求 :-(

你知道为什么会出现这种情况吗?


感谢您分享此 EA



MQL5 酷宝书 – 财经日历 MQL5 酷宝书 – 财经日历
本文着重于财经日历的编程特性,并研究创建一个类来简化访问日历属性和接收事件值。 运用 CFTC(商品期货交易委员会)的非商业净持仓规则来开发指标作为一个实际例子。
处理时间(第一部分):基础 处理时间(第一部分):基础
能够简化和澄清时间、经纪商时移、以及夏季或冬季变更的时间处理函数和代码片段。 准确的时序也许是交易中的一个关键因素。 在当前钟点,伦敦或纽约的证券交易所是否已经或尚未开市,外汇交易的交易时间何时开始和结束? 对于一名实况手工交易者来说,这不算是个大问题。
手工制图表和交易工具箱(第三部分)。 优化和全新工具 手工制图表和交易工具箱(第三部分)。 优化和全新工具
在本文中,我们将深入开发利用键盘快捷键在图表上绘制图形对象的设想。 全新工具已被加到函数库当中了,包括一条贯穿任意顶点绘制的直线,以及一组能够评估逆转时间和价位的矩形。 此外,本文还展示了优化代码从而提高性能的可能性。 实现示例已经重写,能够使用其它交易程序的快捷方式。 所需的代码知识水平:略高于初学者。
DoEasy 函数库中的图形(第八十八部分):图形对象集合 — 存储对象动态变化属性的二维动态数组 DoEasy 函数库中的图形(第八十八部分):图形对象集合 — 存储对象动态变化属性的二维动态数组
在本文中,我将创建一个动态多维数组类,该类能够改变任意维度中的数据量。 基于所创建的类,我还将创建一个二维动态数组来存储一些会动态变动的图形对象属性。