所有执行交易操作的命令都从 MetaTrader 5 客户端通过发送请求的形式被传递到交易服务器。每个请求都应依据请求的操作正确提交;否则它不会通过初步验证并且不会被服务器接受以进一步处理。
被交易服务器接受的请求既能够以挂单的形式,也能够以按市场价立即执行的订单的形式存储在服务器中。订单存储在服务器中,直到它们被执行或被撤销。订单执行的结果即为成交。
成交按给定交易品种改变交易持仓,它可以建仓、平仓、增仓、减仓或反向仓位。因此,一个未平持仓始终是执行一个或多个成交的结果。在《 Orders, Positions, and Deals in MetaTrader 5》(MetaTrader 5 中的订单、持仓和成交)一文中提供了更为详细的信息。
本文说明了从发送请求到在处理之后将其移到交易历史记录的时间内的概念、术语和处理过程。
要执行交易操作,您应将订单发送到交易系统。请求始终是通过从客户端提交一个订单来发送到交易服务器的。无论您如何进行交易,是手动还是使用 MQL5 程序,都必须正确填写请求的结构。
要手动执行交易操作,您应通过按 F9 键来打开填写交易请求的对话窗口。当通过 MQL5 自动交易时,使用 OrderSend() 函数发送请求。因为很多不正确的请求会导致交易服务器出现不希望的过载,在发送之前,必须使用 OrderCheck() 函数检查每个请求。请求的检查结果被放入 MqlTradeCheckresult 结构所描述的一个变量中。
一旦请求到达交易服务器,则将对它进行初步检查:
如果一个请求通过了针对正确与否的初步检查,则它将被放入等待处理的请求队列中。作为请求的处理结果,在交易服务器的资料库中创建一个订单(执行交易操作的命令)。但是,有两种类型的请求不会导致订单的创建:
客户端收到一条指出请求已被接受并且放入 MetaTrader 5 平台的交易子系统中的信息。服务器将接受的请求放入请求队列供进一步处理,这可能导致:
请求在服务器的请求队列中的寿命限制为三分钟。一旦过期,则从请求队列中删除该请求。
事件模型和事件处理函数是以 MQL5 语言实施的。这意味着在对任何预定义事件的响应中,MQL5 执行环境调用相应的函数 - 事件处理程序。有一个预定义函数 OnTrade() 用于处理交易事件;用于处理订单、持仓和成交的代码必须放在此函数中。此函数仅供 EA 交易程序调用,即使您在指标和脚本中添加具有相同名称和类型的函数,也不能在这些指标和脚本中使用此函数。
如果出现以下情况,则服务器生成交易事件:
注意,一个操作可能导致几个事件发生。例如,触发挂单导致两个事件的发生:
不能从相对的报价获得需要的数量时依据一个订单执行几个成交也是导致多个事件的例子。交易服务器创建事件,并将有关信息发送到客户端。这是为什么可以针对表面上是一个的事件多次调用 OnTrade() 函数的原因。这是在 MetaTrader 5 平台的交易子系统中订单处理过程的一个简单例子。
以下是一个示例:在一个要买入 10 手 EURUSD 的挂单等待执行时,卖出 1 手、4 手和 5 手的三个相反报价出现。这三个请求加起来提供所需的 10 手,因此,如果平仓政策允许部分执行交易操作,则会逐个执行它们。
作为 4 个订单的执行结果,服务器将依据现有相反请求执行 1 手、4 手和 5 手的三笔成交。在这个例子中将生成多少交易事件?第一个要卖出 1 手的相反请求将执行 1 手成交的执行。这是第一个交易事件(1 手成交)。但是要买入 10 手的挂单也改变了;现在,它变成要买入 9 手 EURUSD 的订单。等办订单的数量的改变是第二个交易事件(等办订单的数量的改变)。
对于第二笔 4 手成交,将生成另外两个交易事件,有关它们的消息将被发送到发起要买入 10 手 EURUSD 的初始挂单的客户端。
最后 5 手成交将导致三个交易事件的发生:
作为成交的执行结果,客户端依次收到 7 个交易事件 Trade(假定客户端和交易服务器之间的连接是稳定的,没有消息丢失)。必须在 EA 交易程序中使用 OnTrade() 函数处理这些消息。
所有等待执行的订单最终都将被移到历史记录,无论是否满足它们的执行条件,或者是否撤消了它们。订单的撤消有几种情形:
无论何种原因导致活动订单被移到历史记录,有关改变的消息都会被发送到客户端。有关交易事件的消息并不会发送到所有客户端,而是发送到连接到对应帐户的客户端。
这是为什么 OrderSend() 函数的说明文档指出:
返回值
如果请求成功通过基本检查,则 OrderSend() 函数返回 true - 这并不是成功执行交易操作的标志。欲知函数执行结果的更为详细的说明,请分析结构 MqlTradeResult 的字段。
有关交易事件和交易历史记录的改变的消息通过几个渠道传送。使用 OrderSend() 函数发送买入请求时,您可以获得订单单证,该单证是作为请求成功通过验证的结果创建的。同时,订单本身可能并没有出现在客户端中,并且尝试使用 OrderSelect() 选择该订单会导致失败。
在上图中,您可以看到交易服务器如何将订单单证传递给 MQL5 程序,但是有关交易事件 Trade 的消息(新订单的出现)尚未到达。有关活动订单列表改变的消息也没有到达。
有可能存在这样的情形,当有关新订单的出现的 Trade(交易)消息到达程序时,据该订单进行的成交已经执行,因此,该订单已经不在活动订单列表中,而是在历史记录中。这是真实存在的情形,因为相对于通过网络提交消息的当前速度,请求的处理速度要快得多。
交易服务器上的所有操作和交易事件的相关消息的发送是不对称的。找出交易帐户出现什么确切变化只有一种确信的方法。这种方法就是记住交易状态和交易历史记录,然后将其与新状态进行比较。
在 EA 交易程序中跟踪交易事件的算法如下所述:
这是最简单的算法,它允许发现未平持仓(订单、成交)的数量是否改变以及改变的方向。如果存在改变,则我们可以进一步获取更为详细的信息。如果订单的数量没有改变,但是订单本身出现改变,则需要不同的方法;因此,这种情形不在本文的讨论范围之内。
可以在 EA 交易程序的 OnTrade() 和 OnTick() 函数中检查计数器的改变。
让我们一步接一步地编写一个程序示例。
1. 在全局范围内声明订单、成交和持仓的计数器。
int orders; // 活动订单数量 int positions; // 开启仓位数量 int deals; // 交易历史缓存中的交易数量 int history_orders; // 交易历史缓存中的订单数量 bool started=false; // 计数器初始化的标志
2. 在输入变量 days 中设置要载入缓存的交易历史记录的深度(载入在此变量中指定的天数的交易历史记录)。
input int days=7; // 交易历史深度,以天数为单位 //--- 设置全局范围内交易历史的限制 datetime start; // 缓存中交易历史的起始日期 datetime end; // 缓存中交易历史的结束日期
3. 对交易历史记录的计数器和限制进行初始化。在 InitCounters() 函数以外对计数器进行初始化可以使代码具有更好的可读性:
int OnInit() { //--- end=TimeCurrent(); start=end-days*PeriodSeconds(PERIOD_D1); PrintFormat("将要载入的交易历史限制为: 起始日期 - %s, 结束日期 - %s", TimeToString(start),TimeToString(end)); InitCounters(); //--- return(0); }
InitCounters() 函数试图将交易历史记录载入缓存,并且在成功时,对所有计数器进行初始化。同样的,如果成功载入了历史记录,则全局变量 'started' 的值将被设置为 'true',这表示计数器已成功初始化。
//+------------------------------------------------------------------+ //| 初始化仓位,订单和交易的计数器 | //+------------------------------------------------------------------+ void InitCounters() { ResetLastError(); //--- 载入历史 bool selected=HistorySelect(start,end); if(!selected) { PrintFormat("%s. 从缓存中载入历史失败,时间从 %s 到 %s. 错误代码: %d", __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError()); return; } //--- 读取当前数值 orders=OrdersTotal(); positions=PositionsTotal(); deals=HistoryDealsTotal(); history_orders=HistoryOrdersTotal(); started=true; Print("订单,仓位和交易计数器初始化成功"); }
4. 在 OnTick() 和 OnTrade() 处理程序中对交易帐户的状态改变进行检查。首先检查 'started' 变量 - 如果其值为 'true',则调用 SimpleTradeProcessor() 函数,否则调用计数器的初始化函数 InitCounters()。
//+------------------------------------------------------------------+ //| EA订单函数 | //+------------------------------------------------------------------+ void OnTick() { if(started) SimpleTradeProcessor(); else InitCounters(); } //+------------------------------------------------------------------+ //| 当 Trade 事件发生时调用 | //+------------------------------------------------------------------+ void OnTrade() { if(started) SimpleTradeProcessor(); else InitCounters(); }
5. SimpleTradeProcessor() 函数检查订单、成交和持仓的数量是否改变。在进行所有检查之后,我们调用 CheckStartDateInTradeHistory() 函数,该函数在需要时将变量 'start' 的值移到更接近当前时刻的值。
//+------------------------------------------------------------------+ //| 处理交易历史改变的简单实例 | //+------------------------------------------------------------------+ void SimpleTradeProcessor() { end=TimeCurrent(); ResetLastError(); //--- 载入历史 bool selected=HistorySelect(start,end); if(!selected) { PrintFormat("%s. 从缓存中载入历史失败,时间从 %s 到 %s. 错误代码: %d", __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError()); return; } //--- 读取当前数值 int curr_orders=OrdersTotal(); int curr_positions=PositionsTotal(); int curr_deals=HistoryDealsTotal(); int curr_history_orders=HistoryOrdersTotal(); //--- 检查活动订单数是否改变 if(curr_orders!=orders) { //--- 活动订单数改变了 PrintFormat("订单数改变了. 之前数量为 %d, 目前数量为 %d", orders,curr_orders); /* 其他与订单改变关联的行为 */ //--- 更新数值 orders=curr_orders; } //--- 开启仓位数量改变 if(curr_positions!=positions) { //--- number of open positions has been changed PrintFormat("仓位数量改变了. 之前数量为 %d, 目前数量为 %d", positions,curr_positions); /* 其他与仓位改变关联的行为 */ //--- 更新数值 positions=curr_positions; } //--- 交易历史缓存中交易数量的改变 if(curr_deals!=deals) { //--- 交易历史缓存中交易数量已经改变 PrintFormat("交易数量改变了. 之前数量为 %d, 目前数量为 %d", deals,curr_deals); /* 其他与交易数量改变关联的行为 */ //--- 更新数值 deals=curr_deals; } //--- 交易历史缓存中订单数量的改变 if(curr_history_orders!=history_orders) { //--- 交易历史缓存中订单数量已经改变 PrintFormat("历史中订单数量已经改变. 之前数量为 %d, 目前数量为 %d", history_orders,curr_history_orders); /* 其他与交易历史缓存中订单数量改变关联的行为 */ //--- 更新数值 history_orders=curr_history_orders; } //--- 检查是否有必要改变在缓存中请求交易历史的限制 CheckStartDateInTradeHistory(); }
CheckStartDateInTradeHistory() 函数计算当前时刻的交易历史记录请求的开始日期 (curr_start),并将其与变量 'start' 进行比较。如果两者之差大于 1 天,则 'start' 是正确的,并且更新历史订单和成交的计数器。
//+------------------------------------------------------------------+ //| 修改请求交易历史的起始日期 | //+------------------------------------------------------------------+ void CheckStartDateInTradeHistory() { //--- 初始时间段,就像我们马上开始工作 datetime curr_start=TimeCurrent()-days*PeriodSeconds(PERIOD_D1); //--- 确认交易历史起始限制的时间离 //--- 计划时间多于一天 if(curr_start-start>PeriodSeconds(PERIOD_D1)) { //--- 我们需要改正载入历史缓存的起始日期 start=curr_start; PrintFormat("将要载入交易历史的新的起始限制为: 起始日期 => %s", TimeToString(start)); //--- 现在再次根据改正过的时间间隔载入交易历史 HistorySelect(start,end); //--- 改正历史中交易和订单的数量,以便用于将来的比较 history_orders=HistoryOrdersTotal(); deals=HistoryDealsTotal(); } }
本文附带了 Expert Advisor DemoTradeEventProcessing.mq5 的完整代码。
在线交易平台 MetaTrader 5 的所有操作都是异步的,有关交易帐户任何改变的消息都是相互独立发送的。因此,试图依据“一个请求,一个交易事件”的原则跟踪单个事件是不正确的。如果您需要在一个交易事件发生时精确地确定出现什么改变,则您应在每次调用 OnTrade 处理程序时通过将它们的当前状态与以前的状态进行比较来分析您的所有成交、持仓和订单。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程