请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  源码分享 帖子:3365773 新帖:5

交易机器人的虚假触发保护

2020来了发表于:4 月 17 日 19:55回复(1)

概论

本文讨论了多种方式来增加交易机器人的操作稳定性, 譬如消减可能的重复触发 (抖动): 既可以分别使用入场和离场算法,也可将它们连结。


问题的本质

若是当前蜡烛条幅度过高, 并且在交易机器人里没有提供预防抖动措施, 则虚假触发问题在暴跌暴涨行情中尤为突出。它会导致在当前蜡烛条上连续重复开单、平仓。

依据行情的特殊算法以及交易机器人的开发者所设置的参数, 其结算后果是变化的。在所有情况下, 交易者的点差开销会随着抖动期间触发数量而成比例增加。

在本文中, 我不会涉及金融工具 (技术和基本面特征) 分析的话题, 这能够影响智能交易程序操作的稳定性, 并有助于避免散射 (这是一个单独的话题 — 我是脉冲均衡理论及其应用系统的作者)。在此, 我们重点关注那些软件手段, 而非直接依赖金融市场分析的方法。

所以, 让我们来着手解决问题。作为一个示例, 我将使用来自 М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;
              }


如此小的修改可令入场算法起作用 (已开仓位, 无条件平仓), 持仓直到 TimeCurrent() == Time[0] 时刻并在新蜡烛条开始的脉冲到达时并行平仓。最终, 不仅散射得到保护, 我们还赢得了良好的收益 (参看图片 "MACD Sample_plus2"):

为此目的, 我们不得不从离场算法里去除由 MACD 触发, 否则离场的必要条件将不能发生。

因此, 看来该散射问题可以分别在入场和离场的算法里解决。现在, 我们来讨论如何通过连接这些开仓和平仓算法来解决问题。


连接开仓和平仓算法

连接意味着整个过程的初步建模: 开仓 - 管理 - 平仓。这也反映在入场和离场算法里如何选择指标和函数。

例如, 如果您在离场算法里使用 TimeCurrent() = Time[0] 条件, 且离场点的设置限制在当前蜡烛条的开始点, 则入场算法应在之前的完整柱线上测试, 所以离场条件可满足。因此, 为了在 TimeCurrent() = Time[0] 条件且无其它附加条件下平仓, 完整的比较算法 (离场) 有必要在之前 (完成) 的柱线上执行。在指标的设置里应有一个抵消等于 1, 参与数值比较。在这种情况下, 数值比较将会是正确的, 并且当前蜡烛条的开始将是离场算法逻辑的终结。

这样, 离场与入场的连接算法也与时间因素链接。


结论

通过在入场算法里使用时间因素, 智能交易程序的虚假触发问题得以有效解决。此外, 通过固定离场点 (例如, 依照时间), EA 操作的稳定性也得以达成, 并通过触发和抵消的主要逻辑的初步建模连接入场和离场算法 (指标的一根柱线或是函数将被计算)。

以下是 EA 代码: 初始那个 (MACD_Sample.mq4), 含有入场改进 (MACD_Sample_plus1.mq4), 含有离场改进 (MACD_Sample_plus2.mq4)。只有买入通道有所提高, 而卖出通道依旧没有改变, 这是为了刻意比较初始和改进算法。

而且, 当然, 所有介绍的 EA 均用于演示目的, 而非针对金融市场的实盘交易。

全部回复

0/140

量化课程

    移动端课程