怎样开发可以获利的交易策略

Yury Reshetov | 24 十二月, 2015

简介


通过技术分析开发成功交易策略的过程可以分为以下几步:
  1. 在某个资产价格图表窗口上附加几个技术指标, 并识别出其中信号指标与市场关联的模式.
  2. 把上一步相关性分析取得的数据进行公式化.
  3. 把策略转换为对应的编程语言进而创建一个机械化的交易系统.
  4. 在基于历史数据的模拟器中运行这个交易系统并调整它的输入参数(优化).
  5. 如果之前的步骤不能增加资产, 则返回第一步.
  6. 在模拟账户中运行之前创建的系统进行测试.
  7. 如果之前的步骤不能在模拟系统中获利, 返回第一步.
  8. 在真实效益中使用该系统, 并且根据市场条件的变化时而调节其输入参数.
事实就是这样. 通过这种方式建立的系统不仅可以用于自动化交易, 也可以在人工交易中作建议, 提示来自技术指标的重要信号.
      
让我们尝试一下计算机化这个过程, 看看会发生什么.

本文分析这样的实例: 根据读取的加速/减速振荡指标 (AC), 使用单层神经网络来预测未来价格的走向.

 

神经网络

什么是神经网络或感知器?它是一种算法, 它使用线性过滤在某个类上增加或者减少对象. 不等式看起来是这样的:
      
      w1 * a1 + w2 * a2 + ... wn * an > d,
      
其中:
      
wi - 以 i 为索引的权重系数,

ai - 对象索引 i 的数值,

d - 阈值, 通常等于0.

      
如果不等式的左侧大于阈值, 则对象属于某一类, 如果低于阈值, 则不属于那一类. 在我们的对象分类只需要分成两类的情况下, 单层神经网络就已经足够了.
      
也许看起来在这个在神经网络不等式中的权重系数就像萨满法术一般. 而现实情况并非如此. 神经网络的运行原则是含有几何学意义的.

实际上, 一个几何学上的平面可以被描述为一个线性等式. 例如, 在三维空间中, 关联着X, Y, Z坐标的平面等式如下所示:
      
A * X + B * Y + C * Z + D = 0

在该平面一侧的所有点的坐标都满足如下不等式:
      
A * X + B * Y + C * Z + D > 0


而平面另外一侧的所有点的坐标都满足下面的不等式:
      
A * X + B * Y + C * Z + D < 0

这样的话, 如果我们知道了这样的一个平面等式和任何点的坐标, 我们就可以在空间内通过这个平面把所有的点分成两个集合.
      
相应地, 神经网络不等式中的权重系数也就是在某个对象标记的多维空间中定义的特定平面等式. 通过不等式的方式, 我们就可以准确地区分这些对象是在指定平面的这一侧还是另外一侧. 为了这个目标, 我们只需要确定对象的坐标, 并且在平面等式中做代换, 然后与0坐比较就可以了.



问题定义

如果我们把对象分成两类, 例如买入和卖出, 并使用技术分析中指标或者振荡指标的数值作为标记, 我们只需要找到一个平面等式并且在区分目标中使用它就好了. 问题定义非常清楚.
      
然而, 神经网络有一个问题. 让我们看一下以X和Y坐标标记的二维空间, 我们将在此空间放置坐标点对象.
      
     
            
上图显示, 红色点集不与蓝色点集的坐标相交, 这两个点集被直线分开了(二维空间中, 使用线来分隔, 而三位或多维空间中, 使用面来分隔). 请注意, 这些分隔线的等式可能有所不同. 现在看另外一个实例:       
     
      
我们可以看到, 在平面中这些点有所交汇, 没有办法使用直线清楚地把它们分开. 唯一可行的方案就是在这两个点集中画一条直线, 使得绝大部分红色点在一侧, 蓝色点在另外一侧. 这一次,我们使用一个优化方案, 也就是找到一个对应于平面或者直线的等式, 可以把两类对象最大程度地分开, 但是在区分过程中也许会有所错误, 一些属于某一类的点可能被分到另外一类中.
      
还有一些其他实现神经网络的方法, 如通过非线性过滤或者多层网络. 非线性过滤可以使用高阶曲面来作为分隔面以区分不同类型的对象. 多层网络则使用多个过滤器(分隔面)来把对象分为三类或者更多种类.
      
让我们定义一个需要解决的问题. 如果交易者需要在交易中获利, 他所需要知道的基本信息就是价格改变的方向. 如果价格上涨, 交易者应该开一个买入仓位. 如果价格下跌, 则应该开一个卖出仓位. 这样, 我们就有了两类对象, 即价格移动的方向. 根据技术分析, 交易者需要通过研究技术指标和振荡指标来做出决定. 我们将分析加速减速振荡指标(AC).
      
因为振荡指标都是水平线上分离的柱形图, 我们将需要一个线性过滤器构成的神经网络. 我们将使用的对象标记模式为: 在当前时刻前每隔七个时段进行计算的四个点的值.

           
在上图中, 振荡指标值使用圆形做了标记. 我们将把它们标识为a1, a2, a3和a4, 并且把它们代入到平面等式中计算与零做比较, 以决定其模式应当属于哪一侧.
      
现在问题只剩下怎样获得这个把模式区分为价格上涨还是价格下跌的平面等式了.
      
为了这个目标, 我们将使用MetaTrader 4中内含的遗传算法来加快优化过程. 换句话说, 我们将使用这样的方法来选择线性过滤器的权重系数, 最终我们可以使用基于历史数据优化的策略来得到取得最大利润的线性等式.
      
为此我们至少需要此交易策略的公式, 以把这种交易算法转换为MetaTrader 4中EA的代码..
      
理论上, 一个交易系统应该同时提供进入和退出市场的信号. 然而, 在如下条件下, 退出信号是可选或者不需要的:
  1. 下单带有止损位和获利位;
  2. 在收到市场趋势反转信号时平仓并下反向单.
为了避免交易系统太过复杂, 我们将使用保护性止损, 止损以及反向信号来作为输出. 在这样的条件下, 神经网络将只输出两种信号, 即:       
  • 价格将可能上涨;
  • 价格将可能下跌.
这将简化神经网络的任务, 只需要把对象区分为两类. 交易系统的优化过程也可以去除止盈单的控制来进行简化, 即避免了选择另外一个输入参数. 在这种情况下, 使用移动止损来逐步向获利方向设置止损值, 直至神经网络发出反向信号或者出错的时候就够了.. 神经网络的出错将触发保护性止损. 另外, 订单管理系统变得复杂了. 实现向相反方向的快速仓位反转, 最好的方法是使用双倍手数的订单, 可以随后关闭之前的反向订单. 这种方法可以在收到神经网络的信号后立即进行反向的操作.

为了减少错误的神经网络信号, 我们将只根据已经完成的价格柱以及此柱的开盘价格来读取和做出决定.


问题的解决

请参照以下实现此交易策略的EA源代码:
//+------------------------------------------------------------------+
//|                                       ArtificialIntelligence.mq4 |
//|                               Copyright й 2006, Yury V. Reshetov |
//|                                         http://reshetov.xnet.uz/ |
//+------------------------------------------------------------------+
#property copyright "Copyright й 2006, Yury V. Reshetov ICQ:282715499  http://reshetov.xnet.uz/"
#property link      "http://reshetov.xnet.uz/"
//---- 输入参数
extern int    x1 = 120;
extern int    x2 = 172;
extern int    x3 = 39;
extern int    x4 = 172;
// 止损水平
extern double sl = 50;
extern double lots = 0.1;
extern int    MagicNumber = 888;
static int prevtime = 0;
static int spread = 3;
//+------------------------------------------------------------------+
//| EA 初始化函数                                                      |
//+------------------------------------------------------------------+
int init()
  {
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| EA 终止化函数                                                     |
//+------------------------------------------------------------------+
int deinit()
  {
//----
   return(0);
  }
//+------------------------------------------------------------------+
//| EA 起始处理函数                                                    |
//+------------------------------------------------------------------+
int start()
  {
   if(Time[0] == prevtime) 
       return(0);
   prevtime = Time[0];
//----
   if(IsTradeAllowed()) 
     {
       spread = MarketInfo(Symbol(), MODE_SPREAD);
     } 
   else 
     {
       prevtime = Time[1];
       return(0);
     }
   int ticket = -1;
   // 检查已建成仓位
   int total = OrdersTotal();   
   for(int i = 0; i < total; i++) 
     {
       OrderSelect(i, SELECT_BY_POS, MODE_TRADES); 
       // 检查交易品种名称和特征码
       if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) 
         {
           int prevticket = OrderTicket();
           // 已建立的多仓
           if(OrderType() == OP_BUY) 
             {
               // 检查利润 
               if(Bid > (OrderStopLoss() + (sl * 2  + spread) * Point)) 
                 {               
                   if(perceptron() < 0) 
                     { 
                       // 反向操作
                       ticket = OrderSend(Symbol(), OP_SELL, lots * 2, Bid, 3, 
                                          Ask + sl * Point, 0, "AI", MagicNumber, 
                                          0, Red); 
                       Sleep(30000);
                       if(ticket < 0) 
                         {
                           prevtime = Time[1];
                         } 
                       else 
                         {
                           OrderCloseBy(ticket, prevticket, Blue);   
                         }
                     } 
                   else 
                     { 
                       // 移动止损
                       if(!OrderModify(OrderTicket(), OrderOpenPrice(), 
                          Bid - sl * Point, 0, 0, Blue)) 
                         {
                           Sleep(30000);
                           prevtime = Time[1];
                         }
                     }
                 }  
               // 已建空仓
             } 
           else 
             {
               // 检查利润 
               if(Ask < (OrderStopLoss() - (sl * 2 + spread) * Point)) 
                 {
                   if(perceptron() > 0) 
                     { 
                       // 反向操作
                       ticket = OrderSend(Symbol(), OP_BUY, lots * 2, Ask, 3, 
                                          Bid - sl * Point, 0, "AI", MagicNumber,
                                          0, Blue); 
                       Sleep(30000);
                       if(ticket < 0) 
                         {
                           prevtime = Time[1];
                         } 
                       else 
                         {
                           OrderCloseBy(ticket, prevticket, Blue);   
                         }
                     } 
                   else 
                     { 
                       // 移动止损
                       if(!OrderModify(OrderTicket(), OrderOpenPrice(), 
                          Ask + sl * Point, 0, 0, Blue)) 
                         {
                           Sleep(30000);
                           prevtime = Time[1];
                         }  
                     }
                 }  
             }
           // 退出
           return(0);
         }
     }
   // 检查是否可以建立多单或空单
   if(perceptron() > 0) 
     { 
       //多单
       ticket = OrderSend(Symbol(), OP_BUY, lots, Ask, 3, Bid - sl * Point, 0, 
                      "AI", MagicNumber, 0, Blue); 
       if(ticket < 0) 
         {
           Sleep(30000);
           prevtime = Time[1];
         }
     } 
   else 
     { 
       // 空单
       ticket = OrderSend(Symbol(), OP_SELL, lots, Bid, 3, Ask + sl * Point, 0, 
                      "AI", MagicNumber, 0, Red); 
       if(ticket < 0) 
         {
           Sleep(30000);
           prevtime = Time[1];
         }
     }
//--- 退出
   return(0);
  }
//+------------------------------------------------------------------+
//|  PERCEPRRON - 侦测与识别函数                                       |
//+------------------------------------------------------------------+
double perceptron() 
  {
   double w1 = x1 - 100.0;
   double w2 = x2 - 100.0;
   double w3 = x3 - 100.0;
   double w4 = x4 - 100.0;
   double a1 = iAC(Symbol(), 0, 0);
   double a2 = iAC(Symbol(), 0, 7);
   double a3 = iAC(Symbol(), 0, 14);
   double a4 = iAC(Symbol(), 0, 21);
   return (w1 * a1 + w2 * a2 + w3 * a3 + w4 * a4);
  }
//+------------------------------------------------------------------+

 
现在, 我们只需要简单地选择分割面神经网络线性函数的权重系数了. 让我们按下 Ctrl + R 按键运行策略测试器:
            



在设置页面, 我们选择只针对开盘价的快速市场模拟方法(我们的EA中只读取已完成的柱). 我们勾选重新计算和优化, 然后点击EA属性.
      
测试页面:
      


     
我们选择的初始存款为 $3,000, 优化和测试同时作用于买入与卖出仓位. 在测试过程中我们使用最大余额作为优化索引. 同时, 为了加快优化速度, 应该在其中使用遗传算法.

输入参数页面:
      


      
我们将要勾选使用遗传算法优化的输入参数: 神经网络中的权重参数 x1, x2, x3 和 x4, 同时我们也要选择可以接受的止损水平值 sl. 手数选择为1, EA 幻数 (Magic Number)使用默认值.

优化页面:
      

      
为了加快优化的过程, 最大回撤水平设为 35%. 为了确认可接受最大回撤是必须的, 首先, 进行一下没有任何限制的优化过程. 在取得了第一个优化结果后, 评估一下结果, 然后停止优化过程, 再输入交易的限制. 重新开始的优化过程运行明显加快.
      
点击"确定"以关闭EA设置页面. 现在我们可以按下"开始"按钮来开始优化过程了. 同时建议禁止无效结果的输出:
      

      
在优化过程中, 如果计算机配置不高, 例如内存较少, 则建议时常清除日志以加快优化.
      

      
在奔腾III电脑上, 整个优化过程可能需要一个小时多一点. 时间同时还决定于交易品种.
     

      
剩下来我们要做的就是使用鼠标右键点击最上面一行, 在弹出菜单中选择"设置输入参数", 开始在历史数据中做测试.
      
毫无疑问, 测试结果将和优化器的数据相匹配.
      
我们在这里发布这些测试结果. 然而, 有人会有疑问并且提出来, 这些信息是根据历史数据梳理而来, 如果市场发生改变了, 获得的这个策略将表现如何呢?这样的模式在未来也会有此表现吗?例如, 在自动交易锦标赛的参与中, 其规则是直到比赛结束都不允许调整输入参数.
      
让我们尝试做个实验. 在实验中, 神经网络将使用历史数据进行训练, 但是将排除最近三个月的数据. 为此, 我们将在优化时间上设置限制, 并且在测试器中使用集成的日期函数作测试.

      
让我们开始优化过程. 我们将获得以下结果的输入参数: x1 = 146, x2 = 25, x3 = 154, x4 = 121, sl = 45.



     

现在我们可以在没有进行过优化并且无法做出假定的时段检验取得的策略了, 即优化时间段之后的三个月. 我们只需要简单地去掉"使用日期"的勾选, 看策略会如何进行交易:


           
结果是不言而喻的. 策略最终还是获利的. 当然, 和真实交易锦标赛中看到的利润相比, 其获利是很普通的. 然而我们应该记住, EA中并没有进行资产的风险管理, 它只是使用固定手数进行交易. 另外, 在真实交易中需要更多的优化, 应该比每次三个月更加频繁. 


结论

我们应该如何看待 D. Katz 和 D. McCormick 在他们的书籍<<交易策略百科全书>>( "The Encyclopedia of Trading Strategies")中对神经网络的结论呢?
      
首先, 按照这样的原则: 信任, 但是做验证. D. Katz 和 D. McCormick 所说的过程是不能进行测试的. 换句话说, 没有科学的方法进行重现. 可以理解, 一些人更专于写书和出版, 而不是交易. 他们的任务是把书卖得更好, 而不依赖于其中的内容. 所以也就可以理解, 他们怎样了写出了这些夹杂图表的"500个没用的建议"风格的一堆废纸. 让我们把事情整理清楚.

  • D. Katz 和 D. McCormick 的问题定义是创造一个不可能存在的指标, 或者说得更清楚些, 一个时光反转的慢速%K随机振荡指标, 它像一个时间机器一样, 从未来十个柱读取数据然后来回头预测十个柱之前的数据. 如果我有这个指标, 比尔盖茨和乔治索罗斯将很难和我竞争了.
  • 下面的步骤就是使用心灵感应能力从一些数据中获得随机振荡的预测. 他们已经设置了逼近算法任务, 找到了一些函数参数来获取它的数值. 这里的逼近(approximation)也就是Katz和MacCormick在他们的著作中所说的适应(adaptation).
  • 逼近是怎样获得的并不重要, 更重要的是神经网络不适合完成此类目标. 而通过其他方式, 例如频谱分析则可以更容易地完成此任务;
  • 神经网络在向内插值和向外插值任务中表现得更糟一些. 如果我们从某些样本中取得数据, 进行向外插值而不是对之进行分类.
  • 通过这种心灵感应般的随机振荡性近似, 其中显然包含了明显错误, Katz和McCormick向错误更加迈近一步, 他们从错误中进一步创建了一个"交易策略". 从错误中继续读取, 即如果%K超过了某个限制, 则价格已经达到了最高或者最低点. 总之, 这些作者所向他们的读者所推荐的, 在机械化交易系统中通过了统计和获得的结论都是华而不实的.
那只是短期的结果. 在他们提供的样本中, 是有巨大获利的, 而离开了那些样例, 获利是非常有限的, 甚至只有亏损.

然而, 错误地进行神经网络实验的并不只有Katz和MacCormick两人. 第一个神经网络项目, 叫做"感知机(Perceptron)", 它同样没有完成其期望. 它是带来麻烦的第一步, 和科学怪人恰好一样. 后来 M.Minsky 和 S. Papert 进行了一些神经网络的研究, 分析其能做到哪些而不能做到哪些. [1]. 这样, 在使用神经网络去解决某些难题, 取得解决方案之前, 尽量做到不要重蹈覆辙:
  1. 问题定义中不应该包含心灵感应般的预测未来, 对诸如何时及多少这类问题做出明确回答. 解决方案仅限于基于某种形式的表现而决定把它们分为几个各自独立的情境. 例如, 如果您有一个天气相关的任务, 不要试图预测什么时候开始下雨或者降水量有多少毫米. 只能把预测限于是晴天还是雨天;
  2. 使用"奥卡姆剃刀(Occam's Razor)"原则把非必要部分去除. 一些实验者相信, 使用更多层的神经网络或者更复杂的激活函数(activation function)可以获得更好的结果. 通过这种方法, 您当然可以利用它们的特性来更精确地把对象进行分类. 没有人会反对这一点. 但是为什么要那样呢?总之, 这样的做法就像建造一个沙子城堡. 如果边界有已经确定的形状, 并且随着时间推移和周围环境并不发生改变, 那么使用复杂的方法做最大的优化是有意义的. 但是大多数使用神经网络帮助解决的问题不属于这一类. 金融资产也同样不是一成不变的. 因而, 最简单的神经网络, 仅使用少量输入并且仅单层, 也许比更加复杂的解决方案更好, 因为它的效率更高.
这就是我在本文中想说的.


参考

  1. Minsky, M. 和 Papert, S. (1969) PERCEPTRON; 计算几何学, 麻省理工学院出版社