内容目录
- 概论
- 文件夹结构
- CSymbolInfo
- CSymbolManager
- 添加品种
- 刷新品种
- CAccountInfo
- CExpertTrade
- CTradeManager
概论
在 MQL5 标准库里存在的一些控件被证明在 MQL4 版本的跨平台智能交易程序里十分有用。不过, 它们与 MQL4 编译器的不兼容性, 令它们难以在 MQL4 版本的跨平台智能交易程序里使用。在此情况下至少有两个行动方针:
- 从头开始重写, 仅保留两个版本均支持的控件。
- 拷贝并修改这些类, 以便头文件可以按照 MQL4 编译
在本文中, 我们将探讨第二个选项。使用这种方法, 可令 MQL4 一侧的实现相当松散。然而, 它的主要缺点是, 我们要重写大量代码。当 MQL5 的类更新到最后版本时, 这样做也会比从头开始重写一切要更容易施行。
文件夹结构
不同于大多数将会用到的类, 来自 MQL5 标准库的重用类将需要拷贝到 MQL4 数据文件夹的 include 目录下。因此, 需要为智能交易程序组织一下, 不管什么版本都能使用它们。达成的方法可能各有不同, 但最简单的方法之一是链接一个特定的头文件, 并用即将使用的相应头文件处理该头文件。这有些类似于定制类对象里的正常完成:
对于 MQL5 版本, 主要的头文件应链接到要用到的原始文件位置。
对于 MQL4 版本, 主要的头文件应链接到要用到的拷贝 (修改的) MQL5 头文件。
为此, 一种方法是在 base 目录下创建一个 "Lib" 文件夹。这里将是基本头文件的存放位置。在 MQL4 文件夹下创建类似的跨平台目录 (跨平台可以是 "MQLx-Reuse")。这里是那些拷贝自 MQL5 的类 (修改的) 存放位置。
|-Include
|-MQLx-Reuse
|-Base
|-Lib
<Main Header Files>
|-MQL4
|-Lib
<Copy of MQL5 Header Files>
|-MQL5
MQL5 文件夹不会有 "Lib" 文件夹, 因为主要的头文件应直接链接到头文件在 MQL5 数据文件夹下的原始位置。例如, 假如我们需要在跨平台智能交易程序里重用 CSymbolInfo。这个特殊的类是 MQL5 标准库的一部分, 位于交易类之下。它可使用以下 #include 指令访问:
#include <Trade\SymbolInfo.mqh>
为了在我们当前设置里使用它, 我们需要在 Base 文件夹下的 lib 文件夹内创建主要的头文件, 代码如下:
(/Base/Lib/SymbolInfo.mqh)
#ifdef __MQL5__ #include <Trade\SymbolInfo.mqh> #else #include "..\..\MQL4\Lib\SymbolInfo.mqh" #endif
MQL4 版本调用一条 include 指令指向 " Lib" 文件夹下的头文件。但这次, 它应在 MQL4 文件夹下我们定制目录 (“MQLx-Reuse”) 下的 "Lib" 文件夹。
如早前所述, MQL5 版本不需要 "Lib" 文件夹, 因为主要的头文件位于 Base 文件夹, 它应该已经指向 <Trade\SymbolInfo.mqh>, 即在 MQL5 Include 文件夹内正常发现的类。
这仅是一个推荐的方法, 并非强制性的。然而, 如果有人使用专门文件夹保存来自 MQL5 的头文件以便重用和浏览, 这将很有用。
CSymbolInfo
使用 MQL4 编译器, 编译原始 CSymbolInfo 类的头文件将会产生编译错误。这些错误大多是由于 MQL4 的不兼容导致的, 特别是在使用函数 SymbolInfoDouble 和 SymbolInfoInteger 时。这些函数的调用, 大多可以在类中的 Refresh() 方法里看到。未经修改的 CSymbolInfo 类代码如下所示:
bool CSymbolInfo::Refresh(void) { long tmp=0; //--- if(!SymbolInfoDouble(m_name,SYMBOL_POINT,m_point)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE,m_tick_value)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT,m_tick_value_profit)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_LOSS,m_tick_value_loss)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_SIZE,m_tick_size)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_CONTRACT_SIZE,m_contract_size)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MIN,m_lots_min)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MAX,m_lots_max)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_STEP,m_lots_step)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_LIMIT,m_lots_limit)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_LONG,m_swap_long)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_SHORT,m_swap_short)) return(false); if(!SymbolInfoInteger(m_name,SYMBOL_DIGITS,tmp)) return(false); m_digits=(int)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_ORDER_MODE,tmp)) return(false); m_order_mode=(int)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_EXEMODE,tmp)) return(false); m_trade_execution=(ENUM_SYMBOL_TRADE_EXECUTION)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_CALC_MODE,tmp)) return(false); m_trade_calcmode=(ENUM_SYMBOL_CALC_MODE)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_MODE,tmp)) return(false); m_trade_mode=(ENUM_SYMBOL_TRADE_MODE)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_MODE,tmp)) return(false); m_swap_mode=(ENUM_SYMBOL_SWAP_MODE)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_ROLLOVER3DAYS,tmp)) return(false); m_swap3=(ENUM_DAY_OF_WEEK)tmp; if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_INITIAL,m_margin_initial)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_MAINTENANCE,m_margin_maintenance)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LONG,m_margin_long)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_SHORT,m_margin_short)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LIMIT,m_margin_limit)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOP,m_margin_stop)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOPLIMIT,m_margin_stoplimit)) return(false); if(!SymbolInfoInteger(m_name,SYMBOL_EXPIRATION_MODE,tmp)) return(false); m_trade_time_flags=(int)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_FILLING_MODE,tmp)) return(false); m_trade_fill_flags=(int)tmp; //--- 成功 return(true); }
与在 首篇文章 里讨论的类似, 我们使用一个通用的头文件, 在理想中, 固化 MQL4 和 MQL5 版本之间的相似代码。实际上可以将 CSymbolInfo 类重写为三个单独的文件, 以便可以在单一文件里共享相似性, 并将差异部分放在其它两个类文件的代码里。不过, 在本文中, 我们用更简单方式处理 (更快速度): 拷贝 MQL5 的 CSymbolInfo 类文件, 然后把与 MQL4 不兼容的行加上注释。对于两个版本, 所得到的文件结构如下所见:
以下代码显示了相同的函数, 但这些注释可能导致在 MQL4 里调用 Refresh 方法时返回 false:
bool CSymbolInfo::Refresh(void) { long tmp=0; //--- if(!SymbolInfoDouble(m_name,SYMBOL_POINT,m_point)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE,m_tick_value)) return(false); //if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_PROFIT,m_tick_value_profit)) //return(false); //if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_VALUE_LOSS,m_tick_value_loss)) //return(false); if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_TICK_SIZE,m_tick_size)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_TRADE_CONTRACT_SIZE,m_contract_size)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MIN,m_lots_min)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_MAX,m_lots_max)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_STEP,m_lots_step)) return(false); //if(!SymbolInfoDouble(m_name,SYMBOL_VOLUME_LIMIT,m_lots_limit)) //return(false); if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_LONG,m_swap_long)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_SWAP_SHORT,m_swap_short)) return(false); if(!SymbolInfoInteger(m_name,SYMBOL_DIGITS,tmp)) return(false); m_digits=(int)tmp; //if(!SymbolInfoInteger(m_name,SYMBOL_ORDER_MODE,tmp)) //return(false); //m_order_mode=(int)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_EXEMODE,tmp)) return(false); m_trade_execution=(ENUM_SYMBOL_TRADE_EXECUTION)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_CALC_MODE,tmp)) return(false); m_trade_calcmode=(ENUM_SYMBOL_CALC_MODE)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_TRADE_MODE,tmp)) return(false); m_trade_mode=(ENUM_SYMBOL_TRADE_MODE)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_MODE,tmp)) return(false); m_swap_mode=(ENUM_SYMBOL_SWAP_MODE)tmp; if(!SymbolInfoInteger(m_name,SYMBOL_SWAP_ROLLOVER3DAYS,tmp)) return(false); m_swap3=(ENUM_DAY_OF_WEEK)tmp; if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_INITIAL,m_margin_initial)) return(false); if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_MAINTENANCE,m_margin_maintenance)) return(false); //if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LONG,m_margin_long)) //return(false); //if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_SHORT,m_margin_short)) //return(false); //if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_LIMIT,m_margin_limit)) //return(false); //if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOP,m_margin_stop)) //return(false); //if(!SymbolInfoDouble(m_name,SYMBOL_MARGIN_STOPLIMIT,m_margin_stoplimit)) //return(false); //if(!SymbolInfoInteger(m_name,SYMBOL_EXPIRATION_MODE,tmp)) //return(false); //m_trade_time_flags=(int)tmp; //if(!SymbolInfoInteger(m_name,SYMBOL_FILLING_MODE,tmp)) //return(false); //m_trade_fill_flags=(int)tmp; //--- 成功 return(true); }
有一些内置的枚举在 MQL5 中存在, 但在 MQL4 中没有。对于这种类, ENUM_SYMBOL_CALC_MODE 和 ENUM_SYMBOL_SWAP_MODE 将需要使用 MQL4 的编译器编译头文件。为包含这些枚举, 我们只需在类的头文件里声明它们:
enum ENUM_SYMBOL_CALC_MODE { SYMBOL_CALC_MODE_FOREX, SYMBOL_CALC_MODE_FUTURES, SYMBOL_CALC_MODE_CFD, SYMBOL_CALC_MODE_CFDINDEX, SYMBOL_CALC_MODE_CFDLEVERAGE, SYMBOL_CALC_MODE_EXCH_STOCKS, SYMBOL_CALC_MODE_EXCH_FUTURES, SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS }; enum ENUM_SYMBOL_SWAP_MODE { SYMBOL_SWAP_MODE_DISABLED, SYMBOL_SWAP_MODE_POINTS, SYMBOL_SWAP_MODE_CURRENCY_SYMBOL, SYMBOL_SWAP_MODE_CURRENCY_MARGIN, SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT, SYMBOL_SWAP_MODE_INTEREST_CURRENT, SYMBOL_SWAP_MODE_INTEREST_OPEN, SYMBOL_SWAP_MODE_REOPEN_CURRENT, SYMBOL_SWAP_MODE_REOPEN_BID };
请注意, 并非所有 MQL4 不支持的功能都要注释掉。截至目前, 为了让 MQL4 编译器可以编译头文件, 所做变更只是最小且必要的。当不确定某个特殊方法或功能是否在两个版本里支持时, 建议参考两种语言的文档。
由于每个版本都有自己的 CSymbolInfo 单独副本, 基础头文件不包含任何类定义。相反, 它只会包含指向基于所用编译器版本的头文件位置:
#ifdef __MQL5__ #include <Trade\SymbolInfo.mqh> #else #include "..\..\MQL4\Lib\SymbolInfo.mqh" #endif
MQL5 头文件将指向 CSymbolInfo 在 MQL5 标准库中的原始位置。另一方面, MQL4 头文件指向 CSymbolInfo 头文件, 即位于 Lib 下面的修改自 MQL5 的 CSymbolInfo 头文件副本。
CSymbolManager
为了适应多币种智能交易程序, 需要 CSymbolInfo 的实例集合。这就是 CSymbolManager 的目的。该类扩展了 CArrayObj, 可令其保存各种品种信息对象的实例, 另带一些额外方法。它与 CSymbolInfo 实例的关系如下图大略所示:
就是说, 它的行为像 CArrayObj, 虽然该类最好用于存储指定类型的对象 (CSymbolInfo 实例)。由于 CSymbolInfo 基于 CArrayObj, 而 CArrayObj 在 MQL4 和 MQL5 里均有提供, 我们可将大部分代码限制在基础头文件里, 然后为两个版本 (取决于使用哪一个编译器) 引用空的类文件。最后, 它将具有相同的文件结构, 如同早前讨论的 CSymbolInfo 类文件:
添加品种
添加 CSymbolnfo 的实例类似于 CArrayObj 的 Add 方法。然而, 不同于 CArrayObj, 添加的实例应是唯一的。就是说, 不应该有两个元素共享相同的品种名称。
bool CSymbolManagerBase::Add(CSymbolInfo *node) { if(Search(node.Name())==-1) return CArrayObj::Add(node); return false; }
派生类使用自定义的 Search 方法。它不会使用 CArrayObj 的 Search 方法, 因为它会使用 CSymbolInfo 的 Compare 方法, 实际上是 CObject 的方法 (CSymbolInfo 没有明确的 Compare 方法)。使用 Compare 方法要求我们扩展 CSymbolInfo, 这可能是一个比较复杂的解决方案, 因为我们已经有了单独的类副本。新的 Search 方法会利用它们跟踪的品种名称来比较对象:
int CSymbolManagerBase::Search(string symbol=NULL) { if(symbol==NULL) symbol= Symbol(); for(int i=0;i<Total();i++) { CSymbolInfo *item=At(i); if(StringCompare(item.Name(),symbol)==0) return i; } return -1; }对于多币种智能交易程序, 也许存在一个主要品种。它可以是当前符号, 但也可以是不同的一个。对这个品种的访问比其余的更频繁。在这个类中, 我们允许智能程序能够在初始化期间设定主要品种。如果没有选择, 第一个 CSymbolInfo 实例将被视为主要品种:
void CSymbolManagerBase::SetPrimary(string symbol=NULL) { if(symbol==NULL) symbol= Symbol(); m_symbol_primary=Get(symbol); } CSymbolInfo *CSymbolManagerBase::GetPrimary(void) { if (!CheckPointer(m_symbol_primary) && Total()>0) SetPrimary(0); return m_symbol_primary; }
有众多可能的方法可以做到。以上代码是最简单的一种。
刷新品种
若要在品种管理器里刷新所有品种的汇率, 简单地迭代当前类中保存的所有对象, 并逐一调用它们的 RefreshRates 方法。请注意, 对于海量的品种集合, 这也许不是最实用的方法来调用。相反, 在这种情况下, 调用特定 CSymbolInfo 实例的 RefreshRates 函数也许更好。以下代码显示类的 RefreshRates 方法:
bool CSymbolManagerBase::RefreshRates(void) { for(int i=0;i<Total();i++) { CSymbolInfo *symbol=At(i); if(!CheckPointer(symbol)) continue; if(!symbol.RefreshRates()) return false; } return true; }
注意, 在 CSymbolInfo 中的 Refresh 和 RefreshRates 方法有所不同。前者是在初始化期间使用的, 而后者则是在最后的分时处理过程中更新品种信息。
以下是 CSymbolManager 类基本实现的完整代码:
(SymbolManagerBase.mqh)
#include <Arrays\ArrayObj.mqh> #include "..\Lib\SymbolInfo.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CSymbolManagerBase : public CArrayObj { protected: CSymbolInfo *m_symbol_primary; CObject *m_container; public: CSymbolManagerBase(void); ~CSymbolManagerBase(void); virtual bool Add(CSymbolInfo*); virtual void Deinit(void); CSymbolInfo *Get(string); virtual bool RefreshRates(void); virtual int Search(string); virtual CObject *GetContainer(void); virtual void SetContainer(CObject*); virtual void SetPrimary(string); virtual void SetPrimary(const int); virtual CSymbolInfo *GetPrimary(void); virtual string GetPrimaryName(void); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSymbolManagerBase::CSymbolManagerBase(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSymbolManagerBase::~CSymbolManagerBase(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CObject *CSymbolManagerBase::GetContainer(void) { return m_container; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSymbolManagerBase::SetContainer(CObject *container) { m_container=container; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSymbolManagerBase::Deinit(void) { Shutdown(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CSymbolManagerBase::Add(CSymbolInfo *node) { if(Search(node.Name())==-1) return CArrayObj::Add(node); return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSymbolInfo *CSymbolManagerBase::Get(string symbol=NULL) { if(symbol==NULL) symbol= Symbol(); for(int i=0;i<Total();i++) { CSymbolInfo *item=At(i); if(!CheckPointer(item)) continue; if(StringCompare(item.Name(),symbol)==0) return item; } return NULL; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CSymbolManagerBase::RefreshRates(void) { for(int i=0;i<Total();i++) { CSymbolInfo *symbol=At(i); if(!CheckPointer(symbol)) continue; if(!symbol.RefreshRates()) return false; } return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CSymbolManagerBase::Search(string symbol=NULL) { if(symbol==NULL) symbol= Symbol(); for(int i=0;i<Total();i++) { CSymbolInfo *item=At(i); if(StringCompare(item.Name(),symbol)==0) return i; } return -1; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CSymbolManagerBase::SetPrimary(string symbol=NULL) { if(symbol==NULL) symbol= Symbol(); m_symbol_primary=Get(symbol); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CSymbolManagerBase::SetPrimary(const int idx) { m_symbol_primary=At(idx); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSymbolInfo *CSymbolManagerBase::GetPrimary(void) { if (!CheckPointer(m_symbol_primary) && Total()>0) SetPrimary(0); return m_symbol_primary; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string CSymbolManagerBase::GetPrimaryName(void) { if(CheckPointer(m_symbol_primary)) return m_symbol_primary.Name(); return NULL; } //+------------------------------------------------------------------+ #ifdef __MQL5__ #include "..\..\MQL5\Symbol\SymbolManager.mqh" #else #include "..\..\MQL4\Symbol\SymbolManager.mqh" #endif //+------------------------------------------------------------------+
所有方法均可跨平台兼容。因此, 特定平台的类将是相同的, 并且不包含任何附加的方法:
(SymbolManager.mqh, MQL4 and MQL5)
class CSymbolManager : public CSymbolManagerBase { public: CSymbolManager(void); ~CSymbolManager(void); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSymbolManager::CSymbolManager(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSymbolManager::~CSymbolManager(void) { } //+------------------------------------------------------------------+
CAccountInfo
类似于 CSymbolInfo 中所作的那样, 大多数不支持的功能将会加入注释。不支持的功能有保证金模式 (仅在 Metatrader 5), OrderCalcMargin 和 OrderCalcProfit 函数, 它们在 MQL4 里没有对口。所以, 如同我们在 CSymbolInfo 类中所作的那样, 我们为两个平台创建同一个类的单独副本, 然后修改类的 MQL4 版本, 以便可以使用 MQL4 编译器编译它。文件结构类似于迄今为止所讨论的其它类:
修改后的类如下所示。
//+------------------------------------------------------------------+ //| AccountInfo.mqh | //| 版权所有 2009-2016, MetaQuotes 软件公司| //| http://www.mql5.com | //+------------------------------------------------------------------+ #include <Object.mqh> //+------------------------------------------------------------------+ //| Class CAccountInfo. | //| 任用: 访问账户信息的类。 | //| 派生自 CObject 类。 | //+------------------------------------------------------------------+ class CAccountInfo : public CObject { public: CAccountInfo(void); ~CAccountInfo(void); //--- 快速访问账户整数属性的方法 long Login(void) const; ENUM_ACCOUNT_TRADE_MODE TradeMode(void) const; string TradeModeDescription(void) const; long Leverage(void) const; ENUM_ACCOUNT_STOPOUT_MODE StopoutMode(void) const; string StopoutModeDescription(void) const; //ENUM_ACCOUNT_MARGIN_MODE MarginMode(void) const; //string MarginModeDescription(void) const; bool TradeAllowed(void) const; bool TradeExpert(void) const; int LimitOrders(void) const; //--- 快速访问账户双精度属性的方法 double Balance(void) const; double Credit(void) const; double Profit(void) const; double Equity(void) const; double Margin(void) const; double FreeMargin(void) const; double MarginLevel(void) const; double MarginCall(void) const; double MarginStopOut(void) const; //--- 快速访问账户字符串属性的方法 string Name(void) const; string Server(void) const; string Currency(void) const; string Company(void) const; //--- MQL5 函数 API 的访问方法 long InfoInteger(const ENUM_ACCOUNT_INFO_INTEGER prop_id) const; double InfoDouble(const ENUM_ACCOUNT_INFO_DOUBLE prop_id) const; string InfoString(const ENUM_ACCOUNT_INFO_STRING prop_id) const; //--- 检查 //double OrderProfitCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, //const double volume,const double price_open,const double price_close) const; //double MarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, //const double volume,const double price) const; //double FreeMarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, //const double volume,const double price) const; //double MaxLotCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, //const double price,const double percent=100) const; }; //+------------------------------------------------------------------+ //| 构造器 | //+------------------------------------------------------------------+ CAccountInfo::CAccountInfo(void) { } //+------------------------------------------------------------------+ //| 析构器 | //+------------------------------------------------------------------+ CAccountInfo::~CAccountInfo(void) { } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_LOGIN" 属性值 | //+------------------------------------------------------------------+ long CAccountInfo::Login(void) const { return(AccountInfoInteger(ACCOUNT_LOGIN)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_TRADE_MODE" 属性值 | //+------------------------------------------------------------------+ ENUM_ACCOUNT_TRADE_MODE CAccountInfo::TradeMode(void) const { return((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_TRADE_MODE" 属性值的字符串 | //+------------------------------------------------------------------+ string CAccountInfo::TradeModeDescription(void) const { string str; //--- switch(TradeMode()) { case ACCOUNT_TRADE_MODE_DEMO : str="演示交易账户"; break; case ACCOUNT_TRADE_MODE_CONTEST: str="竞赛交易账户"; break; case ACCOUNT_TRADE_MODE_REAL : str="实盘交易账户"; break; default : str="未知交易账户"; } //--- return(str); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_LEVERAGE" 属性值 | //+------------------------------------------------------------------+ long CAccountInfo::Leverage(void) const { return(AccountInfoInteger(ACCOUNT_LEVERAGE)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_MARGIN_SO_MODE" 属性值 | //+------------------------------------------------------------------+ ENUM_ACCOUNT_STOPOUT_MODE CAccountInfo::StopoutMode(void) const { return((ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_MARGIN_SO_MODE" 属性值的字符串 | //+------------------------------------------------------------------+ string CAccountInfo::StopoutModeDescription(void) const { string str; //--- switch(StopoutMode()) { case ACCOUNT_STOPOUT_MODE_PERCENT: str="级别指定为百分比"; break; case ACCOUNT_STOPOUT_MODE_MONEY : str="级别指定为资金"; break; default : str="未知强行平仓模式"; } //--- return(str); } /* //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_MARGIN_MODE" 属性值 | //+------------------------------------------------------------------+ ENUM_ACCOUNT_MARGIN_MODE CAccountInfo::MarginMode(void) const { return((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); } */ /* //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_MARGIN_MODE" 属性值的字符串 | //+------------------------------------------------------------------+ string CAccountInfo::MarginModeDescription(void) const { string str; //--- switch(MarginMode()) { case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: str="净值"; break; case ACCOUNT_MARGIN_MODE_EXCHANGE : str="证券"; break; case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: str="对冲"; break; default : str="未知保证金模式"; } //--- return(str); } */ //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_TRADE_ALLOWED" 属性值 | //+------------------------------------------------------------------+ bool CAccountInfo::TradeAllowed(void) const { return((bool)AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_TRADE_EXPERT" 属性值 | //+------------------------------------------------------------------+ bool CAccountInfo::TradeExpert(void) const { return((bool)AccountInfoInteger(ACCOUNT_TRADE_EXPERT)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_LIMIT_ORDERS" 属性值 | //+------------------------------------------------------------------+ int CAccountInfo::LimitOrders(void) const { return((int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_BALANCE" 属性值 | //+------------------------------------------------------------------+ double CAccountInfo::Balance(void) const { return(AccountInfoDouble(ACCOUNT_BALANCE)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_CREDIT" 属性值 | //+------------------------------------------------------------------+ double CAccountInfo::Credit(void) const { return(AccountInfoDouble(ACCOUNT_CREDIT)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_PROFIT" 属性值 | //+------------------------------------------------------------------+ double CAccountInfo::Profit(void) const { return(AccountInfoDouble(ACCOUNT_PROFIT)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_EQUITY" 属性值 | //+------------------------------------------------------------------+ double CAccountInfo::Equity(void) const { return(AccountInfoDouble(ACCOUNT_EQUITY)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_MARGIN" 属性值 | //+------------------------------------------------------------------+ double CAccountInfo::Margin(void) const { return(AccountInfoDouble(ACCOUNT_MARGIN)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_FREEMARGIN" 属性值 | //+------------------------------------------------------------------+ double CAccountInfo::FreeMargin(void) const { return(AccountInfoDouble(ACCOUNT_FREEMARGIN)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_MARGIN_LEVEL" 属性值 | //+------------------------------------------------------------------+ double CAccountInfo::MarginLevel(void) const { return(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_MARGIN_SO_CALL" 属性值 | //+------------------------------------------------------------------+ double CAccountInfo::MarginCall(void) const { return(AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_MARGIN_SO_SO" 属性值 | //+------------------------------------------------------------------+ double CAccountInfo::MarginStopOut(void) const { return(AccountInfoDouble(ACCOUNT_MARGIN_SO_SO)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_NAME" 属性值 | //+------------------------------------------------------------------+ string CAccountInfo::Name(void) const { return(AccountInfoString(ACCOUNT_NAME)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_SERVER" 属性值 | //+------------------------------------------------------------------+ string CAccountInfo::Server(void) const { return(AccountInfoString(ACCOUNT_SERVER)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_CURRENCY" 属性值 | //+------------------------------------------------------------------+ string CAccountInfo::Currency(void) const { return(AccountInfoString(ACCOUNT_CURRENCY)); } //+------------------------------------------------------------------+ //| 获取 "ACCOUNT_COMPANY" 属性值 | //+------------------------------------------------------------------+ string CAccountInfo::Company(void) const { return(AccountInfoString(ACCOUNT_COMPANY)); } //+------------------------------------------------------------------+ //| 访问函数 AccountInfoInteger(...) | //+------------------------------------------------------------------+ long CAccountInfo::InfoInteger(const ENUM_ACCOUNT_INFO_INTEGER prop_id) const { return(AccountInfoInteger(prop_id)); } //+------------------------------------------------------------------+ //| 访问函数 AccountInfoDouble(...) | //+------------------------------------------------------------------+ double CAccountInfo::InfoDouble(const ENUM_ACCOUNT_INFO_DOUBLE prop_id) const { return(AccountInfoDouble(prop_id)); } //+------------------------------------------------------------------+ //| 访问函数 AccountInfoString(...) | //+------------------------------------------------------------------+ string CAccountInfo::InfoString(const ENUM_ACCOUNT_INFO_STRING prop_id) const { return(AccountInfoString(prop_id)); } /* //+------------------------------------------------------------------+ //| 访问函数 OrderCalcProfit(...)。 | //| 输入: name - 品种名称, | //| trade_operation - 交易操作, | //| volume - 开仓交易量, | //| price_open - 开仓价格, | //| price_close - 平仓价格。 | //+------------------------------------------------------------------+ double CAccountInfo::OrderProfitCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, const double volume,const double price_open,const double price_close) const { double profit=EMPTY_VALUE; //--- if(!OrderCalcProfit(trade_operation,symbol,volume,price_open,price_close,profit)) return(EMPTY_VALUE); //--- return(profit); } */ /* //+------------------------------------------------------------------+ //| 访问函数 OrderCalcMargin(...)。 | //| 输入: name - 品种名称, | //| trade_operation - 交易操作, | //| volume - 开仓交易量, | //| price - 开仓价格。 | //+------------------------------------------------------------------+ double CAccountInfo::MarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, const double volume,const double price) const { double margin=EMPTY_VALUE; //--- if(!OrderCalcMargin(trade_operation,symbol,volume,price,margin)) return(EMPTY_VALUE); //--- return(margin); } */ /* //+------------------------------------------------------------------+ //| 访问函数 OrderCalcMargin(...)。 | //| 输入: name - 品种名称, | //| trade_operation - 交易操作, | //| volume - 开仓交易量, | //| price - 开仓价格。 | //+------------------------------------------------------------------+ double CAccountInfo::FreeMarginCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, const double volume,const double price) const { return(FreeMargin()-MarginCheck(symbol,trade_operation,volume,price)); } */ /* //+------------------------------------------------------------------+ //| 访问函数 OrderCalcMargin(...)。 | //| 输入: name - 品种名称, | //| trade_operation - 交易操作, | //| price - 开仓价格, | //| percent - 可用保证金百分比 [1-100%]。 | //+------------------------------------------------------------------+ double CAccountInfo::MaxLotCheck(const string symbol,const ENUM_ORDER_TYPE trade_operation, const double price,const double percent) const { double margin=0.0; //--- 检查 if(symbol=="" || price<=0.0 || percent<1 || percent>100) { Print("CAccountInfo::MaxLotCheck 无效参数"); return(0.0); } //--- 计算 1 手所需保证金 if(!OrderCalcMargin(trade_operation,symbol,1.0,price,margin) || margin<0.0) { Print("CAccountInfo::MaxLotCheck 保证金计算失败"); return(0.0); } //--- if(margin==0.0) // for pending orders return(SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX)); //--- 计算最大交易量 double volume=NormalizeDouble(FreeMargin()*percent/100.0/margin,2); //--- 规范并检查限制 double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP); if(stepvol>0.0) volume=stepvol*MathFloor(volume/stepvol); //--- double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN); if(volume<minvol) volume=0.0; //--- double maxvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX); if(volume>maxvol) volume=maxvol; //--- 返回交易量 return(volume); } */ //+------------------------------------------------------------------+
平台一次只能访问一个账户。因此, 创建 CAccountInfo 的实例集合不再是必要的。
CExpertTrade
尽管具有相同的最终目标, MQL4 和 MQL5 在交易操作的执行方式上依然有所不同。这意味着我们有可能在跨平台智能交易程序里使用 MQL5 版本的 CExpertTrade 类, 但在 MQL4上 并非整体。在此情况下, 对于 MQL4 版本, 我们将创建一个包含相同类名的头文件。我们依然保留基本类的头文件, 以便它依照所用编译器的类型, 根据以下结构使用正确的头文件:
这个类将模拟在 MQL5 的 CexpertTrade 类中发现的大多数成员和方法, 以便当我们执行如下代码时:
CExpertTrade trade;
trade.Buy(/*params*/);
两个版本均可理解我们的意图, 入场开多头仓位。
最低需求是模拟平台上的开仓方法。也就是, CExpertTrade 的 Buy 和 Sell 方法。对于离场, 我们将拆分我们的实现: OrderClose 用于 MQL4 版本, 对于 MQL5 版本则使用原生方法 PositionClose (对冲) 或 Buy 和 Sell 方法 (净余)。以下代码显示 MQL4 版本的 CExpertTrade。从一位程序员到另一位程序员, 代码也许会有很大变化, 这是其中一部分:
#include <Arrays\ArrayInt.mqh> enum ENUM_ORDER_TYPE_TIME { ORDER_TIME_GTC, ORDER_TIME_DAY, ORDER_TIME_SPECIFIED, ORDER_TIME_SPECIFIED_DAY }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CExpertTrade : public CObject //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ { protected: int m_magic; ulong m_deviation; ENUM_ORDER_TYPE_TIME m_order_type_time; datetime m_order_expiration; bool m_async_mode; uint m_retry; int m_sleep; color m_color_long; color m_color_short; color m_color_buystop; color m_color_buylimit; color m_color_sellstop; color m_color_selllimit; color m_color_modify; color m_color_exit; CSymbolInfo *m_symbol; public: CExpertTrade(void); ~CExpertTrade(void); //--- setters/getters 赋值和取值 color ArrowColor(const ENUM_ORDER_TYPE type); uint Retry() {return m_retry;} void Retry(uint retry){m_retry=retry;} int Sleep() {return m_sleep;} void Sleep(int sleep){m_sleep=sleep;} void SetAsyncMode(const bool mode) {m_async_mode=mode;} void SetExpertMagicNumber(const int magic) {m_magic=magic;} void SetDeviationInPoints(const ulong deviation) {m_deviation=deviation;} void SetOrderExpiration(const datetime expire) {m_order_expiration=expire;} bool SetSymbol(CSymbolInfo *); //-- 交易方法 virtual ulong Buy(const double,const double,const double,const double,const string); virtual ulong Sell(const double,const double,const double,const double,const string); virtual bool OrderDelete(const ulong); virtual bool OrderClose(const ulong,const double,const double); virtual bool OrderCloseAll(CArrayInt *,const bool); virtual bool OrderModify(const ulong,const double,const double,const double,const ENUM_ORDER_TYPE_TIME,const datetime,const double); virtual ulong OrderOpen(const string,const ENUM_ORDER_TYPE,const double,const double,const double,const double,const double,const ENUM_ORDER_TYPE_TIME,const datetime,const string); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpertTrade::CExpertTrade(void) : m_magic(0), m_deviation(10), m_order_type_time(0), m_symbol(NULL), m_async_mode(0), m_retry(3), m_sleep(100), m_color_long(clrGreen), m_color_buystop(clrGreen), m_color_buylimit(clrGreen), m_color_sellstop(clrRed), m_color_selllimit(clrRed), m_color_short(clrRed), m_color_modify(clrNONE), m_color_exit(clrNONE) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpertTrade::~CExpertTrade(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CExpertTrade::SetSymbol(CSymbolInfo *symbol) { if(symbol!=NULL) { m_symbol=symbol; return true; } return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CExpertTrade::OrderModify(const ulong ticket,const double price,const double sl,const double tp, const ENUM_ORDER_TYPE_TIME type_time,const datetime expiration,const double stoplimit=0.0) { return ::OrderModify((int)ticket,price,sl,tp,expiration,m_color_modify); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CExpertTrade::OrderDelete(const ulong ticket) { return ::OrderDelete((int)ticket); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ ulong CExpertTrade::Buy(const double volume,const double price,const double sl,const double tp,const string comment="") { if(m_symbol==NULL) return false; m_symbol.RefreshRates(); string symbol=m_symbol.Name(); double stops_level=m_symbol.StopsLevel()*m_symbol.Point(); double ask=m_symbol.Ask(); if(symbol=="") return 0; if(price!=0) { if(price>ask+stops_level) return OrderOpen(symbol,ORDER_TYPE_BUY_STOP,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment); if(price<ask-stops_level) return OrderOpen(symbol,ORDER_TYPE_BUY_LIMIT,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment); } return OrderOpen(symbol,ORDER_TYPE_BUY,volume,0.0,ask,sl,tp,m_order_type_time,m_order_expiration,comment); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ ulong CExpertTrade::Sell(const double volume,const double price,const double sl,const double tp,const string comment="") { if(m_symbol==NULL) return false; m_symbol.RefreshRates(); string symbol=m_symbol.Name(); double stops_level=m_symbol.StopsLevel()*m_symbol.Point(); double bid=m_symbol.Bid(); if(symbol=="") return 0; if(price!=0) { if(price>bid+stops_level) return OrderOpen(symbol,ORDER_TYPE_SELL_LIMIT,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment); if(price<bid-stops_level) return OrderOpen(symbol,ORDER_TYPE_SELL_STOP,volume,0.0,price,sl,tp,m_order_type_time,m_order_expiration,comment); } return OrderOpen(symbol,ORDER_TYPE_SELL,volume,0.0,bid,sl,tp,m_order_type_time,m_order_expiration,comment); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ ulong CExpertTrade::OrderOpen(const string symbol,const ENUM_ORDER_TYPE order_type,const double volume, const double limit_price,const double price,const double sl,const double tp, const ENUM_ORDER_TYPE_TIME type_time=ORDER_TIME_GTC,const datetime expiration=0, const string comment="") { bool res; ulong ticket=0; color arrowcolor=ArrowColor(order_type); datetime expire=0; if(order_type>1 && expiration>0) expire=expiration*1000+TimeCurrent(); double stops_level=m_symbol.StopsLevel(); for(uint i=0;i<m_retry;i++) { if(ticket>0) break; if(IsStopped()) return 0; if(IsTradeContextBusy() || !IsConnected()) { ::Sleep(m_sleep); continue; } if(stops_level==0 && order_type<=1) { ticket=::OrderSend(symbol,order_type,volume,price,(int)(m_deviation*m_symbol.Point()),0,0,comment,m_magic,expire,arrowcolor); ::Sleep(m_sleep); for(uint j=0;j<m_retry;j++) { if(res) break; if(ticket>0 && (sl>0 || tp>0)) { if(OrderSelect((int)ticket,SELECT_BY_TICKET)) { res=OrderModify((int)ticket,OrderOpenPrice(),sl,tp,OrderExpiration()); ::Sleep(m_sleep); } } } } else { ticket=::OrderSend(symbol,order_type,volume,price,(int)m_deviation,sl,tp,comment,m_magic,expire,arrowcolor); ::Sleep(m_sleep); } } return ticket>0?ticket:0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CExpertTrade::OrderClose(const ulong ticket,const double lotsize=0.0,const double price=0.0) { if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) return false; if(OrderCloseTime()>0) return true; double close_price=0.0; int deviation=0; if(OrderSymbol()==m_symbol.Name() && price>0.0) { close_price=NormalizeDouble(price,m_symbol.Digits()); deviation=(int)(m_deviation*m_symbol.Point()); } else { close_price=NormalizeDouble(OrderClosePrice(),(int)MarketInfo(OrderSymbol(),MODE_DIGITS)); deviation=(int)(m_deviation*MarketInfo(OrderSymbol(),MODE_POINT)); } double lots=(lotsize>0.0 || lotsize>OrderLots())?lotsize:OrderLots(); return ::OrderClose((int)ticket,lots,close_price,deviation,m_color_exit); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CExpertTrade::OrderCloseAll(CArrayInt *other_magic,const bool restrict_symbol=true) { bool res=true; int total= OrdersTotal(); for(int i=total-1;i>=0;i--) { double bid=0.0,ask=0.0; if(!OrderSelect(i,SELECT_BY_POS)) continue; if(OrderSymbol()!=m_symbol.Name() && restrict_symbol) continue; if(OrderMagicNumber()!=m_magic && other_magic.Search(OrderMagicNumber())<0) continue; m_symbol.RefreshRates(); if(OrderSymbol()==m_symbol.Name()) { bid = m_symbol.Bid(); ask = m_symbol.Ask(); } else { bid = MarketInfo(OrderSymbol(),MODE_BID); ask = MarketInfo(OrderSymbol(),MODE_ASK); } if(res) res=OrderClose(OrderTicket(),OrderLots(),OrderType()==ORDER_TYPE_BUY?bid:ask); } return res; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ color CExpertTrade::ArrowColor(const ENUM_ORDER_TYPE type) { switch(type) { case ORDER_TYPE_BUY: return m_color_long; case ORDER_TYPE_SELL: return m_color_short; case ORDER_TYPE_BUY_STOP: return m_color_buystop; case ORDER_TYPE_BUY_LIMIT: return m_color_buylimit; case ORDER_TYPE_SELL_STOP: return m_color_sellstop; case ORDER_TYPE_SELL_LIMIT: return m_color_selllimit; } return clrNONE; } //+------------------------------------------------------------------+
注意, 对于 MQL5 版本, CExpertTrade 类继承自 CTrade 类 (CObject 的扩展)。另一方面, 对于 MQL4 版本, CExpertTrade 类直接从 CObject 继承。这意味着, 对于 MQL4 版本, CTrade 和 CExpertTrade 被合并成一个单一的对象。
一些诸如 ORDER_TYPE_TIME 等在 MQL4 里没有对口功能。然而, 对于利用 MQL4 扩展这个类, 以及模拟 MQL5 的订单生命周期的情况下, 这也许有帮助。
CTradeManager
原生 MQL5 类 CExpertTrade 有一个名为 SetSymbol 的方法。此方法允许它切换品种信息的指针指向其它品种的信息实例 (CSymbolInfo)。利用这种设置, 大多数在类中发现的方法将不再需要字符串类型参数, 原本它代表所处理的金融工具名称。
在大多数情况下, 只需简单地使用 CExpertTrade 并简单地切换品种就足够了。然而, 使用这种方法时, 有几个因素需要考虑。在一些情况中, 当品种指针改变时, 偏差或最大滑点也需要更新。这是真的, 特别是当智能交易程序即将处理的金融工具小位数不同时。另一个因素是魔幻数字, 如果智能交易程序有意为每个交易品种的入场仓位使用不同魔幻数字时, 也应更新。
处理此问题的一种方法是使用交易管理器。与早前介绍的品种管理器类似, 此对象扩展了 CArrayObj, 并且几乎与 CSymbolManager 有相同的方法集。我们应该有一个基本的头文件, 依赖所使用的编译器引用正确的子代来编译文件。并且就像品种管理器, 交易管理器处理数据存储及恢复。因此, 大部分代码都可以在基本头文件中找到。文件结构如下图所示。
我们的交易管理器所用的类代码示意如下。
(TradeManagerBase.mqh)
#include <Arrays\ArrayObj.mqh> #include "ExpertTradeXBase.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CTradeManagerBase : public CArrayObj { public: CTradeManagerBase(void); ~CTradeManagerBase(void); virtual int Search(string); virtual bool Add(CExpertTradeX*); virtual void Deinit(void); CExpertTrade *Get(const string); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CTradeManagerBase::CTradeManagerBase(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CTradeManagerBase::~CTradeManagerBase(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CTradeManagerBase::Deinit(void) { Shutdown(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CTradeManagerBase::Add(CExpertTradeX *node) { if(Search(node.Name())==-1) return CArrayObj::Add(node); return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CTradeManagerBase::Search(string symbol=NULL) { if(symbol==NULL) symbol= Symbol(); for(int i=0;i<Total();i++) { CExpertTradeX *item=At(i); if(StringCompare(item.Name(),symbol)==0) return i; } return -1; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpertTrade *CTradeManagerBase::Get(const string name=NULL) { if(name==NULL && Total()>0) return At(0); for(int i=0;i<Total();i++) { CExpertTradeX *item=At(i); if(StringCompare(item.Name(),name)==0) return item; } return NULL; } //+------------------------------------------------------------------+ #ifdef __MQL5__ #include "..\..\MQL5\Trade\TradeManager.mqh" #else #include "..\..\MQL4\Trade\TradeManager.mqh" #endif //+------------------------------------------------------------------+
也可以简单地使用 CExpertTrade 的单个实例, 并简单地扩展它, 以便它能有自己的品种集合进行切换。但是, 这也会使用更多的内存空间, 由于也需要存储魔幻数字和偏差, 并将其链接到特定品种。
就像品种管理器, 该类还采用了自定义的 Search 方法来比较两个交易对象 (交易对象应该是唯一的)。使用交易对象的名字来执行比较, 然后基于它们所链接的 CSymbolInfo 实例名称。然而, 对于 MQL5 版本, CExpertTrade 对象不会返回任何品种指针或名称。在此情况下, 我们需要扩展 CExpertTrade 来允许这个对象的实例能够返回其所包含的品种名称。我们应为对象给出一个 CExpertTradeX 的名字。就像本文中所有其它的类定义, 应有一个基本头文件, 依赖使用的编译器决定选用哪个子代的头文件:
下图示意所述类的基本实现:
(CExpertTradeXBase.mqh)
#include "ExpertTradeBase.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CExpertTradeXBase : public CExpertTrade { public: CExpertTradeXBase(void); ~CExpertTradeXBase(void); string Name(void); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpertTradeXBase::CExpertTradeXBase(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpertTradeXBase::~CExpertTradeXBase(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string CExpertTradeXBase::Name(void) { if (CheckPointer(m_symbol)) return m_symbol.Name(); return NULL; } //+------------------------------------------------------------------+ #ifdef __MQL5__ #include "..\..\MQL5\Trade\ExpertTradeX.mqh" #else #include "..\..\MQL4\Trade\ExpertTradeX.mqh" #endif //+------------------------------------------------------------------+
注意, 这个 CExpertTradeX 类扩展了 CExpertTrade。它应表现出该对象从一个单一对象继承。然而, 在现实中, MQL4 和 MQL5 版本有不同的 CExpertTrade 版本。这是一个相当简单的对象, 其方法是跨平台兼容的, 所以像一些类, 我们在声明它的平台特定类时无需额外方法:
(CExpertTradeX.mqh, MQL4 和 MQL5 版本)
class CExpertTradeX : public CExpertTradeXBase { public: CExpertTradeX(void); ~CExpertTradeX(void); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpertTradeX::CExpertTradeX(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpertTradeX::~CExpertTradeX(void) { } //+------------------------------------------------------------------+
结论
在本文中, 我们已经展示了如何将 MQL5 标准库中的一些原生控件进行重构, 以便可以在 MQL4 智能交易程序里重用, 而不用从头编写这些类的 MQL4 版本。这些类 CSymbolInfo, CAccount, 和 CExpertTrade 已被修改, 诸如 CSymbolManager 和 CTradeManager 的管理器已经开发出来, 智能交易程序和脚本可以处理多个所述的对象实例 (CSymbolInfo, CAccount, 和 CExpertTrade)。