一个基于不同大陆不同时区的交易策略实例

Vasily | 3 十二月, 2013


简介

我有段时间很闲,当然了,我把这段时间都花在调查市场和研究经济周期与技术指标的异同上了。大家可以到《创建具有图形控制选项的指标》一文中查看调查结果。但如果认为里面只有调查结果,您可就错了!我发现了一个规模更大的现象,但要理解它,我们还要从时区角度来看看我们所在的世界(图 1)。

图 1。时区

我们可以看到,每个国家新一天的开始都不相同,而且都在以不同的时间继续着自己的生活。我们幅员辽阔的伟大国家跨越了接近 10 个时区,而大西洋不过才覆盖了 6 个。

这里有什么规律模式呢?我们来看下述国家中的开市顺序:日本、澳大利亚、中国和俄罗斯。在俄罗斯职员抵达办公室开始交易操作的时候,亚洲地区已经是夜间了;而当欧洲开市时,他们又已经收市了。

这里开始好玩了。开市后,欧洲经纪人和对冲基金经理就会将其资产拿到市场上来,实施投机或是与其投资者的利益、行动并发。不,这还远远没到最有趣的部分 - 从芝加哥的黎明开始,大西洋的少部分地区即会迎来朝阳,芝加哥证券市场的经理们就会打开他们的交易终端,开始他们的资本管理。

我们先停一停,谈谈所谓的大西洋少部分地区。欧洲时段市场尚未结束(图 1)。伦敦与芝加哥相距 8 个时区,如果我们假设工作日是 8 小时 + 预留出的45分钟或 1 小时的吸烟休息时间(可能会再延长 1,30 - 2 小时,办公室工作过的人都明白这是怎么回事)。

而这正是我们获取欧洲与美洲经理人市场资产的时间。我可不敢列出该点位的所有资产零值,欧美的经理人都开始忙于证券交易的价格,而此点位的现象如图 2 所示。

图 2。市场脉搏

市场当中充斥着金融战争,资产每秒钟都会易主,但还是不能就此罢休!此周期过后,又会朝着当前趋势的方向开始一种平静行走。在此点位,范围边界有所扩展,之后又再一次缩窄,继续向主趋势方向行走。

我相信您已经注意到了,市场价格可能会上涨也可能下滑,但其始终向右移动。


2. 作为算法开发主途径的流程图

"Start-Stop" (启动-停止)程序的第一个程序块如图 3 所示:


图 3。"Start-Stop" 块

此程序块用于指明程序的启动,某函数(或初始化与取消初始化之类的其它流程)的开头和结尾。我们下一个要研究的标签为 "Data" 的程序块,如图 4 所示。


图 4。"Data" 块

"Data" 程序块用于确定程序启动时指定的参数,如为 MQL5,则是输出变量。此单元还充当着全局变量的目标函数。

接下来,我们来研究一种常用程序块(MQL 中 99% 的程序都会使用此方法) - 它分两部分描述,从而也标记了一个周期的界限。看看图 5:

周期块

图 5。周期块

通常于上述周期内发生的赋值或描述之类的处理,示例见图 6。

操作

图 6。操作

而且,我们可不能忘记逻辑块 - 图 7 所示为 "solution" 块。


图 7。"Solution" 块

如果 "solution" 块位于 "switch which depends on the number of placings" (根据配售数量切换)类型的运算符内,则可能有两个输出。此单元会有相应数量的输出。

下一个程序块会引出预定义函数,比如 iMACD iRSA,以及在程序或库中其它位置定义的自定义函数(图 8)。


图 8。函数

最后两个程序块只是实施服务函数 - 比如注释与违背(图 9)。

服务块

图 9。服务块

可用于描述为机器编写的任何程序的块类型,全都在上面列出来了;在开发初期,它们都很明确、简单且方便使用,还揭示出了系统的弱点、想出了解决的方法。

这种方法您已经了解了,但我可不是让您照本宣科地对照这些计划来实施,而只是简单地了解一下流程图的初始值,如此则足够理解某些类型的计算方法了。此方法有助于我快速形成一个理念,而这个理念一度从我的脑中一闪而过、捕捉不及。

3. 算法的构造

那么,我们现在就继续基于流程图策略来准备“EA 交易”。

第一个程序块要求输入参数。我们已经清楚,于主战争时刻的等待至关重要,即,美国开市之后 2 小时,界时欧洲市场已收市。我们会就此监控全局时钟(即终端时间),因此,我们会自行计算开市时间。

之后,我们来确定仓位的大小和盈利与损失水平,而这些将来都还有优化的潜力。要格外注意幻数参数,因为“EA 交易”会用它来确定其订单和开盘交易。再进一步,我们会制作一个追踪止损,以限制就我们所知的仓位风险。

还有一个我们需要的有趣参数 - 安全水平。我们要根据它来观察此刻有无重大经济新闻,不管新闻能否构成威胁,都要认真考量这两个小时内出现的主要市场恐慌。


图 10。输入参数

//--- 输入参数
input int      America=16;
input double   Lots=0.1;
input int      TakeProfit=500;
input long     MagicNumber=665;
input int      Limited=600;
input int      TrailingStop=100;

我们继续讲解策略形成的下一部分。

我们需要确定时段是否有交叉、订单是否已设置或正被设置。(图 11)。

交易时间

图 11。交易时间

您可以看到,给定的算法是一个带有输入参数、完整计算与输出结果的封闭式程序。这种迷你程序被称为函数,且通过封装于主程序保护起来。

封装与各程序或程序各个部分之间的屏障,通过 Get 和 Set (获取与设置)之类的方法分隔开来,避免其突破其它 Get 与 Set 的界限。该过程的本质在于,函数中的变量名称可能与主程序中的相同。但当 Get 方法试图从带有一个变量名称的单元格获取时,它就会面临封装 - 只赋予其访问(分配给该函数或程序的)存储单元某特定部分。

Set 方法也适用,只是与 Get 不同。它会将单元内存中的值设置为变量名称,如果程序中的变量名称与函数中的一致,则封装不允许 Set 方法为另一程序或函数内的变量赋值。

bool time2trade(int TradeHour,int Number)
  {
   MqlDateTime time2trade;
   TimeTradeServer(time2trade);
   if(time2trade.hour!=TradeHour) return(false);
   time2trade.hour= 0;
   time2trade.min = 0;
   time2trade.sec = 1;
   for(int ii=OrdersTotal()-1;ii>=0;ii--)
     {
      OrderGetTicket(ii);
      long ordmagic=OrderGetInteger(ORDER_MAGIC);
      if(Number==ordmagic) return(false);
     }
   HistorySelect(StructToTime(time2trade),TimeTradeServer());
   for(int ii=HistoryOrdersTotal()-1;ii>=0;ii--)
     {
      long HistMagic=HistoryOrderGetInteger(HistoryOrderGetTicket(ii),ORDER_MAGIC);
      if(Number==HistMagic) return(false);
     }
   return(true);
  }

我们已经识别了所需的时段,并确定了是否已经设置订单。我们考虑一下接下来要做什么。

此前,我们注意到,大波动是在美国开市时段之后 2 个小时发生的。美洲开市时段之后,我们得到了 9 个十五分钟柱。我们找到该周期的最大范围,并仔细研究 - 如果此变化足够大,则市场中很有可能会出现大范围的恐慌,未来趋势也不好判断。因此,我们这里将需要一些限制。

当市场冷静下来时,则该时段就会加大其波动。这样就会赋予我们一个机会,确定于主趋势的最大偏差,把订单陷阱(会因主趋势继续而产生作用)放到一个安全的距离。前面我们已经讲过,价格可能上涨也可能下滑,但会始终向右移动。(图 12)。

下单算法

图 12。下单算法

修改程序代码,注意交易终端 MetaTrader 5 不允许接近上一次交易价格的订单设置。如果此刻价格绘制出了一个新的最小值或最大值,我们会通过退到上一次交易价格最小距离的方式来坚持自己的立场,以实现订单的可靠确认。而且,我们还会在一天结束之前设置自己订单的持续期,因为它们稍后就要过期了。

void OnTick()
  {
//---
   if(time2trade(America+2,MagicNumber))
     {
      int i;
      double Highest = 0;
      double Lowest = 0;
      MqlRates Range[];
      CopyRates(Symbol(),15,0,9,Range);
      Lowest=Range[1].low;
      for(i=0; i<9;i++)
        {
         if(Highest<Range[i].high) Highest=Range[i].high;//MathMax(,Highest);
         if(Lowest>Range[i].low)  Lowest=Range[i].low;
        }
      long StopLevel=SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL);
      Highest=Highest+StopLevel*Point();
      // 把订单设置可能的最小距离增加到当前价格参数中
      Lowest=Lowest-StopLevel*Point();
      // 以保证我们的订单最大可能被接受

      if((Higest-Lowest)/Point()<Limited)
        {
         MqlTradeRequest BigDogBuy;
         MqlTradeRequest BigDogSell;
         BigDogBuy.action=TRADE_ACTION_PENDING;
         // 设置挂单
         BigDogBuy.magic = MagicNumber;
         BigDogBuy.symbol=Symbol();
         BigDogBuy.price=Highest;
         //将要设置的订单价格
         BigDogBuy.volume=Lots;
         BigDogBuy.sl=Lowest;
         //如果止损没有设置, 则根据策略设置
         BigDogBuy.tp=Highest+TakeProfit*Point();
         //设置获利</s41>
         BigDogBuy.deviation=dev;
         //请求价格的最小偏离, 
         //换句话说就是执行的价格可以和指定价格相差多少
         BigDogBuy.type=ORDER_TYPE_BUY_STOP;
         //订单类型, 根据指定的价格或者高于指定价格执行
         //在本案中, 订单设置为高于或等于指定价格
         //如果订单类型为限制买单, 则会被 
         //根据指定价格或者比指定价格低的价格执行
         BigDogBuy.type_filling=ORDER_FILLING_AON;
         //提供的参数演示了订单怎样
         //在范围内部分执行 
         BigDogBuy.expiration=TimeTradeServer()+6*60*60;
         //根据策略描述, 订单的生命周期仅限于当前的工作日
         //因为从美市开始已经有2个小时了, 工作日为8个小时, 我们还剩下 8-2 = 6
         BigDogSell.action=TRADE_ACTION_PENDING;

         // 设置挂单
         BigDogSell.magic = MagicNumber;
         BigDogSell.symbol=Symbol();
         BigDogSell.price=Lowest;
         //订单将要设置的价格
         BigDogSell.volume=Lots;
         BigDogSell.sl=Highest;
         //根据策略设置止损
         BigDogSell.tp=Lowest-TakeProfit*Point();
         //设置获利
         BigDogSell.deviation=dev;
         //离请求价格的最小偏差, 
         //换句话说就是执行的价格可以和指定价格相差多少
         BigDogSell.type=ORDER_TYPE_SELL_STOP;
         //订单类型, 根据指定的价格或者高于指定价格执行
         //在本例中, 订单被设为等于或者高于指定价格
         //如果订单类型是 buy_limit, 它将被执行
         //根据指定价格或者比指定价格低的价格执行
         BigDogSell.type_filling=ORDER_FILLING_AON;
         //提供的参数演示了订单怎样
         ///在范围内部分执行  
         BigDogSell.expiration=TimeTradeServer()+6*60*60;
         //根据策略描述, 订单的生命周期仅限于当前的工作日
         //因为从美市开始已经有2个小时了, 工作日为8个小时, 我们还剩下 8-2 = 6
         MqlTradeResult ResultBuy,ResultSell;
         OrderSend(BigDogBuy,ResultBuy);
         OrderSend(BigDogSell,ResultSell);
        }
     }


订单已下、陷阱已设 - 现在,我们该着手处理仓位风险的降低了,我们应用追踪止损技术。

为确定我们的仓位,我们会采用幻数 (MagicNumber),当其利用最小的价格变化达到某个特定的利润水平时,移动止损水平。(图 13).

追踪止损的实施

图 13。追踪止损的实施

针对不同的策略,利用最简单的方法实施追踪止损。尽管某些策略建议不使用追踪止损,以便不阻止价格达其目标,或只是用这样一种机制将持仓转为无损失。但在本策略中,我们应用这种经典的机制是为了移动保护性止损 - 如果价格向我们的方向移动最小价格变化的特定量。

//--- 实现追踪止损
   int PosTotal=PositionsTotal();
   for(int i=PosTotal-1; i>=0; i--)
     {
      //--- 遍历持仓查看是否有本EA交易所建仓位.
      if(PositionGetSymbol(i)==Symbol())
        {
         if(MagicNumber==PositionGetInteger(POSITION_MAGIC))
           {
            MqlTick lasttick;
            SymbolInfoTick(Symbol(),lasttick);
            if(PositionGetInteger(POSITION_TYPE)==0)
              { //--- 买入
               if(TrailingStop>0
                  &&(((lasttick.bid-PositionGetDouble(POSITION_PRICE_OPEN))/Point())>TrailingStop)
                  && ((lasttick.bid-PositionGetDouble(POSITION_SL))/Point())>TrailingStop)
                 {
                  MqlTradeRequest BigDogModif;
                  ZeroMemory(BigDogModif);
                  BigDogModif.action= TRADE_ACTION_SLTP;
                  BigDogModif.symbol= Symbol();
                  BigDogModif.sl = lasttick.bid - TrailingStop*Point();
                  BigDogModif.tp = PositionGetDouble(POSITION_TP);
                  BigDogModif.deviation=3;
                  MqlTradeResult BigDogModifResult;
                  ZeroMemory(BigDogModifResult);
                  OrderSend(BigDogModif,BigDogModifResult);
                 }
              }
            if(PositionGetInteger(POSITION_TYPE)==1)
              {//--- 卖出
               if(TrailingStop>0
                  && ((PositionGetDouble(POSITION_PRICE_OPEN)-lasttick.ask)/Point()>TrailingStop)
                  && (PositionGetDouble(POSITION_SL)==0
                  || (PositionGetDouble(POSITION_SL)-lasttick.ask)/Point()>TrailingStop))
                 {
                  MqlTradeRequest BigDogModif;
                  ZeroMemory(BigDogModif);
                  BigDogModif.action= TRADE_ACTION_SLTP;
                  BigDogModif.symbol= Symbol();
                  BigDogModif.sl = lasttick.ask + TrailingStop*Point();
                  BigDogModif.tp = PositionGetDouble(POSITION_TP);
                  BigDogModif.deviation=3;
                  MqlTradeResult BigDogModifResult;
                  ZeroMemory(BigDogModifResult);
                  OrderSend(BigDogModif,BigDogModifResult);
                 }
              }
           }
        }
     }
  }

接下来,集合我们的算法(图 14)。





图 14。组合成一个算法

//+------------------------------------------------------------------+
//|                                          BigDog_By_CoreWinTT.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
//--- 输入参数
input int      America=16;
input double   Lots=0.1;
input int      TakeProfit=500;
input long     MagicNumber=665;
input int      Limited=600;
input int      TrailingStop=100;
int dev=30;
//+------------------------------------------------------------------+
//| EA交易初始化函数                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| EA交易去初始化函数                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
bool time2trade(int TradeHour,int Number)
  {
   MqlDateTime time2trade;
   TimeTradeServer(time2trade);
   if(time2trade.hour!=TradeHour) return(false);
   time2trade.hour= 0;
   time2trade.min = 0;
   time2trade.sec = 1;
   for(int ii=OrdersTotal()-1;ii>=0;ii--)
     {
      OrderGetTicket(ii);
      long ordmagic=OrderGetInteger(ORDER_MAGIC);
      if(Number==ordmagic) return(false);
     }
   HistorySelect(StructToTime(time2trade),TimeTradeServer());
   for(int ii=HistoryOrdersTotal()-1;ii>=0;ii--)
     {
      long HistMagic=HistoryOrderGetInteger(HistoryOrderGetTicket(ii),ORDER_MAGIC);
      if(Number==HistMagic) return(false);
     }
   return(true);
  }
//+------------------------------------------------------------------+
//| EA交易订单函数                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(time2trade(America+2,int(MagicNumber)))
     {
      int i;
      double Highest= 0;
      double Lowest = 0;
      MqlRates Range[];
      CopyRates(Symbol(),15,0,9,Range);
      Lowest=Range[1].low;
      for(i=0; i<9;i++)
        {
         if(Highest<Range[i].high) Highest=Range[i].high;
         if(Lowest>Range[i].low) Lowest=Range[i].low;
        }
      long StopLevel=SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL);
      Highest=Highest+StopLevel*Point();
      //--- 为当前的订单集合价格加上可能的最小距离参数.
      Lowest=Lowest-StopLevel*Point();
      //--- 以确保我们订单最大可能被接受.

      if((Highest-Lowest)/Point()<Limited)
        {
         MqlTradeRequest BigDogBuy;
         MqlTradeRequest BigDogSell;
         ZeroMemory(BigDogBuy);
         ZeroMemory(BigDogSell);
         BigDogBuy.action=TRADE_ACTION_PENDING;
         //--- 设置挂单
         BigDogBuy.magic = MagicNumber;
         BigDogBuy.symbol=Symbol();
         BigDogBuy.price=Highest;
         //--- 订单将被设置的价格
         BigDogBuy.volume=Lots;
         BigDogBuy.sl=Lowest;
         //--- 如果没有设止损, 则根据策略设置
         BigDogBuy.tp=Highest+TakeProfit*Point();
         //--- 设置获利
         BigDogBuy.deviation=dev;
         //--- 请求价格的最小偏移量, 
         //--- 换句话说, 执行价格可以偏离指定价格多远
         BigDogBuy.type=ORDER_TYPE_BUY_STOP;
         //--- 订单类型, 执行价格基于指定价格或高于指定价格
         //--- 在本例中订单设为价格高于或等于指定价格 
         //--- 如果订单类型是限制买入, 它会被按照指定价格执行 
         //--- 或者执行价格低于指定价格
         BigDogBuy.type_filling=ORDER_FILLING_FOK;
         //--- 提供的参数演示了订单如何  
         //--- 在范围内部分执行   
         BigDogBuy.expiration=TimeTradeServer()+6*60*60;
         //--- 根据策略描述, 订单生命周期只限于当前工作日
         //--- 因为从美市开盘已经有2个小时了, 
         //--- 而工作日有8个小时, 我们还剩下 8-2 = 6
         BigDogSell.action=TRADE_ACTION_PENDING;

         //-- 设置挂单
         BigDogSell.magic = MagicNumber;
         BigDogSell.symbol=Symbol();
         BigDogSell.price=Lowest;
         //--- 订单将被设置的价格
         BigDogSell.volume=Lots;
         BigDogSell.sl=Highest;
         //-- 根据策略设置止损
         BigDogSell.tp=Lowest-TakeProfit*Point();
         //--- 设置获利
         BigDogSell.deviation=dev;
         //--- 请求价格的最小偏移量, 
         //--- 换句话说, 执行价格可以偏离指定价格多远
         BigDogSell.type=ORDER_TYPE_SELL_STOP;
         //--- 订单类型, 执行价格基于指定价格或高于指定价格
         //--- 在本例中订单被设置为高于或等于指定价格 
         //--- 如果订单类型为限制买入, 它将被执行
         //--- 或者执行价格低于指定价格
         BigDogSell.type_filling=ORDER_FILLING_FOK;
         //--- 提供的参数演示了订单如何  
         //--- 在范围内部分执行 
         BigDogSell.expiration=TimeTradeServer()+6*60*60;
         //-- 根据策略描述, 订单的生命周期只限于当前工作日
         //--- 因为从美市开盘已经有2个小时了, 
         //--- 而工作日有8个小时, 我们还剩下 8-2 = 6
         MqlTradeResult ResultBuy,ResultSell;
         ZeroMemory(ResultBuy);
         ZeroMemory(ResultSell);
         OrderSend(BigDogBuy,ResultBuy);
         OrderSend(BigDogSell,ResultSell);
        }
     }

//--- 实现追踪止损
   int PosTotal=PositionsTotal();
   for(int i=PosTotal-1; i>=0; i--)
     {
      //--- 遍历持仓查看是否有本EA交易所建仓位.
      if(PositionGetSymbol(i)==Symbol())
        {
         if(MagicNumber==PositionGetInteger(POSITION_MAGIC))
           {
            MqlTick lasttick;
            SymbolInfoTick(Symbol(),lasttick);
            if(PositionGetInteger(POSITION_TYPE)==0)
              { //--- 买入
               if(TrailingStop>0
                  &&(((lasttick.bid-PositionGetDouble(POSITION_PRICE_OPEN))/Point())>TrailingStop)
                  && ((lasttick.bid-PositionGetDouble(POSITION_SL))/Point())>TrailingStop)
                 {
                  MqlTradeRequest BigDogModif;
                  ZeroMemory(BigDogModif);
                  BigDogModif.action= TRADE_ACTION_SLTP;
                  BigDogModif.symbol= Symbol();
                  BigDogModif.sl = lasttick.bid - TrailingStop*Point();
                  BigDogModif.tp = PositionGetDouble(POSITION_TP);
                  BigDogModif.deviation=3;
                  MqlTradeResult BigDogModifResult;
                  ZeroMemory(BigDogModifResult);
                  OrderSend(BigDogModif,BigDogModifResult);
                 }
              }
            if(PositionGetInteger(POSITION_TYPE)==1)
              {//--- 卖出
               if(TrailingStop>0
                  && ((PositionGetDouble(POSITION_PRICE_OPEN)-lasttick.ask)/Point()>TrailingStop)
                  && (PositionGetDouble(POSITION_SL)==0
                  || (PositionGetDouble(POSITION_SL)-lasttick.ask)/Point()>TrailingStop))
                 {
                  MqlTradeRequest BigDogModif;
                  ZeroMemory(BigDogModif);
                  BigDogModif.action= TRADE_ACTION_SLTP;
                  BigDogModif.symbol= Symbol();
                  BigDogModif.sl = lasttick.ask + TrailingStop*Point();
                  BigDogModif.tp = PositionGetDouble(POSITION_TP);
                  BigDogModif.deviation=3;
                  MqlTradeResult BigDogModifResult;
                  ZeroMemory(BigDogModifResult);
                  OrderSend(BigDogModif,BigDogModifResult);
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+


总结

周期性和经常性发生的市场现象有很多,如果加以研究,我们就能得到一些好处。经验丰富的交易者可能也注意到了与著名策略 "BigDog" 的重复之处,文中并未提到,我是故意的 - 这样一来,读者就会把注意力放到究竟如何来准备它们。

您还可以到网上熟悉各种不同的该策略的变体,本文处理的只是该策略所本的现象。