MetaTrader 5のWebSocket — WindowsAPIの使用
はじめに
Metatrader 5のWebsockets稿では、WebSocketプロトコルの基本について説明し、MQL5で実装されたソケットに依存するクライアントを作成しました。今回は、Windows APIを活用して、MetaTrader 5プログラム用のWebSocketクライアントを構築します。追加のソフトウェアは必要なくすべてがオペレーティングシステムによって提供されるため、これは次善のオプションです。クライアントをクラスとして実装し、Binary.com WebSocket APIを使用してライブティックデータをMetaTrader 5にフィードすることでテストを実施します。
WindowsのWebsockets
Windows APIとインターネットに関しては、MQL5開発者はWindowsインターネット(WinINeT)ライブラリに最も精通しています。ファイル転送プロトコル(FTP)やHTTPなどのインターネットプロトコルが実装されており、Windows HTTPサービス(WinHTTP)ライブラリに似ています。これは、サーバーサイドの開発に役立つ機能を備えたHTTPプロトコル専用のライブラリです。WinHTTPによって公開される機能の一部は、WebSocket接続を処理するためのユーティリティです。
WebSocketプロトコルは、Windows8.1およびWindowsServer 2012 R2以降のWindowsオペレーティングシステムに導入されたもので、Windows 7以前のオペレーティングシステムで、ネイティブサポートを備えていません。この記事で説明するプログラムは、これらの古いオペレーティングシステムを実行しているマシンでは機能しません。
Winhttpライブラリ
winhttpを使用してWebSocketクライアント接続を作成するには、以下にリストされている関数が必要です。
WinHttpOpen | ライブラリを初期化して、アプリケーションで使用できるように準備 |
WinHttpConnect | アプリケーションが通信するサーバーのドメイン名を設定 |
WinHttpOpenRequest | HTTPリクエストハンドルを作成 |
WinHttpSetOption | HTTP接続のさまざまな構成オプションを設定 |
WinHttpSendRequest | サーバーにリクエストを送信 |
WinHttpReceiveResponse | リクエストを送信した後、サーバーから応答を受信 |
WinHttpWebSocketCompleteUpgrade | サーバーから受信した応答がWebSocketプロトコルを満たしていることを確認 |
WinHttpCloseHandle | 以前に使用されていたリソース記述子を破棄するために使用 |
WinHttpWebSocketSend | WebSocket接続を介してデータを送信するために使用 |
WinHttpWebSocketReceive | WebSocket接続を使用してデータを受信 |
WinHttpWebSocketClose | WebSocket接続を閉じる |
WinHttpWebSocketQueryCloseStatus | サーバーから送信されたクローズステータスメッセージを確認 |
ライブラリで使用可能なすべての関数はMicrosoftによって文書化されています。また、上記の対応するリンクをたどると、すべての関数と入力パラメーターおよび戻り値の型の詳細な説明を表示できます。
MetaTrader5用に作成するクライアントは同期モードで動作します。つまり、関数呼び出しは、返されるまで実行をブロックします。たとえば、WinHttpWebSocketReceive()を呼び出すと、データを読み取ることができるようになるまで、実行中のスレッドがブロックされます。MetaTrader 5アプリケーションを作成するときは、このことに注意してください。
winhttp関数は宣言され、winhttp.mqhインクルードファイルにインポートされます。
#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 //+------------------------------------------------------------------+
winhttp関数の使用
これらの関数を使用してWebSocketクライアントを確立するには、まずWinHttpOpen()を呼び出してライブラリを初期化する必要があります。この関数は、他のwinhttpライブラリ関数への後続の呼び出しで使用されるセッションハンドルを返します。
#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; }
2番目の手順は、WinHttpConnect()を使用して実行される接続ハンドルを作成することです。ここでサーバーアドレスとポート番号を指定します。この時点で必要なのはスキームやパスを含まないサーバーのドメイン名であることに注意することが重要です。パブリックIPアドレスは、既知の場合は使用することもできます。winhttpを使用するときに発生するほとんどのエラーは、誤ってフォーマットされたサーバーアドレスを渡すことに関連しています。たとえば、完全なサーバーアドレスがwss://ws.example.com/pathの場合、WinHttpConnect()はws.example.comのみを予期します。
connectionhandle=WinHttpConnect(sessionhandle,server,Port,0); if(connectionhandle==NULL) { Print("WinHttpConnect error "+string(kernel32::GetLastError())); if(sessionhandle!=NULL) WinHttpCloseHandle(sessionhandle); return; }
接続ハンドルが正常に作成されたら、それを使用してWinHttpOpenRequest()を呼び出してリクエストハンドルを確立します。ここでは、サーバーのアドレスからパスコンポーネントがある場合はそれを指定し、接続を安全にするかどうかのオプションも設定します。
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; }
それが完了し、有効なリクエストハンドルができたら、WinHttpSetOption()を呼び出してWebSocketハンドシェイクプロセスの準備をします。
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; }
これにより、WebSocketプロトコルで指定されたhttpリクエストに必要なヘッダーが追加されます。WebSocketハンドシェイクは、WinHttpSendRequest()を呼び出してからWinHttpReceiveResponse()を呼び出して、要求に対する応答の受信を確認することで開始されます。
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()は応答を確認し、WebSocketプロトコルに準拠していることを確認します。準拠している場合、関数は変換されたWebSocketハンドルを返します。
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;
これで、WebSocketクライアントは完全に機能し、WinHttpWebSocketSend()を使用してデータを送信し、WinHttpWebSocketReceive()を使用してデータを受信できるようになります。WebSocketハンドルが作成されてhttp接続がWebSocket接続にアップグレードされたため、リクエストハンドルは不要になります。次に、WinHttpCloseHandle()を呼び出すことにより、リクエストハンドルに関連付けられているリソースを解放できます。
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); } //+------------------------------------------------------------------+
WinHttpWebSocketClose()を呼び出すと、WebSocket接続が閉じられます。接続が閉じられたら、それに関連付けられているすべてのハンドルを呼び出して非初期化する必要があります。
WinHttpCloseHandle() for each.
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; }
CWebsocketクラス
websocket.mqhファイルには、WebSocketクライアントを有効にするために必要なwinhttpライブラリ関数のラッパーとなるCWebsocketクラスが含まれます。
このファイルは、WindowsAPIライブラリからインポートされたすべての関数と宣言を許可するincludeディレクティブで始まります。
#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 };
WebSocketサーバーへの接続プロセスを開始するために最初に呼び出すメソッドはConnect()です。
Connect()パラメータは次の通りです。
- _serveraddress - サーバーの完全なアドレス(型: string)
- _port - サーバーのポート番号(型: ushort)
- _appname - WebSocketクライアントを使用してアプリケーションを一意に識別するために設定できる文字列パラメータで、最初のhttpリクエストのヘッダーの1つとして送信される(型: string)
- _secure —安全な接続を使用するかどうかを設定するブール値(型: boolean)
Connect()メソッドは、privateメソッドinitialize()とupgrade()をそれぞれ呼び出します。privateメソッドinitialize()では、完全なサーバーアドレスを処理し、それをドメイン名とパスのコンポーネントに分割します。最後に、createSessionConnection()はセッションハンドルと接続ハンドルを作成します。upgrade()メソッドは、クライアント接続の新しい状態を設定する前に、リクエストとWebSocketハンドルを作成するために機能します。
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); }
Connect()メソッドが「true」を返すと、WebSocketクライアントを介してデータの送信を開始できます。これを容易にするために使用できるメソッドは2つあります。
SendString()メソッドは文字列を入力として受け取り、Send()メソッドは符号なしの文字配列を唯一の関数パラメータとして受け取ります。両メソッドは成功すると「true」を返し、クラスのすべての送信操作を処理するprivateメソッドclientsend()を呼び出します。サーバーから送信されたデータを読み取るには、Read()またはReadString()のいずれかを使用できます。
//+------------------------------------------------------------------+ //| 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)); }
サーバーから送信されたデータを読み取るには、Read()またはReadString()のいずれかを使用できます。メソッドは、受信したデータのサイズを返します。ReadString()は、受信したデータが書き込まれる参照によって渡される文字列を必要としますが、Read()は符号なし文字配列に書き込みます。
//+------------------------------------------------------------------+ //|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); }
WebSocketクライアントが不要になったら、サーバーへの接続をClose()またはAbort()のいずれかで閉じます。Abort()メソッドはClose()メソッドとは異なり、WebSocket接続を閉じるだけでなく、一部のクラスプロパティの値をリセットして、デフォルトの状態に設定します。
//+------------------------------------------------------------------+ //| 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()は、WebSocketクライアントの現在の状態を照会します。
DomainName()、Port()、およびServerPath()ではそれぞれ、現在の接続のドメイン名、ポート、およびパスコンポーネントを返します。
LastErrorMessage()を使用して、最後のエラーを詳細な文字列として取得できますが、LastError()を呼び出すと、エラーコードが整数値として取得されます。
//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); }
クラス全体を以下に示します。
//+------------------------------------------------------------------+ //|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); } };
WebSocketクラスができたので、その使用例を検討します。
CWebsocketクラスのテスト
テストのために、Binary.comからカスタム銘柄を追加するMetaTrader5アプリケーションを作成します。チャートにロードされると、履歴がダウンロードされ、ライブティックデータで更新されるカスタム銘柄の新しいチャートが開きます。
2つのバージョンがあります。BinaryCustomSymboWithTickHistory.ex5はティック履歴を使用し、他のBinaryCustomSymbolWithBarHistory.ex5はOHLCバー履歴をダウンロードします。どちらも同様のコードになります。
Binary.comでは十分に文書化されたAPI が提供され、開発者はシステムと相互作用するインターフェイスを構築できます。APIは、JSON形式で提供されるクエリと応答を備えたWebSocketに依存しています。
このアプリケーションは、3つの重要なライブラリの助けを借りるエキスパートアドバイザーとして実装されます。
- 1つ目はWebSocket接続を処理するwebsocket.mqhです。
- 2つ目は、AlexeySergeevによって作成されvivazziのgithubリポジトリから取得可能なJSON形式のデータを操作するためのJAson.mqhです。
- 必要な3番目のライブラリは、ファイル操作を処理するためのFileTxt.mqhです。
EAには、次のユーザー調整可能な入力があります。
- binary_appid - アプリケーションにAPIへのアクセスを許可するために必要な文字列パラメタです。アプリIDは、開発者ポータルに記載されている手順に従って取得できます。銘柄ティックストリームをサブスクライブする場合、Binary.comでのユーザー認証は必要ありません。そのため、APIトークンを指定する必要はありません。
- binary_symbol - ユーザーがMetaTrader5にインポートしたい銘柄を選択できるようにする列挙です。
- binary_timeframe - 履歴データがダウンロードされてMetaTrader5に追加されたときに開かれるチャートの時間枠です。
#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
EAにはCCustomSymbolとCBinarySymbolの2つのクラスがあります。
CCustomSymbolクラス
CCustomSymbolは、外部ソースからのカスタム銘柄を操作するためのクラスです。これは、fxsaberのSYMBOLライブラリに触発されています。銘柄のプロパティを操作および取得したり、他の機能の中でも対応するチャートを開いたり閉じたりするためのメソッドを提供します。さらに重要なことに、カスタム銘柄の実装のバリエーションを可能にするために、子クラスがオーバーライドできる3つの仮想メソッドを提供します。
//+------------------------------------------------------------------+ //|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); } };
Initialize()メソッドパラメータは次の通りです。
- sy - カスタム銘柄名を設定する文字列パラメータ
- sy_path - 銘柄パスプロパティを設定する文字列パラメータ
- chart_tf - 銘柄履歴がロードされたときに開かれるチャートの期間を設定
他の2つの仮想メソッド、UpdateHistory()とAddTick()はCCustomSymbolでは実装されていないので、派生クラスでオーバーライドする必要があります。
CBinarySymbolクラス
CBinarySymbolクラスの出番です。CCustomSymbolから継承し、親クラスのすべての仮想メソッドをオーバーライドするメソッドを提供します。ここでは、WebSocketクライアントを使用してBinary.comAPIを使用します。
//+------------------------------------------------------------------+ //|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); };
CBinarySymbolクラスのインスタンスが作成されたら、SetAppID()メソッドを使用して有効なアプリケーション識別子app_idを設定する必要があります。そうして初めて、カスタム銘柄の初期化に進むことができます。
//+------------------------------------------------------------------+ //|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; }
Initialize()メソッドは、getSymbolSpecs()privateメソッドを使用して、選択した銘柄のプロパティを取得します。<分節5986¶>次に、関連情報を使用して、新しいカスタム銘柄の銘柄プロパティを設定します。
//+------------------------------------------------------------------+ //|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); }
銘柄が初期化されたら、チャートを作成するためにレートまたはティックデータのいずれかを取得する必要があります。これは、UpdateHistory()メソッドによって実行されます。履歴をターミナルにロードした後、カスタム銘柄がまだ存在しない場合は、新しいチャートが開きます。以下に示すコードには、UpdateHistory()メソッドの2つのバージョンがあり、最初のバージョンはバーデータを使用して履歴を埋め、2番目のバージョンはティックデータに依存しています。履歴が更新され、チャートが開いているので、次の手順はBinary.comからのティックデータストリームにサブスクライブすることです。
//+------------------------------------------------------------------+ //|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); }
履歴が更新されてチャートが開いているので、次の手順はBinary.comからのティックデータストリームにサブスクライブすることです。StartTicksStream()メソッドは対応するクエリを送信し、成功すると、サーバーはAddTick()メソッドによって処理されるライブ相場のストリーミングを開始します。一方、StopTicksStream()メソッドは、ライブ相場の送信を停止するようにサーバーに通知するために使用されます。
//+---------------------------------------------------------------------+ //|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; } } } } //--- }
EAのコードを以下に示します。
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(); } //+------------------------------------------------------------------+
UpdateHistory()メソッドを除いて、両方のEAのコードは同じです。
終わりに
Win32APIを使用してMetaTrader5用のWebSocketクライアントを作成する方法を検討しました。この機能をカプセル化するクラスを作成し、Binary.comWebSocketAPIと相互作用するEAでの使用を示しました。
フォルダ | 目次 | 説明 |
---|---|---|
MT5zip\Mt5zip\Mql5\include | JAson.mqh、websocket.mqh、winhttp.mqh | これらのインクルードファイルには、JSONパーサー(CJAvalクラス)、WebSocketクライアント(CWebsocketクラス)、WinHttpインポート関数と型宣言のコードがそれぞれ含まれています。 |
MT5zip\ Mt5zip\Mql5\ Experts | BinaryCustomSymboWithTickHistory.mq5、BinaryCustomSymbolWithBarHistory.mq5 | CWebsocketクラスを使用してBinary.comWebSocketAPIを利用してカスタム銘柄を作成するサンプルEA |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/10275
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
typedefを使って 、このように関数へのポインタを定義しました。
そして、msdnに従ってwinhttp.dllからWinHttpSetStatusCallBack()をインポートしました。
ああ、いい試みだ :)
私もこれが可能であることを望んでいましたが、フォーラムを検索すると、MQLの関数はメモリアドレスではなくハンドルであり、「C/C++」コールバック-APIで必要であることがわかります。
いつかMQLが「本当の」Function Pointerを追加してくれるかもしれませんね。
うん、いい試みだ :)
私もこれが可能になることを望んでいましたが、フォーラムを検索すると、MQLの関数は、「C/C++」コールバック-APIで必要とされるメモリアドレスではなく、ハンドルであることがわかります。
いつかMQLが「本当の」関数ポインタを追加するかもしれませんね。
早くネイティブでサポートされることを望みます。
@Francis Dube WebSocketサーバーとして動作するMQL5サービスを作成することは可能ですか?何か例はありますか?
@Francis Dube WebSocketサーバーとして動作するMQL5サービスを作成することは可能ですか?いくつかの例がありますか?
それはサーバーではなく、WebSocketクライアントです。