大概两个月前,一个以前共事过的交易员给我发了条语音,语气挺急的。他说他的EA开单一切正常,但就是不会平仓。止损止盈明明设好了,价格打到了却一点反应没有。单子就那么一直挂着,直到他手动去关。
我把代码要过来看了一下。问题其实不复杂,就是两个小毛病叠在一起了。一个是写代码的人对MQL4里if条件的判断逻辑理解有偏差,另一个是OrderSend和OrderModify之间有个小时间差他没处理。这两种情况你去翻那些入门教程,基本都不会专门拿出来讲。
零和一之间的陷阱
那个EA里有一段判断要不要开仓的代码,结构大概是这样的:
``
if (CheckOpenConditions()) {
OrderSend(Symbol(), OP_BUY, Lots, Ask, 3, 0, 0, "Buy", Magic, 0, clrGreen);
}
int CheckOpenConditions() {
// 一些逻辑判断
return(0); // 条件不满足时返回0
}
`
问题出在后面的平仓函数上。MQL4里if判断数字的时候,0代表假,非0代表真。这本身没问题,但那个EA里另一个关单函数的设计是:成功返回0,失败返回-1。
`
int CloseOrder(int ticket) {
if (OrderClose(ticket, Lots, Bid, 3, clrRed)) {
return(0); // 成功!
} else {
return(-1); // 失败!
}
}
`
调用的时候是这样写的:
`
if (CloseOrder(ticket)) {
Print("订单关闭成功");
} else {
Print("订单关闭失败");
}
`
这就是个经典陷阱了。因为-1不是0,if(-1)在MQL4里被认为是true。所以当CloseOrder()返回-1表示失败的时候,调用它的代码以为成功了,啥也没干。那个交易员盯着图表纳闷了半天为什么EA不平仓,其实EA一直觉得自己已经平了。
OrderModify那个要命的时间差
另一个问题更隐蔽。EA开仓的时候不带止损止盈,然后用OrderModify()单独加上去。代码长得挺标准:
`
int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask, 3, 0, 0, "MyOrder", 12345, 0, clrGreen);
if (ticket > 0) {
OrderSelect(ticket, SELECT_BY_TICKET);
double stopLoss = Ask - 50 _Point;
double takeProfit = Ask + 100 _Point;
OrderModify(ticket, OrderOpenPrice(), stopLoss, takeProfit, 0, clrNONE);
}
`
这个写法我在论坛上见过无数次,甚至有些付费EA里面也这么写。问题就出在OrderSend()返回和OrderModify()执行之间那零点几秒的时间差。如果价格刚好在那会儿跳了一下,算好的止损价可能就变成无效价位了。比如买单的止损算出来比当前市价还高,OrderModify()就直接失败,而且代码里也没检查返回值,就这么静悄悄地挂了。
我是怎么修的
修起来其实比找问题简单。if判断那个问题,我把所有返回值逻辑都改成明确的了:
`
bool CheckOpenConditions() {
// 满足条件返回true,否则返回false
if (someLogic) return(true);
return(false);
}
bool CloseOrder(int ticket) {
if (OrderClose(ticket, Lots, Bid, 3, clrRed)) {
return(true);
} else {
Print("关单失败。错误码: ", GetLastError());
return(false);
}
}
`
这样if (CloseOrder(ticket))就只会在明确返回true的时候才跑,不会把-1当成成功。
OrderModify的问题更好办。把止损止盈直接塞进OrderSend()里一次性提交,不用分两步走:
`
double stopLoss = NormalizeDouble(Ask - 50 _Point, _Digits);
double takeProfit = NormalizeDouble(Ask + 100 _Point, _Digits);
int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask, 3, stopLoss, takeProfit, "SafeOrder", 12345, 0, clrGreen);
if (ticket < 0) {
Print("开仓失败。错误码: ", GetLastError());
}
`
这样就没有时间差了,止损止盈和开仓请求一起发出去,不存在中间环节失败的可能。MQL4官方文档里其实没有明确说哪种写法更好,但实战经验告诉我,一次提交的方式在实盘环境下稳得多。
顺便提一句iCustom的坑
那次调代码的时候还注意到一个问题。那个EA用iCustom()去读自定义指标的信号,但传进去的参数顺序跟指标里外部变量的声明顺序对不上。MQL4教程里提到过,iCustom()的参数必须和自定义指标外部变量的声明顺序一致。这个很容易忽略,因为编译器不会报错,你只能拿到一堆垃圾数据,然后交易逻辑就全乱套了。
我当时是翻回指标源码,一个一个数参数才把顺序捋顺的。从那以后我每个用到iCustom()的EA都会专门写个注释把参数顺序标清楚,省得以后再踩坑。
那次排查之后
那套EA改完之后就没再出过问题。策略本身没变,只是把逻辑写规矩了,把执行流程理顺了。说到底MQL4这个语言看着简单,但恰恰是因为简单,很多坑得靠自己踩过才知道。它不会帮你去判断-1该不该当成真,也不会提醒你OrderModify()`可能失败。这些细节没处理好的话,EA在实盘上表现和回测里可以完全是两回事。参考来源:MQL4 Book – 联合使用程序(book.mql4.com),MQL4官方文档 – 交易函数(docs.mql4.com),MQL4教程 – if-else逻辑陷阱(book.mql4.com)。
本文首发于FXEAR.com,原创内容,未经授权禁止转载。