Cross-Plattform Expert Advisor: Wiederverwendung von Komponenten aus der Standardbibliothek von MQL5
Inhaltsverzeichnis
Einführung
Es gibt einige Komponenten in der Standardbibliothek von MQL5, die für einen Cross-Plattform Expert Advisors hilfreich sein könnten. Allerdings verunmöglicht ihre Inkompatibilität mit dem MQL4 Kompiler eine Verwendung für eine MQL4 Version eines Cross-Plattform Expert Advisors. Es gibt aber zwei Möglichkeiten in diesem Fall:
- Alles neu schreiben unter Verwendung nur der von beiden unterstützen Teile
- Kopieren und modifizieren der Klassen, damit die Headerdateien in MQL4 kompiliert werden können
In diesem Artikel untersuchen wir die zweite Option. Diese Methode erzeugt eine etwas lockerere Übersetzung nach MQL4. Der Hauptvorteil ist jedoch, dass wir nicht eine Menge Code neu schreiben müssen. Auch ein Update der MQL5 Klassen ist so leichter, als wenn alles neu geschrieben worden wäre.
Verzeichnisstruktur
Anders als die meisten verwendeten Klassen, müssen die aus der Standardbibliothek des MQL5 an einen Ort im Include-Verzeichnis des MQL4 kopiert werden. Daher muss ein bisschen getan werde, bevor ein Expert Advisor, unabhängig von der Version, erstellt werden kann. Die Methoden, dies zu erreichen, können variieren, aber eine der einfachsten ist die Verknüpfung mit einer bestimmten Headerdatei, die ihrerseits mit einer entsprechenden Headerdatei arbeitet. Das ist durchaus ähnlich den normalen, nutzererstellten Objektklassen:
Für die MQL5-Version muss die Haupt-Headerdatei mit der verwendeten Headerdatei am originalen Speicherort verlinkt werden.
Für die MQL4-Version muss die Haupt-Headerdatei mit einer (modifizierten) Kopie der MQL5 Headerdatei verlinkt werden.
Ein Weg dies zu tun ist, ist ein "Lib"-Verzeichnis im Basisverzeichnis. Das ist der Ort für die Basis-Headerdatei. Eine ähnlicher Ordner wird im MQL4-Ordner des Cross-Plattform-Verzeichnis' erstellt (nennen Sie die Cross-Plattform "MQLx-Reuse"). Hier werden die (modifizierbaren) Kopien einiger Klassen von MQL5 platziert.
|-Include
|-MQLx-Reuse
|-Base
|-Lib
<Main Header Files>
|-MQL4
|-Lib
<Copy of MQL5 Header Files>
|-MQL5
Das Verzeichnis von MQL5 braucht kein Verzeichnis “Lib”, da die Haupt-Headerdatei sich direkt mit den Headerdateien verbinden können, sie liegen ja bereits im Verzeichnis von MQL5. Nehmen wir an, wir benötigen CSymbolInfo für einen Cross-Plattform Expert Advisor. Diese besondere Klasse ist Teil der Standardbibliothek von MQL5 im Verzeichnis Trade\. Es kann mit folgender #include Direktive verwendet werden:
#include <Trade\SymbolInfo.mqh>
Um diese zu verwenden, müssen wir die Haupt-Headerdatei im Verzeichnis Lib des Basis-Ordners erstellen, mit dem folgendem Code:
(/Base/Lib/SymbolInfo.mqh)
#ifdef __MQL5__ #include <Trade\SymbolInfo.mqh> #else #include "..\..\MQL4\Lib\SymbolInfo.mqh" #endif
Die Version für MQL4 verwendet die include Direktive auf eine Headerdatei im Verzeichnis “Lib”. Aber diesmal ist es das "Lib"-Verzeichnis im Ordner MQL4 (“MQLx-Reuse”).
Wie bereits erwähnt,benötigt die MQL5 kein "Lib" Verzeichnis, die Haupt-Headerdatei im Basisverzeichnis ohne Problem auf <Trade\SymbolInfo.mqh> zugreifen kann, dort also, wo die Klasse im Include-Verzeichnis sowie so liegt.
Dies ist nur eine empfohlene Vorgehensweise und keineswegs zwingend. Es hilft jedoch, wenn es einen separaten Ordner ausschließlich für die wiederverwendeten oder geliehenen Headerdateien aus MQL5 gibt.
CSymbolInfo
Der MQL4 Kompiler erzeugt bei der originalen Headerdatei der Klasse von CSymbolInfo einen Kompilerfehler. Die Fehler entstehen hauptsächlich durch Inkompatibilität mit MQL4, besonders durch die Verwendung von SymbolInfoDouble und SymbolInfoInteger. Die meisten Aufrufe dieser Funktionen stehen in der Methode Refresh() der Klasse. Der unveränderte Code der Klasse CSymbolInfo ist unten dargestellt:
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; //--- succeed return(true); }
So wie bereits im ersten Artikel verwenden wir eine gemeinsame Headerdatei, in der, idealerweise, die Gemeinsamkeiten von MQL4 und MQL5 zusammengelegt werden. Wir können die Klasse von CSymbolInfo neu schreiben und in drei Dateien aufteilen, so dass die Gemeinsamkeiten in einer Datei sind und der Rest in den beiden anderen steht. In diesem Artikel nehmen wir aber einen einfacheren (und schnelleren) Weg: Wir kopieren die MQL5 Klassendatei CSymbolInfo und auskommentieren die mit MQL4 inkompatiblen Codezeilen. Für beide Versionen würde die resultierende Dateistruktur wie folgt aussehen:
Der folgende Code zeigt die gleiche Funktion, aber mit den auskommentierten Zeilen, die in MQL4 zu einem Fehler führen könnten:
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; //--- succeed return(true); }
Es gibt in MQL5 Enumerationen, die in MQL4 fehlen. Für diese Klasse benötigen wir ENUM_SYMBOL_CALC_MODE und ENUM_SYMBOL_SWAP_MODE, damit die Headerdatei von einem MQL4 Kompiler kompiliert wird. Wir ergänzen einfach diese Enumerationen in der Headerdatei der Klasse:
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 };
Beachten Sie, dass nicht alle der in MQL4 nicht unterstützten Zeilen auskommentiert wurden. Die bisherigen Änderungen sind nur die notwendigsten, um die Headerdatei für den MQL4 Kompiler kompatible zu machen. Es wird empfohlen, die Dokumentationen beider Sprachen zu lesen, wenn Sie unsicher sind, ob eine bestimmte Methode oder Eigenschaft von beiden Kompiler unterstützt wird.
Da jede Version ihre eigene Kopie von CSymbolInfo hat, hat die Headerdatei keine Klassendefinition. Stattdessen verweist sie nur auf die Headerdatei, die vom Kompiler zu verwenden ist:
#ifdef __MQL5__ #include <Trade\SymbolInfo.mqh> #else #include "..\..\MQL4\Lib\SymbolInfo.mqh" #endif
Die Headerdatei für MQL5 zeigt auf das Original von CSymbolInfo in der Standardbibliothek von MQL5. Die MQL4 Headerdatei laden hingegen die Headerdatei von CSymbolInfo, die kopiert und modifiziert im Verzeichnis Lib liegt.
CSymbolManager
Für einen mehrwährungs Expert Advisor werden eine Reihe von Kopien von CSymbolInfo benötigt. Dies ist der Zweck des CSymbolManagers. Die Klasse erweitert CArrayObj, um für jedes Symbol eine Version mit weiteren Methoden zu haben. Deren Beziehung zu den Versionen von CSymbolInfo zeigt folgendes Bild:
Das bedeutet, sie verhält sich wie CArrayObj, obwohl diese Klasse am besten die einzelnen Objekte speichert (Instanzen von CSymbolInfo). Da CSymbolInfo auf CArrayObj basiert, und es CArrayObj sowohl für MQL4 und MQL5 gibt, kann der größte Teil des Codes für beide Versionen auf die Headerdatei beschränken werden (je nachdem, welchen Kompiler verwendet wird). Am Ende sollte es gleiche Dateistruktur haben wie die oben besprochene Klassendatei von CSymbolInfo:
Symbole hinzufügen
Die Instanzen von CSymbolnfo werden ähnlich der Methode Add aus CArrayObj hinzugefügt. Anders aber als in CArrayObj sollten die hinzugefügten Instanzen eindeutig sein. Das heißt, es darf nur eine Instanz je Symbol geben.
bool CSymbolManagerBase::Add(CSymbolInfo *node) { if(Search(node.Name())==-1) return CArrayObj::Add(node); return false; }
Die abgeleitete Methode verwendet die übliche Suchmethode. Die Suchmethode aus CArrayObj wird nicht verwendet, da diese die Methode Compare aus CObject (CSymbolInfo selbst hat ja keine Methode Compare) verwendet. Die Methode Compare würde eine Erweiterung von CSymbolInfo verlangen und das würde die Situation noch weiter verkomplizieren zusätzlich zu den separaten Kopien der Klassen. Die neue Suchmethode vergleicht die Symbolnamen der Objekte:
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; }Für mehrwährungs Expert Advisor existiert immer ein erste Symbol. Es könnte das aktuelle Chartsymbol sein oder ein anderes. Es ist das Symbol, auf das am häufigsten zugegriffen wird. In dieser Klasse erlauben wir es dem Experten, das primäre Symbol während der Initialisierung selbst festzulegen. Ist kein Symbol ausgewählt, wird die erste Instanz von CSymbolInfo als primäre verwendet:
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; }
Es gibt dafür viele Möglichkeiten. Der Code oben ist mit der Einfachste.
Symbol aktualisieren
Um alle Preise der Symbole durch den Symbolmanager zu aktualisieren, iteriert die Klasse einfach durch alle augenblicklich gespeicherten Objekte und ruft jeweils die Methode RefreshRates auf. Beachten Sie, dass für eine große Anzahl von Symbolen, dieses Vorgehen nicht unbedingt das eleganteste ist. Satt dessen könnte es besser sein, die Methode RefreshRates immer nur einzeln aufzurufen. Der folgende Code zeigt die Methode RefreshRates der Klasse:
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; }
Beachten Sie, die Methoden Refresh und RefreshRates in CSymbolInfo sind verschieden. Die Erste wird bei der Initialisierung verwendet, die Zweite für die Aktualisierung bei einem neuen Tick.
Im folgenden ist der gesamte Code der Umsetzung der Klasse 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 //+------------------------------------------------------------------+
Alle Methoden funktionieren Cross-Plattform. Damit sind die plattform-spezifischen Klassen identisch und enthalten keine zusätzlichen Methoden:
(SymbolManager.mqh, MQL4 and MQL5)
class CSymbolManager : public CSymbolManagerBase { public: CSymbolManager(void); ~CSymbolManager(void); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSymbolManager::CSymbolManager(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSymbolManager::~CSymbolManager(void) { } //+------------------------------------------------------------------+
CAccountInfo
Ähnlich wie bei CSymbolInfo müssen wir das nicht Unterstützte auskommentieren. Nicht unterstützt sind Marginabfragen (nur in Metatrader 5), und die Funktionen OrderCalcMargin und OrderCalcProfit, für die es nichts Vergleichbares in MQL4 gibt. So werden wir, wie bei der Klasse CSymbolInfo, separate Kopien dieser Klasse für beide Plattformen erstellen und die Version für MQL4 ändern, damit sie in MQL4 kompiliert werden kann. Die Dateistruktur ist ähnlich die der anderen, bisher besprochenen, Klassen:
Die geänderte Klasse ist wie folgt.
//+------------------------------------------------------------------+ //| AccountInfo.mqh | //| Copyright 2009-2016, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include <Object.mqh> //+------------------------------------------------------------------+ //| Class CAccountInfo. | //| Appointment: Class for access to account info. | //| Derives from class CObject. | //+------------------------------------------------------------------+ class CAccountInfo : public CObject { public: CAccountInfo(void); ~CAccountInfo(void); //--- fast access methods to the integer account propertyes 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; //--- fast access methods to the double account propertyes 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; //--- fast access methods to the string account propertyes string Name(void) const; string Server(void) const; string Currency(void) const; string Company(void) const; //--- access methods to the API MQL5 functions 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; //--- checks //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; }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAccountInfo::CAccountInfo(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CAccountInfo::~CAccountInfo(void) { } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_LOGIN" | //+------------------------------------------------------------------+ long CAccountInfo::Login(void) const { return(AccountInfoInteger(ACCOUNT_LOGIN)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_TRADE_MODE" | //+------------------------------------------------------------------+ ENUM_ACCOUNT_TRADE_MODE CAccountInfo::TradeMode(void) const { return((ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_TRADE_MODE" as string | //+------------------------------------------------------------------+ string CAccountInfo::TradeModeDescription(void) const { string str; //--- switch(TradeMode()) { case ACCOUNT_TRADE_MODE_DEMO : str="Demo trading account"; break; case ACCOUNT_TRADE_MODE_CONTEST: str="Contest trading account"; break; case ACCOUNT_TRADE_MODE_REAL : str="Real trading account"; break; default : str="Unknown trade account"; } //--- return(str); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_LEVERAGE" | //+------------------------------------------------------------------+ long CAccountInfo::Leverage(void) const { return(AccountInfoInteger(ACCOUNT_LEVERAGE)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_MARGIN_SO_MODE" | //+------------------------------------------------------------------+ ENUM_ACCOUNT_STOPOUT_MODE CAccountInfo::StopoutMode(void) const { return((ENUM_ACCOUNT_STOPOUT_MODE)AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_MARGIN_SO_MODE" as string | //+------------------------------------------------------------------+ string CAccountInfo::StopoutModeDescription(void) const { string str; //--- switch(StopoutMode()) { case ACCOUNT_STOPOUT_MODE_PERCENT: str="Level is specified in percentage"; break; case ACCOUNT_STOPOUT_MODE_MONEY : str="Level is specified in money"; break; default : str="Unknown stopout mode"; } //--- return(str); } /* //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_MARGIN_MODE" | //+------------------------------------------------------------------+ ENUM_ACCOUNT_MARGIN_MODE CAccountInfo::MarginMode(void) const { return((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); } */ /* //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_MARGIN_MODE" as string | //+------------------------------------------------------------------+ string CAccountInfo::MarginModeDescription(void) const { string str; //--- switch(MarginMode()) { case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: str="Netting"; break; case ACCOUNT_MARGIN_MODE_EXCHANGE : str="Exchange"; break; case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: str="Hedging"; break; default : str="Unknown margin mode"; } //--- return(str); } */ //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_TRADE_ALLOWED" | //+------------------------------------------------------------------+ bool CAccountInfo::TradeAllowed(void) const { return((bool)AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_TRADE_EXPERT" | //+------------------------------------------------------------------+ bool CAccountInfo::TradeExpert(void) const { return((bool)AccountInfoInteger(ACCOUNT_TRADE_EXPERT)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_LIMIT_ORDERS" | //+------------------------------------------------------------------+ int CAccountInfo::LimitOrders(void) const { return((int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_BALANCE" | //+------------------------------------------------------------------+ double CAccountInfo::Balance(void) const { return(AccountInfoDouble(ACCOUNT_BALANCE)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_CREDIT" | //+------------------------------------------------------------------+ double CAccountInfo::Credit(void) const { return(AccountInfoDouble(ACCOUNT_CREDIT)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_PROFIT" | //+------------------------------------------------------------------+ double CAccountInfo::Profit(void) const { return(AccountInfoDouble(ACCOUNT_PROFIT)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_EQUITY" | //+------------------------------------------------------------------+ double CAccountInfo::Equity(void) const { return(AccountInfoDouble(ACCOUNT_EQUITY)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_MARGIN" | //+------------------------------------------------------------------+ double CAccountInfo::Margin(void) const { return(AccountInfoDouble(ACCOUNT_MARGIN)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_FREEMARGIN" | //+------------------------------------------------------------------+ double CAccountInfo::FreeMargin(void) const { return(AccountInfoDouble(ACCOUNT_FREEMARGIN)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_MARGIN_LEVEL" | //+------------------------------------------------------------------+ double CAccountInfo::MarginLevel(void) const { return(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_MARGIN_SO_CALL" | //+------------------------------------------------------------------+ double CAccountInfo::MarginCall(void) const { return(AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_MARGIN_SO_SO" | //+------------------------------------------------------------------+ double CAccountInfo::MarginStopOut(void) const { return(AccountInfoDouble(ACCOUNT_MARGIN_SO_SO)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_NAME" | //+------------------------------------------------------------------+ string CAccountInfo::Name(void) const { return(AccountInfoString(ACCOUNT_NAME)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_SERVER" | //+------------------------------------------------------------------+ string CAccountInfo::Server(void) const { return(AccountInfoString(ACCOUNT_SERVER)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_CURRENCY" | //+------------------------------------------------------------------+ string CAccountInfo::Currency(void) const { return(AccountInfoString(ACCOUNT_CURRENCY)); } //+------------------------------------------------------------------+ //| Get the property value "ACCOUNT_COMPANY" | //+------------------------------------------------------------------+ string CAccountInfo::Company(void) const { return(AccountInfoString(ACCOUNT_COMPANY)); } //+------------------------------------------------------------------+ //| Access functions AccountInfoInteger(...) | //+------------------------------------------------------------------+ long CAccountInfo::InfoInteger(const ENUM_ACCOUNT_INFO_INTEGER prop_id) const { return(AccountInfoInteger(prop_id)); } //+------------------------------------------------------------------+ //| Access functions AccountInfoDouble(...) | //+------------------------------------------------------------------+ double CAccountInfo::InfoDouble(const ENUM_ACCOUNT_INFO_DOUBLE prop_id) const { return(AccountInfoDouble(prop_id)); } //+------------------------------------------------------------------+ //| Access functions AccountInfoString(...) | //+------------------------------------------------------------------+ string CAccountInfo::InfoString(const ENUM_ACCOUNT_INFO_STRING prop_id) const { return(AccountInfoString(prop_id)); } /* //+------------------------------------------------------------------+ //| Access functions OrderCalcProfit(...). | //| INPUT: name - symbol name, | //| trade_operation - trade operation, | //| volume - volume of the opening position, | //| price_open - price of the opening position, | //| price_close - price of the closing position. | //+------------------------------------------------------------------+ 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); } */ /* //+------------------------------------------------------------------+ //| Access functions OrderCalcMargin(...). | //| INPUT: name - symbol name, | //| trade_operation - trade operation, | //| volume - volume of the opening position, | //| price - price of the opening position. | //+------------------------------------------------------------------+ 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); } */ /* //+------------------------------------------------------------------+ //| Access functions OrderCalcMargin(...). | //| INPUT: name - symbol name, | //| trade_operation - trade operation, | //| volume - volume of the opening position, | //| price - price of the opening position. | //+------------------------------------------------------------------+ 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)); } */ /* //+------------------------------------------------------------------+ //| Access functions OrderCalcMargin(...). | //| INPUT: name - symbol name, | //| trade_operation - trade operation, | //| price - price of the opening position, | //| percent - percent of available margin [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; //--- checks if(symbol=="" || price<=0.0 || percent<1 || percent>100) { Print("CAccountInfo::MaxLotCheck invalid parameters"); return(0.0); } //--- calculate margin requirements for 1 lot if(!OrderCalcMargin(trade_operation,symbol,1.0,price,margin) || margin<0.0) { Print("CAccountInfo::MaxLotCheck margin calculation failed"); return(0.0); } //--- if(margin==0.0) // for pending orders return(SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX)); //--- calculate maximum volume double volume=NormalizeDouble(FreeMargin()*percent/100.0/margin,2); //--- normalize and check limits 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 return(volume); } */ //+------------------------------------------------------------------+
Die Plattform kann immer nur auf ein Konto zugreifen. Die Verwaltung mehrerer Instanzen von CAccountInfo ist daher überflüssig.
CExpertTrade
Trotz desselben Endziels unterscheiden sich MQL4 und MQL5 in der Ausführung. Das bedeutet, der Cross-Plattform Expert Advisor kann für MQL5 die Klasse CExpertTrade als Ganzes verwenden, aber nicht für MQL4. Daher werden wir für MQL4 eine neue Headerdatei mit Klassen mit demselben Namen erstellen. Wir werden die Headerdatei der Basisklasse behalten, damit die richtige Headerdatei abhängig vom Kompiler verwendet wird, gemäß folgender Struktur:
Diese Klasse emuliert die meisten Mitglieder und Methoden aus CexpertTrade von MQL5, so dass wir den Code so ausführen können:
CExpertTrade trade;
trade.Buy(/*params*/);
Beide Versionen sollten eine Kauforder ausführen.
Es müssen mindestens die Methoden emuliert werden, die die Positionen auf der Plattform eröffnen. Das sind die Methoden für Kauf und Verkauf aus CExpertTrade. Für die Methode aber zum Schießen einer Position werden wir abweichen: OrderClose für MQL4 und die originalen Methoden PositionClose ("hedging") oder die Methoden Buy und Sell ("netting") für MQL5. Der folgende Code zeigt die MQL4-Version für CExpertTrade. Dies wäre einer der Teile, wo sich der Code stark von Programmierer zu Programmierer unterscheiden könnte:
#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 and 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 *); //-- trade methods 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; } //+------------------------------------------------------------------+
Beachten Sie, dass die Klasse CExpertTrade von der Klasse CTrade erbt (die dann CObject erweitert). Die MQL4 Version die Klasse CExpertTrade erbt aber direkt von CObject. Das bedeutet für die MQL4 Version, CTrade und CExpertTrade werden in einem einzigen Objekt zusammengeführt.
Einige Bezeichner wie ORDER_TYPE_TIME haben in MQL4 keine direkte Entsprechung. Die könnte jedoch helfen, wenn man die Handelsklasse erweitern will, um die Dauer einer Position einer MQL5-Order in MQL4 emulieren will.
CTradeManager
Die Originalklasse CExpertTrade aus MQL5 hat eine Methode namens SetSymbol. Diese Methode erlaubt es den Fokus auf die Informationsinstanz eines anderen Symbols zu wechseln (CSymbolInfo). Dadurch müssen die meisten Methoden der Klasse das Symbol nicht mehr als Zeichenkette speichern, zur Bezeichnung des bearbeiteten Symbols.
In den meisten Fällen genügt es, nur CExpertTrade zu verwenden und einfach zwischen den Symbolen zu wechseln. Es müssen aber ein paar Überlegungen dazu angestellt werden. In einigen Fällen müssten aber die Kursabweichungen oder der maximale Schlupf (slippage) aktualisiert werden, wenn das Symbol gewechselt wird. Insbesondere dann, wenn der Expert Advisor mit Symbolen mit unterschiedlichen Dezimalstellen arbeitet. Ein weiterer Faktor ist die "MagicNumber", die auch aktualisiert werden müsste, wenn ein Expert Advisor für jedes Handelssymbol eine eigene "MagicNumber" verwendet.
Eine Möglichkeit zur Lösung dieses Problems ist die Verwendung eines TradeManagers. Ähnlich dem SymbolManager von oben, würde dieses Objekt das CArrayObj erweitern und eigentlich die gleichen Methoden haben, wie der CSymbolManager. Es gäbe eine Basis Headerdatei, die die dem Kompiler entsprechenden Nachkommen verwendet. Und so wie der Symbolmanager verwaltet der TradeManager den Speicher und die Abfrage der Daten. Daher findet sich der größte Teil des Codes in der Basis-Headerdatei. Die Dateistruktur ist in der folgenden Abbildung dargestellt.
Der Code für die Basisklasse unseres TradeManagers ist unten dargestellt.
(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 //+------------------------------------------------------------------+
Man kann auch nur eine einzige Instanz von CExpertTrade verwenden und einfach erweitern, so würde sie dann ihre eigene Sammlung von Symbolen haben. Aber das würde zusätzlichen Speicherplatz benötigen, da auch die "MagicNumber" und Abweichungen gespeichert werden müssen mit einem Link zu dem bestimmten Symbol.
Wie der SymbolManager verwendet auch diese Klasse eine eigene Suchmethode, um zwei Handelsobjekte zu vergleichen (Handelsobjekte sollten eindeutig sein). Der Vergleich erfolgt über die Namen der TradeObjects, die auf den Namen der Instanz von CSymbolInfo, mit denen sie verlinkt sind, basieren. Wie auch immer, die MQL5 Version der CExpertTrade-Objekte liefert weder den Namen noch den Link des Symbols. Dafür müssen wir CExpertTrade erweitern, damit die Instanzen dieses Objektes den Symbolnamen zurückgeben können. Wir benennen das Objekt CExpertTradeX. So wie alle anderen Klassen dieses Artikels sollte es eine Basis Headerdatei geben, die entscheidet, welcher kompilerspezifische Header der Nachkommen verwendet werden soll:
Das Folgende zeigt die Umsetzung der beschriebenen Klasse:
(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 //+------------------------------------------------------------------+
Beachte die Klasse CExpertTradeX erweitert CExpertTrade. Es scheint, als ob dieses Objekt von einem einzelnen Objekt erbt. Tatsächlich aber haben die Versionen von MQL4 und MQL5 unterschiedliche CExpertTrade. Das ist ein ziemlich einfaches Objekt, und dessen Methoden sind mit beiden Plattformen kompatibel, daher, wie bei anderen Klassen, deklarieren wir die kompilerspezifischen Klassen ohne weitere Methoden:
(CExpertTradeX.mqh, MQL4 and MQL5 version)
class CExpertTradeX : public CExpertTradeXBase { public: CExpertTradeX(void); ~CExpertTradeX(void); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpertTradeX::CExpertTradeX(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpertTradeX::~CExpertTradeX(void) { } //+------------------------------------------------------------------+
Schlussfolgerung
In diesem Artikel haben wir gezeigt, wie einige native Komponenten der Standardbibliothek von MQL5 möglicherweise so umstrukturiert werden können, dass sie von einem MQL4 Expert Advisor verwendet werden können, um nicht alles komplett neu schreiben zu müssen. Die Klassen CSymbolInfo, CAccount und CExpertTrade wurden geändert, Manager wie CSymbolManager und CTradeManager wurden entwickelt, die es einem Expert Advisor oder Skript erlauben, viele Instanzen der genannten Objekte (CSymbolInfo, CAccount, and CExpertTrade) zu kontrollieren.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/2574
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.