English Русский 中文 Español Deutsch Português
preview
MetaTrader 5のWebSocket — WindowsAPIの使用

MetaTrader 5のWebSocket — WindowsAPIの使用

MetaTrader 5 | 18 4月 2022, 14:13
1 021 12
Francis Dube
Francis Dube

はじめに

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に依存しています。

Binary開発者のWebページ



このアプリケーションは、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 - 銘柄履歴がロードされたときに開かれるチャートの期間を設定
このメソッドはInitsymbol()を呼び出します。これは、新しいカスタム銘柄がまだ存在しない場合は作成するか、銘柄が存在する場合は履歴プロパティをロードします。

他の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のコードは同じです。

EAを実行すると、次に示すように新しいカスタム銘柄が作成されます。

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

添付されたファイル |
Mt5.zip (23.93 KB)
最後のコメント | ディスカッションに移動 (12)
Soewono Effendi
Soewono Effendi | 2 2月 2022 において 01:57
Kareem Abdelhakim #:

typedefを使って 、このように関数へのポインタを定義しました。

そして、msdnに従ってwinhttp.dllからWinHttpSetStatusCallBack()をインポートしました。

ああ、いい試みだ :)
私もこれが可能であることを望んでいましたが、フォーラムを検索すると、MQLの関数はメモリアドレスではなくハンドルであり、「C/C++」コールバック-APIで必要であることがわかります。

いつかMQLが「本当の」Function Pointerを追加してくれるかもしれませんね。

Kareem Abdelhakim
Kareem Abdelhakim | 5 2月 2022 において 12:27
Soewono Effendi #:

うん、いい試みだ :)
私もこれが可能になることを望んでいましたが、フォーラムを検索すると、MQLの関数は、「C/C++」コールバック-APIで必要とされるメモリアドレスではなく、ハンドルであることがわかります。

いつかMQLが「本当の」関数ポインタを追加するかもしれませんね。

早くネイティブでサポートされることを望みます。

Faisal Mahmood
Faisal Mahmood | 7 2月 2022 において 02:26

@Francis Dube WebSocketサーバーとして動作するMQL5サービスを作成することは可能ですか?何か例はありますか?

Francis Dube
Francis Dube | 8 2月 2022 において 13:22
Faisal Mahmood #:

@Francis Dube WebSocketサーバーとして動作するMQL5サービスを作成することは可能ですか?いくつかの例がありますか?

それはサーバーではなく、WebSocketクライアントです。

dark_sam
dark_sam | 21 2月 2022 において 00:12
こんにちは、VPSでは 動作せず、通常のローカルPCで試したところ良好でした。 VPSで何かポートを開く必要があるのでしょうか?
アルゴリズム取引システムを設計する理由と方法を学ぶ アルゴリズム取引システムを設計する理由と方法を学ぶ
この記事では、MQL5のいくつかの基本に言及した後で、単純なアルゴリズム取引システムを設計することによって初心者がアルゴリズム取引システム(エキスパートアドバイザー)を設計するためのMQLの基本を示します。
マーケットからエキスパートアドバイザーを選択する正しい方法 マーケットからエキスパートアドバイザーを選択する正しい方法
この記事では、エキスパートアドバイザーを購入する際に注意すべき重要なポイントのいくつかを検討します。また、利益を増やし、お金を賢く使ってこの支出から利益を得る方法を探します。また、記事を読み終われば、シンプルで無料の製品を使用しても収益を得られることがわかると思います。
DoEasyライブラリのグラフィックス(第94部): 複合グラフィカルオブジェクトの移動と削除 DoEasyライブラリのグラフィックス(第94部): 複合グラフィカルオブジェクトの移動と削除
本稿では、さまざまな複合グラフィカルオブジェクトイベントの開発を開始します。また、複合グラフィカルオブジェクトの移動と削除についても部分的に検討します。実際、ここでは、前の記事で実装したものを微調整します。
取引における数学:シャープレシオとソルティノレシオ 取引における数学:シャープレシオとソルティノレシオ
投資収益率は、投資家や初心者のトレーダーが取引効率の分析に使用する最も明白な指標です。プロのトレーダーは、シャープレシオやソルティノレシオなどのより信頼性の高いツールを使用して、ストラテジーを分析します。