- EA 交易的主要事件:OnTick
- 基本原理和概念:订单、交易和仓位
- 交易操作类型
- 订单类型
- 按价格和数量划分的订单执行模式
- 挂单到期日期
- 期货订单的保证金计算方法:OrderCalcMargin
- 估算交易操作的利润:OrderCalcProfit
- MqlTradeRequest 结构体
- MqlTradeCheckResult 结构体
- 请求验证:OrderCheck
- 请求发送结果:MqlTradeResult 结构体
- 发送交易请求:OrderSend 和 OrderSendAsync
- 买入和卖出操作
- 修改仓位的止损和/或止盈水平
- 跟踪止损
- 平仓:全部和部分
- 反向平仓:全部和部分
- 挂单
- 修改挂单
- 删除挂单
- 获取活动订单列表
- 订单特性(现行和历史)
- 用于读取活动订单特性的函数
- 按特性选择订单
- 获取仓位列表
- 仓位特性
- 用于读取仓位特性的函数
- 交易特性
- 从历史中选择订单和交易
- 用于从历史中读取订单特性的函数
- 用于从历史中读取交易特性的函数
- 交易类型
- OnTradeTransaction 事件
- 同步和异步请求
- OnTrade 事件
- 监测交易环境变化
- 创建多交易品种 EA 交易
- EA 交易的优势和局限性
- 在 MQL 向导中创建 EA 交易
同步和异步请求
在学习细节之前,请注意,每个 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
|
OnCalculate 处理程序为空。
int OnCalculate(const int rates_total,
|
在代码中,我们需要一个现成 转换器 (double 和 ulong 的相互转换),因为如果使用简单的类型转换将缓冲区单元写入 ulong,它们可能会破坏大的 ulong 值(参见 实数型)。
#include <MQL5Book/ConverterT.mqh>
|
以下是 OnTradeTransaction 函数。
#defineFIELD_NUM6// the most important fields in MqlTradeResult
|
我们决定仅保留 MqlTradeResult 结构体中最重要的六个字段。如果需要,可以将该该机制扩展到整个结构体,但是要传递 comment 字符串字段,需要一个字符数组,因此必须为该数组保留相当多的元素。
因此,每个结果现在占据六个连续的缓冲区单元。这六个单元中的第一个单元的索引是根据请求 ID 确定的:将这个数字乘以 6 即可。因为可能有许多请求,所以条目根据环形缓冲区的原理工作,即,通过用余数(“%”)除以指示符缓冲区的大小(向上舍入到 6 的柱线数量)来规范化结果索引。当请求数量超过该大小时,记录将从初始元素开始循环。
由于柱线编号受新柱线形成的影响,建议将指标放在大的时间框架上,如 D1。然后,只有在一天开始的时候,才有可能(但概率较低)出现这样的情况:在处理下一个交易的过程中,指标中的柱线编号会直接发生变化,然后 EA 交易不会读取该指标记录的结果(可能会遗漏一个交易)。
指标准备就绪。现在让我们开始实现测试 EA 交易 OrderSendTransaction3.mq5 的新修改版本(这是其最新版本)。我们来介绍指标句柄的 handle 变量,并在 OnInit 中创建指标。
int handle = 0;
|
为了从指标缓冲区读取查询结果,我们来准备一个辅助函数 AwaitAsync。作为其第一个参数,它用于接收对 MqlTradeRequestSync 结构体的引用。如果成功,则从带有 handle 的指标缓冲区获得的结果将被写入该结构体。我们感兴趣的请求标识符应已经在嵌套结构体中,即 result.request_id 字段中。当然,此处必须按照同样的原理来读取数据,也就是六条柱线。
#define FIELD_NUM 6 // the most important fields in MqlTradeResult
|
现在我们有了这个函数,让我们以异步/同步的方式编写一个交易算法:作为一个直接的步骤序列,每个步骤都会等待前一个步骤准备就绪,因为通知来自并行指标程序,同时保留在一个函数中。
void OnTimer()
|
第 1 步.
Print("Start trade");
|
第 2 步.
Print("SL/TP modification");
|
第 3 步.
Print("Close down");
|
请注意,现在 completed 方法调用不是在发送请求之后,而是在 AwaitAsync 函数收到结果之后。
除此之外,一切都与该算法的第一个版本非常相似,但现在起建立在异步函数调用的基础上,并可对异步事件做出反应。
在这个特定的示例中,对单个仓位的一系列操作可能看起来并不重要。但是,我们可以使用相同的技术来发送和控制一批订单。优势就显而易见了。稍后,我们将在网格 EA 交易的帮助下演示这一点,同时比较两个函数的性能:OrderSend 和 OrderSendAsync。
但是现在,当我们完成 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 函数写入。对我们而言很重要的一点是,在包含结果的结构体中,订单号(deal 和 order)、行权价格 (price) 和交易量 (volume) 是最新的。
基于前面考虑的 OrderSendTransaction3.mq5 示例,让我们创建一个新版本的网格 EA 交易 PendingOrderGrid3.mq5(以前的版本在 用于读取仓位特性的函数一节中提供)。该 EA 交易能够按用户选择在同步或异步模式下设置一个完整的订单网格。我们还可以检测设置完整网格的时间,以便进行比较。
该模式由输入变量 EnableAsyncSetup 控制。handle 变量被分配给指标句柄。
input bool EnableAsyncSetup = false;
|
在初始化期间,在异步模式的情况下,我们可创建一个 TradeTransactionRelay 指标的实例。
int OnInit()
|
为了简化编码,我们在 SetupGrid 函数中用一维数组替换了二维 request 数组。
uint SetupGrid()
|
在数组的循环中,我们使用寻址请求 request[i * 2 + 1],而不是请求 request[i][1] 类型的调用。
出于以下原因,需要进行这个小的转换。因为我们在创建网格时使用这个结构体数组进行查询,并且我们需要等待所有的结果,所以 AwaitAsync 函数现在应引用该数组作为其第一个参数。一维数组更容易处理。
对于每个请求,根据其 request_id 计算其在指标缓冲区中的偏移量:所有偏移量都放入 offset 数组中。当收到请求确认时,通过在数组中写入值 -1,将数组的相应元素标记为已处理。执行的请求数在 done 变量中统计。当这个数量等于数组的大小时,整个网格准备就绪。
bool AwaitAsync(MqlTradeRequestSyncLog &r[], const int _handle)
|
回到 SetupGrid 函数,让我们展示一下在请求发送循环之后 AwaitAsync 是如何被调用的。
uint SetupGrid()
|
如果在设置网格时发生超时(并非所有请求都会在分配的时间内收到确认),我们将返回 TRADE_RETCODE_ERROR 代码,EA 交易将尝试“回滚”其创建的内容。
需要注意的是,异步模式只是为了在我们需要发送一批请求时建立一个完整的网格。否则,仍会使用同步模式。因此,我们必须在发送循环之前将 MqlTradeRequestSync::AsyncEnabled 标志设置为 true,之后再将其设置回 false。但是,请注意以下几点。循环内部可能会出现错误,因此循环会过早终止,从服务器返回最后一个代码。因此,如果我们在循环之后进行异步复位,就不能保证其能够复位。
为了解决这个问题,在 MqlTradeSync.mqh 文件中添加了一个小的 AsyncSwitcher 类。该类可通过其构造函数和析构函数来控制异步模式的启用和禁用。这与 文件描述符管理一节中讨论的 RAII 资源管理概念一致。
class AsyncSwitcher
|
现在,为了安全地临时激活异步模式,我们可以简单地在 SetupGrid 函数中描述本地 AsyncSwitcher 对象。退出该函数时,代码将自动返回到同步模式。
uint SetupGrid()
|
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
|
由于所有请求都是并行执行的,所以总的发送时间(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 的结构体数组,不再翻倍。请求数量也将减少到原来的一半:用于这些请求的方法仅限于 buyLimit 和 sellLimit。
CheckGrid 函数可以不同的方式检查网格的完整性。以前,在有限制的层级上没有成对的止损限价订单被认为是错误的。当相邻层级在服务器上触发限价止损订单时,可能会发生这种情况。但是,如果一根柱线上出现强烈的双向价格波动(尖峰),这种方案就无法恢复网格:它不仅会取消原始限价订单,还会取消止损限价订单产生的新限价订单。现在,该算法会如实检查当前价格两侧的空缺层级,并使用 RepairGridLevel 在该处创建限价订单。这个辅助函数以前下达过限价止损订单。
最后,OnTradeTransaction 处理程序出现在 PendingOrderGrid4.mq5 中。触发挂单会导致一个交易的执行(以及需要纠正的网格配置改变),所以我们通过给定的交易品种和魔术数字来控制交易。当检测到一个交易时,CheckGrid 函数会被立即调用,除此之外,它还会在每个柱线的开始处执行。
void OnTradeTransaction(const MqlTradeTransaction &transaction,
|
应注意的是,OnTradeTransaction 的存在并不足以编写能够抵抗不可预见外部影响的 EA 交易。当然,许多事件允许你对情况做出快速响应,但是我们不能保证 EA 交易在一段时间内不会因为某种原因而平仓(或离线)并跳过某个交易。因此,OnTradeTransaction 处理程序只能帮助加速程序在无事件时也能执行的流程。特别是在启动后正确恢复其状态。
但是,除了 OnTradeTransaction 事件之外,MQL5 还提供了另一个更简单的事件:OnTrade。