下载MetaTrader 5

跨平台智能交易系统: CExpertAdvisor 和 CExpertAdvisors 类

6 十一月 2017, 08:24
Enrico Lambino
0
5 522

内容目录

  1. 概述
  2. 智能交易系统类
  3. 初始化
  4. 新柱线检测
  5. OnTick 处理函数
  6. 智能交易系统容器
  7. 数据持久化
  8. 示例
  9. 后记
  10. 结束语

概述

在早前有关这个主题的文章中, 智能交易系统的例子通过使用自定义函数将其组件分散至智能交易系统的主体头文件中。本文阐述的 CExpertAdvisor 和 CExpertsAdvisors 类, 目标是为跨平台智能交易系统的各个组件之间创建更加和谐的交互。它还解决了智能交易系统中经常遇到的一些常见问题, 如加载和保存易失性数据, 以及新柱线的检测。

智能交易系统类

CExpertAdvisorBase 类显示在下面的代码片段中。此刻, MQL4 和 MQL5 之间的大部分差异都可由之前文章中讨论的其它类对象进行处理。

class CExpertAdvisorBase : public CObject
  {
protected:
   //--- 交易参数
   bool              m_active;
   string            m_name;
   int               m_distance;
   double            m_distance_factor_long;
   double            m_distance_factor_short;
   bool              m_on_tick_process;
   //--- 信号参数
   bool              m_every_tick;
   bool              m_one_trade_per_candle;
   datetime          m_last_trade_time;
   string            m_symbol_name;
   int               m_period;
   bool              m_position_reverse;
   //--- 信号对象
   CSignals         *m_signals;
   //--- 交易对象   
   CAccountInfo      m_account;
   CSymbolManager    m_symbol_man;
   COrderManager     m_order_man;
   //--- 交易时间对象
   CTimes           *m_times;
   //--- 蜡烛
   CCandleManager    m_candle_man;
   //--- 事件
   CEventAggregator *m_event_man;
   //--- 容器
   CObject          *m_container;
public:
                     CExpertAdvisorBase(void);
                    ~CExpertAdvisorBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_EXPERT;}
   //--- 初始化
   bool              AddEventAggregator(CEventAggregator*);
   bool              AddMoneys(CMoneys*);
   bool              AddSignal(CSignals*);
   bool              AddStops(CStops*);
   bool              AddSymbol(const string);
   bool              AddTimes(CTimes*);
   virtual bool      Init(const string,const int,const int,const bool,const bool,const bool);
   virtual bool      InitAccount(void);
   virtual bool      InitCandleManager(void);
   virtual bool      InitEventAggregator(void);
   virtual bool      InitComponents(void);
   virtual bool      InitSignals(void);
   virtual bool      InitTimes(void);
   virtual bool      InitOrderManager(void);
   virtual bool      Validate(void) const;
   //--- 容器
   void              SetContainer(CObject*);
   CObject          *GetContainer(void);
   //--- 激活与失活
   bool              Active(void) const;
   void              Active(const bool);
   //--- 赋值与取值       
   string            Name(void) const;
   void              Name(const string);
   int               Distance(void) const;
   void              Distance(const int);
   double            DistanceFactorLong(void) const;
   void              DistanceFactorLong(const double);
   double            DistanceFactorShort(void) const;
   void              DistanceFactorShort(const double);
   string            SymbolName(void) const;
   void              SymbolName(const string);
   //--- 对象指针
   CAccountInfo     *AccountInfo(void);
   CStop            *MainStop(void);
   CMoneys          *Moneys(void);
   COrders          *Orders(void);
   COrders          *OrdersHistory(void);
   CStops           *Stops(void);
   CSignals         *Signals(void);
   CTimes           *Times(void);
   //--- 订单管理
   string            Comment(void) const;
   void              Comment(const string);
   bool              EnableTrade(void) const;
   void              EnableTrade(bool);
   bool              EnableLong(void) const;
   void              EnableLong(bool);
   bool              EnableShort(void) const;
   void              EnableShort(bool);
   int               Expiration(void) const;
   void              Expiration(const int);
   double            LotSize(void) const;
   void              LotSize(const double);
   int               MaxOrdersHistory(void) const;
   void              MaxOrdersHistory(const int);
   int               Magic(void) const;
   void              Magic(const int);
   uint              MaxTrades(void) const;
   void              MaxTrades(const int);
   int               MaxOrders(void) const;
   void              MaxOrders(const int);
   int               OrdersTotal(void) const;
   int               OrdersHistoryTotal(void) const;
   int               TradesTotal(void) const;
   //--- 信号管理   
   int               Period(void) const;
   void              Period(const int);
   bool              EveryTick(void) const;
   void              EveryTick(const bool);
   bool              OneTradePerCandle(void) const;
   void              OneTradePerCandle(const bool);
   bool              PositionReverse(void) const;
   void              PositionReverse(const bool);
   //--- 额外蜡烛
   void              AddCandle(const string,const int);
   //--- 新柱线检测
   void              DetectNewBars(void);
   //-- 事件
   virtual bool      OnTick(void);
   virtual void      OnChartEvent(const int,const long&,const double&,const string&);
   virtual void      OnTimer(void);
   virtual void      OnTrade(void);
   virtual void      OnDeinit(const int,const int);
   //--- 恢复
   virtual bool      Save(const int);
   virtual bool      Load(const int);

protected:
   //--- 蜡烛管理   
   virtual bool      IsNewBar(const string,const int);
   //--- 订单管理
   virtual void      ManageOrders(void);
   virtual void      ManageOrdersHistory(void);
   virtual void      OnTradeTransaction(COrder*) {}
   virtual datetime  Time(const int);
   virtual bool      TradeOpen(const string,const ENUM_ORDER_TYPE,double,bool);
   //--- 品种管理
   virtual bool      RefreshRates(void);
   //--- 逆初始化
   void              DeinitAccount(void);
   void              DeinitCandle(void);
   void              DeinitSignals(void);
   void              DeinitSymbol(void);
   void              DeinitTimes(void);
  };

在这个类中所声明的类方法大部分作为其组件方法的包装器。类中的关键方法将在后面的章节中讨论。

初始化

在智能交易系统的初始化阶段, 我们的主要目标是实例化交易策略所需的对象 (如资金管理, 信号等), 然后将它们与 CExpertAdvisor 的实例集成, 它也需要在 OnInit 期间创建。为了这个目标, 当智能交易系统中任何事件函数被触发时, 我们需要提供的只是一行代码, 调用 CExpertAdvisor 实例当中的适当处理程序或方法。这与使用 MQL5 标准库 CExpert 的方式非常相似。

创建 CExpertAdvisor 实例后, 下一个要调用的方法是其 Init 方法。所述方法的代码如下所示:

bool CExpertAdvisorBase::Init(string symbol,int period,int magic,bool every_tick=true,bool one_trade_per_candle=true,bool position_reverse=true)
  {
   m_symbol_name=symbol;
   CSymbolInfo *instrument;
   if((instrument=new CSymbolInfo)==NULL)
      return false;
   if(symbol==NULL) symbol=Symbol();
   if(!instrument.Name(symbol))
      return false;
   instrument.Refresh();
   m_symbol_man.Add(instrument);
   m_symbol_man.SetPrimary(m_symbol_name);
   m_period=(ENUM_TIMEFRAMES)period;
   m_every_tick=every_tick;
   m_order_man.Magic(magic);
   m_position_reverse=position_reverse;
   m_one_trade_per_candle=one_trade_per_candle;
   CCandle *candle=new CCandle();
   candle.Init(instrument,m_period);
   m_candle_man.Add(candle);
   Magic(magic);
   return false;
  }

在此, 我们创建了在交易策略中常见组件的大多数实例。这包括要使用的品种或金融工具 (必须转换为某种类型的对象), 以及默认周期或时间帧。它的规则还包含是否应该在每笔分价、亦或每根蜡烛的第一笔分价时执行其核心任务, 是否应该限制每根蜡烛最多执行一笔交易 (以防止在同一根蜡烛内多次入场), 以及是否应该在相反信号时逆转其仓位 (现有交易平仓, 并根据新信号重新入场)。

在 OnInit 函数结束时, CExpertAdviso r的实例应调用它的 InitComponents 方法。以下代码展示了 CExpertBase 的所述方法:

bool CExpertAdvisorBase::InitComponents(void)
  {
   if(!InitSignals())
     {
      Print(__FUNCTION__+": 信号初始化错误");
      return false;
     }
   if(!InitTimes())
     {
      Print(__FUNCTION__+": 时间初始化错误");
      return false;
     }
   if(!InitOrderManager())
     {
      Print(__FUNCTION__+": 订单管理器初始化错误");
      return false;
     }
   if(!InitCandleManager())
     {
      Print(__FUNCTION__+": 蜡烛管理器初始化错误");
      return false;
     }
   if(!InitEventAggregator())
     {
      Print(__FUNCTION__+": 事件聚合器初始化错误");
      return false;
     }
   return true;
  }

在该方法中, 调用 Expert Advisor 实例中每个组件的 Init 方法。也是通过该方法, 每个组件的 Validate 方法被调用, 查看它们的设置是否能通过验证。

新柱线检测

一些交易策略只需要在新蜡烛的第一笔分价时操作。有很多方法来实现这个功能。其一是将当前蜡烛的开盘时间和开盘价格与之前的状态进行比较, 这也是 CCandle 类中实现的方法。以下代码展示了以 CCandleBase 声明, 它是 CCandle 的基类:

class CCandleBase : public CObject
  {
protected:
   bool              m_new;
   bool              m_wait_for_new;
   bool              m_trade_processed;
   int               m_period;
   bool              m_active;
   MqlRates          m_last;
   CSymbolInfo      *m_symbol;
   CEventAggregator *m_event_man;
   CObject          *m_container;
public:
                     CCandleBase(void);
                    ~CCandleBase(void);
   virtual int       Type(void) const {return(CLASS_TYPE_CANDLE);}
   virtual bool      Init(CSymbolInfo*,const int);
   virtual bool      Init(CEventAggregator*);
   CObject          *GetContainer(void);
   void              SetContainer(CObject*);
   //--- 赋值与取值
   void              Active(bool);
   bool              Active(void) const;
   datetime          LastTime(void) const;
   double            LastOpen(void) const;
   double            LastHigh(void) const;
   double            LastLow(void) const;
   double            LastClose(void) const;
   string            SymbolName(void) const;
   int               Timeframe(void) const;
   void              WaitForNew(bool);
   bool              WaitForNew(void) const;
   //--- 处理
   virtual bool      TradeProcessed(void) const;
   virtual void      TradeProcessed(bool);
   virtual void      Check(void);
   virtual void      IsNewCandle(bool);
   virtual bool      IsNewCandle(void) const;
   virtual bool      Compare(MqlRates &) const;
   //--- 恢复
   virtual bool      Save(const int);
   virtual bool      Load(const int);
  };

在图表上检查新蜡烛是否存在, 这可通过 Check 方法来完成, 如下所示:

CCandleBase::Check(void)
  {
   if(!Active())
      return;
   IsNewCandle(false);
   MqlRates rates[];
   if(CopyRates(m_symbol.Name(),(ENUM_TIMEFRAMES)m_period,1,1,rates)==-1)
      return;
   if(Compare(rates[0]))
     {
      IsNewCandle(true);
      TradeProcessed(false);
      m_last=rates[0];
     }
  }

如果要检查一根新柱线, 智能交易系统实例应在每笔分价调用一次这个方法。程序员随后可以自由扩展 CCxpertAdvisor, 以便当图表上出现新蜡烛时执行其它任务。

如上面代码所示, 柱线开盘时间和开盘价的实际比较是通过类方法 Compare 完成的. 如下面的代码所示:

bool CCandleBase::Compare(MqlRates &rates) const
  {
   return (m_last.time!=rates.time ||
           (m_last.open/m_symbol.TickSize())!=(rates.open/m_symbol.TickSize()) || 
           (!m_wait_for_new && m_last.time==0));
  }

检查新柱线是否存在的方法取决于三个条件。保证结果为真至少需要满足一个, 在图表上示意新蜡烛存在:

  1. 最后记录的开盘时间不等于当前柱线的开盘时间
  2. 最后记录的开盘价格不等于当前柱线的开盘价格
  3. 最后记录的开盘时间为零, 且新柱线不必是该柱线的第一笔分价

前两个条件涉及当前柱线汇率与之前记录状态的直接比较。第三个条件仅适用于智能交易系统所遇到的第一笔分价。一旦智能交易系统加载到图表上, 它还没有任何以往的记录 (开盘时间和开盘价), 因此最后记录的开盘时间将为零。一些交易者认为这根柱线可作为他们的智能交易系统的一根新柱线, 而另一些人则倾向于让智能交易系统初始化之后等待新柱线出现在图表上。

与前面讨论过的其它类相似, 类 CCandle 也有它的容器 CCandleManager。以下代码所展示的是 CCandleManagerBase 的声明:

class CCandleManagerBase : public CArrayObj
  {
protected:
   bool              m_active;
   CSymbolManager   *m_symbol_man;
   CEventAggregator *m_event_man;
   CObject          *m_container;
public:
                     CCandleManagerBase(void);
                    ~CCandleManagerBase(void);
   virtual int       Type(void) const {return(CLASS_TYPE_CANDLE_MANAGER);}
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual bool      Add(const string,const int);
   CObject          *GetContainer(void);
   void              SetContainer(CObject *container);
   bool              Active(void) const;
   void              Active(bool active);
   virtual void      Check(void) const;
   virtual bool      IsNewCandle(const string,const int) const;
   virtual CCandle *Get(const string,const int) const;
   virtual bool      TradeProcessed(const string,const int) const;
   virtual void      TradeProcessed(const string,const int,const bool) const;
   //--- 恢复
   virtual bool      Save(const int);
   virtual bool      Load(const int);
  };

CCandle 的一个实例是基于金融工具名称和时间帧创建的。使用 CCandleManager 可令智能交易系统更容易地跟踪给定金融工具的多个图表, 例如, 同一智能交易系统可同时在 EURUSD M15 和 EURUSD H1 上检查新蜡烛的发生。具有相同品名和时间帧的 CCandle 实例是多余的, 应予以避免。查找 CCandle 的某个实例时, 只需简单地调用 CCandleManager 里的适当方法, 并指定品名和时间帧。反过来, CCandleManager 会查找适当的 CCandle 实例并调用预期的方法。

除了检查新蜡烛的出现之外, 蜡烛和蜡烛管理器还有其它目的: 检查智能交易系统是否已按给定品名和时间帧入场交易。可以检查最近的交易品种, 但不限于时间帧。此标志的切换应由 CExpertAdvisor 自身的实例在需要时设置或重置。对于这两个类, 可以使用 TradeProcessed 方法设置切换。

对于蜡烛管理器, TradeProcessed 方法 (getter 和 setter) 只处理查找 CCandle 实例的请求, 并应用适当的值:

bool CCandleManagerBase::TradeProcessed(const string symbol,const int timeframe) const
  {
   CCandle *candle=Get(symbol,timeframe);
   if(CheckPointer(candle))
      return candle.TradeProcessed();
   return false;
  }

对于 CCandle, 该过程涉及为其类成员之一 m_trade_processed 分配新值。以下方法处理所述类成员的数值设置:

bool CCandleBase::TradeProcessed(void) const
  {
   return m_trade_processed;
  }

CCandleBase::TradeProcessed(bool value)
  {
   m_trade_processed=value;
  }

OnTick 处理函数

CExpertAdvisor 的 OnTick 方法是该类中最常用的函数。大部分发生的动作出自这个方法。该方法的核心操作如下图所示:


CExpertAdvisorBase OnTick


该过程首先切换智能交易系统的逐笔分价标记。这是为了确保不会发生双重处理逐笔分价。CExpertAdvisor 的 OnTick 方法理想情况下仅在 OnTick 事件函数中调用, 但也可以通过其它方式调用, 例如 OnChartEvent。在缺乏标志的情况下, 如果类的 OnTick 方法正在处理早前的分价时被再次调用, 则可能会重复处理分价, 若此分价会触发交易, 这通常会导致重复交易。

刷新数据也是必要的, 因为这确保智能交易系统能够访问最近的行情数据, 且不会重复处理更早的分价。如果智能交易系统不能刷新数据, 它将重置分价处理标志, 终止方法, 并等待一笔新的分价。

接下来的步骤是检测新的柱线并检查交易信号。默认情况下检查每笔分价。但是, 可以扩展此方法, 使其仅在检测到新柱线时检查信号 (以加快处理时间, 特别是在回测和优化期间)。

该类还提供了一个成员 m_position_reverse, 用于逆转与当前信号相反的仓位。此处执行的逆转只是为了中和抵消当前持仓。在 MetaTrader 4 和 MetaTrader 5 对冲模式下, 与当前信号相反的交易将予以平仓 (与当前信号一起进行的交易不会离场)。在 MetaTrader 5 净持模式下, 任何时候都只能有一个方向的仓位, 所以智能交易系统将会用等同于当前持仓的手数反向开仓。

交易信号主要是使用 m_signals 进行检查, 但其它因素 (如仅在新柱线上进行交易) 和时间过滤器也可以阻止智能交易系统执行新的交易。只有满足所有条件, EA 才能够入场进行新的交易。

在分价处理结束时, 智能交易系统将把分价标记设置为 false, 然后允许处理另一笔分价。

智能交易系统容器

类似于前文中讨论的其它类对象, CExpertAdvisor 类也有其定制容器, 即 CExpertAdvisors。以下代码展示了其基类 CExpertAdvisorsBase 的声明:

class CExpertAdvisorsBase : public CArrayObj
  {
protected:
   bool              m_active;
   int               m_uninit_reason;
   CObject          *m_container;
public:
                     CExpertAdvisorsBase(void);
                    ~CExpertAdvisorsBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_EXPERTS;}
   virtual int       UninitializeReason(void) const {return m_uninit_reason;}
   //--- 取值和赋值
   void              SetContainer(CObject *container);
   CObject          *GetContainer(void);
   bool              Active(void) const;
   void              Active(const bool);
   int               OrdersTotal(void) const;
   int               OrdersHistoryTotal(void) const;
   int               TradesTotal(void) const;
   //--- 初始化
   virtual bool      Validate(void) const;
   virtual bool      InitComponents(void) const;
   //--- 事件
   virtual void      OnTick(void);
   virtual void      OnChartEvent(const int,const long&,const double&,const string&);
   virtual void      OnTimer(void);
   virtual void      OnTrade(void);
   virtual void      OnDeinit(const int,const int);
   //--- 恢复
   virtual bool      CreateElement(const int);
   virtual bool      Save(const int);
   virtual bool      Load(const int);
  };

此容器主要反映 CExpertAdvisor 类中的公有方法。OnTick 处理程序就是一个例子。该方法只是在 CExpertAdvisor 的每个实例上进行迭代, 并调用其 OnTick 方法:

void CExpertAdvisorsBase::OnTick(void)
  {
   if(!Active()) return;
   for(int i=0;i<Total();i++)
     {
      CExpertAdvisor *e=At(i);
      e.OnTick();
     }
  }

有了这个容器, 可以存储 CExpertAdvisor 的多个实例。这可能是在单个图表实例上运行多个智能交易系统的唯一方法。只需初始化 CExpertAdvisor 的多个实例, 将它们的指针存储在一个 CExpertAdvisors 容器下, 然后使用容器的 OnTick 方法来触发每个 CExpertAdvisor 实例的 OnTick 方法。对于 MQL5 标准库的每个 CExpert 类实例, 使用 CArrayObj 类或其继承类也可以完成同样的事情。

数据持久化

一些在 CExpertAdvisor 实例中使用的数据仅驻留在计算机内存中。正常情况下, 数据通常存储在平台中, 智能交易系统通过函数调用从平台本身获取所需的数据。但是, 对于智能交易系统运行时动态创建的数据, 情况通常不是这样。当智能交易系统触发 OnDeinit 事件时, 智能交易系统会销毁所有对象, 从而丢失数据。

OnDeinit 可以通过多种方式触发, 如关闭整个交易平台 (MetaTrader 4 或 MetaTrader 5), 从图表中卸载智能交易系统, 甚或重新编译智能交易系统源代码。可以使用 UninitializeReason 函数找到触发逆初始化可能事件的完整列表。当智能交易系统失去对这些数据的访问权时, 它也许就像首次加载到图表一样。

CExpertAdvisor 类中大部分易失性数据都可以在成员之一中找到, 这是 COrderManager 的一个实例。这是智能交易系统执行其常规程序时创建 COrder和COrderStop (及其后代) 实例的地方。由于这些实例是在 OnTick 中动态创建的, 因此在智能交易系统重新初始化时不会重新创建这些实例。因此, 智能交易系统应该实现一种方法来保存和恢复这些易失性数据。实现这个需求的一种方法是使用 CFileBin 类的后代 CExpertFile。以下代码片段展示了其基类 CExpertFileBase 的声明:

class CExpertFileBase : public CFileBin
  {
public:
                     CExpertFileBase(void);
                    ~CExpertFileBase(void);
   void              Handle(const int handle) { m_handle=handle; };
   uint              WriteBool(const bool value);
   bool              ReadBool(bool &value);
  };

此处, 我们将 CFileBin 扩展为显式声明方法, 用来写入并读取 Boolean 类型的数据。

在类文件的末尾, 我们声明了一个 CExpertFile 类的实例。如果易失性数据能够妥善加以保存和加载, 在整个智能交易系统中均可使用此实例。再有, 可以简单地依赖从 CObject 继承的 SaveLoad 方法, 并以常规方式处理数据的保存和加载。不过, 这可能是一个非常枯燥的过程。单独使用 CFile (或其继承者) 可以节省大量的精力和代码。

//CExpertFileBase 类定义
//+------------------------------------------------------------------+
#ifdef __MQL5__
#include "..\..\MQL5\File\ExpertFile.mqh"
#else
#include "..\..\MQL4\File\ExpertFile.mqh"
#endif
//+------------------------------------------------------------------+
CExpertFile file;
//+------------------------------------------------------------------+

订单管理器通过其 Save 方法保存易失性数据:

bool COrderManagerBase::Save(const int handle)
  {
   if(handle==INVALID_HANDLE)
      return false;
   file.WriteDouble(m_lotsize);
   file.WriteString(m_comment);
   file.WriteInteger(m_expiration);
   file.WriteInteger(m_history_count);
   file.WriteInteger(m_max_orders_history);
   file.WriteBool(m_trade_allowed);
   file.WriteBool(m_long_allowed);
   file.WriteBool(m_short_allowed);
   file.WriteInteger(m_max_orders);
   file.WriteInteger(m_max_trades);
   file.WriteObject(GetPointer(m_orders));
   file.WriteObject(GetPointer(m_orders_history));
   return true;
  }

这些数据中的大部分都是原始类型, 除了最后两个是订单和历史订单容器。对于这些数据, 使用 CFileBinWriteObject 方法, 它简单地调用写入对象的 Save 方法。以下代码展示了 COrderBase 的 Save 方法:

bool COrderBase::Save(const int handle)
  {
   if(handle==INVALID_HANDLE)
      return false;
   file.WriteBool(m_initialized);
   file.WriteBool(m_closed);
   file.WriteBool(m_suspend);
   file.WriteInteger(m_magic);
   file.WriteDouble(m_price);
   file.WriteLong(m_ticket);
   file.WriteEnum(m_type);
   file.WriteDouble(m_volume);
   file.WriteDouble(m_volume_initial);
   file.WriteString(m_symbol);
   file.WriteObject(GetPointer(m_order_stops));
   return true;
  }

正如我们在这里看到的, 在保存对象时只是重复这个过程。对于原始数据类型, 数据将像往常一样简单地保存到文件中。对于复杂的数据类型, 通过 CFileBin 的 WriteObject 方法调用对象的 Save 方法。

在存在多个 CExpertAdvisor 实例的情况下, 容器 CExpertAdvisors 也应该具有保存数据的能力:

bool CExpertAdvisorsBase::Save(const int handle)
  {
   if(handle!=INVALID_HANDLE)
     {
      for(int i=0;i<Total();i++)
        {
         CExpertAdvisor *e=At(i);
         if(!e.Save(handle))
            return false;
        }
     }
   return true;
  }

该方法调用每个 CExpertAdvisor 实例的 Save 方法。单个文件句柄意味着每个智能交易系统只有一个保存文件。每个 CExpertAdvisor 实例都可能有自己的保存文件, 但这将导致方法更复杂。

最复杂的部分是加载数据。在保存数据时, 一些类成员的数值只是简单地被写入文件。另一方面, 在加载数据时, 对象实例需要重新创建, 以便恢复保存之前的理想状态。以下代码展示订单管理器的 Load 方法:

bool COrderManagerBase::Load(const int handle)
  {
   if(handle==INVALID_HANDLE)
      return false;
   if(!file.ReadDouble(m_lotsize))
      return false;
   if(!file.ReadString(m_comment))
      return false;
   if(!file.ReadInteger(m_expiration))
      return false;
   if(!file.ReadInteger(m_history_count))
      return false;
   if(!file.ReadInteger(m_max_orders_history))
      return false;
   if(!file.ReadBool(m_trade_allowed))
      return false;
   if(!file.ReadBool(m_long_allowed))
      return false;
   if(!file.ReadBool(m_short_allowed))
      return false;
   if(!file.ReadInteger(m_max_orders))
      return false;
   if(!file.ReadInteger(m_max_trades))
      return false;
   if(!file.ReadObject(GetPointer(m_orders)))
      return false;
   if(!file.ReadObject(GetPointer(m_orders_history)))
      return false;
   for(int i=0;i<m_orders.Total();i++)
     {
      COrder *order=m_orders.At(i);
      if(!CheckPointer(order))
         continue;
      COrderStops *orderstops=order.OrderStops();
      if(!CheckPointer(orderstops))
         continue;
      for(int j=0;j<orderstops.Total();j++)
        {
         COrderStop *orderstop=orderstops.At(j);
         if(!CheckPointer(orderstop))
            continue;
         for(int k=0;k<m_stops.Total();k++)
           {
            CStop *stop=m_stops.At(k);
            if(!CheckPointer(stop))
               continue;
            orderstop.Order(order);
            if(StringCompare(orderstop.StopName(),stop.Name())==0)
              {
               orderstop.Stop(stop);
               orderstop.Recreate();
              }
           }
        }
     }
   return true;
  }

以上的 COrderManager 代码与 CExpertAdvisor 的 Load 方法相比要复杂得多。原因是, 与订单管理器不同, CExpertAdvisor 的实例是在 OnInit 中创建的, 所以容器只需调用 CExpertAdvisor 中每个实例的 Load 方法, 而不是使用 CFileBin 的 ReadObject 方法。

若在 OnInit 期间不去创建类实例, 则不得不在重新加载智能交易系统时创建。这是通过扩展 CArrayObj 的 方法 CreateElement 实现的。一个对象不能简单地创建它自己, 所以它必须由其父对象或容器创建, 甚至从主程序或头文件本身创建。在 COrdersBase 上扩展的 CreateElement 方法中可以看到这样的示例。在这个类下, 容器是 COrders (COrdersBase 的后代), 要创建的对象的类型是 COrder:

bool COrdersBase::CreateElement(const int index)
  {
   COrder*order=new COrder();
   if(!CheckPointer(order))
      return(false);
   order.SetContainer(GetPointer(this));
   if(!Reserve(1))
      return(false);
   m_data[index]=order;
   m_sort_mode=-1;
   return CheckPointer(m_data[index]);
  }

此处, 除了创建元素, 我们还设置其父对象或容器, 以便区分它是否属于活动交易列表 (COrderManagerBase 的类成员 m_orders) 或历史记录 (COrderManagerBase 的 m_orders_history)。

示例

本文中的示例 #1 - #4 是上一篇文章中四个示例的改编版本 (请参阅《跨平台智能交易: 自定义停止, 尾随和盈亏平衡》)。我们来看看最复杂的例子, expert_custom_trail_ha_ma.mqh, 它是 custom_trail_ha_ma.mqh 的改编版本。

在 OnInit 函数之前, 我们声明了以下全局对象实例:

COrderManager *order_manager;
CSymbolManager *symbol_manager;
CSymbolInfo *symbol_info;
CSignals *signals;
CMoneys *money_manager;
CTimes *time_filters;

我们用 CExpert 的一个实例替换它。上面的一些可以在 CExpetAdvisor 本身 (例如 COrderManager) 中找到, 而其余的必须在 OnInit (即容器) 中实例化:

CExpertAdvisors experts;

在方法一开始, 我们创建一个 CExpertAdvisor 实例。我们还调用其 Init 方法输入最基本的设置:

int OnInit()
  {
//---
   CExpertAdvisor *expert=new CExpertAdvisor();
   expert.Init(Symbol(),Period(),12345,true,true,true);
//--- 其它代码
//---
   return(INIT_SUCCEEDED);
  }

CSymbolInfo / CSymbolManager 不再需要实例化, 因为 CExpertAdvisor 类的实例能够自行创建这些类的实例。

用户定义的函数也将被删除, 因为我们的新智能交易系统将不再需要这些。

我们在代码的全局声明中删除了容器, 所以需要在 OnInit中 声明它们。譬如这个时间过滤器容器 (CTimeFilters) 的例子, 在下面所示的 OnInit 函数代码中找到:

CTimes *time_filters=new CTimes();

指向容器的指针以前 "添加" 到订单管理器中, 现在则替换为加入 CExpertAdvisor 的实例。所有未添加到订单管理器的其它容器也必须添加到 CExpertAdvisor 实例中。COrderManager 的实例将会存储这些指针 。CExpertAdvisor 实例只创建包装器方法。

此后, 我们将 CExpertAdvisor 实例添加到 CExpertAdvisors 的一个实例中。然后我们调用 CExpertAdvisors 实例的 InitComponents 方法。这将确保 CExpertAdvisor 的所有实例及其组件的初始化。

int OnInit()
  {
//---
//--- 其它代码
   experts.Add(GetPointer(expert));
   if(!experts.InitComponents())
      return(INIT_FAILED);
//--- 其它代码
//---
   return(INIT_SUCCEEDED);
  }

最后, 如果智能交易系统在操作中被中断, 我们插入加载所需的代码:

int OnInit()
  {
//---
//--- 其它代码   
 file.Open(savefile,FILE_READ);
   if(!experts.Load(file.Handle()))
      return(INIT_FAILED);
   file.Close();
//---
   return(INIT_SUCCEEDED);
  }

如果智能交易系统不能从文件加载, 它将返回 INIT_FAILED。不过, 若是没有提供保存文件 (因此会生成 INVALID_HANDLE), 则智能交易系统不会判定初始化失败, 因为 CExpertAdvisors 和 CExpertAdvisor 的 Load 方法在接收到无效句柄时都返回 true。这种方法存在一些风险, 但保存的文件不太可能被其它程序打开。只需确保在图表上运行的每个智能交易系统实例都有一个独占的保存文件 (就像魔幻数字一样)。

第五个例子在前面的文章中找不到。它其实将本文中的所有四个智能交易系统组合成一个智能交易系统。它只是稍微修改了每个智能交易系统的 OnInit 函数, 并将其声明为自定义函数。它的返回值是 CExpertAdvisor* 类型。如果智能交易系统创建失败, 它将返回 NULL 替代 INIT_SUCCEEDED。以下代码展示在组合后的智能交易系统头文件里更新的 OnInit 函数:

int OnInit()
  {
//---
   CExpertAdvisor *expert1=expert_breakeven_ha_ma();
   CExpertAdvisor *expert2=expert_trail_ha_ma();
   CExpertAdvisor *expert3=expert_custom_stop_ha_ma();
   CExpertAdvisor *expert4=expert_custom_trail_ha_ma();
      
   if (!CheckPointer(expert1))
      return INIT_FAILED;
   if (!CheckPointer(expert2))
      return INIT_FAILED;
   if (!CheckPointer(expert3))
      return INIT_FAILED;
   if (!CheckPointer(expert4))
      return INIT_FAILED;
   
   experts.Add(GetPointer(expert1));
   experts.Add(GetPointer(expert2));
   experts.Add(GetPointer(expert3));
   experts.Add(GetPointer(expert4));   
   
   if(!experts.InitComponents())
      return(INIT_FAILED);
   file.Open(savefile,FILE_READ);
   if(!experts.Load(file.Handle()))
      return(INIT_FAILED);
   file.Close();
//---
   return(INIT_SUCCEEDED);
  }

智能交易系统首先实例化 CExpertAdvisor 的每个实例。然后它将继续检查每个指向 CExpertAdvisor 的指针。如果指针不是动态的, 那么函数返回 INIT_FAILED, 初始化失败。如果每个实例都通过指针检查, 则这些指针将存储在 CExpertAdvisors 的一个实例中。CExpertAdvisors 实例 (容器, 而非智能交易系统实例) 随后将初始化其组件, 并在必要时加载以前的数据。

智能交易系统使用自定义函数来创建一个 CExpertAdvisor 的实例。以下代码展示了用于创建第四个智能交易系统实例的函数:

CExpertAdvisor *expert_custom_trail_ha_ma()
{
   CExpertAdvisor *expert=new CExpertAdvisor();
   expert.Init(Symbol(),Period(),magic4,true,true,true);
   CMoneys *money_manager=new CMoneys();
   CMoney *money_fixed=new CMoneyFixedLot(0.05);
   CMoney *money_ff=new CMoneyFixedFractional(5);
   CMoney *money_ratio=new CMoneyFixedRatio(0,0.1,1000);
   CMoney *money_riskperpoint=new CMoneyFixedRiskPerPoint(0.1);
   CMoney *money_risk=new CMoneyFixedRisk(100);

   money_manager.Add(money_fixed);
   money_manager.Add(money_ff);
   money_manager.Add(money_ratio);
   money_manager.Add(money_riskperpoint);
   money_manager.Add(money_risk);
   expert.AddMoneys(GetPointer(money_manager));

   CTimes *time_filters=new CTimes();
   if(time_range_enabled && time_range_end>0 && time_range_end>time_range_start)
     {
      CTimeRange *timerange=new CTimeRange(time_range_start,time_range_end);
      time_filters.Add(GetPointer(timerange));
     }
   if(time_days_enabled)
     {
      CTimeDays *timedays=new CTimeDays(sunday_enabled,monday_enabled,tuesday_enabled,wednesday_enabled,thursday_enabled,friday_enabled,saturday_enabled);
      time_filters.Add(GetPointer(timedays));
     }
   if(timer_enabled)
     {
      CTimer *timer=new CTimer(timer_minutes*60);
      timer.TimeStart(TimeCurrent());
      time_filters.Add(GetPointer(timer));
     }

   switch(time_intraday_set)
     {
      case INTRADAY_SET_1:
        {
         CTimeFilter *timefilter=new CTimeFilter(time_intraday_gmt,intraday1_hour_start,intraday1_hour_end,intraday1_minute_start,intraday1_minute_end);
         time_filters.Add(timefilter);
         break;
        }
      case INTRADAY_SET_2:
        {
         CTimeFilter *timefilter=new CTimeFilter(0,0,0);
         timefilter.Reverse(true);
         CTimeFilter *sub1 = new CTimeFilter(time_intraday_gmt,intraday2_hour1_start,intraday2_hour1_end,intraday2_minute1_start,intraday2_minute1_end);
         CTimeFilter *sub2 = new CTimeFilter(time_intraday_gmt,intraday2_hour2_start,intraday2_hour2_end,intraday2_minute2_start,intraday2_minute2_end);
         timefilter.AddFilter(sub1);
         timefilter.AddFilter(sub2);
         time_filters.Add(timefilter);
         break;
        }
      default: break;
     }
   expert.AddTimes(GetPointer(time_filters));

   CStops *stops=new CStops();
   CCustomStop *main=new CCustomStop("main");
   main.StopType(stop_type_main);
   main.VolumeType(VOLUME_TYPE_PERCENT_TOTAL);
   main.Main(true);
//main.StopLoss(stop_loss);
//main.TakeProfit(take_profit);
   stops.Add(GetPointer(main));

   CTrails *trails=new CTrails();
   CCustomTrail *trail=new CCustomTrail();
   trails.Add(trail);
   main.Add(trails);

   expert.AddStops(GetPointer(stops));

   MqlParam params[1];
   params[0].type=TYPE_STRING;
#ifdef __MQL5__
   params[0].string_value="Examples\\Heiken_Ashi";
#else
   params[0].string_value="Heiken Ashi";
#endif
   SignalHA *signal_ha=new SignalHA(Symbol(),0,1,params,signal_bar);
   SignalMA *signal_ma=new SignalMA(Symbol(),(ENUM_TIMEFRAMES) Period(),maperiod,0,mamethod,maapplied,signal_bar);
   CSignals *signals=new CSignals();
   signals.Add(GetPointer(signal_ha));
   signals.Add(GetPointer(signal_ma));
   expert.AddSignal(GetPointer(signals));
//---
   return expert;
}

正如我们所看到的, 代码看起来非常类似于原始智能交易系统头文件 (expert_custom_trail_ha_ma.mqh) 中的 OnInit 函数。其它自定义函数也以相同的方式组织。

后记

在结束本文之前, 任何希望使用这个函数库的读者都应该意识到这些有助于函数库发展的因素:

在撰写这篇文章的时候, 本文中的函数库有超过 10,000 行的代码 (包括注释)。尽管如此, 它仍然是一个进度不断前行的项目。为了充分利用 MQL4 和 MQL5 的功能, 需要做更多的工作。

作者在 MetaTrader 5 引入对冲模式之前就开始了这个项目的工作。这极大地影响了函数库的进一步发展。而结果就是, 函数库更倾向于采用 MetaTrader 4 中使用的惯例, 而非 MetaTrader 5。更甚至, 作者还在过去几年里经历了若干发布版本中的一些兼容性问题, 这导致了代码的一些或小或大的调整 (以及一些文章的延误发表)。在撰写这篇文章的时候, 依作者的经验, 两个平台的版本更新不再那么频繁, 且随着时间的推移更加稳定。预计这一趋势将进一步改善。尽管如此,未来的版本更新可能会导致的不兼容问题仍然需要解决。

函数库依靠保存在内存中的数据来跟踪自己的交易。这导致使用这个函数库创建的智能交易系统在很大程度上依赖保存和加载数据, 以便应对智能交易系统在其执行期间可能遇到的中断。未来针对函数库的工作, 以及任何其它意在跨平台兼容性的函数库, 应该着眼于无状态或接近无状态的实现, 类似于 MQL5 标准库的实现。

作为最后一点, 本文中提议的函数库不应视为永久解决方案。相反, 它应该用作 MetaTrader 4 到 MetaTrader 5 平滑过渡的机遇。MQL4 和 MQL5 之间的不兼容性, 给那些打算迁移到新平台的交易者带来了巨大的障碍。结果造成, 他们的智能交易系统 MQL4 源代码需要重构, 以便与 MQL5 编译器兼容。本文中提供的库, 可作为将智能交易系统部署到新平台的手段, 原来的智能交易系统的主要源代码只需或根本无需进行调整。这有助于交易者决定是继续使用 MetaTrader 4 还是切换到 MetaTrader 5。如果决定切换, 只需要很少的调整, 交易者即可用他的智能交易系统按照往常的方式操作。另一方面, 如果他决定继续使用旧的平台, 一旦 MetaTrader 4 变为遗留软件, 他可以选择快速切换到新平台。

结束语

本文介绍了 CExpertAdvisor 和 CExpertAdvisors 类对象, 这些对象用于集成本系列文章中讨论过的跨平台交易系统的所有组件。本文讨论如何将这两个类实例化, 并与跨平台智能交易系统的其它组件进行衔接。它还介绍了一些智能交易系统常遇问题的解决方案, 如新柱线检测以及易失性数据的保存和加载。

文章中使用的程序

 # 名称
类型
描述
1.
expert_breakeven_ha_ma.mqh
头文件
第一个示例中使用的主要头文件
2.
expert_breakeven_ha_ma.mq4 智能交易系统
第一个示例中使用的主要源文件 MQL4 版
3.
expert_breakeven_ha_ma.mq5 智能交易系统 第一个示例中使用的主要源文件 MQL5 版
4.
 expert_trail_ha_ma.mqh 头文件 第二个示例中使用的主要头文件
5.
 expert_trail_ha_ma.mq4 智能交易系统 第二个示例中使用的主要源文件 MQL4 版
6.
 expert_trail_ha_ma.mq5 智能交易系统 第二个示例中使用的主要源文件 MQL5 版
7.
 expert_custom_stop_ha_ma.mqh 头文件 第三个示例中使用的主要头文件
8.
 expert_custom_stop_ha_ma.mq4 智能交易系统 第三个示例中使用的主要源文件 MQL4 版
9.
 expert_custom_stop_ha_ma.mq5 智能交易系统 第三个示例中使用的主要源文件 MQL5 版
10.
 expert_custom_trail_ha_ma.mqh 头文件 第四个示例中使用的主要头文件
11.
 expert_custom_trail_ha_ma.mq4 智能交易系统 第四个示例中使用的主要源文件 MQL4 版
12.
 expert_custom_trail_ha_ma.mq5 智能交易系统 第四个示例中使用的主要源文件 MQL5 版
13.
 combined.mqh 头文件 第五个示例中使用的主要头文件
14.
 combined.mq4 智能交易系统 第五个示例中使用的主要源文件 MQL4 版
15.
 combined.mq5 智能交易系统 第五个示例中使用的主要源文件 MQL5 版

在文章中提到的类文件

#
名称
类型
描述
1. MQLx\Base\Expert\ExperAdvisorsBase 头文件
CExpertAdvisors (CExpertAdvisor 容器, 基类)
2.
MQLx\MQL4\Expert\ExperAdvisors 头文件 CExpertAdvisors (MQL4 版本)
3.
MQLx\MQL5\Expert\ExperAdvisors 头文件
CExpertAdvisors (MQL5 版本)
4.
MQLx\Base\Expert\ExperAdvisorBase 头文件
CExpertAdvisor (基类)
5.
MQLx\MQL4\Expert\ExperAdvisor 头文件
CExpertAdvisor (MQL4 版本)
6.
MQLx\MQL5\Expert\ExperAdvisor 头文件
CExpertAdvisor (MQL5 版本
7.
MQLx\Base\Candle\CandleManagerBase 头文件 CCandleManager (CCandle 容器, 基类)
8.
MQLx\MQL4\Candle\CandleManager 头文件 CCandleManager (MQL4 版本)
9.
MQLx\MQL5\Candle\CandleManager 头文件 CCandleManager (MQL5 版本)
10.
MQLx\Base\Candle\CandleBase 头文件 CCandle (基类)
11.
MQLx\MQL4\Candle\Candle 头文件 CCandle (MQL4 版本)
12.
MQLx\MQL5\Candle\Candle 头文件
CCandle (MQL5 版本)
13.
MQLx\Base\File\ExpertFileBase 头文件 CExpertFile(基类)
14.
MQLx\MQL4\File\ExpertFile 头文件 CExpertFile(MQL4 版本)
15.
MQLx\MQL5\File\ExpertFile 头文件 CExpertFile(MQL5 版本)


由MetaQuotes Software Corp.从英文翻译成
原始文章: https://www.mql5.com/en/articles/3622

附加的文件 |
MQL5.zip (143.3 KB)
使用 CGraphic 开发库实现一个剥头皮市场深度 使用 CGraphic 开发库实现一个剥头皮市场深度

在本文中,我们将会创建一个剥头皮市场深度工具的基本功能。另外,我们将基于 CGraphic 开发库开发一个订单分时图表,并且把它与订单簿整合。使用所描述的市场深度,就可以创造一个用于短线交易的强大辅助工具。

自适应行情跟踪方法的实际评估 自适应行情跟踪方法的实际评估

本文所述交易系统的不同寻常之处主要是使用数学工具分析股票报价。系统应用了数字滤波和离散时间序列的频谱估值。策略的理论层面已描述过, 并曾创建了一款测试智能交易系统。

利用余额图进行策略优化并将结果与 "余额 + 最大锋锐比率" 标准进行比较 利用余额图进行策略优化并将结果与 "余额 + 最大锋锐比率" 标准进行比较

在本文中, 我们研究另一种基于分析余额图来优化自定义交易策略的准则。线性回归使用 ALGLIB 函数库中的函数进行计算。

解读经典与隐性背离的新途径 解读经典与隐性背离的新途径

本文研究经典背离构造方法, 并提供了另外一种解读背离的方法。基于这种新的解释方法开发了交易策略。本文中也描述了这一策略。