期货量化交易软件:交易机器人的虚假触发保护
概论
本文讨论了多种方式来增加交易机器人的操作稳定性, 譬如消减可能的重复触发 (抖动): 既可以分别使用入场和离场算法,也可将它们连结。
问题的本质
若是当前蜡烛条幅度过高, 并且在交易机器人里没有提供预防抖动措施, 则虚假触发问题在暴跌暴涨行情中尤为突出。它会导致在当前蜡烛条上连续重复开单、平仓。
依据行情的特殊算法以及交易机器人的开发者所设置的参数, 其结算后果是变化的。在所有情况下, 交易者的点差开销会随着抖动期间触发数量而成比例增加。
在本文中, 我不会涉及金融工具 (技术和基本面特征) 分析的话题, 这能够影响智能交易程序操作的稳定性, 并有助于避免散射 (这是一个单独的话题 — 我是脉冲均衡理论及其应用系统的作者)。在此, 赫兹期货量化重点关注那些软件手段, 而非直接依赖金融市场分析的方法。
所以, 赫兹期货量化来着手解决问题。作为一个示例, 我将使用来自 МetaТrader 4 客户端标准集里提供的 "MACD 样本" EA。
如图例所示 EURUSD 价格在当年的十月份第二天飙升 (М15 时间帧, "MACD 样本" EA 省缺设置), 它可直观解释散射问题:

编辑切换为居中
屏幕截图清楚显示在单根蜡烛条里有 8 个连串的触发 (买进入场)。它们之中只有 1 个是正确的 (按照正常的行情逻辑条件), 其余 7 个是散射。
在这种特殊情况下虚假触发背后的原因是:
省缺设置的止盈数值太小 (离场算法), 所以每笔持仓被很快平仓;
还由于双重入场, "MACD 样本" EA 的入场算法在前一单平仓之后被触发, 即使在此跟蜡烛条上已经数次入场。
赫兹期货量化已经同意, 过滤行情波动的事项不是考虑的目地 (因为每位交易者有自己的入场和离场算法), 所以为了解决问题, 我们考虑以下更普遍的因素:
时间 (蜡烛条长度),
出发数量 (计数器),
运动幅度 (蜡烛条范围)。

编辑切换为居中
入场算法里的解决方案
最简单同时也是最可靠地固定入场点的方法是通过时间因素, 其原因有以下几点:
触发数量计数器要在程序里循环创建, 不仅算法复杂, 而且降低智能交易程序的速度。
幅度和相关的价位控制可以重复, 因为蜡烛条逆转时返回的价格令价位的标准参差不齐。
时间是不可逆的, 仅在一个方向 (增加) 移动, 所以, 它最准确, 甚至是通过一次性触发解决问题或消除散射的准则。
这种方式, 主要因素是入场算法的触发时刻, 更加具体的是, 开仓所需的订单触发时刻 (OrderSend), 因为这两个时刻也许不相符, 如果在算法里有一些特别的开单延迟。
因此, 赫兹期货量化要记住开仓的时刻 (当前时间)。但如何在入场算法里使用这个参数, 以便在指定的蜡烛条上禁止随后的重复入场?我们无法预先知道这一时刻 (其绝对值), 所以我们不能在入场算法里预先输入它。算法应考虑 (包括) 一些通常的条件来解决在蜡烛条上的首次入场, 且无需计算触发即可禁止在蜡烛条上的后续入场 (我们之前拒绝的带计数器的选项)。
此解决方案是相当简单的。首先, 我将会编写一些带注释的代码, 然后将会澄清更多细节。这是一段辅助代码 (以黄色加亮), 需要放置于交易 EA 的算法里 (参看 MACD_Sample_plus1.mq4):
//+------------------------------------------------------------------+ //| MACD Sample.mq4 | //| 版权所有 2005-2014, MetaQuotes 软件公司| //| https://www.mql4.com | //+------------------------------------------------------------------+ #property copyright "2005-2014, MetaQuotes 软件公司" #property link "https://www.mql4.com" input double TakeProfit =50; input double Lots =0.1; input double TrailingStop =30; input double MACDOpenLevel =3; input double MACDCloseLevel=2; input int MATrendPeriod =26; //--- 输入新变量 (此时间帧内一根柱线的秒数, 对于 М15 等于 60 с х 15 = 900 с) datetime Time_open=900; //--- 输入新变量 (柱线开盘时间, 首次入场) datetime Time_bar = 0; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnTick(void) { double MacdCurrent,MacdPrevious; double SignalCurrent,SignalPrevious; double MaCurrent,MaPrevious; int cnt,ticket,total; //--- // 初始数据检查 // 它对于确保程序能在正常图表上工作十分重要 // 而且用户在设置外部变量时不可出错 // (Lots, StopLoss, TakeProfit, // TrailingStop), 在我们的例子中, 我们检查止盈 // 在图表上是否小于 100 根柱线 //--- if(Bars<100) { Print("柱线数小于 100"); return; } if(TakeProfit<10) { Print("TakeProfit 小于 10"); return; } //--- 为了简化代码, 并加速存取, 数据被放到内部变量 MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0); MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1); SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0); SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1); MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0); MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1); total=OrdersTotal(); if(total<1) { //--- 未识别出已开订单 if(AccountFreeMargin()<(1000*Lots)) { Print("我们没有资金。Free Margin = ",AccountFreeMargin()); return; } //--- 检查多头仓位 (买入) 的可行性 //--- 输入新字符串 (若新柱线开盘, 删除重复入场禁止标志) if( (TimeCurrent() - Time_bar) > 900 ) Time_open = 900; if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious && (TimeCurrent()-Time[0])<Time_open) //输入新字符串至入场算法 (仅执行一次, 此蜡烛条上的条件以后不能完成) { ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point,"macd sample",16384,0,Green); if(ticket>0) { if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) { Print("买入订单已开 : ",OrderOpenPrice()); Time_open = TimeCurrent()-Time[0]; //输入新字符串 (保存入场时的柱线开盘时间到离场时刻的间隔) Time_bar = Time[0]; //输入新字符串 (记住柱线开盘时间已有首次入场) } } else Print("买入开单出错 : ",GetLastError()); return; }
阅读更多:
替代绝对时间 (入场时可), 赫兹期货量化使用相对时间 —自当前蜡烛条开盘时刻至入场时刻的时间缺口。此数值与预先设定的进行比较, 较大时间值 (整根蜡烛条的长度), 允许触发首次入场。在开仓时刻, 我们修改 (降低) Time_open 变量的数值, 写入自蜡烛条开盘到实际收盘时刻之间的缺口值。并且由于在随后的任意时刻, 数值 (TimeCurrent() - Time[0]) 将会超出我们写入的入场点数值, 则 (TimeCurrent() - Time[0]) < Time_open 条件仍将是不可能的, 即通过阻塞此蜡烛条上随后的入场来达成。
这样, 无需任何入场数量计数器, 以及分析价格变动的幅度, 我们就解决了虚假触发的问题。
以下是 EA 的初始入场算法经过简单改进后的结果 ("MACD Sample_plus1"):

编辑切换为居中
赫兹期货量化看到, 在一根蜡烛条上只有一次入场, 不存在任何虚假触发, 且散射完全消除。省缺设置全部保存, 所以很显然, 这个问题在不改变 EA 设置的协助下得以解决。
现在入场的散射问题得以解决, 赫兹期货量化将改进入场算法以便排除快速平仓时可能的散射, 在这种特殊情况下增加盈利 (脉冲很不错, 快速离场, 早发)。
离场算法里的解决方案
由于最初的问题涉及如何消除交易机器人的散射可能性, 而非增加盈利, 那么在此话题里我将不会考虑分析动态金融工具的相关问题, 并通过固定选择的参数限制我自己, 这种动态不予考虑。
之前, 我们已经使用了一个安全性参数和时间因素, 赫兹期货量化将用它再次严格规范依照时间平仓的时刻, 具体而言, 紧随蜡烛条开盘的关键点 (入场之后)。在离场算法中的这一时刻, 我们将显示为:
if(!OrderSelect(cnt,SELECT_BY_POS,MODE_TRADES)) continue; if(OrderType()<=OP_SELL && // 检查已开仓位 OrderSymbol()==Symbol()) // 检查品种 { //--- 已开多头仓位 if(OrderType()==OP_BUY) { //--- 应平仓否? if(/* MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && // 删除 MACD 离场触发代码, 不去干扰平仓的新条件 (看之后) MacdCurrent>(MACDCloseLevel*Point) && */ Bid > OrderOpenPrice() && // enter new string - optional (price in a positive area in regards to the entry level) TimeCurrent() == Time[0] ) // 输入新字符串 (立场算法的简单实现: 离场限制在当前蜡烛条的开盘时刻) { //--- 平仓退出 if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet)) Print("平仓错误 ",GetLastError()); return; }