使用 MQL 4 语言进行交易 EA 开发并非易事,原因有如下几个方面:
我们应公平看待 MetaTrader 4 交易平台 - 相比早期的可选方案,编写交易 Expert Advisor 的现有编程语言已经是一个巨大的进步。编译器在编写正确 EA 时提供了极大帮助。点击’Compile(编译)’后,编译器将马上报告你代码中的所有语法错误。如果我们使用解释性语言进行处理,则这些错误只能在 EA 操作期间被发现,这样就会提高开发难度,增加开发时间。然而,EA 除了语法错误外,还可能包含逻辑错误。现在我们就要来处理这种错误。
尽管没有交易 EA 可以在不使用内置函数的情况下工作,但我们还是应该在分析这些函数返回的错误时尝试简化操作。首先,让我们来查看与交易操作直接相关的函数运行的结果,忽视这些函数的错误可能会导致 EA 受到严重影响。但是,更多的自变量指向了其他内置函数。
遗憾的是,使用 MQL4 选项无法编写出一个泛化的库来处理所有可能出现的错误情况。我们需要针对每个单独的情况进行错误的区别处理。好消息是——我们无需处理所有错误,因为许多错误在 EA 开发阶段就被消除了。但通过此举可及时检测到这些错误。让我们来看在 MQL 4 中两个 EA 的典型错误这一示例:
1) Error 130 - ERR_INVALID_STOPS
2) Error 146 - ERR_TRADE_CONTEXT_BUSY
其中一个情况是,当第一个错误出现时,EA 尝试将挂单放置在过于靠近市场的位置。它的存在在某些情况下会严重破坏 EA 的特性。例如,假设 EA 在已经建立盈利仓位的情况下每隔 150 点削减利润。如果下一次尝试时发生了错误 130,则价格将回落至先前的止损水平,这可能会剥夺你的合法利润。尽管存在这种可能性,这种错误在初始阶段就可以通过将函数插入 EA 代码中进行消除,并将价格和止损水平之间可接受的最短距离考虑在内。
第二个‘交易文本已满’错误则无法彻底消除。当数个 EA 在一个终端上操作时,我们便可面临这种错误情况:一个 Expert Advisor 在尝试建仓,第二个也在进行相同的操作。因此,必须始终处理这类错误。
因此我们总是需要知道,EA 操作期间是否有任何内置函数会返回错误。可以使用简单的额外函数实现这一点:
#include <stderror.mqh> #include <stdlib.mqh> void logError(string functionName, string msg, int errorCode = -1) { Print("ERROR: in " + functionName + "()"); Print("ERROR: " + msg ); int err = GetLastError(); if(errorCode != -1) err = errorCode; if(err != ERR_NO_ERROR) { Print("ERROR: code=" + err + " - " + ErrorDescription( err )); } }
最简单情况下,应使用如下方法:
void openLongTrade() { int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask + 5, 5, 0, 0); if(ticket == -1) logError("openLongTrade", "could not open order"); }
函数 logError() 的第一个参数显示函数名称,名称内检测出一个错误,在我们的例子中则是函数 openLongTrade()。如果我们的 Expert Advisor 在几个位置调用了函数 OrderSend(),则我们可以精确地判定错误发生的情况。第二个参数提供了错误描述,让我们得以了解在函数 openLongTrade 中检测出错误的确切位置。描述可长可短,包括了传递到内置函数中的所有参数值。
我倾向于最后一个变量,因为如果发生错误,你可以马上获取分析所需要的全部信息。假设当前价格在调用 OrderSend() 之前已大幅偏离了已知最新价格。如此以来,将发生错误,而以下代码行将出现在 EA 日志文件中:
ERROR: in openLongTrade()
ERROR: could not open order
ERROR: code=138 - requote
即可清晰看到:
现在让我们查看函数 logError() 的第三可选参数。当我们要处理某个错误类型时,会需要用到此参数,其他错误将和此前的错误一样,被包括在日志文件内:
void updateStopLoss(double newStopLoss) { bool modified = OrderModify(OrderTicket(), OrderOpenPrice(), newStopLoss, OrderTakeProfit(), OrderExpiration()); if(!modified) { int errorCode = GetLastError(); if(errorCode != ERR_NO_RESULT ) logError("updateStopLoss", "failed to modify order", errorCode); } }
内置函数 OrderModify() 在函数 updateStopLoss() 中被调用。这个函数和 OrderSend() 在错误处理方面有些许不同。如果已更改订单的参数和其当前参数没有区别,则函数将返回错误 ERR_NO_RESULT。如果这种情况在 EA 中是可以接受的话,我们应忽略此错误。为此,我们分析由 GetLastError() 返回的值。如果出现带代码 ERR_NO_RESULT 的错误,我们将不在日志文件中做记录。
然而,如果出现另一错误,则应效仿之前做法报告错误。正是为此,我们将函数 GetLastError() 的结果保存到中间变量,并使用第三个参数将其传递至函数 logError()。实际上内置函数 GetLastError() 将在调用后自动归零最后的错误代码。如果我们不将错误代码传递至 logError(),则 EA 日志文件将包含带有代码 0 和描述为“没有错误”的错误。
处理其他错误时应采取相似措施,例如重新报价。主要思路是,仅处理需要处理的错误,并将其他错误传递进函数 logError() 。这样一来,我们就会在 EA 运行期间获悉是否出现了意外错误。分析日志文件后,我们就会知道是对错误进行单独处理,还是将其消除以改进 EA 代码。这种方式,将让我们的工作更为简单,也可以缩短我们修复问题的时间。
Expert Advisor 代码中的逻辑错误可能会带来不少麻烦。由于缺少 EA 单步调试选项,纠正这种错误非常让人头疼。目前用于诊断这种错误的主要工具是内置函数 Print()。我们可以使用这个函数打印重要变量的当前值,并记录 EA 运行流程。在进行可视化测试时,内置函数 Comment() 可协助调试 EA。一般而言,当确认 EA 工作出错后,我们必须添加函数 Print() 的临时调用,并在假定错误出现的位置记录 EA 的内部状态。
但针对很难检测的错误,我们有时需要添加数十次函数 Print() 的诊断调用。检测和修复错误后,必须对函数调用进行删除或添加注释,以避免 EA 日志文件过载和测试速度放慢。更糟糕的情况是,如果 EA 代码已经包括函数 Print() 来对不同状态进行定期记录。则临时调用的函数 Print() 无法通过 EA 代码内简单的 ‘Print’ 搜索进行删除。你必须考虑是否要删除函数。
举例而言,记录函数 OrderSend()、OrderModify() 和 OrderClose() 的错误时,可以将变量买入价和卖出价的当前值记录进日志文件内。这让我们能够更简单地找到如 ERR_INVALID_STOPS 和 ERR_OFF_QUOTES 这样的错误原因。
为将此诊断信息写入日志文件,我建议使用以下额外函数:
void logInfo(string msg) { Print("INFO: " + msg); }
原因在于:
添加和删除函数 Print() 的临时诊断调用需较长时间。这就是为何我建议使用新的方法,此方法在检测代码内的逻辑错误时非常有效,还可以帮助我们节省时间。让我们来分析下面这个简单函数:
void openLongTrade(double stopLoss) { int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask, 5, stopLoss, 0); if(ticket == -1) logError("openLongTrade", "could not open order"); }
很显然,在此情况下,当我们建立多头头寸时,如果 EA 正确运行,则参数 stopLoss 的值无法大于或等于当前买入价。即以下语句正确:调用函数 openLongTrade() 时,始终满足 stopLoss < Bid 的条件。如我们所知,已处在编写已分析函数的阶段,我们可以按照以下方式来使用函数:
void openLongTrade( double stopLoss ) { assert("openLongTrade", stopLoss < Bid, "stopLoss < Bid"); int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask, 5, stopLoss, 0); if(ticket == -1) logError("openLongTrade", "could not open order"); }
即,我们通过新的附加函数 assert() 将语句插入代码中。函数本身非常简单:
void assert(string functionName, bool assertion, string description = "") { if(!assertion) Print("ASSERT: in " + functionName + "() - " + description); }
函数的第一个参数是函数名称,在此函数内对我们的条件进行检查(与函数 logError() 类似)。第二个参数显示条件检查的结果。第三个参数表示其描述。如此一来,如果无法满足预期条件,则 EA 日志文件将包含以下信息:
作为描述,如果能够帮助找到错误原因,我们可以显示诸如条件本身,或包含检查条件时受控变量的值的更详尽描述。
当然,给出的示例已经过最大程度简化。但我希望此示例能清晰反映思路。随着 EA 功能提升,我们可以清晰看到正确操作 EA 的方式,以及可接受和不可接受的条件和输入参数。通过函数 assert() 将其插入 EA 代码中,我们掌握了 EA 运行逻辑受到破坏的位置的有用信息。此外,我们一定程度上减少了添加和删除临时调用函数 Print() 的必要性,因为函数 assert() 仅在检测到预期条件有背离的情况下,才会将生成的诊断信息传入 EA 日志文件。
还有一个有用方法是,在每次除法运算前使用此函数。实际上,这样或那样的逻辑错误有时会导致除数为零的情况。这种情况下,Expert Advisor 将停止运行,而日志文件中将出现一行字:‘零除’。如果在代码中多次使用除法运算,则可能很难检测到出错的位置。此时,使用函数 assert() 会非常管用。我们仅需要在每个除法运算前加入相应的检查标记:
assert("buildChannel", distance > 0, "distance > 0"); double slope = delta / distance;
如果面临除数为零的情况,则应仔细查看日志文件以找到出错的确切位置。
记录错误的推荐函数有助于在日志文件中更简单地找到错误。我们仅需要以文本模式打开日志文件,然后以”ERROR:”和”ASSERT:”搜索。然而,我们有时会面临问题,我们可能会在开发时忽略该内置函数的调用结果。有时还会忽略除数为零的情况。我们应如何在满是建仓、平仓和改变仓位信息的成千上万的代码行中检测到此类错误的信息?如果你面对这种问题,我向你推荐以下方法。
打开 Microsoft Excel 然后将 EA 操作日志文件下载为 CSV-file,表明应将一个空格或多个空格作为分隔符使用。然后,启用“Autofilter”。这将让你可以通过两个相邻的列(哪些列不难理解)来便捷地查看过滤器选项,从而了解日志文件是否含有错误,以及该错误是否通过终端写入日志文件。所有由函数 logInfo()、logError() 和 assert() 生成的条目均以前缀(”INFO:”、 “ERROR:” 和 “ASSERT:”)开头。终端关于错误的条目可轻易在关于处理订单的典型条目的少数变量中发现。
这个任务可以通过一种更加简洁的方式来解决。例如,如果你熟悉文本文件的处理工具,你便可以编写仅显示指向 EA 运行错误的 EA 日志文件的小脚本。我强烈建议你在一个较长期间内让每个 Expert Advisor 都通过测试程序,然后使用所述方法来分析其运行日志文件。这让你在演示账户上运行 EA 前,能够检测出大部分可能错误。此后,日志文件会时不时进行运行分析,助你及时检测错误,特别是针对实时帐户的 EA 运行。
所述附加函数和简单方法可简化已写入编程语言 MQL4 的 EA 代码中的错误检测和修复流程。为你方便起见,上述函数已纳入随附文件中。只需使用目录 #include 即可添加文件到你的 EA 上。希望对 Expert Advisor 的强大性和正确性有所怀疑的交易者能够成功运用所述方法。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程