English Русский 中文 Español 日本語 Português
Integration von MetaTrader 5 und Python: Daten senden und empfangen

Integration von MetaTrader 5 und Python: Daten senden und empfangen

MetaTrader 5Handel | 8 Mai 2019, 11:28
3 705 0
Maxim Dmitrievsky
Maxim Dmitrievsky

Warum MQL5 und Python integrieren?

Eine umfassende Datenverarbeitung erfordert umfangreiche Werkzeuge und geht oft über den Sandkasten (Sandbox) einer einzigen Anwendung hinaus. Für die Verarbeitung und Analyse von Daten, Statistiken und maschinellem Lernen werden spezielle Programmiersprachen verwendet. Eine der führenden Programmiersprachen für die Datenverarbeitung ist Python. Eine sehr effektive Lösung ist die Nutzung der Leistungsfähigkeit der Sprache und der enthaltenen Bibliotheken für die Entwicklung von Handelssystemen.

Es gibt verschiedene Lösungen, um das Zusammenspiel von zwei oder mehr Programmen zu realisieren. Sockets sind eine der schnellsten und flexibelsten Lösungen.

Ein Netzwerk-Socket ist ein Endpunkt der Prozesskommunikation über ein Computernetzwerk. Die Standardbibliothek von MQL5 enthält eine Gruppe von Socket-Funktionen, die eine Low-Level-Schnittstelle für die Arbeit im Internet bieten. Dies ist eine gemeinsame Schnittstelle verschiedenster Programmiersprachen, da sie Systemaufrufe auf Betriebssystemebene verwendet.

Der Datenaustausch zwischen den Preisen erfolgt über TCP/IP (Transmission Control Protocol/Internet Protocol). So können sich Prozesse innerhalb eines einzelnen Computers und über ein lokales Netzwerk oder das Internet gegenseitig beeinflussen.

Um eine Verbindung herzustellen, ist es notwendig, einen TCP-Server zu erstellen und zu initialisieren, mit dem sich der Client-Prozess verbinden wird. Ist das Zusammenspiel der Prozesse abgeschlossen, muss die Verbindung zwangsweise geschlossen werden. Daten in einem TCP-Austausch sind ein Stream von Bytes.

Wenn wir einen Server erstellen, müssen wir einen Socket einem oder mehreren Hosts (IP-Adressen) und einem ungenutzten Port zuordnen. Wenn die Liste der Hosts nicht gesetzt ist oder als "0.0.0.0" angegeben wird, hört der Socket auf alle Hosts. Wenn Sie "127.0.0.0.1" oder "localhost" angeben, ist die Verbindung nur innerhalb der "internen Schleife", d.h. nur innerhalb eines Computers möglich.

Da in MQL5 nur der Client verfügbar ist, werden wir in Python einen Server erstellen.


Erstellen eines Socket-Servers in Python

Der Zweck des Artikels ist es nicht, die Grundlagen der Python-Programmierung zu vermitteln. Es wird daher davon ausgegangen, dass der Leser mit dieser Sprache vertraut ist. 

Wir werden die Version 3.7.2 und das integrierte Socket-Paket verwenden. Bitte lesen Sie die zugehörige Dokumentation für weitere Details.

Wir werden ein einfaches Programm schreiben, das einen Socket-Server erstellt und die benötigten Informationen vom Client (das MQL5-Programm) übernimmt, sie bearbeitet und das Ergebnis zurückschickt. Dies scheint die effizienteste Interaktionsmethode zu sein. Angenommen, wir müssten eine maschinelle Lernbibliothek verwenden, wie z.B. scikit learn, die die lineare Regression von Preisen berechnet und Koordinaten zurückschickt, mit denen eine Linie im MetaTrader 5 Terminal gezeichnet werden soll. Dies ist ein einfaches Beispiel. Diese Interaktion kann aber auch zum Trainieren eines neuronalen Netzwerks, zum Senden von Daten (Kurse) vom Terminal, zum Lernen und Zurückgeben des Ergebnisses an das Terminal verwendet werden.

Lassen Sie uns das Programm socketserver.py erstellen und die oben beschriebenen Bibliotheken importieren:

import socket, numpy as np
from sklearn.linear_model import LinearRegression

Nun können wir mit dem Erstellen einer Klasse fortfahren, die für den Socket verantwortlich ist:

class socketserver:
    def __init__(self, address = '', port = 9090):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.address = address
        self.port = port
        self.sock.bind((self.address, self.port))
        self.cummdata = ''
        
    def recvmsg(self):
        self.sock.listen(1)
        self.conn, self.addr = self.sock.accept()
        print('connected to', self.addr)
        self.cummdata = ''

        while True:
            data = self.conn.recv(10000)
            self.cummdata+=data.decode("utf-8")
            if not data:
                break    
            self.conn.send(bytes(calcregr(self.cummdata), "utf-8"))
            return self.cummdata
            
    def __del__(self):
        self.sock.close()

Beim Erstellen eines Klassenobjekts erhält der Konstruktor den Hostnamen (IP-Adresse) und die Portnummer. Dann wird das Objekt sock erstellt, das der Adresse und dem Port sock.bind() zugeordnet ist.

Die Methode recvmsg wartet auf die eingehende Verbindung: sock.listen(1). Wenn eine eingehende Client-Verbindung ankommt, akzeptiert der Server diese: self.sock.accept().

Dann wartet der Server in einer Endlosschleife auf eine eingehende Client-Nachricht, die als Byte-Stream ankommt. Da die Nachrichtenlänge nicht im Voraus bekannt ist, erhält der Server diese Nachricht in Teilen, z.B. immer wieder 1 Kbyte, bis die gesamte Nachricht angekommen ist: self.conn.recv(10000). Empfangene Daten werden in eine Zeichenkette data.decode("utf-8") umgewandelt und zum Rest der Zeichenkette summdata hinzugefügt.

Nachdem alle Daten empfangen wurden (if not data:), sendet der Server an den Client eine Zeichenkette, die die äußersten rechten und linken Koordinaten der berechneten Regressionslinie enthält. Die Zeichenkette wird vorübergehend in ein Byte-Array conn.send(bytes(calcregr(self.cummdata), "utf-8")) umgewandelt.

Am Ende gibt die Methode die vom Client empfangene Zeichenkette zurück. Sie kann unter anderem zur Visualisierung von erhaltenen Kursen verwendet werden.

Ein Destruktor schließt den Socket, sobald die Ausführung des Python-Programms abgeschlossen ist.

Bitte beachten Sie, dass dies nicht die einzige mögliche Realisierung der Klasse ist. Alternativ können Sie die Methoden zum Empfangen und Senden von Nachrichten trennen und zu verschiedenen Zeitpunkten unterschiedlich verwenden. Ich habe nur die grundlegende Technologie zum Erstellen einer Verbindung beschrieben. Sie können Ihre eigenen Lösungen implementieren.

Lassen Sie uns die lernende Methode der linearen Regression innerhalb der aktuellen Implementierung genauer betrachten:

def calcregr(msg = ''):
    chartdata = np.fromstring(msg, dtype=float, sep= ' ') 
    Y = np.array(chartdata).reshape(-1,1)
    X = np.array(np.arange(len(chartdata))).reshape(-1,1)
        
    lr = LinearRegression()
    lr.fit(X, Y)
    Y_pred = lr.predict(X)
    type(Y_pred)
    P = Y_pred.astype(str).item(-1) + ' ' + Y_pred.astype(str).item(0)
    print(P)
    return str(P)

Der empfangene Bytestrom wird in einen utf-8-Zeichenkette umgewandelt, die dann von der Methode calcregr(msg = ' '') akzeptiert wird. Da die Zeichenkette eine Folge von Preisen enthält, die durch Leerzeichen getrennt sind (wie im Client implementiert), wird sie in ein NumPy-Array vom Typ float umgewandelt. Danach wird das Preisarray in eine Spalte umgewandelt (das Datenempfangsformat ist sclearn) Y = np.array(chartdata).reshape(-1,1). Der Prädiktor für das Modell ist die lineare Zeit (eine Folge von Werten; ihre Größe entspricht der Länge der Trainingsprobe) X = np.array(np.arange(len(chartdata)))).reshape(-1,1)

Es folgt das Training und die Modellvorhersage, während der erste und letzte Wert der Zeile (die Kanten des Segments) in die Variable "P" geschrieben, in eine Zeichenkette umgewandelt und in Form von Bytes an den Client übergeben werden.

Jetzt müssen wir nur noch das Klassenobjekt erstellen und die Methode recvmsg() in einer Schleife aufrufen:

serv = socketserver('127.0.0.1', 9090)

while True:  
    msg = serv.recvmsg()


Erstellen eines Socket-Clients in MQL5

Lassen Sie uns einen einfachen Expert Advisor erstellen, der sich mit dem Server verbinden kann, die angegebene Anzahl der letzten Schlusskurse übergibt, die Koordinaten der Regressionslinie zurückholt und sie auf das Chart zeichnet. 

Die Funktion socksend() übergibt Daten an den Server:

bool socksend(int sock,string request) 
  {
   char req[];
   int  len=StringToCharArray(request,req)-1;
   if(len<0) return(false);
   return(SocketSend(sock,req,len)==len); 
  }

Sie erhält die Zeichenkette, konvertiert sie in ein Byte-Array und sendet sie an einen Server.

Die Funktion socketreceive() überwacht den Port. Sobald eine Serverantwort empfangen wurde, gibt die Funktion sie als Zeichenkette zurück:

string socketreceive(int sock,int timeout)
  {
   char rsp[];
   string result="";
   uint len;
   uint timeout_check=GetTickCount()+timeout;
   do
     {
      len=SocketIsReadable(sock);
      if(len)
        {
         int rsp_len;
         rsp_len=SocketRead(sock,rsp,len,timeout);
         if(rsp_len>0) 
           {
            result+=CharArrayToString(rsp,0,rsp_len); 
           }
        }
     }
   while((GetTickCount()<timeout_check) && !IsStopped());
   return result;
  }

Die letzte Funktion drawlr() erhält eine Zeichenkette, in die die Koordinaten der linken und rechten Linie geschrieben wurden, wandelt die Zeichenkette dann in ein Zeichenkettenarray um und zeichnet die lineare Regressionslinie auf ein Chart:

void drawlr(string points) 
  {
   string res[];
   StringSplit(points,' ',res);

   if(ArraySize(res)==2) 
     {
      Print(StringToDouble(res[0]));
      Print(StringToDouble(res[1]));
      datetime temp[];
      CopyTime(Symbol(),Period(),TimeCurrent(),lrlenght,temp);
      ObjectCreate(0,"regrline",OBJ_TREND,0,TimeCurrent(),NormalizeDouble(StringToDouble(res[0]),_Digits),temp[0],NormalizeDouble(StringToDouble(res[1]),_Digits)); 
     }
  

Die Funktion wird in der Funktion OnTick() aufgerufen.

void OnTick() {
 socket=SocketCreate();
 if(socket!=INVALID_HANDLE) {
  if(SocketConnect(socket,"localhost",9090,1000)) {
   Print("Connected to "," localhost",":",9090);
         
   double clpr[];
   int copyed = CopyClose(_Symbol,PERIOD_CURRENT,0,lrlenght,clpr);
         
   string tosend;
   for(int i=0;i<ArraySize(clpr);i++) tosend+=(string)clpr[i]+" ";       
   string received = socksend(socket, tosend) ? socketreceive(socket, 10) : ""; 
   drawlr(recieved); }
   
  else Print("Connection ","localhost",":",9090," error ",GetLastError());
  SocketClose(socket); }
 else Print("Socket creation error ",GetLastError()); }

Testen der MQL5-Python Client-Server Anwendung

Um die Anwendung auszuführen, müssen Sie den Python-Interpreter installiert haben. Sie können sie von der offiziellen Website herunterladen.

Führen Sie dann die Serveranwendung socketserver.py aus. Sie erstellt einen Socket und wartet auf neue Verbindungen aus dem MQL5-Programm socketclientEA.mq5.

Nach einer erfolgreichen Verbindung werden der Verbindungsvorgang und die Preise der Regressionslinie im Programmfenster angezeigt. Die Preise werden an den Client zurückgeschickt:



Die Verbindungsaktivität und die Preise der Regressionslinie werden auch im MetaTrader 5 Terminal angezeigt. Die Regressionslinie wird auch auf dem Chart dargestellt und bei jedem neuen Tick erneut aktualisiert:

Wir haben die Implementierung der direkten Interaktion von zwei Programmen über eine Socket-Verbindung besprochen. Gleichzeitig hat MetaQuotes ein Python-Paket entwickelt, mit dem Daten direkt vom Terminal empfangen werden können. Für weitere Details lesen Sie bitte die Forumsdiskussion im Zusammenhang mit der Verwendung von Python in MetaTrader (auf Russisch, verwenden Sie daher die automatische Übersetzungsoption).

Lassen Sie uns ein Skript erstellen, das zeigt, wie man Kurse vom Terminal erhält.

Erhalten und Analysieren von Kursen mit dem MetaTrader 5 Python API

Zuerst müssen Sie das MetaTrader5 Python-Modul installieren (die Zusammenfassung der Pythondiskussionen ist hier verfügbar). 

pip install MetaTrader5

Importieren Sie es in das Programm und initialisieren Sie die Verbindung zum Terminal:

from MetaTrader5 import *
from datetime import date
import pandas as pd 
import matplotlib.pyplot as plt 

# Initialisieren der MT5-Verbindung 
MT5Initialize()
MT5WaitForTerminal()

print(MT5TerminalInfo())
print(MT5Version())

Danach erstellen Sie die Liste der gewünschten Symbole und fordern nacheinander die Schlusskurse für jedes Währungspaar vom Terminal zum Pandas-Datenrahmen an:

# Erstellen einer Beobachtungsliste von Währungen für die die Korrelationsmatrix gezeichnet werden soll
sym = ['EURUSD','GBPUSD','USDJPY','USDCHF','AUDUSD','GBPJPY']

# Kopieren des Datenrahmens
d = pd.DataFrame()
for i in sym:
     rates = MT5CopyRatesFromPos(i, MT5_TIMEFRAME_M1, 0, 1000)
     d[i] = [y.close for y in rates]

Jetzt können wir die Verbindung zum Terminal trennen und dann die Preise der Währungspaare als prozentuale Veränderungen darstellen, indem wir die Korrelationsmatrix berechnen und auf dem Bildschirm anzeigen:

# Deinitialisieren der MT5-Verbindung
MT5Shutdown()

# Berechnen der prozentualen Veränderung
rets = d.pct_change()

# Berechnen der Korrelation
corr = rets.corr()

# Zeichnen der Korrelationsmatrix
plt.figure(figsize=(10, 10))
plt.imshow(corr, cmap='RdYlGn', interpolation='none', aspect='auto')
plt.colorbar()
plt.xticks(range(len(corr)), corr.columns, rotation='vertical')
plt.yticks(range(len(corr)), corr.columns);
plt.suptitle('FOREX Correlations Heat Map', fontsize=15, fontweight='bold')
plt.show()

Wir sehen eine gute Korrelation zwischen GBPUSD und GBPJPY in der obigen Heatmap. Jetzt können wir die Co-Integration testen, indem wir die Bibliothek statmodels importieren:

# Importieren von statmodels für den Test der Kointegration
import statsmodels
from statsmodels.tsa.stattools import coint

x = d['GBPUSD']
y = d['GBPJPY']
x = (x-min(x))/(max(x)-min(x))
y = (y-min(y))/(max(y)-min(y))

score = coint(x, y)
print('t-statistic: ', score[0], ' p-value: ', score[1])

Die Beziehung zwischen zwei Währungspaaren kann als Z-Score angezeigt werden:

# Zeichnen der Transformation Z-Score
diff_series = (x - y)
zscore = (diff_series - diff_series.mean()) / diff_series.std()

plt.plot(zscore)
plt.axhline(2.0, color='red', linestyle='--')
plt.axhline(-2.0, color='green', linestyle='--')

plt.show()



Visualisierung der Marktdaten mittels der Bibliothek Plotly

Es ist oft notwendig, Kurse in einer ansprechenden Form zu visualisieren. Dies kann mit der Bibliothek Plotly realisiert werden, die auch das Speichern von Diagrammen im interaktiven .html-Format ermöglicht.

Lassen Sie uns EURUSD-Kurse herunterladen und in einem Kerzenchart anzeigen:

# -*- kodieren: utf-8 -*-
"""
Created on Thu Mar 14 16:13:03 2019

@author: dmitrievsky
"""
from MetaTrader5 import *
from datetime import datetime
import pandas as pd
# Initialisieren der MT5-Verbindung 
MT5Initialize()
MT5WaitForTerminal()

print(MT5TerminalInfo())
print(MT5Version())

# Kopieren der Daten nach Panda Datenrahmen
stockdata = pd.DataFrame()
rates = MT5CopyRatesFromPos("EURUSD", MT5_TIMEFRAME_M1, 0, 5000)
# Deinitialisieren der MT5-Verbindung
MT5Shutdown()

stockdata['Open'] = [y.open for y in rates]
stockdata['Close'] = [y.close for y in rates]
stockdata['High'] = [y.high for y in rates]
stockdata['Low'] = [y.low for y in rates]
stockdata['Date'] = [y.time for y in rates]

import plotly.graph_objs as go
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot

trace = go.Ohlc(x=stockdata['Date'],
                open=stockdata['Open'],
                high=stockdata['High'],
                low=stockdata['Low'],
                close=stockdata['Close'])

data = [trace]
plot(data)

Es ist auch möglich, einen beliebigen Umfang historischer Bid- und Ask-Kurse herunterzuladen und anzuzeigen:

# -*- kodieren: utf-8 -*-
"""
Created on Thu Mar 14 16:13:03 2019

@author: dmitrievsky
"""
from MetaTrader5 import *
from datetime import datetime

# Initialisieren der MT5-Verbindung 
MT5Initialize()
MT5WaitForTerminal()

print(MT5TerminalInfo())
print(MT5Version())

# Kopieren der Daten in die Liste
rates = MT5CopyTicksFrom("EURUSD", datetime(2019,3,14,13), 1000, MT5_COPY_TICKS_ALL)
bid = [x.bid for x in rates]
ask = [x.ask for x in rates]
time = [x.time for x in rates]

# Deinitialisieren der MT5-Verbindung
MT5Shutdown()

import plotly.graph_objs as go
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
data = [go.Scatter(x=time, y=bid), go.Scatter(x=time, y=ask)]

plot(data)


Schlussfolgerung

In diesem Artikel haben wir Möglichkeiten zur Implementierung der Kommunikation zwischen dem Terminal und einem in Python geschriebenen Programm über Sockets und direkt über die Fachbibliothek von MetaQuotes diskutiert. Leider ist die aktuelle Implementierung des Socket-Clients in MetaTrader 5 nicht für den Betrieb im Strategy-Tester geeignet, so dass kein vollständiges Testen und Messen der Leistung dieser Lösung durchgeführt wurde. Lassen Sie uns auf weitere Updates der Socket-Funktionen warten.

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/5691

Beigefügte Dateien |
Optimale Farben für Handelsstrategien Optimale Farben für Handelsstrategien
In diesem Artikel werden wir ein Experiment durchführen: Wir werden die Optimierungsergebnisse einfärben. Die Farbe wird durch drei Parameter bestimmt: die Werte für Rot, Grün und Blau (RGB). Es gibt noch andere Methoden der Farbcodierung, die ebenfalls drei Parameter verwenden. So können drei Prüfparameter in eine Farbe umgewandelt werden, die die Werte visuell darstellt. Lesen Sie diesen Artikel, um herauszufinden, ob eine solche Darstellung nützlich sein kann.
Extrahieren von strukturierten Daten aus HTML-Seiten mit Hilfe von CSS-Selektoren Extrahieren von strukturierten Daten aus HTML-Seiten mit Hilfe von CSS-Selektoren
Der Artikel beschreibt eine universelle Methode zur Analyse und Konvertierung von Daten aus HTML-Dokumenten auf Basis von CSS-Selektoren. Handelsberichte, Testerberichte, Ihren bevorzugten Wirtschaftskalender, öffentliche Signale, Kontoüberwachung und zusätzliche Online-Kursquellen werden direkt mit MQL verfügbar gemacht.
Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil III). Erhebung (Collection) von Marktorders und Positionen Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil III). Erhebung (Collection) von Marktorders und Positionen
Im ersten Teil begannen wir mit der Erstellung einer großen plattformübergreifenden Bibliothek, die die Entwicklung von Programmen für MetaTrader 5 und MetaTrader 4 Plattformen vereinfacht. Danach haben wir die Collection (Sammlung bzw. Liste) von historischen Aufträgen und Deals implementiert. Unser nächster Schritt ist das Erstellen einer Klasse für eine komfortable Auswahl und Sortierung von Aufträgen, Deals und Positionen in Collections. Wir werden das Basis-Bibliotheksobjekt Engine implementieren und der Bibliothek die Collection von Marktorders und Positionen hinzufügen.
Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil II). Erhebung (Collection) historischer Aufträge und Deals Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil II). Erhebung (Collection) historischer Aufträge und Deals
Im ersten Teil begannen wir mit dem Erstellen einer großen plattformübergreifenden Bibliothek, die die Entwicklung von Programmen für MetaTrader 5 und MetaTrader 4 Plattformen vereinfacht. Wir haben das abstrakte Objekt COrder angelegt, das als Basisobjekt für die Speicherung von Daten zu historischen Aufträgen und Deals sowie zu Marktorders und Positionen dient. Jetzt werden wir alle notwendigen Objekte entwickeln, um die Daten der Kontenhistorie in "Collections" (Sammlungen bzw. Listen) zu speichern.