트레이딩 Expert Advisor를 처음부터 개발하기(16부): 웹에서 데이터 액세스하기(II)
소개
이전의 기사 "트레이딩 Expert Advisor 처음부터 개발하기 (15부): 웹에서 데이터에 액세스하기 (I)"에서 웹 사이트에서 우리가 선택한 데이터에 액세스하기 위해 MetaTrader 5 플랫폼을 사용하는 방법에 대한 전체 로직과 아이디어를 살펴보았습니다.
해당 글에서는 이러한 사이트에 액세스하는 방법과 플랫폼에서 사용하기 위해 해당 사이트에서 정보를 찾고 검색하는 방법에 대해 살펴보았습니다. 하지만 단순히 데이터를 캡처하는 것만으로는 의미가 없습니다. 그게 다가 아닙니다. 이러한 테크닉에서 가장 흥미로운 부분은 플랫폼으로 이 데이터를 가져오고 Expert Advisor에서 사용하는 방법을 배우는 것입니다. 이 방법은 그렇게 명확한 것이 아닙니다. 그러므로 MetaTrader 5에서 사용할 수 있는 모든 함수와 기능을 알고 이해하지 않으면 구현하기 어렵습니다.
계획 및 구현
이전 기사를 읽어보지 못했거나 이해하지 못했다면 이전 기사의 모든 개념을 이해하는 것이 좋습니다. 우리는 여기에서 그 주제들에 대해 계속 다룰 것이기 때문입니다. 우리는 여기서 많은 양의 내용들을 다룰 것이고 일련의 문제들을 해결하고 결국에는 아름다운 솔루션에 도달 할 것입니다. 우리는 여기서 보통의 사람들이 MetaTrader 5를 사용하는 방식과는 좀 다르게 접근할 것입니다. 플랫폼에 있는 몇몇 기능을 사용할 수 있는 링크를 찾기가 어려웠기 때문에 이렇게 말하지만 여기서는 이러한 리소스들 중에 하나를 사용하는 방법에 대해 설명하려고 합니다.
이제 작업을 시작해 봅시다
1. Expert Advisor를 통해서 인터넷 데이터에 액세스하기
이부분이 이 시스템에서 구현할 수 있는 가장 흥미로운 부분입니다. 간단한 일이기는 하지만 제대로 계획하지 않으면 위험한 일이 될 수 있습니다. 짧은 시간이라도 EA가 서버의 응답을 기다리게 되는 상태가 된다면 위험합니다.
로직은 아래 그림에 나와 있습니다:
EA가 캡처하려는 정보가 포함된 웹 서버와 직접 상호 작용하는 방법에 대해 살펴보겠습니다. 아래에서 이와 똑같이 작동하는 코드 예제 전체를 확인할 수 있습니다.
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { Print(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D)); } //+------------------------------------------------------------------+ string 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 "Bad"; for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed"; for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error"; for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++); for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]); return szInfo; } //+------------------------------------------------------------------+
자세히 살펴보면 이전 기사에서 만든 코드와 완전히 동일하지만 이제 이 코드는 EA의 일부이며 여기에서 사용하기 위해 약간 조정되었음을 알 수 있습니다. 즉 이전에 작동하던 것이 있으면 여기에서도 작동할 것입니다. 차이점은 EA에 새로운 조건이 포함되어 있다는 것인데 이는 코드가 매초마다 실행된다는 것을 의미합니다. 다시 말해 EA는 매초마다 원하는 웹 서버에 요청을 하고 응답을 기다립니다. 그런 다음 EA는 캡처한 데이터를 주고 다른 내부 함수로 돌아갑니다. 이 루프는 EA가 작동하는 동안 반복됩니다. 실행 결과는 아래에서 확인할 수 있습니다.
이 방법은 정확히 이런 식으로 수행되지만 저는 이 방법을 권장하지 않습니다. EA가 서버의 응답을 기다리면서 짧은 시간 동안에도 멈출 수 있기 때문입니다. - 이는 플랫폼의 거래 시스템과 EA를 위험에 빠뜨릴 수 있습니다. 하지만 여러분이 이 방법을 배우는 데 관심이 있다면 이 시스템을 통해 많은 것을 배울 수 있습니다.
그러나 여러분에게 인터넷과 플랫폼 간에 정보를 라우팅할 로컬 서버가 있다면 이 방법으로 충분할 수 있습니다. 이 경우 시스템이 요청을 하면 다음과 같은 일이 발생할 것입니다: 로컬 서버에는 아직 정보가 없으며 신속하게 응답하여 다음 단계를 빨리 실행할 수 있도록 할 것입니다
이제 이러한 유형의 작업을 수행하는 또 다른 방법을 살펴 보겠습니다. 이번에는 좀더 보안이 강화된 버전입니다. MetaTrader 5 스레딩 시스템을 사용하여 어느 정도의 보안을 달성하도록 하고 EA가 원격 웹 서버에 영향을 받지 않도록 할 것입니다. 그러므로 원격 서버의 응답을 기다리는 동안 잠시 대기할 수 있습니다. 우리는 EA가 웹에서 정보를 수집하여 무슨 일이 일어나고 있는지 알 수 있도록 하는 추가적인 조건을 만들 것입니다.
2. 커뮤니케이션 채널 만들기
온라인에서 수집한 데이터를 가져와 EA에서 사용하는 더 쉽고 간단한 방법은 채널입니다. 이 방법은 작동은 하겠지만 경우에 따라서는 이러한 채널을 사용하는 것에는 제한이 있기 때문에 우리에게 적합하지 않은 경우도 있습니다. 하지만 적어도 EA는 원격 서버의 응답을 기다릴 필요 없이 웹에서 수집한 정보에 액세스할 수 있습니다.
문제를 해결하는 가장 쉬운 방법은 로컬 서버를 만드는 것인데 로컬 서버는 데이터를 다운로드하여 MetaTrader 5 플랫폼에 제공하는 일을 합니다. 이를 데이터 라우팅이라고 이미 위에서 언급했습니다. 그러나 이를 위해서는 관련 지식과 컴퓨팅 성능이 필요하며 복잡합니다. 하지만 MetaTrader 5의 기능을 사용하면 로컬 서버를 통한 라우팅보다 훨씬 간단하면서도 상당히 유사한 채널을 만들 수 있습니다.
아래 그림은 이러한 채널을 만드는 방법을 보여줍니다.
채널은 객체를 사용하여 생성됩니다. EA는 스크립트가 배치한 정보를 찾기 위해 객체의 내부를 살펴볼 것입니다. 이것이 실제로 어떻게 작동하는지 이해하기를 위해 아래 세 가지 코드를 살펴보겠습니다. 하나는 EA이고 다른 하나는 객체를 포함하는 헤더이며 세 번째는 스크립트입니다.
#property copyright "Daniel Jose" #property description "Testing Inner Channel" #property version "1.00" //+------------------------------------------------------------------+ #include <Inner Channel.mqh> //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { Print(GetInfoInnerChannel()); } //+------------------------------------------------------------------+
다음 코드는 우리가 사용해야 하는 헤더입니다. 여기에서 객체는 EA와 스크립트 간에 공유되도록 선언되어 있습니다.
//+------------------------------------------------------------------+ //| Canal Intra Process.mqh | //| Daniel Jose | //| | //+------------------------------------------------------------------+ #property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_NameObjectChannel "Inner Channel Info WEB" //+------------------------------------------------------------------+ void CreateInnerChannel(void) { long id; ObjectCreate(id = ChartID(), def_NameObjectChannel, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(id, def_NameObjectChannel, OBJPROP_COLOR, clrNONE); } //+------------------------------------------------------------------+ void RemoveInnerChannel(void) { ObjectDelete(ChartID(), def_NameObjectChannel); } //+------------------------------------------------------------------+ inline void SetInfoInnerChannel(string szArg) { ObjectSetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT, szArg); } //+------------------------------------------------------------------+ inline string GetInfoInnerChannel(void) { return ObjectGetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT); } //+------------------------------------------------------------------+
마지막으로 스크립트입니다. 로컬 서버를 생성하는 것을 대체하며 실제로 서버의 작업을 수행합니다.
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ #include <Inner Channel.mqh> //+------------------------------------------------------------------+ void OnStart() { CreateInnerChannel(); while (!IsStopped()) { SetInfoInnerChannel(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D)); Sleep(200); } RemoveInnerChannel(); } //+------------------------------------------------------------------+ string 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 "Bad"; for (int c0 = 0, c1 = StringLen(szTest); (!_StopFlag) && (c0 < c1); c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed"; for (int c0 = 0, c1 = StringLen(szFind); (!_StopFlag) && (c0 < c1); c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error"; for (counter = 0; (!_StopFlag) && (charResultPage[counter + iInfo] == 0x20); counter++); for (;(!_StopFlag) && (charResultPage[counter + iInfo] != cLimit); counter++) szInfo += CharToString(charResultPage[counter + iInfo]); return (_StopFlag ? "" : szInfo); } //+------------------------------------------------------------------+
여기에서 우리에게는 EA가 볼 수 있고 스크립트에 의해 생성된 객체가 있습니다. 이렇게 하면 EA와 스크립트가 동일한 차트에 공존할 수 있다면 이 객체가 EA와 스크립트 간의 통신 채널이 될 것입니다. EA는 클라이언트가 되고 스크립트는 서버가 되며 객체는 이들 간의 통신 채널이 될 것입니다. 따라서 스크립트는 원격 웹 서버의 값을 캡처하고 원하는 값을 객체에 넣을 것입니다. 스크립트가 실행되고 있지 않으면 객체를 사용할 수 없어야 하므로 EA는 수시로 객체에 어떤 값이 있는지 확인합니다(객체가 있는 경우). 어쨌든 EA가 객체의 값을 살펴보는 시간은 스크립트를 위반하지 않습니다. 스크립트가 원격 서버의 응답을 대기 중이기 때문에 차단된 경우에도 EA는 스크립트가 수행하는 작업과 관계없이 계속 작동하므로 EA에는 영향을 미치지 않습니다.
이 방법은 훌륭한 솔루션이지만 완벽하지는 않습니다. 문제는 스크립트에 있습니다.
이를 이해하려면 다음 동영상을 시청하면서 모든 세부 사항에 주의를 기울이세요.
모든 것이 잘 작동합니다. 예상대로입니다. 왜냐하면 이러한 종류의 솔루션은 클라이언트-서버 프로그램을 개발할 때 프로그래밍에서 널리 사용되기 때문입니다. 다시 말해 우리는 프로세스 간 커뮤니케이션을 위해 채널을 사용하는 것입니다. 종종 동일한 환경에 있는 경우 채널은 메모리를 사용하여 생성되며 이를 위해 격리된 영역이 특별히 할당되지만 클라이언트와 서버가 모두 공유하여 볼 수 있습니다. 서버는 여기에 데이터를 추가하고 클라이언트는 기존 데이터를 가져오기 위해 같은 영역을 방문합니다. 따라서 둘은 서로 연결되어 있지만 하나는 다른 하나에 의존하지 않습니다.
우리는 이와 동일한 원리를 사용하는 것입니다. 하지만 스크립트가 작동하는 방식에 문제가 있습니다. 차트 주기를 전환하면 스크립트가 닫히고 무한 루프를 사용하더라도 MetaTrader 5에 의해 닫힙니다. 이 경우 우리는 스크립트를 다시 초기화하여 차트에 다시 넣어야 합니다. 하지만 계속해서 차트 주기를 전환해야 하는 경우 이는 문제가 될 것입니다. 매번 차트에서 스크립트를 다시 실행해야 하는 것은 말할 것도 없이 말입니다.
또한 차트에 스크립트가 있는지 여부를 확인하는 것을 잊어버릴 수 있으며 EA를 코딩하는 방식은 스크립트가 차트에 있는지 여부를 알려주지 않습니다. 그러므로 잘못된 정보를 사용하게 될 것입니다. 이 문제는 차트에 스크립트가 있는지 여부를 확인하여 해결할 수 있습니다. 이 작업은 어렵지 않습니다. 객체에 마지막 스크립트 게시 시간 확인을 작성하기만 하면 됩니다. 이렇게 하면 문제가 해결됩니다.
그러나 훨씬 더 나은 솔루션을 만들 수 있습니다(적어도 일부 경우에는). 솔직히 말해서 이것이 사실상 이상적인 솔루션이며 위에서 제시한 것과 동일한 개념을 사용할 것입니다. 단 스크립트 대신 서비스를 사용할 것입니다.
3. 서비스 만들기
이는 극단적인 해결책이지만 스크립트가 차트주기를 변경할 때마다 종료되는 문제가 있습니다. 그러므로 우리는 다른 방법을 사용해야 합니다. 하지만 한 가지 문제를 해결하면 또 다른 문제가 생깁니다. 어쨌든 어떤 솔루션이 가능하고 어떻게 사용할 수 있는지를 아는 것은 졸은 일입니다. 그러나 가장 중요한 것은 각 솔루션이 제시하는 한계를 파악하여 가능한 최선의 방법으로 문제를 해결할 수 있도록 하는 중간의 무언가를 찾는 것입니다.
프로그래밍은 하나의 문제를 해결하려고 할 때 새로운 문제를 만들어내는 것과 같이 일이 많습니다.
우리의 목표는 아래 이미지와 비슷한 것을 만드는 것입니다:
간단한 문제처럼 보일 수 있지만 관련된 리소스는 많지 않습니다. 따라서 이러한 리소스로 작업하는 방법에 대해 더 자세히 알고 싶은 분들을 돕기 위해 자세한 내용을 살펴보겠습니다.
3.1. 전역 변수에 대한 액세스
이 부분은 워낙 연구된 바가 적어서 처음에는 이 기능이 작동하도록 하기 위해 dll을 만들까도 생각했었지만 MQL5 문서를 찾아보고 나서야 찾아냈습니다. 문제는 서비스와 EA 간에 공통 지점에 액세스할 수 있거나 공통 지점을 만들어야 한다는 것입니다. 스크립트를 사용할 때는 이 지점이 객체였지만 서비스를 사용할 때는 그렇게 할 수 없습니다. 해결책은 외부 변수를 사용하는 것이지만 그렇게 하려고 하니 성능이 기대에 미치지 못했습니다. 자세한 내용은 외부 변수와 관련된 문서를 참조하세요. 문서에서 수행해야 할 작업에 대해 설명하는 부분이 있습니다.
이렇게 이 아이디어가 그리 좋지 않은 것을 알게 되었고 dll을 사용하기로 결정했습니다. 하지만 저는 여전히 MetaTrader 5와 MQL5를 배우고 싶었기 때문에 터미널을 살펴보았고 마침내 아래 이미지에서 볼 수 있는 것을 발견했습니다:
이것이 우리가 찾던 것입니다: 이 프로시저를 어떻게 구성할 수 있는지 확인할 수 있는 변수를 추가하는 것입니다. 하지만 double 값만 사용할 수 있습니다. 이것이 문제라고 생각할 수 있지만 (실제로는 제한 사항일 뿐이지만) 우리의 경우와 같이 짧은 메시지를 전송하려는 경우에는 이 정도면 충분합니다. 실제로 double 유형은 8자의 짧은 문자열이므로 프로그램 간에 값이나 짧은 메시지를 전송할 수 있습니다.
이제 문제의 첫 번째 부분이 해결되었습니다. MetaTrader 5는 dll을 만들지 않고 채널을 만드는 메서드를 제공하지만 또 다른 문제가 있습니다: 프로그램을 통해 이러한 변수에 어떻게 액세스할까요? Expert Advisor, 스크립트, 보조지표 또는 서비스 등 프로그램 내에서 전역 변수를 만들 수 있을까요? 아니면 터미널에 선언된 것만 사용해야 하나요?
이 솔루션을 실제로 사용하려면 이러한 질문이 매우 중요합니다. 프로그램을 통해 사용할 수 없다면 dll을 사용해야 합니다. 하지만 가능합니다. 자세한 내용은 터미널의 전역 변수를 참조하세요.
3.2. 정보 교환을 위해 터미널 변수를 사용하기
이제 기본적인 사항을 살펴보았으미 터미널 변수를 사용하는 프로세스가 실제로 어떻게 작동하는지를 테스트하고 이해할 수 있도록 간단한 것을 만들어 보겠습니다.
이를 위해 다음과 같은 코드를 만들었습니다. 첫 번째는 헤더 파일입니다:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_GlobalNameChannel "InnerChannel" //+------------------------------------------------------------------+
여기서는 그래픽 터미널에서 실행될 두 프로세스에 대해 동일한 전역 터미널 변수의 이름을 정의하기만 하면 됩니다.
아래는 실행할 서비스를 나타내는 코드입니다.
#property service #property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ #include <Inner Channel.mqh> //+------------------------------------------------------------------+ void OnStart() { double count = 0; while (!IsStopped()) { if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel); GlobalVariableSet(def_GlobalNameChannel, count); count += 1.0; Sleep(1000); } } //+------------------------------------------------------------------+
작동 방식은 간단합니다: 코드는 변수가 이미 선언되었는지 여부와 GlobalVariableCheck가 수행하는 작업을 확인합니다. 변수가 존재하지 않는 경우 GlobalVariableTemp 함수에 의해 임시로 생성된 다음 GlobalVariableSet 함수에서 값을 받습니다. 즉 우리는 정보를 테스트하고, 만들고, 작성하고 있으며, 서비스는 서버의 역할을 하고 있습니다. 마치 스크립트처럼 말입니다. 그러나 아직 웹 사이트에 액세스하지는 않고 있습니다. 먼저 우리는 시스템이 어떻게 작동하는지를 이해해야 합니다.
다음 단계는 이러한 경우 Expert Advisor인 클라이언트를 만드는 단계입니다:
#property copyright "Daniel Jose" #property description "Testing internal channel\nvia terminal global variable" #property version "1.03" //+------------------------------------------------------------------+ #include <Inner Channel.mqh> //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { double value; if (GlobalVariableCheck(def_GlobalNameChannel)) { GlobalVariableGet(def_GlobalNameChannel, value); Print(value); } } //+------------------------------------------------------------------+
코드는 간단합니다: 매초마다 EA가 변수의 존재 여부를 확인합니다. 존재하는 경우 EA는 GlobalVariableGet을 사용하여 값을 읽고 이 값을 터미널에 출력합니다.
이 프로세스가 어떻게 구현되는지 살펴보겠습니다. 먼저 서비스를 실행합니다. 다음과 같이 수행됩니다:
그러나 서비스가 중지되었다가 다시 시작하는 또 다른 시나리오도 가능합니다. 이 경우 다음과 같이 진행합니다:
그런 다음 터미널 변수를 확인하면 다음과 같은 결과를 얻을 수 있습니다:
시스템은 분명히 작동하고 있고 이제 차트에 EA를 배치하고 값을 가져와서 채널을 통해 연결을 확인해야 합니다. 차트에 EA를 배치하면 다음과 같은 결과를 얻을 수 있습니다:
이상입니다. 이제 시스템은 우리가 원하는 방식으로 작동합니다. 실제로 아래와 같은 모델이 있습니다. 일반적인 클라이언트-서버의 형식이며 이것이 바로 우리가 원하는 방식입니다. 앞서 말씀드린 장점 때문에 우리는 이 형식을 구현하려고 하는 것입니다.
이제 웹에서 값을 읽고 가져오는 시스템만 추가하면 됩니다. 그러면 우리는 테스트할 최종 모델을 갖게 됩니다. 이 부분은 매우 간단합니다. 처음부터 사용하던 코드를 가져와 서비스에 추가하기만 하면 됩니다. 테스트를 수행하려면 웹 사이트에서 값을 읽고 클라이언트가 읽을 수 있도록 하기 위해 해당 값을 게시하도록 서버 파일을 수정하기만 하면 됩니다. 새로운 서비스 코드는 다음과 같습니다.
#property service #property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ #include <Inner Channel.mqh> //+------------------------------------------------------------------+ void OnStart() { string szRet; while (!IsStopped()) { if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel); szRet = GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D); GlobalVariableSet(def_GlobalNameChannel, StringToDouble(szRet)); Sleep(1000); } } //+------------------------------------------------------------------+ string 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 "Bad"; for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed"; for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error"; for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++); for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]); return szInfo; } //+------------------------------------------------------------------+
이제 우리는 아래 이미지와 같이 작동하는 시스템을 갖게 되었습니다:
이제 준비가 완료되었으며 다음과 같은 결과를 얻을 수 있습니다. 또한 차트주기를 변경하는 것도 더 이상 문제가 되지 않습니다.
결론
오늘은 MetaTrader5의 몇 가지 기능에 대해 살펴보았습니다. 그중 하나가 바로 커뮤니케이션 채널입니다. 하지만 우리는 아직 이 기능을 최대한으로 활용하고 있지는 않는 상태입니다. 이에 대해서는 다음 글에서 좀 더 자세히 알아보도록 하겠습니다. 이 시리즈에서 지금까지 살펴본 모든 내용은 우리가 MetaTrader5 플랫폼에서 얼마나 많은 것을 할 수 있는지를 보여줍니다. 경로를 선택하고 원하는 결과를 얻을 때까지 계속 진행하면 되지만 가능한 각 경로와 관련된 제한 사항과 이점 및 위험을 아는 것이 필요합니다.
MetaQuotes 소프트웨어 사를 통해 포르투갈어가 번역됨
원본 기고글: https://www.mql5.com/pt/articles/10442