- EA 交易的主要事件:OnTick
- 基本原理和概念:订单、交易和仓位
- 交易操作类型
- 订单类型
- 按价格和数量划分的订单执行模式
- 挂单到期日期
- 期货订单的保证金计算方法:OrderCalcMargin
- 估算交易操作的利润:OrderCalcProfit
- MqlTradeRequest 结构体
- MqlTradeCheckResult 结构体
- 请求验证:OrderCheck
- 请求发送结果:MqlTradeResult 结构体
- 发送交易请求:OrderSend 和 OrderSendAsync
- 买入和卖出操作
- 修改仓位的止损和/或止盈水平
- 跟踪止损
- 平仓:全部和部分
- 反向平仓:全部和部分
- 挂单
- 修改挂单
- 删除挂单
- 获取活动订单列表
- 订单特性(现行和历史)
- 用于读取活动订单特性的函数
- 按特性选择订单
- 获取仓位列表
- 仓位特性
- 用于读取仓位特性的函数
- 交易特性
- 从历史中选择订单和交易
- 用于从历史中读取订单特性的函数
- 用于从历史中读取交易特性的函数
- 交易类型
- OnTradeTransaction 事件
- 同步和异步请求
- OnTrade 事件
- 监测交易环境变化
- 创建多交易品种 EA 交易
- EA 交易的优势和局限性
- 在 MQL 向导中创建 EA 交易
OnTradeTransaction 事件
如果 EA 交易和指标的代码包含 OnTradeTransaction 特殊处理函数,他们可以收到关于交易事件的通知。
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request, const MqlTradeResult &result)
第一个参数为 MqlTradeTransaction 结构体,在 上一节中介绍过。第二个和第三个参数为 MqlTradeRequest 和 MqlTradeResult 结构体,在前面的相关章节中介绍过。
MqlTradeTransaction 结构体可描述交易,其填充方式根据 type 字段中指定的交易类型而不同。例如,对于 TRADE_TRANSACTION_REQUEST 类型的交易,所有其他字段都不重要,为了获得附加信息,需要分析该函数的第二个和第三个参数(request 和 result)。但对于所有其他类型的交易,应忽略该函数的最后两个参数。
在 TRADE_TRANSACTION_REQUEST 的情况下,result 变量中的 request_id 字段包含一个标识符(通过序列号),交易 request 在终端中以该标识符注册。该编号与订单和交易订单号以及仓位标识符无关。在与终端的每次会话期间,编号从头开始(从 1 开始)。请求标识符的存在允许你将执行的动作(调用 OrderSend 或 OrderSendAsync 函数)与传递给 OnTradeTransaction 的动作结果相关联。我们稍后会看示例。
对于与活动订单(TRADE_TRANSACTION_ORDER_ADD、 TRADE_TRANSACTION_ORDER_UPDATE 和 TRADE_TRANSACTION_ORDER_DELETE)及其订单历史(TRADE_TRANSACTION_HISTORY_ADD、 TRADE_TRANSACTION_HISTORY_UPDATE、 TRADE_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_ADD、 TRADE_TRANSACTION_DEAL_UPDATE 和 TRADE_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 - 仓位订单号
并非所有关于订单、交易和仓位的可用信息(例如,注释)都会在交易的说明中传输。有关更多信息,请使用相关函数:OrderGet、HistoryOrderGet、HistoryDealGet 和 PositionGet。
从终端手动或通过交易函数 OrderSend/OrderSendAsync 发送的一个交易请求可以在交易服务器上生成几个连续的交易。同时,不能保证这些交易的通知到达终端的顺序,因此你不能依赖特定交易事务的等待顺序来构建交易算法。
交易事件是异步处理的,也就是说,相对于生成时刻有所延迟。每个交易事件都会被发送到 MQL 程序的队列中,程序会按照队列顺序依次选取它们。
当 EA 交易在 OnTradeTransaction 处理器内处理交易时,终端会继续接受传入的交易。因此,在 OnTradeTransaction 运行时,交易账户的状态可能会发生变化。将来,程序将按照事件出现的顺序得到所有这些事件的通知。
交易队列的长度为 1024 个元素。如果 OnTradeTransaction 处理下一个交易的时间过长,队列中的旧交易可能会被新交易取代。
由于终端与交易对象的并行多线程操作,在调用 OnTradeTransaction 处理程序时,其中提到的所有实体,包括订单、交易和仓位,可能已经处于与交易特性中指定的不同状态。要获得它们的当前状态,应在当前环境或历史中选择它们,并使用适当的 MQL5 函数请求其特性。
我们从一个简单的 EA 交易示例 TradeTransactions.mq5 开始,其记录了所有的 OnTradeTransaction 交易事件。其唯一参数 DetailedLog 允许你有选择地使用 OrderMonitor、DealMonitor 和 PositionMonitor 类来显示所有特性。默认情况下,EA 交易只显示 MqlTradeTransaction、MqlTradeRequest 和 MqlTradeResult 结构体的填充字段内容,这些内容以参数形式到达处理程序;同时,只为 TRADE_TRANSACTION_REQUEST 交易处理 request 和 result。
input bool DetailedLog = false; // DetailedLog ('true' shows order/deal/position details)
|
让我们在 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 Loss 和 Take Profit 之间有 5 秒钟的暂停,然后平仓,以便用户可以干预(例如,手动编辑止损),尽管这不是必需的,因为我们已经确保手动操作可被程序拦截。
enum ENUM_ORDER_TYPE_MARKET
|
该策略将启动一次,为此使用一个 1 秒钟的计时器,该计时器会在其自己的处理程序中关闭。
int OnInit()
|
所有操作都是通过一个我们已经熟悉的具有高级功能的 MqlTradeRequestSync 结构体 (MqlTradeSync.mqh) 来执行的:用正确的值隐式初始化字段、用于市场订单的 buy/sell 方法、用于保护水平的 adjust 以及用于平仓的 close。
第 1 步:
MqlTradeRequestSync request;
|
第 2 步:
Sleep(5000); // wait 5 seconds (user can edit position)
|
第 3 步:
Sleep(5000); // wait another 5 seconds
|
通过中间等待,不仅可以有时间考虑进程,还展示了 MQL5 编程的一个重要方面,即单线程。当我们的 EA 交易在 OnTimer 中时,终端生成的交易事件可在其队列中累积,并且只有在退出 OnTimer 后才会以延迟的方式转发给内部 OnTradeTransaction 处理程序。
同时,并行运行的 TradeTransactions EA 交易不会参与任何计算,并将尽快接收交易事件。
两个 EA 交易的执行结果显示在下面的日志中,并显示了时间(为简单起见,OrderSendTransaction1 标记为 OS1,Trade 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 函数也不是完全同步的,因为它最多返回订单和交易订单号,但不会返回仓位。很快,我们将能够测量同一网格策略中一批订单的执行时间,使用的两个函数变体为:OrderSend 和 OrderSendAsync。
为了统一同步和异步程序的开发,要是能在我们的结构体 MqlTradeRequestSync 中支持 OrderSendAsync 就太好了(不考虑其名称)。这可以通过几处修改来完成。首先,需要将所有当前存在的调用 OrderSend 替换为你自己的方法 orderSend,并在其中根据一个标志改为调用 OrderSend 或 OrderSendAsync。
struct MqlTradeRequestSync: public MqlTradeRequest
|
通过将 AsyncEnabled 公共变量设置为 true 或 false,可以从一种模式切换到另一种模式,例如,在发送批量订单的代码片段中。
第二,那些返回订单号(例如,进场)的结构方法应返回 request_id 字段,而不是 order。例如,在 _pending 和 _market 方法中,我们有以下运算符:
if(OrderSend(this, result)) return result.order; |
现在将其替换为:
if(orderSend(this, result)) return result.order ? result.order :
|
当然,当启用异步模式时,我们不能再使用 completed 方法来等待查询结果在发送后立即准备好。但是这个方法本质上是可选的:即使是通过 OrderSend 工作,也可以放弃这个方法。
因此,考虑到对 MqlTradeSync.mqh 文件的新修改,我们来创建 OrderSendTransaction2.mq5。
该 EA 交易将像以前一样从 OnTimer 发送初始请求,同时在 OnTradeTransaction 中逐步设置保护水平并平仓。虽然我们这次不会在各个阶段之间有人为延迟,但状态的顺序本身是许多 EA 交易的标准:开仓、修改、平仓(如果满足某些市场条件,这些条件此处不做赘述)。
两个全局变量将允许你跟踪状态:RequestID 变量包含发送的最后一个请求的 ID(我们期望的结果),Position Ticket 变量包含未平仓仓位订单号。当该仓位尚未出现或不再存在时,该订单号等于 0。
uint RequestID = 0;
|
OnInit 处理程序中启用了异步模式。
int OnInit()
|
OnTimer 函数现在更简短了。
void OnTimer()
|
成功完成请求后,我们只获得 request_id,并将其存储在 RequestID 变量中。状态打印现在包含一个问号,如“OK Open?”,因为实际结果还不知道。
OnTradeTransaction 由于结果的验证以及根据条件执行后续交易订单,OnTradeTransaction 变得更加复杂。我们逐一来看吧。
在这种情况下,整个交易逻辑已经转移到 TRADE_TRANSACTION_REQUEST 类型的分支中。当然,如果需要的话,开发人员也可以使用其他类型,但是我们使用这种类型,因为其包含熟悉的 MqlTradeResult 结构体形式的信息,也就是说,这种类型表示异步调用 OrderSendAsync 的延迟结束。
void OnTradeTransaction(const MqlTradeTransaction &transaction,
|
我们应只对具有我们期望的 ID 的请求感兴趣。所以下一条语句是嵌套 if。在这个块中,我们预先描述了 MqlTradeRequestSync 对象,因为根据计划需要发送常规的交易请求。
if(result.request_id == RequestID)
|
我们只有两种工作请求类型,所以我们为其增加了另一个嵌套的 if。
if(request.action == TRADE_ACTION_DEAL)
|
请注意,TRADE_ACTION_DEAL 用于开仓和平仓,因此还需要一个 if,其中我们可根据 PositionTicket 变量的值来区分这两种状态。
if(PositionTicket == 0)
|
在所考虑的交易策略中没有增加仓位(用于净额结算)或多个仓位(用于对冲),这就是为什么这一部分在逻辑上很简单的原因。真正的 EA 交易需要更多不同的中间状态估计。
接收到开仓通知时,代码块如下所示:
if(PositionTicket == 0)
|
为了简单起见,我们此处省略了错误和重新报价检查。你可以在附加的源代码中看到它们的处理示例。回想一下,所有这些检查都已经在 MqlTradeRequestSync 结构体的方法中实现了,但是它们只在同步模式下工作,因此我们必须显式地重复它们。
下一个用于设置保护水平的代码片段变化不大。
if(PositionTicket == 0)
|
此处唯一的区别是:我们用新的 TRADE_ACTION_SLTP 请求的 ID 来填充 RequestID 变量。
收到关于非零 PositionTicket 交易的通知意味着该仓位已平仓。
if(PositionTicket == 0)
|
如果成功删除,则不能使用 PositionSelectByTicket 选择仓位,因此我们应重置 RequestID 和 PositionTicket。然后,EA 交易会返回到其初始状态,并准备进行下一个买入/卖出-修改-平仓循环。
我们仍需考虑发送平仓请求。在我们简化到最低限度的策略中,这应发生在成功修改保护水平之后。
if(request.action == TRADE_ACTION_DEAL)
|
这就是整个 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 需要更复杂的代码。问题出现了:有没有可能以某种方式将顺序说明交易逻辑(代码透明度)和并行处理(速度)的原则结合起来呢?
这在理论上是可能的,但是需要在某个节点花时间去创建某种辅助机制。