第十八节 学习一款神经网络EA

随着计算能力的增强以及深度学习的出现,人们采用神经网络技术解决了很多实际问题,同时对神经网络也越来越重视。那么,神经网络如何用于外汇的交易?其实早在2008年就已经出现了神经网络EA,并在那一年的交易大赛中拿了第一名,所以在十年以前就已经有人把这项技术用来交易了。

鉴于大家对神经网络没有一个大致的概念和了解,这里贴上一个网址供大家学习和参考:

cnblogs.com/subconsciou

这篇文章比较通俗易懂,建议大家细读,有一定数学基础的可以去看看神经网络的教材,认真研究研究。

本节课拿一款神经网络的EA给大家做一个讲解,看看神经网络类的EA是如何工作的,然后教大家使用MQL4里面自带的遗传算法来寻找优化的参数。

1 策略代码

这个策略的代码是2008年那款得第一名的源码,在网上已经有公布,有兴趣的可以自己搜一下,完整的代码如下:

extern double       tp1 = 50;
extern double       sl1 = 50;
extern int          p1 = 10;
extern int          x12 = 100;
extern int          x22 = 100;
extern int          x32 = 100;
extern int          x42 = 100;
extern double       tp2 = 50;
extern double       sl2 = 50;
extern int          p2 = 20;
extern int          x13 = 100;
extern int          x23 = 100;
extern int          x33 = 100;
extern int          x43 = 100;
extern double       tp3 = 50;
extern double       sl3 = 50;
extern int          p3 = 20;
extern int          x14 = 100;
extern int          x24 = 100;
extern int          x34 = 100;
extern int          x44 = 100;
extern int          p4 = 20;
extern int          pass = 1;
extern double       lots = 0.01;
extern int          mn = 888;
static int          prevtime = 0;
static double       sl = 10;
static double       tp = 10;
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
      if (Time[0] == prevtime)
      {
         return(0);
      }
   prevtime = Time[0];
   if (! IsTradeAllowed()) 
   {
      again();
      return(0);
   }
   int total = OrdersTotal();
   for (int i = 0; i < total; i++) {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if (OrderSymbol() == Symbol() && OrderMagicNumber() == mn) 
         {
            return(0);
         }
      }
   }
  
   sl = sl1;
   tp = tp1;

   int ticket = -1;
  
   RefreshRates();
  
   if (Supervisor() > 0) {
      ticket = OrderSend(Symbol(), OP_BUY, lots, Ask, 1, Bid - sl * Point, Bid +
 tp * Point, WindowExpertName(), mn, 0, Blue);
      if (ticket < 0) {
         again();     
      }
   } else {
      ticket = OrderSend(Symbol(), OP_SELL, lots, Bid, 1, Ask + sl * Point, Ask –
 tp * Point, WindowExpertName(), mn, 0, Red);
      if (ticket < 0) {
         again();
      }
   }
//-- Exit --
   return(0);
}
//+--------------------------- getLots ----------------------------------+
double Supervisor() {
   if (pass == 4) {
      if (perceptron3() > 0) 
      {
         if (perceptron2() > 0) 
         {
            sl = sl3;
            tp = tp3;
            return(1);
         }
      } 
       else
      {
         if (perceptron1() < 0) {
            sl = sl2;
            tp = tp2;
            return(-1);
         }
      }
      return(basicTradingSystem());
   }

   if (pass == 3) 
   {
      if (perceptron2() > 0)
       {
         sl = sl3;
         tp = tp3;
         return(1);
       } 
       else
       {
         return(basicTradingSystem());
       }
   }

   if (pass == 2) 
   {
      if (perceptron1() < 0) 
      {
         sl = sl2;
         tp = tp2;
         return(-1);
       } 
       else 
       {
         return(basicTradingSystem());
       }

   }
   return(basicTradingSystem());
}

double perceptron1()   {
   double       w1 = x12 - 100;
   double       w2 = x22 - 100;
   double       w3 = x32 - 100;
   double       w4 = x42 - 100;
   double a1 = Close[0] - Open[p2];
   double a2 = Open[p2] - Open[p2 * 2];
   double a3 = Open[p2 * 2] - Open[p2 * 3];
   double a4 = Open[p2 * 3] - Open[p2 * 4];
   return(w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4);
}

double perceptron2()   {
   double       w1 = x13 - 100;
   double       w2 = x23 - 100;
   double       w3 = x33 - 100;
   double       w4 = x43 - 100;
   double a1 = Close[0] - Open[p3];
   double a2 = Open[p3] - Open[p3 * 2];
   double a3 = Open[p3 * 2] - Open[p3 * 3];
   double a4 = Open[p3 * 3] - Open[p3 * 4];
   return(w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4);
}

double perceptron3()   {
   double       w1 = x14 - 100;
   double       w2 = x24 - 100;
   double       w3 = x34 - 100;
   double       w4 = x44 - 100;
   double a1 = Close[0] - Open[p4];
   double a2 = Open[p4] - Open[p4 * 2];
   double a3 = Open[p4 * 2] - Open[p4 * 3];
   double a4 = Open[p4 * 3] - Open[p4 * 4];
   return(w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4);
}

double basicTradingSystem() {
   return(iCCI(Symbol(), 0, p1, PRICE_OPEN, 0));
}

void again() {
   prevtime = Time[1];
   Sleep(30000);
}

2 主函数原理

首先从START()函数看起,start()函数的第一部分的意思如果当前k线的开盘时间等于之前保存的时间prevtime,那么就结束,否则的话把该k线的开盘时间赋给prevtime,所以第一部分的内容和我们之前写的Barjudge()模块类似,只有在开盘的时候才做交易。

第二部分是意思是,只有允许交易的情况下才做交易,若服务器繁忙或者市场关闭那么就不做。

第三部分是一个遍历订单的操作,相信大家都能看懂,意思是如果当前持有订单,那么就不做处理。

第四部分,刷新数据为交易做准备,若判断交易的函数Supervisor()大于0,则说明是多方走势,开一个多单,如果Supervisor()小于0,则说明是空方走势,开一个空单,如果开单不成功,那么就执行again()函数,把pretime重新赋值回上一根K线的开盘时间,于是下一个tick来的时候程序会重新开单。

这里注意止损止盈分别为sl1和tp1,要说明一下sl1和tp1是主策略开的单子的止盈止损,sl2、tp2是神经网络开的空单的止盈止损,sl3、tp3是神经网络开的多单的止盈止损。

以上就是start函数,很简单,从这里面我们知道其实整个策略最核心的其实就是判断交易的函数Supervisor()。

3 交易判断函数原理

Supervisor()可以看到有4部分,每一个部分对应了一个通道pass,而pass是一个需要输入的变量,也就是说根据输入的参数,EA会运行不同的部分。里面的perceptron是感知机的意思,关于感知机是什么,可以看一下之前发的神经网络介绍的链接。

编号5的程序块的意思是,如果当前pass值设定为4,那么当3号感知机和2号感知机输出均大于0时,市场处于多头方向,supervisor()输出1,开多单,并采用sl3和tp3作为止损止盈,当3号感知机和1号感知机输出均小于0时,supervisor()输出-1,市场处于空头方向,开空单并采用sl2和tp2作为止损止盈,如果都不满足则市场处于混沌状态,这时采用主策略做单,止损止盈保持之前的赋值sl1和tp1不变。

编号6的程序块的的意思是,当pass值为3时,若感知机2的输出大于0则返回1,采用sl3和tp3作为止损止盈开多单,否则采用主策略做单,止损止盈保持之前的赋值sl1和tp1不变。

编号7的程序块的意思是,当pass值为2时,若感知机1的输出小于0则返回-1,采用sl2和tp2作为止损止盈开空单,否则采用主策略做单,止损止盈保持之前的赋值sl1和tp1不变。

编号8的程序块,如果pass不等于2、3、4里面的任意一个数,那么直接采用主策略做单。

这里解释以下为什么pass有1(其他值)、2、3、4这四个值,当我们刚开始用这款EA的时候,需要把pass设置为1,此时感知器不工作,只有主策略工作,我们可以采用遗传算法对主策略的参数进行优化;当pass为2时,感知机1和主策略同时工作,此时可以优化感知机1的参数(主策略已经被优化了,对感知机的优化相当于教育感知机如何感受当前的市场);当pass为3时,感知机2和主策略工作,此时可以优化感知机2的参数;最后当pass为4时,感知机1、2、3和主策略都工作,此时可以优化感知机3的参数,当所有优化做完后,保留pass为4,将参数都调整为最佳参数即可开始交易。

4 主策略函数与感知机函数

主策略函数就是编号9的代码块,意思很简单,返回p1周期的CCI的值,如果该值大于0,配合start()函数那就是做多,反之如果p1周期的CCI的值小于0,那么就空。

感知机函数有三个,原理上都是一样的,我们拿感知机1的代码来看一下它是如何运行的。1号感知机以P2为间距,从当前K线开始向前挑出了5根K线(包括当前K线),将其前后的开盘价作差(由于是开盘的时候才会激活,而开盘时的close与open相近,所以可以认为是当前K线的开盘价),得出了四个数据,作为感知机的输入,然后感知机将四个数据乘以对应的参数返回给了Supervisor()函数,如果这个返回值大于0,那么Supervisor()函数返回1,主函数就会做多。

这里需要注意的是对于1号感知机,其返回值只有在大于0的时候才会被Supervisor()函数当做有效信号,而在返回值小于0的时候Supervisor()函数会忽视,而对于2号感知机则仅识别小于0的信号。所以实际上,单独的感知机函数并不是一个完整的感知机,感知机函数和Supervisor()函数联合起来才是完整的感知机,单一感知机的原理如下图:

对于1号感知机,它的神经元激活函数为:

对于2号感知机,它的神经元激活函数为:

3号感知机的神经元激活函数为:

当所有参数都优化完毕,即当pass为4时,三个感知机会联合工作,此时要开多单需要3号感知机与1号感知机返回的参数都大于0,开空单需要3号感知机与2号感知机返回的参数都小于0,也就是说这三个感知机是一个并列的关系,1号与3号是逻辑与的关系,2号和3号也是逻辑与的关系,那么我们可以画出当三个感知机一起运作时的原理图:

其中f4和f5两个神经元是有程序里面的与逻辑实现的,f4的激活函数和f1(1号感知机)相同,f5的激活函数和f2相同。

从上图可以很明显地看出这是一个双层的神经网络系统。

5 策略使用

说了这么多这款EA的原理,下面说一下如何优化参数(教育感知机)。

首先看一下没有优化的时候的净值曲线,采用EURUSD1H级别的数据,点差为当前,直接回测EA得到的结果如下:

可以看到主策略是稳定亏损的策略。

接下来我们对主策略的tp1、sl1、p1三个参数优化,点击右边的EA属性界面,在输入参数中把tp1、sl1的初始值设置为10,,终止值设置为100,,每次增加值为1,p1的初始值设置为3,终止值设置为100,每次增加1,然后勾选tp1、sl1、p1,将pass值赋值为1,在优化中勾选最大利润,如图:

确定后回到测试界面,把优化勾上,然后点击开始,接下来我们就可以得到不同参数的测试结果的点阵图:

选取盈利较高的参数,这里选择tp1=100,sl1=93,p1=89,然后打开EA属性,把这三个值输入到赋值里面,继续用当前的点差测试一下,如图:

可以看到净值曲线比优化前好很多。

第二步,教育1号感知机,使其参数达到最优,去掉之前tp1、sl1、p1的勾,把x12、x22、x32、x42、tp2、sl2、p2勾上,同样输入优化用的初始值、步长和终值,把pass改为2,确定后开始优化,得到以下点阵图。

选取收益高,较为密集的区域,这里选择参数:

x12=14,x22=137,x32=131,x42=67,tp2=96,sl2=62,p2=10

再回测一下,结果如下:

第三、四步的优化步骤同二,在这里不赘述,得出参数后同样放到赋值里面,最后可以得出以下结果:

盈利因子达到了1.33,算得上是盈利的策略,当点差为10时,盈利因子可以达到1.68,10万美元的账户手数为10手的情况下,2个月的最大回撤仅为3.89%,盈利47%,非常可观。

但是,如作者所说,这个系统存在过度拟合的风险,采用一年的数据测试结果如下:

6 策略评价

可以看到这款EA只在优化时间段表现得不错,其余时间段虽然没有裸策略差,但是还是一个稳定亏损的状态,所以如作者所说,这款EA只能应用在短期上,若当周出现了亏损,那么说明市场状况变了,就要重新对参数进行优化以适应市场的变化。

这款EA若用于实际可能表现不会很好,主要原因还是主策略太差了,样本外测试也证明了这一点,不过神经网络这一工具的强大可见一斑。

神经网络类的EA最需要提防的就是过度拟合,所以尤其需要注意样本外的测试,一款好的神经网络策略应该是在训练样本内表现和在样本外表现都比较优异的。


更多内容请关注公众号【火象】~