![MQL5 객체 지향 프로그래밍 접근 방식을 사용한 Expert Advisor 작성하기](https://c.mql5.com/i/articles/overlay.png)
MQL5 객체 지향 프로그래밍 접근 방식을 사용한 Expert Advisor 작성하기
소개
첫 번째 글에서는 MQL5에서 Expert Advisor를 생성, 디버깅 및 테스트하는 기본 단계를 살펴 보았습니다.
우리가 한 모든 것은 매우 간단하고 흥미로웠습니다. 그러나 새로운 MQL5 언어는 훨씬 더 많은 것을 제공합니다. 이 글에서는 첫 번째 글에서 수행한 작업에 대한 객체 지향 접근 방식을 고려할 것입니다. 대부분의 사람들은 이것이 어렵다고 생각하지만, 이 글을 다 읽었을 때 쯤이면 객체 지향 기반의 Expert Advisor를 직접 작성할 수 있을 것임을 확신하고 싶습니다.
첫 번째 글에서 배운 내용 중 일부를 반복하지 않을 것이므로, 아직 읽지 않았다면 먼저 글을 읽어 보시기 바랍니다.
1. 객체 지향 패러다임
새로운 MQL5를 MQL4보다 훨씬 강력하고 견고하게 만드는 것 중 하나는 OOP (객체 지향 프로그래밍) 접근 방식입니다.
OOP에서는 객체가 구현 세부 사항을 노출하지 않는 것이 좋습니다. 이렇게 하면 객체를 사용하는 코드를 변경하지 않고 구현을 변경할 수 있습니다. 즉, 클래스는 프로그래머가 자신이 작성한 클래스가 구현되는 방식을 숨기고 변경을 방지할 수 있음을 의미합니다.
좀 더 명확하게 하기 위해 방금 언급한 "클래스"와 "객체"라는 용어에 대해 살펴 보겠습니다.
- 클래스. 클래스는 데이터 구조의 확장된 개념과 비슷하지만 데이터만 보유하는 대신 데이터와 함수를 모두 보유합니다. 클래스는 여러 변수와 함수를 포함할 수 있습니다. 클래스의 멤버라고 합니다. 데이터를 조작하는 데이터 멤버와 함수를 캡슐화한 것입니다. 클래스는 모든 Expert Advisors 함수를 클래스로 마무리 할 수 있다는 점에서 훨씬 더 강력합니다. EA 코드에서 필요할 때만 함수를 참조하게 됩니다. 그건 그렇고, 이것이 이 글의 전부입니다.
- 객체. 객체는 클래스의 인스턴스입니다. 클래스가 생성되면 클래스를 사용하려면 클래스의 인스턴스를 선언해야 합니다. 이것을 객체라고 합니다. 즉, 객체를 생성하려면 클래스가 필요합니다.
1.1. 클래스 선언
기본적으로 클래스는 클래스에서 생성하려는 객체의 멤버 (속성 및 함수/메소드)에 대한 설명을 포함합니다. 예를 살펴 보겠습니다…
문, 좌석, 타이어, 무게 등이 있고 시작, 기어 변경, 정지 및 경적도 할 수있는 개체를 만들고 싶다면; 그런 다음 이를 위한 클래스를 작성해야 합니다. 문, 좌석, 타이어, 무게, 시작, 변속기, 정지 및 경적 이 클래스 구성원이 됩니다.
물론 이러한 구성원이 분류되어 있음을 알 수 있습니다. 어떤 것은 우리의 객체가 가질 것 (속성) 이고 다른 것들은 우리의 객체가 할 것입니다 (액션 – 함수/방법). 클래스를 선언하려면 아주 훌륭하고 설명적인 이름을 생각해야 합니다. 이 경우 클래스를 CAR이라고 부릅니다. 우리의 CAR 클래스는 위에서 언급한 속성과 함수를 멤버로 가질 것입니다.
클래스를 선언하려면 키워드 클래스 다음에 클래스 이름 뒤에 클래스 멤버를 포함하는 중괄호 쌍을 입력합니다.
따라서 클래스의 기본 형식은 다음과 같습니다.class class_name
{
access_keyword_1:
members1;
access_keyword_2:
members2;
...
};
여기서 class_name은 작성하려는 클래스의 유효한 식별자이고 members1 및 members2는 클래스의 데이터 멤버입니다.
access_keyword는 클래스 구성원에 대한 액세스 권한을 지정합니다. access_keyword는 비공개, 보호 또는 공개일 수 있습니다. 우리는 구현 세부 사항을 실제로 노출하지 않고 우리 자신과 다른 사람들이 사용할 수 있는 클래스를 작성하려고 합니다. 그렇기 때문에 액세스 권한이 필요합니다.
클래스 외부에서 액세스하고 싶지 않은 클래스 구성원이 있을 수 있습니다. 이들은 비공개 또는 보호된 키워드를 사용하여 비공개 액세스 섹션 내에서 선언됩니다. 클래스 외부에서 액세스하려는 다른 멤버는 public 키워드를 사용하여 공개 액세스 섹션 내에서 선언됩니다. 이제 새로운 CAR 클래스는 다음과 같습니다.
class CAR { private: int doors; int sits; int tyres; double weight; public: bool start(); void changegear(); void stop(); bool horn(); ... };
우리의 CAR 클래스는 키워드 클래스를 사용하여 선언됩니다. 이 클래스에는 비공개 액세스 권한이 있는 4 명의 멤버와 공개 액세스 권한이 있는 4 명의 멤버가 있는 8 번째 멤버가 포함됩니다. 비공개 섹션의 4 개 멤버는 데이터 멤버입니다. 세 개는 정수(int) 데이터 유형이고 하나는 이중 데이터 유형입니다. 이 멤버는 이 클래스 외부에서 선언된 다른 함수에서 액세스 할 수 없습니다.
공개 섹션의 4 명의 구성원은 함수 구성원입니다. 두 개는 bool 데이터 유형을 반환하고 두 개는 void 유형을 반환합니다. 이들은 우리 클래스를 사용하는 모든 사람이 생성 할 때마다 이 클래스의 모든 객체에 액세스 할 수 있는 멤버입니다. 클래스의 객체가 생성되면 이러한 멤버를 쉽게 사용할 수 있습니다.
당연히 알 수 있듯이 액세스 키워드 (private, public, protected)는 항상 콜론으로 이어집니다. 클래스 선언도 세미콜론으로 끝났습니다. 멤버는 올바른 데이터 유형을 사용하여 선언됩니다.
일단 클래스를 선언하면 위에서 한 것처럼 명시적으로 지정하지 않는 한 클래스의 모든 멤버에게 개인 액세스 권한이 부여됩니다. 예를 들어, 아래 클래스 선언에서:
class CAR { int doors; int sits; int tyres; double weight; public: bool start(); void changegear(); void stop(); bool horn(); ... };
public access 키워드 위에 선언된 4 개의 모든 멤버는 자동으로 private access를 갖습니다.
클래스를 사용하려면 먼저 클래스의 객체를 만들어야 합니다. 이제 클래스의 유형 인 객체를 생성해 보겠습니다. 이를 위해 우리는 클래스 이름과 객체에 부여할 이름을 사용할 것입니다.
차량 혼다;
또는 다른 개체를 만들 수 있습니다.
차량 토요타;
혼다 또는 토요타 이제 차량 유형이며 이제 멤버 함수가 공개 액세스 섹션 내에서 선언된 경우 차량 클래스의 모든 멤버 함수에 액세스 할 수 있습니다. 나중에 다시 설명하겠습니다.
이 클래스의 객체를 원하는 만큼 만들 수 있음을 알 수 있습니다. 이것은 객체 지향 프로그래밍의 이점 중 하나입니다.
이 시점에서 MQL5의 클래스 형식에 대해 자세히 살펴보겠습니다.
class class_name { private: members1; members2; members3; public: class_name() //Constructor; ~class_name() //Destructor; Members4(); Members5(); protected: members6; members7; };
class_name이 클래스의 이름인 클래스 선언입니다. 이 클래스에는 9 명의 멤버가 있지만 이 9 명 중 2 명은 특별 멤버입니다.
생성자:
생성자 (class_name()로 표시)는 클래스 유형의 새 객체가 생성될 때 자동으로 호출되는 특수 함수입니다. 따라서 이 경우 이 클래스 유형의 개체를 만들 때
class_name 객체;
생성자 >class_name()이 자동으로 호출됩니다. 생성자의 이름은 클래스의 이름과 일치해야 하므로 생성자의 이름을 class_name()으로 지정했습니다. MQL5에서 생성자는 입력 매개 변수를 취하지 않으며 리턴 유형이 없습니다. 클래스 멤버의 메모리 할당 및 초기화는 일반적으로 생성자가 호출될 때 수행됩니다. 생성자는 일반 멤버 함수인 것처럼 명시적으로 호출할 수 없습니다. 해당 클래스의 새 객체가 생성될 때만 실행됩니다. MQL5의 클래스는 생성자를 하나만 가질 수 있습니다.
소멸자:
두 번째 특수 멤버는 ~class_name()로 표시됩니다. 이것은 클래스 이름 앞에 조수 (~)로 작성된 클래스 소멸자입니다. 클래스 객체가 소멸되면 자동으로 호출됩니다. 초기화 해제해야 하는 클래스의 모든 멤버는 이 단계에서 초기화 해제되며 명시 적으로 소멸자를 선언했는지 여부는 중요하지 않습니다.
데이터 멤버:
클래스의 구성원은 모든 유효한 데이터 유형, 클래스 유형 또는 struct 유형일 수 있습니다. 즉, 클래스의 멤버 변수를 선언할 때 유효한 데이터 유형 (int, double, string 등), 다른 클래스의 객체 또는 구조 유형 (예: MQL5 MqlTradeRequest 등)을 사용할 수 있습니다.)
함수 구성원:
이들은 데이터 멤버를 수정하고 클래스의 주요 함수/메소드를 실행하는 데 사용되는 클래스의 멤버입니다. 함수 멤버의 반환 유형은 모든 유효한 반환 유형 (bool, void, double, string 등) 일 수 있습니다.
사적:
이 섹션 내에서 선언 된 멤버는 클래스의 함수 멤버 만 액세스 할 수 있습니다. 클래스 외부의 다른 함수는 액세스 할 수 없습니다.
보호:
이 섹션 내에서 선언된 멤버는 클래스의 함수 멤버가 액세스 할 수 있으며 이 클래스에서 파생된 다른 클래스의 멤버 함수에서도 액세스 할 수 있습니다. 즉, 이 클래스에서 새 클래스를 만들 수도 있습니다. 이 경우 이 클래스에서 파생된 새 클래스 (이제 기본 클래스가 됨)는 기본 클래스의 보호된 멤버에 액세스 할 수 있습니다. 이것이 OOP의 상속 개념입니다. 곧 논의하겠습니다…
공적:
이 섹션 내에서 선언된 멤버는 클래스의 객체가 클래스 외부에서 사용할 수 있습니다. 다른 프로그램에서 클래스를 사용하는 데 필요한 일부 함수를 선언하는 곳입니다.
이제 우리는 클래스의 기본 형식을 살펴 보았으니 아직 지루하지 않으셨으면 합니다. Expert Advisor를 위한 클래스 래퍼를 만들기 전에 살펴 봐야 할 다른 흥미로운 클래스 측면이 아직 남아있기 때문입니다.
1.2. 상속
이 초기 클래스 base_class에서 다른 클래스를 만들고 싶다고 가정해보겠습니다. 초기 클래스에서 새 클래스를 파생하는 형식은 다음과 같습니다.
기본 클래스:
class base_class { private: members1; members2; members3; public: class_name() //Constructor; ~class_name() //Destructor; Members4(); Members5(); protected: members6; members7; };
파생 클래스:
class new_class : access_keyword base_class { private: members8; public: new_class() //Constructor; ~new_class() //Destructor; Members9(); };
세부 사항을 설명하기 전에 여기에 몇 가지 설명이 있습니다. new_class 클래스는 위와 같이 콜론과 access_keyword를 사용하여 base_class 클래스에서 파생됩니다. 이제 base_class에서 파생/만든 new_class는 base_class의 공개 및 보호 된 구성원 모두에 액세스 (또는 상속) 할 수 있지만 base_class의 개인 구성원에는 액세스 할 수 없습니다 (또는 상속하지 않음). new_class는 base_class와 다른 새로운 멤버 메소드/함수를 구현할 수도 있습니다. 즉, new_class는 base_class에서 상속하는 것과는 별도로 자체 데이터 및 함수 멤버를 가질 수도 있습니다.
파생 클래스를 만드는 데 public 키워드가 사용되는 경우 기본 클래스의 공용 및 보호된 멤버가 파생 클래스의 공용 및 보호된 멤버로 상속됨을 의미합니다. Protected 키워드를 사용하면 기본 클래스의 public 및 protected 멤버가 파생 클래스의 보호된 멤버로 상속됩니다. private 키워드를 사용하면 기본 클래스의 public 및 protected 멤버가 파생 클래스의 private 멤버로 상속됩니다.
new_clas (파생 클래스)의 새 개체가 생성될 때 base_class의 생성자가 new_class의 생성자보다 먼저 호출된다는 점에 유의해야 합니다. 객체가 소멸되면 new_class (파생 클래스)의 소멸자가 base_class의 소멸자 전에 먼저 호출됩니다.
이 상속 개념을 더 잘 이해하기 위해 초기 클래스 CAR로 돌아가 보겠습니다.
class CAR { protected: int doors; int sits; double weight; public: bool start(); void changegear(); void stop(); bool horn(); private: int tyres; };
이 클래스에서 다른 클래스 SALOON을 파생시킬 수 있습니다. CAR 클래스의 데이터 멤버 중 세 개를 보호 된 것으로 선언했습니다. 이것은 우리의 새 클래스 SALOON이 이러한 멤버를 상속 할 수 있도록 하기 위한 것입니다.
또한 액세스 키워드를 배치하는 순서는 중요하지 않음을 이해하시기 바랍니다. 중요한 것은 액세스 키워드로 선언된 모든 멤버가 해당 키워드에 속한다는 것입니다.
class SALOON : public CAR { private: int maxspeed; public: void runathighspeed(); };
파생 클래스 SALOON에는 두 개의 멤버가 있으며 동시에 기본 클래스 CAR에서 7 개의 멤버 (보호 및 공용 멤버)를 상속합니다. 즉, SALOON의 객체가 생성되면 자신의 공용 멤버 함수 runathighspeed()와 함께 CAR의 공용 멤버 함수인 start(), changegear(), stop() 및 horn()에 액세스 할 수 있습니다. 이것이 상속의 개념입니다.
우리 아버지/부모 (기본 클래스)의 일부 캐릭터/행동 (방법)이 우리에게 나타나는 것처럼, 그들의 자녀 (파생 클래스)는 유전적으로든 다른 방법이든 그들로부터 그러한 행동 (방법/함수)을 상속하기 때문입니다. 죄송합니다. 저는 의료진은 아니지만 제가 그림을 그리려는 그림을 잘 이해하신 것 같습니다. 그건 그렇고, MQL5는 다중 상속을 지원하지 않으므로 그것에 대해 이야기할 필요가 없습니다.
흠!!! OOP 또는 CLASS라는 신비한 것을 덮고있는 검은 색 천이 조금씩 제거되기를 바랍니다... 피곤하지 마십시오. 이 시점에서 우리가 논의하는 내용이 아직 명확하지 않다고 느끼면 긴장을 풀어야 합니다. 커피 한 잔을 마시고 돌아와서 처음부터 시작하십시오. 생각만큼 신비롭지는 않습니다…
지금 이 시점으로 돌아오셨다면 제 설명을 따르고 계실 것입니다. 기본 클래스 CAR에서 파생 할 수 있는 클래스가 몇 개 더 있는지 알려주세요. 대답해주세요. 난 진지해. 이름을 지정하고 선언문을 작성하여 우편으로 보내주십시오. 모두 이름을 지어 주시면 출시를 위해 데리고 나가겠습니다... (제가 농담하는 걸까요?)
이제 더 많은 것을 설정 했으니 계속하겠습니다…
글을 쓸 때 아빠처럼 글을 쓴다는 게 사실이에요. 그의 손글씨는 저처럼 매우 깔끔하고 스타일리시합니다. 저는 그것이 내가 그에게서 물려받은 것이라고 생각하지만 무엇을 추측하십시오. 그는 내가 오른손을 사용하는 동안 왼손을 사용하여 글을 쓰고, 글을 보면 비슷해보이기 때문에 거의 구별할 수 없습니다. 여기서 문제는 무엇입니까? 저는 아버지로부터 좋은 손글씨를 물려받았지만 아버지처럼 왼손으로 글씨를 쓰지는 않습니다. 이것은 내가 물려받은 것과 비슷해보이지만 내 방식은 아버지와 다르다는 것을 의미합니다. 이것이 당신에게 의미가 있습니까? 이것은 OOP에서 Polymorphism이라는 아이디어입니다.
파생 클래스 (위의 예에서와 같이 나 자신)는 기본 클래스 (내 아빠)에서 멤버 함수 (writefine() – 내 손글씨 용)를 상속하지만 베이스 클래스 (우리 아빠)에서 그것은 (나) 함수 (writefine())를 다른 방식으로 구현합니다.
다시 CAR 클래스와 파생 클래스 SALOON으로 돌아갑니다.
class CAR { protected: int doors; int sits; double weight; public: bool start(); virtual void changegear(){return(0);} void stop(); bool horn(); private: int tyres; };
class SALOON : public CAR { private: int maxspeed; public: void runathighspeed(); virtual void changegear(){gear1=reverse; gear2=low; gear3=high;} }; class WAGON : public CAR { private: bool hasliftback; public: virtual void changegear(){gear1=low; gear2=high; gear3=reverse;} };
여기에서 몇 가지 변경 사항을 살펴보겠습니다. 먼저 두 개의 멤버가있는 WAGON이라는 CAR에서 새 파생 클래스를 선언했습니다. 또한 멤버 함수 changegear()를 기본 클래스에서 가상 함수가 수정했습니다. changegear()를 가상 함수로 만든 이유는 무엇입니까. 기본 클래스에서 함수를 상속하는 모든 클래스가 자체 방식으로 구현할 수 있기를 원하기 때문입니다.
즉, 클래스의 가상 멤버 함수는 선언된 클래스에서 파생 된 모든 클래스에서 재정의되거나 다르게 구현 될 수 있는 멤버 함수입니다. 그런 다음 멤버 함수 본문을 파생 클래스의 새 구현 집합으로 바꿀 수 있습니다. 파생 클래스에서 가상이라는 단어를 다시 사용하지 않을 수 있지만 파생 클래스에서 항상 사용하는 것이 좋은 프로그래밍 관행입니다.
위의 예에서 SALOON 및 WAGON 클래스는 자체 방식으로 changegear() 함수를 구현합니다.
1.3. 클래스 방법 정의 (멤버 함수)
우리는 클래스를 선언하는 방법을 어느 정도 알고 있었기 때문에 클래스의 멤버 함수를 정의하는 방법을 논의하여 더 나아가 보겠습니다. 클래스를 선언한 후 다음은 클래스의 멤버 함수를 정의하는 것입니다. CAR 클래스를 다시 보자
class CAR { protected: int doors; int sits; double weight; public: void CAR() // Constructor bool start(); void changegear(); void stop(); bool horn(){press horn;} private: int tyres; }; void CAR::CAR() { // initialize member variables here } bool CAR::start() { // car start procedure here } void CAR::changegear() { // car changegear procedure here } void CAR::stop() { // car stop procedure here }
멤버 함수를 정의 할 때 범위 연산자라는 이중 콜론 (::) 연산자를 사용했습니다. 이것은 일반 함수와 동일하게 작성되며 추가되는 클래스 이름과 범위 연산자만 다릅니다. 또한 함수 중 하나가 이미 클래스 (멤버 함수 horn()) 내에 정의되어 있는 것을 볼 수 있습니다. 멤버 함수는 여기에서 본 것처럼 클래스 선언 또는 클래스 선언 외부에서 정의할 수 있습니다.
진행하기 조금 전에 함수의 개념을 검토할 수 있다면 중요하다고 생각합니다.
1.4. 함수
그런데 함수가란 무엇입니까?
때로는 세 자녀가 있는 집에서 그 중 한 명만 집에서 모든 일을 하는 것이 아닙니다. 한 명은 저녁 식사 후 매일 접시를 씻도록 요청 받았고, 한 명은 청소를 하라는 요청을 받았으며, 세 번째는 매일 아침 침대 정리 작업을 맡겼습니다.
집에서 해야 할 일이 몇 가지 있는데, 모든 일을 한 아이에게 주는 것이 아니라 세 사람에게 나누어 주었습니다. 이것은 그들 중 한 명에게만 부담이 되는 것이 아니라 각자에게 매우 쉽고 가벼울 것입니다. 또한, 아이들 중 한 명이 자신의 일을 하지 않으면, 우리는 그들 중 누구를 블로깅 해야 하는지 금방 알 수 있습니다. 이것이 바로 함수 뒤에 있는 아이디어입니다.
대부분의 경우 우리는 많은 작업을 수행할 코드를 작성하려고 합니다. 이것이 함수가 들어오는 곳입니다. 작업을 더 작은 작업으로 나누고 각각의 작은 작업을 수행하는 함수를 작성하기로 결정할 수 있습니다. 함수는 일련의 작업을 수행하거나 구현하는 코드 블록입니다. 프로그램의 특정 지점에서 호출될 때마다 실행되는 명령문 그룹입니다.
함수는 다음과 같이 정의 할 수 있습니다.
Return_type function_name (parameters1,parameters2,…)
{
Expressions; //(actions to carry out by the function)
}
- Return_type: 함수에 의해 반환된 데이터 유형 (유효한 데이터 유형이거나 아무것도 반환하지 않는 경우 void여야 함)
- Function_name: 함수 호출에 사용될 함수 이름 (유효한 이름이어야 함)
- 매개 변수: 매개 변수는 함수 내에서 지역 변수로 작동하는 유효한 데이터 유형 변수입니다. 함수에 둘 이상의 매개 변수가 있는 경우 쉼표로 구분됩니다.
- 표현식 : 문장 블록을 포함하는 함수의 본문
함수의 예:
int doaddition (int x, int y) { return (x+y); }
함수 반환 유형은 정수 (int)이고 doaddition은 함수 이름이며 int x 및 int y는 매개 변수입니다. 함수가 하는 일은 제공된 두 입력 매개 변수를 추가하고 결과를 반환하는 것입니다. 따라서 함수에 두 개의 정수 변수 2와 3을 제공하면 함수는 더하기를 수행하고 결과로 5를 반환합니다.
int doaddition(2,3) // returns 5
함수에 대한 자세한 내용은 MQL5 참조 설명서를 참조하십시오.
이제 충분한 이론을 바탕으로 작업을 시작했습니다.
이 글의 핵심은 MQL5에 제시된 객체 지향 접근 방식을 사용하여 Expert Advisor를 위한 클래스를 작성하는 방법을 가르치는 것입니다.
이제 행동할 시간입니다…
2. Expert Advisor 쓰기
이 시점에서 우리는 첫 번째 글에서 만든 Expert Advisor를 참조할 것입니다. 아직 글을 읽지 않으 셨다면 지금부터 읽어 보시고 지금부터 논의 할 내용 대부분이 이상하지 않도록 해주세요. 그러나 여전히 필요한 몇 가지 사항을 수정할 수 있습니다.
클래스를 작성하기 전에 먼저 앉아서 거래 전략을 개발해야 합니다. 첫 번째 글에서 이미 이 작업을 수행했습니다. 다음은 클래스에 위임할 함수를 선택하는 것입니다. 이러한 함수는 클래스의 멤버 변수를 결정합니다. 첫 번째 글에서 우리의 거래 전략을 요약했습니다.
EA가 할 일:
- 특정 인디케이터를 모니터링하고 특정 조건이 충족되면 (또는 특정 조건이 충족되면) 충족된 현재 조건에 따라 거래 (숏/매도 또는 롱/매수)를 합니다.
위의 것을 거래 전략이라고 합니다. EA를 작성하기 전에 먼저 자동화하려는 전략을 EA로 개발해야 합니다. 따라서 이 경우 EA로 개발하려는 전략을 반영하도록 위의 설명을 수정해보겠습니다.
- 기간이 8인 이동 평균이라는 인디케이터를 사용합니다 (기간을 선택할 수 있지만 전략상 8을 사용합니다).
- 우리는 EA가 이동 평균-8 (논의를 위해 MA-8이라고 칭함)이 상승하고 가격이 그 위에 가까워지면 롱 (매수) 거래를 하기를 원합니다. MA-8이 아래로 하락하고 가격이 그 아래에 근접할 때 숏 (매도)을 치게 됩니다.
- 또한 기간 8의 평균 방향 이동 (ADX)이라는 또 다른 인디케이터를 사용하여 시장이 추세인지 여부를 판단하는 데 도움이 될 것입니다. 우리는 시장이 추세일 때만 거래에 들어가고 시장이 범위에있을 때 (즉, 추세가 아닌) 긴장을 풀기를 원하기 때문에 이를 수행합니다. 이를 위해 위의 조건을 충족하고 ADX 값이 22보다 큰 경우에만 거래 (매수 또는 매도) 할 것입니다. ADX가 22보다 크지만 감소하거나 ADX가 22보다 작으면 조건 B가 충족 되더라도 거래하지 않습니다.
- 또한 손절매를 30 핍으로 설정하고 수익 목표를 설정하여 자신을 보호하고자 합니다. 우리는 100 핍의 이익을 목표로 할 것입니다.
- 우리는 또한 EA가 새로운 바가 형성된 경우에만 매수/매도 기회를 찾길 원하며 매수 조건이 충족되고 아직 열리지 않은 경우 매수 포지션을 열고 매도를 시작합니다.
또한, 우리는 거래를 할 때 사용할 수 있는 자유 마진의 비율을 통제 할 수 있는지 확인하고 또한 거래를 하기 전에 사용 가능한 마진을 확인하고 싶습니다. EA는 사용 가능한 마진이 거래에 충분한 경우에만 거래를 할 것입니다.
이제 우리가 원하는 것을 이해합니다. 클래스에 위임하고자 하는 함수는 다음과 같습니다.
- 매수 및 매도 조건 확인
- 확인된 조건의 결과에 따라 매수/매도
기본적으로 이것이 EA가 원하는 전부입니다. 이 두 가지 함수가 주요 함수가지만 여전히 더 있습니다. 예를 들어 매수/매도 포지션을 확인할 때 인디케이터를 사용해야 합니다. 이것은 인디케이터의 값을 얻는 것이 우리 클래스에도 있어야 함을 의미합니다. 따라서 다음을 포함합니다.
- 모든 인디케이터 핸들 가져 오기 (EA OnInit 섹션에서)
- 모든 인디케이터 버퍼 가져 오기 (EA OnTick 섹션에서)
- 모든 인디케이터 핸들을 해제합니다 (EA OnDeinit 섹션에서).
인디케이터 값을 얻으려면 클래스에서 MA 및 ADX 기간, 차트 기간 및 기호 (작업중인 통화 쌍)를 알아야하므로 다음도 포함해야 합니다.
- ADX 및 MA 기간과 차트 기간 및 기호와 같은 기타 중요한 매개 변수를 가져옵니다.
또한 거래하기 전에 여유 마진을 확인하기 위해
- 거래에 사용할 여유 증거금/계정 비율 확인
이것으로 우리는 이미 우리 클래스에 어떤 변수와 함수가 있어야하는지에 대한 아이디어를 가지고 있습니다.
좋아요, 내가 당신을 위해 생각했습니다. 코드를 작성할 때입니다.
2.1. 클래스 작성
먼저 MetaEditor를 시작하는 것으로 시작하겠습니다(이미 알고 계실 것입니다). MetaEditor가 열리면 New 도구 모음 또는 Ctrl+N을 클릭하여 새 MQL 문서를 시작할 수 있습니다. 이 마법사 윈도우에서, "포함"을 선택하고 다음 버튼을 클릭합니다.
그림 1. 새 MQL5 문서 시작
아래와 같이 파일 이름을 입력하고 마침을 클릭합니다.
그림 2. 새 문서 이름 지정
클래스는 EA 코드를 사용할 준비가 되면 포함될 포함 파일이 될 것이므로 include를 선택했습니다. 그렇기 때문에 입력 매개 변수를 입력 할 공간이 없습니다.
평소와 같이 편집기는 사용자가 수행하고 싶다고 생각하는 스켈레톤을 제공합니다.
시작하려면 "#property link…" 코드 줄 아래에있는 모든 항목을 삭제하십시오. 이제 이와 같은 것이 있어야 합니다.
//+------------------------------------------------------------------+ //| my_expert_class.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "http://www.mql5.com"
이제 클래스 선언을 작성하겠습니다. 클래스를 MyExpert라고 합니다.
//+------------------------------------------------------------------+ //| CLASS DECLARATION | //+------------------------------------------------------------------+ class MyExpert {
클래스 선언을 분석해보겠습니다. 선언은 클래스 이름으로 시작됩니다. 다음으로 우리는 클래스의 private 멤버를 선언했습니다.
전용 멤버:
//+------------------------------------------------------------------+ //| CLASS DECLARATION | //+------------------------------------------------------------------+ class MyExpert { //--- private members private: int Magic_No; // Expert Magic Number int Chk_Margin; // Margin Check before placing trade? (1 or 0) double LOTS; // Lots or volume to Trade double TradePct; // Percentage of Account Free Margin to trade double ADX_min; // ADX Minimum value int ADX_handle; // ADX Handle int MA_handle; // Moving Average Handle double plus_DI[]; // array to hold ADX +DI values for each bars double minus_DI[]; // array to hold ADX -DI values for each bars double MA_val[]; // array to hold Moving Average values for each bars double ADX_val[]; // array to hold ADX values for each bars double Closeprice; // variable to hold the previous bar closed price MqlTradeRequest trequest; // MQL5 trade request structure to be used for sending our trade requests MqlTradeResult tresult; // MQL5 trade result structure to be used to get our trade results string symbol; // variable to hold the current symbol name ENUM_TIMEFRAMES period; // variable to hold the current timeframe value string Errormsg; // variable to hold our error messages int Errcode; // variable to hold our error codes
앞에서 설명한 것처럼 이러한 전용 멤버 변수는 클래스 외부의 함수에서 액세스 할 수 없습니다. 대부분의 변수는 선언에서 매우 명확하므로 이에 대해 이야기하는데 시간을 낭비하지 않겠습니다.
그러나 토론에서 멤버 변수가 유효한 데이터 유형, 구조 또는 클래스가 될 수 있다고 언급 했음을 기억할 것입니다.
MqlTradeRequest 및 MqlTradeResults 유형의 선언을 통해 여기에서 작동하는 것을 볼 수 있다고 생각합니다.
생성자
//--- Public member/functions public: void MyExpert(); //Class Constructor
생성자는 입력 매개 변수를 사용하지 않습니다. 자신의 클래스를 작성할 때 이것을 명심하십시오.
멤버십 함수
//--- Public member/functions public: void MyExpert(); //Class Constructor void setSymbol(string syb){symbol = syb;} //function to set current symbol void setPeriod(ENUM_TIMEFRAMES prd){period = prd;} //function to set current symbol timeframe/period void setCloseprice(double prc){Closeprice=prc;} //function to set prev bar closed price void setchkMAG(int mag){Chk_Margin=mag;} //function to set Margin Check value void setLOTS(double lot){LOTS=lot;} //function to set The Lot size to trade void setTRpct(double trpct){TradePct=trpct/100;} //function to set Percentage of Free margin to use for trading void setMagic(int magic){Magic_No=magic;} //function to set Expert Magic number void setadxmin(double adx){ADX_min=adx;} //function to set ADX Minimum values
우리는 클래스가 함수를 수행하는데 필요한 중요한 변수를 설정할 수 있도록 이러한 멤버 함수를 정의했습니다. 이러한 함수를 사용하지 않으면 이러한 변수를 클래스에서 사용할 수 없습니다. 또한 알다시피, 우리는 이미 이러한 함수에 의해 설정되면 이러한 값을 보유할 해당 변수를 클래스에 선언했습니다.
주목해야 할 또 다른 사항은 클래스 선언 내에 이러한 멤버 함수를 정의했다는 것입니다. 앞서 설명했듯이 허용됩니다. 이는 곧 보게 될 다른 멤버 함수를 정의할 때 다시 정의할 필요가 없음을 의미합니다.
일반 함수와 마찬가지로 각 함수의 반환 값에 따라 올바른 데이터 유형의 매개 변수가 있습니다. 저는 이것이 당신에게 이상하지 않아야 한다고 믿습니다.
void doInit(int adx_period,int ma_period); //function to be used at our EA intialization void doUninit(); //function to be used at EA de-initializatio bool checkBuy(); //function to check for Buy conditions bool checkSell(); //function to check for Sell conditions void openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL, double TP,int dev,string comment=""); //function to open Buy positions void openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL, double TP,int dev,string comment=""); //function to open Sell positions
이 멤버 함수만 선언하고 정의하지는 않았습니다. 나중에 할 것이기 때문입니다. 이들은 클래스의 멤버 변수에 저장된 대부분의 값을 조작하는 함수이며 동시에 클래스의 주요 역할을 위한 함수를 형성합니다. 나중에 논의할 것입니다.
보호된 멤버십
이 멤버는 우리 클래스에서 파생된 모든 클래스에 상속됩니다. 이 클래스에서 다른 클래스를 파생시키지 않으려면 실제로 필요하지 않습니다. 개인 구성원으로 배치할 수도 있습니다. 이 작업은 이전에 클래스에 대해 논의한 다양한 문제를 이해할 수 있도록 하기 위한 것입니다.
//--- Protected members protected: void showError(string msg, int ercode); //function for use to display error messages void getBuffers(); //function for getting Indicator buffers bool MarginOK(); //function to check if margin required for lots is OK
이 세 가지 함수는 클래스 내부에도 매우 중요합니다. showError는 오류를 표시하고 getBuffers는 인디케이터 버퍼를 가져오는 데 사용됩니다. MarginOK는 포지션을 열기에 충분한 여유 마진이 있는지 확인합니다.
클래스 선언을 마치면 세미콜론을 잊지 마십시오. 매우 중요합니다.
}; // end of class declaration
클래스를 선언한 직후에 해야 할 일은 선언 섹션에 정의되지 않은 멤버 함수를 정의하는 것입니다.
//+------------------------------------------------------------------+ // Definition of our Class/member functions //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| This CLASS CONSTRUCTOR //| *Does not have any input parameters //| *Initilizes all the necessary variables //+------------------------------------------------------------------+ void MyExpert::MyExpert() { //initialize all necessary variables ZeroMemory(trequest); ZeroMemory(tresult); ZeroMemory(ADX_val); ZeroMemory(MA_val); ZeroMemory(plus_DI); ZeroMemory(minus_DI); Errormsg=""; Errcode=0; }
이것이 우리의 클래스 생성자입니다. 여기서는 클래스 이름과 멤버 함수 이름 사이에 이중 콜론 (::) (범위 연산자)을 사용했습니다. 우리가 말하려는 것은 다음과 같습니다.
이 멤버 함수를 클래스 선언 외부에서 정의하고 있지만 여전히 클래스 범위에 있습니다. 두 콜론 (범위 연산자) 앞에 이름이 오는 클래스의 멤버입니다.
입력 매개 변수가 없습니다. 이 시점에서 필요한 멤버 변수의 대부분을 초기화하고 이를 수행하기 위해 ZeroMemory 함수를 사용합니다.
void ZeroMemory(
void & variable // reset variable
);
이 함수는 전달된 변수의 값을 재설정합니다. 이 경우 구조 유형 (MqlTradeRequest 및 MqlTradeResult) 및 배열의 값을 재설정하는 데 사용합니다.
showError 함수:
//+------------------------------------------------------------------+ //| SHOWERROR FUNCTION //| *Input Parameters - Error Message, Error Code //+------------------------------------------------------------------+ void MyExpert::showError(string msg,int ercode) { Alert(msg,"-error:",ercode,"!!"); // display error }
이것은 우리 클래스의 모든 객체의 작업 중에 발생한 모든 오류를 표시하는데 사용되는 보호된 멤버 함수입니다. 두 개의 인수/매개 변수가 필요합니다 – 오류 설명 및 오류 코드.
getBuffers 함수:
//+------------------------------------------------------------------+ //| GETBUFFERS FUNCTION //| *No input parameters //| *Uses the class data members to get indicator's buffers //+------------------------------------------------------------------+ void MyExpert::getBuffers() { if(CopyBuffer(ADX_handle,0,0,3,ADX_val)<0 || CopyBuffer(ADX_handle,1,0,3,plus_DI)<0 || CopyBuffer(ADX_handle,2,0,3,minus_DI)<0 || CopyBuffer(MA_handle,0,0,3,MA_val)<0) { Errormsg="Error copying indicator Buffers"; Errcode = GetLastError(); showError(Errormsg,Errcode); } }
이 함수는 모든 인디케이터 버퍼를 각 인디케이터 핸들을 사용하여 멤버 변수에 지정한 배열로 복사하는 데 사용됩니다.
CopyBuffer 함수는 첫 번째 글에서 설명했습니다. getBuffers 함수는 클래스의 멤버 변수 값을 사용하기 때문에 입력 매개 변수가 없습니다.
여기에서 내부 오류 함수를 사용하여 버퍼를 복사하는 과정에서 발생할 수 있는 오류를 표시했습니다.
MarginOK 함수:
//+------------------------------------------------------------------+ //| MARGINOK FUNCTION //| *No input parameters //| *Uses the Class data members to check margin required to place a trade //| with the lot size is ok //| *Returns TRUE on success and FALSE on failure //+------------------------------------------------------------------+ bool MyExpert::MarginOK() { double one_lot_price; //Margin required for one lot double act_f_mag = AccountInfoDouble(ACCOUNT_FREEMARGIN); //Account free margin long levrage = AccountInfoInteger(ACCOUNT_LEVERAGE); //Leverage for this account double contract_size = SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE); //Total units for one lot string base_currency = SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE); //Base currency for currency pair // if(base_currency=="USD") { one_lot_price=contract_size/levrage; } else { double bprice= SymbolInfoDouble(symbol,SYMBOL_BID); one_lot_price=bprice*contract_size/levrage; } // Check if margin required is okay based on setting if(MathFloor(LOTS*one_lot_price)>MathFloor(act_f_mag*TradePct)) { return(false); } else { return(true); } }
이 함수는 실제로 두 가지 작업을 수행합니다. 그것은 우리가 거래를하기에 충분한 여유 마진이 있는지 확인하고 또한 우리가 거래를 하는 데 사용 가능한 여유 마진의 지정된 비율 이상을 사용하지 않는지 확인합니다. 이런 식으로 우리는 각 거래에 사용하는 돈을 제어 할 수 있습니다.
AccountInfoDouble() 함수를 ENUM_ACCOUNT_INFO_DOUBLE 식별자와 함께 사용하여 계정의 여유 증거금을 얻습니다. 또한 AccountInfoInteger() 함수를 ENUM_ACCOUNT_INFO_INTEGER 식별자와 함께 사용하여 계정에 대한 레버리지를 얻습니다. 그만큼 AccountInfoInteger() 및 AccountInfoDouble()은 EA를 사용하여 현재 계정의 세부 정보를 가져 오는데 사용되는 계정 함수입니다.
double AccountInfoDouble( int property_id // identifier of the property );
또한 기호 속성 함수 SymbolInfoDouble() 및 SymbolInfoString()을 사용하여 각각 현재 기호 (통화 쌍)에 대한 계약 크기 및 기본 통화를 가져 왔습니다. SymbolInfoDouble() 함수는 기호 이름과 ENUM_SYMBOL_INFO_DOUBLE 식별자를 매개 변수로 사용하고 SymbolInfoString() 함수는 기호 이름과 ENUM_SYMBOL_INFO_STRING 식별자를 매개 변수로 사용합니다. 이러한 함수의 결과는 각 데이터 유형에 대해 선언된 변수에 저장됩니다.
double SymbolInfoDouble( string name, // symbol int prop_id // identifier of the property );
여기서 수행한 계산은 매우 간단합니다.
거래에 필요한 마진을 얻기 위해 두 가지 상황을 고려합니다.
- 기본 통화는 USD (USD/CAD, USD/CHF, USD/JPY 등)입니다.
필요한 증거금 = 랏 당 계약 규모/레버리지
2. 기본 통화가 USD가 아닙니다 (EUR/USD 등).
필요한 증거금 = 기호의 현재 가격 * 랏/레버리지 당 계약 규모.
이제 지정된 랏 크기 또는 거래량을 거래하는 데 필요한 마진이 거래에 사용하려는 여유 마진의 백분율보다 큰지 확인하기로 결정했습니다. 필요한 증거금이 적으면 함수는 TRUE를 반환하고 거래가 배치됩니다. 그렇지 않으면 FALSE를 반환하고 거래가 배치되지 않습니다.
doInit 함수:
//+-----------------------------------------------------------------------+ // OUR PUBLIC FUNCTIONS | //+-----------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| DOINIT FUNCTION //| *Takes the ADX indicator's Period and Moving Average indicator's //| period as input parameters //| *To be used in the OnInit() function of our EA //+------------------------------------------------------------------+ void MyExpert::doInit(int adx_period,int ma_period) { //--- Get handle for ADX indicator ADX_handle=iADX(symbol,period,adx_period); //--- Get the handle for Moving Average indicator MA_handle=iMA(symbol,period,ma_period,0,MODE_EMA,PRICE_CLOSE); //--- What if handle returns Invalid Handle if(ADX_handle<0 || MA_handle<0) { Errormsg="Error Creating Handles for indicators"; Errcode=GetLastError(); showError(Errormsg,Errcode); } // Set Arrays as series // the ADX values arrays ArraySetAsSeries(ADX_val,true); // the +DI value arrays ArraySetAsSeries(plus_DI,true); // the -DI value arrays ArraySetAsSeries(minus_DI,true); // the MA values arrays ArraySetAsSeries(MA_val,true); }
조만간 작성할 EA의 OnInit() 함수에서 사용할 공용 함수이며 두 가지 작업을 수행할 예정입니다.
먼저 인디케이터에 대한 핸들을 설정하고 배열 변수에 대해 array-set-as-series 작업을 수행합니다. EA 코드 내에서 제공되는 두 개의 입력 매개 변수가 있습니다.
doUninit 함수:
//+------------------------------------------------------------------+ //| DOUNINIT FUNCTION //| *No input parameters //| *Used to release ADX and MA indicators handleS | //+------------------------------------------------------------------+ void MyExpert::doUninit() { //--- Release our indicator handles IndicatorRelease(ADX_handle); IndicatorRelease(MA_handle); }
이 함수는 또한 우리가 사용한 인디케이터에 대한 모든 핸들을 해제하기 위해 EA의 UnDeInit 함수에서 사용될 공용 멤버 함수입니다. 입력 매개 변수가 없습니다.
The checkBuy 함수:
//+------------------------------------------------------------------+ //| CHECKBUY FUNCTION //| *No input parameters //| *Uses the class data members to check for Buy setup based on the //| the defined trade strategy //| *Returns TRUE if Buy conditions are met or FALSE if not met //+------------------------------------------------------------------+ bool MyExpert::checkBuy() { /* Check for a Long/Buy Setup : MA increasing upwards, previous price close above MA, ADX > ADX min, +DI > -DI */ getBuffers(); //--- Declare bool type variables to hold our Buy Conditions bool Buy_Condition_1=(MA_val[0]>MA_val[1]) && (MA_val[1]>MA_val[2]); // MA Increasing upwards bool Buy_Condition_2=(Closeprice>MA_val[1]); // previous price closed above MA bool Buy_Condition_3=(ADX_val[0]>ADX_min); // Current ADX value greater than minimum ADX value bool Buy_Condition_4=(plus_DI[0]>minus_DI[0]); // +DI greater than -DI //--- Putting all together if(Buy_Condition_1 && Buy_Condition_2 && Buy_Condition_3 && Buy_Condition_4) { return(true); } else { return(false); } }
매수 조건이 설정되었는지 확인하는 함수입니다. 이것이 반환 유형이 bool인 이유입니다. TRUE 또는 FALSE를 반환한다는 의미입니다. 여기에서 매수 거래 전략을 정의했습니다. 우리가 정의한 전략에 따라 매수 조건이 충족되면 TRUE를 반환합니다. 그러나 매수 조건이 충족되지 않으면 FALSE를 반환합니다. 코드에서 이 함수를 사용할 때 TRUE를 반환하면 매수를 합니다.
여기서 가장 먼저 한 일은 내부 멤버 함수 getBuffers()를 호출하는 것입니다. 이 함수는 checkBuy 함수에 필요한 모든 배열 값을 해당 배열 변수에 복사합니다.
여기에 코딩 된 조건은 첫 번째 글에 설명되어 있습니다.
checkSell 함수:
//+------------------------------------------------------------------+ //| CHECKSELL FUNCTION //| *No input parameters //| *Uses the class data members to check for Sell setup based on the //| the defined trade strategy //| *Returns TRUE if Sell conditions are met or FALSE if not met //+------------------------------------------------------------------+ bool MyExpert::checkSell() { /* Check for a Short/Sell Setup : MA decreasing downwards, previous price close below MA, ADX > ADX min, -DI > +DI */ getBuffers(); //--- Declare bool type variables to hold our Sell Conditions bool Sell_Condition_1=(MA_val[0]<MA_val[1]) && (MA_val[1]<MA_val[2]); // MA decreasing downwards bool Sell_Condition_2=(Closeprice <MA_val[1]); // Previous price closed below MA bool Sell_Condition_3=(ADX_val[0]>ADX_min); // Current ADX value greater than minimum ADX bool Sell_Condition_4=(plus_DI[0]<minus_DI[0]); // -DI greater than +DI //--- Putting all together if(Sell_Condition_1 && Sell_Condition_2 && Sell_Condition_3 && Sell_Condition_4) { return(true); } else { return(false); } }
checkBuy와 마찬가지로 이 함수는 매도 조건이 설정되었는지 여부를 확인하는데 사용됩니다. 이것이 반환 유형도 bool인 이유입니다. TRUE 또는 FALSE를 반환함을 의미합니다. 여기에서 매도 거래 전략을 정의했습니다. 우리가 정의한 전략에 따라 매도 조건이 충족되면 TRUE를 반환합니다. 그러나 매도 조건이 충족되지 않으면 FALSE를 반환합니다.
코드에서이 함수를 사용할 때 TRUE를 반환하면 매도를 합니다. checkBuy에서와 마찬가지로 먼저 내부 함수 getBuffers() 를 호출했습니다. 여기에 코딩 된 조건은 첫 번째 도움말에서도 설명되었습니다.
openBuy 함수:
//+------------------------------------------------------------------+ //| OPENBUY FUNCTION //| *Has Input parameters - order type, Current ASK price, Stop Loss, //| Take Profit, deviation, comment //| *Checks account free margin before pacing trade if trader chooses //| *Alerts of a success if position is opened or shows error //+------------------------------------------------------------------+ void MyExpert::openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,double TP,int dev,string comment="") { //--- do check Margin if enabled if(Chk_Margin==1) { if(MarginOK()==false) { Errormsg= "You do not have enough money to open this Position!!!"; Errcode =GetLastError(); showError(Errormsg,Errcode); } else { trequest.action=TRADE_ACTION_DEAL; trequest.type=otype; trequest.volume=LOTS; trequest.price=askprice; trequest.sl=SL; trequest.tp=TP; trequest.deviation=dev; trequest.magic=Magic_No; trequest.symbol=symbol; trequest.type_filling=ORDER_FILLING_FOK; // send OrderSend(trequest,tresult); // check result if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed { Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!"); } else { Errormsg= "The Buy order request could not be completed"; Errcode =GetLastError(); showError(Errormsg,Errcode); } } } else { trequest.action=TRADE_ACTION_DEAL; trequest.type=otype; trequest.volume=LOTS; trequest.price=askprice; trequest.sl=SL; trequest.tp=TP; trequest.deviation=dev; trequest.magic=Magic_No; trequest.symbol=symbol; trequest.type_filling=ORDER_FILLING_FOK; //--- send OrderSend(trequest,tresult); //--- check result if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed { Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!"); } else { Errormsg= "The Buy order request could not be completed"; Errcode =GetLastError(); showError(Errormsg,Errcode); } } }
EA에서 호출 될 때마다 매수 포지션을 여는 함수입니다. 입력 매개 변수로서 거래를 하는 데 필요한 대부분의 변수가 있습니다. 일부 변수는 EA 코드에서 제공합니다. 첫 번째 글에서 설명한 것처럼 여기에서 MqlTraderequest 유형 변수를 사용했음을 알 수 있습니다.
EA 코드에서 사용할 필요가 없습니다. 거래가 시작되기 전에 사용자가 마진을 확인하고 싶은지 확인하고, Chk_Margin (EA에서 획득)의 값이 1이면 이를 위해 MarginOK() 함수를 호출합니다. 이 함수의 결과는 수행할 다음 단계를 결정합니다. 그러나 사용자가 마진 확인을 원하지 않으면 계속해서 거래를 합니다.
openSell 함수:
//+------------------------------------------------------------------+ //| OPENSELL FUNCTION //| *Has Input parameters - order type, Current BID price, Stop Loss, //| Take Profit, deviation, comment //| *Checks account free margin before pacing trade if trader chooses //| *Alerts of a success if position is opened or shows error //+------------------------------------------------------------------+ void MyExpert::openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,double TP,int dev,string comment="") { //--- do check Margin if enabled if(Chk_Margin==1) { if(MarginOK()==false) { Errormsg= "You do not have enough money to open this Position!!!"; Errcode =GetLastError(); showError(Errormsg,Errcode); } else { trequest.action=TRADE_ACTION_DEAL; trequest.type=otype; trequest.volume=LOTS; trequest.price=bidprice; trequest.sl=SL; trequest.tp=TP; trequest.deviation=dev; trequest.magic=Magic_No; trequest.symbol=symbol; trequest.type_filling=ORDER_FILLING_FOK; // send OrderSend(trequest,tresult); // check result if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed { Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!"); } else { Errormsg= "The Sell order request could not be completed"; Errcode =GetLastError(); showError(Errormsg,Errcode); } } } else { trequest.action=TRADE_ACTION_DEAL; trequest.type=otype; trequest.volume=LOTS; trequest.price=bidprice; trequest.sl=SL; trequest.tp=TP; trequest.deviation=dev; trequest.magic=Magic_No; trequest.symbol=symbol; trequest.type_filling=ORDER_FILLING_FOK; //--- send OrderSend(trequest,tresult); //--- check result if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed { Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!"); } else { Errormsg= "The Sell order request could not be completed"; Errcode =GetLastError(); showError(Errormsg,Errcode); } } }
openBuy 함수와 마찬가지로 이 함수는 EA에서 호출될 때마다 매도 포지션을 엽니다. 입력 매개 변수로서 거래를 하는 데 필요한 대부분의 변수가 있습니다. 일부 변수는 EA 코드에서 제공합니다.
매수 포지션을 열 때와 마찬가지로 거래가 시작되기 전에 사용자가 마진을 확인하고 싶은지 확인하고, EA에서 획득 할 Chk_Margin의 값이 1이면 이를 위해 MarginOK() 함수를 호출합니다.
이 함수의 결과는 수행할 다음 단계를 결정합니다. 그러나 사용자가 마진 확인을 원하지 않으면 계속해서 거래를 합니다.
이제 클래스와 멤버 함수의 선언과 정의를 마쳤지만 EA 코드에서 처리하려는 다른 작업은 생략했습니다. 여기에는 사용 가능한 바 확인, 새로운 바 확인 및 사용 가능한 열린 위치 확인이 포함됩니다. EA 코드에서 처리됩니다.
우리 클래스의 모든 함수와 메소드의 목록을 보려면 아래 그림과 같이 MetaEditor의 functions 명령/메뉴를 클릭하십시오. 이 함수는 코드에서 명시적으로 선언하지 않은 Destructor를 포함한 모든 멤버 함수를 표시합니다.
보호된 멤버는 녹색 화살표로 가리키고 생성자와 소멸자는 파란색 화살표로 가리킵니다.
그림 3. 클래스 소멸자를 보여주는 클래스 멤버 함수
그래서 다음은 무엇입니까?
디버그라는 말을 들었나요? 항상 테스트하고 코드에 오류가 있는지 확인하는 것이 좋습니다. 그렇지 않으면 공개 할 때 실망하게 됩니다. 여기서 문제는 이것이 단지 포함 파일이며, 차트에 첨부 할 수있는 Expert Advisor 코드 나 스크립트 또는 인디케이터 코드가 아니라는 것입니다. 이 시점에서 두 가지 옵션이 있습니다 (제 경험상).,
- .mqh 파일 때문에 표시되는 '실행 파일이 생성되지 않음' 오류를 제외하고 디버거가 코드의 오류를 보고하도록 편집기의 디버그 버튼을 누를 위험이 있습니다.ex5 파일로 컴파일 할 수 없습니다. 또는
- 계속해서 클래스를 사용할 EA 코드를 작성하십시오. EA 디버깅을 시작하면 포함 된 파일도 함께 확인됩니다. 사실, 이것이 최선의 방법이고 가장 수용 가능한 방법입니다.
그림 4. .mqh 파일을 컴파일 할 수 없습니다.
2.2. Expert Advisor 작성
편집자가 아직 열려있는 것 같습니다. 새 문서를 다시 시작하지만 이번에는 Expert Advisor를 선택합니다. 을(자세한 내용은 첫 번째 글을 참조하십시오). 하지만 이번에는 EA 이름을 ‘my_oop_ea’로 지정합니다.
이것이 당신이 지금 있어야 할 곳입니다.
이제 OOP 기반 EA를 작성할 준비가 되었습니다.
여기서 가장 먼저 할 일은 #include 전 처리기 명령을 사용하여 방금 작성한 클래스를 포함하는 것입니다. 마지막 전 처리기 property 명령 바로 뒤에 클래스를 포함합니다.
//+------------------------------------------------------------------+ //| my_oop_ea.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" // Include our class #include <my_expert_class.mqh>
파일을 포함하는 방법에는 두 가지가 있습니다.
// Include using angle brackets #include <my_expert_class.mqh> // Include using quotations #include "my_expert_class.mqh"
꺾쇠 괄호 (<...>)를 사용하면 포함 할 파일이 표준 포함 디렉터리 (즉, MQL5 디렉터리 내의 포함 폴더)에서 가져옴을 의미합니다. 현재 디렉토리 (MQL5 디렉토리 내의 Experts 폴더는 파일을 찾을 수 있는 위치로 간주되지 않음). 그러나 파일이 따옴표 ( "...")로 묶인 경우 파일은 현재 디렉터리 (Experts 폴더)에 있는 것으로 간주되며 표준 디렉터리 (Include 폴더)는 확인되지 않습니다.
클래스가 Include 폴더 (표준 디렉터리)에 저장되고 꺾쇠 괄호 대신 따옴표를 사용하거나 그 반대의 경우 코드를 컴파일 할 때 오류가 발생합니다.
그림 5. 포함 파일을 찾을 수 없을 때 표시되는 오류 메시지
EA 입력 매개 변수
//--- input parameters input int StopLoss=30; // Stop Loss input int TakeProfit=100; // Take Profit input int ADX_Period=14; // ADX Period input int MA_Period=10; // Moving Average Period input int EA_Magic=12345; // EA Magic Number input double Adx_Min=22.0; // Minimum ADX Value input double Lot=0.2; // Lots to Trade input int Margin_Chk=0; // Check Margin before placing trade(0=No, 1=Yes) input double Trd_percent=15.0; // Percentage of Free Margin To use for Trading
여기에 있는 대부분의 입력 매개 변수는 새로운 것이 아닙니다. 새로운 것에 대해 논의합시다.
여백 확인을 사용하려면 1 값을, 사용하지 않으면 0 값을 유지하는 정수 변수를 도입했습니다. 또한 포지션 개시에 사용되는 최대 여유 마진 비율을 유지하는 또 다른 변수를 선언했습니다. 이 값은 나중에 생성될 때 클래스 개체에서 사용됩니다.
입력 매개 변수 바로 다음에는 입력 변수의 값을 변경할 수 없기 때문에 조작 할 수 있는 다른 두 매개 변수 (STP 및 TKP)를 정의합니다 (5 자리 및 3 자리 가격을 제공하기 위해). 그런 다음 EA 코드 내에서 사용할 클래스의 객체를 만듭니다.
//--- Other parameters int STP,TKP; // To be used for Stop Loss & Take Profit values // Create an object of our class MyExpert Cexpert;
앞에서 설명한대로 클래스의 객체를 생성하려면 클래스 이름과 생성하려는 객체의 이름을 차례로 사용합니다. 여기에서 MyExpert 클래스 유형인 Cexpert 개체를 만들었습니다. 이제 Cexpert를 사용하여 MyExpert 클래스의 모든 공용 멤버 함수에 액세스 할 수 있습니다.
EA 초기화 섹션
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Run Initialize function Cexpert.doInit(ADX_Period,MA_Period); //--- Set all other necessary variables for our class object Cexpert.setPeriod(_Period); // sets the chart period/timeframe Cexpert.setSymbol(_Symbol); // sets the chart symbol/currency-pair Cexpert.setMagic(EA_Magic); // sets the Magic Number Cexpert.setadxmin(Adx_Min); // sets the ADX miniumm value Cexpert.setLOTS(Lot); // set the Lots value Cexpert.setchkMAG(Margin_Chk); // set the margin check variable Cexpert.setTRpct(Trd_percent); // set the percentage of Free Margin for trade //--- Let us handle brokers that offers 5 digit prices instead of 4 STP = StopLoss; TKP = TakeProfit; if(_Digits==5 || _Digits==3) { STP = STP*10; TKP = TKP*10; } //--- return(0); }
이 시점에서 우리는 클래스의 doInit 함수를 호출하고 여기에 ADX 및 MA 기간 변수를 전달합니다. 다음으로 방금 만든 객체에 필요한 다른 모든 변수를 설정하여 클래스를 작성할 때 이미 설명한 함수를 사용하여 객체의 멤버 변수에 저장되도록합니다.
다음 코드 줄은 이상하지 않아야 합니다. 3 자리 및 5 자리 가격에 대한 손절매 및 이익 실현 값을 조정하기로 결정했습니다.
EA 비 초기화 섹션
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Run UnIntilialize function Cexpert.doUninit(); }
EA 초기화 함수에서 생성되어야 하는 모든 인디케이터 핸들을 해제하기 위해 클래스의 doUninit 함수를 호출했습니다.
EA ONTICK 섹션
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Do we have enough bars to work with int Mybars=Bars(_Symbol,_Period); if(Mybars<60) // if total bars is less than 60 bars { Alert("We have less than 60 bars, EA will now exit!!"); return; } //--- Define some MQL5 Structures we will use for our trade MqlTick latest_price; // To be used for getting recent/latest price quotes MqlRates mrate[]; // To be used to store the prices, volumes and spread of each bar /* Let's make sure our arrays values for the Rates is store serially similar to the timeseries array */ // the rates arrays ArraySetAsSeries(mrate,true);
여기서 가장 먼저 할 일은 사용 가능한 총 바를 확인하는 것입니다. EA가 거래하기에 충분하다면, 그렇지 않으면 충분한 바 (즉, 60 바)가 있을 때까지 거래되지 않을 것입니다. 그런 다음 MQL5 구조 (MqlTick 및 MqlRates)의 두 변수를 선언했습니다. 마지막으로 요율 배열에 ArraySetAsSeries 함수를 사용합니다.
//--- Get the last price quote using the MQL5 MqlTick Structure if(!SymbolInfoTick(_Symbol,latest_price)) { Alert("Error getting the latest price quote - error:",GetLastError(),"!!"); return; } //--- Get the details of the latest 3 bars if(CopyRates(_Symbol,_Period,0,3,mrate)<0) { Alert("Error copying rates/history data - error:",GetLastError(),"!!"); return; } //--- EA should only check for new trade if we have a new bar // lets declare a static datetime variable static datetime Prev_time; // lest get the start time for the current bar (Bar 0) datetime Bar_time[1]; // copy time Bar_time[0] = mrate[0].time; // We don't have a new bar when both times are the same if(Prev_time==Bar_time[0]) { return; } //copy time to static value, save Prev_time = Bar_time[0];
여기서는 SymbolInfoTick 함수를 사용하여 최신 가격 견적을 얻었고 CopyRates 를 사용하여 지난 세 개의 바 (현재 바 포함한)를 얻었습니다. 다음 코드 줄은 새로운 바가 있는지 확인합니다. 두 개의 datetime 변수를 선언했습니다. 하나는 정적 변수 (Prev_Time)이고 다른 하나는 Bar_Time입니다.
새로운 바가있는 경우 바 시간은 정적 변수 Prev_Time에 저장되어 다음 틱에서 해당 값과 Bar_Time 값을 비교할 수 있습니다. . 다음 틱에서 Prev_Time이 Bar_Time과 같으면 시간이 저장된 동일한 바입니다. 따라서 EA는 긴장을 풀 것입니다.
그러나 Bar_Time이 Prev_Time과 같지 않으면 새로운 바가 있습니다. 새로운 바 시작 시간을 정적 datetime 변수인 Prev_Time에 저장하기로 결정했으며 이제 EA는 새로운 매수 또는 매도 기회를 확인할 수 있습니다.
//--- we have no errors, so continue //--- Do we have positions opened already? bool Buy_opened = false, Sell_opened=false; // variables to hold the result of the opened position if (PositionSelect(_Symbol) ==true) // we have an opened position { if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { Buy_opened = true; //It is a Buy } else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { Sell_opened = true; // It is a Sell } }
우리는 이미 열린 포지션이 있는지 확인하기로 결정합니다. 우리는 매수가 열리지 않았을 때 매수 거래를 시작하고 매도가 열리지 않을때 매도 거래를 열었는지 확인하고 싶습니다.
// Copy the bar close price for the previous bar prior to the current bar, that is Bar 1 Cexpert.setCloseprice(mrate[1].close); // bar 1 close price //--- Check for Buy position if(Cexpert.checkBuy()==true) { // Do we already have an opened buy position if(Buy_opened) { Alert("We already have a Buy Position!!!"); return; // Don't open a new Buy Position } double aprice = NormalizeDouble(latest_price.ask,_Digits); // current Ask price double stl = NormalizeDouble(latest_price.ask - STP*_Point,_Digits); // Stop Loss double tkp = NormalizeDouble(latest_price.ask + TKP*_Point,_Digits); // Take profit int mdev = 100; // Maximum deviation // place order Cexpert.openBuy(ORDER_TYPE_BUY,aprice,stl,tkp,mdev); }
이제 우리가 만든 개체로 돌아 왔습니다. 왜 그런가요? 객체가 작업을 수행하는 데 필요한 모든 필요한 검사를 할 수 있었기 때문입니다.
가장 먼저 할 일은 개체 멤버 함수 setCloseprice를 사용하여 이전 바의 마감 가격을 가져 오는 것입니다.
그런 다음 checkBuy 함수를 호출하여 매수 조건이 설정되었는지 확인하고 TRUE를 반환하면 이미 열린 매수 포지션이 없는지 확인하려고 합니다. 이미 개설 된 매수 포지션이없는 경우 주문에 사용할 필수 변수 (주문 유형, 현재 ASK 가격, 손절매, 이익 실현 및 )를 준비합니다. 최대 편차)를 입력하고 openBuy 함수를 호출합니다. 우리가 작성한 클래스를 사용하는 것이 얼마나 쉬운지 보십시오.
//--- Check for any Sell position if(Cexpert.checkSell()==true) { // Do we already have an opened Sell position if(Sell_opened) { Alert("We already have a Sell position!!!"); return; // Don't open a new Sell Position } double bprice=NormalizeDouble(latest_price.bid,_Digits); // Current Bid price double bstl = NormalizeDouble(latest_price.bid + STP*_Point,_Digits); // Stop Loss double btkp = NormalizeDouble(latest_price.bid - TKP*_Point,_Digits); // Take Profit int bdev=100; // Maximum deviation // place order Cexpert.openSell(ORDER_TYPE_SELL,bprice,bstl,btkp,bdev); }
이것은 위에서 한 것과 동일합니다. 매도를 확인하고 있으므로 checkSell 함수를 호출하고 TRUE를 반환하고 아직 매도 포지션을 개설하지 않은 경우 주문에 필요한 변수를 준비합니다 (주문 유형, 현재 ASK 가격, 손절매, 이익 실현 및 최대 편차)를 입력 한 다음 openSell 함수를 호출합니다.
쉽죠? 코드 작성이 끝났습니다. 이제 코드를 디버깅할 시간입니다. 디버거 사용 방법을 모르면 첫 번째 글을 읽어보십시오.
F5를 누르거나 디버그 버튼을 누르면 포함된 파일 (우리 클래스)이 포함되어 검사되며 오류가 있으면 보고합니다. 오류가 표시되면 코드로 돌아가 오류를 수정해야 합니다.
그림 6. 기본 EA 코드를 디버깅 할 때 포함 파일이 포함됩니다.
모든 것이 괜찮다면 잘한 것입니다. 이제 전략 테스터를 사용하여 EA를 테스트 할 때입니다. 전략 테스터로 테스트하기 전에 EA를 컴파일해야 합니다. 이렇게 하려면 컴파일 버튼을 클릭하거나 컴퓨터 키보드에서 F7을 누르십시오.
그림 7. 컴파일 메뉴 버튼을 클릭하여 코드를 컴파일 합니다.
거래 터미널 메뉴 바에서 View-> Strategy Tester로 이동하거나 CONTROL+R을 눌러 전략 테스터를 시작하세요. (테스터 사용 방법에 대한 자세한 내용은 첫 번째 글을 참조하십시오).
전략 테스터로 EA를 테스트 할 수 있으려면 먼저 컴파일해야 합니다. 컴파일하지 않으면 전략 테스터의 설정 표시 줄에서 Expert Advisor를 선택하면 오류가 발생합니다. (저는 새 버전의 터미널에서 이것을 발견했습니다.)
그림 8. EA 코드는 Strategy Tester에서 사용하기 전에 컴파일해야 합니다.
아래에서 OOP 기반 Expert Advisor에 대한 전략 테스터의 결과를 찾으십시오.
그림 9. Object Oriented Expert Advisor의 거래 결과
그래프:
그림 10. Object Oriented Expert Advisor의 그래프 결과
무역 활동 보고서/저널:
그림 11. Object Oriented Expert Advisor의 거래 활동 결과
테스트 차트:
그림 12. Object Oriented Expert Advisor에 대한 거래 차트 결과
결론
이 글에서는 클래스의 기본 사항과 간단한 Expert Advisor를 작성하는 데 사용하는 방법에 대해 어느 정도 논의했습니다. 우리는 클래스의 고급 영역에 대해 너무 많이 탐구하지는 않았지만 이 글에서 논의한 내용은 자신만의 객체 지향 Expert Advisor 코드를 작성할 수 있는 수준으로 개발하는 데 도움이 될만큼 충분합니다.
또한 사용 가능한 여유 마진이 오픈하려는 포지션에 충분하지 않을 때 EA가 거래하지 않도록 여유 마진을 확인하는 방법에 대해서도 논의했습니다.
이제 새로운 MQL5 언어가 제공 할 것이 훨씬 더 많으며 이 새로운 언어를 활용하기 위해 프로그래밍 전문가가 될 필요가 없다는 데 동의 할 것입니다. 이것이 단계별 가이드를 작성하는 주된 이유입니다.
MetaQuotes 소프트웨어 사를 통해 영어가 번역됨
원본 기고글: https://www.mql5.com/en/articles/116
![특정 매직 넘버에 의한 총 포지션 볼륨 계산을 위한 최적 방법](https://c.mql5.com/2/0/calculate_volume_MQL5__1.png)
![Named Pipes를 사용하여 MetaTrader 5 터미널 간 통신을 위한 DLL없는 솔루션](https://c.mql5.com/2/0/Named_Pipes_MetaTrader5.png)
![TesterWithdrawal() 함수를 이용한 수익 인출 모델링](https://c.mql5.com/2/0/TesterWithdrawal_MQL5.png)
![Google Chart API를 통해 차트를 구성하는 라이브러리](https://c.mql5.com/2/0/Google_Chart_API_MQL5__1.png)