English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
MQL5-RPC. MQL5에서의 원격 프로시져 호출(Remote Procedure Call): 재미와 돈을 위한 웹 서비스 접속과 XML-RPC ATC 분석기

MQL5-RPC. MQL5에서의 원격 프로시져 호출(Remote Procedure Call): 재미와 돈을 위한 웹 서비스 접속과 XML-RPC ATC 분석기

MetaTrader 5통합 | 11 10월 2021, 15:56
118 0
investeo
investeo

들어가며

본 문서는 제가 지난 수 주 간 만든 MQL5-RPC 프레임워크를 설명할 것입니다. 이 문서에서는 XML-RPC 접속 기초, MQL5 구현 설명 및 두개의 실전 MQL5-RPC 예제가 담겨있습니다. 첫 예제는 외부 외환거래 홈페이지의 웹서비스를 호출하는 원격 프로시져 호출이고, 두번째 예제는 Automated Trading Championship 2011의 결과를 받아와서 파싱하고 분석하는 자체 XML-RPC 서버용 클라이언트입니다.. 만약 리얼타임으로 ATC 2011의 각기 다른 통계 자료를 구현하고 분석하는지 보고싶으시다면 이 문서는 바로 당신을 위한 것입니다.


XML-RPC 기초

XML-RPC 기초부터 다뤄봅시다. XML-RPC이란 XML Remote Procedure Call의 약자입니다. 외부 메소드를 호출하기 위해 전달된 패러미터를 인코딩 및 디코딩하기 위해 XML을 사용하는 네트워크 프로토콜입니다. HTTP 프로토콜을 전송 메커니즘으로 사용하여 데이터를 교환합니다. 외부 방법으로는 원격 프로시저를 노출하는 다른 컴퓨터 프로그램이나 웹 서비스를 의미합니다.

노출된 메소드는 XML-RPC 프로토콜 스택을 사용하고 서버에 대한 네트워크 액세스 권한을 가진 경우 네트워크에 연결된 모든 컴퓨터에서 모든 컴퓨터 언어로 호출할 수 있습니다. 이는 또한 XML-RPC를 사용하여 다른 프로그래밍 언어로 작성된 동일한 시스템에서 메소드를 호출할 수 있음을 의미합니다. 이 내용은 이 문서의 두번째 파트에서 보실 수 있습니다.


XML-RPC 데이터 모델

XML-RPC 사양에는 6개의 기본 자료형이 있습니다. int, double, boolean, string, datetime, base64 그리고 두개의 복합 자료형 array and struct. 어레이는 모든 기본 요소로 구성될 수 있으며 스트럭트는 연관 배열 또는 객체 특성과 같은 이름-값 쌍을 제공합니다.


XML-RPC의 기본 자료형
 타입 값 예시
int 혹은 i42,147,483,648 및 2,147,483,647 사이의 32-bit 정수.<int>11<int>
<i4>12345<i4>
double64-bit 소수 <double>30.02354</double>
<double>-1.53525</double>
Boolean참 (1) or 거짓 (0)<boolean>1</boolean>
<boolean>0</boolean>
스트링ASCII 텍스트, 다양한 구현이 유니코드를 지원함<string>Hello</string>
<string>MQL5</string>
dateTime.iso8601ISO8601 포맷을 따른 일자: CCYYMMDDTHH:MM:SS<dateTime.iso8601>
20111125T02:20:04
</dateTime.iso8601>
<dateTime.iso8601>
20101104T17:27:30
</dateTime.iso8601>
base64RFC 2045을 따라 만들어진 이진 정보 인코딩<base64>
TDVsbG8sIFdvdwxkIE==
</base64>


1번 테이블. XML-RPC의 기본 자료형

어레이는 어떠한 기본 자료형이건 담을 수 있고, 꼭 같은 타입일 필요도 없습니다. 어레이 요소는 값 요소 안에 자리잡아야합니다. 데이터 요소에는 데이터 요소 하나와 데이터 요소에 있는 값 요소가 하나 이상 포함됩니다. 아래 예제는 네 개의 인트 값의 어레이를 보여줍니다.

<value>
   <array>
      <data>
         <value><int>111</int></value>
         <value><int>222</int></value>
         <value><int>-3456</int></value>
         <value><int>666</int></value>
      </data>
   </array>
</value>

두 번째 예에서는 5개의 스트링 값의 배열을 보여 줍니다.

<value>
   <array>
      <data>
         <value><string>MQL5</string></value>
         <value><string>is </string></value>
         <value><string>a</string></value>
         <value><string>great</string></value>
         <value><string>language.</string></value>
      </data>
   </array>
</value>

이 두 가지 예에서 유사한 점을 찾아 다른 XML-RPC 어레이를 구축할 수 있으실 것으로 확신합니다.

스트럭트에는 값 요소 내부의 스트럭트 요소와 스트럭트 요소 내부의 멤버 섹션이 있습니다. 각 멤버는 이름과 값을 가지고있습니다. 따라서 스트럭트를 사용하여 연관 배열 또는 객체의 구성원 값을 전달하기가 쉽습니다.

아래의 예시를 보아주십시오.

<value>
   <struct>
      <member>
         <name>AccountHolder</name>
         <value><string>John Doe</string></value>
      </member>
      <member>
         <name>Age</name>
         <value><int>77</int></value>
      </member>
      <member>
         <name>Equity</name>
         <value><double>1000000.0</double></value>
      </member>
   </struct>
</value>

XML-RPC 자료 모델에 좀 익숙해졌으니 이제 리퀘스트와 리스폰스 구조를 살펴볼 것입니다. 이를 통해 MQL5으로 XML-RPC 클라이언트를 짜는 기초가 다져질 것입니다.


XML-RPC 요청 구조

XML-RPC 요청은 메세지 헤더와 메세지 페이로드로 되어있습니다. 메세지 헤더는 HTTP 송신 메소드 (POST)를 명시하고, XML-RPC 서비스로 향하는 상대 경로, HTTP 프로토콜 버전, user-agent 명, 호스트 IP 주소, content type(text/xml) 그리고 바이트로 표시된 컨텐트 길이를 담고 있습니다. 

POST /xmlrpc HTTP 1.1
User-Agent: mql5-rpc/1.0
Host: 10.12.10.10
Content-Type: text/xml
Content-Length: 188

XML-RPC 리퀘스트의 페이로드는 XML 문서입니다. XML 트리의 루트 요소는 methodCall이라 불려야합니다. methodCall에는 실행된 메소드 이름을 가진 methodName 요소가 하나 포함되어 있습니다. MethodName 요소에 0 또는 1개의 패러미터 요소가 포함되어 있습니다.

패러미터 요소에는 하나 이상의 값, 어레이 또는 스트럭트가 포함됩니다. 모든 값은 데이터 형식으로 인코딩됩니다(위 표 참조). 함수에 전달할 두 개의 double 값을 가진 'multiply' 메소드 실행 리퀘스트을 보여주는 아래 페이로드를 참조하십시오.

<?xml version="1.0"?>
<methodCall>
   <methodName>multiply</methodName>
      <params>
         <param>
            <value><double>8654.41</double></value>
         </param>
         <param>
            <value><double>7234.00</double></value>
         </param>
      </params>
</methodCall>

헤더와 페이로드는 HTTP를 통해 입력을 수신하는 서버로 전송됩니다. 서버가 사용 가능한 경우 메소드 이름과 패러미터 목록을 확인하고 원하는 메소드를 실행합니다. 처리가 완료되면 클라이언트에서 읽을 수 있는 XML-RPC 리스폰스 구조를 준비합니다.

 

XML-RPC 리스폰스 구조

XML-RPC 리퀘스트처럼, XML-RPC 리스폰스는 헤더와 페이로드로 구성되어 있습니다. 헤더는 텍스트고 페이로드는 XML 문서입니다. 요청이 올바른 헤더인 경우 첫 번째 행은 서버가 발견되었음을 알리고(200 코드) 프로토콜 버전을 지정합니다. 헤더에는 Content-Type text/xml과 페이로드 길이(바이트)인 Content-Length도 포함되어야 합니다.

HTTP/1.1 200 OK
Date: Tue, 08 Nov 2011 23:00:01 GMT
Server: Unix
Connection: close
Content-Type: text/xml
Content-Length: 124

당연히 리스폰스 페이로드도 XML 문서입니다. XML 트리의 루트 요소는 methodResponse여야 합니다. methodResponse 요소에는 성공 요소에 대한 패러미터 하나 또는 실패 시 오류 요소가 하나 포함되어 있습니다. 패러미터 요소에는 정확히 하나의 패러미터 요소가 포함되어 있습니다. 패러미터\ 요소에는 정확히 하나의 값 요소가 포함되어 있습니다.

성공적인 리스폰스의 예시는 다음과 같습니다. 

<?xml version="1.0"?>
<methodResponse>
   <params>
      <param>
         <value><double>62606001.94</double></value>
      </param>
   </params>
</methodResponse>

실패 리스폰스는 만약 XML-RPC 리퀘스트 처리 중 문제가 발생하였을 때에 쓰입니다.

폴트 요소는 다른 패러미터 요소들처럼 하나의 출력 값을 가집니다.

<?xml version="1.0"?>
<methodResponse>
   <fault>
      <value><string>No such method!</string></value>
   </fault>
</methodResponse>

XML-RPC는 에러 코드를 표준화하지않기때문에 에러 메세지는 구현에따라 다릅니다.


MQL5-RPC 소개

저는 Alex Sergeev가 작성한 두 글, "Using WinInet.dll for Data Exchange between Terminals via the Internet""Using WinInet in MQL5. Part 2: POST Requests and Files"을 읽게 되었고, MetaTrader 5를 위한 XML-RPC 클라이언트를 구현할 수 있음을 깨달았습니다. 사양을 확인한 후 스스로 코드를 짜냈습니다. 이 프로젝트는 진행 중이며 전체 사양을 다루지는 않지만(base64 지원이 가까운 시일 내에 추가될 예정) MetaTrader 5에서 XML-RPC 호출의 큰 부분 집합을 만드는 데 이미 사용할 수 있습니다.


MQL5-RPC 데이터 모델

제가 구현하면서 가장 어려웠던 부분은 MQL5에 대한 정확한 데이터 모델을 알아내는 것이었습니다. 프레임워크 사용자에게 최대한 간단해야 한다고 판단하여 기능을 캡슐화하는 클래스를 여러 개 구축했습니다. 첫 번째 결정은 리퀘스트 패러미터 데이터를 단일 CObject* 포인터로 만드는 것이었습니다. 이 포인터는 포인터 어레이를 CObject 클래스에서 파생된 어레이로 가리키게 해줍니다.

CObject 어레이를 담는 표준 클래스들이 있습니다. CArrayInt, CArrayDouble, CArrayString, 따라서 이걸 기반으로 CArrayBool, CArrayDatetime를 추가 생성하여 기본 자료형을 모두 다룰 수 있게 했으며, 스트럭트 어레이를 만들 수 있게 CArrayMqlRates를 추가했습니다. 지금은 base64 유형만 누락되어 있지만 가까운 시일 내에 지원될 예정입니다. 어레이에 요소가 하나만 포함되어 있으면 단일 값 요소로 XML에 캡슐화됩니다.

CObject* 어레이에 서로 다른 어레이를 추가하고 서로 다른 유형의 전체 어레이를 표시하는 방법에 대한 예제를 작성했습니다. 이 예시는 아래에서 찾아보실 수 있습니다. 

//+------------------------------------------------------------------+
//|                                                 ArrayObjTest.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                               http://Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://Investeo.pl"
#property version   "1.00"
//---
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayDouble.mqh>
#include <Arrays\ArrayString.mqh>
#include <Arrays\ArrayBool.mqh>
#include <Arrays\ArrayMqlRates.mqh>
#include <Arrays\ArrayDatetime.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CArrayObj* params = new CArrayObj;
   
   CArrayInt* arrInt = new CArrayInt;
   
   arrInt.Add(1001);
   arrInt.Add(1002);
   arrInt.Add(1003);
   arrInt.Add(1004);
   
   CArrayDouble* arrDouble = new CArrayDouble;
   
   arrDouble.Add(1001.0);
   arrDouble.Add(1002.0);
   arrDouble.Add(1003.0);
   arrDouble.Add(1004.0);
      
   CArrayString* arrString = new CArrayString;
   
   arrString.Add("s1001.0");
   arrString.Add("s1002.0");
   arrString.Add("s1003.0");
   arrString.Add("s1004.0");
   
   CArrayDatetime* arrDatetime = new CArrayDatetime;
   
   arrDatetime.Add(TimeCurrent());
   arrDatetime.Add(TimeTradeServer()+3600);
   arrDatetime.Add(TimeCurrent()+3600*24);
   arrDatetime.Add(TimeTradeServer()+3600*24*7);
   
   CArrayBool* arrBool = new CArrayBool;
   
   arrBool.Add(false);
   arrBool.Add(true);
   arrBool.Add(true);
   arrBool.Add(false);
   
   CArrayMqlRates* arrRates = new CArrayMqlRates;
   
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   int copied=CopyRates(Symbol(),0,0,4,rates);
   
   arrRates.Add(rates[0]);
   arrRates.Add(rates[1]);
   arrRates.Add(rates[2]);
   arrRates.Add(rates[3]);
   
   params.Add(arrInt);
   params.Add(arrDouble);
   params.Add(arrString);
   params.Add(arrDatetime);
   params.Add(arrBool);
   params.Add(arrRates);
   
   Print("params has " + IntegerToString(params.Total()) + " arrays.");
 
   for (int p=0; p<params.Total(); p++)
   {
      int type = params.At(p).Type();
      
      switch (type) {
         case TYPE_INT: { 
            CArrayInt *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               PrintFormat("%d %d %d", p, i, arr.At(i));
            break; }
         case TYPE_DOUBLE: { 
            CArrayDouble *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               PrintFormat("%d %d %f", p, i, arr.At(i));
            break; }
         case TYPE_STRING: { 
            CArrayString *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               PrintFormat("%d %d %s", p, i, arr.At(i));
            break; }
         case TYPE_BOOL: { 
            CArrayBool *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               if (arr.At(i) == true)
                  PrintFormat("%d %d true", p, i);
               else
                  PrintFormat("%d %d false", p, i);
            break; }
         case TYPE_DATETIME: { 
            CArrayDatetime *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               PrintFormat("%d %d %s", p, i, TimeToString(arr.At(i), TIME_DATE|TIME_MINUTES));
            break; }
         case TYPE_MQLRATES: {  //  
            CArrayMqlRates *arr = params.At(p); 
            for (int i=0; i<arr.Total(); i++)
               PrintFormat("%d %d %f %f %f %f", p, i, arr.At(i).open, arr.At(i).high, arr.At(i).low, arr.At(i).close);
            break; }
      };
         
   };
   delete params;
   
  }
//+------------------------------------------------------------------+

결과는 꽤 뻔합니다. 6개의 서브어레이가 완성됐습니다. integer 값 어레이, double 값 어레이, string 값 어레이, datetime 값 어레이, boolean 값 어레이 그리고 MqlRates 값 어레이.

ArrayObjTest (EURUSD,H1)        23:01:54        params has 6 arrays.
ArrayObjTest (EURUSD,H1)        23:01:54        0 0 1001
ArrayObjTest (EURUSD,H1)        23:01:54        0 1 1002
ArrayObjTest (EURUSD,H1)        23:01:54        0 2 1003
ArrayObjTest (EURUSD,H1)        23:01:54        0 3 1004
ArrayObjTest (EURUSD,H1)        23:01:54        1 0 1001.000000
ArrayObjTest (EURUSD,H1)        23:01:54        1 1 1002.000000
ArrayObjTest (EURUSD,H1)        23:01:54        1 2 1003.000000
ArrayObjTest (EURUSD,H1)        23:01:54        1 3 1004.000000
ArrayObjTest (EURUSD,H1)        23:01:54        2 0 s1001.0
ArrayObjTest (EURUSD,H1)        23:01:54        2 1 s1002.0
ArrayObjTest (EURUSD,H1)        23:01:54        2 2 s1003.0
ArrayObjTest (EURUSD,H1)        23:01:54        2 3 s1004.0
ArrayObjTest (EURUSD,H1)        23:01:54        3 0 2011.11.11 23:00
ArrayObjTest (EURUSD,H1)        23:01:54        3 1 2011.11.12 00:01
ArrayObjTest (EURUSD,H1)        23:01:54        3 2 2011.11.12 23:00
ArrayObjTest (EURUSD,H1)        23:01:54        3 3 2011.11.18 23:01
ArrayObjTest (EURUSD,H1)        23:01:54        4 0 false
ArrayObjTest (EURUSD,H1)        23:01:54        4 1 true
ArrayObjTest (EURUSD,H1)        23:01:54        4 2 true
ArrayObjTest (EURUSD,H1)        23:01:54        4 3 false
ArrayObjTest (EURUSD,H1)        23:01:54        5 0 1.374980 1.374980 1.374730 1.374730
ArrayObjTest (EURUSD,H1)        23:01:54        5 1 1.375350 1.375580 1.373710 1.375030
ArrayObjTest (EURUSD,H1)        23:01:54        5 2 1.374680 1.375380 1.373660 1.375370
ArrayObjTest (EURUSD,H1)        23:01:54        5 3 1.375270 1.377530 1.374360 1.374690

다른 데이터 유형의 어레이를 구현한 방법에 관심이 있을 수 있습니다. CArrayBool 그리고 CArrayDatetime에서는 CArrayInt를 기반으로 했지만 CArrayMqlRates에서는 조금 달랐던게, 스트럭트는 레퍼런스로 패싱해야하는데다가 TYPE_MQLRATES가 정의되어있지 않기 때문이었습니다.

아래에서 CArrayMqlRates 클래스의 부분 소스 코드를 보실 수 있습니다. 다른 클래스들은 문서의 첨부 파일에서 보실 수 있습니다. 

//+------------------------------------------------------------------+
//|                                                ArrayMqlRates.mqh |
//|                                      Copyright 2011, Investeo.pl |
//|                                               http://Investeo.pl |
//|                                              Revision 2011.03.03 |
//+------------------------------------------------------------------+
#include "Array.mqh"
//+------------------------------------------------------------------+
//| Class CArrayMqlRates.                                            |
//| Purpose: Class of dynamic array of structs                       |
//|          of MqlRates type.                                       |
//|          Derived from CArray class.                              |
//+------------------------------------------------------------------+
#define TYPE_MQLRATES 7654

class CArrayMqlRates : public CArray
  {
protected:
   MqlRates          m_data[];           // data array
public:
                     CArrayMqlRates();
                    ~CArrayMqlRates();
   //--- method of identifying the object
   virtual int       Type() const        { return(TYPE_MQLRATES); }
   //--- methods for working with files
   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
   //--- methods of managing dynamic memory
   bool              Reserve(int size);
   bool              Resize(int size);
   bool              Shutdown();
   //--- methods of filling the array
   bool              Add(MqlRates& element);
   bool              AddArray(const MqlRates &src[]);
   bool              AddArray(const CArrayMqlRates *src);
   bool              Insert(MqlRates& element,int pos);
   bool              InsertArray(const MqlRates &src[],int pos);
   bool              InsertArray(const CArrayMqlRates *src,int pos);
   bool              AssignArray(const MqlRates &src[]);
   bool              AssignArray(const CArrayMqlRates *src);
   //--- method of access to the array
   MqlRates          At(int index) const;
   //--- methods of changing
   bool              Update(int index,MqlRates& element);
   bool              Shift(int index,int shift);
   //--- methods of deleting
   bool              Delete(int index);
   bool              DeleteRange(int from,int to);
protected:
   int               MemMove(int dest,int src,int count);
  };
//+------------------------------------------------------------------+
//| Constructor CArrayMqlRates.                                      |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CArrayMqlRates::CArrayMqlRates()
  {
//--- initialize protected data
   m_data_max=ArraySize(m_data);
  }
//+------------------------------------------------------------------+
//| Destructor CArrayMqlRates.                                       |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CArrayMqlRates::~CArrayMqlRates()
  {
   if(m_data_max!=0) Shutdown();
  }
...

//+------------------------------------------------------------------+
//| Adding an element to the end of the array.                       |
//| INPUT:  element - variable to be added.                          |
//| OUTPUT: true if successful, false if not.                        |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CArrayMqlRates::Add(MqlRates& element)
  {
//--- checking/reserve elements of array
   if(!Reserve(1)) return(false);
//--- adding
   m_data[m_data_total++]=element;
   m_sort_mode=-1;
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Adding an element to the end of the array from another array.    |
//| INPUT:  src - source array.                                      |
//| OUTPUT: true if successful, false if not.                        |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CArrayMqlRates::AddArray(const MqlRates &src[])
  {
   int num=ArraySize(src);
//--- checking/reserving elements of array
   if(!Reserve(num)) return(false);
//--- adding
   for(int i=0;i<num;i++) m_data[m_data_total++]=src[i];
   m_sort_mode=-1;
//---
   return(true);
  }

...

모든 MQL5 데이터를 RPC 요청으로 보내기 전에 XML 값으로 변환해야 하므로 값을 가져와서 XML 문자열로 인코딩하는 CXMLRPCEncoder 도우미 클래스를 설계했습니다.

class CXMLRPCEncoder
  {
    public:
                    CXMLRPCEncoder(){};
   string            header(string path,int contentLength);
   string            fromInt(int param);
   string            fromDouble(double param);
   string            fromBool(bool param);
   string            fromString(string param);
   string            fromDateTime(datetime param);
   string            fromMqlRates(MqlRates &param);
  };

구현 메소드들 중 세개를 아래에 붙여두었습니다. 모두 패러미터(bool, string, datetime)를 하나씩 사용하고 XML-RPC 프로토콜에 대해 XML에 쓸 수 있는 자료형인 string을 반환합니다.

//+------------------------------------------------------------------+
//| fromBool                                                         |
//+------------------------------------------------------------------+
string CXMLRPCEncoder::fromBool(bool param)
  {
   CString s_bool;
   s_bool.Clear();
   s_bool.Append(VALUE_B);
   s_bool.Append(BOOL_B);
   if(param==true)
      s_bool.Append("1");
   else s_bool.Append("0");
   s_bool.Append(BOOL_E);
   s_bool.Append(VALUE_E);

   return s_bool.Str();
  }
//+------------------------------------------------------------------+
//| fromString                                                       |
//+------------------------------------------------------------------+
string CXMLRPCEncoder::fromString(string param)
  {
   CString s_string;
   s_string.Clear();
   s_string.Append(VALUE_B);
   s_string.Append(STRING_B);
   s_string.Append(param);
   s_string.Append(STRING_E);
   s_string.Append(VALUE_E);

   return s_string.Str();
  }
//+------------------------------------------------------------------+
//| fromDateTime                                                     |
//+------------------------------------------------------------------+
string CXMLRPCEncoder::fromDateTime(datetime param)
  {
   CString s_datetime;
   s_datetime.Clear();
   s_datetime.Append(VALUE_B);
   s_datetime.Append(DATETIME_B);
   CString s_iso8601;
   s_iso8601.Assign(TimeToString(param, TIME_DATE|TIME_MINUTES));
   s_iso8601.Replace(" ", "T");
   s_iso8601.Remove(":");
   s_iso8601.Remove(".");
   s_datetime.Append(s_iso8601.Str());
   s_datetime.Append(DATETIME_E);
   s_datetime.Append(VALUE_E);

   return s_datetime.Str();
  }

'태그 시작'을 의미하는 _B 접미사와 '태그 끝'을 의미하는 _E 접미사가 있는 특정 태그가 있음을 알 수 있습니다.

구현이 훨씬 더 명료해졌기 때문에 XML 태그와 헤더의 이름을 유지하는 헤더 파일을 사용하기로 했습니다.

//+------------------------------------------------------------------+
//|                                                   xmlrpctags.mqh |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"

#define HEADER_1a      "POST"
#define HEADER_1b      "HTTP/1.1"
#define HEADER_2       "User-Agent: MQL5RPC/1.1"
#define HEADER_3       "Host: host.com"
#define HEADER_4       "Content-Type: text/xml"
#define HEADER_5       "Content-Length: "
#define HEADER_6       "<?xml version='1.0'?>"
#define METHOD_B       "<methodCall>"
#define METHOD_E       "</methodCall>"
#define METHOD_NAME_B  "<methodName>"
#define METHOD_NAME_E  "</methodName>"
#define RESPONSE_B     "<methodResponse>"
#define RESPONSE_E     "</methodResponse>"
#define PARAMS_B       "<params>"
#define PARAMS_E       "</params>"
#define PARAM_B        "<param>"
#define PARAM_E        "</param>"
#define VALUE_B        "<value>"
#define VALUE_E        "</value>"
#define INT_B          "<int>"
#define INT_E          "</int>"
#define I4_B           "<i4>"
#define I4_E           "</i4>"
#define BOOL_B         "<boolean>"
#define BOOL_E         "</boolean>"
#define DOUBLE_B       "<double>"
#define DOUBLE_E       "</double>"
#define STRING_B       "<string>"
#define STRING_E       "</string>"
#define DATETIME_B     "<dateTime.iso8601>"
#define DATETIME_E     "</dateTime.iso8601>"
#define BASE64_B       "<base64>"
#define BASE64_E       "</base64>"
#define ARRAY_B        "<array>"
#define ARRAY_E        "</array>"
#define DATA_B         "<data>"
#define DATA_E         "</data>"
#define STRUCT_B       "<struct>"
#define STRUCT_E       "</struct>"
#define MEMBER_B       "<member>"
#define MEMBER_E       "</member>"
#define NAME_B         "<name>"
#define NAME_E         "</name>"
//+------------------------------------------------------------------+

MQL5-RPC의 데이터 모델을 정의했으면 전체 XML-RPC 요청을 작성할 수 있습니다.


MQL5-RPC 리퀘스트

앞서 언급한 바와 같이 XML-RPC 요청은 요청 헤더와 XML 페이로드로 구성됩니다. MQL5 데이터 어레이에서 쿼리 객체를 자동으로 구성하는 CXMLRPCQuery 클래스를 설계했습니다. 이 클래스는 CXMLRPCEncoder를 사용하여 XML에 데이터를 캡슐화하고 메소드 이름 태그 안에 메소드 이름을 추가합니다.

class CXMLRPCQuery
  {
private:
   CString           s_query;
   void              addValueElement(bool start,bool array);
public:
                    CXMLRPCQuery() {};
                    CXMLRPCQuery(string method="",CArrayObj *param_array=NULL);
   string            toString();
  };

이 클래스의 생성자는 두개의 패러미터, 메소드 이름과 메소드 콜을 담고 있는 CArrayObj를 가리키는 포인터를 가지고 있습니다. 모든 패러미터는 이전 섹션에서 설명한 대로 XML로 래핑되고 쿼리 헤더가 추가됩니다. 모든 XML 쿼리는 toString() 메소드를 통해 표시할 수 있습니다.

CXMLRPCQuery::CXMLRPCQuery(string method="",CArrayObj *param_array=NULL)
  {
//--- constructs a single XMLRPC Query
   this.s_query.Clear();

   CXMLRPCEncoder encoder;
   this.s_query.Append(HEADER_6);
   this.s_query.Append(METHOD_B);
   this.s_query.Append(METHOD_NAME_B);
   this.s_query.Append(method);
   this.s_query.Append(METHOD_NAME_E);
   this.s_query.Append(PARAMS_B);

   for(int i=0; i<param_array.Total(); i++)
     {
      int j=0;
      this.s_query.Append(PARAM_B);

      int type=param_array.At(i).Type();
      int elements=0;

      switch(type)
        {
         case TYPE_INT:
           {
            CArrayInt *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++) this.s_query.Append(encoder.fromInt(arr.At(j)));
            break;
           }
         case TYPE_DOUBLE:
           {
            CArrayDouble *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++) this.s_query.Append(encoder.fromDouble(arr.At(j)));
            break;
           }
         case TYPE_STRING:
           {
            CArrayString *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++) this.s_query.Append(encoder.fromString(arr.At(j)));
            break;
           }
         case TYPE_BOOL:
           {
            CArrayBool *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++) this.s_query.Append(encoder.fromBool(arr.At(j)));
            break;
           }
         case TYPE_DATETIME:
           {
            CArrayDatetime *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++) this.s_query.Append(encoder.fromDateTime(arr.At(j)));
            break;
           }
         case TYPE_MQLRATES:
           {
            CArrayMqlRates *arr=param_array.At(i);
            elements=arr.Total();
            if(elements==1) addValueElement(true,false); else addValueElement(true,true);
            for(j=0; j<elements; j++)
              {
               MqlRates tmp=arr.At(j);
               this.s_query.Append(encoder.fromMqlRates(tmp));
              }
            break;
           }
        };

      if(elements==1) addValueElement(false,false); else addValueElement(false,true);

      this.s_query.Append(PARAM_E);
     }

   this.s_query.Append(PARAMS_E);
   this.s_query.Append(METHOD_E);
  }

아래의 쿼리 테스트 예시를 보십시오. 꽤 복잡해보이는 메소드도 호출할 수 있다는 것을 보여드리기 위해 일부러 복잡한 것을 썼습니다.

입력 패러미터는 double 값 어레이, integer 값 어레이, string 값 어레이, bool 값 어레이, 단일datetime값과 MqlRates 구조 어레이입니다.

//+------------------------------------------------------------------+
//|                                          MQL5-RPC_query_test.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayDouble.mqh>
#include <Arrays\ArrayString.mqh>
#include <Arrays\ArrayBool.mqh>
#include <Arrays\ArrayDatetime.mqh>
#include <Arrays\ArrayMqlRates.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- query test
   CArrayObj* params = new CArrayObj;
   
   CArrayDouble*   param1 = new CArrayDouble;
   CArrayInt*      param2 = new CArrayInt;
   CArrayString*   param3 = new CArrayString;
   CArrayBool*     param4 = new CArrayBool;
   CArrayDatetime* param5 = new CArrayDatetime;
   CArrayMqlRates* param6 = new CArrayMqlRates;
   
   for (int i=0; i<4; i++)
      param1.Add(20000.0 + i*100.0);
   
   params.Add(param1);
   
   for (int i=0; i<4; i++)
      param2.Add(i);
      
   params.Add(param2);
   
   param3.Add("first_string");
   param3.Add("second_string");
   param3.Add("third_string");
   
   params.Add(param3);
   
   param4.Add(false);
   param4.Add(true);
   param4.Add(false);
   
   params.Add(param4);
   
   param5.Add(TimeCurrent());
   params.Add(param5);
   
   const int nRates = 3;
   MqlRates rates[3];
   ArraySetAsSeries(rates,true);
   int copied=CopyRates(Symbol(),0,0,nRates,rates);
   if (copied==nRates) {
      param6.AddArray(rates);
      params.Add(param6);
   }
        
   CXMLRPCQuery query("sampleMethodname", params);
   
   Print(query.toString());
   
   delete params;
  }
//+------------------------------------------------------------------+

결과 쿼리는 다음과 같습니다. 일반적으로 이 값은 한 줄 문자열 값이지만 XML-RPC 섹션과 단일 값을 쉽게 구분할 수 있도록 탭 텍스트로 표시합니다.

<?xml version="1.0" ?>
<methodCall>
      <methodName>sampleMethodname</methodName>
      <params>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <double>20000.0000000000000000</double>
                                    </value>
                                    <value>
                                          <double>20100.0000000000000000</double>
                                    </value>
                                    <value>
                                          <double>20200.0000000000000000</double>
                                    </value>
                                    <value>
                                          <double>20300.0000000000000000</double>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <int>0</int>
                                    </value>
                                    <value>
                                          <int>1</int>
                                    </value>
                                    <value>
                                          <int>2</int>
                                    </value>
                                    <value>
                                          <int>3</int>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <string>first_string</string>
                                    </value>
                                    <value>
                                          <string>second_string</string>
                                    </value>
                                    <value>
                                          <string>third_string</string>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <boolean>0</boolean>
                                    </value>
                                    <value>
                                          <boolean>1</boolean>
                                    </value>
                                    <value>
                                          <boolean>0</boolean>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
            <param>
                  <value>
                        <dateTime.iso8601>20111111T2042</dateTime.iso8601>
                  </value>
            </param>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <struct>
                                                <member>
                                                      <name>open</name>
                                                      <value>
                                                            <double>1.02902000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>high</name>
                                                      <value>
                                                            <double>1.03032000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>low</name>
                                                      <value>
                                                            <double>1.02842000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>close</name>
                                                      <value>
                                                            <double>1.02867000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>time</name>
                                                      <value>
                                                            <dateTime.iso8601>
                                                                  <value>
                                                                        <dateTime.iso8601>20111111T1800</dateTime.iso8601>
                                                                  </value>
                                                            </dateTime.iso8601>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>tick_volume</name>
                                                      <value>
                                                            <double>4154.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>real_volume</name>
                                                      <value>
                                                            <double>0.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>spread</name>
                                                      <value>
                                                            <double>30.00000000</double>
                                                      </value>
                                                </member>
                                          </struct>
                                    </value>
                                    <value>
                                          <struct>
                                                <member>
                                                      <name>open</name>
                                                      <value>
                                                            <double>1.02865000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>high</name>
                                                      <value>
                                                            <double>1.02936000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>low</name>
                                                      <value>
                                                            <double>1.02719000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>close</name>
                                                      <value>
                                                            <double>1.02755000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>time</name>
                                                      <value>
                                                            <dateTime.iso8601>
                                                                  <value>
                                                                        <dateTime.iso8601>20111111T1900</dateTime.iso8601>
                                                                  </value>
                                                            </dateTime.iso8601>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>tick_volume</name>
                                                      <value>
                                                            <double>3415.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>real_volume</name>
                                                      <value>
                                                            <double>0.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>spread</name>
                                                      <value>
                                                            <double>30.00000000</double>
                                                      </value>
                                                </member>
                                          </struct>
                                    </value>
                                    <value>
                                          <struct>
                                                <member>
                                                      <name>open</name>
                                                      <value>
                                                            <double>1.02760000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>high</name>
                                                      <value>
                                                            <double>1.02901000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>low</name>
                                                      <value>
                                                            <double>1.02756000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>close</name>
                                                      <value>
                                                            <double>1.02861000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>time</name>
                                                      <value>
                                                            <dateTime.iso8601>
                                                                  <value>
                                                                        <dateTime.iso8601>20111111T2000</dateTime.iso8601>
                                                                  </value>
                                                            </dateTime.iso8601>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>tick_volume</name>
                                                      <value>
                                                            <double>1845.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>real_volume</name>
                                                      <value>
                                                            <double>0.00000000</double>
                                                      </value>
                                                </member>
                                                <member>
                                                      <name>spread</name>
                                                      <value>
                                                            <double>30.00000000</double>
                                                      </value>
                                                </member>
                                          </struct>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
      </params>
</methodCall>

이것은 상당히 발전된 XML 트리이며 프로토콜은 훨씬 더 복잡한 메소드를 호출할 수 있습니다.


MQL5-RPC 리스폰스

XML-RPC 요청과 마찬가지로 XML-RPC 응답은 헤더 및 XML 페이로드로 구성되지만 이번에는 처리 순서를 반대로 해야 합니다. 즉 XML 응답을 MQL5 데이터로 변환해야 합니다. CXMLRPResult 클래스는 이 작업을 처리하도록 설계되었습니다.

원하는 메소드가 실행된 후 클래스 생성자에게 전달되는 문자열로 결과를 수신하거나 CXMLServerProxy 클래스에서 자동으로 검색할 수 있습니다. 만약 결과 안에 이전에 알려진 적 없는 스트럭트가 포함된 경우 parseXMLResponseRAW() 메소드는 모든 <value> 태그를 찾아 보고 발견된 모든 값 요소의 어레이가 담긴 CArrayObj포인터를 반환합니다.

class CXMLRPCResult
  {
private:
   CArrayObj        *m_resultsArr;

   CString           m_cstrResponse;
   CArrayString      m_params;

   bool              isValidXMLResponse();
   bool              parseXMLValuesToMQLArray(CArrayString *subArr,CString &val);
   bool              parseXMLValuesToMQLArray(CArrayDouble *subArr,CString &val);
   bool              parseXMLValuesToMQLArray(CArrayInt *subArr,CString &val);
   bool              parseXMLValuesToMQLArray(CArrayBool *subArr,CString &val);
   bool              parseXMLValuesToMQLArray(CArrayDatetime *subArr,CString &val);
   bool              parseXMLValuesToMQLArray(CArrayMqlRates *subArr,CString &val);
   bool              parseXMLResponse();

public:
                     CXMLRPCResult() {};
                    ~CXMLRPCResult();
                     CXMLRPCResult(string resultXml);
   
   CArrayObj        *getResults();
   bool              parseXMLResponseRAW();
   string            toString();
  };

이 클래스의 생성자는 리스폰스 XML의 모든 헤더를 체크한 후 XML을 MQL5로 전환시켜주는 private 메소드 parseXMLValuesToMQLArray()를 호출합니다.

만약 패러미터가 어레이나 단일 요소이면 CArrayObj 어레이 결과에 추가합니다.

bool CXMLRPCResult::parseXMLResponse()
  {
   CArrayObj *results=new CArrayObj;

   m_params.Clear();

   //--- find params and put them in m_params array
   int tagStartIdx= 0;
   int tagStopIdx = 0;
   while((tagStartIdx!=-1) && (tagStopIdx!=-1))
     {

      tagStartIdx= m_cstrResponse.Find(tagStartIdx,PARAM_B);
      tagStopIdx = m_cstrResponse.Find(tagStopIdx,PARAM_E);

      if((tagStartIdx!=-1) && (tagStopIdx!=-1))
        {
         m_params.Add(m_cstrResponse.Mid(tagStartIdx+StringLen(PARAM_B),tagStopIdx-tagStartIdx-StringLen(PARAM_B)));
         tagStartIdx++; tagStopIdx++;
        };

     };

   for(int i=0; i<m_params.Total(); i++)
     {
      CString val;
      val.Assign(m_params.At(i));

      //--- parse value tag

      val.Assign(val.Mid(StringLen(VALUE_B),val.Len()-StringLen(VALUE_B)-StringLen(VALUE_E)));

      //--- now check first tag and handle it approprietaly

      string param_type=val.Mid(0,val.Find(0,">")+1);

      if(param_type==INT_B || param_type==I4_B)
        {
         val.Assign(m_params.At(i));
         CArrayInt *subArr=new CArrayInt;
         bool isValid=parseXMLValuesToMQLArray(subArr,val);
         if(isValid==true)
            results.Add(subArr);
        }
      else if(param_type==BOOL_B)
        {
         val.Assign(m_params.At(i));
         CArrayBool *subArr=new CArrayBool;
         bool isValid=parseXMLValuesToMQLArray(subArr,val);
         if(isValid==true)
            results.Add(subArr);
        }
      else if(param_type==DOUBLE_B)
        {
         val.Assign(m_params.At(i));
         CArrayDouble *subArr=new CArrayDouble;
         bool isValid=parseXMLValuesToMQLArray(subArr,val);
         if(isValid==true)
            results.Add(subArr);
        }
      else if(param_type==STRING_B)
        {
         val.Assign(m_params.At(i));
         CArrayString *subArr=new CArrayString;
         bool isValid=parseXMLValuesToMQLArray(subArr,val);
         if(isValid==true)
            results.Add(subArr);
        }
      else if(param_type==DATETIME_B)
        {
         val.Assign(m_params.At(i));
         CArrayDatetime *subArr=new CArrayDatetime;
         bool isValid=parseXMLValuesToMQLArray(subArr,val);
         if(isValid==true)
            results.Add(subArr);
        }
      else if(param_type==ARRAY_B)
        {
         val.Assign(val.Mid(StringLen(ARRAY_B)+StringLen(DATA_B),val.Len()-StringLen(ARRAY_B)-StringLen(DATA_E)));
         //--- find first type and define array
         string array_type=val.Mid(StringLen(VALUE_B),val.Find(StringLen(VALUE_B)+1,">")-StringLen(VALUE_B)+1);

         if(array_type==INT_B || array_type==I4_B)
           {
            CArrayInt *subArr=new CArrayInt;
            bool isValid=parseXMLValuesToMQLArray(subArr,val);
            if(isValid==true)
               results.Add(subArr);
           }
         else if(array_type==BOOL_B)
           {
            CArrayBool *subArr=new CArrayBool;
            bool isValid=parseXMLValuesToMQLArray(subArr,val);
            if(isValid==true)
               results.Add(subArr);
           }
         else if(array_type==DOUBLE_B)
           {
            CArrayDouble *subArr=new CArrayDouble;
            bool isValid=parseXMLValuesToMQLArray(subArr,val);
            if(isValid==true)
               results.Add(subArr);
           }
         else if(array_type==STRING_B)
           {
            CArrayString *subArr=new CArrayString;
            bool isValid=parseXMLValuesToMQLArray(subArr,val);
            if(isValid==true)
               results.Add(subArr);

           }
         else if(array_type==DATETIME_B)
           {
            CArrayDatetime *subArr=new CArrayDatetime;
            bool isValid=parseXMLValuesToMQLArray(subArr,val);
            if(isValid==true)
               results.Add(subArr);
           }
        }
     };

   m_resultsArr=results;

   return true;
  }

변환은 오버로드된 parseXMLValuesToMQLArray 메소드 내에서 수행됩니다. string 값은 XML에서 추출되어 기본 MQL5 변수로 변환됩니다.

변환에 사용되는 메소드 중 세 가지를 아래에 참조용으로 붙여넣습니다.

//+------------------------------------------------------------------+
//| parseXMLValuesToMQLArray                                         |
//+------------------------------------------------------------------+
bool CXMLRPCResult::parseXMLValuesToMQLArray(CArrayBool *subArr,CString &val)
  {
   //--- parse XML values and populate MQL array
   int tagStartIdx=0; int tagStopIdx=0;

   while((tagStartIdx!=-1) && (tagStopIdx!=-1))
     {
      tagStartIdx= val.Find(tagStartIdx,VALUE_B);
      tagStopIdx = val.Find(tagStopIdx,VALUE_E);
      if((tagStartIdx!=-1) && (tagStopIdx!=-1))
        {
         CString e;
         e.Assign(val.Mid(tagStartIdx+StringLen(VALUE_B)+StringLen(BOOL_B),
                  tagStopIdx-tagStartIdx-StringLen(VALUE_B)-StringLen(BOOL_B)-StringLen(BOOL_E)));
         if(e.Str()=="0")
            subArr.Add(false);
         if(e.Str()=="1")
            subArr.Add(true);

         tagStartIdx++; tagStopIdx++;
        };
     }
   if(subArr.Total()<1) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| parseXMLValuesToMQLArray                                         |
//+------------------------------------------------------------------+
bool CXMLRPCResult::parseXMLValuesToMQLArray(CArrayInt *subArr,CString &val)
  {
    //--- parse XML values and populate MQL array
   int tagStartIdx=0; int tagStopIdx=0;

   while((tagStartIdx!=-1) && (tagStopIdx!=-1))
     {
      tagStartIdx= val.Find(tagStartIdx,VALUE_B);
      tagStopIdx = val.Find(tagStopIdx,VALUE_E);
      if((tagStartIdx!=-1) && (tagStopIdx!=-1))
        {
         CString e;
         e.Assign(val.Mid(tagStartIdx+StringLen(VALUE_B)+StringLen(INT_B),
                  tagStopIdx-tagStartIdx-StringLen(VALUE_B)-StringLen(INT_B)-StringLen(INT_E)));
         subArr.Add((int)StringToInteger(e.Str()));
         tagStartIdx++; tagStopIdx++;
        };
     }
   if(subArr.Total()<1) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| parseXMLValuesToMQLArray                                         |
//+------------------------------------------------------------------+
bool CXMLRPCResult::parseXMLValuesToMQLArray(CArrayDatetime *subArr,CString &val)
  {
   // parse XML values and populate MQL array
   int tagStartIdx=0; int tagStopIdx=0;

   while((tagStartIdx!=-1) && (tagStopIdx!=-1))
     {
      tagStartIdx= val.Find(tagStartIdx,VALUE_B);
      tagStopIdx = val.Find(tagStopIdx,VALUE_E);
      if((tagStartIdx!=-1) && (tagStopIdx!=-1))
        {
         CString e;
         e.Assign(val.Mid(tagStartIdx+StringLen(VALUE_B)+StringLen(DATETIME_B),
                  tagStopIdx-tagStartIdx-StringLen(VALUE_B)-StringLen(DATETIME_B)-StringLen(DATETIME_E)));
         e.Replace("T"," "); e.Insert(4,"."); e.Insert(7,".");
         subArr.Add(StringToTime(e.Str()));
         tagStartIdx++; tagStopIdx++;
        };
     }
   if(subArr.Total()<1) return false;
   return true;
  }

보시다시피 XML-RPC 태그들에서 추출한 string 값들은 CString 클래스의 메소드들을 이용하여 추출되었습니다. Assign(), Mid(), Find(), 그리고 이 값들은 StringToTime(), StringToInteger(), StringToDouble(), 그리고 boolean의 경우 커스텀 메소드를 활용하여 MQL5로 전환되었습니다.

모든 값을 구문 분석한 후 toString() 메소드를 사용하여 사용 가능한 모든 데이터를 표시할 수 있습니다. 나는 단순히 값을 구분하기 위해 : 기호를 사용합니다.

string CXMLRPCResult::toString(void) 
  {
   // returns results array of arrays as a string
   CString r;

   for(int i=0; i<m_resultsArr.Total(); i++) 
     {
      int rtype=m_resultsArr.At(i).Type();
      switch(rtype) 
        {
         case(TYPE_STRING) : 
           {
            CArrayString *subArr=m_resultsArr.At(i);
            for(int j=0; j<subArr.Total(); j++) 
              {
               r.Append(subArr.At(j)+":");
              }
            break;
           };
         case(TYPE_DOUBLE) : 
           {
            CArrayDouble *subArr=m_resultsArr.At(i);
            for(int j=0; j<subArr.Total(); j++) 
              {
               r.Append(DoubleToString(NormalizeDouble(subArr.At(j),8))+":");
              }
            break;
           };
         case(TYPE_INT) : 
           {
            CArrayInt *subArr=m_resultsArr.At(i);
            for(int j=0; j<subArr.Total(); j++) 
              {
               r.Append(IntegerToString(subArr.At(j))+":");
              }
            break;
           };
         case(TYPE_BOOL) : 
           {
            CArrayBool *subArr=m_resultsArr.At(i);
            for(int j=0; j<subArr.Total(); j++) 
              {
               if(subArr.At(j)==false) r.Append("false:");
               else r.Append("true:");
              }
            break;
           };
         case(TYPE_DATETIME) : 
           {
            CArrayDatetime *subArr=m_resultsArr.At(i);
            for(int j=0; j<subArr.Total(); j++) 
              {
               r.Append(TimeToString(subArr.At(j),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+" : ");
              }
            break;
           };
        };
     }

   return r.Str();
  }

XML-RPC 결과 패러미터가 MQL5로 변환되는 방법을 명시적으로 확인할 수 있도록 결과 테스트를 구현했습니다.

//+------------------------------------------------------------------+
//|                                         MQL5-RPC_result_test.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"
//---
#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayDouble.mqh>
#include <Arrays\ArrayString.mqh>
#include <Arrays\ArrayBool.mqh>
#include <Arrays\ArrayDatetime.mqh>
#include <Arrays\ArrayMqlRates.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
    string sampleXMLResult = "<?xml version='1.0'?>"
                            "<methodResponse>" +
                            "<params>" +
                            "<param>" +
                            "<value><array><data>" +
                            "<value><string>Xupypr</string></value>" +
                            "<value><string>anuta</string></value>" +
                            "<value><string>plencing</string></value>" +
                            "<value><string>aharata</string></value>" +
                            "<value><string>beast</string></value>" +
                            "<value><string>west100</string></value>" +
                            "<value><string>ias</string></value>" +
                            "<value><string>Tim</string></value>" +
                            "<value><string>gery18</string></value>" +
                            "<value><string>ronnielee</string></value>" +
                            "<value><string>investeo</string></value>" +
                            "<value><string>droslan</string></value>" +
                            "<value><string>Better</string></value>" +
                            "</data></array></value>" +
                            "</param>" + 
                            "<param>" +
                            "<value><array><data>" +
                            "<value><double>1.222</double></value>" +
                            "<value><double>0.456</double></value>" +
                            "<value><double>1000000000.10101</double></value>" +
                            "</data></array></value>" +
                            "</param>" + 
                            "<param>" +
                            "<value><array><data>" +
                            "<value><boolean>1</boolean></value>" +
                            "<value><boolean>0</boolean></value>" +
                            "<value><boolean>1</boolean></value>" +
                            "</data></array></value>" +
                            "</param>" + 
                            "<param>" +
                            "<value><array><data>" +
                            "<value><int>-1</int></value>" +
                            "<value><int>0</int></value>" +
                            "<value><int>1</int></value>" +
                            "</data></array></value>" +
                            "</param>" + 
                            "<param>" +
                            "<value><array><data>" +
                            "<value><dateTime.iso8601>20021125T02:20:04</dateTime.iso8601></value>" +
                            "<value><dateTime.iso8601>20111115T00:00:00</dateTime.iso8601></value>" +
                            "<value><dateTime.iso8601>20121221T00:00:00</dateTime.iso8601></value>" +
                            "</data></array></value>" +
                            "</param>" + 
                            "<param><value><string>Single string value</string></value></param>" +
                            "<param><value><dateTime.iso8601>20111115T00:00:00</dateTime.iso8601></value></param>" +
                            "<param><value><int>-111</int></value></param>" +
                            "<param><value><boolean>1</boolean></value></param>" +
                            "</params>" +
                            "</methodResponse>";

   CXMLRPCResult* testResult = new CXMLRPCResult(sampleXMLResult);
      
   Print(testResult.toString());

   delete testResult;
  }
//+------------------------------------------------------------------+

결과는 아래에서 보실 수 있습니다.

MQL5-RPC__result_test (EURUSD,H1)       23:16:57        
Xupypr:anuta:plencing:aharata:beast:west100:ias:Tim:gery18:ronnielee:investeo:
droslan:Better:1.22200000:0.45600000:1000000000.10099995:true:false:true:-1:0:
1:2002.11.25 02:20:04 : 2011.11.15 00:00:00 : 2012.12.21 00:00:00 :
Single string value:2011.11.15 00:00:00 :-111:true:

XML에서 변환된 서로 다른 MQL5 값의 어레이입니다. 보시다시피 단일 CArrayObj 포인터에서 액세스 할 수 있는 strings, double 값이 있고, boolean 값이 있고, integer 값, datetime 값이 있습니다.


MQL5-RPC 프록시 클래스

CXMLRPCServer 프록시는 HTTP 연결의 핵심 클래스입니다. 저는 "Using WinInet in MQL5. Part 2: POST Requests and Files" 문서를 이용하여 HTTP 기능을 구현시키고 XML-RPC 사양에 맞춰 커스텀 헤더를 추가하였습니다.

CXMLRPCServerProxy::CXMLRPCServerProxy(string s_proxy,int timeout=0)
  {
   CString proxy;
   proxy.Assign(s_proxy);
   //--- find query path
   int sIdx = proxy.Find(0,"/");
   if (sIdx == -1)  
      m_query_path = "/";
   else  {
       m_query_path = proxy.Mid(sIdx, StringLen(s_proxy) - sIdx) + "/";
       s_proxy = proxy.Mid(0, sIdx);
    };
   //--- find query port. 80 is default
   int query_port = 80;
   int pIdx = proxy.Find(0,":");
   if (pIdx != -1) {
      query_port = (int)StringToInteger(proxy.Mid(pIdx+1, sIdx-pIdx));
      s_proxy = proxy.Mid(0, pIdx);
   };
   //Print(query_port);
   //Print(proxy.Mid(pIdx+1, sIdx-pIdx));
   if(InternetAttemptConnect(0)!=0)
     {
      this.m_connectionStatus="InternetAttemptConnect failed.";
      this.m_session=-1;
      this.m_isConnected=false;
      return;
     }
   string agent = "Mozilla";
   string empty = "";

   this.m_session=InternetOpenW(agent,OPEN_TYPE_PRECONFIG,empty,empty,0);

   if(this.m_session<=0)
     {
      this.m_connectionStatus="InternetOpenW failed.";
      this.m_session=-2;
      this.m_isConnected=true;
      return;
     }
   this.m_connection=InternetConnectW(this.m_session,s_proxy,query_port,empty,empty,SERVICE_HTTP,0,0);
   if(this.m_connection<=0)
     {
      this.m_connectionStatus="InternetConnectW failed.";
      return;
     }
   this.m_connectionStatus="Connected.";

  }

CXMLRPCQuery 객체는 CXMLRPCServerProxy execute() 메소드로 넘겨져 XML-RPC 호출을 발동시켜야합니다.

메서드는 XML-RPC 호출이라는 이름의 스크립트에서 사용할 수 있는 CXMLRPResult 객체에 대한 포인터를 반환합니다.

CXMLRPCResult *CXMLRPCServerProxy::execute(CXMLRPCQuery &query)
  {
   
   //--- creating descriptor of the request
   string empty_string = "";
   string query_string = query.toString();
   string query_method = HEADER_1a;
   string http_version = HEADER_1b;
   
   uchar data[];
   
   StringToCharArray(query.toString(),data);
   
   int ivar=0;
   int hRequest=HttpOpenRequestW(this.m_connection,query_method,m_query_path,http_version,
                                 empty_string,0,FLAG_KEEP_CONNECTION|FLAG_RELOAD|FLAG_PRAGMA_NOCACHE,0);
   if(hRequest<=0)
     {
      Print("-Err OpenRequest");
      InternetCloseHandle(this.m_connection);
      return(new CXMLRPCResult);
     }
   //-- sending the request
   CXMLRPCEncoder encoder;
   string header=encoder.header(m_query_path,ArraySize(data));

   int aH=HttpAddRequestHeadersW(hRequest,header,StringLen(header),HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);
   bool hSend=HttpSendRequestW(hRequest,empty_string,0,data,ArraySize(data)-1);
   if(hSend!=true)
     {
      int err=0;
      err=GetLastError();
      Print("-Err SendRequest= ",err);
     }
   string res;

   ReadPage(hRequest,res,false);
   CString out;
   out.Assign(res);
   out.Remove("\n");
   
   //--- closing all handles
   InternetCloseHandle(hRequest); InternetCloseHandle(hSend);
   CXMLRPCResult* result = new CXMLRPCResult(out.Str());
   return result;
  }


예시 1 - 웹 서비스 접속

MQL5-RPC의 첫 번째 예제는 외부 웹 서비스호출입니다. 제가 찾은 예는 특정 금액의 통화를 다른 통화로 변환하는 과정에서 현재의 환율로 사용합니다. 메소드 패러미터의 정확한 사양은 온라인에서 확인해보실 수 있습니다..

웹서비스의 노출된 foxrate.currencyConvert 메소드는 3개 패러미터를 받는데, string이 2개, float가 하나입니다.

  • 원본 화폐 (예: USD) = string;
  • 바꿀 화폐 (예: GBP) = string;
  • 전환할 양 (예:100.0) = float.

구현은 상당히 쉽고 오직 몇 줄 만이 필요합니다.

//+------------------------------------------------------------------+
//|                              MQL5-RPC_ex1_ExternalWebService.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"
//---
#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayDouble.mqh>
#include <Arrays\ArrayString.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //--- web service test
   CArrayObj* params = new CArrayObj;
   
   CArrayString* from   = new CArrayString;
   CArrayString* to     = new CArrayString;
   CArrayDouble* amount = new CArrayDouble;
   
   from.Add("GBP"); to.Add("USD"); amount.Add(10000.0);
   params.Add(from); params.Add(to); params.Add(amount);
   
   CXMLRPCQuery query("foxrate.currencyConvert", params);
   
   Print(query.toString());
   
   CXMLRPCServerProxy s("foxrate.org/rpc");
   CXMLRPCResult* result;
   
   result = s.execute(query);
   
   result.parseXMLResponseRAW();
   Print(result.toString());
     
   delete params;
   delete result;
  }
//+------------------------------------------------------------------+

메소드가 복합 구조를 반환하므로 parseX를 사용하여 결과를 구문 분석했습니다.MLResponseRAW() 메소드입니다.

예시 안의 XML-RPC 쿼리는 다음과 같습니다.

<?xml version="1.0" ?>
<methodCall>
      <methodName>foxrate.currencyConvert</methodName>
      <params>
            <param>
                  <value>
                        <string>GBP</string>
                  </value>
            </param>
            <param>
                  <value>
                        <string>USD</string>
                  </value>
           </param>
           <param>
                  <value>
                        <double>10000.00000000</double>
                  </value>
           </param>
      </params>
</methodCall>

그리고 리스폰스는 XML에 싸인 스트럭트로 되어있습니다.

<?xml version="1.0" ?>
<methodResponse>
      <params>
            <param>
                  <value>
                        <struct>
                              <member>
                                    <name>flerror</name>
                                    <value>
                                          <int>0</int>
                                    </value>
                              </member>
                              <member>
                                    <name>amount</name>
                                    <value>
                                          <double>15773</double>
                                    </value>
                              </member>
                              <member>
                                    <name>message</name>
                                    <value>
                                          <string>cached</string>
                                    </value>
                              </member>
                        </struct>
                  </value>
            </param>
      </params>
</methodResponse>

toString()의 결과값을 보면 3개의 값이 있습니다. 0 - 에러 없음, 두번째 값은 환전에 필요한 기준 화폐의 양이고, 세번째 패러미터는 마지막 환전의 시간입니다 (캐싱됩니다).

0:15773.00000000:cached:

더욱 흥미로운 유즈 케이스를 봅시다.


예시 2 - XML-RPC ATC 2011 분석기

만약 Automated Trading Championship 2011에서 통계자료를 받아오고 싶다고 생각해봅시다 (제가 그랬습니다) 그리고 이 자료를 MetaTrader 5 터미널에서 사용하고 싶었다고 할때. 어떻게 그걸 할 수 있을지 알고싶으시다면 계속 읽으세요.

MetaQuotes Software Corp.이 특정 Expert Advisor들에서 시그널을 구독할 수 있는 시그널 팔로잉 서비스를 준비중인 바, 이걸 분석기와 합치면 더욱 세련된 분석이 가능하고 ATC에서 덕을 볼 수 있을거라고 생각합니다. 실제로 이 방법을 사용하면 몇 가지 소스로부터 데이터를 가져와 이를 기반으로 복잡한 Expert Advisor를 만들 수 있습니다.


XML-RPC ATC 2011 분석기 서버

ATC 분석기 서버를 만드는 첫 단계는 출력 분석을 준비하는 것입니다. 모든 참가자를 대상으로 계정 주식이 특정 기준 이상이고 현재 보유하고 있는 포지션에 관심이 있는 것으로 파악하여 관심 있는 화폐쌍의 매수 및 매도 포지션 통계를 표시한다고 가정해 보겠습니다.

저는 데이터를 받고 파싱하는데에 파이썬과 BeautifulSoup 라이브러리를 사용했습니다. 만약 과거에 파이썬을 써보신 적 없다면 http:/Python.org에 가셔서 이 언어로 무얼 할 수 있는지 알아보시는 것을 추천드립니다, 결코 후회하지않으실테니까요.

만약 빠르게 분석기 작업을 하고 싶으시다면Python 2.7.1 인스톨러 다운받기setup_tools 패키지를 받아 둘 다 설치하십시오. 그 뒤 윈도우즈 콘솔에서 C:\Python27\Scripts 나 파이썬을 설치한 폴더로 가십시오. 그 후 'easy_install BeautifulSoup' 를 입력. 이걸로 BeautifulSoup 패키지가 설치됩니다.

C:\Python27\Scripts>easy_install BeautifulSoup
Searching for BeautifulSoup
Reading http://pypi.python.org/simple/BeautifulSoup/
Reading http://www.crummy.com/software/BeautifulSoup/
Reading http://www.crummy.com/software/BeautifulSoup/download/
Best match: BeautifulSoup 3.2.0
Downloading http://www.crummy.com/software/BeautifulSoup/download/3.x/BeautifulS
oup-3.2.0.tar.gz
Processing BeautifulSoup-3.2.0.tar.gz
Running BeautifulSoup-3.2.0\setup.py -q bdist_egg --dist-dir c:\users\przemek\ap
pdata\local\temp\easy_install-zc1s2v\BeautifulSoup-3.2.0\egg-dist-tmp-hnpwoo
zip_safe flag not set; analyzing archive contents...
C:\Python27\lib\site-packages\setuptools\command\bdist_egg.py:422: UnicodeWarnin
g: Unicode equal comparison failed to convert both arguments to Unicode - interp
reting them as being unequal
  symbols = dict.fromkeys(iter_symbols(code))
Adding beautifulsoup 3.2.0 to easy-install.pth file

Installed c:\python27\lib\site-packages\beautifulsoup-3.2.0-py2.7.egg
Processing dependencies for BeautifulSoup
Finished processing dependencies for BeautifulSoup

C:\Python27\Scripts>

그 뒤 파이썬 콘솔을 실행하고 'import BeautifulSoup' 커맨드를 에러 없이 사용할 수 있게 됩니다.

Python 2.7.1 (r271:86832, Nov 27 2010, 18:30:46) [MSC v.1500 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import BeautifulSoup
>>> 

'python ContestantParser.py' 커맨드를 통해 첨부해둔 분석기를 실행시키십시오. 분석기가 홈페이지를 긁어오고, 일부 키워드를 찾아본 후 콘솔 창에 결과물을 출력합니다.

import re
import urllib
from BeautifulSoup import BeautifulSoup

class ContestantParser(object):
    URL_PREFIX = 'https://championship.mql5.com/2011/en/users/'
    
    def __init__(self, name):
        print "User:", name
        url = ContestantParser.URL_PREFIX + name
        feed = urllib.urlopen(url)
        s = BeautifulSoup(feed.read())
        account = s.findAll('td', attrs={'class' : 'alignRight'})
        self.balance = float(account[0].contents[0].replace(' ', ''))
        self.equity = float(account[2].contents[0].replace(' ', ''))
        terminals = s.findAll('table', attrs={'class': 'terminal'})
        terminal = terminals[0]
        trs = terminal.findAll('tr')
        pairs = terminal.findAll('span', attrs={'class':'stateText'})
        self.stats = {}
        for i in range(len(pairs)-1):
            if len(pairs[i].string)> 6:
                break
            self.stats[str(pairs[i].string)] = str(trs[i+1].contents[7].string)

    def __str__(self):
        for k in self.stats.keys():
            print k, self.stats[k] 
        
        return "Bal " + str(self.balance) + " Equ " +  str(self.equity)
    
    def position(self, pair):
        if pair in self.stats.keys():
            return self.stats[pair]
        else:
            return "none"
        
class ContestantsFinder(object):
    URL_PREFIX = "https://championship.mql5.com/2011/en/users/index/page"
    URL_SUFFIX = "?orderby=equity&dir=down"
    
    def __init__(self, min_equity):
        self.min_equity = min_equity        
        self.user_list = []
        self.__find()
        
    def __find(self):
        isLastPage = False
        pageCnt = 1
        while isLastPage == False:
            url = ContestantsFinder.URL_PREFIX + str(pageCnt) + \
                  ContestantsFinder.URL_SUFFIX
            feed = urllib.urlopen(url)
            s = BeautifulSoup(feed.read())
            urows = s.findAll('tr', attrs={'class' : re.compile('row.*')})
            for row in urows:
                user_name = row.contents[5].a.string
                equity = float(row.contents[19].string.replace(' ',''))
                if equity <= self.min_equity:
                    isLastPage = True
                    break
                self.user_list.append(str(user_name))
                print user_name, equity
            pageCnt += 1
    
        
    def list(self):
        return self.user_list
    
    
 
if __name__ == "__main__":
        
    # find all contestants with equity larger than threshold
    contestants = ContestantsFinder(20000.0)
    print contestants.list()
    # display statistics
    print "* Statistics *"
    for contestant in contestants.list():
        u = ContestantParser(contestant)    
        print u
        print u.position('eurusd')
        print '-' * 60
    

이 과정은 그냥 HTML 소스 코드와 태그 사이의 관계를 들여다보는 것으로 쉽게 해결할 수 있었습니다. 만약 이 코드를 파이썬 콘솔로 직접 실행시키면 참가자명, 잔고, 자본, 그리고 현재 개방 포지션을 출력할 것입니다.

샘플 쿼리의 출력값을 한 번 보아주십시오.

* Statistics *
User: Tim
Bal 31459.2 Equ 31459.2
none
------------------------------------------------------------
User: enivid
eurusd sell
euraud sell
Bal 26179.98 Equ 29779.89
sell
------------------------------------------------------------
User: ias
eurjpy sell
usdchf sell
gbpusd buy
eurgbp buy
eurchf sell
audusd buy
gbpjpy sell
usdjpy buy
usdcad buy
euraud buy
Bal 15670.0 Equ 29345.66
none
------------------------------------------------------------
User: plencing
eurusd buy
Bal 30233.2 Equ 29273.2
buy
------------------------------------------------------------
User: anuta
audusd buy
usdcad sell
gbpusd buy
Bal 28329.85 Equ 28359.05
none
------------------------------------------------------------
User: gery18
Bal 27846.7 Equ 27846.7
none
------------------------------------------------------------
User: tornhill
Bal 27402.4 Equ 27402.4
none
------------------------------------------------------------
User: rikko
eurusd sell
Bal 25574.8 Equ 26852.8
sell
------------------------------------------------------------
User: west100
eurusd buy
Bal 27980.5 Equ 26255.5
buy
------------------------------------------------------------
...

괜찮아보이지않나요? 이 데이터를 통해 흥미로운 통계값을 얻고 자료를 받아올 XML-RPC를 구현할 수 있습니다. MetaTrader 5의 XMLRPC 클라이언트는 필요할 때 이 통계자료를 받아올 수 있습니다.

XMLRPC 서버를 만들기 위해 저는 파이썬의 SimpleXMLRPC 서버 라이브러리를 이용하였습니다. 이 서버는 외부에 listContestants 그리고 getStats 이 두 메소드를 노출시킵니다. 앞의 메소드가 일정 이상의 자본을 가지고 있는 참가자명만을 출력하지만, 뒤의 메소드는 그들 중 몇이나 정해진 화폐쌍에 개방 포지션을 가지고 있는지, 이 포지션들의 매수와 매도 비율은 어떻게 되는지 보여줍니다.

시그널 서비스, 그리고(혹은) 본문에서 나온 매매 카피기를 이용하여 당신이 매매하려고 했던 것이 실제로 벌 가능성이 높은지 확인할 수 있습니다.

from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler

from PositionGrabberTest import ContestantParser, ContestantsFinder

class RequestHandler( SimpleXMLRPCRequestHandler ):
    rpc_path = ( '/RPC2', )
    
class ATC2011XMLRPCServer(SimpleXMLRPCServer):

    def __init__(self):
        SimpleXMLRPCServer.__init__( self, ("192.168.235.168", 6666), requestHandler=RequestHandler, logRequests = False)
        
        self.register_introspection_functions()
        self.register_function( self.xmlrpc_contestants, "listContestants" )        
        self.register_function( self.xmlrpc_get_position_stats, "getStats" )        
        
    
    def xmlrpc_contestants(self, min_equity):
        try:
            min_equity = float(min_equity)
        except ValueError as error:
            print error
            return []
    
        self.contestants = ContestantsFinder(min_equity)
        
        return self.contestants.list()
    
    def xmlrpc_get_position_stats(self, pair):
        total_users = len(self.contestants.list())
        holding_pair = 0
        buy_positions = 0
        
        for username in self.contestants.list():
            u = ContestantParser(username)
            position = u.position(pair)
            if position != "none":
                holding_pair += 1
                if position == "buy":
                    buy_positions += 1
        
        return [ total_users, holding_pair, buy_positions ]            
    

if __name__ == '__main__':
    server = ATC2011XMLRPCServer() 
    server.serve_forever()        

이 서버는 파이썬 콘솔에서 직접 액세스할 수 있습니다. 스크립트를 실행할 때, IP 주소를 당신의 시스템이 사용하고 있는 것으로 변경하는 것을 잊지 마시기 바랍니다.

C:\Program Files\MetaTrader 5\MQL5>python
Python 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win
32
Type "help", "copyright", "credits" or "license" for more information.
>>> from xmlrpclib import ServerProxy
>>> u = ServerProxy('http://192.168.235.168:6666')
>>> u.listContestants(20000.0)
['lf8749', 'ias', 'aharata', 'Xupypr', 'beast', 'Tim', 'tmt0086', 'yyy999', 'bob
sley', 'Diubakin', 'Pirat', 'notused', 'AAA777', 'ronnielee', 'samclider-2010',
'gery18', 'p96900', 'Integer', 'GradyLee', 'skarkalakos', 'zioliberato', 'kgo',
'enivid', 'Loky', 'Gans-deGlucker', 'zephyrrr', 'InvestProm', 'QuarkSpark', 'ld7
3', 'rho2011', 'tornhill', 'botaxuper']
>>>

이 글을 읽고 계실때엔 아마 참가자들의 결과가 완전히 바뀌었겠지만 제가 하고자 했던 것이 뭔지 느낄 수 있으실 것입니다.

getStats() 메소드에 "eurusd" 패러미터를 이용하여 호출하면 3개의 수가 리턴됩니다.

In : u.getStats("eurusd")
Out: [23, 12, 9]

첫 수는 일정 기준 이상의 자본을 가진 참가자의 수, 두번째는 EURUSD 개방 포지션을 들고 있는 참가자의 수, 세번째는 EURUSD 롱 포지션을 가진 참가자의 수 입니다. 이 경우엔 수상자 중 2/3이 롱 EURUSD를 오픈했습니다. 따라서 당신의 봇이나 인디케이터가 "open eurusd long" 시그널을 만드는 확정이라 볼 수 있을 것입니다.


MQL5-RPC ATC 2011 분석기 클라이언트

MQL5-RPC 프레임워크를 이용하여 MetaTrader 5가 필요로 하는 데이터를 받아올 때가 되었습니다. 실제로는 소스 코드를 쭉 살펴보시면 어떻게 쓰는건지 바로 이해하실 수 있으실 것입니다.

//+------------------------------------------------------------------+
//|                           MQL5-RPC_ex2_ATC2011AnalyzerClient.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                           http://www.investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http://www.investeo.pl"
#property version   "1.00"

#include <MQL5-RPC.mqh>
#include <Arrays\ArrayObj.mqh>
#include <Arrays\ArrayInt.mqh>
#include <Arrays\ArrayDouble.mqh>
#include <Arrays\ArrayString.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   //--- ATC 2011 analyzer test
   CXMLRPCServerProxy s("192.168.235.168:6666");
   CXMLRPCResult* result;
   
   //--- Get list of contestants
   CArrayObj* params1 = new CArrayObj;
   CArrayDouble* amount = new CArrayDouble;
   
   amount.Add(20000.0); params1.Add(amount);
   CXMLRPCQuery query("listContestants", params1);
   
   Print(query.toString());
   
   result = s.execute(query);
   Print(result.toString());
   delete result;

   //--- Get position statistics
   CArrayObj* params2 = new CArrayObj;
   CArrayString* pair = new CArrayString;
   
   pair.Add("eurusd"); params2.Add(pair);
   CXMLRPCQuery query2("getStats", params2);
   
   Print(query2.toString());
   
   result = s.execute(query2);
   
   CArrayObj* resultArray = result.getResults();
   CArrayInt* stats = resultArray.At(0);
   
   Print("Contestants = " + IntegerToString(stats.At(0)) + 
         ". EURUSD positions = " + IntegerToString(stats.At(1)) +
         ". BUY positions = " + IntegerToString(stats.At(2)));
         
   delete params1;
   delete params2;
   
   delete result;
  }
//+------------------------------------------------------------------+

스크립트가 호출되면 이렇게 됩니다. 이 쿼리는 XML에 래핑되었습니다.

<?xml version="1.0" ?>
<methodCall>
      <methodName>listContestants</methodName>
      <params>
            <param>
                  <value>
                        <double>20000.00000000</double>
                  </value>
            </param>
      </params>
</methodCall>

잠시 후 XML-RPC 서버에서 리스폰스가 수신됩니다. 제 경우 파이썬 XML-RPC 서비스를 실행하는 리눅스 호스트를 사용했으며 MetaTrader 5를 실행하는 윈도우즈 VirtualBox 게스트 설치 내에서 호출했습니다.

<?xml version="1.0" ?>
<methodResponse>
      <params>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <string>lf8749</string>
                                    </value>
                                    <value>
                                          <string>ias</string>
                                    </value>
                                    <value>
                                          <string>Xupypr</string>
                                    </value>
                                    <value>
                                          <string>aharata</string>
                                    </value>
                                    <value>
                                          <string>beast</string>
                                    </value>
                                    <value>
                                          <string>Tim</string>
                                    </value>
                                    <value>
                                          <string>tmt0086</string>
                                    </value>
                                    <value>
                                          <string>yyy999</string>
                                    </value>
                                    <value>
                                          <string>bobsley</string>
                                    </value>
                                    <value>
                                          <string>Diubakin</string>
                                    </value>
                                    <value>
                                          <string>Pirat</string>
                                    </value>
                                    <value>
                                          <string>AAA777</string>
                                    </value>
                                    <value>
                                          <string>notused</string>
                                    </value>
                                    <value>
                                          <string>ronnielee</string>
                                    </value>
                                    <value>
                                          <string>samclider-2010</string>
                                    </value>
                                    <value>
                                          <string>gery18</string>
                                    </value>
                                    <value>
                                          <string>Integer</string>
                                    </value>
                                    <value>
                                          <string>GradyLee</string>
                                    </value>
                                    <value>
                                          <string>p96900</string>
                                    </value>
                                    <value>
                                          <string>skarkalakos</string>
                                    </value>
                                    <value>
                                          <string>Loky</string>
                                    </value>
                                    <value>
                                          <string>zephyrrr</string>
                                    </value>
                                    <value>
                                          <string>Medvedev</string>
                                    </value>
                                    <value>
                                          <string>Gans-deGlucker</string>
                                    </value>
                                    <value>
                                          <string>InvestProm</string>
                                    </value>
                                    <value>
                                          <string>zioliberato</string>
                                    </value>
                                    <value>
                                          <string>QuarkSpark</string>
                                    </value>
                                    <value>
                                          <string>rho2011</string>
                                    </value>
                                    <value>
                                          <string>ld73</string>
                                    </value>
                                    <value>
                                          <string>enivid</string>
                                    </value>
                                    <value>
                                          <string>botaxuper</string>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
      </params>
</methodResponse>

toString() 메소드를 사용하여 표시되는 첫 번째 쿼리의 결과는 다음과 같습니다.

lf8749:ias:Xupypr:aharata:beast:Tim:tmt0086:yyy999:bobsley:Diubakin:Pirat:
AAA777:notused:ronnielee:samclider-2010:gery18:Integer:GradyLee:p96900:
skarkalakos:Loky:zephyrrr:Medvedev:Gans-deGlucker:InvestProm:zioliberato:
QuarkSpark:rho2011:ld73:enivid:botaxuper:

두번째 쿼리는 getStats() 메소드를 호출하기 위해 사용되었습니다.

<?xml version="1.0" ?>
<methodCall>
      <methodName>getStats</methodName>
      <params>
            <param>
                  <value>
                        <string>eurusd</string>
                  </value>
            </param>
      </params>
</methodCall>

XML-RPC 응답은 간단하며, 세 개의 정수 값만 포함합니다.

<?xml version="1.0" ?>
<methodResponse>
      <params>
            <param>
                  <value>
                        <array>
                              <data>
                                    <value>
                                          <int>31</int>
                                    </value>
                                    <value>
                                          <int>10</int>
                                    </value>
                                    <value>
                                          <int>3</int>
                                    </value>
                              </data>
                        </array>
                  </value>
            </param>
      </params>
</methodResponse>

이번에는 다른 방법을 사용하여 반환된 값을 MQL5 변수로 액세스했습니다.

MQL5-RPC_ex2_ATC2011AnalyzerClient (AUDUSD,H1)  12:39:23

lf8749:ias:Xupypr:aharata:beast:Tim:tmt0086:yyy999:bobsley:Diubakin:Pirat:
AAA777:notused:ronnielee:samclider-2010:gery18:Integer:GradyLee:p96900:
skarkalakos:Loky:zephyrrr:Medvedev:Gans-deGlucker:InvestProm:zioliberato:
QuarkSpark:rho2011:ld73:enivid:botaxuper:

MQL5-RPC_ex2_ATC2011AnalyzerClient (AUDUSD,H1)  12:39:29        

Contestants = 31. EURUSD positions = 10. BUY positions = 3

보시다시피 결과물도 꽤나 이해하기 쉬운 형태로 되어있습니다.


마치며

지금까지 저는 XML-RPC 프로토콜을 이용하여 MetaTrader 5가 원격 프로시져 호출을 할 수 있게 해주는 새로운 MQL5-RPC 프레임워크를 선보였습니다. 두가지 유즈 케이스를 보여드렸고, 처음 것은 웹 서비스에 접속하게해주는 것, 두번째 것은 Automated Trading Championship 2011을 위한 커스텀 분석기였습니다. 저 두 예시는 추후에 진행할 실험의 기반으로 활용될 것입니다.

저는 MQL5-RPC가 다양한 방법으로 활용될 수 있는 매우 강력한 기능이라고 생각합니다. 이 프레임워크를 오픈 소스로 만들고 GPL licencehttp://code.google.com/p/mql5-rpc/로 배포하기로 결정했습니다. 만약 이 코드를 더 개선시키거나 리팩터하거나 버그를수정하고싶으신 분들은 언제나 프로젝트에 합류하셔도 됩니다. 소스 코드와 예제들은 이 문서에 첨부된 첨부파일에서 확인하실 수 있습니다.


MetaQuotes 소프트웨어 사를 통해 영어가 번역됨
원본 기고글: https://www.mql5.com/en/articles/342

파일 첨부됨 |
mql5-rpc.zip (22.18 KB)
MetaTrader 5를MetaTrader4 시그널 프로바이더로 활용하기 MetaTrader 5를MetaTrader4 시그널 프로바이더로 활용하기
MetaTrader 5의 거래 분석을MetaTrader4에서 실행하는 방법, 그 예제 및 분석MetaTrader5로 시그널 프로바이더를 만들어 클라이언트에 연결하고,MetaTrader4에서 실행하는 방법을 알아보겠습니다.MetaTrader4의 실제 거래 계좌로 자동 매매 챔피언십 참가자들을 따르는 방법도 배우실 겁니다.
EA 트리를 이용하여 MQL5 Expert Advisor 뚝딱 만들기: 1부 EA 트리를 이용하여 MQL5 Expert Advisor 뚝딱 만들기: 1부
EA Tree는 최초의 드래그 앤 드랍 MetaTrader MQL5 Expert Advisor 생성기입니다. 매우 사용하기 편리한 GUI를 이용하여 복잡한 MQL5도 만들 수 있습니다. EA 트리에서는 박스들을 서로 연결하는 것으로 Expert Advisor를 만들 수 있습니다. 각 박스에는 MQL5 함수, 기술 인디케이터, 커스텀 인디케이터, 혹은 값이 들어있을 수 있습니다. "박스 트리"를 이용하여 EA 트리는 Expert Advisor MQL5 코드를 생성합니다.
다중 선형 회귀 분석 올인원 전략 생성기와 전략 테스터 다중 선형 회귀 분석 올인원 전략 생성기와 전략 테스터
이번 글에서는 매매 시스템 개발에 여러 방식으로 다중 선형 회귀 분석을 적용하는 방법을 다룹니다. 자동 전략 검색에 회귀 분석을 이용하는 방법을 알아보겠습니다. 프로그래밍에 대한 이해도가 높지 않아도 이용할 수 있는 회귀 방정식이 포함된 EA를 예로 들겠습니다.
올인 외환거래 전략 올인 외환거래 전략
본 문서의 목적은 가장 단순한 매매 전략인 "올인" 게임 원칙을 구현하는 것입니다. 우리의 목적은 딱히 수익을 올리는 Expert Advisor를 만드는게 아닙니다. 우리의 목표는 가장 나은 확률을 검토하여 여러번에 거쳐 초기 액수를 늘리는 것입니다. 인디케이터나 기술 분석, 이런 것에 대해 하나도 모르면서 외환시장에서 대박을 터뜨리는 것이 과연 가능할까요?