English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
preview
Expert Advisor 개발 기초부터(17부): 웹에서 데이터 액세스하기(III)

Expert Advisor 개발 기초부터(17부): 웹에서 데이터 액세스하기(III)

MetaTrader 5 | 20 4월 2023, 15:13
363 0
Daniel Jose
Daniel Jose

소개

이전 글 Expert Advisor 개발 기초부터(16부): 웹에서 데이터에 액세스하기(II) 에서 웹에서 데이터를 캡처할 때의 문제점과 결과에 대해 다루었습니다. 우리는 Expert Advisor에서 웹에서 데이터에 액세스 하는 방법과 각각의 장단점이 있는 세 가지 가능한 솔루션에 대해 살펴보았습니다.

Expert Advisor를 통해 직접 데이터를 캡처하는 첫 번째 솔루션에서는 느린 서버 응답과 관련된 문제가 있었습니다. 또한 이러한 문제가 거래 시스템에 미칠 수 있는 영향에 대해서도 언급했습니다.

두 번째 솔루션에서는 EA가 클라이언트의 역할을 하고 스크립트는 서버의 역할을 하며 객체는 채널의 역할을 하는 클라이언트-서버 모델을 기반으로 채널을 구현했습니다. 이 모델은 차트 주기를 변경하기로 결정하기 전 까지만 잘 작동하다가 사용하기가 불편해집니다. 이러한 점에도 불구하고 클라이언트-서버 모델을 사용하면 EA가 원격 서버를 기다리지 않고 이 정보의 출처에 관계없이 객체에 포함된 데이터를 읽기만 하면 되기 때문에 최고의 시스템입니다.

세 번째이자 마지막 솔루션에서는 서비스를 사용하여 클라이언트-서버 시스템을 개선했습니다. 그리고 우리는 잘 알려지지 않은 MetaTrader 5 플랫폼의 리소스인 터미널의 전역 변수를 사용하기로 했습니다. 이 솔루션은 스크립트를 활용하는 모델의 가장 큰 단점이었던 차트 주기 변경의 문제를 해결했습니다. 하지만 터미널의 전역 변수 시스템에서는 double 유형만 사용할 수 있다는 새로운 문제가 있습니다. 많은 사람들은 이 문제를 피해 가는 방법을 모릅니다. 그래서 MetaTrader 5에서 제공하는 채널을 통해 간단한 텍스트와 같은 다양한 정보를 전달합니다.

이 글에서는 이 제한을 극복하는 방법에 대해 설명합니다. 그러나 기적을 기대하지는 마세요. 시스템이 원하는 방식으로 작동하려면 우리는 많은 작업을 해야 할 것입니다.

이번에는 대체 시스템 개발에 대해 알아볼 것입니다.


1. 계획

아시다시피 MetaTrader 5에서 제공하는 채널 시스템에서는 double 유형의 변수만 사용할 수 있습니다. 이 유형은 8바이트로 구성됩니다. 여러분은 이것이 그다지 유용한 정보가 아니라고 생각할 수도 있습니다. 하지만 다음 내용을 보면 그렇지 않을 것입니다:

컴퓨터 시스템은 바이트 단위로 작동하지만, 많은 사람들이 이 개념을 잊고 있습니다. 하지만 이 사실은 이 시스템을 이해하는 것은 매우 중요합니다. 각 바이트는 8비트로 구성됩니다. 1 비트는 컴퓨팅 시스템에서 가능한 가장 작은 숫자입니다. 언어에 존재하는 가장 작고 간단한 유형은 하나의 단일 비트로 구성된 bool 유형입니다. 이것은 가장 간단한 기본 사항입니다.

따라서 아무리 복잡한 정보라도 1바이트 이내에 모든 정보가 포함될 수 있습니다. 다시 말하지만 아무리 복잡한 정보라 하더라도 정보는 항상 8비트로 구성된 1바이트 이내로 유지됩니다. 2바이트를 결합하면 시스템에서 첫 번째 컴파운드 집합을 얻습니다. 이 첫 번째 세트는 WORD, 두 번째 세트는 2 WORD가 되는 DWORD, 세 번째 세트는 2 DWORD가 되는 QWORD로 알려져 있습니다. 이는 모든 현대 언어의 뿌리인 assembly 에서 사용되는 명명법이며 대부분의 시스템에서 동일한 유형을 사용합니다. 유일한 차이점은 이러한 유형의 이름이 지정되는 방식입니다.

여기까지의 추론을 잘 따라오셨기를 바랍니다. 이제 막 시작하는 분들은 아래 그림을 참고하세요:

          

                         

위 이미지는 현재 사용 가능한 주요 유형을 보여주며, 1비트에서 64비트까지를 보여줍니다. "이런 설명이 왜 필요하죠?"라고 생각할 수도 있습니다. 이 글 전체에서 수행할 작업을 이해하려면 이러한 내용들을 알고 있는 것이 중요합니다. 왜냐하면 우리는 내부 속성이 다른 정보들을 전달할 수 있도록 이러한 유형들을 조작할 것이기 때문입니다.

이러한 각각의 유형은 사용되는 언어에 따라 다른 이름을 가질 수 있으며 MQL5의 경우 다음 표에 나와 있습니다:

이름 바이트 수 어셈블리 언어 기반 이름(위 이미지) 
bool   1비트만 사용하며 바이트에는 8개의 bool 값이 있을 수 있습니다.  1비트만 사용하며 바이트에는 8개의 bool 값이 있을 수 있습니다.
char 1
 Byte
short 2  Word
int  4  DWord
long 8  QWord

이 테이블에서는 부호 있는 정수를 다니다. MQL5에 대한 자세한 내용은 Interger 유형을 참조하시기 바랍니다. 다른 유형들이 정의되어 있습니다. 다음으로 실수 유형은 정수 유형과 몇 가지 유사점이 있지만 고유한 내부 포맷과 스타일이 있습니다. 포맷 지정 및 모델링의 예는 double precision number에서 확인할 수 있지만 기본적으로는 아래의 표와 같습니다.

이름 바이트 수 어셈블리 언어 기반 이름(위 이미지) 
Float 4  DWord
Doble 8  QWord

한 가지 흥미로운 점은 부동 소수점 모델과 정수 모델이 모두 동일한 데이터베이스를 사용하지만 길이가 다르다는 점입니다. 이제 우리가 정말 궁금해하는 지점에 도달했습니다. 만약 여러분이 관련 논리를 이해한다면 아래 이미지에서 볼 수 있는 다음과 같은 결론에 도달할 수 있습니다:

QWORD는 8바이트이므로 'double'을 사용하면 8바이트의 정보를 입력할 수 있습니다. 예를 들어 터미널 전역 변수에 인쇄 가능한 문자 8개를 전달하면 아래와 같이 서비스와 EA 간의 연결 결과를 얻을 수 있습니다.

데이터는 괜찮고 아이디어 자체는 이해할 수 있는 것으로 보입니다. 중요한 것은 메시지에 인쇄 가능한 문자가 8자 이상인 경우 메시지를 더 많은 부분으로 나눠야 한다는 점입니다. 그러나 정보를 매우 빠르게 전달해야 하는 경우, 즉 1사이클에 메시지를 전송해야 하는 경우에는 필요한 만큼의 글로벌 터미널 변수를 사용하여 한 사이클에 메시지를 전송해야 합니다. 그런 다음 원본 메시지를 복원하기 위해 함께 붙여야 합니다. 그러나 패킷으로 전달할 수 있는 경우 서비스는 클라이언트(이 경우 EA)가 메시지를 읽고 다음 블록을 기다릴 것임을 알 수 있도록 서버용 양식을 만들어야 합니다.

이러한 내용의 문제에는 여러 가지 해결책이 있습니다. 이러한 솔루션을 이해하거나 구현하려면 모든 것을 처음부터 만들 필요 없이 또는 같은 네트워크 통신 프로토콜에서와 동일한 모델링을 사용하고 글로벌 터미널 변수를 사용하여 정보 전송 시스템에 아이디어를 적용하면 됩니다. 프로토콜이 어떻게 작동하는지 이해하면 이 작업은 더 이상 복잡하지 않게 될 것이고 그저 여러분이 사용 중인 언어에 대한 기술과 지식에 달린 문제가 되는 것입니다. 이는 매우 광범위한 주제이며 각 상황과 문제의 유형에 대해 별도로 연구할 가치가 있습니다.


2. 구현

이제 사용할 아이디어가 무엇인지를 이해했으므로 초기의 구현을 통해 시스템이 서비스와 EA 간에 정보를 전송하는 방법을 테스트할 수 있을 것입니다. 여기서 우리의 작업은 인쇄 가능한 문자만 전달할 것입니다.

2.1. 기본 모델

이전 기사에서 살펴본 시스템을 사용하여 헤더 파일부터 시작하여 파일을 수정하겠습니다. 새로운 콘텐츠는 아래 코드에 전체 내용이 나와 있습니다:

//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalNameChannel   "InnerChannel"
//+------------------------------------------------------------------+
union uDataServer
{
        double  value;
        char    Info[sizeof(double)];
};
//+------------------------------------------------------------------+

이 헤더는 기본입니다. 여기에는 글로벌 터미널 값의 선언이 포함되어 있습니다. 또한 새로운 구조인 union이 있습니다. 구조는 인터리빙 없이 데이터를 조합하는 반면, 유니온은 작은 데이터가 큰 데이터 안에 있을 때 인터리빙을 항상 사용한다는 점에서 구조와 다릅니다. 이전 사례에서는 내부에 8바이트가 있는 double 값을 기본값으로 사용했습니다. 저는 길이 sizeof를 캡처하기 위해 시스템을 사용합니다. 그러므로 향후에 더 큰 double이 있게 되면 - 그럴거 같지는 않지만 - 이 코드가 자동으로 이에 맞게 조정된다는 점에 유의하세요.

결과적으로 다음과 같은 결과를 얻을 수 있습니다:

위의 그림과 비슷하지만 union이 하는 일이 바로 이것입니다.

다음으로 수정할 코드는 클라이언트에 해당하는 EA입니다. 전체 코드는 아래에서 확인할 수 있습니다:

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.04"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        uDataServer loc;
        
        if (GlobalVariableCheck(def_GlobalNameChannel))
        {
                GlobalVariableGet(def_GlobalNameChannel, loc.value);
                Print(CharArrayToString(loc.Info, 0, sizeof(uDataServer)));
        }
}
//+------------------------------------------------------------------+

여기서는 uchar 배열을 문자열로 변환하는 함수 CharArrayToString을 사용합니다. 그러나 여전히 double 값이란 점에 유의하세요. double 값이 터미널의 전역 변수에서 수신할 수 있는 유일한 값이기 때문입니다. 이와는 대조적으로 MQL5의 string은 C/C++의 원칙을 따르기 때문에 어떤 문자도 사용할 수 없고 오직 우리 자신만의 문자를 만들 수 있습니다. 하지만 그건 또 다른 이야기입니다. 여기서는 이에 대해 자세히 설명하지 않겠습니다: 여러분은 아마 8바이트 제한을 초과하기 위해 모델링 데이터 압축을 사용하는 것을 생각하고 있을지 모릅니다.

하지만 우리는 여전히 서버의 역할을 하는 프로그램이 필요합니다. 우리의 경우 서버는 서비스입니다. 아래는 이 시스템을 테스트하는 코드입니다:

//+------------------------------------------------------------------+
#property service
#property copyright "Daniel Jose"
#property version   "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        uDataServer loc;
        char car = 33;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                for (char c0 = 0; c0 < sizeof(uDataServer); c0++)
                {
                        loc.Info[c0] = car;
                        car = (car >= 127 ? 33 : car + 1);
                }
                GlobalVariableSet(def_GlobalNameChannel, loc.value);
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+

간단하지만 매우 효과적이고 기능적으로 좋습니다.

플랫폼에서 프로그램을 실행하면 다음과 같은 결과를 얻을 수 있습니다.


바보같고 무의미해 보일 수도 있지만 약간의 창의력을 발휘하면 이 시스템을 충분히 유용하게 만들어 다른 사람들이 상상조차 할 수 없는 일을 할 수 있습니다.

그것이 무엇인지를 보여드리기 위해 시스템을 수정하고 아주 간단한 것을 보여드리겠습니다. 여러분의 호기심과 관심을 불러일으킬 수 있다고 생각해서입니다. 이러한 통신 시스템에서 무언가 이색적인 기능을 찾을 수 있는 것이 있을지 생각해 보세요.


2.2. 스티커 교환

스티커 교환은 클라이언트와 서버 간에 정보를 교환하는 것으로 이 과정에서 서버는 클라이언트가 어떤 정보를 받기를 원하는지 파악하여 해당 정보를 생성하거나 찾을 수 있습니다.

개념은 이해하기가 아주 간단합니다. 그러나 특히 채널이 데이터 전송에 사용되는 동안 8바이트만 사용할 수 있는 데이터 모델링의 경우 구현이 상당히 어려울 수 있습니다.

2.2.1. 클라이언트-서버 통신 테스트

아래 서비스 코드 전체를 살펴보세요:

#property service
#property copyright "Daniel Jose"
#property version   "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        uDataServer loc, loc1, loc2;
        char car = 33;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalValueInChannel))
                {
                        GlobalVariableTemp(def_GlobalValueInChannel);
                        GlobalVariableTemp(def_GlobalMaskInfo);
                        GlobalVariableTemp(def_GlobalPositionInfos);
                }
                for (char c0 = 0; c0 < sizeof(uDataServer); c0++)
                {
                        loc.Info[c0] = car;
                        car = (car >= 127 ? 33 : car + 1);
                }
                GlobalVariableSet(def_GlobalValueInChannel, loc.value);
                GlobalVariableGet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableGet(def_GlobalPositionInfos, loc2.value);
                Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), "   ",loc2.Position[0], "    ", loc2.Position[1]);
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+

새로운 서비스 코드에서 특히 흥미로운 부분(서버 역할을 하는)에 주목하세요. 이제 변수가 하나가 아닌 세 개가 있습니다. 이들은 클라이언트(EA 또는 케이스)와 서버(당사 서비스) 간의 통신이 가능하도록 충분히 큰 채널을 생성하는 데에 쓰입니다. 다음 줄에 주목하세요:

Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), "   ",loc2.Position[0], "    ", loc2.Position[1]);

클라이언트가 게시한 데이터입니다. 여기서 2개의 변수를 사용하여 3개의 서로 다른 정보를 전달하고 있다는 점에 유의하세요. 하지만 그게 어떻게 가능할까요? 이를 이해하려면 아래 헤더 코드를 확인해야 합니다.

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalValueInChannel        "Inner Channel"
#define def_GlobalMaskInfo                      "Mask Info"
#define def_GlobalPositionInfos         "Positions Infos"
//+------------------------------------------------------------------+
union uDataServer
{
        double  value;
        uint    Position[2];
        char    Info[sizeof(double)];
};
//+------------------------------------------------------------------+

여러분은 이 union 내의 각 변수가 서로 격리되어 있다고 생각할지 모릅니다. 이 글의 앞 부분을 살펴보시기 바랍니다. 이름이 다른 변수가 있는데 여기서는 8바이트 너비의 하나의 변수로 취급됩니다. 더 명확하게 이해하려면 현재 상황을 정확하게 나타내 주는 아래의 이미지를 참조하세요:

이 구성표는 uDataServer 내부에 무엇이 있는지를 보여줍니다.

이것이 너무 복잡해 보이면 union이 실제로 어떻게 작동하는지 이해하기 위해 union을 실험해 보는 것이 좋습니다. uinon은 프로그래밍에 매우 유용합니다.

다시 시스템으로 돌아가 보겠습니다. 다음으로 할 일은 클라이언트용 코드, 즉 EA를 만드는 것입니다. 아래에서 그 전체 내용을 확인할 수 있습니다.

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.04"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
enum eWhat {DOW_JONES, SP500};
input eWhat     user01 = DOW_JONES;     //Search
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        uDataServer loc;
        
        SetFind();
        if (GlobalVariableCheck(def_GlobalValueInChannel))
        {
                GlobalVariableGet(def_GlobalMaskInfo, loc.value);
                Print(CharArrayToString(loc.Info, 0, sizeof(uDataServer)), "  ", GlobalVariableGet(def_GlobalValueInChannel));
        }
}
//+------------------------------------------------------------------+
inline void SetFind(void)
{
        static int b = -1;
        uDataServer loc1, loc2;
        
        if ((GlobalVariableCheck(def_GlobalValueInChannel)) && (b != user01))
        {
                b = user01;
                switch (user01)
                {
                        case DOW_JONES  :
                                StringToCharArray("INDU:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 172783;
                                loc2.Position[1] = 173474;
                                break;
                        case SP500              :
                                StringToCharArray("SPX:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 175484;
                                loc2.Position[1] = 176156;
                                break;
                }
                GlobalVariableSet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableSet(def_GlobalPositionInfos, loc2.value);
        }
};
//+------------------------------------------------------------------+

이 EA에서 우리는 정보를 송수신합니다. 다시 말해 서비스 작동 방식을 우리가 제어할 수 있는 것입니다. 하나의 변수에서 우리는 서비스가 무엇을 찾아야 하는지를 나타내는 작은 문자열을 전달하고 다른 변수에는 2개의 주소 지점을 전달합니다.

이에 대한 응답으로 서비스는 정보를 반환합니다. 이 첫 번째 요점을 이해하려면 아래 비디오에서 결과를 살펴보시기 바랍니다:



3.1.2.2 - 실용적인 버전 만들기

이제 우리는 시스템이 어떻게 작동하는지를 알았습니다. 이제는 정말 기능적인 것을 만들 수 있을 것입니다. 이번에는 웹 서버에서 정보를 수집합니다. 이를 위해서는 무슨 일이 일어나고 있는지를 완벽하게 파악할 수 있도록 여러 가지 수정이 필요합니다. 우리는 분석에 가비지를 사용하고 있지만 실제로는 업데이트된 데이터를 수신하고 있다고 생각할 수 있습니다. 여러분은 프로그래밍 단계에서 이러한 위험에 노출되지 않도록 매우 주의해야 합니다. 여러분이 할 수 있는 일은 가능한 한 많은 테스트를 하고 실행 중에 감지할 수 있는 이상 활동을 시스템에서 보고하도록 하는 것입니다.

기억하세요: 정보는 사용자가 신뢰하는 경우에만 유용합니다.

먼저 헤더 파일을 다음과 같이 편집해 보겠습니다:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalValueInChannel        "Inner Channel"
#define def_GlobalMaskInfo              "Mask Info"
#define def_GlobalPositionInfos         "Positions Infos"
//+------------------------------------------------------------------+
#define def_MSG_FailedConnection        "BAD"
#define def_MSG_FailedReturn            "FAILED"
#define def_MSG_FailedMask              "ERROR"
#define def_MSG_FinishServer            "FINISH"
//+------------------------------------------------------------------+
union uDataServer
{
        double  value;
        uint            Position[2];
        char            Info[sizeof(double)];
};
//+------------------------------------------------------------------+

강조 표시된 부분은 이상한 활동을 보고할 때 사용할 코드를 나타냅니다. 여러분은 최대 8자를 사용해야 합니다. 그러나 시장에서 만들어지지 않을 것 같은 시퀀스를 만들어야 하는데 이는 쉬운 일이 아닙니다. 모든 것이 정상으로 보이더라도 서버 오류 메시지로 사용할 시퀀스에 해당하는 값을 생성할 위험은 항상 존재합니다. 어쨌든 여러분은 이러한 목적으로 터미널의 전역 변수를 사용할 수도 있습니다. 그럴 경우 만들 수 있는 조합의 수가 증가하여 더 많은 것을 만들 수 있습니다. 하지만 저는 가능한 한 적은 수의 전역 터미널 변수를 사용하고 싶었습니다. 하지만 실제 상황에서 제가 보기에는 오류나 비정상적인 활동을 표시하고 보고하는 용도로만 변수를 사용할 수 있을 것 같습니다.

다음 부분은 EA의 전체 코드입니다.

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.04"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
enum eWhat {DOW_JONES, SP500};
input eWhat     user01 = DOW_JONES;             //Search
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        ClientServer();
}
//+------------------------------------------------------------------+
inline void ClientServer(void)
{
        uDataServer loc1, loc2;
        string          sz0;
        
        SetFind();
        if (GlobalVariableCheck(def_GlobalValueInChannel))
        {
                GlobalVariableGet(def_GlobalMaskInfo, loc1.value);
                loc2.value = GlobalVariableGet(def_GlobalValueInChannel);
                sz0 = CharArrayToString(loc2.Info, 0, sizeof(uDataServer));
                if (sz0 == def_MSG_FailedConnection) Print("Failed in connection."); else
                if (sz0 == def_MSG_FailedReturn) Print("Error in Server Web."); else
                if (sz0 == def_MSG_FailedMask) Print("Bad Mask or position."); else
                if (sz0 == def_MSG_FinishServer) Print("Service Stop."); else
                Print(CharArrayToString(loc1.Info, 0, sizeof(uDataServer)), "  ", loc2.value);
        }
}
//+------------------------------------------------------------------+
inline void SetFind(void)
{
        static int b = -1;
        uDataServer loc1, loc2;
        
        if ((GlobalVariableCheck(def_GlobalValueInChannel)) && (b != user01))
        {
                b = user01;
                switch (user01)
                {
                        case DOW_JONES  :
                                StringToCharArray("INDU:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 172783;
                                loc2.Position[1] = 173474;
                                break;
                        case SP500              :
                                StringToCharArray("SPX:IND", loc1.Info, 0, sizeof(uDataServer));
                                loc2.Position[0] = 175487;
                                loc2.Position[1] = 176159;
                                break;
                }
                GlobalVariableSet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableSet(def_GlobalPositionInfos, loc2.value);
        }
};
//+------------------------------------------------------------------+

강조 표시된 줄은 매우 중요합니다. 무슨 일이 일어나고 있는지 알려주는 곳이기 때문입니다. 보시다시피 우리는 이제 헤더 파일에 생성된 시퀀스가 제공하는 것보다 사용자에게 더 자세한 내용을 알려줄 수 있으므로 솔루션을 프로그래밍하고 유지 관리하기가 더 쉬워집니다. 나머지 코드는 크게 변경되지 않았습니다. 아래 서비스 코드를 확인하세요.

#property service
#property copyright "Daniel Jose"
#property version   "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        uDataServer loc1, loc2;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalValueInChannel))
                {
                        GlobalVariableTemp(def_GlobalValueInChannel);
                        GlobalVariableTemp(def_GlobalMaskInfo);
                        GlobalVariableTemp(def_GlobalPositionInfos);
                }
                GlobalVariableGet(def_GlobalMaskInfo, loc1.value);
                GlobalVariableGet(def_GlobalPositionInfos, loc2.value);
                if (!_StopFlag)
                {
                        GlobalVariableSet(def_GlobalValueInChannel, GetDataURL(
                                                                                "https://tradingeconomics.com/stocks",
                                                                                100,
                                                                                "<!doctype html>",
                                                                                2,
                                                                                CharArrayToString(loc1.Info, 0, sizeof(uDataServer)),
                                                                                loc2.Position[0],
                                                                                loc2.Position[1],
                                                                                0x0D
                                                                               )
                                        );
                        Sleep(1000);
                }
        }
        GlobalVariableSet(def_GlobalValueInChannel, Codification(def_MSG_FinishServer));
}
//+------------------------------------------------------------------+
double GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string          headers, szInfo = "";
        char                    post[], charResultPage[];
        int                     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1) return Codification(def_MSG_FailedConnection);
        for (int c0 = 0, c1 = StringLen(szTest); (c0 < c1) && (!_StopFlag); c0++) if (szTest[c0] != charResultPage[iTest + c0]) return Codification(def_MSG_FailedReturn);
        for (int c0 = 0, c1 = StringLen(szFind); (c0 < c1) && (!_StopFlag); c0++) if (szFind[c0] != charResultPage[iPos + c0]) return Codification(def_MSG_FailedMask);
        if (_StopFlag) return Codification(def_MSG_FinishServer);
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return StringToDouble(szInfo);
}
//+------------------------------------------------------------------+
inline double Codification(const string arg)
{
        uDataServer loc;
        StringToCharArray(arg, loc.Info, 0, sizeof(uDataServer));
        
        return loc.value;
}
//+------------------------------------------------------------------+

강조 표시된 줄도 중요합니다. 서비스가 더 이상 실행되고 있지 않음을 알려주는 알림이 표시됩니다.

따라서 이 시스템을 실행하면 다음과 같은 결과를 얻을 수 있습니다:


결론

MetaTader 5 플랫폼에서 웹 데이터 조사, 검색 및 사용과 관련된 아이디어에 대해 제가 절 설명해 드렸기를 바랍니다. 특히 프로그래밍에 대한 지식이 많지 않은 분들에게는 처음에는 이 내용이 명확하지 않을 수 있지만 시간이 지나면서 훈련을 하고 학습을 하다 보면 결국 이 자료의 대부분을 마스터할 수 있을 것입니다. 제가 아는 것을 조금이라도 공유하려고 노력했습니다.

MetaQuotes 소프트웨어 사를 통해 포르투갈어가 번역됨
원본 기고글: https://www.mql5.com/pt/articles/10447

파일 첨부됨 |
Servi0o_-_EA.zip (10.71 KB)
표준 편차로 거래 시스템을 설계하는 방법 알아보기 표준 편차로 거래 시스템을 설계하는 방법 알아보기
이 기사는 MetaTrader 5 거래 플랫폼에서 가장 인기 있는 기술 지표를 사용하여 거래 시스템을 설계하는 방법에 대해 알아보는 시리즈의 새로운 기사입니다. 이번 기사에서는 표준 편차 지표로 거래 시스템을 설계하는 방법에 대해 알아봅니다.
트레이딩 Expert Advisor를 처음부터 개발하기(16부): 웹에서 데이터 액세스하기(II) 트레이딩 Expert Advisor를 처음부터 개발하기(16부): 웹에서 데이터 액세스하기(II)
웹에서 Expert Advisor에 데이터를 입력하는 방법은 그리 당연하지 않습니다. MetaTrader 5가 제공하는 모든 가능성을 이해하지 않고는 그렇게 쉬운 일이 아닙니다.
차이킨 오실레이터로 트레이딩 시스템을 설계하는 방법 알아보기 차이킨 오실레이터로 트레이딩 시스템을 설계하는 방법 알아보기
가장 인기 있는 보조지표로 트레이딩 시스템을 설계하는 방법을 소개하는 시리즈의 새로운 글에 오신 것을 환영합니다. 이 새로운 기사를 통해 우리는 차이킨 오실레이터 지표로 거래 시스템을 설계하는 방법을 배웁니다.
데이터 과학 및 머신 러닝(파트 06): 경사 하강법(Gradient Descent) 데이터 과학 및 머신 러닝(파트 06): 경사 하강법(Gradient Descent)
경사 하강법은 신경망과 여러가지 머신러닝 알고리즘을 훈련하는 데 중요한 역할을 합니다. 경사 하강법은 인상적인 작업을 하면서도 빠르고 지능적인 알고리즘입니다. 많은 데이터 과학자들이 잘못 알고 있기도 한데 경사 하강법이 무엇인지 살펴보겠습니다.