English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
MetaTrader 5의 병렬 계산

MetaTrader 5의 병렬 계산

MetaTrader 5 | 4 8월 2021, 16:42
108 0
ds2
ds2


프로세서 병렬성 소개

거의 모든 최신 PC는 여러 프로세서 코어가 있기 때문에 여러 작업을 동시에 수행할 수 있습니다. 그들의 수는 매년 증가하고 있습니다 - 2, 3, 4, 6 코어 ... 인텔은 최근에 작동하는 80 코어 프로세서를 시연했습니다 (예, 이것은 오타가 아닙니다 - 80 코어 - 안타깝게도 이 프로세서는 기술의 잠재적 능력 연구라는 목적으로만 제작되었으므로 이 컴퓨터는 매장에 나타나지 않습니다).

모든 컴퓨터 사용자 (초보 프로그래머도 아님)가 작동 방식을 이해하는 것은 아닙니다. 따라서 누군가는 분명히 질문 할 것입니다. 이전에도 (단일 코어를 사용하는) 컴퓨터가 여러 프로그램을 동시에 실행할 수 있었고 모두 작동 했는데도 왜 그렇게 많은 코어가 있는 프로세서가 필요합니까? 신뢰는 그렇지 않다는 것입니다. 다음 다이어그램을 보겠습니다.

그림 1. 응용 프로그램의 병렬 실행

그림 1. 응용 프로그램의 병렬 실행

다이어그램의 사례 A는 단일 코어 프로세서에서 단일 프로그램을 실행하면 어떻게 되는지 보여줍니다. 프로세서는 구현에 모든 시간을 할애하고 프로그램은 시간 T에 걸쳐 일정량의 작업을 수행합니다.

Case B - 2개의 프로그램 시작. 그러나 프로세서는 물리적으로 어느 한 시점에서 코어 중 하나가 하나의 명령만 실행할 수 있도록 배열되어 있으므로 두 프로그램 간에 지속적으로 전환해야 합니다. 즉, 첫 번째 프로그램 중 일부를 실행 한 다음 두 번째 등. 이것은 초당 여러 번 매우 빠르게 발생하므로 프로세서가 두 프로그램을 동시에 실행하는 것처럼 보입니다. 그러나 실제로는 각 프로그램이 프로세서에서 개별적으로 실행되는 경우보다 실행 시간이 두 배 더 오래 걸립니다.

사례 C는 프로세서의 코어 수가 실행중인 프로그램 수와 일치하면 이 문제가 효과적으로 해결되었음을 보여줍니다. 각 프로그램에는 별도의 코어가 있으며 A의 경우처럼 실행 속도가 빨라집니다.

사례 D는 많은 사용자의 일반적인 착각에 대한 반응입니다. 그들은 프로그램이 멀티 코어 프로세서에서 실행되고 있다면 몇 배 더 빨리 실행된다고 믿습니다. 일반적으로 프로세서가 프로그램을 개별 부분으로 독립적으로 나누고 동시에 모두 실행할 수 없기 때문에 이것은 사실이 아닙니다.

예를 들어, 프로그램이 먼저 암호를 요청한 다음 확인을 수행하면 한 코어에서 암호 프롬프트를 수행하고 다른 코어에서 동시에 확인을 수행하는 것은 허용되지 않습니다. 시작 당시 암호가 아직 입력되지 않았기 때문에 확인은 성공하지 못할 것입니다.

프로세서는 프로그래머가 구현 한 모든 디자인이나 프로그램 작업의 전체 논리를 알지 못하기 때문에 코어간에 프로그램을 독립적으로 분리 할 수 ​​없습니다. 따라서 다중 코어 시스템에서 단일 프로그램을 실행하면 하나의 코어만 사용하고 단일 코어 프로세서에서 실행되는 것과 동일한 속도로 실행됩니다.

사례 E는 프로그램이 모든 코어를 사용하고 더 빨리 실행되도록 하기 위해 수행해야 하는 작업을 설명합니다. 프로그래머는 프로그램의 논리를 알고 있으므로 개발 중에 동시에 실행될 수 있는 프로그램의 해당 부분을 표시해야 합니다. 프로그램은 실행 중에 이 정보를 프로세서에 전달하고 프로세서는 필요한 수의 코어에 프로그램을 할당합니다.


MetaTrader의 병렬성

이전 장에서 우리는 모든 CPU 코어를 사용하고 프로그램 실행을 가속화하기 위해 수행해야 하는 작업을 파악했습니다. 프로그램의 병렬화 가능한 코드를 별도의 스레드에 할당해야 합니다. 많은 프로그래밍 언어에는 이를 위한 특수 클래스 또는 연산자가 있습니다. 그러나 MQL5 언어에는 이러한 내장 도구가 없습니다. 그래서 우리가 뭘 할 수 있을까요?

이 문제를 해결하는 방법에는 두 가지가 있습니다.

1. DLL 사용 2. MetaTrader의 비 언어 자원 사용
병렬화 도구가 내장된 언어로 DLL을 생성하면 MQL5-EA에서도 병렬화를 얻을 수 있습니다. MetaTrader 개발자의 정보에 따르면 클라이언트 터미널의 아키텍처는 다중 스레드입니다. 따라서 특정 조건에서 들어오는 시장 데이터는 별도의 스레드에서 처리됩니다. 따라서 프로그램 코드를 여러 EA 또는 표시기로 분리하는 방법을 찾을 수 있다면 MetaTrader는 실행을 위해 여러 CPU 코어를 사용할 수 있습니다.


첫 번째 방법은 이 글에서 다루지 않을 것입니다. DLL에서 우리가 원하는 모든 것을 구현할 수 있다는 것은 분명합니다. 우리는 MetaTrader의 표준 수단만을 포함하고 MQL5 이외의 다른 언어를 사용할 필요가 없는 솔루션을 찾으려고 노력할 것입니다.

그리고 두 번째 방법에 대해 자세히 설명합니다. MetaTrader에서 다중 코어가 어떻게 지원되는지 정확히 알아내기 위해 일련의 실험을 수행해야 합니다. 이를 위해 CPU를 많이 로드하는 진행 중인 작업을 수행하는 테스트 표시기와 테스트 EA를 생성해 보겠습니다.

다음 i-flood 지표를 작성했습니다.

//+------------------------------------------------------------------+
//|                                                      i-flood.mq5 |
//+------------------------------------------------------------------+
#property indicator_chart_window

input string id;
//+------------------------------------------------------------------+
void OnInit()
  {
   Print(id,": OnInit");
  }
//+------------------------------------------------------------------+
int OnCalculate(const int rt,const int pc,const int b,const double &p[])
  {
   Print(id,": OnCalculate Begin");
   
   for (int i=0; i<1e9; i++)
     for (int j=0; j<1e1; j++);
     
   Print(id,": OnCalculate End");
   return(0);   
  }
//+------------------------------------------------------------------+

다음과 유사한 e-flood EA:

//+------------------------------------------------------------------+
//|                                                      e-flood.mq5 |
//+------------------------------------------------------------------+
input string id;
//+------------------------------------------------------------------+
void OnInit()
  {
   Print(id,": OnInit");
  }
//+------------------------------------------------------------------+
void OnTick()
  {
   Print(id,": OnTick Begin");
   
   for (int i=0; i<1e9; i++)
     for (int j=0; j<1e1; j++);
     
   Print(id,": OnTick End");
  }
//+------------------------------------------------------------------+

또한 다양한 조합의 차트 창 (하나의 차트, 같은 심볼을 가진 두 개의 차트, 다른 심볼을 가진 두 개의 차트)을 열고 그 위에이 표시기 또는 EA의 복사본을 하나 또는 두 개 배치함으로써 단말이 CPU 코어를 어떻게 사용하는지 관찰 할 수 있습니다.

이러한 표시기와 EA는 또한 메시지를 로그에 전송하며, 표시 순서를 관찰하는 것은 흥미롭습니다. 이러한 로그는 직접 생성 할 수 있으므로 제공하지 않겠지만 이 글에서는 터미널에서 사용되는 코어 수와 차트 조합을 확인하는 데 관심이 있습니다.

Windows "작업 관리자"를 통해 작업 코어 수를 측정 할 수 있습니다.

그림 2. CPU 코어

그림 2. CPU 코어


모든 측정 결과는 아래 표에 정리되어 있습니다.


조합
 터미널의 내용
CPU 사용량
1
하나의 차트에 2 개의 지표 1 core
2
다른 차트에 있는 2 개의 지표, 동일한 쌍 1 core
3
다른 차트, 다른 쌍에 2 개의 지표 2 코어
4
같은 차트에 EA 2 개 - 불가능한 상황 -
5
다른 차트에 있는 2 개의 EA, 동일한 쌍 2 코어
6
다른 차트, 다른 쌍에 2 개 2 코어
7
EA에서 생성된 서로 다른 쌍에 대한 2 개의 지표 2 코어


7 번째 조합은 많은 거래 전략에서 사용되는 지표를 만드는 일반적인 방법입니다.

유일한 특징은 두 개의 다른 통화 쌍에 대해 두 개의 지표를 만들었다는 것입니다. 조합 1과 2는 지표를 같은 쌍에 배치하는 것이 합리적이지 않다는 것을 분명히 하기 때문입니다. 이 조합을 위해 저는 EA e-flood-starter를 사용하여 두 개의 i-flood 사본을 생성했습니다.

//+------------------------------------------------------------------+
//|                                              e-flood-starter.mq5 |
//+------------------------------------------------------------------+
void OnInit() 
  {
   string s="EURUSD";
   for(int i=1; i<=2; i++) 
     {
      Print("Indicator is created, handle=",
            iCustom(s,_Period,"i-flood",IntegerToString(i)));
      s="GBPUSD";
     }
  }
//+------------------------------------------------------------------+

따라서 모든 코어 계산이 수행되었으며 이제 MetaTrader가 다중 코어를 사용하는 조합을 알고 있습니다. 다음으로 이 지식을 적용하여 병렬 계산의 아이디어를 구현하려고 합니다.


우리는 병렬 시스템을 설계합니다.

병렬 시스템의 거래 터미널과 관련하여 우리는 함께 거래를 수행하거나 차트에 그리기와 같은 공통 작업을 수행하는 표시기 그룹 또는 EA (또는 둘 모두의 혼합)를 의미합니다. 이 그룹은 하나의 큰 표시기 또는 하나의 큰 EA로 작동 함을 의미합니다. 그러나 동시에 사용 가능한 모든 프로세서 코어에 계산 부하를 분산합니다.

이러한 시스템은 두 가지 유형의 소프트웨어 구성 요소로 구성됩니다.

  • CM - 계산 모듈. 해당 수는 2 개에서 최대 프로세서 코어 수까지 가능합니다. 병렬화해야 하는 모든 코드가 배치되는 것은 CM에 있습니다. 이전 장에서 알 수 있듯이 CM은 EA뿐만 아니라 지표로도 구현할 수 있습니다. 모든 형태의 구현에는 모든 프로세서 코어를 사용하는 조합이 있습니다.
  • MM - 메인 모듈. 시스템의 주요 기능을 수행합니다. 따라서 MM이 지표면 차트에 그리기를 수행하고 MM이 Ea이면 거래 기능을 수행합니다. MM은 또한 모든 CM을 관리합니다.

예를 들어 MM EA 및 2 코어 프로세서의 경우 시스템 작업 체계는 다음과 같습니다.

그림 3. 2 개의 CPU 코어가 있는 시스템 구성.

그림 3. 2 개의 CPU 코어가 있는 시스템 구성.

우리가 개발한 시스템은 주어진 순간에 필요한 절차를 호출 할 수 있는 전통적인 프로그램이 아니라는 점을 이해해야 합니다. MM 및 CM은 EA 또는 지표입니다. 즉, 이것은 독립적이고 독립형 프로그램입니다. 그들 사이에는 직접적인 연결이 없으며 독립적으로 작동하며 서로 직접 통신 할 수 없습니다.

이러한 프로그램의 실행은 이벤트 (예: 견적의 도착 또는 타이머 틱)의 터미널에 나타나는 경우에만 시작됩니다. 그리고 이벤트 사이에 이러한 프로그램이 서로에게 전달하려는 모든 데이터는 공개적으로 액세스되는 장소 ( "데이터 교환 버퍼"라고 하겠습니다) 외부의 어딘가에 저장되어야 합니다. 따라서 위의 방식은 단말기에서 다음과 같은 방식으로 구현됩니다.

그림 4. 구현 세부 정보

그림 4. 구현 세부 정보

이 시스템을 구현하려면 다음 질문에 답해야 합니다.

  • 이전 장에서 찾은 다중 코어 조합 중 시스템에서 사용할 것은 무엇입니까?
  • 시스템이 여러 EA 또는 지표로 구성되어 있기 때문에 이들 간의 데이터 교환 (양면)을 어떻게 더 잘 구성 할 수 있습니까 (즉, 클립 보드 데이터가 물리적으로 어떻습니까)?
  • 그들의 행동의 조정과 동기화를 어떻게 구성 할 수 있습니까?

이러한 각 질문에 대해 하나 이상의 답변이 있으며 모두 아래에 제공됩니다. 실제로 특정 상황에 따라 특정 옵션을 선택해야 합니다. 다음 장에서 이 작업을 수행합니다. 그동안 가능한 모든 답변을 고려해봅시다.

콤비네이션

조합 7은 터미널에서 추가 창을 열고 EA 또는 표시기에 배치 할 필요가 없기 때문에 일반적인 실제 사용에 가장 편리합니다 (다른 모든 조합은 이전 장에 나열 됨). 전체 시스템은 단일 창에 있으며 모든 표시기 (CM-1 및 CM-2)는 EA (MM)에 의해 자동으로 생성됩니다. 추가 창과 수동 조치가 없기 때문에 거래자에게 혼란을 줄 수 있으며, 따라서 이러한 혼란 오류와 관련이 있습니다.

일부 거래 전략에서는 다른 조합이 더 유용 할 수 있습니다. 예를 들어 이들 중 하나를 기반으로 '클라이언트-서버' 원칙에 따라 작동하는 전체 소프트웨어 시스템을 만들 수 있습니다. 동일한 CM이 여러 MM에 공통되는 경우. 이러한 공통 CM은 '컴퓨터'의 2 차 역할을 수행 할 수 있을뿐만 아니라 모든 전략 정보에 대해 일종의 통합을 저장하는 '서버'가 될 수 있으며 심지어는 공동 작업의 코디네이터가 될 수도 있습니다. 예를 들어 CM 서버는 원하는 전체 위험 수준을 유지하면서 일부 전략 및 통화 쌍 포트폴리오의 수단 분배를 중앙에서 제어 할 수 있습니다.

데이터 교환

다음 세 가지 방법 중 하나를 사용하여 MM과 CM간에 정보를 전송할 수 있습니다.

  1. 터미널의 전역 변수;
  2. 파일;
  3. 표시기 버퍼.

첫 번째 방법은 전송되는 숫자 변수가 적을 때 최적입니다. 텍스트 데이터를 전송할 필요가 있는 경우 전역 변수에는 double 유형만 있기 때문에 어떻게 든 숫자로 코딩되어야 합니다.

다른 방법은 두 번째 방법입니다. 파일에 무엇이든 쓸 수 있기 때문입니다. 그리고 이것은 많은 양의 데이터를 전송해야 하는 상황에서 편리한 (그리고 아마도 1 차보다 빠르다) 방법입니다.

세 번째 방법은 MM과 CM이 표시기인 경우에 적합합니다. double 유형의 데이터만 전송할 수 있지만 큰 숫자 배열을 전송하는 것이 더 편리합니다. 그러나 단점이 있습니다. 새로운 바를 형성하는 동안 버퍼의 요소 번호가 이동합니다. MM과 CM이 서로 다른 통화 쌍에 있기 때문에 새로운 바가 동시에 나타나지 않습니다. 우리는 이러한 변화를 고려해야 합니다.

동기화

단말기가 MM에 대한 견적을 수신하고 처리를 시작하면 즉시 제어권을 CM에 양도할 수 없습니다. (위의 다이어그램에 표시된대로) 태스크를 형성하고 (전역 변수, 파일 또는 표시기 버퍼에 배치) CM이 실행될 때까지 기다릴 수 있습니다. 모든 CM이 서로 다른 통화 쌍에 있으므로 대기하는 데 시간이 걸릴 수 있습니다. 이는 한 쌍이 견적을 수신하고 다른 쌍은 아직 수신하지 않았기 때문에 몇 초 또는 몇 분 내에만 제공 될 것입니다 (예: non-liquid 쌍에서 야간에 발생할 수 있음).

따라서 CM이 제어권을 얻으려면 따옴표에 의존하는 OnTick OnCalculate 이벤트를 사용하지 않아야 합니다. 대신 지정된 빈도 (예: 1 초)로 실행되는 OnTimer 이벤트 (MQL5의 혁신)를 사용해야 합니다. 이 경우 시스템의 지연이 심각하게 제한됩니다.

또한 OnTimer 대신 순환 기술을 사용할 수 있습니다. 즉, OnInit 또는 OnCalculate에 CM에 대한 무한 순환을 배치하는 것을 의미합니다. 각각의 반복은 타이머 틱과 유사합니다.

경고. 몇 가지 실험을 수행 한 결과 Combination 7을 사용할 때 타이머가 성공적으로 생성되었지만 표시기에서 OnTimer 이벤트가 작동하지 않는 것을 발견했습니다 (어떤 이유로든).

또한 OnInit 및 OnCalculate의 무한 루프에주의해야 합니다. CM 표시기가 MM-EA와 동일한 통화 쌍에 있으면 가격이 차트에서descev 움직이지 않고 EA가 작동을 멈춥니다 (OnTick 이벤트 생성을 중지합니다). 터미널 개발자는 이 동작의 이유를 설명했습니다.

개발자의 의견: 스크립트와 EA는 별도의 스레드에서 작동하는 반면 모든 표시기는 단일 심볼에서 동일한 스레드에서 작동합니다. 표시기와 동일한 스트림에서 이 기호에 대한 다른 모든 작업 (틱 처리, 기록 동기화 및 표시기 계산)도 연속적으로 실행됩니다. 따라서 표시기가 무한 동작을 수행하면 해당 기호에 대한 다른 모든 이벤트가 실행되지 않습니다.

프로그램 실행 노트
스크립트 자체 스레드에는 스크립트만큼 많은 실행 스레드가 있습니다. 순환 스크립트는 다른 프로그램의 작업을 방해할 수 없습니다
Expert Advisor 자체 스레드에는 EA만큼 많은 실행 스레드가 있습니다. 순환 스크립트는 다른 프로그램의 작업을 방해할 수 없습니다
지표 하나의 심볼에 있는 모든 표시기에 대한 하나의 실행 스레드. 실행 스레드 수만큼 표시기가 있는 기호 한 표시기의 무한 순환은 해당 기호에 있는 다른 모든 표시기의 작동을 중지합니다.


테스트 Expert Advisor 만들기

병렬화하기에 적합한 거래 전략과 이에 적합한 알고리즘을 선택해봅시다.

예를 들어, 이것은 간단한 전략이 될 수 있습니다: N개의 마지막 마디에서 시퀀스를 컴파일하고 내역상 이것과 가장 유사한 시퀀스를 찾는 것입니다. 가격이 내역상 어디로 이동했는지 알고 관련 거래를 시작합니다.

시퀀스의 길이가 비교적 작은 경우 이 전략은 MetaTrader 5에서 몇 초 내에 매우 빠르게 작동합니다. 그러나 긴 길이 (예 : 지난 24 시간 동안 M1의 모든 바 (1440 바))를 취하고 1년 전의 기록에서 다시 검색하면 (약 375,000 바), 상당한 시간이 소요됩니다. 그러나 이 검색은 쉽게 병렬화 될 수 있습니다. 사용 가능한 프로세서 코어 수에 대해 내역을 동일한 부분으로 나누고 각 코어를 지정하여 특정 포지션을 검색하는 것으로 충분합니다.

병렬 시스템의 매개 변수는 다음과 같습니다.

  • MM - 패턴화 된 거래 전략을 구현하는 EA입니다.
  • 병렬 계산은 CM 표시기에서 수행되며 Ea에서 자동으로 생성됩니다 (즉, 조합 7 사용).
  • CM 표시기의 컴퓨팅 코드는 OnInit의 무한주기 내에 배치됩니다.
  • MM-EA와 CM 표시기 간의 데이터 교환 - 터미널의 전역 변수를 통해 수행됩니다.

개발의 편의와 후속 사용을 위해, 우리는 설정에 따라, EA가 (지표에서 계산과 함께) 평소와 같이 (지표를 사용하지 않는) EA로 작동할 수 있는 방식으로 EA를 만들 것이다. 획득한 e-MultiThread Expert Advisor의 코드:

//+------------------------------------------------------------------+
//|                                                e-MultiThread.mq5 |
//+------------------------------------------------------------------+
input int Threads=1; // How many cores should be used
input int MagicNumber=0;

// Strategy parameters
input int PatternLen  = 1440;   // The length of the sequence to analyze (pattern)
input int PrognozeLen = 60;     // Forecast length (bars)
input int HistoryLen  = 375000; // History length to search

input double Lots=0.1;
//+------------------------------------------------------------------+
class IndData
  {
public:
   int               ts,te;
   datetime          start_time;
   double            prognoze,rating;
  };

IndData Calc[];
double CurPattern[];
double Prognoze;
int  HistPatternBarStart;
int  ExistsPrognozeLen;
uint TicksStart,TicksEnd;
//+------------------------------------------------------------------+
#include <ThreadCalc.mqh>
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
int OnInit()
  {

   double rates[];

//--- Make sure there is enough history
   int HistNeed=HistoryLen+Threads+PatternLen+PatternLen+PrognozeLen-1;
   if(TerminalInfoInteger(TERMINAL_MAXBARS)<HistNeed)
     {    
      Print("Change the terminal setting \"Max. bars in chart\" to the value, not lesser than ",
            HistNeed," and restart the terminal");
      return(1);      
     }
   while(Bars(_Symbol,_Period)<HistNeed)
     {      
      Print("Insufficient history length (",Bars(_Symbol,_Period),") in the terminal, upload...");
      CopyClose(_Symbol,_Period,0,HistNeed,rates);
     }
   Print("History length in the terminal: ",Bars(_Symbol,_Period));

//--- For a multi-core mode create computational indicators
   if(Threads>1)
     {
      GlobalVarPrefix="MultiThread_"+IntegerToString(MagicNumber)+"_";
      GlobalVariablesDeleteAll(GlobalVarPrefix);

      ArrayResize(Calc,Threads);

      // Length of history for each core
      int HistPartLen=MathCeil(HistoryLen/Threads);
      // Including the boundary sequences
      int HistPartLenPlus=HistPartLen+PatternLen+PrognozeLen-1;

      string s;
      int snum=0;
      // Create all computational indicators
      for(int t=0; t<Threads; t++)
        {      
         // For each indicator - its own currency pair,
         // it should not be the same as for the EA
         do
            s=SymbolName(snum++,false);
         while(s==_Symbol);

         int handle=iCustom(s,_Period,"i-Thread",
                            GlobalVarPrefix,t,_Symbol,PatternLen,
                            PatternLen+t*HistPartLen,HistPartLenPlus);

         if(handle==INVALID_HANDLE) return(1);
         Print("Indicator created, pair ",s,", handle ",handle);
        }
     }

   return(0);
  }
//+------------------------------------------------------------------+
void OnTick()
  {
   TicksStart=GetTickCount();

   // Fill in the sequence with the last bars
   while(CopyClose(_Symbol,_Period,0,PatternLen,CurPattern)<PatternLen) Sleep(1000);

   // If there is an open position, measure its "age"
   // and modify the forecast range for the remaining 
   // planned life time of the deal
   CalcPrognozeLen();

   // Find the most similar sequence in the history
   // and the forecast of the movement of its price on its basis
   FindHistoryPrognoze();

   // Perform the necessary trade actions
   Trade();

   TicksEnd=GetTickCount();
   // Debugging information in
   PrintReport();
  }
//+------------------------------------------------------------------+
void FindHistoryPrognoze()
  {
   Prognoze=0;
   double MaxRating;

   if(Threads>1)
     {
      //--------------------------------------
      // USE COMPUTATIONAL INDICATORS
      //--------------------------------------
      // Look through all of the computational indicators 
      for(int t=0; t<Threads; t++)
        {
         // Send the parameters of the computational task
         SetParam(t,"PrognozeLen",ExistsPrognozeLen);
         // "Begin computations" signal 
         SetParam(t,"Query");
        }

      for(int t=0; t<Threads; t++)
        {
         // Wait for results
         while(!ParamExists(t,"Answer"))
            Sleep(100);
         DelParam(t,"Answer");

         // Obtain results
         double progn        = GetParam(t, "Prognoze");
         double rating       = GetParam(t, "Rating");
         datetime time[];
         int start=GetParam(t,"PatternStart");
         CopyTime(_Symbol,_Period,start,1,time);
         Calc [t].prognoze   = progn;
         Calc [t].rating     = rating;
         Calc [t].start_time = time[0];
         Calc [t].ts         = GetParam(t, "TS");
         Calc [t].te         = GetParam(t, "TE");

         // Select the best result
         if((t==0) || (rating>MaxRating))
           {
            MaxRating = rating;
            Prognoze  = progn;
           }
        }
     }
   else
     {
      //----------------------------
      // INDICATORS ARE NOT USED
      //----------------------------
      // Calculate everything in the EA, into one stream
      FindPrognoze(_Symbol,CurPattern,0,HistoryLen,ExistsPrognozeLen,
                   Prognoze,MaxRating,HistPatternBarStart);
     }
  }
//+------------------------------------------------------------------+
void CalcPrognozeLen()
  {
   ExistsPrognozeLen=PrognozeLen;

   // If there is an opened position, determine 
   // how many bars have passed since its opening
   if(PositionSelect(_Symbol))
     {
      datetime postime=PositionGetInteger(POSITION_TIME);
      datetime curtime,time[];
      CopyTime(_Symbol,_Period,0,1,time);
      curtime=time[0];
      CopyTime(_Symbol,_Period,curtime,postime,time);
      int poslen=ArraySize(time);
      if(poslen<PrognozeLen)
         ExistsPrognozeLen=PrognozeLen-poslen;
      else
         ExistsPrognozeLen=0;
     }
  }
//+------------------------------------------------------------------+
void Trade()
  {

   // Close the open position, if it is against the forecast
   if(PositionSelect(_Symbol))
     {
      long type=PositionGetInteger(POSITION_TYPE);
      bool close=false;
      if((type == POSITION_TYPE_BUY)  && (Prognoze <= 0)) close = true;
      if((type == POSITION_TYPE_SELL) && (Prognoze >= 0)) close = true;
      if(close)
        {
         CTrade trade;
         trade.PositionClose(_Symbol);
        }
     }

   // If there are no position, open one according to the forecast
   if((Prognoze!=0) && (!PositionSelect(_Symbol)))
     {
      CTrade trade;
      if(Prognoze > 0) trade.Buy (Lots);
      if(Prognoze < 0) trade.Sell(Lots);
     }
  }
//+------------------------------------------------------------------+
void PrintReport()
  {
   Print("------------");
   Print("EA: started ",TicksStart,
         ", finished ",TicksEnd,
         ", duration (ms) ",TicksEnd-TicksStart);
   Print("EA: Forecast on ",ExistsPrognozeLen," bars");

   if(Threads>1)
     {
      for(int t=0; t<Threads; t++)
        {
         Print("Indicator ",t+1,
               ": Forecast ", Calc[t].prognoze,
               ", Rating ", Calc[t].rating,
               ", sequence from ",TimeToString(Calc[t].start_time)," in the past");
         Print("Indicator ",t+1,
               ": started ",  Calc[t].ts,
               ", finished ",   Calc[t].te,
               ", duration (ms) ",Calc[t].te-Calc[t].ts);
        }
     }
   else
     {
      Print("Indicators were not used");
      datetime time[];
      CopyTime(_Symbol,_Period,HistPatternBarStart,1,time);
      Print("EA: sequence from ",TimeToString(time[0])," in the past");
     }

   Print("EA: Forecast ",Prognoze);
   Print("------------");
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // Send the "finish" command to the indicators
   if(Threads>1)
      for(int t=0; t<Threads; t++)
         SetParam(t,"End");
  }
//+------------------------------------------------------------------+

Expert Advisor가 사용하는 계산 표시기 i-Thread의 코드:

//+------------------------------------------------------------------+
//|                                                     i-Thread.mq5 |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1

//--- input parameters
input string VarPrefix;  // Prefix for global variables (analog to MagicNumber)
input int    ThreadNum;  // Core number (so that indicators on different cores could
                        // differentiate their tasks from the tasks of the "neighboring" cores)
input string DataSymbol; // On what pair is the MM-EA working
input int    PatternLen; // Length of the sequence for analysis
input int    BarStart;   // From which bar in the history the search for a similar sequence began
input int    BarCount;   // How many bars of the history to perform a search on

//--- indicator buffers
double Buffer[];
//---
double CurPattern[];

//+------------------------------------------------------------------+
#include <ThreadCalc.mqh>
//+------------------------------------------------------------------+
void OnInit()
  {
   SetIndexBuffer(0,Buffer,INDICATOR_DATA);

   GlobalVarPrefix=VarPrefix;

   // Infinite loop - so that the indicator always "listening", 
   // for new commands from the EA
   while(true)
     {
      // Finish the work of the indicator, if there is a command to finish
      if(ParamExists(ThreadNum,"End"))
         break;

      // Wait for the signal to begin calculations
      if(!ParamExists(ThreadNum,"Query"))
        {
         Sleep(100);
         continue;
        }
      DelParam(ThreadNum,"Query");

      uint TicksStart=GetTickCount();

      // Obtain the parameters of the task
      int PrognozeLen=GetParam(ThreadNum,"PrognozeLen");

      // Fill the sequence from the last bars
      while(CopyClose(DataSymbol,_Period,0,PatternLen,CurPattern)
            <PatternLen) Sleep(1000);

      // Perform calculations
      int HistPatternBarStart;
      double Prognoze,Rating;
      FindPrognoze(DataSymbol,CurPattern,BarStart,BarCount,PrognozeLen,
                   Prognoze,Rating,HistPatternBarStart);

      // Send the results of calculations
      SetParam(ThreadNum,"Prognoze",Prognoze);
      SetParam(ThreadNum,"Rating",Rating);
      SetParam(ThreadNum,"PatternStart",HistPatternBarStart);
      SetParam(ThreadNum,"TS",TicksStart);
      SetParam(ThreadNum,"TE",GetTickCount());
      // Signal "everything is ready"
      SetParam(ThreadNum,"Answer");
     }
  }
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   // The handler of this event is required
   return(0);
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   SetParam(ThreadNum,"End");
  }
//+------------------------------------------------------------------+

Expert Advisor와 표시기는 공통 ThreadCalc.mqh 라이브러리를 사용합니다.

코드는 다음과 같습니다.

//+------------------------------------------------------------------+
//|                                                   ThreadCalc.mqh |
//+------------------------------------------------------------------+
string GlobalVarPrefix;
//+------------------------------------------------------------------+
// It finds the price sequence, most similar to the assigned one.
// in the specified range of the history
// Returns the estimation of similarity and the direction 
// of the further changes of prices in history.
//+------------------------------------------------------------------+
void FindPrognoze(
                  string DataSymbol,    // symbol
                  double  &CurPattern[],// current pattern
                  int BarStart,         // start bar
                  int BarCount,         // bars to search
                  int PrognozeLen,      // forecast length

                  // RESULT
                  double  &Prognoze,        // forecast (-,0,+)
                  double  &Rating,          // rating
                  int  &HistPatternBarStart // starting bar of the found sequence
                  ) 
  {

   int PatternLen=ArraySize(CurPattern);

   Prognoze=0;
   if(PrognozeLen<=0) return;

   double rates[];
   while(CopyClose(DataSymbol,_Period,BarStart,BarCount,rates)
         <BarCount) Sleep(1000);

   double rmin=-1;
   // Shifting by one bar, go through all of the price sequences in the history
   for(int bar=BarCount-PatternLen-PrognozeLen; bar>=0; bar--) 
     {
      // Update to eliminate the differences in the levels of price in the sequences
      double dr=CurPattern[0]-rates[bar];

      // Calculate the level of differences between the sequences - as a sum 
      // of squares of price deviations from the sample values
      double r=0;
      for(int i=0; i<PatternLen; i++)
         r+=MathPow(MathAbs(rates[bar+i]+dr-CurPattern[i]),2);

      // Find the sequence with the least difference level
      if((r<rmin) || (rmin<0)) 
        {
         rmin=r;
         HistPatternBarStart   = bar;
         int HistPatternBarEnd = bar + PatternLen-1;
         Prognoze=rates[HistPatternBarEnd+PrognozeLen]-rates[HistPatternBarEnd];
        }
     }
   // Convert the bar number into an indicator system of coordinates
   HistPatternBarStart=BarStart+BarCount-HistPatternBarStart-PatternLen;

   // Convert the difference into the rating of similarity
   Rating=-rmin;
  }
//====================================================================
// A set of functions for easing the work with global variables.
// As a parameter contain the number of computational threads 
// and the names of the variables, automatically converted into unique
// global names.
//====================================================================
//+------------------------------------------------------------------+
string GlobalParamName(int ThreadNum,string ParamName) 
  {
   return GlobalVarPrefix+IntegerToString(ThreadNum)+"_"+ParamName;
  }
//+------------------------------------------------------------------+
bool ParamExists(int ThreadNum,string ParamName) 
  {
   return GlobalVariableCheck(GlobalParamName(ThreadNum,ParamName));
  }
//+------------------------------------------------------------------+
void SetParam(int ThreadNum,string ParamName,double ParamValue=0) 
  {
   string VarName=GlobalParamName(ThreadNum,ParamName);
   GlobalVariableTemp(VarName);
   GlobalVariableSet(VarName,ParamValue);
  }
//+------------------------------------------------------------------+
double GetParam(int ThreadNum,string ParamName) 
  {
   return GlobalVariableGet(GlobalParamName(ThreadNum,ParamName));
  }
//+------------------------------------------------------------------+
double DelParam(int ThreadNum,string ParamName) 
  {
   return GlobalVariableDel(GlobalParamName(ThreadNum,ParamName));
  }
//+------------------------------------------------------------------+

작업에 둘 이상의 프로세서 코어를 사용할 수 있는 거래 시스템이 준비되었습니다!

사용할 때 이 예제에서는 무한 루프가 있는 CM 표시기를 사용했음을 기억해야 합니다.

터미널에서 이 시스템과 함께 다른 프로그램을 실행할 계획이라면 CM 표시기가 사용하지 않는 통화 쌍에서 해당 프로그램을 사용하고 있는지 확인해야 합니다. 이러한 충돌을 피하는 좋은 방법은 MM-EA의 입력 매개 변수에서 CM 표시기에 대한 통화 쌍을 직접 지정할 수 있도록 시스템을 수정하는 것입니다.


EA 작업 속도 측정

일반 모드

EURUSD M1 차트를 열고 이전 장에서 만든 Expert Advisor를 시작합니다. 설정에서 패턴 길이를 24시간 (1440 분 바)으로 지정하고 기록 검색 깊이를 1 년 (375,000 바)으로 지정합니다.

그림 4. Expert Advisor 입력 매개 변수

그림 4. Expert Advisor 입력 매개 변수

매개 변수 "스레드"가 1로 설정됩니다. 이것은 EA의 모든 계산이 하나의 스레드 (단일 코어에서)로 생성된다는 것을 의미합니다. 한편, 계산 지표를 사용하지 않고 모든 것을 자체적으로 계산합니다. 기본적으로 일반 EA의 작업 원칙에 따라.

실행 기록:

그림 6. Expert Advisor 로그

그림 6. Expert Advisor 로그 (1 thread)


병렬 모드

이제 EA와 그에 의해 열린 포지션을 삭제하겠습니다. EA를 다시 추가하지만 이번에는 "Threads" 매개 변수를 2로 지정합니다.

이제 EA는 2 개의 프로세서 코어를 차지하는 2 개의 계산 지표를 생성하고 작업에 사용해야 합니다. 실행 기록:

그림 7. Expert Advisor 로그 (2개 스레드)

그림 7. Expert Advisor Log (2 개 스레드)


속도 비교

이 두 로그를 모두 분석한 결과 EA의 대략적인 실행 시간은 다음과 같습니다.

  • 일반 모드 - 52 초;
  • 2 코어 모드 - 27초.

따라서 2 코어 CPU에서 병렬화를 수행하여 EA의 속도를 1.9 배 높일 수 있었습니다. 코어 수가 다량 인 프로세서를 사용하면 코어 수에 비례하여 실행 속도가 더 빨라진다 고 가정 할 수 있습니다.

작업의 정확성 제어

실행 시간 외에도 로그는 모든 측정이 올바르게 수행되었는지 확인할 수 있는 추가 정보를 제공합니다. EA: 작업 시작 ... 작업 종료 ... ""표시기 ... 작업 시작 ... 작업 종료 ... " 줄은 지표가 EA가 그 명령을 내리기 직전에 계산을 시작했습니다.

병렬 모드에서 EA를 시작하는 동안 거래 전략을 위반하지 않았는지 확인하겠습니다. 로그에 따르면 병렬 모드에서 EA의 시작은 정규 모드에서 시작된 직후에 이루어졌다는 것이 분명합니다. 두 경우 모두 시장 상황이 비슷하다는 것을 의미합니다. 두 경우 모두 패턴의 내역에서 발견된 연대도 매우 유사하다는 것을 로그에서 확인할 수 있다. 따라서 모든 것이 좋습니다. 전략 알고리즘은 두 경우 모두 똑같이 잘 작동합니다.

다음은 로그 상황에 설명된 패턴입니다. EA를 일반 모드로 실행했을 때의 현재 시장 상황 (길이 - 1440 분 바)은 다음과 같습니다.

그림 8. 현재 시장 상황

그림 8. 현재 시장 상황

EA는 내역에서 다음과 유사한 패턴을 발견했습니다.

그림 9. 유사한 시장 상황

그림 9. 유사한 시장 상황

병렬 모드에서 EA를 실행할 때 "Indicator 1"에서 동일한 패턴을 찾았습니다. 로그에서 다음과 같이 "Indicator 2"는 내역의 다른 반년 동안 패턴을 찾고 있었기 때문에 다른 유사한 패턴을 발견했습니다.

그림 10. 유사한 시장 상황

그림 10. 유사한 시장 상황

그리고 이것은 MetaTrader 5의 전역 변수가 병렬 모드에서 EA가 작업하는 동안 어떻게 생겼는지입니다.

그림 11. 전역 변수

그림 11. 전역 변수

글로벌 변수를 통한 EA와 표시기 간의 데이터 교환이 성공적으로 구현되었습니다.


결론

이 연구에서 우리는 MetaTrader 5의 표준 수단으로 수완이 풍부한 알고리즘을 병렬화 할 수 있음을 발견했습니다. 그리고 이 문제에 대한 발견 된 해결책은 실제 거래 전략에서 편리하게 사용하기에 적합합니다.

멀티 코어 시스템에서 이 프로그램은 실제로 비례 적으로 더 빠른 방식으로 작동합니다. 프로세서의 코어 수는 매년 증가하고 있으며 MetaTrader를 사용하는 트레이더가 이러한 하드웨어 리소스를 효과적으로 사용할 수 있는 기회를 갖는 것이 좋습니다. 실시간으로 시장을 분석 할 수 있는 보다 수완이 풍부한 거래 전략을 안전하게 만들 수 있습니다.


MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/197

파일 첨부됨 |
i-flood.mq5 (0.7 KB)
e-flood.mq5 (0.58 KB)
i-thread.mq5 (3.1 KB)
threadcalc.mqh (3.93 KB)
채널 그리기 - 내부 및 외부 보기 채널 그리기 - 내부 및 외부 보기
채널이 시장 분석과 이동 평균 이후 거래 결정을 위한 가장 인기있는 도구라고 말하면 과장이 아닐 것 같습니다. 채널과 그 구성 요소를 사용하는 대량의 거래 전략에 깊이 들어 가지 않고 수학적 기반과 지표의 실제 구현에 대해 논의 할 것입니다.
CChartObject 클래스 기반의 새로운 GUI 위젯 설계 및 구현 CChartObject 클래스 기반의 새로운 GUI 위젯 설계 및 구현
GUI 인터페이스가 있는 반자동 Expert Advisor에 대한 이전 글을 작성한 후 더 복잡한 지표와 Expert Advisors를 위한 몇 가지 새로운 기능으로 인터페이스를 향상시키는 것이 바람직하다는 것이 밝혀졌습니다. MQL5 표준 라이브러리 클래스에 익숙해 진 후 새로운 위젯을 구현했습니다. 이 글에서는 표시기 및 Expert Advisor에서 사용할 수 있는 새로운 MQL5 GUI 위젯을 설계하고 구현하는 프로세스를 설명합니다. 글에 제시된 위젯은 CChartObjectSpinner, CChartObjectProgressBar 및 CChartObjectEditTable입니다.
Trading Model 기반 Multi-Expert Advisor 양성 Trading Model 기반 Multi-Expert Advisor 양성
MQL5에서 객체 지향 접근 방식을 사용하면 다중 통화/다중 시스템/다중 타임 프레임 Expert Advisors 생성이 크게 간소화됩니다. 하나의 EA가 수십 가지 거래 전략, 사용 가능한 모든 상품 및 모든 가능한 타임 프레임에서 거래한다고 상상해보십시오! 또한 EA는 테스터에서 쉽게 테스트되며 구성에 포함된 모든 전략에 대해 하나 또는 여러 개의 자금 관리 시스템이 있습니다.
사전 정의된 위험 및 R/R 비율을 기반으로 인터랙티브 반자동 드래그 앤 드롭 Expert Advisor 구축 사전 정의된 위험 및 R/R 비율을 기반으로 인터랙티브 반자동 드래그 앤 드롭 Expert Advisor 구축
일부 거래자는 모든 거래를 자동으로 실행하고 일부는 여러 지표의 출력을 기반으로 자동 및 수동 거래를 혼합합니다. 후자 그룹의 일원이기 때문에 동적으로 위험을 평가하고 차트에서 직접 가격 수준을 보상 할 수 있는 대화형 도구가 필요했습니다. 이 글에서는 사전 정의된 주식 위험 및 R/R 비율을 사용하여 대화형 반자동 Expert Advisor를 구현하는 방법을 설명합니다. Expert Advisor 위험, R/R 및 랏 크기 매개 변수는 EA 패널에서 런타임 중에 변경할 수 있습니다.