同步和异步请求

在学习细节之前,请注意,每个 MQL 程序都是在自己的线程中执行的,因此,交易(和其他事件)的并行异步处理之所以可能,是因为另一个 MQL 程序也会这样做。同时,要保证程序之间的信息交换。我们已经了解了几种方法,包括终端的 全局变量文件。在本书的第 7 章,我们将探讨其他特性,如 图形资源数据库

实际上,设想一个类似于 TradeTransactions.mq5 的 EA 交易与交易中 EA 交易并行运行,并在全局变量中保存接收到的交易(不一定是所有字段,只是影响决策的可选字段)。然后,EA 交易可以在发送下一个请求后立即检查全局变量,并在不离开当前函数的情况下从中读取结果。此外,它不需要自己的 OnTradeTransaction 处理程序。

但是,组织第三方 EA 交易的运行并不容易。从技术角度来看,这可以通过创建一个 图表对象 并应用一个 模板 (带有预定义交易监视器的 EA 交易)来实现。但是有一个更简单的方法。关键是,OnTradeTransaction 的事件不仅可转化为 EA 交易,还可转化为指标。反过来,指标是最容易启动的 MQL 程序类型:只需调用 iCustom即可。

此外,使用指标还有一个好处:它可以说明通过CopyBuffer 从外部程序获得的指标缓冲区,并在其中安排一个 ring buffer 来存储来自终端的交易(请求结果)。因此,没有必要弄乱全局变量。

注意!测试程序中的指标并不会生成 OnTradeTransaction 事件,因此你只能在线检查 EA 交易/指标对的运行情况。

让我们将这个指标称为 TradeTransactionRelay.mq5,并描述其中的一个缓冲区。该缓冲区可以是不可见的,因为其将写入无法呈现的数据,但我们让其保持可见,以证明这个概念。

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
   
double Buffer[];
   
void OnInit()
{
   SetIndexBuffer(0BufferINDICATOR_DATA);
}

OnCalculate 处理程序为空。

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
   return rates_total;
}

在代码中,我们需要一个现成 转换器doubleulong 的相互转换),因为如果使用简单的类型转换将缓冲区单元写入 ulong,它们可能会破坏大的 ulong 值(参见 实数型)。

#include <MQL5Book/ConverterT.mqh>
Converter<ulong,doublecnv;

以下是 OnTradeTransaction 函数。

#defineFIELD_NUM6// the most important fields in MqlTradeResult
   
void OnTradeTransaction(const MqlTradeTransaction &transaction,
   const MqlTradeRequest &request,
   const MqlTradeResult &result)
{
   if(transaction.type == TRADE_TRANSACTION_REQUEST)
   {
      ArraySetAsSeries(Buffertrue);
      
      // store FIELD_NUM result fields into consecutive buffer cells
      const int offset = (int)((result.request_id * FIELD_NUM)
         % (Bars(_Symbol_Period) / FIELD_NUM * FIELD_NUM));
      Buffer[offset + 1] = result.retcode;
      Buffer[offset + 2] = cnv[result.deal];
      Buffer[offset + 3] = cnv[result.order];
      Buffer[offset + 4] = result.volume;
      Buffer[offset + 5] = result.price;
      // this assignment must come last,
      // because it is the result ready flag
      Buffer[offset + 0] = result.request_id;
   }
}

我们决定仅保留 MqlTradeResult 结构体中最重要的六个字段。如果需要,可以将该该机制扩展到整个结构体,但是要传递 comment 字符串字段,需要一个字符数组,因此必须为该数组保留相当多的元素。

因此,每个结果现在占据六个连续的缓冲区单元。这六个单元中的第一个单元的索引是根据请求 ID 确定的:将这个数字乘以 6 即可。因为可能有许多请求,所以条目根据环形缓冲区的原理工作,即,通过用余数(“%”)除以指示符缓冲区的大小(向上舍入到 6 的柱线数量)来规范化结果索引。当请求数量超过该大小时,记录将从初始元素开始循环。

由于柱线编号受新柱线形成的影响,建议将指标放在大的时间框架上,如 D1。然后,只有在一天开始的时候,才有可能(但概率较低)出现这样的情况:在处理下一个交易的过程中,指标中的柱线编号会直接发生变化,然后 EA 交易不会读取该指标记录的结果(可能会遗漏一个交易)。

指标准备就绪。现在让我们开始实现测试 EA 交易 OrderSendTransaction3.mq5 的新修改版本(这是其最新版本)。我们来介绍指标句柄的 handle 变量,并在 OnInit 中创建指标。

int handle = 0;
   
int OnInit()
{
   ...
   const static string indicator = "MQL5Book/p6/TradeTransactionRelay";
   handle = iCustom(_SymbolPERIOD_D1indicator);
   if(handle == INVALID_HANDLE)
   {
      Alert("Can't start indicator "indicator);
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

为了从指标缓冲区读取查询结果,我们来准备一个辅助函数 AwaitAsync。作为其第一个参数,它用于接收对 MqlTradeRequestSync 结构体的引用。如果成功,则从带有 handle 的指标缓冲区获得的结果将被写入该结构体。我们感兴趣的请求标识符应已经在嵌套结构体中,即 result.request_id 字段中。当然,此处必须按照同样的原理来读取数据,也就是六条柱线。

#define FIELD_NUM   6  // the most important fields in MqlTradeResult
#define TIMEOUT  1000  // 1 second
   
bool AwaitAsync(MqlTradeRequestSync &rconst int _handle)
{
   Converter<ulong,doublecnv;
   const int offset = (int)((r.result.request_id * FIELD_NUM)
      % (Bars(_Symbol_Period) / FIELD_NUM * FIELD_NUM));
   const uint start = GetTickCount();
   // wait for results or timeout
   while(!IsStopped() && GetTickCount() - start < TIMEOUT)
   {
      double array[];
      if((CopyBuffer(_handle0offsetFIELD_NUMarray)) == FIELD_NUM)
      {
         ArraySetAsSeries(arraytrue);
         // when request_id is found, fill other fields with results
         if((uint)MathRound(array[0]) == r.result.request_id)
         {
            r.result.retcode = (uint)MathRound(array[1]);
            r.result.deal = cnv[array[2]];
            r.result.order = cnv[array[3]];
            r.result.volume = array[4];
            r.result.price = array[5];
            PrintFormat("Got Req=%d at %d ms",
               r.result.request_idGetTickCount() - start);
            Print(TU::StringOf(r.result));
            return true;
         }
      }
   }
   Print("Timeout for: ");
   Print(TU::StringOf(r));
   return false;
}

现在我们有了这个函数,让我们以异步/同步的方式编写一个交易算法:作为一个直接的步骤序列,每个步骤都会等待前一个步骤准备就绪,因为通知来自并行指标程序,同时保留在一个函数中。

void OnTimer()
{
   EventKillTimer();
   
   MqlTradeRequestSync::AsyncEnabled = true;
   
   MqlTradeRequestSync request;
   request.magic = Magic;
   request.deviation = Deviation;
   
   const double volume = Volume == 0 ?
      SymbolInfoDouble(_SymbolSYMBOL_VOLUME_MIN) : Volume;
   ...

第 1 步.

   Print("Start trade");
   ResetLastError();
   if((bool)(Type == MARKET_BUY ? request.buy(volume) : request.sell(volume)))
   {
      Print("OK Open?");
   }
   
   if(!(AwaitAsync(requesthandle) && request.completed()))
   {
      Print("Failed Open");
      return;
   }
   ...

第 2 步.

   Print("SL/TP modification");
   ...
   if(request.adjust(SLTP))
   {
      Print("OK Adjust?");
   }
   
   if(!(AwaitAsync(requesthandle) && request.completed()))
   {
      Print("Failed Adjust");
   }

第 3 步.

   Print("Close down");
   if(request.close(request.result.position))
   {
      Print("OK Close?");
   }
   
   if(!(AwaitAsync(requesthandle) && request.completed()))
   {
      Print("Failed Close");
   }
   
   Print("Finish");
}

请注意,现在 completed 方法调用不是在发送请求之后,而是在 AwaitAsync 函数收到结果之后。

除此之外,一切都与该算法的第一个版本非常相似,但现在起建立在异步函数调用的基础上,并可对异步事件做出反应。

在这个特定的示例中,对单个仓位的一系列操作可能看起来并不重要。但是,我们可以使用相同的技术来发送和控制一批订单。优势就显而易见了。稍后,我们将在网格 EA 交易的帮助下演示这一点,同时比较两个函数的性能:OrderSendOrderSendAsync

但是现在,当我们完成 OrderSendTransaction EA 交易的序列时,让我们运行最新版本,并在日志中查看所有步骤的常规线性执行。

Start trade

OK Open?

Got Req=1 at 62 ms

DONE, D=1282677007, #=1300045365, V=0.01, @ 1.10564, Bid=1.10564, Ask=1.10564, Order placed, Req=1

Waiting for position for deal D=1282677007

SL/TP modification

OK Adjust?

Got Req=2 at 63 ms

DONE, Order placed, Req=2

Close down

OK Close?

Got Req=3 at 78 ms

DONE, D=1282677008, #=1300045366, V=0.01, @ 1.10564, Bid=1.10564, Ask=1.10564, Order placed, Req=3

Finish

响应延迟的计时可能主要取决于服务器、时间和交易品种。当然,此处的部分时间不是花在带确认的交易请求上,而是花在 CopyBuffer 函数的执行上。根据我们的观察结果,该时间不会超过 16 毫秒(在标准系统计时器的一个周期内,希望使用高精度计时器 GetMicrosecondCount 的人可以分析该程序)。

忽略状态 (DONE) 和字符串说明 ("Order placed") 之间的差异。事实上,从 OrderSendAsync 函数发送注释(以及 ask/bid 字段)的那一刻起,注释就一直保留在该结构体中,retcode 字段中的最终状态由 AwaitAsync 函数写入。对我们而言很重要的一点是,在包含结果的结构体中,订单号(dealorder)、行权价格 (price) 和交易量 (volume) 是最新的。

基于前面考虑的 OrderSendTransaction3.mq5 示例,让我们创建一个新版本的网格 EA 交易 PendingOrderGrid3.mq5(以前的版本在 用于读取仓位特性的函数一节中提供)。该 EA 交易能够按用户选择在同步或异步模式下设置一个完整的订单网格。我们还可以检测设置完整网格的时间,以便进行比较。

该模式由输入变量 EnableAsyncSetup 控制。handle 变量被分配给指标句柄。

input bool EnableAsyncSetup = false;
   
int handle;

在初始化期间,在异步模式的情况下,我们可创建一个 TradeTransactionRelay 指标的实例。

int OnInit()
{
   ...
   if(EnableAsyncSetup)
   {
      const uint start = GetTickCount();
      const static string indicator = "MQL5Book/p6/TradeTransactionRelay";
      handle = iCustom(_SymbolPERIOD_D1indicator);
      if(handle == INVALID_HANDLE)
      {
         Alert("Can't start indicator "indicator);
         return INIT_FAILED;
      }
      PrintFormat("Started in %d ms"GetTickCount() - start);
   }
   ...
}

为了简化编码,我们在 SetupGrid 函数中用一维数组替换了二维 request 数组。

uint SetupGrid()
{
   ...                                  // prev:
   MqlTradeRequestSyncLog request[];    // MqlTradeRequestSyncLog request[][2];
   ArrayResize(requestGridSize * 2);  // ArrayResize(request, GridSize);
   ...
}

在数组的循环中,我们使用寻址请求 request[i * 2 + 1],而不是请求 request[i][1] 类型的调用。

出于以下原因,需要进行这个小的转换。因为我们在创建网格时使用这个结构体数组进行查询,并且我们需要等待所有的结果,所以 AwaitAsync 函数现在应引用该数组作为其第一个参数。一维数组更容易处理。

对于每个请求,根据其 request_id 计算其在指标缓冲区中的偏移量:所有偏移量都放入 offset 数组中。当收到请求确认时,通过在数组中写入值 -1,将数组的相应元素标记为已处理。执行的请求数在 done 变量中统计。当这个数量等于数组的大小时,整个网格准备就绪。

bool AwaitAsync(MqlTradeRequestSyncLog &r[], const int _handle)
{
   Converter<ulong,doublecnv;
   int offset[];
   const int n = ArraySize(r);
   int done = 0;
   ArrayResize(offsetn);
   
   for(int i = 0i < n; ++i)
   {
      offset[i] = (int)((r[i].result.request_id * FIELD_NUM)
         % (Bars(_Symbol_Period) / FIELD_NUM * FIELD_NUM));
   }
   
   const uint start = GetTickCount();
   while(!IsStopped() && done < n && GetTickCount() - start < TIMEOUT)
   for(int i = 0i < n; ++i)
   {
      if(offset[i] == -1continue// skip empty elements
      double array[];
      if((CopyBuffer(_handle0offset[i], FIELD_NUMarray)) == FIELD_NUM)
      {
         ArraySetAsSeries(arraytrue);
         if((uint)MathRound(array[0]) == r[i].result.request_id)
         {
            r[i].result.retcode = (uint)MathRound(array[1]);
            r[i].result.deal = cnv[array[2]];
            r[i].result.order = cnv[array[3]];
            r[i].result.volume = array[4];
            r[i].result.price = array[5];
            PrintFormat("Got Req=%d at %d ms"r[i].result.request_id,
               GetTickCount() - start);
            Print(TU::StringOf(r[i].result));
            offset[i] = -1// mark processed
            done++;
         }
      }
   }
   return done == n;
}

回到 SetupGrid 函数,让我们展示一下在请求发送循环之后 AwaitAsync 是如何被调用的。

uint SetupGrid()
{
   ...
   const uint start = GetTickCount();
   for(int i = 0i < (int)GridSize / 2; ++i)
   {
      // calls of buyLimit/sellStopLimit/sellLimit/buyStopLimit
   }
   
   if(EnableAsyncSetup)
   {
      if(!AwaitAsync(requesthandle))
      {
         Print("Timeout");
         return TRADE_RETCODE_ERROR;
      }
   }
   
   PrintFormat("Done %d requests in %d ms (%d ms/request)",
      GridSize * 2GetTickCount() - start,
      (GetTickCount() - start) / (GridSize * 2));
   ...
}

如果在设置网格时发生超时(并非所有请求都会在分配的时间内收到确认),我们将返回 TRADE_RETCODE_ERROR 代码,EA 交易将尝试“回滚”其创建的内容。

需要注意的是,异步模式只是为了在我们需要发送一批请求时建立一个完整的网格。否则,仍会使用同步模式。因此,我们必须在发送循环之前将 MqlTradeRequestSync::AsyncEnabled 标志设置为 true,之后再将其设置回 false。但是,请注意以下几点。循环内部可能会出现错误,因此循环会过早终止,从服务器返回最后一个代码。因此,如果我们在循环之后进行异步复位,就不能保证其能够复位。

为了解决这个问题,在 MqlTradeSync.mqh 文件中添加了一个小的 AsyncSwitcher 类。该类可通过其构造函数和析构函数来控制异步模式的启用和禁用。这与 文件描述符管理一节中讨论的 RAII 资源管理概念一致。

class AsyncSwitcher
{
public:
   AsyncSwitcher(const bool enabled = true)
   {
      MqlTradeRequestSync::AsyncEnabled = enabled;
   }
   ~AsyncSwitcher()
   {
      MqlTradeRequestSync::AsyncEnabled = false;
   }
};

现在,为了安全地临时激活异步模式,我们可以简单地在 SetupGrid 函数中描述本地 AsyncSwitcher 对象。退出该函数时,代码将自动返回到同步模式。

uint SetupGrid()
{
   ...
   AsyncSwitcher sync(EnableAsyncSetup);
   ...
   for(int i = 0i < (int)GridSize / 2; ++i)
   {
      ...
   }
   ...
}

EA 交易准备就绪。让我们尝试运行两次:对于足够大的网格(10 个层级,网格步长 200),以同步和异步模式运行。

对于 10 层级的网格,我们将得到 20 个查询,所以以下是一些日志。首先,使用同步模式。让我们澄清一下,关于请求准备就绪的标志显示在关于请求的消息之前,因为后者是在该函数退出时由结构析构函数生成的。每个请求的处理速度为 51 毫秒。

Start setup at 1.10379

Done 20 requests in 1030 ms (51 ms/request)

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10200, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978336, V=0.01, Request executed, Req=1

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10200, »

» X=1.10400, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978337, V=0.01, Request executed, Req=2

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10000, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978343, V=0.01, Request executed, Req=5

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10000, »

» X=1.10200, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978344, V=0.01, Request executed, Req=6

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.09800, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978348, V=0.01, Request executed, Req=9

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.09800, »

» X=1.10000, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978350, V=0.01, Request executed, Req=10

...

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10600, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978339, V=0.01, Request executed, Req=3

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10600, »

» X=1.10400, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978340, V=0.01, Request executed, Req=4

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10800, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978345, V=0.01, Request executed, Req=7

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10800, »

» X=1.10600, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978347, V=0.01, Request executed, Req=8

...

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.11400, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978365, V=0.01, Request executed, Req=19

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.11400, »

» X=1.11200, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300978366, V=0.01, Request executed, Req=20

网格中间匹配了 1.10400 的价格。该系统按照收到请求的顺序为请求分配编号,其在数组中的编号对应于我们下订单的顺序:从中心基础层开始,逐渐向两侧发散。因此,不要惊讶在一对编号 1 和 2(对于层级 1.10200)之后是 5 和 6 (1.10000),因为 3 和 4 (1.10600) 发送地更早。

在异步模式下,析构函数前面是关于在 AwaitAsync 中实时接收的特定请求准备就绪消息,不一定按照请求发送的顺序(例如,第 49 个和第 50 个请求可能先于第 47 个和第 48 个请求)。

Started in 16 ms
Start setup at 1.10356
Got Req=41 at 109 ms
DONE, #=1300979180, V=0.01, Order placed, Req=41
Got Req=42 at 109 ms
DONE, #=1300979181, V=0.01, Order placed, Req=42
Got Req=43 at 125 ms
DONE, #=1300979182, V=0.01, Order placed, Req=43
Got Req=44 at 140 ms
DONE, #=1300979183, V=0.01, Order placed, Req=44
Got Req=45 at 156 ms
DONE, #=1300979184, V=0.01, Order placed, Req=45
Got Req=46 at 172 ms
DONE, #=1300979185, V=0.01, Order placed, Req=46
Got Req=49 at 172 ms
DONE, #=1300979188, V=0.01, Order placed, Req=49
Got Req=50 at 172 ms
DONE, #=1300979189, V=0.01, Order placed, Req=50
Got Req=47 at 172 ms
DONE, #=1300979186, V=0.01, Order placed, Req=47
Got Req=48 at 172 ms
DONE, #=1300979187, V=0.01, Order placed, Req=48
Got Req=51 at 172 ms
DONE, #=1300979190, V=0.01, Order placed, Req=51
Got Req=52 at 203 ms
DONE, #=1300979191, V=0.01, Order placed, Req=52
Got Req=55 at 203 ms
DONE, #=1300979194, V=0.01, Order placed, Req=55
Got Req=56 at 203 ms
DONE, #=1300979195, V=0.01, Order placed, Req=56
Got Req=53 at 203 ms
DONE, #=1300979192, V=0.01, Order placed, Req=53
Got Req=54 at 203 ms
DONE, #=1300979193, V=0.01, Order placed, Req=54
Got Req=57 at 218 ms
DONE, #=1300979196, V=0.01, Order placed, Req=57
Got Req=58 at 218 ms
DONE, #=1300979198, V=0.01, Order placed, Req=58
Got Req=59 at 218 ms
DONE, #=1300979199, V=0.01, Order placed, Req=59
Got Req=60 at 218 ms
DONE, #=1300979200, V=0.01, Order placed, Req=60
Done 20 requests in 234 ms (11 ms/request)
...

由于所有请求都是并行执行的,所以总的发送时间(234 毫秒)只比单个请求的时间(此处约为 100 毫秒,但是你可以自己计时)多一点。结果,我们获得了每个请求 11 毫秒的速度,速度是使用同步方法的 5 倍。由于请求几乎是同时发送的,所以我们无法知道每个请求的执行时间,毫秒表示从一般的组发送开始时特定请求结果的到达时间。

与前一种情况一样,更多的日志包含所有从结构体析构函数打印的查询和结果字段。在 OrderSendAsync 之后,"Order placed" 行保持不变,因为我们的辅助指标 TradeTransactionRelay.mq5 没有通过 TRADE_TRANSACTION_REQUEST 消息完整地发布 MqlTradeResult 结构体。

...

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10200, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979180, V=0.01, Order placed, Req=41

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10200, »

» X=1.10400, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979181, V=0.01, Order placed, Req=42

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10000, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979184, V=0.01, Order placed, Req=45

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10000, »

» X=1.10200, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979185, V=0.01, Order placed, Req=46

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.09800, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979188, V=0.01, Order placed, Req=49

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.09800, »

» X=1.10000, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979189, V=0.01, Order placed, Req=50

...

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10600, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979182, V=0.01, Order placed, Req=43

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10600, »

» X=1.10400, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979183, V=0.01, Order placed, Req=44

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10800, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979186, V=0.01, Order placed, Req=47

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.10800, »

» X=1.10600, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979187, V=0.01, Order placed, Req=48

...

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.11400, »

» ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979199, V=0.01, Order placed, Req=59

TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, @ 1.11400, »

» X=1.11200, ORDER_TIME_GTC, M=1234567890, G[1.10400]

DONE, #=1300979200, V=0.01, Order placed, Req=60

到现在为止,我们的网格 EA 交易在每个层级都有一对挂单:限价和止损限价。为了避免这种重复,我们只留下限价订单。这将是 PendingOrderGrid4.mq5 的最终版本,它也可以在同步和异步模式下运行。我们不会详细讨论源代码,只会指出与前一版本的主要区别。

SetupGrid 函数中,我们需要一个大小等于 GridSize 的结构体数组,不再翻倍。请求数量也将减少到原来的一半:用于这些请求的方法仅限于 buyLimitsellLimit

CheckGrid 函数可以不同的方式检查网格的完整性。以前,在有限制的层级上没有成对的止损限价订单被认为是错误的。当相邻层级在服务器上触发限价止损订单时,可能会发生这种情况。但是,如果一根柱线上出现强烈的双向价格波动(尖峰),这种方案就无法恢复网格:它不仅会取消原始限价订单,还会取消止损限价订单产生的新限价订单。现在,该算法会如实检查当前价格两侧的空缺层级,并使用 RepairGridLevel 在该处创建限价订单。这个辅助函数以前下达过限价止损订单。

最后,OnTradeTransaction 处理程序出现在 PendingOrderGrid4.mq5 中。触发挂单会导致一个交易的执行(以及需要纠正的网格配置改变),所以我们通过给定的交易品种和魔术数字来控制交易。当检测到一个交易时,CheckGrid 函数会被立即调用,除此之外,它还会在每个柱线的开始处执行。

void OnTradeTransaction(const MqlTradeTransaction &transaction,
   const MqlTradeRequest &,
   const MqlTradeResult &)
{
   if(transaction.type == TRADE_TRANSACTION_DEAL_ADD)
   {
      if(transaction.symbol == _Symbol)
      {
         DealMonitor dm(transaction.deal); // select the deal
         if(dm.get(DEAL_MAGIC) == Magic)
         {
            CheckGrid();
         }
      }
   }
}

应注意的是,OnTradeTransaction 的存在并不足以编写能够抵抗不可预见外部影响的 EA 交易。当然,许多事件允许你对情况做出快速响应,但是我们不能保证 EA 交易在一段时间内不会因为某种原因而平仓(或离线)并跳过某个交易。因此,OnTradeTransaction 处理程序只能帮助加速程序在无事件时也能执行的流程。特别是在启动后正确恢复其状态。

但是,除了 OnTradeTransaction 事件之外,MQL5 还提供了另一个更简单的事件:OnTrade