Die Verwendung von MetaTrader5 als Signalgeber für MetaTrader4

Karlis Balcers | 4 Mai, 2016


Einleitung

Es gibt viele Gründe, warum ich mich dazu entschied, diesen Artikel zu schreiben und zu untersuchen, ob die Fragestellung Sinn macht.

Erstens ist MetaTrader 5 bereits länger veröffentlicht, aber wir warten alle immer noch darauf, dass unsere beliebtesten Makler uns erlauben, wirklich zu handeln. Einige haben Strategien mit MQL5 gemacht und sind mit der Performance zufrieden, weswegen sie sie nun mit echten Accounts ausprobieren wollen. Anderen gefällt möglicherweise die Handelsstruktur, und sie wollen manuell handeln, aber eher mit MetaTrader5 anstatt MetaTrader4.

Zweitens, während der Automated Trading Championship wollte jeder mit seinem eigenen Account Maklern folgen. Einige haben ihre eigenen Wege gefunden, den Abschlüssen zu folgen, aber einige wissen noch nicht, wie man die bestmöglichen Ergebnisse erzielt oder das gleiche System verwendet, wie die Trader in der Championship.

Drittens haben einige Leute gute Strategien und wollen diese nicht nur für sich selbst nutzen, sondern auch ihren Freunden und anderen zur Verfügung stellen. Sie brauchen die Möglichkeit, mehrere Verbindungen zu akzeptieren, ohne Leistung zu verlieren und Signale in Echtzeit zu verteilen.

Dies sind die Fragen, die ich ständig in meinem Kopf hatte, und ich versuche nun, hier eine Lösung zu finden, die diese Auflagen erfüllt.

1. Wie man die MQL5-Championship verfolgen kann?

Vor Kurzem fand ich mehrere Artikel in der MQL5-Community, die mir nicht so schwer vorkamen und bei denen ich mir dachte, dass ich das auch bauen kann. Ich habe auch mit einer Anwendung die Aktivitäten auf der Championship-Homepage verfolgt und mit meinem realen Account gehandelt (mit Gewinn, zum Glück). Das Problem war, dass sich die Daten alle 5 Minuten aktualisieren und man den richtigen Moment für das Öffnen und Schließen von Positionen verpassen kann.

Im Championship-Forum habe ich gelesen, dass andere Leute das gleiche machten und dass es nicht effektiv ist, der Homepage großen User-Verkehr einbringt und die Organisatoren es nicht mögen. Gibt es eine Lösung? Ich habe mir alle Lösungen angeschaut, und ich mag die Möglichkeit, auf den Account jedes Teilnehmers im 'Investor'-Modus (Handeln nicht möglich) zuzugreifen mit MetaTrader5.

Können wir das verwenden, um über jede Handelsaktivität in Echtzeit upgedatet zu werden und sie in Echtzeit zu transferieren? Um das herauszufinden, habe ich den Expert Advisor erstellt und versuchte, ihn auf dem Account laufen zu lassen, der nur den 'Investor'-Modus hatte. Ich war überrascht, als es klappte, und es war auch möglich, Informationen über Positionen, Bestellungen (im Skript: "order") und Deals zu bekommen - der Weg zu möglichen Lösungen war gefunden!


2. Positionen, Bestellungen oder Deals folgen?

Wenn wir Informationen von MetaTrader5 zu MetaTrader4 transferieren, müssen wir an alle Bestellarten denken, die in MetaTrader4 möglich waren. Außerdem möchten wir über jede mögliche Aktion im Account, die mit Handeln zu tun hat, informiert werden, wenn wir jemandem folgen. Also wird uns die "Position" keine vollständigen Informationen liefern, wenn wir nicht ihren Status jede Sekunde überprüfen.

Deswegen ist es besser, "Bestellungen (Orders)" oder "Deals" zu folgen.

Ich fing mit "Bestellungen" an:

Bestellung

Ich fand es gut, dass sie ausgeführt werden, bevor "Deal" ausgeführt wird und auch, dass sie Informationen über ausstehende Bestellungen haben, aber eine wichtige Sache fehlt im Vergleich mit "Deals": der Eintragstyp (ENUM_DEAL_ENTRY):

Deals

DEAL_ENTRY_TYPE hilft zu verstehen, was im Traders Account passiert ist, während "Orders" eine gleichzeitige Kalkulation benötigt. Am besten wäre es, "Deals" mit "Orders" zusammenzulegen, dann hätten wir ausstehende Bestellungen und könnten auch jeder Aktion im Handelsaccount folgen. Da sich Preis-Bewegungen bei verschiedenen Maklerunternehmen unterscheiden, könnten ausstehende Bestellungen zu Fehlern und falschen Ergebnissen führen.

Wenn wir nur 'Deals' folgen, können wir immer noch ausstehende Bestellungen ausführen, aber mit einer kurzen Verzögerung (je nach Netzwerkverbindung). Bei der Wahl zwischen Geschwindigkeit (ausstehende Bestellungen) und Performance (Deals) habe ich mich für Performance entschieden.


3. Wie "Signale" bereitgestellt werden

Es gibt mehrere Artikel und Diskussionen über die Kommunikation und den Datentransfer von MetaTrader5 zu anderen Anwendungen und PCs. Andere Clients sollen dazu in der Lage sein, sich mit uns zu verbinden, doch wahrscheinlich sind sie auf anderen PCs, deswegen habe ich mich für die TCP-Verbindung entschieden.

Da MQL5 uns nicht erlaubt, es mit API-Funktionen zu machen, müssen wir die externe Bibliothek benutzen. Es gibt verschiedene Artikel über die Einbeziehung der "WinInet.dll"-Bibliothek (z. B. "Using WinInet.dll for Data Exchange between Terminals via the Internet (Die Verwendung von WinInet.dll für den Datanaustausch zwischen Terminals duch das Internet)" und andere), aber keiner von ihnen hat, was wir brauchen.

Da ich mich ein bisschen mit C# auskenne, habe ich beschlossen, meine eigene Bibliothek zu entwerfen. Dafür hat mir der Artikel "Exposing C# code to MQL5 using unmanaged exports (Mit unbeaufsichtigten Exporten C#-Code nach MQL5 bringen)" bei Kompatibilitätsproblemen geholfen. Ich habe einen Server mit einer sehr einfach Benutzerschnittstelle entworfen, der bis zu 500 Clients in der gleichen Zeit akzeptieren kann (dafür ist ein .NET framework 3.5 oder neuer auf Ihrem PC notwendig). Dies ist in den meisten PCs bereits installiert. "Microsoft .NET Framework 3.5").

#import "SocketServer.dll"    // Library created on C# (created by using information available on https://www.mql5.com/en/articles/249)
string About();            // Information about library.
int SendToAll(string msg);  // Sends one text message to all clients.
bool Stop();               // Stops the server.
bool StartListen(int port); // Starts the server. Server will listen from incomming connections (max 500 clients). 
                               // All clients are built on Assync threads.
string ReadLogLine();       // Retrieve one log line from server (can contain erros and other information). 
                               // Reading is optional. Server stores only last 100 lines.
#import

Der Server selbst läuft im Hintergrund auf verschiedenen Threads und wird unsere Arbeit mit MetaTrader5 weder blockieren noch verlangsamen, egal wie viele Clients verbunden sind.

C# Quellcode: 

         internal static void WaitForClients()
        {
            if (server != null)
            {
                Debug("Cant start lisening! Server not disposed.");
                return;
            }
            try
            {

                IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, iPort);
                server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                server.Bind(localEndPoint);
                server.Listen(500);
                isServerClosed = false;
                isServerClosedOrClosing = false;

                while (!isServerClosedOrClosing)
                {
                    allDone.Reset();
                    server.BeginAccept(new AsyncCallback(AcceptCallback), server);
                    allDone.WaitOne();
                }
                
            }
            catch (ThreadAbortException)
            {
            }
            catch (Exception e)
            {
                Debug("WaitForClients() Error: " + e.Message);
            }
            finally
            {
                if (server != null)
                {
                    server.Close();
                    server = null;
                }
                isServerClosed = true;
                isServerClosedOrClosing = true;
            }
        }

        internal static void AcceptCallback(IAsyncResult ar)
        {
            try
            {
                allDone.Set();
                if (isServerClosedOrClosing)
                    return;
                Socket listener = (Socket)ar.AsyncState;
                Socket client = listener.EndAccept(ar);

                if (clients != null)
                {
                    lock (clients)
                    {
                        Array.Resize(ref clients, clients.Length + 1);
                        clients[clients.Length - 1].socket = client;
                        clients[clients.Length - 1].ip = client.RemoteEndPoint.ToString();
                        clients[clients.Length - 1].alive = true;
                    }
                    Debug("Client connected: " + clients[clients.Length - 1].ip);
                }
            }
            catch (Exception ex)
            {
                Debug("AcceptCallback() Error: " + ex.Message);
            }
        }

Um mehr über Asynchrone Server Anschlüsse in C# zu erfahren, schlage ich vor, dass Sie Microsoft MSDN lesen, oder es einfach googlen.

4. Wie "Signale" gesammelt werden?

Auf MetaTrader4 würden wir gerne immer Informationen bekommen, und nicht nur, wenn neue Ticks generiert werden, darum können wir ein "Skript" erstellen, statt eines Expert Advisors. Außerdem müssen wir auch fähig sein, die Verbindung zu unserem Signalgeber zu öffnen - MetaTrader5.

Dafür bekomme ich Hilfe von der MQL4-Codebase: "https://www.mql5.com/en/code/9296". Dort habe ich eine gute Datei gefunden (WinSock.mqh) die eine einfache Arbeit mit Verbindungen ermöglicht. Obwohl sich manche Leute über Stabilität beschwert haben, fand ich es ausreichend für meine Zwecke und habe während des Tests keine Probleme gefunden.

#include <winsock.mqh>  // Downloaded from MQ4 homepage
                     // DOWNLOAD:   http://codebase.mql4.com/download/18644
                     // ARTICLE:    http://codebase.mql4.com/6122


5. Datenverarbeitung

Nun haben wir unsere Vorgehensweise und müssen nur mehr sicher stellen, dass Deals nacheinander verarbeitet und in einem für sie kompatiblen Format zu allen Clients übertragen werden.

5.1. Von Seiten des Servers

Es ist nicht wichtig, auf welche Währung der Expert Advisor eingestellt ist.

Während des Starts wird auch der Thread gestartet, der für einkommende Verbindungen wartet.

int OnInit()
  {
   string str="";
   Print(UTF8_to_ASCII(About()));
//--- start the server
   Print("Starting server on port ",InpPort,"...");
   if(!StartListen(InpPort))
     {
      PrintLogs();
      Print("OnInit() - FAILED");
      return -1;
     }

In dieser Version wird der Expert Advisor sich nicht um verbundene Clients kümmern. Jedes Mal, wenn ein Handel zustande kommt, wird er alle Clients informieren, auch wenn es keine gibt. Da wir alles über Handel wissen müssen, werden wir die Funktion OnTrade() verwenden und OnTick() entfernen. In dieser Funktion schauen wir uns den neuesten Verlauf an und entscheiden, ob es ein Deal ist, über den wir mehr wissen wollen oder nicht.

Sehen Sie sich hier meine Kommentare im Code an, um es besser zu verstehen.

//+------------------------------------------------------------------+
//| OnTrade() - every time when there is an activity related to      |
//|             traiding.                                            |
//+------------------------------------------------------------------+
void OnTrade()
  {
//--- find all new deals and report them to all connected clients
//--- 24 hours back.
   datetime dtStart=TimeCurrent()-60*60*24;
//--- 24 hours front (in case if you live in GMT-<hours>)
   datetime dtEnd=TimeCurrent()+60*60*24;
//--- select history from last 24 hours.
   if(HistorySelect(dtStart,dtEnd))
     {
      //--- go through all deals (from oldest to newest).
      for(int i=0;i<HistoryDealsTotal();i++)
        {
         //--- get deal ticket.
         ulong ticket=HistoryDealGetTicket(i);
         //--- if this deal is interesting for us.
         if(HistoryDealGetInteger(ticket,DEAL_ENTRY)!=DEAL_ENTRY_STATE)
           {
            //Print("Entry type ok.");
            //--- check if this deal is newer than previously reported one.
            if(HistoryDealGetInteger(ticket,DEAL_TIME)>g_dtLastDealTime)
              {
               //--- if some part of position has been closed then check if we need to enable it
               if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_OUT)
                 {
                  vUpdateEnabledSymbols();
                 }
               //--- if opposite position is opened, then we need to enable disabled symbol.
               else if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_INOUT)
                 {
                  //--- enable this specific symbol.
                  vEnableSymbol(HistoryDealGetString(ticket,DEAL_SYMBOL));
                 }
               //--- check if symbol is enabled.
               if(bIsThisSymbolEnabled(HistoryDealGetString(ticket,DEAL_SYMBOL)))
                 {
                  //--- build deal-string and send to all connected clients
                  int cnt=SendToAll(sBuildDealString(ticket));
                  //--- technical error with server.
                  if(cnt<0)
                    {
                     Print("Failed to send new deals!");
                    }
                  //--- if sent to no one (cnt==0) or if sent to someone (cnt>0)                  
                  else
                    {
                     //--- update datetime for last sucessfully transfered deal
                     g_dtLastDealTime=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME);
                    }
                 }
               //--- do not notify becayse symbol is disabled.
               else
                 {
                  //--- update datetime for last deal, we will not notify about.
                  g_dtLastDealTime=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME);
                 }
              }
           }
        }
     }
  }

Wie Sie gesehen haben, wenn es einen neuen Deal gibt, rufen wir die Funktion BuildDealString() auf, um Daten für den Transfer vorzubereiten. Alle Daten werden in Textformat übertragen und jeder Deal fängt mit '<' an und hört mit '>' auf.

Dies wird uns dabei helfen, mehrfache Deals zu trennen, da es möglich ist, mehr als einen Deal auf einmal zu bekommen, dank des TCP/IP-Protokolls.

//+------------------------------------------------------------------+
//| This function builds deal string                                 |
//| Examples:                                                        |
//| EURUSD;BUY;IN;0.01;1.37294                                       |
//| EURUSD;SELL;OUT;0.01;1.37310                                     |
//| EURUSD;SELL;IN;0.01;1.37320                                      |
//| EURUSD;BUY;INOUT;0.02;1.37294                                    |
//+------------------------------------------------------------------+
string sBuildDealString(ulong ticket)
  {
   string deal="";
   double volume=0;
   bool bFirstInOut=true;
//--- find deal volume.
//--- if this is INOUT then volume must contain ONLY volume of 'IN'.
   if(HistoryDealGetInteger(ticket,DEAL_ENTRY)==DEAL_ENTRY_INOUT)
     {
      if(PositionSelect(HistoryDealGetString(ticket,DEAL_SYMBOL)))
        {
         volume=PositionGetDouble(POSITION_VOLUME);
        }
      else
        {
         Print("Failed to get volume!");
        }
     }
//--- if it's 'IN' or 'OUT' deal then use it's volume as is.
   else
     {
      volume=HistoryDealGetDouble(ticket,DEAL_VOLUME);
     }
//--- build deal string(format sample: "<EURUSD;BUY;IN;0.01;1.37294>").
   int iDealEntry=(int)HistoryDealGetInteger(ticket,DEAL_ENTRY);
//--- if this is OUT deal, and there are no open positions left.
   if(iDealEntry==DEAL_ENTRY_OUT && !PositionSelect(HistoryDealGetString(ticket,DEAL_SYMBOL)))
     {
      //--- For safety reasons, we check if there is any position left with current symbol. If NO, then let's use 
      //--- new deal type - OUTALL. This will guarante that there are no open orders left on or account when all
      //--- position has been closed on 'remote' MetaTrader 5 side. This can happen due to fact, that volume is 
      //--- is mapped to new values on client side, therefor there can be some very small difference which leaves
      //--- order open with very small lot size. 
      iDealEntry=DEAL_ENTRY_OUTALL;  // My own predefined value (this value should not colide with EMUN_DEAL_ENTRY values).
     }
   StringConcatenate(deal,"<",AccountInfoInteger(ACCOUNT_LOGIN),";",
                   HistoryDealGetString(ticket,DEAL_SYMBOL),";",
                   Type2String((ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket,DEAL_TYPE)),";",
                   Entry2String(iDealEntry),";",DoubleToString(volume,2),";",
                      DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE),
                   (int)SymbolInfoInteger(HistoryDealGetString(ticket,DEAL_SYMBOL),SYMBOL_DIGITS)),">");
   Print("DEAL:",deal);
   return deal;
  }

Wenn Sie sich den Code anschauen, sind Sie vielleicht überrascht über den neuen DEAL_ENTRY-Typ - DEAL_ENTRY_OUTALL. Ich habe ihn entworfen, und Sie werden ihn besser verstehen, wenn ich Volume-Handling in MetaTrader4 erklären werde.

Die Funktion OnTimer() könnte auch noch interessant sein. Während der Initialisierung gebe ich den Befehl EventSetTimer(1), um OnTimer() jede Sekunde zu laufen. Innerhalb dieser Funktion ist eine Zeile, die Information (logs) von der Server-Bibliothek ausgibt.

//+------------------------------------------------------------------+
//| Print logs from Server every second (if there are any)           |
//+------------------------------------------------------------------+
void OnTimer()
  {
   PrintLogs();
  }

Rufen Sie diese Funktion (PrintLogs) nach jeder Funktion auf, die Sie von der Server-Bibliothek ausführen, um Status- und Fehler-Informationen auszugeben.

Im Server finden Sie auch den Input-Parameter "StartupType":

enum ENUM_STARTUP_TYPE
  {
   STARTUP_TYPE_CLEAR,    // CLEAR - Send every new DEAL wich appears on account.
   STARTUP_TYPE_CONTINUE  // CONTINUE - Do not send DEAL before existing POSITION has been closed.
  };
//--- input parameters
input ENUM_STARTUP_TYPE InpStartupType=STARTUP_TYPE_CONTINUE; // Startup type

Der Signalgeber kann auf Accounts, die bereits geöffnete Positionen haben, hinzugefügt werden (z.B. wenn der Championship gefolgt wird) und die Information kann daher für Clients irreführend sein. Mit diesen Parametern können Sie wählen, ob Sie Information von bereits bestehenden Abschlüssen haben wollen, oder nur von neu eröffneten Positionen.

Es ist auch wichtig, ob Sie sich mit diesem Account zum ersten Mal anmelden, oder ob Sie sich neu für einen Account anmelden, der schon einmal in Betrieb war, und Sie haben nur Ihren PC neu gestartet oder den Code geändert.

5.2. Client

Beim Client haben wir ein Skript in Endlosschleife in der Verbindungs-Empfangs-Funktion (rev). Setzen Sie diese Funktion auf "blockend", dann ist das Skript für eine Zeit lang blockiert, bis der Server etwas empfängt, sorgen Sie sich also nicht über Prozessoren-Zeit.

//--- server up and running. Start data collection and processing
   while(!IsStopped())
     {
      Print("Client: Waiting for DEAL...");
      ArrayInitialize(iBuffer,0);
      iRetVal=recv(iSocketHandle,iBuffer,ArraySize(iBuffer)<<2,0);
      if(iRetVal>0)
        {
         string sRawData=struct2str(iBuffer,iRetVal<<18);
         Print("Received("+iRetVal+"): "+sRawData);

Dies stoppt den Client. Wenn Sie nur einmal "Remove Script" klicken, wird es nicht funktionieren. Sie müssen zweimal klicken, dann wird das Skript durch time-out entfernt. Das könnte repariert werden, wenn time-out für Empfang-Funktionen angewandt werden kann, aber da ich ein Beispiel benutze, das bereits in der Codebase zur Verfügung steht, werde ich das dem Original-Autor überlassen.  

Wenn die Daten empfangen sind, werden wir aufteilen und verifizieren bevor der Deal im echten Account verarbeitet wird.

         //--- split records
         string arrDeals[];
         //--- split raw data in multiple deals (in case if more than one is received).
         int iDealsReceived=Split(sRawData,"<",10,arrDeals);
         Print("Found ",iDealsReceived," deal orders.");
         //--- process each record
         //--- go through all DEALs received
         for(int j=0;j<iDealsReceived;j++) 
           {
            //--- split each record to values
            string arrValues[];
            //--- split each DEAL in to values
            int iValuesInDeal=Split(arrDeals[j],";",10,arrValues);
            //--- verify if DEAL request received in correct format (with correct count of values)
            if(iValuesInDeal==6)
              {
                 if(ProcessOrderRaw(arrValues[0],arrValues[1],arrValues[2],
                                    arrValues[3],arrValues[4],
                                         StringSubstr(arrValues[5],0,StringLen(arrValues[5])-1)))
                 {
                  Print("Processing of order done sucessfully.");
                 }
               else
                 {
                  Print("Processing of order failed:\"",arrDeals[j],"\"");
                 }
              }
            else
              {
               Print("Invalid order received:\"",arrDeals[j],"\"");
               //--- this was last one in array
               if(j==iDealsReceived-1)
                 {
                  //--- it might be incompleate beginning of next deal.
                  sLeftOver=arrDeals[j];
                 }
              }
           }
//+------------------------------------------------------------------+
//| Processing received raw data (text format)                       |
//+------------------------------------------------------------------+
bool ProcessOrderRaw(string saccount,string ssymbol,string stype,string sentry,string svolume,string sprice)
  {
//--- clearing
   saccount= Trim(saccount);
   ssymbol = Trim(ssymbol);
   stype=Trim(stype);
   sentry=Trim(sentry);
   svolume= Trim(svolume);
   sprice = Trim(sprice);
//--- validations
   if(!ValidateAccountNumber(saccount)){Print("Invalid account:",saccount);return(false);}
   if(!ValidateSymbol(ssymbol)){Print("Invalid symbol:",ssymbol);return(false);}
   if(!ValidateType(stype)){Print("Invalid type:",stype);return(false);}
   if(!ValidateEntry(sentry)){Print("Invalid entry:",sentry);return(false);}
   if(!ValidateVolume(svolume)){Print("Invalid volume:",svolume);return(false);}
   if(!ValidatePrice(sprice)){Print("Invalid price:",sprice);return(false);}
//--- convertations
   int account=StrToInteger(saccount);
   string symbol=ssymbol;
   int type=String2Type(stype);
   int entry=String2Entry(sentry);
   double volume= GetLotSize(StrToDouble(svolume),symbol);
   double price = NormalizeDouble(StrToDouble(sprice),(int)MarketInfo(ssymbol,MODE_DIGITS));
   Print("DEAL[",account,"|",symbol,"|",Type2String(type),"|",
        Entry2String(entry),"|",volume,"|",price,"]");
//--- execution
   ProcessOrder(account,symbol,type,entry,volume,price);
   return(true);
  }

Da nicht jeder 10.000 Dollar auf seinem Account hat, wird die Neuberechnung der Los-Menge vom Client mit der Funktion GetLotSize() gemacht. Die Strategie, die auf Servern läuft, kann auch finanzielles Management beinhalten, deswegen müssen wir das gleiche auf Server-Seite machen.

Ich biete Ihnen eine "Los-Abbildung" - User eines Clients können die Vorlieben für Los-Mengen festlegen (Minimum und Maximum) und dann wird das Client Script die Abbildung für Sie machen:

extern string _1 = "--- LOT MAPPING ---";
extern double  InpMinLocalLotSize  =  0.01;
extern double  InpMaxLocalLotSize  =  1.00; // Recomended bigger than
extern double  InpMinRemoteLotSize =  0.01;
extern double  InpMaxRemoteLotSize =  15.00;
//+------------------------------------------------------------------+
//| Calculate lot size                                               |
//+------------------------------------------------------------------+
double GetLotSize(string remote_lots, string symbol)
{
   double dRemoteLots = StrToDouble(remote_lots);
   double dLocalLotDifference = InpMaxLocalLotSize - InpMinLocalLotSize;
   double dRemoteLotDifference = InpMaxRemoteLotSize - InpMinRemoteLotSize;
   double dLots = dLocalLotDifference * (dRemoteLots / dRemoteLotDifference);
   double dMinLotSize = MarketInfo(symbol, MODE_MINLOT); 
   if(dLots<dMinLotSize)
      dLots=dMinLotSize;
   return (NormalizeDouble(dLots,InpVolumePrecision));
}

Die Clients unterstützen 4 und 5 Digit Broker und haben auch "normale Lose" (0,1) und "Mini-Lose" (0,01). Deswegen musste ich neue DEAL_ENTRY-Typen erstellen - DEAL_OUTALL.

Da die Clients die Abbildungen machen, kann es zu einer Situation kommen, wenn kleine Lose offen gelassen werden.

void ProcessOrder(int account, string symbol, int type, int entry, double volume, double price)
{
   if(entry==OP_IN)
   {
      DealIN(symbol,type,volume,price,0,0,account);
   }
   else if(entry==OP_OUT)
   {
      DealOUT(symbol, type, volume, price, 0, 0,account);
   }
   else if(entry==OP_INOUT)
   {
      DealOUT_ALL(symbol, type, account);
      DealIN(symbol,type,volume,price,0,0,account);
   }
   else if(entry==OP_OUTALL)
   {
      DealOUT_ALL(symbol, type, account);
   }
}

5.3. MetaTrader5-Positionen vs MetaTrader4-Bestellungen

Während der Implementierung fand ich ein anderes Problem: in MetaTrader5 gibt es immer eine andere Position für jedes Symbol, während das in MetaTrader4 ganz anders gehandhabt wird. Um die beiden Systeme so gut wie möglich anzugleichen, öffne ich in MetaTrader4 "multiple orders".

Jeder neue "IN"-Deal ist eine neue Bestellung, und wenn es einen "OUT"-Deal gibt, implementiere ich eine Funktion, die Schritt 3 ausführt:

  1. Durch alle offenen Bestellungen gehen und jene schließen, die der angefragten Menge entsprechen, dann
  2. Durch alle offenen Bestellungen gehen und jene schließen, die kleiner sind als angefragt OUT Volume size; wenn noch etwas übrig ist, dann
  3. Alle Bestellungen schließen, deren Menge größer ist als angefragt und neue Bestellung öffnen mit Menge die offen bleiben sollte. Normalerweise wird der dritte Schritt nie ausgeführt. Erstellt für Schutzzwecke.
//+------------------------------------------------------------------+
//| Process DEAL ENTRY OUT                                           |
//+------------------------------------------------------------------+
void DealOUT(string symbol, int cmd, double volume, double price, double stoploss, double takeprofit, int account)
{
   int type = -1;
   int i=0;
   
   if(cmd==OP_SELL)
      type = OP_BUY;
   else if(cmd==OP_BUY)
      type = OP_SELL;  
   
   string comment = "OUT."+Type2String(cmd);
   //--- Search for orders with equal VOLUME size and with PROFIT > 0
   for(i=0;i<OrdersTotal();i++)
   {
      if(OrderSelect(i,SELECT_BY_POS))
      {
         if(OrderMagicNumber()==account)
         {
            if(OrderSymbol()==symbol)
            {
               if(OrderType()==type)
               {
                  if(OrderLots()==volume)
                  {
                     if(OrderProfit()>0)
                     {
                        if(CloseOneOrder(OrderTicket(), symbol, type, volume))
                        {
                           Print("Order with exact volume and profit>0 found and executed.");
                           return;
                        }
                     }
                  }
               }
            }
         }
      }
   }
   //--- Search for orders with equal VOLUME size and with ANY profit size
   for(i=0;i<OrdersTotal();i++)
   {
      if(OrderSelect(i,SELECT_BY_POS))
      {
         if(OrderMagicNumber()==account)
         {
            if(OrderSymbol()==symbol)
            {
               if(OrderType()==type)
               {
                  if(OrderLots()==volume)
                  {
                     if(CloseOneOrder(OrderTicket(), symbol, type, volume))
                     {
                        Print("Order with exact volume found and executed.");
                        return;
                     }
                  }
               }
            }
         }
      }
   }
   double volume_to_clear = volume;
   //--- Search for orders with smaller volume AND with PROFIT > 0
   int limit = OrdersTotal();
   for(i=0;i<limit;i++)
   {
      if(OrderSelect(i,SELECT_BY_POS))
      {
         if(OrderMagicNumber()==account)
         {
            if(OrderSymbol()==symbol)
            {
               if(OrderType()==type)
               {
                  if(OrderLots()<=volume_to_clear)
                  {
                     if(OrderProfit()>0)
                     {
                        if(CloseOneOrder(OrderTicket(), symbol, type, OrderLots()))
                        {
                           Print("Order with smaller volume and profit>0 found and executed.");
                           volume_to_clear-=OrderLots();
                           if(volume_to_clear==0)
                           {
                              Print("All necessary volume is closed.");
                              return;
                           }
                           limit = OrdersTotal();
                           i = -1; // Because it will be increased at end of cycle and will have value 0.
                        }
                     }
                  }
               }
            }
         }
      }
   }
   //--- Search for orders with smaller volume
   limit = OrdersTotal();
   for(i=0;i<limit;i++)
   {
      if(OrderSelect(i,SELECT_BY_POS))
      {
         if(OrderMagicNumber()==account)
         {
            if(OrderSymbol()==symbol)
            {
               if(OrderType()==type)
               {
                  if(OrderLots()<=volume_to_clear)
                  {
                     if(CloseOneOrder(OrderTicket(), symbol, type, OrderLots()))
                     {
                        Print("Order with smaller volume found and executed.");
                        volume_to_clear-=OrderLots();
                        if(volume_to_clear==0)
                        {
                           Print("All necessary volume is closed.");
                           return;
                        }
                        limit = OrdersTotal();
                        i = -1; // Because it will be increased at end of cycle and will have value 0.
                     }
                  }
               }
            }
         }
      }
   }
   //--- Search for orders with higher volume
   for(i=0;i<OrdersTotal();i++)
   {
      if(OrderSelect(i,SELECT_BY_POS))
      {
         if(OrderMagicNumber()==account)
         {
            if(OrderSymbol()==symbol)
            {
               if(OrderType()==type)
               {
                  if(OrderLots()>=volume_to_clear)
                  {
                     if(CloseOneOrder(OrderTicket(), symbol, type, OrderLots()))
                     {
                        Print("Order with smaller volume found and executed.");
                        volume_to_clear-=OrderLots();
                        if(volume_to_clear<0)//Closed too much
                        {
                           //Open new to compensate lose
                           DealIN(symbol,type,volume_to_clear,price,OrderStopLoss(),OrderTakeProfit(),account);
                        }
                        else if(volume_to_clear==0)
                        {
                           Print("All necessary volume is closed.");
                           return;
                        }
                     }
                  }
               }
            }
         }
      }
   }
   if(volume_to_clear!=0)
   {
      Print("Some volume left unclosed: ",volume_to_clear);
   }
}

Fazit

Daten, die hier gemacht und angehängt werden, können definitiv durch bessere Client-Server-Protokolle verbessert werden, genauso wie durch schlaue Kommunikation und bessere Ausführung. Aber meine Aufgabe war es, herauszufinden, ob es möglich ist, und es in akzeptabler Qualität zu bauen, damit jeder es privat nutzen kann.

Es funktioniert gut genug, um seine eigenen Strategien und die Strategien der Teilnehmer der MQL5-Championship zu verfolgen. Performance und Möglichkeiten, die von MQL4 und MQL5 geboten werden, sind gut genug, um es professionell oder kommerziell zu nutzen. Ich glaube, dass es möglich ist, einen sehr guten Signalgeber für alle MetaTrader4 und MetaTrader5-Clients zu kreieren, indem einfach Ihr eigener PC benutzt wird und Ihre eigene Strategie.

Ich sehe es auch gerne, wenn Leute den Code, den ich hierher gestellt habe, verbessern und würde gerne ihre Meinungen und Empfehlungen dazu hören. Ich werde auch versuchen, etwaige Fragen zu beantworten. Zur gleichen Zeit mache ich gerade einen Test bei dem ich meinen liebsten Championship-Teilnehmern folge. Er läuft nun seit einigen Wochen. Sollte ich Probleme finden, werde ich Sie hier auf dem Laufenden halten.

Tsaktuo

Beachten Sie, dass Sie volle Verantwortung für jeglichen Verlust oder Schaden übernehmen, wenn Sie die beschriebenen Funktionen und ausführenden Programme durchführen. Handeln Sie mit einem echten Account NUR nach einem qualitativen Test und NUR mit einem kompetenten Verständnis über die hier gebotenen Funktionalitäten.