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

量化交易吧 /  量化策略 帖子:3364712 新帖:0

在 MetaTrader 5 中实施多货币模式

K线达人发表于:4 月 17 日 18:50回复(1)

简介

目前,市面上有大量成熟的多货币交易系统、指标和“EA 交易”。然而,开发人员仍然面临多货币系统开发的具体问题。

随着 MetaTrader 5 客户端和 MQL5 编程语言的发布,我们获得了实施完备多货币模式、以及随之而来的更高效的多货币机器人和指标的新的机会。这些新的机会正是本文探讨的主题。


传统方法概述

对我们而言,传统方法就是基于标准 OnTick() 和 OnCalculate() 函数(MQL4 中 start() 函数的替代)实施多货币系统的尝试。简单地说,随着新的价格变动或柱出现在当前图表上,所有(多货币系统中的)货币对被循序请求后续分析和决策。

这种方法存在以下问题:

  1. 整个系统依赖于当前图表的交易品种的到来价格变动。

    对于一个快速市场,当前图表的交易品种的价格变动十分频繁,不会真正存在问题。而对于慢速市场,例如,在夜间,出现的价格变动可能十分稀少:每半分钟一个或甚至更少。在稀疏的到来价格变动的间隔期间,整个多货币系统处于“睡眠”状态,即使其他交易品种的变动可能十分频繁。

    如果系统配置为在较大的时间表上工作,这个缺点就不是那么明显。而越小的时间表,影响就越明显。世界每天都在加速,电脑单位时间内能够处理的信息越来越多,因此,更多的人倾向于使用较小的期段甚至是价格变动。

  2. 历史数据在多货币系统使用的所有交易品种上同步的复杂性。

    “在 MetaTrader 4 中,只有那些其内价格至少发生了一次变化的柱才会被绘制。如果在一分钟内没有发生价格变化,一个一分钟期段的单柱缺口将出现在图表上。”-《“无缺口”图表》一文的开头这么写道。

    这样一种建立图表的方法在 MetaTrader 5 中保留了下来。换言之,每个交易品种在图表上具有同样数量的柱并不意味着它们按时间同步。例如,对于每个交易品种,第一百个柱可能具有不同的开盘时间。因此,在构建和重新计算多货币指标期间,确保所有柱对于每个交易品种彼此保持一致是很重要的。

    但如果系统配置为在较大的时间表上工作,这一点将同样不是十分明显,因为随着期段的延长,柱缺失的可能性急剧下降。虽然,俗话说再细心也不为过。但你永远没法预见,除非你什么都不做。

    我们应另外注意当前未完成柱的同步时间。当一个新柱出现在当前图表上,这并不意味着其他交易品种上同样有新柱形成。因此,使用函数 CopyXXXX() 通过其他交易品种查询新柱价格的尝试可导致下述情况:您将得到该交易品种上一个柱的价格,或仅仅是一个复制错误。顺便一提,其他交易品种上的新柱形成的时间可能远远早于当前交易品种的新柱。这也可能影响情况评估的准确性。

    《使用若干中间指标缓冲区创建多货币指标》一文中介绍的选项可或多或少地解决历史数据同步的问题。

  3. 另一个要点与数据同步相关:我们如何发现一些交易品种更新了历史数据?

    例如,当我们构建一个单一货币指标时,不会有任何问题。如果函数 OnCalculate() 的输入变量 prev_calculated 归零,则我们重新计算指标。但如果交易品种的历史数据更新,且在当前图表上,我们要如何做呢?这可以在任何时间发生,并可能需要重新计算多货币指标。该问题的答案至关重要。

甚至无需考察其他方面,我们可以看到这三个示例已足以产生很多问题:多货币 EA 或指标的代码也过于冗长。而问题完全没有得到解决...


OnTimer() 函数带来的新希望

MQL 程序生成 Timer 事件和 OnTimer() 标准事件处理程序的新能力,带来了新类型多货币系统问世的希望。这是由于,一方面,现在多货币“EA 交易”/指标可独立于通过当前图表的交易品种接收价格变动;另一方面,我们可以按时间监控 EA 工作。但是...

这部分解决了上一节的第 1 段中描述的问题,当然,也带来了一些优势。但是,就像您通过接收 Timer 事件来接收NewTick 事件一样,要追踪变化必须循序查询所有货币对。有时候这会经常发生,从而显著提高对 MQL5 程序的资源使用。

在段落 2 和段落 3 中提出的问题,保持在同一解决方案水平。除此之外,还必须解决 OnTimer() 函数特有的问题。例如,计时器初始化/取消初始化的问题,它在周末工作的问题,等等。

我无意贬低 OnTimer() 事件处理程序的明显优势,但必须指出的是,它依然无法实现完备的多货币模式。


OnChartEvent() 函数带来的新的可能性

上述标准函数的局限性源自它们狭隘的具体化:它们旨在处理具体的、预定义的事件,而不是为多货币系统准备的。

OnChartEvent() 标准自定义事件处理程序可以成为多货币系统开发人员的“救命稻草”。它允许编程人员根据自己的决定生成自己的事件。

例如,没有人能够阻止我们使用该函数获得当前图表上任何交易品种的价格变动。所有我们需要做的就是发送一个“间谍”到具有相应交易品种的图表。

通过玩笑用语“间谍”,我指的是下述指标:

//+------------------------------------------------------------------+
//|                                                         iSpy.mq5 |
//|                                            Copyright 2010, Lizar |
//|                                               lizar-2010@mail.ru |
//+------------------------------------------------------------------+
#define VERSION         "1.00 Build 2 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/ru/users/Lizar"
#property version     VERSION
#property description "iSpy agent-indicator. If you want to get ticks, attach it to the chart"
#property indicator_chart_window

input long            chart_id=0;        // chart id
input ushort          custom_event_id=0; // event id

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,        // size of price[] array
                 const int prev_calculated,  // bars, calculated at the previous call
                 const int begin,            // starting index of data
                 const double& price[]       // array for the calculation
   )
  {
   double price_current=price[rates_total-1];

   //--- Initialization:
   if(prev_calculated==0) 
     { // Generate and send "Initialization" event
      EventChartCustom(chart_id,0,(long)_Period,price_current,_Symbol);
      return(rates_total);
     }
   
   // When the new tick, let's generate the "New tick" custom event
   // that can be processed by Expert Advisor or indicator
   EventChartCustom(chart_id,custom_event_id+1,(long)_Period,price_current,_Symbol);
   
   //--- return value of prev_calculated for next call
   return(rates_total);
  }

我们可以令该“间谍”进入所需交易品种的图表,然后使用 OnChartEvent() 函数在 EA 或指标中处理它的消息。为正确解码“间谍”的消息,我们必须按照下述方式解读参数:

  • id - 事件标识符。如果 id-CHARTEVENT_CUSTOM = 0,我们的“间谍”报告变量 prev_calculated 已归零,应采取相应操作;
  • lparam - 在本例中它表示“间谍”在其上启动的图表的期段;
  • dparam - 变动价格。默认情况下,这是最后的收盘价。虽然在“间谍”启动期间,这可以从枚举 ENUM_APPLIED_PRICE 设置为任意值;
  • sparam - 事件在其上接收的交易品种的名称。

要展示多个“间谍”的同步工作,我们将编写一个简单的 EA exSpy.mq5(完整版本请见档案):

//+------------------------------------------------------------------+
//|                                                        exSpy.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/ru/users/Lizar |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 1 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/ru/users/Lizar"
#property version     VERSION
#property description "The Expert Advisor shows the work of iSPY indicator"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {   
   if(iCustom("GBPUSD",PERIOD_M1,"iSpy",ChartID(),0)==INVALID_HANDLE) 
      { Print("Error in setting of spy on GBPUSD"); return(true);}
   if(iCustom("EURUSD",PERIOD_M1,"iSpy",ChartID(),1)==INVALID_HANDLE) 
      { Print("Error in setting of spy on EURUSD"); return(true);}
   if(iCustom("USDJPY",PERIOD_M1,"iSpy",ChartID(),2)==INVALID_HANDLE) 
      { Print("Error in setting of spy on USDJPY"); return(true);}
      
   Print("Spys ok, waiting for events...");
   //---
   return(0);
  }
  
//+------------------------------------------------------------------+
//| The Standard event handler.                                      |
//| See MQL5 Reference for the details.                              |
//|                                                                  |
//| In this case it is used for decoding of spy messages, sent by    |
//| iSPY spy indicator                                               |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // event id:
                                     // if id-CHARTEVENT_CUSTOM=0-"initialization" event
                const long&   lparam, // chart period
                const double& dparam, // price
                const string& sparam  // symbol
               )
  {
   if(id>=CHARTEVENT_CUSTOM)      
     {
      Print(TimeToString(TimeCurrent(),TIME_SECONDS)," -> id=",
            id-CHARTEVENT_CUSTOM,":  ",sparam," ",
            EnumToString((ENUM_TIMEFRAMES)lparam)," price=",dparam);
     }
  }
//+------------------------------------------------------------------+

在任意图表上启动“EA 交易”。

结果如下:


 

从日志中我们可以看到,我们获得所需交易品种的所有价格变动以及“初始化”事件,尤其是在更新或上传历史数据时会接收到后者。

我们来总结一下中间结果:

  • 正如我们在使用标准 OnTick() 和 OnCalculate() 函数时所做的那样,我们没有依靠特定交易品种的价格变动。
  • 当使用 OnTimer() 时,我们没有受限于严格定义的时间间隔。
  • 没有必要要求所有交易品种位于序列中或循环中以持续追踪它们的变化。一旦交易品种发生变化,我们获得相应的事件。我们可以通过 id 事件标识符或 sparam 参数,来识别在其上发生事件的交易品种。
  • 我们解决了单独为每个交易品种追踪历史数据与服务器同步的问题。
  • 给出的工作的一小部分被指定为其他程序。这和数据处理的并行化相关。
  • 在 OnChartEvent() 函数的参数中,我们还获得了其他的信息:交易品种的期段、价格和名称。这可以极大地简化 EA 或指标的代码,因为不需要额外地查询这些数据。

至此,我们已能够基本圆满地结束本文,因为我们已通过 MetaTrader 5 终端和 MQL5 编程语言成功实施了多货币模式。但这只是一个“原始”的实施。因此,我们将就此作进一步的讨论。


多货币模式的实施

如果您以其纯粹形式使用上述实施多货币体系的理念,则从某种意义上说,您将开始遇上麻烦。问题是,这种方法允许我们获得“间谍”在其上运行的每个交易品种的所有价格变动。

对于快速市场而言,在一秒钟内,每个交易品种上就可能有海量的价格变动。这可能导致事件顺序“堵塞”。下面是一则来自帮助部分的警告消息:

客户端将出现的事件添加到事件队列。因此事件按照它们被接收的顺序逐个接受处理。NewTick 事件是一个例外。如果队列已经有了这样一个事件,或正在处理该事件,则新的 NewTick 事件不会进入队列。

事件队列在大小上有限制。如果队列溢出,旧的事件将不经处理直接移除,以允许接受新的事件。因此,建议编写高效的事件处理程序,并且不推荐使用无限循环(有一个仅处理 Start 事件的例外脚本)。

队列的溢出可导致多货币指标或 EA 的重要事件损失。这是一方面。另一方面,我们并不总是需要所有交易品种的价格变动。有时我们只需要获得任意时间表上的“新柱”事件。或不同时间表上的多个“新柱”事件。从根本上说,我们的“间谍”不适于此类要求,并且它的使用不是很方便。

我们来使其具有普适性,以便我们不会再次回到如何基于多货币 EA 或指标的交易品种获得事件的问题。为此,我们将来自多货币“EA 交易”和指标的“MCM 控制面板”说明的 ENUM_CHART_EVENT_SYMBOL 事件枚举作为范例:

enum ENUM_CHART_EVENT_SYMBOL
  {
   CHARTEVENT_INIT      =0,         // "Initialization" event
   CHARTEVENT_NO        =0,         // No events

   CHARTEVENT_NEWBAR_M1 =0x00000001, // "New bar" event on M1 chart
   CHARTEVENT_NEWBAR_M2 =0x00000002, // "New bar" event on M2 chart
   CHARTEVENT_NEWBAR_M3 =0x00000004, // "New bar" event on M3 chart
   CHARTEVENT_NEWBAR_M4 =0x00000008, // "New bar" event on M4 chart
   
   CHARTEVENT_NEWBAR_M5 =0x00000010, // "New bar" event on M5 chart
   CHARTEVENT_NEWBAR_M6 =0x00000020, // "New bar" event on M6 chart
   CHARTEVENT_NEWBAR_M10=0x00000040, // "New bar" event on M10 chart
   CHARTEVENT_NEWBAR_M12=0x00000080, // "New bar" event on M12 chart
   
   CHARTEVENT_NEWBAR_M15=0x00000100, // "New bar" event on M15 chart
   CHARTEVENT_NEWBAR_M20=0x00000200, // "New bar" event on M20 chart
   CHARTEVENT_NEWBAR_M30=0x00000400, // "New bar" event on M30 chart
   CHARTEVENT_NEWBAR_H1 =0x00000800, // "New bar" event on H1 chart
   
   CHARTEVENT_NEWBAR_H2 =0x00001000, // "New bar" event on H2 chart
   CHARTEVENT_NEWBAR_H3 =0x00002000, // "New bar" event on H3 chart
   CHARTEVENT_NEWBAR_H4 =0x00004000, // "New bar" event on H4 chart
   CHARTEVENT_NEWBAR_H6 =0x00008000, // "New bar" event on H6 chart
   
   CHARTEVENT_NEWBAR_H8 =0x00010000, // "New bar" event on H8 chart
   CHARTEVENT_NEWBAR_H12=0x00020000, // "New bar" event on H12 chart
   CHARTEVENT_NEWBAR_D1 =0x00040000, // "New bar" event on D1 chart
   CHARTEVENT_NEWBAR_W1 =0x00080000, // "New bar" event on W1 chart
     
   CHARTEVENT_NEWBAR_MN1=0x00100000, // "New bar" event on MN1 chart
   CHARTEVENT_TICK      =0x00200000, // "New tick" event
   
   CHARTEVENT_ALL       =0xFFFFFFFF, // All events
  };

事实上,该枚举是自定义图表事件的标志。它是极小集,这可能是多货币模式所要求的。当然,我们可以对它进行补充。标志的组合将决定我们要从“间谍”发出的事件。

标志可使用逐位运算 "OR" 进行组合。例如,CHARTEVENT_NEWBAR_M1 | CHARTEVENT_NEWBAR_H1 组合表示我们将在“间谍”的帮助下发出来自分钟和小时时间表的“新柱”。这些标志将是我们“间谍”指标的输入参数。接下来我们将称它为“代理指标”。

指标本身符合新的理念,其代码如下:

//+------------------------------------------------------------------+
//|                                        Spy Control panel MCM.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/zh/users/Lizar |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 3 (26 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/zh/users/Lizar"
#property version     VERSION
#property description "This is the MCM Control Panel agent-indicator."
#property description "Is launched on the required symbol on any time-frame"
#property description "and generates the custom NewBar event and/or NewTick"
#property description "for the chart which receives the event"

#property indicator_chart_window
  
input long                    chart_id;                 // identifier of the chart which receives the event
input ushort                  custom_event_id;          // event identifier  
input ENUM_CHART_EVENT_SYMBOL flag_event=CHARTEVENT_NO;// indicator, which determines the event type.

MqlDateTime time, prev_time;

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate (const int rates_total,       // size of the price[] array
                 const int prev_calculated, // bars processed at the previous call
                 const int begin,           // where the data begins
                 const double& price[]      // calculations array
   )
  {  
   double price_current=price[rates_total-1];

   TimeCurrent(time);
   
   if(prev_calculated==0)
     {
      EventCustom(CHARTEVENT_INIT,price_current);
      prev_time=time; 
      return(rates_total);
     }
   
//--- new tick
   if((flag_event & CHARTEVENT_TICK)!=0) EventCustom(CHARTEVENT_TICK,price_current);       

//--- check change time
   if(time.min==prev_time.min && 
      time.hour==prev_time.hour && 
      time.day==prev_time.day &&
      time.mon==prev_time.mon) return(rates_total);

//--- new minute
   if((flag_event & CHARTEVENT_NEWBAR_M1)!=0) EventCustom(CHARTEVENT_NEWBAR_M1,price_current);     
   if(time.min%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_M2)!=0)  EventCustom(CHARTEVENT_NEWBAR_M2,price_current);
   if(time.min%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_M3)!=0)  EventCustom(CHARTEVENT_NEWBAR_M3,price_current); 
   if(time.min%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_M4)!=0)  EventCustom(CHARTEVENT_NEWBAR_M4,price_current);      
   if(time.min%5 ==0 && (flag_event & CHARTEVENT_NEWBAR_M5)!=0)  EventCustom(CHARTEVENT_NEWBAR_M5,price_current);     
   if(time.min%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_M6)!=0)  EventCustom(CHARTEVENT_NEWBAR_M6,price_current);     
   if(time.min%10==0 && (flag_event & CHARTEVENT_NEWBAR_M10)!=0) EventCustom(CHARTEVENT_NEWBAR_M10,price_current);      
   if(time.min%12==0 && (flag_event & CHARTEVENT_NEWBAR_M12)!=0) EventCustom(CHARTEVENT_NEWBAR_M12,price_current);      
   if(time.min%15==0 && (flag_event & CHARTEVENT_NEWBAR_M15)!=0) EventCustom(CHARTEVENT_NEWBAR_M15,price_current);      
   if(time.min%20==0 && (flag_event & CHARTEVENT_NEWBAR_M20)!=0) EventCustom(CHARTEVENT_NEWBAR_M20,price_current);      
   if(time.min%30==0 && (flag_event & CHARTEVENT_NEWBAR_M30)!=0) EventCustom(CHARTEVENT_NEWBAR_M30,price_current);      
   if(time.min!=0) {prev_time=time; return(rates_total);}
//--- new hour
   if((flag_event & CHARTEVENT_NEWBAR_H1)!=0) EventCustom(CHARTEVENT_NEWBAR_H1,price_current);
   if(time.hour%2 ==0 && (flag_event & CHARTEVENT_NEWBAR_H2)!=0)  EventCustom(CHARTEVENT_NEWBAR_H2,price_current);
   if(time.hour%3 ==0 && (flag_event & CHARTEVENT_NEWBAR_H3)!=0)  EventCustom(CHARTEVENT_NEWBAR_H3,price_current);      
   if(time.hour%4 ==0 && (flag_event & CHARTEVENT_NEWBAR_H4)!=0)  EventCustom(CHARTEVENT_NEWBAR_H4,price_current);      
   if(time.hour%6 ==0 && (flag_event & CHARTEVENT_NEWBAR_H6)!=0)  EventCustom(CHARTEVENT_NEWBAR_H6,price_current);      
   if(time.hour%8 ==0 && (flag_event & CHARTEVENT_NEWBAR_H8)!=0)  EventCustom(CHARTEVENT_NEWBAR_H8,price_current);      
   if(time.hour%12==0 && (flag_event & CHARTEVENT_NEWBAR_H12)!=0) EventCustom(CHARTEVENT_NEWBAR_H12,price_current);      
   if(time.hour!=0) {prev_time=time; return(rates_total);}
//--- new day
   if((flag_event & CHARTEVENT_NEWBAR_D1)!=0) EventCustom(CHARTEVENT_NEWBAR_D1,price_current);      
//--- new week
   if(time.day_of_week==1 && (flag_event & CHARTEVENT_NEWBAR_W1)!=0) EventCustom(CHARTEVENT_NEWBAR_W1,price_current);      
//--- new month
   if(time.day==1 && (flag_event & CHARTEVENT_NEWBAR_MN1)!=0) EventCustom(CHARTEVENT_NEWBAR_MN1,price_current);      
   prev_time=time;
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

void EventCustom(ENUM_CHART_EVENT_SYMBOL event,double price)
  {
   EventChartCustom(chart_id,custom_event_id,(long)event,price,_Symbol);
   return;
  } 

该指标是 MCM 控制面板的一部分,我们没有对其重命名,附件只包含了它的更新版本(请参见 "Spy Control panel MCM.mq5")。但这并不意味着它不能从面板脱离出来单独使用。

该代理指标使用 OnChartEvent() 函数生成自定义用户事件,并将它们传递至图表接收程序以在 EA 或指标中进一步处理这些事件。现在,该函数的输入参数应解读如下:

  • id - 事件标识符;
  • lparam - 事件指标,接受自面板代理。这些指标对应于 ENUM_CHART_EVENT_SYMBOL 枚举;
  • dparam - 具体时间表的新柱的变动价格或开盘价;
  • sparam - 事件在其上发生的交易品种的名称。

 演示 EA 看上去并没有比上一个 EA 更复杂(完整版本附于档案中):

//+------------------------------------------------------------------+
//|                                      exSpy Control panel MCM.mq5 |
//|                                            Copyright 2010, Lizar |
//|                            https://www.mql5.com/zh/users/Lizar |
//+------------------------------------------------------------------+
#define VERSION       "1.00 Build 1 (28 Dec 2010)"

#property copyright   "Copyright 2010, Lizar"
#property link        "https://www.mql5.com/zh/users/Lizar"
#property version     VERSION
#property description "The EA demonstrates the work of the MCM Spy Control panel"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {   
   if(iCustom("GBPUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),0,
             CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_M5)==INVALID_HANDLE) 
      { Print("Error in setting of spy on GBPUSD"); return(true);}
   if(iCustom("EURUSD",PERIOD_M1,"Spy Control panel MCM",ChartID(),1,
             CHARTEVENT_NEWBAR_M2)==INVALID_HANDLE) 
      { Print("Error in setting of spy on EURUSD"); return(true);}
   if(iCustom("USDJPY",PERIOD_M1,"Spy Control panel MCM",ChartID(),2,
             CHARTEVENT_NEWBAR_M6)==INVALID_HANDLE) 
      { Print("Error in setting of spy on USDJPY"); return(true);}
      
   Print("Spys ok, waiting for events...");
   //---
   return(0);
  }
  
//+------------------------------------------------------------------+
//| The Standard event handler.                                      |
//| See MQL5 Reference for the details.                              |
//|                                                                  |
//| In this case it is used for decoding of spy messages, sent by    |
//| iSPY Control panel MCM indicator.                                |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,           // event identifier
                  const long&   lparam, // the event flag.
                                       // event flag is a bit mask of ENUM_CHART_EVENT_SYMBOL enumeration.
                  const double& dparam, // price
                  const string& sparam  // symbol 
                 )
  {
   if(id>=CHARTEVENT_CUSTOM)      
     {
      Print(TimeToString(TimeCurrent(),TIME_SECONDS)," -> id=",id-CHARTEVENT_CUSTOM,
           ":  ",sparam," ",EnumToString((ENUM_CHART_EVENT_SYMBOL)lparam)," price=",dparam);
     }
  }

exSpy 控制面板 MCM 的工作结果:


 

如您所见,我们定期接收所有请求的事件。

我们再次对中间结果做一小结:

  • 得益于新版本的代理指标,我们保留了之前取得的所有成绩。
  • 现在,我们可以通过我们的多货币 MQL 程序指定至少 23 个事件,其中包括“新价格变动”事件、“新柱”事件和“初始化”事件。
  • 更多来自 EA/指标的工作被指派给代理,以进行卸载。

好了,事实证明,在 MetaTrader 5 中实施完备的多货币模式也并不是那么困难。

此外,我想强调几处细微的差异。

首先,“代理”为我们的多货币 EA/指标生成的所有事件都是外部的。与此相关的问题出现:“是否有必要直接从 EA/指标运行代理?”我的回答是:“不需要。”

其次,在 OnChartEvent() 函数中,事件标识符 id 似乎是多余的,因为我们可以通过参数 sparam - 交易品种的名称找到事件接收所针对的交易品种。因此,我们也许可以将它用于其他目的?我的回答是:“是的,我们可以这样做。”

这些观点带来了多货币“EA 交易”和指标的“MCM 控制面板”的问世。它是一种位于终端和 EA/指标之间的“中间层”,在配置多货币环境中为我们提供了更多的益处和灵活性。

  • 该面板可作为一个独立的指标安装在图表上,然后分配与面板兼容的多货币指标。
  • 面板可将指标和 EA 作为组成单元包含进来。它将与它们一起上传。  
  • 我们可以使用 Events(事件)菜单,从 Market Watch(市场报价)窗口激活/取消激活交易品种以进行交易或分析。在 OnChartEvent() 函数中,我们无法通过事件标识符 id 在 Market Watch(市场报价)窗口中找到交易品种的序列号。
  • 我们可以通过在 Market Watch(市场报价)窗口中选择的任何期段和任何交易品种的价格变动或“新柱”事件设置交易模式。所有这些都通过常规菜单完成。
  • 我们可以更改上述所有配置而无需卸载、停止或进入 EA 或指标的属性窗口。
  • 所有这些都不会在多货币指标和 EA 的创建中限制创新潜力。此外,我们现在无需将该面板的输出合并至我们的代码中。代理指标的管理现已落实到控制面板中。
  • 多货币系统的结构更加简单。


USDx 美元指数的多货币指标 RSI

要体验上述方法的所有优势,我建议使用 MCM 控制面板实施 USDx 美元指数的 RSI 指标的多货币变体。

首先,我要指出几个特点。通常,在尝试分析美元指数时,我们只依赖于指标的指数读数。在我看来,这样做不是完全正确的,因为货币对指数篮子中的每一个交易品种都做出了自己的贡献。因此,作为示例,我们使用与指数计算公式相似的公式来计算美元指数的 RSI:

换句话说,首先我们将计算特定货币对的 RSI,然后为指数读取 RSI,价格系数的权重纳入考虑。

读者可能会注意到与多货币系统中使用的所有交易品种的历史数据同步有关的问题。(请参见“传统方法概述”一节的段落 2).

指标中使用类函数构建 RSI 同步缓冲区解决了此问题(SynchronizedBufferRSI.mqh 文件)。提供该类的完整代码毫无意义,因此下文仅将一些相关部分列示了出来。

首先,指标缓冲区使用公共访问修饰符在类中定义:

public:
   double   buffer[];   // indicator buffer

其次,指标初始化使用类方法进行初始化:

//--- Initialization methods:
bool Init(int n,string symbol,int rsi_count,int rsi_period);

再次,对于每个柱,指标缓冲区的值使用类的刷新方法通过当前时间表进行同步:

//+------------------------------------------------------------------+
//| The method of receiving/updating indicator data for one bar      |
//| of the indicator buffer.                                         |
//| INPUT:  bar   - bar number                                       |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CSynchronizedBufferRSI::Refresh(int bar=0)
  {
   buffer[bar]=EMPTY_VALUE; // Initialization of the bar of the indicator buffer.
     
   //--- Inquire the time of the bar for the current graph:
   datetime time[1];      
   if(CopyTime(_Symbol,_Period,bar,1,time)!=1) return; // In case of an error, we wait for the next tick/bar...

   //--- Request the value of the indicator for the symbol for the time,
   //--- consistent with that of the bar of the current graph:
   double value[1];
   if(CopyBuffer(m_handle,0,time[0],time[0],value)!=1) return; // In case of an error, wait for the next tick/bar...

   buffer[bar]=value[0];
   return;
  }

为使所有指标缓冲区完美同步,我们需要使用在后续文章中介绍的整个无“缺口”分钟时间表。但对于本指标缓冲区同步方法,我们专门选择当前图表的时间表,因为指标在其上显示。

根据我自己的经验,可以说对于较小的周期,针对任何时间序列或指标缓冲区使用这种同步方法是有道理的,如果它们的交易品种与当前图表上的交易品种不同的话。

该图清楚地显示了为什么这样做是值得的:


 

对于较大的时间表,这不是通常所观察到的。

最后一点,下面是在指标中使用的标准用户事件处理程序的代码:

//+------------------------------------------------------------------+
//| The Standard event handler.                                      |
//| See MQL5 Reference for the details.                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // event identifier or position symbol in the "Market Match"+CHARTEVENT_CUSTOM  
                const long& lparam,   // event indicator
                const double& dparam, // price
                const string& sparam  // symbol
                )
  {
   int custom_id=id-CHARTEVENT_CUSTOM-1;
   
   if(custom_id>=0)      
     {
      if(lparam!=CHARTEVENT_NEWBAR_NO)
        { 
         //--- Recalculation of the last uncompleted bar:
         if(EventToPeriod(lparam)==_Period && sparam==_Symbol)
           { // Recalculation of the indicator, if a new bar on the current chart
            iRSIUSDx_Ind[0]=EMPTY_VALUE;
            //--- Updating the value of the RSI for all of the currency pairs for the new bar
            for(int i=0;i<symbol_total;i++) buffer[i].Refresh();
            iRSIUSDx(symbol_total);   // calculation of the current incomplete bar RSI for the index
            return;
           }
         
         buffer[custom_id].Refresh(); // The value of RSI for the custom_id of the currency pair for the current bar
         iRSIUSDx(symbol_total);      // calculation of the RSI for the current(uncompleted) bar RSIx
         return;
        }
      else 
        { 
         //--- Recalculation of the indicator for the "Initialization" event 
         buffer[custom_id].RefreshBuffer();     // Update of the RSI buffer for the custom_id of the currency pair
         Init_iRSIUSDx(symbol_total,calculate); // Update of the RSI buffer for the index
         return;
        }
     }
  }

代码特点:

  • 使用事件标识符 id 以引用包含指向类实例的指针的数组,类实例旨在计算指标缓冲区 RSI,与当前时间表同步。该方法极大地简化了代码的结构。
  • “初始化”事件用于仅为被其接收的货币对而非所有六个交易品种重新计算指标缓冲区 RSI。如前文所述,这允许您同步指标,例如,在为某个交易品种更新历史数据时。
  • “新柱”事件用于同步当前图表上新柱的所有指标缓冲区 RSI。
  • 从所有货币对使用“新的价格变动”事件以更新最后一个未完成柱上的指标。此外,仅为接收到“新的价格变动”的货币对重新计算柱。

在研究了美元指数 USDx 的 RSI 指标的完整代码后,它的工作方式更加一目了然。

安装要点:

  • 下载多货币“EA 交易”和指标的“MCM 控制面板”并编译 "iControl panel MCM.mq5" 和 "Spy Control panel MCM.mq5" 文件。
  • 在 Market Match(市场匹配)窗口中指定交易品种的下述顺序:
    1. EURUSD
    2. USDJPY
    3. GBPUSD
    4. USDCAD
    5. USDSEK
    6. USDCHF
    这是必须的,因为我没有对指标执行适当的检查,并且该序列是指标正确计算所必需的。
  • 将 iRSIUSDx.zip 档案解压缩至 /MQL5 文件夹。将 iRSIUSDx.ex5 从 /MQL5/Indicators/iRSIUSDx/ 文件夹置于期段为 M1 的 EURUSD 图表上。
  • 如此处所述,在 Control panel MCM(控制面板 MCM)面板的 Event(事件)菜单中,按顺序为所有六个交易品种设置 New tick(新的价格变动)事件。您将得到一个与上文图片类似的图片。
  • 此外,对于 EURUSD 交易品种,在分钟图表上设置 new bar(新柱)事件。在指标中,该事件用于在新柱位于当前时间表(等于 M1)上时进行同步。
  • 如果您想要更直观的示例,请如此处所述设置美元指数。

总结

文中论述的在 MetaTrader 5 中完备多货币模式的实施充分展示了该平台和 MQL5 编程语言在解决该问题上的优势。之前遇到的大多数困难现已迎刃而解。

显然,这仅仅是在这个方向上的发展的开端。可以肯定,未来会有更多更好的方法用于数据同步、多货币模式管理等。我们现在已具备了所有必要工具,我希望这一点毫无疑问。


全部回复

0/140

达人推荐

量化课程

    移动端课程