English Русский 中文 Español 日本語 Português
preview
Websockets für MetaTrader 5 — Unter Verwendung der Windows API

Websockets für MetaTrader 5 — Unter Verwendung der Windows API

MetaTrader 5Beispiele | 21 Januar 2022, 10:30
425 0
Francis Dube
Francis Dube

Einführung

Im Artikel Websockets für MetaTrader 5 haben wir die Grundlagen des Websocket-Protokolls besprochen und einen Client erstellt, der sich auf die in MQL5 implementierten Sockets stützt. Dieses Mal werden wir die Windows-API nutzen, um einen Websocket-Client für MetaTrader 5-Programme zu erstellen. Dies ist die nächstbeste Option, da keine zusätzliche Software erforderlich ist, sondern alles vom Betriebssystem bereitgestellt wird. Wir werden den Client als Klasse implementieren und Tests durchführen, indem wir die Websocket-API von Binary.com nutzen, um Live-Tick-Daten in MT5 einzuspeisen.


Websockets unter Windows

Wenn es um die Windows API und das Internet geht, sind MQL5-Entwickler meist mit der Windows Internet (WinINeT) Bibliothek vertraut. Sie implementiert unter anderem Internetprotokolle wie das File Transfer Protocol (FTP) und HTTP. Ähnlich ist es mit der Bibliothek Windows HTTP Services (WinHTTP). Dabei handelt es sich um eine spezielle Bibliothek für das HTTP-Protokoll mit Funktionen, die für die serverseitige Entwicklung nützlich sind. Einige der von WinHTTP bereitgestellten Funktionen sind Dienstprogramme für die Handhabung von Websocket-Verbindungen.

Das Websocket-Protokoll wurde in Windows-Betriebssystemen ab Windows 8.1 und Windows Server 2012 R2 eingeführt. Windows 7 und ältere Betriebssysteme haben keine native Unterstützung dafür. Die in diesem Artikel beschriebenen Programme funktionieren nicht auf Rechnern mit diesen älteren Betriebssystemen.

Die Bibliothek Winhttp

Um eine Websocket-Client-Verbindung mit Winhttp zu erstellen, benötigen wir die unten aufgeführten Funktionen:

WinHttpOpen
initialisiert die Bibliothek und bereitet sie für die Verwendung durch eine Anwendung vor.
WinHttpConnect
legt den Domänennamen des Servers fest, mit dem die Anwendung kommunizieren möchte.
WinHttpOpenRequest
erstellt ein HTTP-Anfrage-Handle.
WinHttpSetOption
setzt verschiedene Konfigurationsoptionen für eine HTTP-Verbindung.
WinHttpSendRequest
sendet eine Anfrage an einen Server.
WinHttpReceiveResponse
empfängt die Antwort eines Servers nach dem Senden einer Anforderung.
WinHttpWebSocketCompleteUpgrade
bestätigt, dass die vom Server empfangene Antwort das Websocket-Protokoll erfüllt.
WinHttpCloseHandle
wird verwendet, um zuvor verwendete Ressourcen-Deskriptoren zu verwerfen.
WinHttpWebSocketSend
wird verwendet, um Daten über eine Websocket-Verbindung zu senden.
WinHttpWebSocketReceive
empfängt Daten über eine Websocket-Verbindung.
WinHttpWebSocketClose
schließt eine WebSocket-Verbindung.
WinHttpWebSocketQueryCloseStatus
prüft die vom Server gesendete Schließstatusmeldung.

Alle in der Bibliothek verfügbaren Funktionen werden von Microsoft dokumentiert. Eine detaillierte Beschreibung aller Funktionen, ihrer Eingabeparameter und Rückgabetypen können Sie sich über die entsprechenden Links oben anschauen.

Der Client, den wir für mt5 erstellen werden, arbeitet im synchronen Modus. Das bedeutet, dass Funktionsaufrufe die Ausführung blockieren, bis sie zurückkehren. Zum Beispiel wird ein Aufruf von WinHttpWebSocketReceive() den ausführenden Thread blockieren, bis Daten zum Lesen verfügbar sind. Behalten Sie dies im Hinterkopf, wenn Sie mt5-Anwendungen erstellen.

Die winhttp-Funktionen werden in der Include-Datei winhttp.mqh deklariert und importiert.

#include <WinAPI\errhandlingapi.mqh>



#define WORD  ushort
#define DWORD ulong
#define BYTE  uchar
#define INTERNET_PORT WORD
#define HINTERNET long
#define LPVOID uint&

#define WINHTTP_ERROR_BASE                     12000

#define ERROR_WINHTTP_OUT_OF_HANDLES           (WINHTTP_ERROR_BASE + 1)
#define ERROR_WINHTTP_TIMEOUT                  (WINHTTP_ERROR_BASE + 2)
#define ERROR_WINHTTP_INTERNAL_ERROR           (WINHTTP_ERROR_BASE + 4)
#define ERROR_WINHTTP_INVALID_URL              (WINHTTP_ERROR_BASE + 5)
#define ERROR_WINHTTP_UNRECOGNIZED_SCHEME      (WINHTTP_ERROR_BASE + 6)
#define ERROR_WINHTTP_NAME_NOT_RESOLVED        (WINHTTP_ERROR_BASE + 7)
#define ERROR_WINHTTP_INVALID_OPTION           (WINHTTP_ERROR_BASE + 9)
#define ERROR_WINHTTP_OPTION_NOT_SETTABLE      (WINHTTP_ERROR_BASE + 11)
#define ERROR_WINHTTP_SHUTDOWN                 (WINHTTP_ERROR_BASE + 12)


#define ERROR_WINHTTP_LOGIN_FAILURE            (WINHTTP_ERROR_BASE + 15)
#define ERROR_WINHTTP_OPERATION_CANCELLED      (WINHTTP_ERROR_BASE + 17)
#define ERROR_WINHTTP_INCORRECT_HANDLE_TYPE    (WINHTTP_ERROR_BASE + 18)
#define ERROR_WINHTTP_INCORRECT_HANDLE_STATE   (WINHTTP_ERROR_BASE + 19)
#define ERROR_WINHTTP_CANNOT_CONNECT           (WINHTTP_ERROR_BASE + 29)
#define ERROR_WINHTTP_CONNECTION_ERROR         (WINHTTP_ERROR_BASE + 30)
#define ERROR_WINHTTP_RESEND_REQUEST           (WINHTTP_ERROR_BASE + 32)

#define ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED  (WINHTTP_ERROR_BASE + 44)


#define ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN   (WINHTTP_ERROR_BASE + 100)
#define ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND   (WINHTTP_ERROR_BASE + 101)
#define ERROR_WINHTTP_CANNOT_CALL_AFTER_SEND (WINHTTP_ERROR_BASE + 102)
#define ERROR_WINHTTP_CANNOT_CALL_AFTER_OPEN (WINHTTP_ERROR_BASE + 103)

#define ERROR_WINHTTP_HEADER_NOT_FOUND             (WINHTTP_ERROR_BASE + 150)
#define ERROR_WINHTTP_INVALID_SERVER_RESPONSE      (WINHTTP_ERROR_BASE + 152)
#define ERROR_WINHTTP_INVALID_HEADER               (WINHTTP_ERROR_BASE + 153)
#define ERROR_WINHTTP_INVALID_QUERY_REQUEST        (WINHTTP_ERROR_BASE + 154)
#define ERROR_WINHTTP_HEADER_ALREADY_EXISTS        (WINHTTP_ERROR_BASE + 155)
#define ERROR_WINHTTP_REDIRECT_FAILED              (WINHTTP_ERROR_BASE + 156)


#define ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR  (WINHTTP_ERROR_BASE + 178)
#define ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT     (WINHTTP_ERROR_BASE + 166)
#define ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT (WINHTTP_ERROR_BASE + 167)
#define ERROR_WINHTTP_UNHANDLED_SCRIPT_TYPE     (WINHTTP_ERROR_BASE + 176)
#define ERROR_WINHTTP_SCRIPT_EXECUTION_ERROR    (WINHTTP_ERROR_BASE + 177)
#define ERROR_WINHTTP_NOT_INITIALIZED          (WINHTTP_ERROR_BASE + 172)
#define ERROR_WINHTTP_SECURE_FAILURE           (WINHTTP_ERROR_BASE + 175)


#define ERROR_WINHTTP_SECURE_CERT_DATE_INVALID    (WINHTTP_ERROR_BASE + 37)
#define ERROR_WINHTTP_SECURE_CERT_CN_INVALID      (WINHTTP_ERROR_BASE + 38)
#define ERROR_WINHTTP_SECURE_INVALID_CA           (WINHTTP_ERROR_BASE + 45)
#define ERROR_WINHTTP_SECURE_CERT_REV_FAILED      (WINHTTP_ERROR_BASE + 57)
#define ERROR_WINHTTP_SECURE_CHANNEL_ERROR        (WINHTTP_ERROR_BASE + 157)
#define ERROR_WINHTTP_SECURE_INVALID_CERT         (WINHTTP_ERROR_BASE + 169)
#define ERROR_WINHTTP_SECURE_CERT_REVOKED         (WINHTTP_ERROR_BASE + 170)
#define ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE     (WINHTTP_ERROR_BASE + 179)


#define ERROR_WINHTTP_AUTODETECTION_FAILED                  (WINHTTP_ERROR_BASE + 180)
#define ERROR_WINHTTP_HEADER_COUNT_EXCEEDED                 (WINHTTP_ERROR_BASE + 181)
#define ERROR_WINHTTP_HEADER_SIZE_OVERFLOW                  (WINHTTP_ERROR_BASE + 182)
#define ERROR_WINHTTP_CHUNKED_ENCODING_HEADER_SIZE_OVERFLOW (WINHTTP_ERROR_BASE + 183)
#define ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW               (WINHTTP_ERROR_BASE + 184)
#define ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY            (WINHTTP_ERROR_BASE + 185)
#define ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY     (WINHTTP_ERROR_BASE + 186)

#define ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED_PROXY         (WINHTTP_ERROR_BASE + 187)
#define ERROR_WINHTTP_SECURE_FAILURE_PROXY                  (WINHTTP_ERROR_BASE + 188)
#define ERROR_WINHTTP_RESERVED_189                          (WINHTTP_ERROR_BASE + 189)
#define ERROR_WINHTTP_HTTP_PROTOCOL_MISMATCH                (WINHTTP_ERROR_BASE + 190)

#define WINHTTP_ERROR_LAST                                  (WINHTTP_ERROR_BASE + 188)

enum WINHTTP_WEB_SOCKET_BUFFER_TYPE
  {
   WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE       = 0,
   WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE      = 1,
   WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE         = 2,
   WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE        = 3,
   WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE                = 4
  };

enum _WINHTTP_WEB_SOCKET_CLOSE_STATUS
  {
   WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS                = 1000,
   WINHTTP_WEB_SOCKET_ENDPOINT_TERMINATED_CLOSE_STATUS    = 1001,
   WINHTTP_WEB_SOCKET_PROTOCOL_ERROR_CLOSE_STATUS         = 1002,
   WINHTTP_WEB_SOCKET_INVALID_DATA_TYPE_CLOSE_STATUS      = 1003,
   WINHTTP_WEB_SOCKET_EMPTY_CLOSE_STATUS                  = 1005,
   WINHTTP_WEB_SOCKET_ABORTED_CLOSE_STATUS                = 1006,
   WINHTTP_WEB_SOCKET_INVALID_PAYLOAD_CLOSE_STATUS        = 1007,
   WINHTTP_WEB_SOCKET_POLICY_VIOLATION_CLOSE_STATUS       = 1008,
   WINHTTP_WEB_SOCKET_MESSAGE_TOO_BIG_CLOSE_STATUS        = 1009,
   WINHTTP_WEB_SOCKET_UNSUPPORTED_EXTENSIONS_CLOSE_STATUS = 1010,
   WINHTTP_WEB_SOCKET_SERVER_ERROR_CLOSE_STATUS           = 1011,
   WINHTTP_WEB_SOCKET_SECURE_HANDSHAKE_ERROR_CLOSE_STATUS = 1015
  };

#define WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH 123
#define WINHTTP_FLAG_SECURE                0x00800000

#define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY               0

#define WINHTTP_OPTION_SECURITY_FLAGS                   31
#define WINHTTP_OPTION_SECURE_PROTOCOLS                 84
#define WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET            114
#define WINHTTP_OPTION_WEB_SOCKET_CLOSE_TIMEOUT         115
#define WINHTTP_OPTION_WEB_SOCKET_KEEPALIVE_INTERVAL    116
#define WINHTTP_OPTION_WEB_SOCKET_RECEIVE_BUFFER_SIZE   122
#define WINHTTP_OPTION_WEB_SOCKET_SEND_BUFFER_SIZE      123


#define SECURITY_FLAG_IGNORE_UNKNOWN_CA         0x00000100
#define SECURITY_FLAG_IGNORE_CERT_DATE_INVALID  0x00002000
#define SECURITY_FLAG_IGNORE_CERT_CN_INVALID    0x00001000
#define SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE   0x00000200


#define ERROR_INVALID_PARAMETER          87L
#define ERROR_INVALID_OPERATION          4317L

#import "winhttp.dll"
HINTERNET WinHttpOpen(string,DWORD,string,string,DWORD);
HINTERNET WinHttpConnect(HINTERNET,string,INTERNET_PORT,DWORD);
HINTERNET WinHttpOpenRequest(HINTERNET,string,string,string,string,string,DWORD);
bool WinHttpSetOption(HINTERNET,DWORD,LPVOID[],DWORD);
bool WinHttpQueryOption(HINTERNET,DWORD,LPVOID[],DWORD&);
bool WinHttpSetTimeouts(HINTERNET,int,int,int,int);
HINTERNET WinHttpSendRequest(HINTERNET,string,DWORD,LPVOID[],DWORD,DWORD,DWORD);
bool WinHttpReceiveResponse(HINTERNET,LPVOID[]);
HINTERNET WinHttpWebSocketCompleteUpgrade(HINTERNET,DWORD&);
bool WinHttpCloseHandle(HINTERNET);
DWORD WinHttpWebSocketSend(HINTERNET,WINHTTP_WEB_SOCKET_BUFFER_TYPE,BYTE&[],DWORD);
DWORD WinHttpWebSocketReceive(HINTERNET,BYTE&[],DWORD,DWORD&,WINHTTP_WEB_SOCKET_BUFFER_TYPE&);
DWORD WinHttpWebSocketClose(HINTERNET,ushort,BYTE&[],DWORD);
DWORD WinHttpWebSocketQueryCloseStatus(HINTERNET,ushort&,BYTE&[],DWORD,DWORD&);
#import
//+------------------------------------------------------------------+


Verwendung der winhttp-Funktionen

Um einen Websocket-Client mit diesen Funktionen einzurichten, müssen wir zuerst WinHttpOpen() aufrufen, um die Bibliothek zu initialisieren. Die Funktion gibt ein Sitzungshandle zurück, das in nachfolgenden Aufrufen anderer winhttp-Bibliotheksfunktionen verwendet werden kann.

#include<winhttp.mqh>

HINTERNET sessionhandle,connectionhandle,requesthandle,websockethandle;

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   sessionhandle=connectionhandle=requesthandle=websockethandle=NULL;

   sessionhandle=WinHttpOpen("MT5 app",WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,NULL,NULL,0);

   if(sessionhandle==NULL)
     {
      Print("WinHttpOpen error" +string(kernel32::GetLastError()));
      return;
     }

Der zweite Schritt ist die Erstellung eines Verbindungshandles. Dies geschieht mit Hilfe von WinHttpConnect(). Hier geben wir die Serveradresse und die Portnummer an. Es ist wichtig zu beachten, dass an dieser Stelle nur der Domänenname des Servers benötigt wird, nicht aber das Schema oder der Pfad. Die öffentliche IP-Adresse kann ebenfalls verwendet werden, wenn sie bekannt ist. Die meisten Fehler, die bei der Verwendung von winhttp auftreten, hängen mit der Übergabe einer falsch formatierten Serveradresse zusammen. Wenn zum Beispiel die vollständige Serveradresse wss://ws.example.com/path lautet, erwartet WinHttpConnect() nur ws.example.com.

connectionhandle=WinHttpConnect(sessionhandle,server,Port,0);

   if(connectionhandle==NULL)
     {
      Print("WinHttpConnect error "+string(kernel32::GetLastError()));

      if(sessionhandle!=NULL)
         WinHttpCloseHandle(sessionhandle);

      return;
     }

Nachdem das Verbindungshandle erfolgreich erstellt wurde, verwenden wir es, um ein Anfragehandle zu erstellen, indem wir WinHttpOpenRequest() aufrufen. Hier spezifizieren wir die Pfadkomponente, falls vorhanden, aus der Serveradresse und setzen auch die Option, die Verbindung sicher zu machen oder nicht.

requesthandle=WinHttpOpenRequest(connectionhandle,"GET",path,NULL,NULL,NULL,(ExtTLS)?WINHTTP_FLAG_SECURE:0);

   if(requesthandle==NULL)
     {
      Print("WinHttpOpenRequest error "+string(kernel32::GetLastError()));


      if(connectionhandle!=NULL)
         WinHttpCloseHandle(connectionhandle);

      if(sessionhandle!=NULL)
         WinHttpCloseHandle(sessionhandle);

      return;
     }

Sobald das erledigt ist und wir ein gültiges Anfrage-Handle haben, bereiten wir uns auf den Websocket-Handshake-Prozess vor, indem wir WinHttpSetOption() aufrufen.

uint nullpointer[]= {};
   if(!WinHttpSetOption(requesthandle,WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET,nullpointer,0))
     {
      Print("WinHttpSetOption upgrade error "+string(kernel32::GetLastError()));
      if(requesthandle!=NULL)
         WinHttpCloseHandle(requesthandle);

      if(connectionhandle!=NULL)
         WinHttpCloseHandle(connectionhandle);

      if(sessionhandle!=NULL)
         WinHttpCloseHandle(sessionhandle);

      return;
     }

Damit werden die erforderlichen Kopfzeilen zu einer HTTP-Anforderung hinzugefügt, wie sie im Websocket-Protokoll festgelegt sind. Der Websocket-Handshake wird durch den Aufruf von WinHttpSendRequest() und anschließend WinHttpReceiveResponse() eingeleitet, um den Empfang einer Antwort auf unsere Anfrage zu bestätigen.

if(!WinHttpSendRequest(requesthandle,NULL,0,nullpointer,0,0,0))
     {
      Print("WinHttpSendRequest error "+string(kernel32::GetLastError()));
      if(requesthandle!=NULL)
         WinHttpCloseHandle(requesthandle);

      if(connectionhandle!=NULL)
         WinHttpCloseHandle(connectionhandle);

      if(sessionhandle!=NULL)
         WinHttpCloseHandle(sessionhandle);

      return;
     }



   if(!WinHttpReceiveResponse(requesthandle,nullpointer))
     {
      Print("WinHttpRecieveResponse no response "+string(kernel32::GetLastError()));
      if(requesthandle!=NULL)
         WinHttpCloseHandle(requesthandle);

      if(connectionhandle!=NULL)
         WinHttpCloseHandle(connectionhandle);

      if(sessionhandle!=NULL)
         WinHttpCloseHandle(sessionhandle);

      return;
     }

WinHttpWebSocketCompleteUpgrade() prüft die Antwort und stellt sicher, dass sie mit dem Websocket-Protokoll übereinstimmt. Wenn dies der Fall ist, gibt die Funktion das begehrte Websocket-Handle zurück.

ulong nv=0;
   websockethandle=WinHttpWebSocketCompleteUpgrade(requesthandle,nv);
   if(websockethandle==NULL)
     {
      Print("WinHttpWebSocketCompleteUpgrade error "+string(kernel32::GetLastError()));
      if(requesthandle!=NULL)
         WinHttpCloseHandle(requesthandle);

      if(connectionhandle!=NULL)
         WinHttpCloseHandle(connectionhandle);

      if(sessionhandle!=NULL)
         WinHttpCloseHandle(sessionhandle);

      return;
     }

   WinHttpCloseHandle(requesthandle);
   requesthandle=NULL;

Von nun an ist unser Websocket-Client voll funktionsfähig und wir können WinHttpWebSocketSend() zum Senden und WinHttpWebSocketReceive() zum Empfangen von Daten verwenden, da das Websocket-Handle erstellt wurde und das Request-Handle nicht mehr benötigt wird, da unsere HTTP-Verbindung zu einer Websocket-Verbindung aufgerüstet wurde. Anschließend können wir alle mit dem Anforderungshandle verbundenen Ressourcen durch den Aufruf von WinHttpCloseHandle() freigeben.

bool WebsocketSend(const string message)
  {
   BYTE msg_array[];

   StringToCharArray(message,msg_array,0,WHOLE_ARRAY);

   ArrayRemove(msg_array,ArraySize(msg_array)-1,1);

   DWORD len=(ArraySize(msg_array));

   ulong send=WinHttpWebSocketSend(websockethandle,WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE,msg_array,len);

   if(send)
      return(false);


   return(true);
  }
//+------------------------------------------------------------------+
bool WebSocketRecv(uchar &rxbuffer[],ulong &bytes_read)
  {
   WINHTTP_WEB_SOCKET_BUFFER_TYPE rbuffertype=-1;

   BYTE rbuffer[65539];

   ulong rbuffersize=ulong(ArraySize(rbuffer));

   ulong done=0;
   ulong transferred=0;
   ZeroMemory(rxbuffer);
   ZeroMemory(rbuffer);
   bytes_read=0;
   int called=0;

   do
     {
      called++;
      ulong get=WinHttpWebSocketReceive(websockethandle,rbuffer,rbuffersize,transferred,rbuffertype);
      if(get)
        {
         return(false);
        }

      ArrayCopy(rxbuffer,rbuffer,(int)done,0,(int)transferred);

      done+=transferred;

      transferred=0;

      ZeroMemory(rbuffer);

     }
   while(rbuffertype==WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE || rbuffertype==WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE);

   Print("Buffer type is "+EnumToString(rbuffertype)+" bytes read "+IntegerToString(done)+" looped "+IntegerToString(called));

   bytes_read=done;

   return(true);

  }

//+------------------------------------------------------------------+

Durch den Aufruf von WinHttpWebSocketClose() wird eine Websocket-Verbindung geschlossen. Sobald eine Verbindung geschlossen ist, sollten alle damit verbundenen Handles durch den Aufruf von
deinitialisiert werden, für jedes einzeln mit WinHttpCloseHandle().

BYTE closearray[]= {};

   ulong close=WinHttpWebSocketClose(websockethandle,WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS,closearray,0);
   if(close)
     {
      Print("websocket close error "+string(kernel32::GetLastError()));
      if(requesthandle!=NULL)
         WinHttpCloseHandle(requesthandle);

      if(websockethandle!=NULL)
         WinHttpCloseHandle(websockethandle);

      if(connectionhandle!=NULL)
         WinHttpCloseHandle(connectionhandle);

      if(sessionhandle!=NULL)
         WinHttpCloseHandle(sessionhandle);

      return;
     }

Die CWebsocket-Klasse

Die Datei websocket.mqh enthält die Klasse CWebsocket, die einen Wrapper für die Funktionen der Winhttp-Bibliothek darstellt, die für die Aktivierung eines Websocket-Clients benötigt werden.
Die Datei beginnt mit einer Include-Direktive, um alle Funktionen und Deklarationen aufzunehmen, die aus den Windows-API-Bibliotheken importiert werden.

#include<winhttp.mqh>

#define WEBSOCKET_ERROR_FIRST              WINHTTP_ERROR_LAST+1000
#define WEBSOCKET_ERROR_NOT_INITIALIZED    WEBSOCKET_ERROR_FIRST+1
#define WEBSOCKET_ERROR_EMPTY_SEND_BUFFER  WEBSOCKET_ERROR_FIRST+2
#define WEBSOCKET_ERROR_NOT_CONNECTED      WEBSOCKET_ERROR_FIRST+3
//+------------------------------------------------------------------+
//| websocket state enumeration                                      |
//+------------------------------------------------------------------+

enum ENUM_WEBSOCKET_STATE
  {
   CLOSED = 0,
   CLOSING,
   CONNECTING,
   CONNECTED
  };

Um eine Verbindung zu einem Websocket-Server herzustellen, ist Connect() die erste Methode, die aufgerufen wird.

Parameter von Connect():

  • _serveraddress — die vollständige Adresse des Servers (Typ:string),
  • _port — die Portnummer des Servers (type:ushort),
  • _appname — dies ist ein String-Parameter, der gesetzt werden kann, um eine Anwendung eindeutig
              unter Verwendung des Websocket-Clients zu identifizieren. Er wird als einer der Header in der anfänglichen
             http-Anfrage (Typ:String) gesendet.
  • _secure — ein boolescher Wert, der festlegt, ob eine sichere Verbindung verwendet werden soll oder nicht (type:boolean).

Die Methode Connect() ruft die privaten Methoden initialize() und upgrade() auf. Die private Methode initialize() verarbeitet die vollständige Serveradresse und zerlegt sie in Domänenname und Pfadkomponenten. Schließlich erzeugt createSessionConnection() die Sitzungs- und Verbindungshandles. Die Methode upgrade() kommt ins Spiel, um die Anfrage- und Websocket-Handles zu erstellen, bevor der neue Status der Client-Verbindung festgelegt wird.

bool CWebsocket::Connect(const string _serveraddress, const INTERNET_PORT _port=443, const string _appname=NULL,bool _secure=true)
  {
   if(clientState==CONNECTED)
     {
      if(StringCompare(_serveraddress,serveraddress,false))
         Abort();
      else
         return(true);
     }

   if(!initialize(_serveraddress,_port,appname,_secure))
      return(false);

   return(upgrade());
  }




bool CWebsocket::initialize(const string _serveraddress,const ushort _port,const string _appname,bool _secure)
  {
   if(initialized)
      return(true);

   if(_secure)
      isSecure=true;

   if(_port==0)
     {
      if(isSecure)
         serverPort=443;
      else
         serverPort=80;
     }
   else
     {
      serverPort=_port;
      isSecure=_secure;

      if(serverPort==443 && !isSecure)
         isSecure=true;
     }



   if(_appname!=NULL)
      appname=_appname;
   else
      appname="Mt5 app";

   serveraddress=_serveraddress;

   int dot=StringFind(serveraddress,".");

   int ss=(dot>0)?StringFind(serveraddress,"/",dot):-1;

   serverPath=(ss>0)?StringSubstr(serveraddress,ss+1):"/";

   int sss=StringFind(serveraddress,"://");

   if(sss<0)
      sss=-3;

   serverName=StringSubstr(serveraddress,sss+3,ss);

   initialized=createSessionConnection();

   return(initialized);
  }



bool CWebsocket::createSessionConnection(void)
  {
   hSession=WinHttpOpen(appname,WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,NULL,NULL,0);

   if(hSession==NULL)
     {
      setErrorDescription();
      return(false);
     }


   hConnection=WinHttpConnect(hSession,serverName,serverPort,0);

   if(hSession==NULL)
     {
      setErrorDescription();
      reset();
      return(false);
     }

   return(true);

  }

bool CWebsocket::upgrade(void)
  {
   clientState=CONNECTING;

   hRequest=WinHttpOpenRequest(hConnection,"GET",serverPath,NULL,NULL,NULL,(isSecure)?WINHTTP_FLAG_SECURE:0);

   if(hRequest==NULL)
     {
      setErrorDescription();
      reset();
      return(false);
     }

   uint nullpointer[]= {};
   if(!WinHttpSetOption(hRequest,WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET,nullpointer,0))
     {
      setErrorDescription();
      reset();
      return(false);
     }

   if(!WinHttpSendRequest(hRequest,NULL,0,nullpointer,0,0,0))
     {
      setErrorDescription();
      reset();
      return(false);
     }

   if(!WinHttpReceiveResponse(hRequest,nullpointer))
     {
      setErrorDescription();
      reset();
      return(false);
     }

   ulong nv=0;
   hWebSocket=WinHttpWebSocketCompleteUpgrade(hRequest,nv);
   if(hWebSocket==NULL)
     {
      setErrorDescription();
      reset();
      return(false);
     }

   WinHttpCloseHandle(hRequest);
   hRequest=NULL;
   clientState=CONNECTED;

   return(true);

  }

 Wenn die Methode Connect() den Wert true zurückgibt, können wir mit dem Senden von Daten über den Websocket-Client beginnen. Um dies zu erleichtern, gibt es zwei Methoden, die verwendet werden können.
 Die Methode SendString() nimmt als Eingabe eine Zeichenkette und die Methode Send() nimmt ein vorzeichenloses Zeichenarray als einzigen Funktionsparameter. Beide geben bei Erfolg true zurück und rufen die private Methode clientsend() auf, die alle Sendevorgänge für die Klasse abwickelt.

//+------------------------------------------------------------------+
//| helper method for sending data to the server                     |
//+------------------------------------------------------------------+
bool CWebsocket::clientsend(BYTE &txbuffer[],WINHTTP_WEB_SOCKET_BUFFER_TYPE buffertype)
  {
   DWORD len=(ArraySize(txbuffer));

   if(len<=0)
     {
      setErrorDescription(WEBSOCKET_ERROR_EMPTY_SEND_BUFFER);
      return(false);
     }

   ulong send=WinHttpWebSocketSend(hWebSocket,WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE,txbuffer,len);

   if(send)
     {
      setErrorDescription();
      return(false);
     }

   return(true);

  }

//+------------------------------------------------------------------+
//|public method for sending raw string messages                     |
//+------------------------------------------------------------------+
bool CWebsocket::SendString(const string msg)
  {
   if(!initialized)
     {
      setErrorDescription(WEBSOCKET_ERROR_NOT_INITIALIZED);
      return(false);
     }

   if(clientState!=CONNECTED)
     {
      setErrorDescription(WEBSOCKET_ERROR_NOT_CONNECTED);
      return(false);
     }

   if(StringLen(msg)<=0)
     {
      setErrorDescription(WEBSOCKET_ERROR_EMPTY_SEND_BUFFER);
      return(false);
     }

   BYTE msg_array[];

   StringToCharArray(msg,msg_array,0,WHOLE_ARRAY);

   ArrayRemove(msg_array,ArraySize(msg_array)-1,1);

   DWORD len=(ArraySize(msg_array));

   return(clientsend(msg_array,WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE));
  }

//+------------------------------------------------------------------+
//|Public method for sending data prepackaged in an array            |
//+------------------------------------------------------------------+
bool CWebsocket::Send(BYTE &buffer[])
  {
   if(!initialized)
     {
      setErrorDescription(WEBSOCKET_ERROR_NOT_INITIALIZED);
      return(false);
     }

   if(clientState!=CONNECTED)
     {
      setErrorDescription(WEBSOCKET_ERROR_NOT_CONNECTED);
      return(false);
     }

   return(clientsend(buffer,WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE));
  }

Um vom Server gesendete Daten zu lesen, können wir entweder Read() oder ReadString() verwenden. Die Methoden geben die Größe der empfangenen Daten zurück. ReadString() benötigt einen String, der als Referenz übergeben wird und in den die empfangenen Daten geschrieben werden. Read() hingegen schreibt in ein Array mit vorzeichenlosen Zeichen.

//+------------------------------------------------------------------+
//|helper method for reading received messages from the server       |
//+------------------------------------------------------------------+
void CWebsocket::clientread(BYTE &rbuffer[],ulong &bytes)
  {

   WINHTTP_WEB_SOCKET_BUFFER_TYPE rbuffertype=-1;

   ulong done=0;
   ulong transferred=0;
   ZeroMemory(rbuffer);
   ZeroMemory(rxbuffer);
   bytes=0;

   do
     {
      ulong get=WinHttpWebSocketReceive(hWebSocket,rxbuffer,rxsize,transferred,rbuffertype);
      if(get)
        {
         setErrorDescription();
         return;
        }

      ArrayCopy(rbuffer,rxbuffer,(int)done,0,(int)transferred);

      done+=transferred;

      ZeroMemory(rxbuffer);

      transferred=0;

     }
   while(rbuffertype==WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE || rbuffertype==WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE);

   bytes=done;

   return;

  }

//+------------------------------------------------------------------+
//|public method for reading data sent from the server               |
//+------------------------------------------------------------------+
ulong CWebsocket::Read(BYTE &buffer[])
  {
   if(!initialized)
     {
      setErrorDescription(WEBSOCKET_ERROR_NOT_INITIALIZED);
      return(false);
     }

   if(clientState!=CONNECTED)
     {
      setErrorDescription(WEBSOCKET_ERROR_NOT_CONNECTED);
      return(false);
     }

   ulong bytes_read_from_socket=0;

   clientread(buffer,bytes_read_from_socket);

   return(bytes_read_from_socket);

  }
//+------------------------------------------------------------------+
//|public method for reading data sent from the server               |
//+------------------------------------------------------------------+
ulong CWebsocket::ReadString(string &_response)
  {
   if(!initialized)
     {
      setErrorDescription(WEBSOCKET_ERROR_NOT_INITIALIZED);
      return(false);
     }

   if(clientState!=CONNECTED)
     {
      setErrorDescription(WEBSOCKET_ERROR_NOT_CONNECTED);
      return(false);
     }

   ulong bytes_read_from_socket=0;
   BYTE buffer[];

   clientread(buffer,bytes_read_from_socket);

   _response=(bytes_read_from_socket)?CharArrayToString(buffer):NULL;

   return(bytes_read_from_socket);

  }

Wenn der Websocket-Client nicht mehr benötigt wird, kann die Verbindung zum Server entweder mit Close() oder Abort() geschlossen werden. Die Abort()-Methode unterscheidet sich von der Close()-Methode dadurch, dass sie nicht nur eine Websocket-Verbindung schließt, sondern darüber hinaus die Werte einiger Klasseneigenschaften zurücksetzt und auf ihren Standardzustand zurücksetzt.

//+------------------------------------------------------------------+
//| Closes a websocket client connection                             |
//+------------------------------------------------------------------+
void CWebsocket::Close(void)
  {
   if(clientState==CLOSED)
      return;

   clientState=CLOSING;

   BYTE nullpointer[]= {};

   ulong result=WinHttpWebSocketClose(hWebSocket,WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS,nullpointer,0);
   if(result)
      setErrorDescription();

   reset();

   return;
  }


//+--------------------------------------------------------------------------+
//|method for abandoning a client connection. All previous server connection |
//|   parameters are reset to their default state                            |
//+--------------------------------------------------------------------------+
void CWebsocket::Abort(void)
  {
   Close();
//---
   serveraddress=serverName=serverPath=NULL;
   serverPort=0;
   isSecure=false;
   last_error=0;
   StringFill(errormsg,0);
//---
   return;
  }

ClientState() fragt den aktuellen Zustand des Websocket-Clients ab.

DomainName(), Port() und ServerPath() geben den Domainnamen, den Port bzw. die Pfadkomponente der aktuellen Verbindung zurück.

LastErrorMessage() kann verwendet werden, um den letzten Fehler als detaillierte Zeichenkette zu erhalten. Ein Aufruf von LastError() hingegen liefert den Fehlercode als ganzzahligen Wert.

//public getter methods
   string            LastErrorMessage(void)          {  return(errormsg);    }
   uint              LastError(void)      {  return(last_error);  }
   ENUM_WEBSOCKET_STATE ClientState(void) {  return(clientState); }
   string            DomainName(void)                {  return(serverName);  }
   INTERNET_PORT     Port(void)               {  return(serverPort);  }
   string            ServerPath(void)                {  return(serverPath);  }

Die gesamte Klasse ist unten aufgelistet.

//+------------------------------------------------------------------+
//|Class CWebsocket                                                  |
//| Purpose: class for websocket client                              |
//+------------------------------------------------------------------+

class CWebsocket
  {
private:
   ENUM_WEBSOCKET_STATE clientState;            //websocket state
   HINTERNET            hSession;               //winhttp session handle
   HINTERNET            hConnection;            //winhttp connection handle
   HINTERNET            hWebSocket;             //winhttp websocket handle
   HINTERNET            hRequest;               //winhtttp request handle
   string               appname;                //optional application name sent as one of the headers in initial http request
   string               serveraddress;          //full server address
   string               serverName;             //server domain name
   INTERNET_PORT        serverPort;             //port number
   string               serverPath;             //server path
   bool                 initialized;            //boolean flag that denotes the state of underlying winhttp infrastruture required for client
   BYTE                 rxbuffer[];             //internal buffer for reading from the socket
   bool                 isSecure;               //secure connection flag
   ulong                rxsize;                 //rxbuffer arraysize
   string               errormsg;               //internal buffer for error messages
   uint                 last_error;             //last winhttp/win32/class specific error
   // private methods
   bool              initialize(const string _serveraddress, const INTERNET_PORT _port, const string _appname,bool _secure);
   bool              createSessionConnection(void);
   bool              upgrade(void);
   void              reset(void);
   bool              clientsend(BYTE &txbuffer[],WINHTTP_WEB_SOCKET_BUFFER_TYPE buffertype);
   void              clientread(BYTE &rxbuffer[],ulong &bytes);
   void              setErrorDescription(uint error=0);

public:
                     CWebsocket(void):clientState(0),
                     hSession(NULL),
                     hConnection(NULL),
                     hWebSocket(NULL),
                     hRequest(NULL),
                     serveraddress(NULL),
                     serverName(NULL),
                     serverPort(0),
                     initialized(false),
                     isSecure(false),
                     rxsize(65539),
                     errormsg(NULL),
                     last_error(0)
     {
      ArrayResize(rxbuffer,(int)rxsize);
      ArrayFill(rxbuffer,0,rxsize,0);
      StringInit(errormsg,1000);
     }

                    ~CWebsocket(void)
     {
      Close();
      ArrayFree(rxbuffer);
     }
   //public methods

   bool              Connect(const string _serveraddress, const INTERNET_PORT _port=443, const string _appname=NULL,bool _secure=true);
   void              Close(void);
   bool              SendString(const string msg);
   bool              Send(BYTE &buffer[]);
   ulong             ReadString(string &response);
   ulong             Read(BYTE &buffer[]);
   void              Abort(void);
   void              ResetLastError(void)
     {
      last_error=0;
      StringFill(errormsg,0);
      ::ResetLastError();
     }
   //public getter methods
   string            LastErrorMessage(void)          {  return(errormsg);    }
   uint              LastError(void)      {  return(last_error);  }
   ENUM_WEBSOCKET_STATE ClientState(void) {  return(clientState); }
   string            DomainName(void)                {  return(serverName);  }
   INTERNET_PORT     Port(void)               {  return(serverPort);  }
   string            ServerPath(void)                {  return(serverPath);  }



  };

Nun, da wir unsere Websocket-Klasse haben, können wir uns ein Beispiel für ihre Verwendung ansehen.


Testen der CWebsocket-Klasse

Zum Testen werden wir eine mt5-Programm erstellen, das ein nutzerdefiniertes Symbol von Binary.com abruft. Wenn es in einen Chart geladen wird, wird die Historie heruntergeladen und öffnet einen neuen Chart für das nutzerdefinierte Symbol, der mit Live-Tick-Daten aktualisiert wird.

Es wird zwei Versionen geben: BinaryCustomSymboWithTickHistory.ex5 verwendet die Tick-Historie, während die andere Version BinaryCustomSymbolWithBarHistory.ex5 die OHLC-Balken-Historie herunterlädt. Beide werden einen ähnlichen Code haben.

Binary.com bietet eine gut dokumentierte API, die es Entwicklern ermöglicht, Schnittstellen zu erstellen, die mit ihren Systemen interagieren. Die API basiert auf Websockets, wobei Abfragen und Antworten im json-Format bereitgestellt werden.

Die Entwickler-Webseite von Binary



Die Anwendung wird als Expert Advisor implementiert, der die Hilfe von drei wichtigen Bibliotheken in Anspruch nimmt:

  • Die erste ist websocket.mqh zur Verarbeitung von Websocket-Verbindungen,
  • die zweite ist JAson.mqh für die Arbeit mit json formatierten Daten, verfasst von Alexey Sergeev und erhältlich im github repository von vivazzi
  •  die dritte Bibliothek, die wir benötigen, ist FileTxt.mqh für die Handhabung von Dateioperationen.

Der EA wird die folgenden vom Nutzer einstellbaren Eingaben haben:

  • binary_appid - dies ist ein String-Parameter, der benötigt wird, um unserer Anwendung Zugriff auf die API zu gewähren. Eine App-ID kann durch Befolgen der Anweisungen auf dem Entwicklerportal erworben werden. Das Abonnieren des Tickstreams eines Symbols erfordert keine Benutzerauthentifizierung auf Binary.com, daher ist es nicht notwendig, ein API-Token anzugeben.
  • binary_symbol - dies ist eine Enumeration, die es dem Nutzer ermöglicht, das Symbol auszuwählen, das er in mt5 importieren möchte.
  • binary_timeframe - dies ist der Zeitrahmen des Charts, der geöffnet wird, sobald die Datenhistorie heruntergeladen und dem mt5 hinzugefügt wurden.
#include<websocket.mqh>
#include<JAson.mqh>
#include<Files/FileTxt.mqh>

#define BINARY_URL "ws.binaryws.com/websockets/v3?app_id="
#define BINARY_SYMBOL_SETTINGS "binarysymbolset.json"
#define BINARY_SYMBOL_BASE_PATH "Binary.com\\"

enum ENUM_BINARY_SYMBOL
{
 BINARY_1HZ10V=0,//Volatility 10 (1s)
 BINARY_1HZ25V,//Volatility 25 (1s)
 BINARY_1HZ50V,//Volatility 50 (1s)
 BINARY_1HZ75V,//Volatility 75 (1s)
 BINARY_1HZ100V,//Volatility 100 (1s)
 BINARY_1HZ200V,//Volatility 200 (1s)
 BINARY_1HZ300V,//Volatility 300 (1s)
 BINARY_BOOM300N,//BOOM 300
 BINARY_BOOM500,//BOOM 500
 BINARY_BOOM1000,//BOOM 1000
 BINARY_CRASH300N,//CRASH 300
 BINARY_CRASH500,//CRASH 500
 BINARY_CRASH1000,//CRASH 1000
 BINARY_cryBTCUSD,//BTCUSD
 BINARY_cryETHUSD,//ETHUSD
 BINARY_frxAUDCAD,//AUDCAD
 BINARY_frxAUDCHF,//AUDCHF
 BINARY_frxAUDJPY,//AUDJPY
 BINARY_frxAUDNZD,//AUDNZD
 BINARY_frxAUDUSD,//AUDUSD
 BINARY_frxBROUSD,//BROUSD
 BINARY_frxEURAUD,//EURAUD
 BINARY_frxEURCAD,//EURCAD
 BINARY_frxEURCHF,//EURCHF
 BINARY_frxEURGBP,//EURGBP
 BINARY_frxEURJPY,//EURJPY
 BINARY_frxEURNZD,//EURNZD
 BINARY_frxEURUSD,//EURUSD
 BINARY_frxGBPAUD,//GBPAUD
 BINARY_frxGBPCAD,//GBPCAD
 BINARY_frxGBPCHF,//GBPCHF
 BINARY_frxGBPJPY,//GBPJPY
 BINARY_frxGBPNOK,//GBPNOK
 BINARY_frxGBPNZD,//GBPNZD
 BINARY_frxGBPUSD,//GBPUSD
 BINARY_frxNZDJPY,//NZDJPY
 BINARY_frxNZDUSD,//NZDUSD
 BINARY_frxUSDCAD,//USDCAD
 BINARY_frxUSDCHF,//USDCHF
 BINARY_frxUSDJPY,//USDJPY
 BINARY_frxUSDMXN,//USDMXN
 BINARY_frxUSDNOK,//USDNOK
 BINARY_frxUSDPLN,//USDPLN
 BINARY_frxUSDSEK,//USDSEK
 BINARY_frxXAUUSD,//XAUUSD
 BINARY_frxXAGUSD,//XAGUSD
 BINARY_frxXPDUSD,//XPDUSD
 BINARY_frxXPTUSD,//XPTUSD
 BINARY_JD10,//Jump 10 Index
 BINARY_JD25,//Jump 25 Index
 BINARY_JD50,//Jump 50 Index
 BINARY_JD75,//Jump 75 Index
 BINARY_JD100,//Jump 100 Index
 BINARY_OTC_AEX,//Dutch Index
 BINARY_OTC_AS51,//Australian Index
 BINARY_OTC_DJI,//Wall Street Index
 BINARY_OTC_FCHI,//French Index 
 BINARY_OTC_FTSE,//UK Index
 BINARY_OTC_GDAXI,//German Index
 BINARY_OTC_HSI,//Hong Kong Index
 BINARY_OTC_N225,//Japanese Index
 BINARY_OTC_NDX,//US Tech Index
 BINARY_OTC_SPC,//US Index
 BINARY_OTC_SSMI,//Swiss Index 
 BINARY_OTC_SX5E,//Euro 50 Index
 BINARY_R_10,//Volatility 10 Index
 BINARY_R_25,//Volatility 25 Index
 BINARY_R_50,//Volatility 50 Index
 BINARY_R_75,//Volatility 75 Index
 BINARY_R_100,//Volatility 100 Index
 BINARY_RDBEAR,//Bear Market Index
 BINARY_RDBULL,//Bull Market Index
 BINARY_stpRNG,//Step Index
 BINARY_WLDAUD,//AUD Index
 BINARY_WLDEUR,//EUR Index
 BINARY_WLDGBP,//GBP Index
 BINARY_WLDUSD,//USD Index
 BINARY_WLDXAU//Gold Index
};

input string binary_appid="";//Binary.com registered application ID
input ENUM_BINARY_SYMBOL binary_symbol=BINARY_R_100;//Binary.com symbol
input ENUM_TIMEFRAMES binary_timeframe=PERIOD_M1;//Chart period


Der Expert Advisor besteht aus zwei Klassen, CCustomSymbol und CBinarySymbol.


Die Klasse CCustomSymbol

CCustomSymbol ist eine Klasse für die Arbeit mit benutzerdefinierten Symbolen aus externen Quellen. Sie ist inspiriert von der SYMBOL-Bibliothek von fxsaber. Sie bietet Methoden zum Bearbeiten und Abrufen der Eigenschaften von Symbolen sowie zum Öffnen und Schließen der entsprechenden Charts neben anderen Funktionen. Noch wichtiger ist, dass sie drei virtuelle Methoden bereitstellt, die von untergeordneten Klassen überschrieben werden können, um Variationen bei der Implementierung eines benutzerdefinierten Symbols zu ermöglichen.

//+------------------------------------------------------------------+
//|General class for creating custom symbols from external source    |
//+------------------------------------------------------------------+
class CCustomSymbol
  {
protected:
   string            m_symbol_name;       //symbol name
   datetime          m_history_start;     //existing tick history start date
   datetime          m_history_end;       //existing tick history end date
   bool              m_new;               //flag specifying whether a symbol has just been created or already exists in the terminal
   ENUM_TIMEFRAMES   m_chart_tf;          //chart timeframe
public:
   //constructor
                     CCustomSymbol(void)
     {
      m_symbol_name=NULL;
      m_chart_tf=PERIOD_M1;
      m_history_start=0;
      m_history_end=0;
      m_new=false;
     }
   //destructor
                    ~CCustomSymbol(void)
     {

     }
   //method for initializing symbol, sets the symbol name and chart timeframe properties
   virtual bool      Initialize(const string sy,string sy_path=NULL, ENUM_TIMEFRAMES chart_tf=PERIOD_M1)
     {
      m_symbol_name=sy;
      m_chart_tf=chart_tf;
      return(InitSymbol(sy_path));
     }
   //gets the symbol name
   string            Name(void) const
     {
      return(m_symbol_name);
     }
   //sets the history start date
   bool              SetHistoryStartDate(const datetime startime)
     {
      if(startime>=TimeLocal())
        {
         Print("Invalid history start time");
         return(false);
        }

      m_history_start=startime;

      return(true);
     }
   //gets the history start date
   datetime          GetHistoryStartDate(void)
     {
      return(m_history_start);
     }
   //general methods for setting the properties of the custom symbol
   bool              SetProperty(const ENUM_SYMBOL_INFO_DOUBLE Property, double Value) const
     {
      return(::CustomSymbolSetDouble(m_symbol_name, Property, Value));
     }

   bool              SetProperty(const ENUM_SYMBOL_INFO_INTEGER Property, long Value) const
     {
      return(::CustomSymbolSetInteger(m_symbol_name, Property, Value));
     }

   bool              SetProperty(const ENUM_SYMBOL_INFO_STRING Property, string Value) const
     {
      return(::CustomSymbolSetString(m_symbol_name, Property, Value));
     }
   //general methods for getting the symbol properties of the custom symbol
   long              GetProperty(const ENUM_SYMBOL_INFO_INTEGER Property) const
     {
      return(::SymbolInfoInteger(m_symbol_name, Property));
     }

   double            GetProperty(const ENUM_SYMBOL_INFO_DOUBLE Property) const
     {
      return(::SymbolInfoDouble(m_symbol_name, Property));
     }

   string            GetProperty(const ENUM_SYMBOL_INFO_STRING Property) const
     {
      return(::SymbolInfoString(m_symbol_name, Property));
     }
   //method for deleting a custom symbol
   bool              Delete(void)
     {
      return((bool)(GetProperty(SYMBOL_CUSTOM)) && DeleteAllCharts()  && ::CustomSymbolDelete(m_symbol_name) && SymbolSelect(m_symbol_name,false));
     }
   //unimplemented virtual method for adding new ticks
   virtual void      AddTick(void)
     {
      return;
     }
   //unimplemented virtual method for aquiring the either ticks or candle history from an external source
   virtual bool      UpdateHistory(void)
     {
      return(false);
     }

protected:
   //checks if the symbol already exists or not
   bool              SymbolExists(void)
     {
      return(SymbolSelect(m_symbol_name,true));
     }
   //method that opens a new chart according to the m_chart_tf property
   void              OpenChart(void)
     {
      long Chart = ::ChartFirst();

      bool opened=false;

      while(Chart != -1)
        {
         if((::ChartSymbol(Chart) == m_symbol_name))
           {
            ChartRedraw(Chart);
            if(ChartPeriod(Chart)==m_chart_tf)
               opened=true;
           }
         Chart = ::ChartNext(Chart);
        }

      if(!opened)
        {
         long id = ChartOpen(m_symbol_name,m_chart_tf);
         if(id == 0)
           {
            Print("Can't open new chart for " + m_symbol_name + ", code: " + (string)GetLastError());
            return;
           }
         else
           {
            Sleep(1000);
            ChartSetSymbolPeriod(id, m_symbol_name, m_chart_tf);
            ChartSetInteger(id, CHART_MODE,CHART_CANDLES);
           }
        }
     }
   //deletes all charts for the specified symbol
   bool              DeleteAllCharts(void)
     {
      long Chart = ::ChartFirst();

      while(Chart != -1)
        {
         if((Chart != ::ChartID()) && (::ChartSymbol(Chart) == m_symbol_name))
            if(!ChartClose(Chart))
              {
               Print("Error closing chart id ", Chart, m_symbol_name, ChartPeriod(Chart));
               return(false);
              }

         Chart = ::ChartNext(Chart);
        }

      return(true);
     }
   //helper method that initializes a custom symbol
   bool              InitSymbol(const string _path=NULL)
     {
      if(!SymbolExists())
        {
         if(!CustomSymbolCreate(m_symbol_name,_path))
           {
            Print("error creating custom symbol ", ::GetLastError());
            return(false);
           }

         if(!SetProperty(SYMBOL_CHART_MODE,SYMBOL_CHART_MODE_BID)   ||
            !SetProperty(SYMBOL_SWAP_MODE,SYMBOL_SWAP_MODE_DISABLED) ||
            !SetProperty(SYMBOL_TRADE_MODE,SYMBOL_TRADE_MODE_DISABLED))
           {
            Print("error setting symbol properties");
            return(false);
           }

         if(!SymbolSelect(m_symbol_name,true))
           {
            Print("error adding symbol to market watch",::GetLastError());
            return(false);
           }

         m_new=true;

         return(true);
        }
      else
        {
         long custom=GetProperty(SYMBOL_CUSTOM);

         if(!custom)
           {
            Print("Error, symbol is not custom ",m_symbol_name,::GetLastError());
            return(false);
           }

         m_history_end=GetLastBarTime();
         m_history_start=GetFirstBarTime();
         m_new=false;

         return(true);
        }
     }

   //gets the last tick time for an existing custom symbol

   datetime          GetLastTickTime(void)
     {
      MqlTick tick;

      ZeroMemory(tick);

      if(!SymbolInfoTick(m_symbol_name,tick))
        {
         Print("symbol info tick failure ", ::GetLastError());
         return(0);
        }
      else
         return(tick.time);
     }


   //gets the last bar time of the one minute timeframe in candle history
   datetime          GetLastBarTime(void)
     {
      MqlRates candle[1];

      ZeroMemory(candle);

      int bars=iBars(m_symbol_name,PERIOD_M1);

      if(bars<=0)
         return(0);

      if(CopyRates(m_symbol_name,PERIOD_M1,0,1,candle)>0)
         return(candle[0].time);
      else
         return(0);
     }
   //gets the first bar time of the one minute timeframe  in candle  history
   datetime          GetFirstBarTime(void)
     {
      MqlRates candle[1];

      ZeroMemory(candle);

      int bars=iBars(m_symbol_name,PERIOD_M1);

      if(bars<=0)
         return(0);

      if(CopyRates(m_symbol_name,PERIOD_M1,bars-1,1,candle)>0)
         return(candle[0].time);
      else
         return(0);

     }


  };

Die Parameter der Methode Initialize():

  •   sy - dieser String-Parameter legt den Symbolnamen für ein nutzerdefiniertes Symbol fest.
  •   sy_path - String-Parameter, der die Eigenschaft des Symbolpfades festlegt. 
  •   chart_tf - der Parameter legt den Zeitraum des Charts fest, der geöffnet wird, wenn die Symbolhistorie geladen wurde.
Die Methode ruft Initsymbol() auf, die entweder ein neues nutzerdefiniertes Symbol erstellt, wenn es noch nicht existiert, oder die Historie lädt, wenn das Symbol bereits existiert.

Die beiden anderen virtuellen Methoden, UpdateHistory() und AddTick(), sind in CCustomSymbol nicht implementiert. Jede abgeleitete Klasse muss diese Methoden überschreiben.


Die Klasse CBinarySymbol

An dieser Stelle kommt die Klasse CBinarySymbol ins Spiel. Sie ist von CCustomSymbol abgeleitet und bietet Methoden, die alle virtuellen Methoden ihrer Elternklasse überschreibt. Hier werden wir unseren Websocket-Client verwenden, um die Binary.com-API zu nutzen.

//+------------------------------------------------------------------+
//|Class for creating custom Binary.com specific symbols             |
//+------------------------------------------------------------------+
class CBinarySymbol:public CCustomSymbol
  {
private:
   //private properties
   string            m_appID;      //app id string issued by Binary.com
   string            m_url;        //final url
   string            m_stream_id;  //stream identifier for a symbol
   int               m_index;      //array index
   CWebsocket*       websocket;    //websocket client
   CJAVal*           json;         //utility json object
   CJAVal*           symbolSpecs;  //json object storing symbol specification
   //private methods
   bool              CheckBinaryError(CJAVal &j);
   bool              GetSymbolSettings(void);
public:
   //Constructor
                     CBinarySymbol(void):m_appID(NULL),
                     m_url(NULL),
                     m_stream_id(NULL),
                     m_index(-1)

     {
      json=new CJAVal();
      symbolSpecs=new CJAVal();
      websocket=new CWebsocket();
     }
   //Destructor
                    ~CBinarySymbol(void)
     {
      if(CheckPointer(websocket)==POINTER_DYNAMIC)
        {
         if(m_stream_id!="")
            StopTicksStream();
         delete websocket;
        }

      if(CheckPointer(json)==POINTER_DYNAMIC)
         delete json;

      if(CheckPointer(symbolSpecs)==POINTER_DYNAMIC)
         delete symbolSpecs;

      Comment("");

     }
   //public methods
   virtual void      AddTick(void) override;
   virtual bool      Initialize(const string sy,string sy_path=NULL,ENUM_TIMEFRAMES chart_tf=PERIOD_M1) override;
   virtual bool      UpdateHistory(void) override;
   void              SetAppID(const string id);
   bool              StartTicksStream(void);
   bool              StopTicksStream(void);
  };

Nachdem eine Instanz der Klasse CBinarySymbol erstellt wurde, sollte mit der Methode SetAppID() eine gültige Anwendungskennung app_id gesetzt werden. Erst dann können wir mit der Initialisierung eines nutzerdefinierten Symbols fortfahren

//+------------------------------------------------------------------+
//|sets the the application id used to consume binary.com api        |
//+------------------------------------------------------------------+
void CBinarySymbol::SetAppID(const string id)
  {
   if(m_appID!=NULL && StringCompare(id,m_appID,false))
      websocket.Abort();

   m_appID=id;
   m_url=BINARY_URL+m_appID;

  }

Die Methode Initialize() verwendet die private Methode getSymbolSpecs(), um die Eigenschaften eines ausgewählten Symbols zu ermitteln. Die relevanten Informationen werden dann verwendet, um
die Eigenschaften für ein neues benutzerdefiniertes Symbol festzulegen.

//+------------------------------------------------------------------+
//|Begins process of creating custom symbol                          |
//+------------------------------------------------------------------+
bool CBinarySymbol::Initialize(const string sy,string sy_path=NULL, ENUM_TIMEFRAMES chart_tf=PERIOD_M1)
  {
   if(CheckPointer(websocket)==POINTER_INVALID || CheckPointer(json)==POINTER_INVALID || CheckPointer(symbolSpecs)==POINTER_INVALID)
     {
      Print("Invalid pointer found ");
      return(false);
     } 


   if(m_appID=="")
     {
      Alert("Application ID has not been set, It is required for the program to work");
      return(false);
     }

   m_symbol_name=(StringFind(sy,"BINARY_")>=0)?StringSubstr(sy,7):sy;
   m_chart_tf=chart_tf;

   Comment("Initializing Symbol "+m_symbol_name+".......");

   if(!GetSymbolSettings())
      return(false);

   string s_path=BINARY_SYMBOL_BASE_PATH+symbolSpecs["active_symbols"][m_index]["market_display_name"].ToStr();
   string symbol_description=symbolSpecs["active_symbols"][m_index]["display_name"].ToStr();
   double s_point=symbolSpecs["active_symbols"][m_index]["pip"].ToDbl();
   int s_digits=(int)MathAbs(MathLog10(s_point));

   if(!InitSymbol(s_path))
      return(false);

   if(m_new)
     {
      if(!SetProperty(SYMBOL_DESCRIPTION,symbol_description) ||
         !SetProperty(SYMBOL_POINT,s_point)                 ||
         !SetProperty(SYMBOL_DIGITS,s_digits))
        {
         Print("error setting symbol properties ", ::GetLastError());
         return(false);
        }
     }

   Comment("Symbol "+m_symbol_name+" initialized.......");

           return(true);
  }

Nach der Initialisierung des Symbols müssen wir entweder Kurs- oder Tickdaten abrufen, um den Chart zu erstellen. Dies geschieht mit der Methode UpdateHistory(). Nachdem die Historie in das Terminal geladen wurde, wird ein neuer Chart für das benutzerdefinierte Symbol geöffnet, sofern er noch nicht existiert. In dem unten gezeigten Code gibt es zwei Versionen der Methode UpdateHistory(). Die erste verwendet Balken-Daten, um die Historie zu füllen, während die zweite auf Tick-Daten basiert.

//+------------------------------------------------------------------+
//|method for updating the tick history for a particular symbol      |
//+------------------------------------------------------------------+
bool CBinarySymbol::UpdateHistory(void)
  {
   if(websocket.ClientState()!=CONNECTED && !websocket.Connect(m_url))
     {
      Print(websocket.LastErrorMessage()," : ",websocket.LastError());
      return(false);
     }

   Comment("Updating history for "+m_symbol_name+".......");

   MqlRates history_candles[];
   string history=NULL;
   json.Clear();

   json["ticks_history"]=m_symbol_name;

   if(m_new)
     {
      if(m_history_start>0)
        {
         json["start"]=(int)(m_history_start);
        }
     }
   else
      if(m_history_end!=0)
        {
         json["start"]=(int)(m_history_start);
        }


   json["end"]="latest";
   json["style"]="candles";

   if(!websocket.SendString(json.Serialize()))
     {
      Print(websocket.LastErrorMessage());
      return(false);
     }

   if(websocket.ReadString(history))
     {
      json.Deserialize(history);

      if(CheckBinaryError(json))
         return(false);

      int i=0;


      if(ArrayResize(history_candles,(json["candles"].Size()),100)<0)
        {
         Print("Last error is "+IntegerToString(::GetLastError()));
         return(false);
        }
          
      while(json["candles"][i]["open"].ToDbl()!=0.0)
        {
         history_candles[i].close=json["candles"][i]["close"].ToDbl();
         history_candles[i].high=json["candles"][i]["high"].ToDbl();
         history_candles[i].low=json["candles"][i]["low"].ToDbl();
         history_candles[i].open=json["candles"][i]["open"].ToDbl();
         history_candles[i].tick_volume=4;
         history_candles[i].real_volume=0;
         history_candles[i].spread=0;
         history_candles[i].time=(datetime)json["candles"][i]["epoch"].ToInt();
         i++;
        }


      if(ArraySize(history_candles)>0)
        {
         if(CustomRatesUpdate(m_symbol_name,history_candles)<0)
           {
            Print("Error adding history "+IntegerToString(::GetLastError()));
            return(false);
           }
        }
      else
        {
         Print("Received unexpected response from server ",IntegerToString(::GetLastError()), " "+history);
         return(false);
        }
     }
   else
     {
      Print("error reading "," error: ",websocket.LastError(), websocket.LastErrorMessage());
      return(false);
     }

   OpenChart();

   return(true);

  }

//+------------------------------------------------------------------+
//|method for updating the tick history for a particular symbol      |
//+------------------------------------------------------------------+
bool CBinarySymbol::UpdateHistory(void)
  {
   if(websocket.ClientState()!=CONNECTED && !websocket.Connect(m_url))
     {
      Print(websocket.LastErrorMessage()," : ",websocket.LastError());
      return(false);
     }

   Comment("Updating history for "+m_symbol_name+".......");

   MqlTick history_ticks[];
   string history=NULL;
   json.Clear();

   json["ticks_history"]=m_symbol_name;

   if(m_new)
     {
      if(m_history_start>0)
        {
         json["start"]=(int)(m_history_start);
        }
     }
   else
      if(m_history_end!=0)
        {
         json["start"]=(int)(m_history_start);
        }


   json["count"]=m_max_ticks;
   json["end"]="latest";
   json["style"]="ticks";

   if(!websocket.SendString(json.Serialize()))
     {
      Print(websocket.LastErrorMessage());
      return(false);
     }

   if(websocket.ReadString(history))
     {
      json.Deserialize(history);

      if(CheckBinaryError(json))
         return(false);

      int i=0;


      int z=i;
      int diff=0;


      while(json["history"]["prices"][i].ToDbl()!=0.0)
        {

         diff=(i>0)?(int)(json["history"]["times"][i].ToInt() - json["history"]["times"][i-1].ToInt()):0;//((m_history_end>0)?(json["history"]["times"][i].ToInt() - (int)(m_history_end)):0);

         if(diff > 1)
           {
            int k=z+diff;
            int p=1;

            if(ArrayResize(history_ticks,k,100)!=k)
              {
               Print("Memory allocation error,  "+IntegerToString(::GetLastError()));
               return(false);
              }

            while(z<(k-1))
              {
               history_ticks[z].bid=json["history"]["prices"][i-1].ToDbl();
               history_ticks[z].ask=0;
               history_ticks[z].time=(datetime)(json["history"]["times"][i-1].ToInt()+p);
               history_ticks[z].time_msc=(long)((json["history"]["times"][i-1].ToInt()+p)*1000);
               history_ticks[z].last=0;
               history_ticks[z].volume=0;
               history_ticks[z].volume_real=0;
               history_ticks[z].flags=TICK_FLAG_BID;
               z++;
               p++;
              }

            history_ticks[z].bid=json["history"]["prices"][i].ToDbl();
            history_ticks[z].ask=0;
            history_ticks[z].time=(datetime)(json["history"]["times"][i].ToInt());
            history_ticks[z].time_msc=(long)((json["history"]["times"][i].ToInt())*1000);
            history_ticks[z].last=0;
            history_ticks[z].volume=0;
            history_ticks[z].volume_real=0;
            history_ticks[z].flags=TICK_FLAG_BID;

            i++;
            z++;
           }
         else
           {
            if(ArrayResize(history_ticks,z+1,100)==(z+1))
              {
               history_ticks[z].bid=json["history"]["prices"][i].ToDbl();
               history_ticks[z].ask=0;
               history_ticks[z].time=(datetime)json["history"]["times"][i].ToInt();
               history_ticks[z].time_msc=(long)(json["history"]["times"][i].ToInt()*1000);
               history_ticks[z].last=0;
               history_ticks[z].volume=0;
               history_ticks[z].volume_real=0;
               history_ticks[z].flags=TICK_FLAG_BID;
              }
            else
              {
               Print("Memory allocation error,  "+IntegerToString(::GetLastError()));
               return(false);
              }

            i++;
            z++;
           }
        }

      //Print("z is ",z,". Arraysize is ",ArraySize(history_ticks));

      if(m_history_end>0 && z>0)
        {
         DeleteAllCharts();

         if(CustomTicksDelete(m_symbol_name,int(m_history_start)*1000,(history_ticks[0].time_msc-1000))<0)
           {
            Print("error deleting ticks ", ::GetLastError());
            return(false);
           }
         else
           {
            m_history_end=history_ticks[z-1].time;
            m_history_start=history_ticks[0].time;
           }
        }


      if(ArraySize(history_ticks)>0)
        {
         //ArrayPrint(history_ticks);
         if(CustomTicksAdd(m_symbol_name,history_ticks)<0)//CustomTicksReplace(m_symbol_name,history_ticks[0].time_msc,history_ticks[z-1].time_msc,history_ticks)
           {
            Print("Error adding history "+IntegerToString(::GetLastError()));
            return(false);
           }
        }
      else
        {
         Print("Received unexpected response from server ",IntegerToString(::GetLastError()), " "+history);
         return(false);
        }
     }
   else
     {
      Print("error reading "," error: ",websocket.LastError(), websocket.LastErrorMessage());
      return(false);
     }

   OpenChart();

   return(true);

  }

Da der Verlauf aktualisiert wurde und der Chart geöffnet ist, besteht der nächste Schritt darin, einen Tick-Datenstrom von Binary.com zu abonnieren. Die Methode StartTicksStream() sendet die entsprechende Abfrage und wenn sie erfolgreich ist, beginnt der Server mit dem Streaming von Live-Kursen, die von der Methode AddTick() verarbeitet werden. Die Methode StopTicksStream() hingegen wird verwendet, um dem Server mitzuteilen, dass er das Senden von Live-Kursen einstellen soll.

//+---------------------------------------------------------------------+
//|method that enables the reciept of new ticks as they become available|
//+---------------------------------------------------------------------+
bool CBinarySymbol::StartTicksStream(void)
  {
   Comment("Starting live ticks stream for "+m_symbol_name+".......");

   if(m_stream_id!="")
      StopTicksStream();

   json.Clear();
   json["subscribe"]=1;
   json["ticks"]=m_symbol_name;

   return(websocket.SendString(json.Serialize()));
  }

//+------------------------------------------------------------------+
//|Used to cancel all tick streams that may have been initiated      |
//+------------------------------------------------------------------+
bool CBinarySymbol::StopTicksStream(void)
  {
   
   json.Clear();
   json["forget_all"]="ticks";

   if(websocket.SendString(json.Serialize()))
     {
      m_stream_id=NULL;
      if(websocket.ReadString(m_stream_id)>0)
        {
         m_stream_id=NULL;
         Comment("Stopping live ticks stream for  "+m_symbol_name+".......");
         return(true);
        }
     }

   return(false);
  }



//+------------------------------------------------------------------+
//|Overridden method that handles new ticks streamed from binary.com |
//+------------------------------------------------------------------+
void CBinarySymbol::AddTick(void)
  {
   string str_tick;

   MqlTick current_tick[1];

   json.Clear();

   if(websocket.ReadString(str_tick))
     {
      json.Deserialize(str_tick);
      ZeroMemory(current_tick);

      if(CheckBinaryError(json))
         return;

      if(!json["tick"]["ask"].ToDbl())
         return;

      current_tick[0].ask=json["tick"]["ask"].ToDbl();
      current_tick[0].bid=json["tick"]["bid"].ToDbl();
      current_tick[0].last=0;
      current_tick[0].time=(datetime)json["tick"]["epoch"].ToInt();
      current_tick[0].time_msc=(long)((json["tick"]["epoch"].ToInt())*1000);
      current_tick[0].volume=0;
      current_tick[0].volume_real=0;

      if(current_tick[0].ask)
         current_tick[0].flags|=TICK_FLAG_ASK;
      if(current_tick[0].bid)
         current_tick[0].flags|=TICK_FLAG_BID;

      if(m_stream_id==NULL)
         m_stream_id=json["tick"]["id"].ToStr();

      if(CustomTicksAdd(m_symbol_name,current_tick)<0)
        {
         Print("failed to add new tick ", ::GetLastError());
         return;
        }
      Comment("New ticks for  "+m_symbol_name+".......");
     }
   else
     {
      Print("read error ",websocket.LastError(), websocket.LastErrorMessage());

      websocket.ResetLastError();

      if(websocket.ClientState()!=CONNECTED && websocket.Connect(m_url))
        {
         if(m_stream_id!=NULL)
            if(StopTicksStream())
              {
               if(InitSymbol())
                  if(UpdateHistory())
                    {
                     StartTicksStream();
                     return;
                    }
              }
        }
     }
//---
  }

Der Code für den EA ist unten dargestellt.

CBinarySymbol b_symbol;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   b_symbol.SetAppID(binary_appid);
//---
   if(!b_symbol.Initialize(EnumToString(binary_symbol)))
      return(INIT_FAILED);
//---
   if(!b_symbol.UpdateHistory())
      return(INIT_FAILED);
//---
   if(!b_symbol.StartTicksStream())
      return(INIT_FAILED);
//--- create timer
   EventSetMillisecondTimer(500);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
//--- stop the ticks stream
   b_symbol.StopTicksStream();

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---

  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   b_symbol.AddTick();
  }
//+------------------------------------------------------------------+

Beide EAs haben den gleichen Code, mit Ausnahme der Methode UpdateHistory().

Wenn Sie den EA ausführen, wird ein neues nutzerdefiniertes Symbol erstellt, wie hier gezeigt.

EA-Demo

Schlussfolgerung

Wir haben untersucht, wie man die Win32-Api verwendet, um einen Websocket-Client für mt5 zu erstellen. Wir haben eine Klasse erstellt, die diese Funktionalität kapselt und ihre Verwendung in einem Expert Advisor demonstriert, der mit der Binary.com Websockets API interagiert.

Verzeichnis
Inhalt
Beschreibung
MT5zip\Mt5zip\Mql5\include
JAson.mqh, websocket.mqh,winhttp.mqh
Die Include-Dateien enthalten den Code für den JSON-Parser (Klasse CJAval), den Websocket-Client (Klasse CWebsocket) und die in WinHttp importierten Funktionen bzw. Typdeklarationen.
  MT5zip\ Mt5zip\Mql5\ Experts  BinaryCustomSymboWithTickHistory.mq5,BinaryCustomSymbolWithBarHistory.mq5  Beispiele für Expert Advisors, die die CWebsocket-Klasse verwenden, um nutzerdefinierte Symbole zu erstellen, indem sie die Binary.com Websocket-API nutzen


Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/10275

Beigefügte Dateien |
Mt5.zip (23.93 KB)
Grafiken in der Bibliothek DoEasy (Teil 89): Programmieren von grafischen Standardobjekten, grundlegende Funktionsweise Grafiken in der Bibliothek DoEasy (Teil 89): Programmieren von grafischen Standardobjekten, grundlegende Funktionsweise
Derzeit ist die Bibliothek in der Lage, Standard-Grafikobjekte auf dem Client-Terminal-Chart zu kontrollieren, einschließlich ihrer Entfernung und Änderung einiger ihrer Parameter. Derzeit fehlt die Möglichkeit, grafische Standardobjekte aus nutzerdefinierten Programmen zu erstellen.
Ein manuelles Chart- und Handelswerkzeug (Teil III). Optimierungen und neue Werkzeuge Ein manuelles Chart- und Handelswerkzeug (Teil III). Optimierungen und neue Werkzeuge
In diesem Artikel werden wir die Idee des Zeichnens von grafischen Objekten auf Charts mit Hilfe von Tastenkombinationen weiterentwickeln. Der Bibliothek wurden neue Werkzeuge hinzugefügt, darunter eine gerade Linie, die durch beliebige Scheitelpunkte gezeichnet wird, und eine Reihe von Rechtecken, die die Auswertung der Umkehrzeit und des Levels ermöglichen. Außerdem zeigt der Artikel die Möglichkeit, den Code zu optimieren, um die Leistung zu verbessern. Das Implementierungsbeispiel wurde umgeschrieben, sodass Shortcuts neben anderen Handelsprogrammen verwendet werden können. Erforderliche Code-Kenntnisse: etwas höher als die eines Anfängers.
Lernen Sie warum und wie Sie Ihr algorithmisches Handelssystem entwerfen Lernen Sie warum und wie Sie Ihr algorithmisches Handelssystem entwerfen
Dieser Artikel zeigt die Grundlagen von MQL für Anfänger, um ihr Algorithmisches Handelssystem (Expert Advisor) zu entwerfen, indem sie ein einfaches algorithmisches Handelssystem entwerfen, nachdem sie einige Grundlagen von MQL5 erwähnt haben.
MQL5 Kochbuch – Der Wirtschaftskalender MQL5 Kochbuch – Der Wirtschaftskalender
Der Artikel hebt die Programmierfunktionen des Wirtschaftskalenders hervor und befasst sich mit der Erstellung einer Klasse für einen vereinfachten Zugriff auf die Kalendereigenschaften und den Empfang der Ereigniswerte. Als praktisches Beispiel dient die Entwicklung eines Indikators, der die nicht-kommerziellen Nettopositionen der CFTC verwendet.