English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
매매봇 프로토타입

매매봇 프로토타입

MetaTrader 5 | 5 7월 2021, 10:27
95 0
---
---

시작하며

모든 거래 시스템의 수명은 포지션 오픈 및 클로즈로 좁혀집니다. 이것은 확실하지요. 다만 알고리즘 실현에 있어서는 프로그래머들은 각각 다른 생각을 가집니다. 모든 사람이 같은 문제를 자신만의 방법으로 해결할 수 있을 것이지만, 결국은 같은 결과에 도달할 것입니다.

시대의 흐름과 함께 익스퍼트 로직과 구조 형성에는 다양한 시도가 이루어져 왔습니다. 현재로서는 모든 코드에 사용되는 명확한 패턴 템플릿을 확립했다고 주장할 수 있습니다.

이 접근 방식은 100% 보편적이지는 않지만 익스퍼트의 논리 설계 방법을 바꿀 수 있습니다. 그리고 익스퍼트를 사용할 계획이라면 주문을 어떻게 다룰까가 중요한게 아닙니다. 요점은 - 매매 모델을 만드는 원칙인 것입니다.


1. 매매 시스템을 디자인하는 원칙과 이벤트 소스들의 타입들

대다수가 사용하는 알고리즘 설계에 대한 기본 접근 방식은 개방에서 폐쇄까지 한 위치를 추적하는 것입니다. 이것은 선형적 접근 방식입니다 코드를 변경하려는 경우 - 많은 조건이 새로 나타나고 코드가 새로운 분석 영역을 늘려가기 때문에 더욱 복잡해지기 십상입니다.

매매 봇을 모델링하는 가장 좋은 해법은"상태를 서빙하는 것"입니다. 그리고 근본적인 원칙은 – 익스퍼트의 해당 조건, 그리고 포지션과 주문들이 발생했는지 분석하는 것이 아니라, 우리가 그것들을 가지고 무엇을 해야하는지 분석하는 것이라는 것입니다. 이 기본 원칙은 매매 관리를 근본적으로 변화시키고 코드 개발을 단순화하는 것입니다.

더 자세히 들여다 보겠습니다.

1.1. "서빙 상태"의 원칙

이미 언급했듯이, 익스퍼트는 어떻게 현재 상태가 되었는지 알 필요가 없습니다. 환경(매개변수 값, 저장된 주문 속성 등)에 따라 지금 어떻게 해야 하는지 알고 있어야 합니다.

이 원칙은 익스퍼트가 루프에서 루프 사이에만 존재한다는 점에 직접적으로 관련이 있으며 (정확하게는 - 틱에서 틱까지), 이전 틱에서의 주문들에 어떤 일이 일어났는지에 대해서는 걱정하지 않아도 됩니다.. 따라서 주문을 관리할 때엔 이벤트-기반 어프로치를 해야합니다. 즉, 현재 틱에서 익스퍼트는 다음 틱에 대한 결정의 시작점인 상태를 저장합니다.

예를 들어, 익스퍼트의 보류 중인 주문을 모두 제거하고 인디케이터를 계속 분석하면서 새 주문을 해야합니다. 우리가 여태까지 본 대부분의 코드 예시는 "while (true) {try to remove}" 루프나 약간 더 순한 "while (k < 1000) {try to remove; k++;}" 루프를 써왔습니다. 오류 분석 없이 remove 명령을 한 번 호출하는 변형은 건너뛰겠습니다.

이 방법은 선형적이며, 익스퍼트를 무기한 "행동"시킵니다.

따라서 익스퍼트를 루프하는 것 보다 주문을 취소하기 위해 주문을 저장하는 것이 더 올바르므로, 보류 중인 주문을 삭제하려고 시도하는 동안 모든 새 틱에서 이 주문을 확인하는 편이 맞을 것입니다. 이 경우 익스퍼트는 상태 패러미터를 읽는 동안 주문을 삭제해야 한다는 것을 알고 있습니다. 그리고 삭제하려고 시도할 것입니다. 만약 매매 오류가 발생한다면, 익스퍼트는 그냥 추가 분석을 차단하고 다음 루프 이전에 일할 것입니다.

1.2. 디자인의 원칙 그 두번째 - 는 가능한 최대한 간략화를 포지션 방향 (매수/매도), 화폐, 그리고 차트로 추구하는 것입니다. 모든 익스퍼트 기능은 방향이나 심볼이 실제로 피할 수 없는 드문 경우에 분석되는 방식으로 구현되어야 합니다(예: 오픈 포지션의 매매가가 오를것이라고 호의적으로 예상하면서도, 상세를 피하는 다른 옵션들이 존재할 때 처럼). 그러한 "로우 레벨" 디자인은 언제나 피해야 합니다. 그러한 디자인은 코드를 줄이고 함수를 쓰는 것을 최소 2번 감소시킵니다. 또한 "매매-독립적으로 만듭니다. 

이 원칙의 구현은 주문 타입, 심볼 패러미터 및 종속 계산 패러미터의 명시적 분석을 매크로 함수로 대체하는 것입니다. 다음 문서에서 우리는 이 구현에 대해 심도있게 알아보겠습니다.
1.3. 세번째 원칙 알고리즘을 논리적 어휘소 (독립적 모듈) 로 쪼개는 것입니다

실제로, 우리는 익스퍼트 운영을 각각의 함수로 분리하는 것이 최선의 접근법이라고 말할 수 있습니다. 저는 당신이 익스퍼트의 전체 알고리즘을 하나의 함수에 작성하기는 어렵고, 그 이후의 분석과 편집을 복잡하게 만든다는 데 동의하실 거라 생각합니다. 따라서 당신의 환경에 거의 완전한 제어를 주는 MQL5에서는 우린 그렇게 하지 않을 것입니다.

따라서 환경 패러미터와 이벤트의 완전한 분석을 통해 논리적 어휘소(예: 주문의 오픈, 트레일링, 클로즈)를 각각 분리해서 구현해야 합니다. 이 어프로치를 통해 익스퍼트의 디자인은 유연하게 됩니다. 기존 모듈을 건드리지 않고도 새로운 독립 모듈을 쉽게 추가할 수 있고, 기본 코드를 변경하지 않고도 기존 모듈을 비활성화할 수 있습니다.

이 세 가지 원칙을 통해 모든 익스퍼트를 위한 단일 프로토타입을 만들 수 있으며, 이를 통해 주어진 작업을 쉽게 수정 및 조정할 수 있습니다.

익스퍼트 시스템을 위한 이벤트 소스는:


1. 인디케이터. 예 - 인디케이터 선 값, 교차점, 조합, 등의 분석이 있습니다. 또한, 인디케이터는: 현재 시각, 인터넷에서 얻어진 데이터, 등 또한 될 수 있습니다. 대부분의 경우에서, 인디케이터 이벤트들은 주문의 오픈과 클로즈를 신호하기 위해 쓰입니다. 조정에는 덜 쓰입니다 (주로 인디케이터의 대기 주문이나 추적 손절).  

예를 들어, 빠르고 느린 이동평균의 교차점을 포지션의 오픈과 디렉션 교차점을 통해 분석하는, 인디케이터의 실용적인 구현은 익스퍼트라고 불릴 수 있습니다.  

2. 기존 주문들, 포지션들과 상태. 예를 들어, 당기손실이나 이익규모, 포지션이나 미결주문의 유무, 마감직위의 이익 등을 들 수 있습니다. 이러한 이벤트의 실제 구현은 인디케이터 이벤트다 더 많은 관계 옵션이 있기 때문에 훨씬 더 광범위하고 다양합니다.

매매 이벤트만을 기반으로 한 가장 간단한 익스퍼트 예는 이미 존재하는 포지션을 평균화하기 위해 다시 채우고 원하는 수익으로 산출하는 것입니다. 즉, 사용 가능한 위치에 손실이 존재하면 새로운 평균 주문을 작성할 수 있습니다.

혹은, 예를들어, 추적 손절. 이 함수는 이전 손절에서 지정된 포인트 수만큼 가격이 이익으로 이동할 때 이벤트를 확인합니다. 그 결과로, 익스퍼트는 가격에서 손절을 뽑아냅니다.

3. 외부 이벤트. 비록 그러한 사건은 대개 순수 익스퍼트 시스템에서 일어나지 않지만, 일반적으로 결정을 내리기 위해 고려되어야 합니다. 여기에는 주문 조정, 포지션 조정, 매매 오류 처리, 차트 이벤트 처리(개체 이동/생성/삭제, 버튼 누름 등)가 포함됩니다. 일반적으로 이들은 기록으로는 검증할 수 없고 익스퍼트가 일할 때만 발생하는 이벤트들입니다.

그러한 전문가들의 두드러진 예가 시각적 매매 통제가 가능한 매매-정보 시스템입니다.

모든 종류의 익스퍼트가 이들 세 개 이벤트 소스의 조합에 그 근간을 두고 있습니다.

2. CExpertAdvisor 기본 클래스 - 익스퍼트 생성자

매매 익스퍼트의 업무는 뭐가 될까요? MQL-프로그램 간 상호작용의 일반적 계획은 밑의 다이어그램에 표시되어 있습니다.

1번 그림. MQL-프로그램 요소 간 상호작용의 일반적인 계획

1번 그림. MQL-프로그램 요소 간 상호작용의 일반적인 계획

위 계획에서 볼 수 있듯이, 먼저 입구에서 나와 루프로 갑니다 (이는 틱이나 타이머 신호일 수 있습니다). 첫 블록에서의 이 단계에서 틱은 먼저 처리 없이 걸러질 수 있습니다. 이런 것은 익스퍼트가 모든 틱에 필요한 것이 아니라 새로운 바에서만 필요하다거나, 익스퍼트를 쓸 수 없는 경우에 이루어집니다.

그런 다음 프로그램은 주문과 포지션을 처리하는 모듈인 두 번째 블록으로 들어가고, 그 후에만 이벤트 처리 블록이 모듈에서 호출됩니다. 각 모듈은 해당 이벤트만 조회할 수 있습니다.  

해당 시퀀스는 직접 논리 계획이라고 불릴 수 있는데, 이는 이 시퀀스가 먼저 익스퍼트가 무엇을 할지 (어떤 이벤트 처리 모듈이 사용되는지) 판단하며, 그 뒤에야 어떻게어째서 그렇게 할 것 (이벤트 신호 받기)인지 구현하기 때문입니다.

직접적인 논리는 세계에 대한 우리의 인식과 보편적인 논리와 일치합니다. 결국, 인간은 먼저 구체적인 개념을 생각하고, 그 개념을 요약한 다음 그들의 상호관계를 분류하고 식별하잖습니까.

익스퍼트의 디자인 또한 이런 관념에서 벗어나지 않습니다. 먼저 익스퍼트가 해야 할 일(포지션 열기 및 닫기, 보호 중지 당기기)을 선언하고, 그 후에야 어떤 이벤트와 방법을 지정합니다 그러나 어떤 경우에도 그 반대는 아닙니다:. 신호를 수신하여 어디에서 어떻게 처리할 것인지 생각해 보십시오. 이는 역논리이며, 결과적으로 조건 분기가 많은 번거로운 코드를 얻을 수 있기 때문에 사용하지 않는 것이 좋습니다.

직접 논리와 역논리의 예시가 여기에 있습니다. RSI 신호로 오픈/클로즈를 받아옵니다.

  • 역논리에서는 익스퍼트가 인디케이터 값을 받으면서 시작되고, 그러고 나서야 신호의 방향을 확인하고 포지션으로 무엇을 해야하는지 확인합니다: 매수를 오픈하고 매도를 닫거나, 반대로 매도를 열고 매수를 닫거나. 이러한 방식에서, 시작점은 신호를 얻고 분석하기 위한 것입니다.
  • 직접 논리에서는 모든 것이 반대입니다. 익스퍼트는 오픈 및 클로즈 포지션 두 개의 모듈을 가지고 있으며, 단지 이 모듈을 실행하기 위한 조건을 점검하기만 하면 됩니다. 즉, 오픈 모듈에 진입한 후 전문가는 인디케이터 값을 수신하여 오픈 신호가 아닌지 확인합니다. 그런 다음, 주문 청산 모듈에 들어간 후 익스퍼트는 청산 위치를 알리는 신호인지 확인합니다. 즉, 진입점이 없습니다. 시스템 상태 분석 모듈(설계의 제1원칙)이 독립적으로 작동됩니다.

이제, 만약 여러분이 익스퍼트를 복잡하게 만들고 싶다면, 첫 번째 선택지보다 두 번째 선택지를 사용하는 것이 훨씬 더 쉬울 것입니다. 이벤트 처리의 새 모듈을 생성하기에 충분합니다.

그리고 첫 번째 선택지에서는 신호 처리의 구조를 수정하거나 별도의 기능으로 붙여 넣어야 합니다.

추천 사항: 매매 시스템을 설명할 때엔 이와같은 단어로 시작하지 마십시오 "1. 신호를 받고 ... 주문을 오픈함", 그 대신 바로 이 섹션들로 나누십시오 "a) 주문 오픈 조건, b) 주문 점검 조건, 등." 그리고 각각에서 필요한 신호들 분석.

이 접근법을 더 잘 이해하기 위하여, 여기 네 개의 각기 다른 엑스퍼트의 다른 계획이 있습니다.

2번 그림. 익스퍼트 구현 예제

2번 그림. 익스퍼트 구현 예제

a). 익스퍼트, 몇몇 인디케이터의 신호에만 기반한 것. 이는 신호가 변할 때 포지션을 열거나 청산할 수 있습니다. 예시 - 이동평균 익스퍼트. b). 시각적 매매 조작 익스퍼트. c). 인디케이터 기반 익스퍼트에 추종 손절과 운영 시간을 붙인 것. 예시- 이동평균 인디케이터로 얻은 트렌드와 에 오픈 포지션 새소식 확인.
d). 평균 포지션이 딸리고 인디케이터는 없는 익스퍼트. 새 바를 열 때에만 포지션 패러미터를 검증합니다. 예시 - 평준화 익스퍼트

계획에서 볼 수 있듯이, 어떠한 매매 시스템도 직접 논리를 사용하여 기술하기가 매우 쉽습니다.


3. Expert Class 구현

위에서 언급한 모든 규칙과 요구 사항을 사용하여 클래스를 만듭니다. 이 클래스는 향후 모든 익스퍼트의 기초가 될 것입니다.

CExpertAdvisor 클래스 안에 들어가야하는 최소한의 기능은 다음과 같습니다:

1. 초기화:

  • 인디케이터 등록
  • 패러미터 초기값 세팅
  • 필요한 심볼과 타임프레임에 맞춰 조정

2. 신호 수신용 함수

  • 허용된 작동 시간 (매매 인터벌)
  • 포지션 혹은 주문 오픈/클로즈을 판단할 시그널 판단
  • 필터 판단 (트렌드, 시간, 등)  
  • 스타트/스탑 타이머

3. 서비스 함수

  • 오픈 가 계산, 손절과 이익 실현 레벨, 주문량
  • 매매 요청 송신 (오픈, 청산, 수정)

4. 매매 모듈

  • 시그널과 필터 처리
  • 포지션과 주문 관리
  • 익스퍼트 함수 사용: OnTrade(), OnTimer(), OnTester(), OnChartEvent().

5. 처리

  • 결과 메세지, 리포트
  • 차트 정리, 인디케이터 언로드

해당클래스의 모든 함수들은 세 종류로 나뉩니다. 내포 함수들의 보편적인 계획과 그 설명들은 아래에 적혀있습니다.

3번 그림. 익스퍼트의 내포 함수 계획

3번 그림. 익스퍼트의 내포 함수 계획

1. 매크로 함수 

이 작은 함수의 그룹은 주문 타입, 심볼 패러미터 및 매매가 값(오픈 및 청산)을 설정하기 위한 기초입니다. 이러한 매크로 함수는 설계의 두 번째 원리인 추상화를 제공합니다. 그들은 익스퍼트가 사용하는 심볼에서 작용합니다.

타입을 변환하는 매크로 기능은 시장의 방향(매수 또는 매도)에 따라 작동합니다. 따라서 나만의 상수를 만들지 않기 위해서는 ORDER_TYPE_BUYORDER_TYPE_SELL 같은 기존 상수를 쓰는 것이 좋습니다. 매크로 사용의 예제 몇가지와 그 결과들입니다.

   //--- Type conversion macro
   long       BaseType(long dir);        // returns the base type of order for specified direction
   long       ReversType(long dir);      // returns the reverse type of order for specified direction
   long       StopType(long dir);        // returns the stop-order type for specified direction
   long       LimitType(long dir);       // returns the limit-order type for specified direction

   //--- Normalization macro
   double     BasePrice(long dir);       // returns Bid/Ask price for specified direction
   double     ReversPrice(long dir);     // returns Bid/Ask price for reverse direction

   long dir,newdir;
   dir=ORDER_TYPE_BUY;
   newdir=ReversType(dir);               // newdir=ORDER_TYPE_SELL
   newdir=StopType(dir);                 // newdir=ORDER_TYPE_BUY_STOP
   newdir=LimitType(dir);                // newdir=ORDER_TYPE_BUY_LIMIT
   newdir=BaseType(newdir);              // newdir=ORDER_TYPE_BUY

   double price;
   price=BasePrice(dir);                 // price=Ask
   price=ReversPrice(dir);               // price=Bid

익스퍼트를 개발할 때엔 매크로가 처리 방향을 명시하지않아도 되게 해주며 더욱 간소화된 코드를 쓸 수 있게 도와줍니다.

2. 서비스 함수

이들 함수는 주문과 포지션을 다루도록 설계되어 있습니다. 로우 레벨도 다루는 매크로 함수 같은거죠. 편의를 위해 서비스 함수는 두가지 분류로 나뉩니다: 정보 함수와 운영 함수. 양쪽 모두 어떠한 이벤트건 분석하는 일 없이 단 하나의 행동만을 취합니다. 이 함수들은 시니어 익스퍼트 핸들러로 부터 명령을 받습니다.

정보 함수들의 예시: 현재 대기 주문에서 최대 오픈 매매가 찾기; 포지션이 어떻게 청산되었는지, 이득인지 손해인지 알아보기; 익스퍼트의 주문 안에서 티켓의 숫자와 목록 얻기, 등.

운영 함수들의 예시: 특정 주문 청산; 특정 포지션에서 손절 수정하기, 등.

이 그룹이 가장 큽니다. 이것이 바로 익스퍼트의 일상적인 작업의 기초가 되는 기능입니다. 이들 함수의 다양한 예시를 포럼 https://www.mql5.com/ru/forum/107476에서 찾아볼 수 있습니다. 하지만 이것에 더해 CTrade 클래스 처럼 주문과 포지션을 다루도록 설계된 함수가 MQL5 표준 라이브러리에 있습니다.

그러나 새로운 구현을 생성하거나 기존 구현을 약간 수정해야 하는 작업이 있습니다.

3. 이벤트 처리 모듈

이들 함수 그룹은 처음 두 그룹에 대한 상위 수준의 상부구조입니다. 위에서 언급한 바와 같이, 이러한 블록은 익스퍼트가 구성되는 즉시 사용할 수 있는 블록입니다. 일반적으로 이들 함수는 MQL 프로그램의 이벤트 처리 함수들을 포함합니다: OnStart(), OnTick(), OnTimer(), OnTrade(), OnChartEvent(). 이 그룹은 많지 않으며, 이러한 모듈의 내용을 작업별로 조정할 수 있습니다. 하지만 딱히 변하는건 없습니다.  

모듈에서 동일한 모듈에 대한 모든 것이 매수와 매도를 위해 호출될 수 있도록 추상적(두 번째 설계 원리)이어야 합니다. 이 또한 당연하게도 매크로의 힘으로 이루어집니다.

고로, 구현시켜봅시다

1. 초기화, 처리

class CExpertAdvisor
  {
protected:
   bool              m_bInit;       // flag of correct initialization
   ulong             m_magic;       // magic number of expert
   string              m_smb;       // symbol, on which expert works
   ENUM_TIMEFRAMES      m_tf;       // working timeframe
   CSymbolInfo      m_smbinf;       // symbol parameters
   int               m_timer;       // time for timer

public:
   double              m_pnt;       // consider 5/3 digit quotes for stops
   CTrade            m_trade;       // object to execute trade orders
   string              m_inf;       // comment string for information about expert's work

이것은 익스퍼트 기능이 작동하기 위해 필요한 최소 패러미터 세트입니다.

m_smb 와 m_tf 패러미터들은 어떤 화폐와 어떤 기간에 작업해야하는지 손쉽게 알려주기 위해 익스퍼트 속성 안에 위치하고 있습니다. 예를 들어, 만약 m_smb = "USDJPY"로 세팅한다면, 익스퍼트는 이전에 작업한 것엔 신경을 끄고 해당 심볼로 작업할 것입니다. 만약 tf = PERIOD_H1 으로 세팅한다면, 모든 인디케이터 신호와 분석이 H1 차트에서 이루어질 것입니다. 

클래스 메소드도 있습니다. 첫 번째 세 가지 방법은 익스퍼트의 초기화 및 정리입니다.

public:
   //--- Initialization
   void              CExpertAdvisor();                               // constructor
   void             ~CExpertAdvisor();                               // destructor
   virtual bool      Init(long magic,string smb,ENUM_TIMEFRAMES tf); // initialization

기본 클래스의 생성자 및 소멸자는 아무 작업도 수행하지 않습니다.

Init() 메소드는 심볼, 타임프레임 및 매직 넘버를 기준으로 익스퍼트 패러미터들을 초기화합니다.

//------------------------------------------------------------------ CExpertAdvisor
void CExpertAdvisor::CExpertAdvisor()
  {
   m_bInit=false;
  }
//------------------------------------------------------------------ ~CExpertAdvisor
void CExpertAdvisor::~CExpertAdvisor()
  {
  }
//------------------------------------------------------------------ Init
bool CExpertAdvisor::Init(long magic,string smb,ENUM_TIMEFRAMES tf)
  {
   m_magic=magic; m_smb=smb; m_tf=tf;         // set initializing parameters
   m_smbinf.Name(m_smb);                      // initialize symbol
   m_pnt=m_smbinf.Point();                    // calculate multiplier for 5/3 digit quote
   if(m_smbinf.Digits()==5 || m_smbinf.Digits()==3) m_pnt*=10;  
   m_trade.SetExpertMagicNumber(m_magic);     // set magic number for expert

   m_bInit=true; return(true);                // trade allowed
  }

2. 신호 수신용 함수

이들 함수는 시장과 인디케이터를 분석합니다.

   bool              CheckNewBar();                          // check for new bar
   bool              CheckTime(datetime start,datetime end); // check allowed trade time
   virtual long      CheckSignal(bool bEntry);               // check signal
   virtual bool      CheckFilter(long dir);                  // check filter for direction

처음 두 함수는 상당히 구체적인 구현이 있으며 이 클래스의 아이 클래스에서 사용될 수 있습니다.

//------------------------------------------------------------------ CheckNewBar
bool CExpertAdvisor::CheckNewBar()          // function of checking new bar
  {
   MqlRates rt[2];
   if(CopyRates(m_smb,m_tf,0,2,rt)!=2)      // copy bar
     { Print("CopyRates of ",m_smb," failed, no history"); return(false); }
   if(rt[1].tick_volume>1) return(false);   // check volume 
   return(true);
  }
//---------------------------------------------------------------   CheckTime
bool CExpertAdvisor::CheckTime(datetime start,datetime end)
  {
   datetime dt=TimeCurrent();                          // current time
   if(start<end) if(dt>=start && dt<end) return(true); // check if we are in the range
   if(start>=end) if(dt>=start|| dt<end) return(true);
   return(false);
  }

후자는 항상 사용 중인 인디케이터에 따라 달라집니다. 모든 경우에 이러한 기능을 설정하는 것은 그냥 불가능합니다.

중요한 것은 Check Signal() 및 CheckFilter() 신호 함수는 모든 인디케이터와 인디케이터 조합을 분석할 수 있다는 것입니다! 즉, 이러한 신호가 이후에 포함될 거래 모듈은 소스로부터 독립적입니다.

이렇게 하면 한 번 작성한 익스퍼트를 유사한 원리로 작업하는 다른 익스퍼트의 템플릿으로 사용할 수 있습니다. 분석된 인디케이터를 변경하거나 새 필터링 조건을 추가하면 됩니다.

3. 서비스 함수

상술했듯이 이 그룹의 함수가 가장 많습니다. 본 문서에서 다룬 실용적인 작업의 경우 그러한 기능을 4개 정도 구현하기에 충분할 것입니다:

   double         CountLotByRisk(int dist,double risk,double lot); // calculate lot by size of risk
   ulong          DealOpen(long dir,double lot,int SL,int TP);     // execute deal with specified parameter
   ulong          GetDealByOrder(ulong order);                     // get deal ticket by order ticket
   double         CountProfitByDeal(ulong ticket);                 // calculate profit by deal ticket
//------------------------------------------------------------------ CountLotByRisk
double CExpertAdvisor::CountLotByRisk(int dist,double risk,double lot) // calculate lot by size of risk
  {
   if(dist==0 || risk==0) return(lot);
   m_smbinf.Refresh();
   return(NormalLot(AccountInfoDouble(ACCOUNT_BALANCE)*risk/(dist*10*m_smbinf.TickValue())));
  }
//------------------------------------------------------------------ DealOpen
ulong CExpertAdvisor::DealOpen(long dir,double lot,int SL,int TP)
  {
   double op,sl,tp,apr,StopLvl;
   // determine price parameters
   m_smbinf.RefreshRates(); m_smbinf.Refresh();
   StopLvl = m_smbinf.StopsLevel()*m_smbinf.Point(); // remember stop level
   apr     = ReversPrice(dir); 
   op      = BasePrice(dir);                         // open price
   sl      = NormalSL(dir, op, apr, SL, StopLvl);    // stop loss
   tp      = NormalTP(dir, op, apr, TP, StopLvl);    // take profit

   // open position
   m_trade.PositionOpen(m_smb,(ENUM_ORDER_TYPE)dir,lot,op,sl,tp);
   ulong order = m_trade.ResultOrder(); 
   if(order<=0) return(0);                           // order ticket
   return(GetDealByOrder(order));                    // return deal ticket
  }
//------------------------------------------------------------------ GetDealByOrder
ulong CExpertAdvisor::GetDealByOrder(ulong order) // get deal ticket by order ticket
  {
   PositionSelect(m_smb);
   HistorySelectByPosition(PositionGetInteger(POSITION_IDENTIFIER));
   uint total=HistoryDealsTotal();
   for(uint i=0; i<total; i++)
     {
      ulong deal=HistoryDealGetTicket(i);
      if(order==HistoryDealGetInteger(deal,DEAL_ORDER))
         return(deal);                            // remember deal ticket
     }
   return(0);
  }
//------------------------------------------------------------------ CountProfit
double CExpertAdvisor::CountProfitByDeal(ulong ticket)  // position profit by deal ticket
  {
   CDealInfo deal; deal.Ticket(ticket);                 // deal ticket
   HistorySelect(deal.Time(),TimeCurrent());            // select all deals after this
   uint total  = HistoryDealsTotal();
   long pos_id = deal.PositionId();                     // get position id
   double prof = 0;
   for(uint i=0; i<total; i++)                          // find all deals with this id
     {
      ticket = HistoryDealGetTicket(i);
         if(HistoryDealGetInteger(ticket,DEAL_POSITION_ID)!=pos_id) continue;
      prof += HistoryDealGetDouble(ticket,DEAL_PROFIT); // summarize profit
     }
   return(prof);                                        // return profit
  }

4. 매매 모듈

마지막으로, 이 함수 그룹은 서비스 함수 및 매크로를 사용하여 신호와 이벤트를 처리하고 거래하는 전체 프로세스를 결합합니다. 매매 운영의 논리적 어휘소는 적습니다. 그것들은 구체적인 목표에 따라 다릅니다. 그러나, 우리는 거의 모든 익스퍼트들에게 존재하는 공통적인 개념을 구별할 수 있습니다.

   virtual bool      Main();                            // main module controlling trade process
   virtual void      OpenPosition(long dir);            // module of opening position
   virtual void      CheckPosition(long dir);           // check position and open additional ones
   virtual void      ClosePosition(long dir);           // close position
   virtual void      BEPosition(long dir,int BE);       // moving Stop Loss to break-even
   virtual void      TrailingPosition(long dir,int TS); // trailing position of Stop Loss
   virtual void      OpenPending(long dir);             // module of opening pending orders
   virtual void      CheckPending(long dir);            // work with current orders and open additional ones
   virtual void      TrailingPending(long dir);         // move pending orders
   virtual void      DeletePending(long dir);           // delete pending orders

우리는 아래의 예에서 이러한 기능의 구체적인 구현을 고려할 것입니다.

올바른 접근 방식을 선택하고 익스퍼트 구조를 구성했기 때문에 새로운 기능을 추가하는 것은 어렵지 않을 것입니다. 이 계획을 정확하게 사용하면 설계에는 최소의 노력과 시간이 필요하며, 코드는 1년 후에도 읽을 수 있는 수준이 될겁니다.

물론 익스퍼트가 그것들만에 쓰일 수 있는건 아닙니다. CExpertAdvisor 클래스에서 우리는 가장 필요한 메소드만을 선언했습니다. 자식 클래스에 새 핸들러를 추가할 수 있고, 기존의 것을 수정하거나, 자신만의 모듈을 확장하고, 따라서 하나의 라이브러리를 만들게 됩니다. 그런 라이브러리를 가지게 되면, "턴키" 익스퍼트를 개발하는데에 삼십 분에서 이틀정도 걸리게 됩니다.


4. CExpertAdvisor 클래스의 사용 예시

4.1. 인디케이터 신호에 기반한 작업 예시

첫 예시에서처럼 가장 간단한 태스크로 시작합시다 - CExpertAdvisor 클래스를 사용한 MovingAverage Expert Advisor (MetaTrader 5 기본 예시). 조금 더 깊게 들어가봅시다.

알고리즘:

a) 포지션 오픈 조건

  • 만약 매매가가 이동평균을 아래에서 위로 돌파할 경우 매수 포지션 오픈.
  • 만약 매매가가 이동평균을 위에서 아래로 돌파할 경우 매도 포지션 오픈.
  • SL (손절, Stop Loss), TP (이익실현, TakeProfit) 세팅.
  • Position 랏은 Risk 패러미터에 의해 계산됩니다 - Stop Loss가 트리거 되었을 때 잔고에서 얼마나 빠져나갈 것인가.

b) 포지션 클로즈 조건

  • 만약 매매가가 이동평균을 아래에서 위로 가로지르면, 매도 포지션을 청산.
  • 만약 매매가가 이동평균을 위에서 아래로 가로지르면, 매수 포지션을 청산.

c) 제한

  • 익스퍼트 활동 시간을 HourStart 에서 HourEnd 로 제한하기.
  • 익스퍼트는 새로운 바에서만 매매를 한다.

d) 포지션 지원

  • TS 거리에서 단순 추종 청산을 쓸 것.

우리가 만든 익스퍼트에는 CExpertAdvisor 클래스에서 7개 함수가 필요할 것입니다:

  • 신호 함수 - CheckSignal()  
  • 틱 필터 - CheckNewBar()
  • 시간 필터 - CheckTime()
  • 오픈 포지션의 보조 펑션 - DealOpen()
  • 세 개의 기능 모듈 - OpenPosition(), ClosePosition(), TrailingPosition()

CheckSignal() 함수와 모듈들은 자식 클래스에서 반드시 정의되어야합니다. 또한 인디케이터 초기화를 추가해야합니다.

//+------------------------------------------------------------------+
//|                                              Moving Averages.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "ExpertAdvisor.mqh"

input double Risk      = 0.1; // Risk
input int    SL        = 100; // Stop Loss distance
input int    TP        = 100; // Take Profit distance
input int    TS        =  30; // Trailing Stop distance
input int    pMA       =  12; // Moving Average period
input int    HourStart =   7; // Hour of trade start
input int    HourEnd   =  20// Hour of trade end
//---
class CMyEA : public CExpertAdvisor
  {
protected:
   double            m_risk;          // size of risk
   int               m_sl;            // Stop Loss
   int               m_tp;            // Take Profit
   int               m_ts;            // Trailing Stop
   int               m_pMA;           // MA period
   int               m_hourStart;     // Hour of trade start
   int               m_hourEnd;       // Hour of trade end
   int               m_hma;           // MA indicator
public:
   void              CMyEA();
   void             ~CMyEA();
   virtual bool      Init(string smb,ENUM_TIMEFRAMES tf); // initialization
   virtual bool      Main();                              // main function
   virtual void      OpenPosition(long dir);              // open position on signal
   virtual void      ClosePosition(long dir);             // close position on signal
   virtual long      CheckSignal(bool bEntry);            // check signal
  };
//------------------------------------------------------------------ CMyEA
void CMyEA::CMyEA() { }
//----------------------------------------------------------------- ~CMyEA
void CMyEA::~CMyEA()
  {
   IndicatorRelease(m_hma); // delete MA indicator
  }
//------------------------------------------------------------------ Init
bool CMyEA::Init(string smb,ENUM_TIMEFRAMES tf)
  {
   if(!CExpertAdvisor::Init(0,smb,tf)) return(false);  // initialize parent class

   m_risk=Risk; m_tp=TP; m_sl=SL; m_ts=TS; m_pMA=pMA;  // copy parameters
   m_hourStart=HourStart; m_hourEnd=HourEnd;

   m_hma=iMA(m_smb,m_tf,m_pMA,0,MODE_SMA,PRICE_CLOSE); // create MA indicator
   if(m_hma==INVALID_HANDLE) return(false);            // if there is an error, then exit
   m_bInit=true; return(true);                         // trade allowed
  }
//------------------------------------------------------------------ Main
bool CMyEA::Main()                            // main function
  {
   if(!CExpertAdvisor::Main()) return(false); // call function of parent class

   if(Bars(m_smb,m_tf)<=m_pMA) return(false); // if there are insufficient number of bars
   
   if(!CheckNewBar()) return(true);           // check new bar

   // check each direction
   long dir;
   dir=ORDER_TYPE_BUY;
   OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts);
   dir=ORDER_TYPE_SELL;
   OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts);

   return(true);
  }
//------------------------------------------------------------------ OpenPos
void CMyEA::OpenPosition(long dir)
  {
   if(PositionSelect(m_smb)) return;     // if there is an order, then exit
   if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"),
                 StringToTime(IntegerToString(m_hourEnd)+":00"))) return;
   if(dir!=CheckSignal(true)) return;    // if there is no signal for current direction
   double lot=CountLotByRisk(m_sl,m_risk,0);
   if(lot<=0) return;                    // if lot is not defined then exit
   DealOpen(dir,lot,m_sl,m_tp);          // open position
  }
//------------------------------------------------------------------ ClosePos
void CMyEA::ClosePosition(long dir)
  {
   if(!PositionSelect(m_smb)) return;                 // if there is no position, then exit
   if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"),
                 StringToTime(IntegerToString(m_hourEnd)+":00")))
     { m_trade.PositionClose(m_smb); return; }        // if it's not time for trade, then close orders
   if(dir!=PositionGetInteger(POSITION_TYPE)) return; // if position of unchecked direction
   if(dir!=CheckSignal(false)) return;                // if the close signal didn't match the current position
   m_trade.PositionClose(m_smb,1);                    // close position
  }
//------------------------------------------------------------------ CheckSignal
long CMyEA::CheckSignal(bool bEntry)
  {
   MqlRates rt[2];
   if(CopyRates(m_smb,m_tf,0,2,rt)!=2)
     { Print("CopyRates ",m_smb," history is not loaded"); return(WRONG_VALUE); }

   double ma[1];
   if(CopyBuffer(m_hma,0,0,1,ma)!=1)
     { Print("CopyBuffer MA - no data"); return(WRONG_VALUE); }

   if(rt[0].open<ma[0] && rt[0].close>ma[0])
      return(bEntry ? ORDER_TYPE_BUY:ORDER_TYPE_SELL); // condition for buy
   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      return(bEntry ? ORDER_TYPE_SELL:ORDER_TYPE_BUY); // condition for sell

   return(WRONG_VALUE);                                // if there is no signal
  }

CMyEA ea; // class instance
//------------------------------------------------------------------ OnInit
int OnInit()
  {
   ea.Init(Symbol(),Period()); // initialize expert
   return(0);
  }
//------------------------------------------------------------------ OnDeinit
void OnDeinit(const int reason) { }
//------------------------------------------------------------------ OnTick
void OnTick()
  {
   ea.Main();                  // process incoming tick
  }

Main() 함수의 구조를 분석해봅시다. 크게 두 파트로 나뉩니다.

첫 파트에서는 부모 함수가 호출됩니다. 이 함수는 익스퍼트의 작업에 전체적으로 영향을 미치는 가능한 패러미터를 처리합니다. 여기에는 익스퍼트가 매매할 수 있는 허용량 확인과 과거 데이터의 검증이 포함됩니다.

두번째 파트에서는 마켓 이벤트가 직접 처리됩니다.

CheckNewBar() 필터가 테스트됩니다 - 새 바를 체크함. 그리고 매매의 두 방향을 위한 모듈들은 차례로 호출됩니다.

모듈에서는 모든 것이 상당히 추상적으로 구성되어 있습니다(두 번째 설계 원리). 심볼 속성에는 직접적 주소가 없습니다. 세 모듈 - OpenPosition(), ClosePosition() 그리고 TrailingPosition() - 은외부에서 해당 모듈들로 오는 패러미터들에만 의존합니다. 이렇게 하면 이러한 모듈을 호출하여 매수 및 매도 주문을 확인할 수 있습니다. 


4.2. CExpertAdvisor 사용 예시 - 인디케이터 없는 익스퍼트, 포지션 상태와 결과 분석

포지션에서만 거래되는 시스템을 랏 증가 후 손실 후 역방향으로 적용(이러한 유형의 익스퍼트는 일반적으로 "Martingale"이라고 함)하는 방법을 시연해 보겠습니다.

a) 첫 주문하기

  •  익스퍼트가 첫 매수 포지션을 초기 랏으로 오픈했을 때

b) 연속적으로 포지션을 오픈

  • 만약 이전 포지션이 익절이었다면, 같은 방향에 초기 랏을 가지고 포지션 오픈
  • 만약 이전 포지션이 손절이었다면, 역방향에 더 큰 랏(팩터 사용)으로 포지션 오픈.

이를 위해 CExpertAdvisor 클래스의 함수 세개를 씁니다:

  • 오픈 포지션 - DealOpen()
  • 거래 티켓으로 청산된 포지션 이익 값 획득 - CountProfitByDeal()
  • 활동 모듈 - OpenPosition(), CheckPosition()

익스퍼트가 어떠한 인디케이터도 분석하지 않고 결과만 처리하기 때문에 우리는 최적 생산성을 위해 OnTrade() 이벤트를 활용할 것입니다. 그말인즉, 일단 처음에 매수 주문을 한 익스퍼트는 이 포지션을 청산한 후에야 이어진 모든 후속 주문을 할 수 있다는 것입니다. 따라서 우리는 OnTick()에서 초기 주문을 한 후 이어진 모든 작업을 OnTrade()에서 처리할 것입니다.

Init() 함수는 평소처럼 단순히 클래스의 패러미터들을 익스퍼트의 외부 패러미터로 초기화합니다.

OpenPosition() 모듈은 초기 포지션을 오픈하며 m_first 플래그에 막혀있습니다.

CheckPosition() 모듈은 추가적인 포지션 역행을 관리합니다.

이들은 각각 대응되는 익스퍼트 함수: OnTick() and OnTrade() 에서 호출됩니다.

//+------------------------------------------------------------------+
//|                                                       eMarti.mq5 |
//|              Copyright Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "ExpertAdvisor.mqh"
#include <Trade\DealInfo.mqh>

input double Lots    = 0.1; // Lot
input double LotKoef = 2;   // lot multiplier for loss
input int    Dist    = 60;  // distance to Stop Loss and Take Profit
//---
class CMartiEA : public CExpertAdvisor
  {
protected:
   double            m_lots;       // Lot
   double            m_lotkoef;    // lot multiplier for loss
   int               m_dist;       // distance to Stop Loss and Take Profit
   CDealInfo         m_deal;       // last deal
   bool              m_first;      // flag of opening the first position
public:
   void              CMartiEA() { }
   void             ~CMartiEA() { }
   virtual bool      Init(string smb,ENUM_TIMEFRAMES tf); // initialization
   virtual void      OpenPosition();
   virtual void      CheckPosition();
  };
//------------------------------------------------------------------ Init
bool CMartiEA::Init(string smb,ENUM_TIMEFRAMES tf)
  {
   if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // initialize parent class
   m_lots=Lots; m_lotkoef=LotKoef; m_dist=Dist;       // copy parameters
   m_deal.Ticket(0); m_first=true;
   m_bInit=true; return(true);                        // trade allowed
  }
//------------------------------------------------------------------ OnTrade
void CMartiEA::OpenPosition()
  {
   if(!CExpertAdvisor::Main()) return;                       // call parent function
   if(!m_first) return;                                      // if already opened initial position
   ulong deal=DealOpen(ORDER_TYPE_BUY,m_lots,m_dist,m_dist); // open initial position
   if(deal>0) { m_deal.Ticket(deal); m_first=false; }        // if position exists
  }
//------------------------------------------------------------------ OnTrade
void CMartiEA::CheckPosition()
  {
   if(!CExpertAdvisor::Main()) return;           // call parent function
   if(m_first) return;                           // if not yet placed initial position 
   if(PositionSelect(m_smb)) return;             // if position exists

   // check profit of previous position
   double lot=m_lots;                            // initial lot
   long dir=m_deal.Type();                       // previous direction
   if(CountProfitByDeal(m_deal.Ticket())<0)      // if there was loss
     {
      lot=NormalLot(m_lotkoef*m_deal.Volume());  // increase lot
      dir=ReversType(m_deal.Type());             // reverse position
     }
   ulong deal=DealOpen(dir,lot,m_dist,m_dist);   // open position
   if(deal>0) m_deal.Ticket(deal);               // remember ticket
  }

CMartiEA ea; // class instance
//------------------------------------------------------------------ OnInit
int OnInit()
  {
   ea.Init(Symbol(),Period()); // initialize expert
   return(0);
  }
//------------------------------------------------------------------ OnDeinit
void OnDeinit(const int reason) { }
//------------------------------------------------------------------ OnTick
void OnTick()
  {
   ea.OpenPosition();          // process tick - open first order
  }
//------------------------------------------------------------------ OnTrade
void OnTrade()
  {
   ea.CheckPosition();         // process trade event
  }


5. 이벤트 작업하기

본 문서에서 여러분은 OnTick() 과 OnTrade() 함수에 의해 대변된 두 이벤트 - NewTick 그리고 Trade 처리 예시를 접했습니다. 대부분의 상황에서 이 두 이벤트는 계속 사용됐죠.

익스퍼트쪽에서는 이벤트 처리를 담당하는 함수가 네개 존재합니다:

  • OnChartEvent는 거대한 이벤트 그룹입니다: 그래픽 객체, 키보드, 마우스, 그리고 커스텀 이벤트. 예를 들어, 이 함수는 주문의 그래픽 관리를 원칙으로 하는 대화형 전문가 또는 전문가를 만드는 데 사용됩니다. 또는 MQL 프로그램 패라미터의 활성 컨트롤을 만듭니다(버튼 및 편집 필드 사용). 일반적으로, 이 함수는 익스퍼트의 외부 이벤트를 처리하는 데에 쓰입니다.
  • OnTimer는 시스템 타이머 이벤트가 처리되었을 때 호출됩니다. MQL 프로그램이 정기적으로 환경을 분석하고 인디케이터 값을 계산해야 하는 경우, 지속적으로 외부 신호 소스를 참조해야 하는 경우 등에 사용됩니다. 개략적으로 말하면 OnTimer() 함수는 이하의 대안이자 최고의 대체자라고 할 수 있는데:
    while(true) {  /* perform analysis */; Sleep(1000); }. 
    즉, 익스퍼트가 시작할 때 무한 루프에서 작업할 필요는 없지만, 함수의 호출을 OnTick()에서 OnTimer()로 넘기기에 충분합니다.
  • OnBookEvent 는 마켓 뎁스 상태가 변할 때에 생성되는 이벤트를 처리합니다. 이 이벤트는 외부로 넘겨져서 태스크에 맞춰 처리될 수 있습니다.
  • OnTester는 정해진 기간 범위에서 익스퍼트를 테스트한 후에 호출되는데, 이는 OnDeinit() 함수가 테스트 생성을 스크리닝 하기 이전이며 커스텀 max 패러미터를 이용한 최적화될 때 쓰입니다.

이벤트와 이벤트 조합은 항상 특정 작업의 솔루션을 위해 사용하는 것이 좋습니다.


마치며

지금까지 보았듯이, 올바른 계획을 가지고 엑스퍼트를 쓰는 것에는 그렇게 많은 시간이 필요하지 않습니다. MQL5에서 이벤트를 처리할 수 있는 새로운 가능성 덕분에, 우리는 매매 프로세스를 관리하는 더 유연한 구조를 가지게 되었습니다. 하지만 이 모든 것들은 매매 알고리즘을 적절히 준비해야만 비로소 강력한 도구가 됩니다.

본 문서에서는 그 탄생의 세 기본 - 중대성, 추상화, 모듈성을 설명했습니다. 당신의 익스퍼트를 이들 "세 근간"을 기반으로 만든다면 더욱 쉬운 매매시스템을 만들 수 있을 것입니다.

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

파일 첨부됨 |
emarti.mq5 (3.57 KB)
emyea.mq5 (5.9 KB)
expertadvisor.mqh (17.92 KB)
MetaTrader 5에서의 거래 이벤트 MetaTrader 5에서의 거래 이벤트
거래 계정의 현재 상태를 모니터링한다는 것은 오픈 포지션과 주문을 통제한다는 것을 의미합니다. 거래 신호가 진정한 거래가 되기 전, 이는 거래 서버로의 요청으로써 클라이언트 터미널에서 전송되어야 하며, 이 때 거래 서버는 처리 대기 중인 주문 대기열에 배치될 것입니다. 거래 서버에 의한 수락하거나, 만료 시 삭제하거나, 거래 기준으로 거래를 수행하는 등과 관련된 이러한 모든 조치에는 거래 이벤트가 뒤따르게 되고, 이 때 거래 서버는 터미널에 이런 사항에 대해 알려주게 됩니다.
20 MQL5에서의 매매 신호들 20 MQL5에서의 매매 신호들
본 문서는 매매시스템이 필요한 매매 신호를 어떻게 받는지 가르쳐줄 것입니다. 이 문서에서 다룰 20개의 매매 신호를 만드는 예시는 Expert Advisor 개발에 쓸 수 있는 별도의 커스텀 함수로 되어있습니다. 편의를 위해서, 본 문서에서 사용된 모든 함수는 미래에 Exper Advisor에 손쉽게 연결할 수 있도록 하나의 mqh include 파일에 들어 있습니다.
MetaTrader 5 테스트의 기초 MetaTrader 5 테스트의 기초
MetaTrader 5의 세 가지 테스트 모드의 차이점은 무엇이며 특히 무엇을 찾아야 합니까? 여러 상품에서 동시에 거래되는 EA 테스트는 어떻게 이루어 집니까? 테스트 중 지표 ​​값은 언제 어떻게 계산되며 이벤트는 어떻게 처리됩니까? "오픈 프라이스 전용" 모드에서 테스트하는 동안 다른 기기의 바를 동기화하는 방법은 무엇입니까? 이 글에서 저희는 이러한 질문과 다른 많은 질문에 대한 답변을 제공하는 것을 목표로 합니다.
다른 인디케이터 기반으로 인디케이터를 쓰는 방법에 관하여 다른 인디케이터 기반으로 인디케이터를 쓰는 방법에 관하여
MQL5은 인디케이터를 백지로부터 만들어갈 수 있게도 해주지만, 클라이언트 터미널에 이미 빌트인 된 것이나 커스텀 인디케이터 등 이미 존재하는 다른 인디케이터 기반으로 만들 수 있는 옵션 또한 제공합니다. 고르고 나면 여기서도 두가지 선택지가 있습니다 - 새 계산이나 그래픽 스타일을 추가하는 방식으로 인디케이터를 개선하는 것, 그리고 iCustom() 이나 IndicatorCreate() 함수를 써서 클라이언트 터미널에 내장된 것이나 커스텀 인디케이터를 쓰는 것.