请求验证:OrderCheck

要执行任何交易操作,MQL 程序必须首先用必要的数据填充 MqlTradeRequest 结构体。在使用交易函数将其发送到服务器之前,必须检查其形式上的正确性,并评估该请求的结果,特别是需要的保证金金额和剩余的可用资金。这项检查由 OrderCheck 函数执行。

bool OrderCheck(const MqlTradeRequest &request, MqlTradeCheckResult &result)

如果没有足够的资金或参数填写不正确,函数将返回 false。此外,当交易被禁用时,该函数还会对拒绝作出反应,无论是在整个终端中还是针对特定程序。有关错误代码,请检查 result 结构体的 retcode 字段。

request 结构体的成功检查和交易环境以 true 状态结束,但是,如果使用 OrderSendOrderSendAsync 函数重复请求的操作,并不能保证请求的操作一定会成功。在不同调用之间,交易条件可能已发生变化,或者服务器上的经纪人可能具有应用于特定外部交易系统的设置,而 OrderCheck 执行的正式验证算法无法满足这些设置。

要获得预期财务结果的说明,应分析 result 结构体的字段。

函数 OrderCalcMargin 仅计算一个拟定仓位所需要的估算保证金,而 OrderCheck 则不同,是以简化模式考虑了交易账户的一般状态。所以,该函数用订单执行后形成的累积变量填充 MqlTradeCheckResult 结构体中的 margin 字段和其他相关字段(margin_freemargin_level)。例如,如果在调用 OrderCheck,时任何金融工具已建仓,并且正在检查的请求增加仓位,则 margin 字段将反映存款的金额,包括以前的保证金负债。如果新订单包含相反方向的操作,保证金将不会增加(实际上,保证金应减少,因为仓位应在净额结算账户上完全平仓,对冲保证金须应用于对冲账户上的相反仓位;但是,该函数不会执行如此精确的计算)。

首先,OrderCheck 对于程序员在熟悉交易 API 的初始阶段非常有用,可在不将请求发送到服务器的情况下进行实验。

我们使用一个简单的非交易性 EA 交易 CustomOrderCheck.mq5 来测试 OrderCheck 函数的性能。为了便于使用,我们将其作为 EA 交易而不是脚本:这样,在使用当前设置启动后 EA 交易还会留在图表上,可以通过更改单个输入参数轻松编辑。而对于脚本,我们必须每次从默认值开始设置这些字段。

要运行检查,让我们在 OnInit 中设置一个计时器。

void OnInit()
{
   // initiate pending execution
   EventSetTimer(1);
}

至于计时器处理程序,主要算法将在此处实现。一开始,我们取消了计时器,因为我们需要代码执行一次,然后等待用户更改参数。

void OnTimer()
{
   // execute the code once and wait for new user settings
   EventKillTimer();
   ...
}

EA 交易的输入参数完全重复交易请求结构体的字段集。

input ENUM_TRADE_REQUEST_ACTIONS Action = TRADE_ACTION_DEAL;
input ulong Magic;
input ulong Order;
input string Symbol;    // Symbol (empty = current _Symbol)
input double Volume;    // Volume (0 = minimal lot)
input double Price;     // Price (0 = current Ask)
input double StopLimit;
input double SL;
input double TP;
input ulong Deviation;
input ENUM_ORDER_TYPE Type;
input ENUM_ORDER_TYPE_FILLING Filling;
input ENUM_ORDER_TYPE_TIME ExpirationType;
input datetime ExpirationTime;
input string Comment;
input ulong Position;
input ulong PositionBy;

其中的许多字段不影响检查和财务计算,但保留了下来以便你验证。

默认情况下,变量的状态对应于以当前金融工具的最小手数建仓的请求。特别是没有显式初始化的 Type 参数会得到 0 值,等于 ENUM_ORDER_TYPE 结构体的 ORDER_TYPE_BUY 成员。在 Action 参数中,我们指定了显式初始化,因为 0 不对应于 ENUM_TRADE_REQUEST_ACTIONS 枚举的任何元素(TRADE_ACTION_DEAL 的第一个元素为 1)。

void OnTimer()
{
   ...
   // initialize structures with zeros
   MqlTradeRequest request = {};
   MqlTradeCheckResult result = {};
   
   // default values
   const bool kindOfBuy = (Type & 1) == 0;
   const string symbol = StringLen(Symbol) == 0 ? _Symbol : Symbol;
   const double volume = Volume == 0 ?
      SymbolInfoDouble(symbolSYMBOL_VOLUME_MIN) : Volume;
   const double price = Price == 0 ?
      SymbolInfoDouble(symbolkindOfBuy ? SYMBOL_ASK : SYMBOL_BID) : Price;
   ...

我们来填充该结构体。真正的机器人通常只需要为几个字段赋值,但是由于这个测试是通用的,我们必须确保用户输入的任何参数都能通过。

   request.action = Action;
   request.magic = Magic;
   request.order = Order;
   request.symbol = symbol;
   request.volume = volume;
   request.price = price;
   request.stoplimit = StopLimit;
   request.sl = SL;
   request.tp = TP;
   request.deviation = Deviation;
   request.type = Type;
   request.type_filling = Filling;
   request.type_time = ExpirationType;
   request.expiration = ExpirationTime;
   request.comment = Comment;
   request.position = Position;
   request.position_by = PositionBy;
   ...

请注意,此处我们还没有标准化价格和手数,尽管在真实程序中必须这样做。因此,这个测试可以输入“不均匀”值,并确保它们会导致错误。在下面的示例中,将启用规范化。

然后我们调用 OrderCheck 并记录 requestresult 结构体。我们仅关注后者的 retcode 字段,所以使用宏 TRCSTR (TradeRetcode.mqh) 将其额外打印为文本形式的“解密”结果。你还可以分析 comment 字符串字段,但是其格式可能会改变,以便更适合向用户显示。

   ResetLastError();
   PRTF(OrderCheck(requestresult));
   StructPrint(requestARRAYPRINT_HEADER);
   Print(TRCSTR(result.retcode));
   StructPrint(resultARRAYPRINT_HEADER2);
   ...

结构体的输出由基于 ArrayPrintStructPrint 辅助函数提供。因此,我们仍然会得到原始的数据显示。具体来说,枚举的元素以数字原样表示。稍后,我们将开发一个更透明(用户友好)的 MqlTradeRequest 结构体输出函数(参见 TradeUtils.mqh)。

为了便于分析结果,在 OnTimer 函数的开始,我们将显示账户的当前状态,最后,为了便于比较,我们将使用 OrderCalcMargin 函数计算给定交易操作的保证金。

void OnTimer()
{
   PRTF(AccountInfoDouble(ACCOUNT_EQUITY));
   PRTF(AccountInfoDouble(ACCOUNT_PROFIT));
   PRTF(AccountInfoDouble(ACCOUNT_MARGIN));
   PRTF(AccountInfoDouble(ACCOUNT_MARGIN_FREE));
   PRTF(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL));
   ...
   // filling in the structure MqlTradeRequest
   // calling OrderCheck and printing results
   ...
   double margin = 0;
   ResetLastError();
   PRTF(OrderCalcMargin(Typesymbolvolumepricemargin));
   PRTF(margin);
}

以下是使用默认设置的 XAUUSD 的日志示例。

AccountInfoDouble(ACCOUNT_EQUITY)=15565.22 / ok

AccountInfoDouble(ACCOUNT_PROFIT)=0.0 / ok

AccountInfoDouble(ACCOUNT_MARGIN)=0.0 / ok

AccountInfoDouble(ACCOUNT_MARGIN_FREE)=15565.22 / ok

AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)=0.0 / ok

OrderCheck(request,result)=true / ok

[action] [magic] [order] [symbol] [volume] [price] [stoplimit] [sl] [tp] [deviation] [type] »

1 0 0 "XAUUSD" 0.01 1899.97 0.00 0.00 0.00 0 0 »

» [type_filling] [type_time] [expiration] [comment] [position] [position_by] [reserved]

» 0 0 1970.01.01 00:00:00 "" 0 0 0

OK_0

[retcode] [balance] [equity] [profit] [margin] [margin_free] [margin_level] [comment] [reserved]

0 15565.22 15565.22 0.00 19.00 15546.22 81922.21 "Done" 0

OrderCalcMargin(Type,symbol,volume,price,margin)=true / ok

margin=19.0 / ok

下一个示例显示了对账户保证金预期增长的估算,其中已经有一个未结头寸,我们将对其翻倍。

AccountInfoDouble(ACCOUNT_EQUITY)=9999.540000000001 / ok

AccountInfoDouble(ACCOUNT_PROFIT)=-0.83 / ok

AccountInfoDouble(ACCOUNT_MARGIN)=79.22 / ok

AccountInfoDouble(ACCOUNT_MARGIN_FREE)=9920.32 / ok

AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)=12622.49431961626 / ok

OrderCheck(request,result)=true / ok

[action] [magic] [order] [symbol] [volume] [price] [stoplimit] [sl] [tp] [deviation] [type] »

1 0 0 "PLZL.MM" 1.0 12642.0 0.0 0.0 0.0 0 0 »

» [type_filling] [type_time] [expiration] [comment] [position] [position_by] [reserved]

» 0 0 1970.01.01 00:00:00 "" 0 0 0

OK_0

[retcode] [balance] [equity] [profit] [margin] [margin_free] [margin_level] [comment] [reserved]

0 10000.87 9999.54 -0.83 158.26 9841.28 6318.43 "Done" 0

OrderCalcMargin(Type,symbol,volume,price,margin)=true / ok

margin=79.04000000000001 / ok

尝试更改任何请求参数,看看请求是否成功。不正确的参数组合将导致 标准列表中的错误代码,但是由于无效选项比保留选项多得多(最常见的错误),该函数通常会返回通用代码 TRADE_RETCODE_INVALID (10013)。在这方面,建议使用更高程度的诊断来实现你自己的结构体检查。

向服务器发送真实请求时,可在各种不可预见的情况下使用相同的 TRADE_RETCODE_INVALID 代码,例如,当试图重新编辑一个在外部交易系统中已经开始(但尚未完成)修改操作的订单时。