OnTradeTransaction 事件

如果 EA 交易和指标的代码包含 OnTradeTransaction 特殊处理函数,他们可以收到关于交易事件的通知。

void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request, const MqlTradeResult &result)

第一个参数为 MqlTradeTransaction 结构体,在 上一节中介绍过。第二个和第三个参数为 MqlTradeRequest MqlTradeResult 结构体,在前面的相关章节中介绍过。

MqlTradeTransaction 结构体可描述交易,其填充方式根据 type 字段中指定的交易类型而不同。例如,对于 TRADE_TRANSACTION_REQUEST 类型的交易,所有其他字段都不重要,为了获得附加信息,需要分析该函数的第二个和第三个参数(requestresult)。但对于所有其他类型的交易,应忽略该函数的最后两个参数。

在 TRADE_TRANSACTION_REQUEST 的情况下,result 变量中的 request_id 字段包含一个标识符(通过序列号),交易 request 在终端中以该标识符注册。该编号与订单和交易订单号以及仓位标识符无关。在与终端的每次会话期间,编号从头开始(从 1 开始)。请求标识符的存在允许你将执行的动作(调用 OrderSendOrderSendAsync 函数)与传递给 OnTradeTransaction 的动作结果相关联。我们稍后会看示例。

对于与活动订单(TRADE_TRANSACTION_ORDER_ADDTRADE_TRANSACTION_ORDER_UPDATETRADE_TRANSACTION_ORDER_DELETE)及其订单历史(TRADE_TRANSACTION_HISTORY_ADDTRADE_TRANSACTION_HISTORY_UPDATETRADE_TRANSACTION_HISTORY_DELETE)相关的交易,在 MqlTradeTransaction 结构体中填写以下字段:

  • order - 订单号
  • symbol - 订单中的金融工具名称
  • type - 交易类型
  • order_type - 订单类型
  • orders_state - 当前订单状态
  • time_type - 订单到期类型
  • time_expiration - 订单到期时间(适用于到期类型为 ORDER_TIME_SPECIFIED 和ORDER_TIME_SPECIFIED_DAY 的订单)
  • price - 客户/程序指定的订单价格
  • Price_trigger - 触发限价止损订单的止损价格(仅适用于 ORDER_TYPE_BUY_STOP_LIMIT 和 ORDER_TYPE_SELL_STOP_LIMIT)
  • price_sl - Stop Loss 订单价格(如果订单中指定则成交)
  • price_tp - Take Profit 订单价格(如果订单中指定则成交)
  • volume - 当前订单交易量(未执行),初始订单交易可从订单历史中找到
  • position - 未平仓、已修改或已平仓的订单号
  • Position_by - 反向仓位订单号(仅用于反向仓位平仓的订单)

对于与交易相关的事务(TRADE_TRANSACTION_DEAL_ADDTRADE_TRANSACTION_DEAL_UPDATETRADE_TRANSACTION_DEAL_DELETE),在 MqlTradeTransaction 结构体中填写以下字段:

  • Deal - 交易订单号
  • order - 作为交易成交依据的订单号
  • symbol - 交易的金融工具名称
  • type - 交易类型
  • deal_type - 交易类型
  • price - 交易价格
  • price_sl - Stop Loss 价格(如果在作为交易依据的订单中指定则成交)
  • price_tp - Take Profit 价格(如果在作为交易依据的订单中指定则成交)
  • volume - 交易量
  • position - 未平仓、已修改或已平仓的订单号
  • position_by - 反向仓位订单号(对于反向仓位平仓交易)

对于与仓位变更相关的交易(TRADE_TRANSACTION_POSITION),在 MqlTradeTransaction 结构体中填写以下字段:

  • symbol - 仓位的金融工具名称
  • type - 交易类型
  • deal_type - 仓位类型(DEAL_TYPE_BUY 或 DEAL_TYPE_SELL)
  • price - 加权平均开盘价
  • price_sl - Stop Loss 价格
  • price_tp - Take Profit 价格
  • volume - 每手仓位交易量
  • position - 仓位订单号

并非所有关于订单、交易和仓位的可用信息(例如,注释)都会在交易的说明中传输。有关更多信息,请使用相关函数:OrderGetHistoryOrderGetHistoryDealGetPositionGet

从终端手动或通过交易函数 OrderSend/OrderSendAsync 发送的一个交易请求可以在交易服务器上生成几个连续的交易。同时,不能保证这些交易的通知到达终端的顺序,因此你不能依赖特定交易事务的等待顺序来构建交易算法。

交易事件是异步处理的,也就是说,相对于生成时刻有所延迟。每个交易事件都会被发送到 MQL 程序的队列中,程序会按照队列顺序依次选取它们。

当 EA 交易在 OnTradeTransaction 处理器内处理交易时,终端会继续接受传入的交易。因此,在 OnTradeTransaction 运行时,交易账户的状态可能会发生变化。将来,程序将按照事件出现的顺序得到所有这些事件的通知。

交易队列的长度为 1024 个元素。如果 OnTradeTransaction 处理下一个交易的时间过长,队列中的旧交易可能会被新交易取代。

由于终端与交易对象的并行多线程操作,在调用 OnTradeTransaction 处理程序时,其中提到的所有实体,包括订单、交易和仓位,可能已经处于与交易特性中指定的不同状态。要获得它们的当前状态,应在当前环境或历史中选择它们,并使用适当的 MQL5 函数请求其特性。

我们从一个简单的 EA 交易示例 TradeTransactions.mq5 开始,其记录了所有的 OnTradeTransaction 交易事件。其唯一参数 DetailedLog 允许你有选择地使用 OrderMonitorDealMonitorPositionMonitor 类来显示所有特性。默认情况下,EA 交易只显示 MqlTradeTransactionMqlTradeRequestMqlTradeResult 结构体的填充字段内容,这些内容以参数形式到达处理程序;同时,只为 TRADE_TRANSACTION_REQUEST 交易处理 requestresult

input bool DetailedLog = false// DetailedLog ('true' shows order/deal/position details)
   
void OnTradeTransaction(const MqlTradeTransaction &transaction,
   const MqlTradeRequest &request,
   const MqlTradeResult &result)
{
   static ulong count = 0;
   PrintFormat(">>>% 6d", ++count);
   Print(TU::StringOf(transaction));
   
   if(transaction.type == TRADE_TRANSACTION_REQUEST)
   {
      Print(TU::StringOf(request));
      Print(TU::StringOf(result));
   }
   
   if(DetailedLog)
   {
      if(transaction.order != 0)
      {
         OrderMonitor m(transaction.order);
         m.print();
      }
      if(transaction.deal != 0)
      {
         DealMonitor m(transaction.deal);
         m.print();
      }
      if(transaction.position != 0)
      {
         PositionMonitor m(transaction.position);
         m.print();
      }
   }
}

让我们在 EURUSD 图表上运行该算法,并手动执行几个操作,相应的条目将出现在日志中(为了实验的纯粹性,假设没有人和其他程序对交易账户执行操作,特别是,没有其他 EA 交易在运行)。

我们用最小手数开一个多头仓位。

>>> 1

TRADE_TRANSACTION_ORDER_ADD, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10947, V=0.01

>>> 2

TRADE_TRANSACTION_DEAL_ADD, D=1279627746(DEAL_TYPE_BUY), »

» #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10947, V=0.01, P=1296991463

>>> 3

TRADE_TRANSACTION_ORDER_DELETE, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10947, P=1296991463

>>> 4

TRADE_TRANSACTION_HISTORY_ADD, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10947, P=1296991463

>>> 5

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10947, #=1296991463

DONE, D=1279627746, #=1296991463, V=0.01, @ 1.10947, Bid=1.10947, Ask=1.10947, Req=7

我们将卖出最低手数的两倍。

>>> 6

TRADE_TRANSACTION_ORDER_ADD, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10964, V=0.02

>>> 7

TRADE_TRANSACTION_DEAL_ADD, D=1279628463(DEAL_TYPE_SELL), »

» #=1296992157(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.02, P=1296992157

>>> 8

TRADE_TRANSACTION_ORDER_DELETE, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296992157

>>> 9

TRADE_TRANSACTION_HISTORY_ADD, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296992157

>>> 10

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.02, ORDER_FILLING_FOK, @ 1.10964, #=1296992157

DONE, D=1279628463, #=1296992157, V=0.02, @ 1.10964, Bid=1.10964, Ask=1.10964, Req=8

让我们执行计数器平仓操作。

>>> 11

TRADE_TRANSACTION_ORDER_ADD, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10964, V=0.01, P=1296991463, b=1296992157

>>> 12

TRADE_TRANSACTION_DEAL_ADD, D=1279628878(DEAL_TYPE_SELL), »

» #=1296992548(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.01, P=1296991463

>>> 13

TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10947, P=1296991463

>>> 14

TRADE_TRANSACTION_DEAL_ADD, D=1279628879(DEAL_TYPE_BUY), »

» #=1296992548(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10947, V=0.01, P=1296992157

>>> 15

TRADE_TRANSACTION_ORDER_DELETE, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296991463, b=1296992157

>>> 16

TRADE_TRANSACTION_HISTORY_ADD, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296991463, b=1296992157

>>> 17

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_CLOSE_BY, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, #=1296992548, »

» P=1296991463, b=1296992157

DONE, D=1279628878, #=1296992548, V=0.01, @ 1.10964, Bid=1.10961, Ask=1.10965, Req=9

我们仍然有最小手数的空头仓位。我们进行平仓。

>>> 18

TRADE_TRANSACTION_ORDER_ADD, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10964, V=0.01, P=1296992157

>>> 19

TRADE_TRANSACTION_ORDER_DELETE, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296992157

>>> 20

TRADE_TRANSACTION_HISTORY_ADD, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10964, P=1296992157

>>> 21

TRADE_TRANSACTION_DEAL_ADD, D=1279639132(DEAL_TYPE_BUY), »

» #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.01, P=1296992157

>>> 22

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10964, #=1297002683, »

» P=1296992157

DONE, D=1279639132, #=1297002683, V=0.01, @ 1.10964, Bid=1.10964, Ask=1.10964, Req=10

如果你愿意,可以启用 DetailedLog 选项,在事件处理时记录交易对象的所有特性。在详细日志中,你能注意到存储在交易结构体中的对象状态(在其启动时)和当前状态之间的差异。例如,当添加平仓订单(反向或正常)时,在交易中指定一张订单号,根据该订单号,监控对象将不再能够读取任何内容,因为该仓位已被删除。因此,我们将在日志中看到这样的行:

TRADE_TRANSACTION_ORDER_ADD, #=1297777749(ORDER_TYPE_CLOSE_BY/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10953, V=0.01, P=1297774881, b=1297776850

...

Error: PositionSelectByTicket(1297774881) failed: TRADE_POSITION_NOT_FOUND

我们重新启动 EA 交易 TradeTransaction.mq5 来重置记录的事件以便进行下一次测试。这一次我们将使用默认设置(没有详细说明)。

现在我们尝试在新的 EA 交易 OrderSendTransaction1.mq5 中以编程方式执行交易动作,同时在其中说明我们的 OnTradeTransaction 处理程序(与前面的示例相同)。

这个 EA 交易允许你选择交易方向和交易量:如果你将其设置为零,默认使用当前交易品种的最小手数。同样,在参数中,有一个以点为单位的到保护水平的距离。使用指定的参数进入市场,在设置 Stop LossTake Profit 之间有 5 秒钟的暂停,然后平仓,以便用户可以干预(例如,手动编辑止损),尽管这不是必需的,因为我们已经确保手动操作可被程序拦截。

enum ENUM_ORDER_TYPE_MARKET
{
   MARKET_BUY = ORDER_TYPE_BUY,    // ORDER_TYPE_BUY
   MARKET_SELL = ORDER_TYPE_SELL   // ORDER_TYPE_SELL
};
   
input ENUM_ORDER_TYPE_MARKET Type;
input double Volume;               // Volume (0 - minimal lot)
input uint Distance2SLTP = 1000;

该策略将启动一次,为此使用一个 1 秒钟的计时器,该计时器会在其自己的处理程序中关闭。

int OnInit()
{
   EventSetTimer(1);
   return INIT_SUCCEEDED;
}
   
void OnTimer()
{
   EventKillTimer();
   ...

所有操作都是通过一个我们已经熟悉的具有高级功能的 MqlTradeRequestSync 结构体 (MqlTradeSync.mqh) 来执行的:用正确的值隐式初始化字段、用于市场订单的 buy/sell 方法、用于保护水平的 adjust 以及用于平仓的 close

第 1 步:

   MqlTradeRequestSync request;
   
   const double volume = Volume == 0 ?
      SymbolInfoDouble(_SymbolSYMBOL_VOLUME_MIN) : Volume;
   
   Print("Start trade");
   const ulong order = (Type == MARKET_BUY ? request.buy(volume) : request.sell(volume));
   if(order == 0 || !request.completed())
   {
      Print("Failed Open");
      return;
   }
   
   Print("OK Open");

第 2 步:

   Sleep(5000); // wait 5 seconds (user can edit position)
   Print("SL/TP modification");
   const double price = PositionGetDouble(POSITION_PRICE_OPEN);
   const double point = SymbolInfoDouble(_SymbolSYMBOL_POINT);
   TU::TradeDirection dir((ENUM_ORDER_TYPE)Type);
   const double SL = dir.negative(priceDistance2SLTP * point);
   const double TP = dir.positive(priceDistance2SLTP * point);
   if(request.adjust(SLTP) && request.completed())
   {
      Print("OK Adjust");
   }
   else
   {
      Print("Failed Adjust");
   }

第 3 步:

   Sleep(5000); // wait another 5 seconds
   Print("Close down");
   if(request.close(request.result.position) && request.completed())
   {
      Print("Finish");
   }
   else
   {
      Print("Failed Close");
   }
}

通过中间等待,不仅可以有时间考虑进程,还展示了 MQL5 编程的一个重要方面,即单线程。当我们的 EA 交易在 OnTimer 中时,终端生成的交易事件可在其队列中累积,并且只有在退出 OnTimer 后才会以延迟的方式转发给内部 OnTradeTransaction 处理程序。

同时,并行运行的 TradeTransactions EA 交易不会参与任何计算,并将尽快接收交易事件。

两个 EA 交易的执行结果显示在下面的日志中,并显示了时间(为简单起见,OrderSendTransaction1 标记为 OS1Trade Transactions 标记为 TTs)。

19:09:08.078 OS1 Start trade

19:09:08.109 TTs >>> 1

19:09:08.125 TTs TRADE_TRANSACTION_ORDER_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), »

EURUSD, @ 1.10913, V=0.01

19:09:08.125 TTs >>> 2

19:09:08.125 TTs TRADE_TRANSACTION_DEAL_ADD, D=1280661362(DEAL_TYPE_BUY), »

#=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10913, V=0.01, »

P=1298021794

19:09:08.125 TTs >>> 3

19:09:08.125 TTs TRADE_TRANSACTION_ORDER_DELETE, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

EURUSD, @ 1.10913, P=1298021794

19:09:08.125 TTs >>> 4

19:09:08.125 TTs TRADE_TRANSACTION_HISTORY_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

EURUSD, @ 1.10913, P=1298021794

19:09:08.125 TTs >>> 5

19:09:08.125 TTs TRADE_TRANSACTION_REQUEST

19:09:08.125 TTs TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10913, »

D=10, #=1298021794, M=1234567890

19:09:08.125 TTs DONE, D=1280661362, #=1298021794, V=0.01, @ 1.10913, Bid=1.10913, Ask=1.10913, »

Req=9

19:09:08.125 OS1 Waiting for position for deal D=1280661362

19:09:08.125 OS1 OK Open

19:09:13.133 OS1 SL/TP modification

19:09:13.164 TTs >>> 6

19:09:13.164 TTs TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10913, SL=1.09913, TP=1.11913, V=0.01, »

P=1298021794

19:09:13.164 OS1 OK Adjust

19:09:13.164 TTs >>> 7

19:09:13.164 TTs TRADE_TRANSACTION_REQUEST

19:09:13.164 TTs TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, SL=1.09913, »

TP=1.11913, D=10, P=1298021794, M=1234567890

19:09:13.164 TTs DONE, Req=10

19:09:18.171 OS1 Close down

19:09:18.187 OS1 Finish

19:09:18.218 TTs >>> 8

19:09:18.218 TTs TRADE_TRANSACTION_ORDER_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_STARTED), »

EURUSD, @ 1.10901, V=0.01, P=1298021794

19:09:18.218 TTs >>> 9

19:09:18.218 TTs TRADE_TRANSACTION_DEAL_ADD, D=1280661967(DEAL_TYPE_SELL), »

#=1298022443(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10901, »

SL=1.09913, TP=1.11913, V=0.01, P=1298021794

19:09:18.218 TTs >>> 10

19:09:18.218 TTs TRADE_TRANSACTION_ORDER_DELETE, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

EURUSD, @ 1.10901, P=1298021794

19:09:18.218 TTs >>> 11

19:09:18.218 TTs TRADE_TRANSACTION_HISTORY_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

EURUSD, @ 1.10901, P=1298021794

19:09:18.218 TTs >>> 12

19:09:18.218 TTs TRADE_TRANSACTION_REQUEST

19:09:18.218 TTs TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10901, »

D=10, #=1298022443, P=1298021794, M=1234567890

19:09:18.218 TTs DONE, D=1280661967, #=1298022443, V=0.01, @ 1.10901, Bid=1.10901, Ask=1.10901, »

Req=11

19:09:18.218 OS1 >>> 1

19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), »

EURUSD, @ 1.10913, V=0.01

19:09:18.218 OS1 >>> 2

19:09:18.218 OS1 TRADE_TRANSACTION_DEAL_ADD, D=1280661362(DEAL_TYPE_BUY), »

#=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

@ 1.10913, V=0.01, P=1298021794

19:09:18.218 OS1 >>> 3

19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_DELETE, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

EURUSD, @ 1.10913, P=1298021794

19:09:18.218 OS1 >>> 4

19:09:18.218 OS1 TRADE_TRANSACTION_HISTORY_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), »

EURUSD, @ 1.10913, P=1298021794

19:09:18.218 OS1 >>> 5

19:09:18.218 OS1 TRADE_TRANSACTION_REQUEST

19:09:18.218 OS1 TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10913, »

D=10, #=1298021794, M=1234567890

19:09:18.218 OS1 DONE, D=1280661362, #=1298021794, V=0.01, @ 1.10913, Bid=1.10913, Ask=1.10913, »

Req=9

19:09:18.218 OS1 >>> 6

19:09:18.218 OS1 TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10913, SL=1.09913, TP=1.11913, V=0.01, »

P=1298021794

19:09:18.218 OS1 >>> 7

19:09:18.218 OS1 TRADE_TRANSACTION_REQUEST

19:09:18.218 OS1 TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, »

SL=1.09913, TP=1.11913, D=10, P=1298021794, M=1234567890

19:09:18.218 OS1 DONE, Req=10

19:09:18.218 OS1 >>> 8

19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_STARTED), »

EURUSD, @ 1.10901, V=0.01, P=1298021794

19:09:18.218 OS1 >>> 9

19:09:18.218 OS1 TRADE_TRANSACTION_DEAL_ADD, D=1280661967(DEAL_TYPE_SELL), »

#=1298022443(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10901, »

SL=1.09913, TP=1.11913, V=0.01, P=1298021794

19:09:18.218 OS1 >>> 10

19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_DELETE, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

EURUSD, @ 1.10901, P=1298021794

19:09:18.218 OS1 >>> 11

19:09:18.218 OS1 TRADE_TRANSACTION_HISTORY_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), »

EURUSD, @ 1.10901, P=1298021794

19:09:18.218 OS1 >>> 12

19:09:18.218 OS1 TRADE_TRANSACTION_REQUEST

19:09:18.218 OS1 TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10901, »

D=10, #=1298022443, P=1298021794, M=1234567890

19:09:18.218 OS1 DONE, D=1280661967, #=1298022443, V=0.01, @ 1.10901, Bid=1.10901, Ask=1.10901, »

Req=11

程序中事件的编号是相同的(假设它们按照建议明确地启动)。注意,相同的事件在请求执行后会立即从 TTs 中打印出来,第二次仅在测试结束时打印出来,实际上,所有的事件都从队列输出到 OS1

如果我们消除了人为延迟,该脚本当然会运行得更快,但是 OnTradeTransaction 处理程序仍然会在所有三个步骤之后(多次)收到通知,而不是在每个请求之后。这有多重要?

现在,示例使用我们对 MqlTradeRequestSync 结构体的修改,特意使用同步选项 OrderSend,还实现了一个通用的 completed 方法,该方法用于检查请求是否成功完成。有了这种控制,我们可以为仓位设置保护水平,因为我们知道如何等待其订单号出现。在这样一个同步概念的框架内(为了方便起见而采用),我们不需要在 OnTradeTransaction 中分析查询结果。但是,情况并非总是如此。

当 EA 交易需要一次发送许多请求时,例如在设置订单网格 PendingOrderGrid2.mq5 的示例中,如 仓位特性一节所述,等待每个仓位或等待订单就绪可能会降低 EA 交易的整体性能。在这种情况下,建议使用 OrderSendAsync 函数。但是如果成功,该函数仅填充 MqlTradeResult 中的 request_id 字段,然后需要用其来跟踪 OnTradeTransaction 中订单、交易和仓位的出现。

实现这种方案的一个最明显但不够优雅的一个技巧是,在全局上下文中,将请求的标识符或发送请求的整个结构体存储在一个数组中。然后可以在 OnTradeTransaction 中的传入交易中查找这些标识符,可以在 MqlTradeResult 参数中找到其订单号,并可以采取进一步操作。因此,交易逻辑被分成不同的函数。例如,在最后一个 EA 交易 OrderSendTransaction1.mq5 的上下文中,这种“分散处理”体现在:在发送第一个订单后,代码片段必须被传输到 OnTradeTransaction 并检查以下内容:

  • MqlTradeTransaction 中的交易类型 (transaction type);
  • MqlTradeRequest 中的请求类型 (request action);
  • MqlTradeResult 中的请求 ID (result.request_id);

所有这些都应辅以特定的应用逻辑(例如,检查仓位是否存在),其提供了按交易策略状态的分支。稍后,我们将在不同的编号下对 OrderSendTransaction EA 交易进行类似的修改,以直观地显示额外源代码的数量。然后我们将提供一种更线性的程序组织方式,但不会放弃交易性事件。

目前,我们只注意到开发者选择是否应围绕 OnTradeTransaction 来构建算法。在许多情况下,若不需要批量发送订单,可以保持同步编程模式。但是,控制挂单和触发保护水平以及服务器生成其他事件,最实用的方法就是使用 OnTradeTransaction。稍作准备后,我们将呈现两个相关的示例:网格 EA 交易的最终修改版本和两个 OCO(互损订单)订单的流行设置实现(参见 交易一节)。

若不想使用 OnTradeTransaction,一种替代方法是定期分析交易环境,实质上就是记住订单和仓位的数量,并寻找它们之间的变化。这种方法适用于基于时间表或允许一定时间延迟的策略。

我们再次强调,OnTradeTransaction 的使用并不意味着程序必须从 OrderSend 切换到 OrderSendAsync:你可以使用任意一个或同时使用两者。注意,OrderSend 函数也不是完全同步的,因为它最多返回订单和交易订单号,但不会返回仓位。很快,我们将能够测量同一网格策略中一批订单的执行时间,使用的两个函数变体为:OrderSendOrderSendAsync

为了统一同步和异步程序的开发,要是能在我们的结构体 MqlTradeRequestSync 中支持 OrderSendAsync 就太好了(不考虑其名称)。这可以通过几处修改来完成。首先,需要将所有当前存在的调用 OrderSend 替换为你自己的方法 orderSend,并在其中根据一个标志改为调用 OrderSendOrderSendAsync

struct MqlTradeRequestSyncpublic MqlTradeRequest
{
   ...
   static bool AsyncEnabled;
   ...
private:
   bool orderSend(const MqlTradeRequest &reqMqlTradeResult &res)
   {
      return AsyncEnabled ? ::OrderSendAsync(reqres) : ::OrderSend(reqres);
   }
};

通过将 AsyncEnabled 公共变量设置为 truefalse,可以从一种模式切换到另一种模式,例如,在发送批量订单的代码片段中。

第二,那些返回订单号(例如,进场)的结构方法应返回 request_id 字段,而不是 order。例如,在 _pending_market 方法中,我们有以下运算符:

if(OrderSend(thisresult)) return result.order;

现在将其替换为:

if(orderSend(thisresult)) return result.order ? result.order :
   (result.retcode == TRADE_RETCODE_PLACED ? result.request_id : 0);

当然,当启用异步模式时,我们不能再使用 completed 方法来等待查询结果在发送后立即准备好。但是这个方法本质上是可选的:即使是通过 OrderSend 工作,也可以放弃这个方法。

因此,考虑到对 MqlTradeSync.mqh 文件的新修改,我们来创建 OrderSendTransaction2.mq5

该 EA 交易将像以前一样从 OnTimer 发送初始请求,同时在 OnTradeTransaction 中逐步设置保护水平并平仓。虽然我们这次不会在各个阶段之间有人为延迟,但状态的顺序本身是许多 EA 交易的标准:开仓、修改、平仓(如果满足某些市场条件,这些条件此处不做赘述)。

两个全局变量将允许你跟踪状态:RequestID 变量包含发送的最后一个请求的 ID(我们期望的结果),Position Ticket 变量包含未平仓仓位订单号。当该仓位尚未出现或不再存在时,该订单号等于 0。

uint RequestID = 0;
ulong PositionTicket = 0;

OnInit 处理程序中启用了异步模式。

int OnInit()
{
   ...
   MqlTradeRequestSync::AsyncEnabled = true;
   ...
}

OnTimer 函数现在更简短了。

void OnTimer()
{
   ...
   // send a request TRADE_ACTION_DEAL (asynchronously!)
   const ulong order = (Type == MARKET_BUY ? request.buy(volume) : request.sell(volume));
   if(order// in asynchronous mode this is now request_id
   {
      Print("OK Open?");
      RequestID = request.result.request_id// same as order
   }
   else
   {
      Print("Failed Open");
   }
}

成功完成请求后,我们只获得 request_id,并将其存储在 RequestID 变量中。状态打印现在包含一个问号,如“OK Open?”,因为实际结果还不知道。

OnTradeTransaction 由于结果的验证以及根据条件执行后续交易订单,OnTradeTransaction 变得更加复杂。我们逐一来看吧。

在这种情况下,整个交易逻辑已经转移到 TRADE_TRANSACTION_REQUEST 类型的分支中。当然,如果需要的话,开发人员也可以使用其他类型,但是我们使用这种类型,因为其包含熟悉的 MqlTradeResult 结构体形式的信息,也就是说,这种类型表示异步调用 OrderSendAsync 的延迟结束。

void OnTradeTransaction(const MqlTradeTransaction &transaction,
   const MqlTradeRequest &request,
   const MqlTradeResult &result)
{
   static ulong count = 0;
   PrintFormat(">>>% 6d", ++count);
   Print(TU::StringOf(transaction));
   
   if(transaction.type == TRADE_TRANSACTION_REQUEST)
   {
      Print(TU::StringOf(request));
      Print(TU::StringOf(result));
      
      ...
      // here is the whole algorithm
   }
}

我们应只对具有我们期望的 ID 的请求感兴趣。所以下一条语句是嵌套 if。在这个块中,我们预先描述了 MqlTradeRequestSync 对象,因为根据计划需要发送常规的交易请求。

      if(result.request_id == RequestID)
      {
         MqlTradeRequestSync next;
         next.magic = Magic;
         next.deviation = Deviation;
         ...
      }

我们只有两种工作请求类型,所以我们为其增加了另一个嵌套的 if

         if(request.action == TRADE_ACTION_DEAL)
         {
            ... // here is the reaction to opening and closing a position
         }
         else if(request.action == TRADE_ACTION_SLTP)
         {
            ... // here is the reaction to setting SLTP for an open position
         }

请注意,TRADE_ACTION_DEAL 用于开仓和平仓,因此还需要一个 if,其中我们可根据 PositionTicket 变量的值来区分这两种状态。

            if(PositionTicket == 0)
            {
               ... // there is no position, so this is an opening notification 
            }
            else
            {
               ... // there is a position, so this is a closure
            }

在所考虑的交易策略中没有增加仓位(用于净额结算)或多个仓位(用于对冲),这就是为什么这一部分在逻辑上很简单的原因。真正的 EA 交易需要更多不同的中间状态估计。

接收到开仓通知时,代码块如下所示:

            if(PositionTicket == 0)
            {
               // trying to get results from the transaction: select an order by ticket
               if(!HistoryOrderSelect(result.order))
               {
                  Print("Can't select order in history");
                  RequestID = 0;
                  return;
               }
               // get position ID and ticket
               const ulong posid = HistoryOrderGetInteger(result.orderORDER_POSITION_ID);
               PositionTicket = TU::PositionSelectById(posid);
               ...

为了简单起见,我们此处省略了错误和重新报价检查。你可以在附加的源代码中看到它们的处理示例。回想一下,所有这些检查都已经在 MqlTradeRequestSync 结构体的方法中实现了,但是它们只在同步模式下工作,因此我们必须显式地重复它们。

下一个用于设置保护水平的代码片段变化不大。

            if(PositionTicket == 0)
            {
               ...
               const double price = PositionGetDouble(POSITION_PRICE_OPEN);
               const double point = SymbolInfoDouble(_SymbolSYMBOL_POINT);
               TU::TradeDirection dir((ENUM_ORDER_TYPE)Type);
               const double SL = dir.negative(priceDistance2SLTP * point);
               const double TP = dir.positive(priceDistance2SLTP * point);
               // sending TRADE_ACTION_SLTP request (asynchronously!)
               if(next.adjust(PositionTicketSLTP))
               {
                  Print("OK Adjust?");
                  RequestID = next.result.request_id;
               }
               else
               {
                  Print("Failed Adjust");
                  RequestID = 0;
               }
            }

此处唯一的区别是:我们用新的 TRADE_ACTION_SLTP 请求的 ID 来填充 RequestID 变量。

收到关于非零 PositionTicket 交易的通知意味着该仓位已平仓。

            if(PositionTicket == 0)
            {
               ... // see above
            }
            else
            {
               if(!PositionSelectByTicket(PositionTicket))
               {
                  Print("Finish");
                  RequestID = 0;
                  PositionTicket = 0;
               }
            }

如果成功删除,则不能使用 PositionSelectByTicket 选择仓位,因此我们应重置 RequestIDPositionTicket。然后,EA 交易会返回到其初始状态,并准备进行下一个买入/卖出-修改-平仓循环。

我们仍需考虑发送平仓请求。在我们简化到最低限度的策略中,这应发生在成功修改保护水平之后。

         if(request.action == TRADE_ACTION_DEAL)
         {
            ... // see above
         }
         else if(request.action == TRADE_ACTION_SLTP)
         {
            // send a TRADE_ACTION_DEAL request to close (asynchronously!)
            if(next.close(PositionTicket))
            {
               Print("OK Close?");
               RequestID = next.result.request_id;
            }
            else
            {
               PrintFormat("Failed Close %lld"PositionTicket);
            }
         }

这就是整个 OnTradeTransaction 函数。EA 交易准备就绪。

我们使用 EURUSD 的默认设置运行 OrderSendTransaction2.mq5。以下是一个日志示例。

Start trade

OK Open?

>>> 1

TRADE_TRANSACTION_ORDER_ADD, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10640, V=0.01

>>> 2

TRADE_TRANSACTION_DEAL_ADD, D=1282135720(DEAL_TYPE_BUY), »

» #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10640, V=0.01, P=1299508203

>>> 3

TRADE_TRANSACTION_ORDER_DELETE, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10640, P=1299508203

>>> 4

TRADE_TRANSACTION_HISTORY_ADD, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10640, P=1299508203

>>> 5

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10640, D=10, »

» #=1299508203, M=1234567890

DONE, D=1282135720, #=1299508203, V=0.01, @ 1.1064, Bid=1.1064, Ask=1.1064, Req=7

OK Adjust?

>>> 6

TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10640, SL=1.09640, TP=1.11640, V=0.01, P=1299508203

>>> 7

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, SL=1.09640, TP=1.11640, »

» D=10, P=1299508203, M=1234567890

DONE, Req=8

OK Close?

>>> 8

TRADE_TRANSACTION_ORDER_ADD, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_STARTED), EURUSD, »

» @ 1.10638, V=0.01, P=1299508203

>>> 9

TRADE_TRANSACTION_ORDER_DELETE, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10638, P=1299508203

>>> 10

TRADE_TRANSACTION_HISTORY_ADD, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, »

» @ 1.10638, P=1299508203

>>> 11

TRADE_TRANSACTION_DEAL_ADD, D=1282135730(DEAL_TYPE_SELL), »

» #=1299508215(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10638, »

» SL=1.09640, TP=1.11640, V=0.01, P=1299508203

>>> 12

TRADE_TRANSACTION_REQUEST

TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10638, D=10, »

» #=1299508215, P=1299508203, M=1234567890

DONE, D=1282135730, #=1299508215, V=0.01, @ 1.10638, Bid=1.10638, Ask=1.10638, Req=9

Finish

交易逻辑按预期工作,交易事件严格按订单发送顺序到达。如果我们现在并行运行新的 EA 交易和交易拦截器 TradeTransactions.mq5,来自两个 EA 交易的日志消息将同步出现。

但是,从第一个直接版本 OrderSendTransaction1.mq5 重构为第二个异步版本 OrderSendTransaction2.mq5 需要更复杂的代码。问题出现了:有没有可能以某种方式将顺序说明交易逻辑(代码透明度)和并行处理(速度)的原则结合起来呢?

这在理论上是可能的,但是需要在某个节点花时间去创建某种辅助机制。