MT4市场报价品种不全 - MT4持仓超时自动平仓EA编写实操详解

EA里计算持仓时间的核心逻辑
要实现在MT4里按持仓时间自动平仓,首先得搞清楚EA是怎么知道单子持有多久的。MT4的订单信息里有一个很关键的函数叫OrderOpenTime(),它返回的是订单开仓时的服务器时间戳,这个时间是以秒为单位的整数。我们只要拿当前时间减去开仓时间,就能得到持仓的秒数,然后再除以3600就能换算成小时数。
具体到代码实现,可以用TimeCurrent()函数获取当前服务器时间,然后做减法。这里要特别提醒一下,MT4的服务器时间通常是GMT+2或者GMT+3,但这不是重点,因为TimeCurrent()和OrderOpenTime()返回的都是同一时区的时间戳,相减之后得到的时间差是准确的,不受时区影响。这一点比用本地时间靠谱多了,因为本地时间可能会因为夏令时或者手动修改而出现误差。
还有一个细节需要注意,就是OrderSelect()函数的使用。在遍历持仓订单之前,必须先选中一笔订单才能读取它的信息。通常我们会用一个循环,从0到OrdersTotal()-1,然后用OrderSelect(i, SELECT_BY_POS)来逐笔选中订单。选中的时候最好加上MODIFY_TRADE的权限参数,这样后面如果要做平仓操作就直接有权限了。
代码写到这里,其实核心逻辑就三行:获取订单开仓时间、计算时间差、判断是否超过阈值。但实际写EA的时候,还有不少边界情况要处理,比如账户里有多笔订单时怎么区分处理,是全部平掉还是只平特定品种的订单,这些都需要在代码里明确逻辑。
把时间判断和平仓动作串起来
有了时间计算的逻辑,接下来就要把它和平仓动作连接起来。MT4的订单平仓函数是OrderClose(),它需要传入单号、手数和价格参数。价格参数要根据订单类型来定,买单用Bid价格平仓,卖单用Ask价格平仓。这个细节如果搞错了,订单是平不掉的,而且会报错。
在EA的结构上,我们需要把这段判断逻辑放在start()函数里,也就是每个价格变动时都会触发执行。不过有个问题,如果价格长时间不动,start()函数就不会被调用,那持仓超时了也检测不到。解决办法是用OnTimer()定时器函数,或者用while循环配合Sleep()函数来强制轮询。我个人比较推荐用OnTimer(),因为它是异步的,不会阻塞主线程,而且可以精确控制检查间隔,比如每30秒检查一次。
设置定时器需要在EA的init()函数里调用EventSetTimer(30),这里的30就是间隔秒数。然后在OnTimer()函数里写上检查逻辑。这样即使价格不动,只要定时器在跑,持仓时间一到就会触发平仓。不过要注意,有些MT4的服务器可能对OnTimer()的支持不够稳定,稳妥起见还是用start()配合一个全局时间变量来记录上次检查时间,用当前时间减去上次检查时间来判断是否该执行检查。
还有一个常见的坑是EA在测试环境跑得好好的,但挂到实盘就不工作了,这多半是因为OrderClose()执行失败却没有处理错误码。建议在OrderClose()之后检查返回值,如果返回false,就调用GetLastError()打印错误码,方便排查问题。比如错误码138表示需要重新报价,这时候可以稍等几毫秒再重试一次。
参数设置和阈值变量化
为了让EA更灵活,我们应该把“X小时”做成一个外部参数,这样不用改代码就能调整持仓时间阈值。在EA开头用extern关键字声明一个变量,比如extern double MaxHoldHours = 4.0;,这样在MT4的输入参数面板里就能直接修改变量值了。这里用double类型是考虑到可能有人想设置成3.5小时这种非整数时间。
还有就是要不要区分买单和卖单的问题。
有些人的策略可能是买单允许持仓8小时,卖单只允许持仓2小时。那就可以定义两个参数,比如extern double MaxHoldLongHours = 8.0;和extern double MaxHoldShortHours = 2.0;,然后在判断逻辑里根据OrderType()的值来分别处理。OrderType()返回0代表买单,1代表卖单,判断起来很方便。
另外,平仓时的手数一定要用OrderLots()来获取,不要用自己记的变量。因为如果单子被部分平仓过(虽然MT4原生不支持部分平仓,但有些脚本可以实现),实际剩余手数可能和原始手数不一样。用OrderLots()读取的是当前最新的手数,这个才是准确的。
说到参数,还有一个容易被忽略的点,就是滑点的设置。OrderClose()的第四个参数就是滑点,单位是点数。如果你设置的滑点太小,比如0,那在行情波动大的时候可能一直成交不了。建议设置成30到50个点,这样既能保证成交速度,又不会滑太多。不过这个值也要根据你交易的品种来调整,黄金和外汇的波动特性不一样,滑点容忍度也不同。
实战测试和常见问题排查
EA写完之后,千万不能直接挂实盘,一定要先在MT4的策略测试器里跑一遍历史数据。测试的时候要把“持仓时间”这个条件触发的情况专门验证一下。有个小技巧,在测试器里把时间周期设置成M1,然后开仓后等一会儿就能看到平仓动作了,不用真的等几个小时。当然,如果你设置的是4小时,测试器里会加速运行,但实际耗时还是很长的,建议把阈值临时改成0.05小时(也就是3分钟)来快速验证逻辑是否正常。
跑测试的时候要重点看两个地方:一是平仓动作有没有被触发,二是平仓的价格是否合理。如果发现平仓价格和当时市场的Bid/Ask差距太大,那说明滑点参数设置得不太合适。另外,测试日志里如果有“OrderClose error 4108”之类的报错,那说明订单号不对,多半是OrderSelect()没有正确选中订单。
还有很多人会遇到的问题是EA在回测时正常,但实盘却不执行。这通常是因为实盘和回测的服务器时间不同步导致的。回测时MT4使用的是历史数据的时间戳,而实盘是实时时间。如果你在代码里用了本地时间函数TimeLocal()而不是TimeCurrent(),那实盘时可能会因为本地时间和服务器时间差异导致计算错误。所以再次强调,一定要用TimeCurrent()。
最后提醒一下,如果你的账户里有多个EA同时在跑,或者你自己也会手动操作平仓,那EA在平仓前最好再检查一下订单是否仍然存在。因为有可能在EA判断超时的那一瞬间,你已经手动平掉了这笔单子,那OrderClose()就会报错。用OrderSelect()的返回值就能判断订单是否有效,如果返回false就直接跳过,不用再处理了。