English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
바보도 할 수 있는 MQL: 객체 클래스 디자인 및 생성 방법

바보도 할 수 있는 MQL: 객체 클래스 디자인 및 생성 방법

MetaTrader 5전문 어드바이저 | 5 7월 2021, 13:25
841 0
Sergey Pavlov
Sergey Pavlov

객체 지향 프로그래밍(OOP) 소개

'바보들'이 자주 하는 질문: 절차적 프로그래밍에 대한 지식이 거의 없는데 OOP를 사용한 자동 트레이딩 전략 개발이 가능한가요? 아니면 일반 사용자는 못 하는 건가요?

대부분의 경우, 객체 지향 프로그래밍 원칙을 사용하지 않아도 객체 지향 언어를 이용해 MQL5 엑스퍼트 어드바이저 또는 인디케이터를 작성할 수 있습니다. 게다가 반드시 새로운 기능을 사용해야 하는 건 아닙니다. 가장 간단하다고 생각되는 방법을 선택하세요. OOP를 더 사용한다고 해서 트레이딩 로봇의 수익성이 올라가는 건 아니니까요.

그러나 새로운 접근법(객체 지향)으로의 전환은 보다 복잡한 수식의 적용을 가능케 하며, 외부 변화에 민감하면서 시장과 동시에 움직이는 엑스퍼트 어드바이저를 만들 수 있게 해 줍니다.

OOP가 무엇을 기반으로 하는지 살펴볼게요.

  1. 이벤트
  2. 객체 클래스

이벤트는 OOP의 토대가 됩니다. 프로그램의 전체 로직이 계속해서 발생하는 이벤트에 대한 처리를 기반으로 하니까요. 이벤트에 대한 반응은 클래스를 통해 정의됩니다. 즉, 클래스 객체는 이벤트의 흐름을 인터셉트하거나 처리하는 역할을 하는 것이죠.

OOP의 두 번째 토대는 클래스인데요. 클래스는 다음의 '세 개의 기둥'이 받쳐 줍니다.

  1. 캡슐화-'블랙박스' 원칙을 기반으로 하는 클래스 보호. 객체는 이벤트에 반응하나 실제 구현 여부는 알 수 없음. 
  2. 상속-'조상' 클래스의 속성 및 메소드를 유지하면서 기존의 클래스에서 새로운 클래스를 생성.
  3. 다형성-'자손' 클래스에 상속된 구현 변경.

기본 컨셉들은 엑스퍼트 어드바이저 코드에 가장 잘 나타나 있습니다.

이벤트

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()                        // OnInit event processing
  {
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)     // OnDeInit event processing
  {
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()                       // OnTick event processing
  {
  }
//+------------------------------------------------------------------+
//| Expert Timer function                                            |
//+------------------------------------------------------------------+
void OnTimer()                      // OnTimer event processing
  {
  }
//+------------------------------------------------------------------+
//| Expert Chart event function                                      |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,     // OnChartEvent event processing
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
  }

객체 클래스

class CNew:public CObject
  {
private:
   int               X,Y;
   void              EditXY();
protected:
   bool              on_event;      //events processing flag
public:
   // Class constructor
   void              CNew();
   // OnChart event processing method
   virtual void      OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam);
  };

캡슐화

private:
   int               X,Y;
   void              EditXY();

상속

class CNew: public CObject

다형성

   // OnChart event processing method
   virtual void      OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam);

해당 메소드의 가상 수정자는 OnEvent 핸들러가 오버라이드 될 수 있음을 의미합니다. 이 경우 메소드 이름은 조상 클래스의 이름과 동일합니다.

2. 클래스 디자인

OOP의 가장 큰 강점 중 하나는 확장이 가능하다는 것입니다. 즉, 기존의 시스템을 수정하지 않고도 새로운 요소를 추가할 수 있다는 것이죠. 이 단계에서 새 속성이 추가됩니다.

MQL5 디자인 MasterWindows 클래스 프로그램을 작성한다고 생각하면 됩니다.

2.1. 1단계: 프로젝트 초안

디자인 과정은 종이에 연필로 스케치를 하면서 시작됩니다. 프로그래밍 과정 중 가장 많은 고민을 하게 되지만 그만큼 재밌기도 합니다. 프로그램과 사용자(인터페이스) 간의 작용 뿐 아니라 데이터 처리 구조도 생각해야 합니다. 그러다 보면 하루가 더 걸릴 수도 있죠. 먼저 인터페이스 작업을 하는 것이 좋습니다. 알고리즘 구조를 본질적으로 규정해 줄 수도 있거든요.

프로그램의 다이얼로그로는 윈도우 애플리케이션 윈도우(그림 1 참고)와 비슷한 형식을 쓸 겁니다. 라인, 셀, 그리고 그래픽 객체의 셀로 이루어지죠. 덕분에 컨셉 디자인 단계에서부터 프로그램 구조와 객체 분류를 확인할 수 있죠.

그림 1. 클래스 생성자 형식(스케치)

그림 1. 클래스 생성자 형식(스케치)

행과 셀(필드)의 개수가 참 많은데요. 사실 OBJ_EDITOBJ_BUTTON 단 두 가지 그래픽 개체로 이루어져 있습니다 . 따라서, 디자인이나 프로그램에서 생성된 구조, 혹은 기본 객체를 정하고 나면 디자인 초안이 완성됐다고 보고 다음 단계로 넘어갑니다..

2.2 2단계: 기초 클래스 디자인

현재 다음의 세 가지 클래스가 있으며, 필요 시 차후에 추가 가능합니다.

  • 셀 클래스 CCell
  • CCell 클래스 셀로 이루어진 로(row) 클래스 CRow
  • CRow 클래스 라인으로 이루어진 윈도우 클래스 CWin

바로 프로그래밍을 시작할 수도 있겠지만 아직 해결해야 할 문제가 하나 있습니다. 바로 클래스 객체 간의 데이터 교환이죠. MQL5에는 이러한 경우를 위해 structure 변수가 새롭게 포함되었습니다 . 물론 현재 단계에서는 연결 관계도 다 알 수 없고 계산도 어렵죠. 따라서 프로그래밍을 진행하면서 구조 및 클래스에 대한 값을 입력하도록 하겠습니다. 이는 OOP 원칙이 오히려 권장하는 것이기도 하죠.

WinCell 구조

struct WinCell
  {
   color             TextColor;     // text color
   color             BGColor;       // background color
   color             BGEditColor;   // background color while editing
   ENUM_BASE_CORNER  Corner;         // anchor corner
   int               H;            // cell height
   int               Corn;         // displacement direction (1;-1)
  };

문자열 또는 동적 배열 객체가 없는 구조는 단순 구조라고 합니다. 이런 유형의 구조의 변수는 자유롭게 복사될 수 있습니다. 다른 구조로도 말이죠. MQL5가 제공하는 구조가 바로 이런 형식입니다. 얼마나 효과적인지는 나중에 확인해 보죠.

베이스 클래스 CCell

//+------------------------------------------------------------------+
//| CCell base class                                                 |
//+------------------------------------------------------------------+
class CCell
  {
private:
protected:
   bool              on_event;      // event processing flag
   ENUM_OBJECT       type;           // cell type
public:
   WinCell           Property;     // cell property
   string            name;          // cell name
   //+---------------------------------------------------------------+
   // Class constructor
   void              CCell();
   virtual     // Draw method
   void              Draw(string m_name,
                          int m_xdelta,
                          int m_ydelta,
                          int m_bsize);
   virtual     // Event processing method
   void              OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam);
  };

베이스 클래스 CRow

//+------------------------------------------------------------------+
//| CRow base class                                                  |
//+------------------------------------------------------------------+
class CRow
  {
protected:
   bool              on_event;      // event processing flag
public:
   string            name;          // row name
   WinCell           Property;     // row property
   //+---------------------------------------------------------------+
   // Class constructor
   void              CRow();
   virtual     // Draw method
   void              Draw(string m_name,
                          int m_xdelta,
                          int m_ydelta,
                          int m_bsize);
   virtual     // Event processing method
   void              OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam);
  };

베이스 클래스 CWin

//+------------------------------------------------------------------+
//| Base CWin class (WINDOW)                                         |
//+------------------------------------------------------------------+
class CWin
  {
private:
   void              SetXY(int m_corner); //Coordinates
protected:
   bool              on_event;   // event processing flag
public:
   string            name;       // window name
   int               w_corner;   // window corner
   int               w_xdelta;   // vertical delta
   int               w_ydelta;   // horizontal detla
   int               w_xpos;     // X coordinate
   int               w_ypos;     // Y coordinate
   int               w_bsize;    // Window width
   int               w_hsize;    // Window height
   int               w_h_corner; // hide mode corner
   WinCell           Property;   // Property
   //---
   CRowType1         STR1;       // CRowType1
   CRowType2         STR2;       // CRowType2
   CRowType3         STR3;       // CRowType3
   CRowType4         STR4;       // CRowType4
   CRowType5         STR5;       // CRowType5
   CRowType6         STR6;       // CRowType6
   //+---------------------------------------------------------------+
   // Class constructor
   void              CWin();
   // Set window properties
   void              SetWin(string m_name,
                            int m_xdelta,
                            int m_ydelta,
                            int m_bsize,
                            int m_corner);
   virtual     // Draw window method
   void              Draw(int &MMint[][3],
                          string &MMstr[][3],
                          int count);
   virtual     // OnEventTick handler
   void              OnEventTick();
   virtual     // OnChart event handler method
   void              OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam);
  };

설명 및 조언

  • 이 프로젝트의 모든 베이스 클래스에는 이벤트 처리 메소드가 포함되어 있습니다. 이벤트를 인터셉트하거나 전송하는 데에 사용되죠. 이벤트 수신 및 전송 메커니즘이 없으면 프로그램(모듈)은 상호 작용을 하지 못합니다.
  • 베이스 클래스를 만들 때는 최대한 적은 수의 메소드를 사용하도록 해 보세요. 그런 다음 다양한 확장 메소드를 자손 클래스에서 구현하면 생성된 객체의 기능이 강화됩니다.
  • 절대 다른 클래스 내부 데이터에 직접 액세스하려고 하지 마세요!

2.3. 3단계: 프로젝트 작업

이제 조금씩 프로그램을 생성하기 시작합니다. 프레임워크부터 시작해서 함수를 입력하고 변수 값을 채울 겁니다. 동시에 최적화 코드를 이용한 디버깅을 실행하고 에러를 찾아 내면서 프로그램의 정확성을 확인하겠습니다.

여기서 잠시 거의 모든 프로그램에 적용할 수 있는 프레임워크 생성 기술을 생각해 봅시다. 가장 중요한 조건은 즉시 작동할(컴파일 중 에러 없이 바로 실행) 수 있어야 한다는 것이겠죠. 이런 것까지 모두 고려한 MQL5 개발자들은 MQL5 마법사를 이용해 엑스퍼트 어드바이저 템플릿을 프레임워크로 사용할 것을 권장합니다.

우리가 만든 템플릿을 예로 들어 볼게요.

1) 프로그램=엑스퍼트 어드바이저

//+------------------------------------------------------------------+
//|                                                MasterWindows.mq5 |
//|                                                 Copyright DC2008 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "DC2008"
#property link      "http://www.mql5.com"
#property version   "1.00"
//--- include files with classes
#include <ClassMasterWindows.mqh>
//--- Main module declaration
CMasterWindows    MasterWin;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Launch of the main module
   MasterWin.Run();
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Deinitialization of the main module
   MasterWin.Deinit();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- call OnTick event handler of main module
   MasterWin.OnEventTick();
  }
//+------------------------------------------------------------------+
//| Expert Event function                                            |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- call OnChartEvent handler of main module
   MasterWin.OnEvent(id,lparam,dparam,sparam);
  }

엑스퍼트 어드바이저의 완성된 코드입니다. 아무 것도 수정하거나 추가할 필요가 없습니다!

2) 메인 모듈=클래스

모든 메인 및 보조 모듈은 여기서부터 개발됩니다. 이러한 접근법은 복잡한 멀티 모듈 프로젝트의 프로그래밍에 도움을 주며 에러 탐지 또한 용이하게 해 주죠. 하지만 에러를 찾는 것은 매우 어렵습니다. '버그'를 찾는 것보다 프로젝트를 그냥 새로 작성하는 게 더 쉽고 빠를 때도 있을 정도니까요.

//+------------------------------------------------------------------+
//|                                           ClassMasterWindows.mqh |
//|                                                 Copyright DC2008 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "DC2008"
#property link      "http://www.mql5.com"
//+------------------------------------------------------------------+
//| Main module: CMasterWindows class                                |
//+------------------------------------------------------------------+
class CMasterWindows
  {
protected:
   bool              on_event;   // event processing flag
public:
   // Class constructor
   void              CMasterWindows();
   // Method of launching the main module (core algorithm)
   void              Run();
   // Deinitialization method
   void              Deinit();
   // OnTick event processing method
   void              OnEventTick();
   // OnChartEvent event processing method
   void              OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam);
  };

아래는 클래스 메인 메소드의 대략적인 초기 디스크립션입니다.

//+------------------------------------------------------------------+
//| CMasterWindows class constructor                                 |
//+------------------------------------------------------------------+
void CMasterWindows::CMasterWindows()
   {
//--- class members initialization
   on_event=false;   // disable events processing
   }
//+------------------------------------------------------------------+
//| Метод запуска главного модуля (основной алгоритм)                |
//+------------------------------------------------------------------+
void CMasterWindows::Run()
   {
//--- Main functional of the class: runs additional modules
   ObjectsDeleteAll(0,0,-1);
   Comment("MasterWindows for MQL5     © DC2008");
//---
   on_event=true;   // enable events processing
   }
//+------------------------------------------------------------------+
//| Deinitialization method                                          |
//+------------------------------------------------------------------+
void CMasterWindows::Deinit()
   {
//--- 
   ObjectsDeleteAll(0,0,-1);
   Comment("");
   }
//+------------------------------------------------------------------+
//| OnTick() event processing method                                 |
//+------------------------------------------------------------------+
void CMasterWindows::OnEventTick()
   {
   if(on_event) // event processing is enabled
     {
     //---
     }
   }
//+------------------------------------------------------------------+
//| OnChartEvent() event processing method                           |
//+------------------------------------------------------------------+
void CMasterWindows::OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam)
  {
   if(on_event) // event processing is enabled
     {
     //---
     }
  }

3) 기본 및 파생 클래스 라이브러리

라이브러리에 포함되는 파생 클래스의 수에는 제한이 없으며 베이스 클래스에 있는 별도의 파일에 파생 클래스를 저장하는 것이 좋습니다. 그렇게 하면 수정이나 추가가 용이해지고 에러를 찾는 일도 쉬워지죠.

이제 프로그램 프레임워크가 완성되었군요. 제대로 작동하는지 한번 봅시다. 컴파일 후 실행합니다. 결과가 성공적이라면 이제 추가 모듈을 만들 수 있습니다.

우선 파생 클래스를 연결시켜 봅시다. 셀부터 시작할게요.

클래스 이름
이미지
 CCellText

 CCellEdit

 CCellButton

 CCellButtonType

1. 셀 클래스 라이브러리

CCellButton 형식의 단일 파생 클래스 생성 과정을 살펴봅시다. 이 클래스는 다양한 형식의 버튼을 만듭니다.

//+------------------------------------------------------------------+
//| CCellButtonType class                                            |
//+------------------------------------------------------------------+
class CCellButtonType:public CCell
  {
public:
   ///Class constructor
   void              CCellButtonType();
   virtual     ///Draw method
   void              Draw(string m_name,
                          int m_xdelta,
                          int m_ydelta,
                          int m_type);
  };
//+------------------------------------------------------------------+
//| CCellButtonType class constructor                                |
//+------------------------------------------------------------------+
void CCellButtonType::CCellButtonType()
  {
   type=OBJ_BUTTON;
   on_event=false;   //disable events processing
  }
//+------------------------------------------------------------------+
//| CCellButtonType class Draw method                                |
//+------------------------------------------------------------------+
void CCellButtonType::Draw(string m_name,
                           int m_xdelta,
                           int m_ydelta,
                           int m_type)
  {
//--- creating an object with specified name
   if(m_type<=0) m_type=0;
   name=m_name+".Button"+(string)m_type;
   if(ObjectCreate(0,name,type,0,0,0,0,0)==false)
      Print("Function ",__FUNCTION__," error ",GetLastError());
//--- object properties initializartion
   ObjectSetInteger(0,name,OBJPROP_COLOR,Property.TextColor);
   ObjectSetInteger(0,name,OBJPROP_BGCOLOR,Property.BGColor);
   ObjectSetInteger(0,name,OBJPROP_CORNER,Property.Corner);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xdelta);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ydelta);
   ObjectSetInteger(0,name,OBJPROP_XSIZE,Property.H);
   ObjectSetInteger(0,name,OBJPROP_YSIZE,Property.H);
   ObjectSetInteger(0,name,OBJPROP_SELECTABLE,0);
   if(m_type==0) // Hide button
     {
      ObjectSetString(0,name,OBJPROP_TEXT,CharToString(MIN_WIN));
      ObjectSetString(0,name,OBJPROP_FONT,"Webdings");
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,12);
     }
   if(m_type==1) // Close button
     {
      ObjectSetString(0,name,OBJPROP_TEXT,CharToString(CLOSE_WIN));
      ObjectSetString(0,name,OBJPROP_FONT,"Wingdings 2");
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8);
     }
   if(m_type==2) // Return button
     {
      ObjectSetString(0,name,OBJPROP_TEXT,CharToString(MAX_WIN));
      ObjectSetString(0,name,OBJPROP_FONT,"Webdings");
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,12);
     }
   if(m_type==3) // Plus button
     {
      ObjectSetString(0,name,OBJPROP_TEXT,"+");
      ObjectSetString(0,name,OBJPROP_FONT,"Arial");
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,10);
     }
   if(m_type==4) // Minus button
     {
      ObjectSetString(0,name,OBJPROP_TEXT,"-");
      ObjectSetString(0,name,OBJPROP_FONT,"Arial");
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,13);
     }
   if(m_type==5) // PageUp button
     {
      ObjectSetString(0,name,OBJPROP_TEXT,CharToString(PAGE_UP));
      ObjectSetString(0,name,OBJPROP_FONT,"Wingdings 3");
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8);
     }
   if(m_type==6) // PageDown button
     {
      ObjectSetString(0,name,OBJPROP_TEXT,CharToString(PAGE_DOWN));
      ObjectSetString(0,name,OBJPROP_FONT,"Wingdings 3");
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,8);
     }
   if(m_type>6) // empty button
     {
      ObjectSetString(0,name,OBJPROP_TEXT,"");
      ObjectSetString(0,name,OBJPROP_FONT,"Arial");
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,13);
     }
   on_event=true;   //enable events processing
  }
//+------------------------------------------------------------------+

보충 설명

  • 클래스 생성자로 이벤트를 처리하지 마세요. 객체를 준비하고 다른 이벤트들의 방해를 막으려면 그래야 합니다. 필요한 연산이 끝나고 나면 해당 처리 과정이 허락될 것이고 객체는 완전한 기능을 수행하게 될 겁니다.
  • 그리기 메소드는 내부 데이터를 이용해 외부 데이터를 받습니다. 따라서 데이터 처리에 앞서 우선 컴플라이언스가 확인되어야 합니다. 하지만 이번만은 컴플라이언스 테스트를 하지 않겠습니다. 왜냐고요? 클래스 객체가 이병이라고 생각해 보세요. 이병이 장군의 계획을 알 필요는 없죠. 명령을 분석하고 개별 결정을 내리는 게 아니라 명령을 정확하고, 빠르고, 철저하게 따르는 것이 그들의 임무이죠. 따라서 모든 외부 데이터는 해당 데이터 클래스와의 작업에 앞서 컴파일 과정을 거쳐야 합니다.

그 다음으로는 전체 셀 라이브러리를 검사해야 하는데요. 다음의 코드를 메인 모듈에 입력(테스트가 진행될 동안만)하고 엑스퍼트 어드바이저를 실행합니다.

//--- include file with classes
#include <ClassUnit.mqh>
//+------------------------------------------------------------------+
//| Main module: CMasterWindows class                                |
//+------------------------------------------------------------------+
class CMasterWindows
  {
protected:
   bool              on_event;   // events processing flag
   WinCell           Property;   // cell property
   CCellText         Text;
   CCellEdit         Edit;
   CCellButton       Button;
   CCellButtonType   ButtonType;
public:
   // Class constructor
   void              CMasterWindows();
   // Main module run method (core algorithm)
   void              Run();
   // Deinitialization method
   void              Deinit();
   // OnTick event processing method
   void              OnEventTick();
   // OnChart event processing method
   void              OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam);
  };
//+------------------------------------------------------------------+
//| Main module run method (core algorithm)                          |
//+------------------------------------------------------------------+
void CMasterWindows::Run()
  {
//--- core algorithm - it launches additional modules
   ObjectsDeleteAll(0,0,-1);
   Comment("MasterWindows for MQL5     © DC2008");
//--- Text field
   Text.Draw("Text",50,50,150,"Text field");
//--- Edit field
   Edit.Draw("Edit",205,50,150,"default value",true);
//--- LARGE BUTTON
   Button.Draw("Button",50,80,200,"LARGE BUTTON");
//--- Hide button
   ButtonType.Draw("type0",50,100,0);
//--- Close button
   ButtonType.Draw("type1",70,100,1);
//--- Return  button
   ButtonType.Draw("type2",90,100,2);
//--- Plus button
   ButtonType.Draw("type3",110,100,3);
//--- Minus button
   ButtonType.Draw("type4",130,100,4);
//--- None button
   ButtonType.Draw("type5",150,100,5);
//--- None button
   ButtonType.Draw("type6",170,100,6);
//--- None button
   ButtonType.Draw("type7",190,100,7);
//---
   on_event=true;   // enable events processing
  }

결과로 나타나는 클래스로 이벤트를 이전하는 것도 잊지 마세요! 이벤트를 이동시키지 않으면 프로젝트 핸들링이 굉장히 어려워집니다. 불가능해질 수도 있고요.

//+------------------------------------------------------------------+
//| CMasterWindows class OnChart event processing method             |
//+------------------------------------------------------------------+
void CMasterWindows::OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam)
  {
   if(on_event) // event processing is enabled
     {
      //--- process events for the cell class objects
      Text.OnEvent(id,lparam,dparam,sparam);
      Edit.OnEvent(id,lparam,dparam,sparam);
      Button.OnEvent(id,lparam,dparam,sparam);
      ButtonType.OnEvent(id,lparam,dparam,sparam);
     }
  }

이제 셀 클래스 라이브러리 내 객체에 대한 모든 옵션을 확인할 수 있습니다.

그림 2. 셀 클래스 라이브러리

그림 2. 셀 클래스 라이브러리

효율성과 이벤트에 대한 객체 반응을 확인해 봅니다.

  • 편집 필드에 디폴트 값 대신 다른 변수를 입력합니다. 다른 값이 나타나면 테스트가 성공한 것입니다.
  • 버튼을 한번 누르면 다음에 누를 때까지 계속 눌린 채로 있는데요. 이건 우리가 원하는 바가 아닙니다. 버튼이 한번 눌리고 나면 자동으로 제자리로 복귀하도록 만들어야 합니다. 바로 여기에서 OOP의 역량이 드러납니다. 상속 기능이죠. 프로그램에는 12개가 넘는 버튼이 사용되고 있을 수도 있습니다. 하지만 각각의 버튼에 원하는 기능을 추가할 필요는 없죠. CCell 베이스 클래스를 수정하기만 하면 모든 파생 클래스 객체가 제대로 기능하게 되니까요!
//+------------------------------------------------------------------+
//| CCell class OnChart event processing method                      |
//+------------------------------------------------------------------+
void CCell::OnEvent(const int id,
                    const long &lparam,
                    const double &dparam,
                    const string &sparam)
  {
   if(on_event) // event processing is enabled
     {
      //--- button click event
      if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,".Button",0)>0)
        {
         if(ObjectGetInteger(0,sparam,OBJPROP_STATE)==1)
           {
            //--- if button stays pressed
            Sleep(TIME_SLEEP);
            ObjectSetInteger(0,sparam,OBJPROP_STATE,0);
            ChartRedraw();
           }
        }
     }
  }

이제 클래스 셀 라이브러리가 프로젝트에 연결됐습니다.

다음은 라인 라이브러리를 추가할 차례입니다.

 클래스 이름
이미지
 CRowType1 (0)

 CRowType1 (1)

 CRowType1 (2)

 CRowType1 (3)

 CRowType2

 CRowType3

 CRowType4

 CRowType5

 CRowType6

2. 라인 클래스 라이브러리

동일한 방법으로 테스트를 진행합니다. 테스트가 완료되면 다음 과정으로 넘어갑니다.

2.4 4단계: 프로젝트 구축

이제 필요한 모듈이 모두 준비되었습니다. 이제 프로젝트를 구축할 차례입니다. 우선 캐스케이드를 생성합니다. 그림 1에 나타난 모양의 창에 기능(예: 미래 이벤트에 대한 요소와 모듈의 반응)을 설정합니다.

그러려면 프로그램에 사용할 프레임이 있어야 하고, 메인 모듈도 준비해야 하죠. 그럼 시작합시다. CWin 베이스 클래스의 '상속' 클래스 가운데 하나인데요. 따라서 '조상' 클래스의 모든 퍼블릿 메소드와 필드가 상속되어 전달된 상태입니다. 따라서 몇 가지 메소드만 오버라이드하면 새로운 CMasterWindows 클래스가 생성되죠.

//--- include files with classes
#include <ClassWin.mqh>
#include <InitMasterWindows.mqh>
#include <ClassMasterWindowsEXE.mqh>
//+------------------------------------------------------------------+
//| CMasterWindows class                                             |
//+------------------------------------------------------------------+
class CMasterWindows:public CWin
  {
protected:
   CMasterWindowsEXE WinEXE;     // executable module
public:
   void              Run();      // Run method
   void              Deinit();   // Deinitialization method
   virtual                       // OnChart event processing method
   void              OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam);
  };
//+------------------------------------------------------------------+
//| CMasterWindows class deinitialization method                     |
//+------------------------------------------------------------------+
void CMasterWindows::Deinit()
  {
//---(delete all objects)
   ObjectsDeleteAll(0,0,-1);
   Comment("");
  }
//+------------------------------------------------------------------+
//| CMasterWindows class Run method                                  |
//+------------------------------------------------------------------+
void CMasterWindows::Run()
  {
   ObjectsDeleteAll(0,0,-1);
   Comment("MasterWindows for MQL5     © DC2008");
//--- creating designer window and launch executable object
   SetWin("CWin1",1,30,250,CORNER_RIGHT_UPPER);
   Draw(Mint,Mstr,21);
   WinEXE.Init("CWinNew",30,18);
   WinEXE.Run();
  }
//+------------------------------------------------------------------+
//| CMasterWindows class event processing method                     |
//+------------------------------------------------------------------+
void CMasterWindows::OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam)
  {
   if(on_event) // event processing is enabled
     {
      //--- Close button click in the main window
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,"CWin1",0)>=0
         && StringFind(sparam,".Button1",0)>0)
        {
         ExpertRemove();
        }
      //--- OnChart event processing for all objects
      STR1.OnEvent(id,lparam,dparam,sparam);
      STR2.OnEvent(id,lparam,dparam,sparam);
      STR3.OnEvent(id,lparam,dparam,sparam);
      STR4.OnEvent(id,lparam,dparam,sparam);
      STR5.OnEvent(id,lparam,dparam,sparam);
      STR6.OnEvent(id,lparam,dparam,sparam);
      WinEXE.OnEvent(id,lparam,dparam,sparam);
     }
  }

메인 모듈은 애플리케이션 창 생성만 담당하므로 꽤 작습니다. 메인 모듈이 컨트롤을 실행 가능한 WinEXE 모듈에 전달하면 아주 흥미로운 일이 일어납니다. 들어오는 이벤트에 대한 반응이죠.

앞서 단순한 WinCell 구조를 만들어 객체 간의 데이터 교환을 해 보았는데요. 이제 이 접근법의 장점을 확실히 알게 됐습니다. 구조 내 모든 멤버 변수를 복사하는 과정은 매우 합리적이고 간단하죠.

   STR1.Property = Property;
   STR2.Property = Property;
   STR3.Property = Property;
   STR4.Property = Property;
   STR5.Property = Property;
   STR6.Property = Property;

클래스 디자인은 여기서 마치고 새로운 클래스 생성 과정을 가속화하는 시각적 기술에 대해 알아봅시다..

3. 클래스 그래픽 디자인

MQL5 디자인 MasterWindows 모드를 사용하면 클래스를 훨씬 빠르게 구축하고 훨씬 쉽게 시각화를 할 수 있습니다.


그림 3. 그래픽 디자인 프로세스

그림 3. 그래픽 디자인 프로세스

이제 개발자는 MasterWindows 형식을 이용해 창을 그리고 이벤트에 대한 리엑션을 결정하기만 하면 됩니다. 코드는 자동으로 생성되죠. 끝이군요! 프로젝트가 완성되었습니다.

그림 4에 CMasterWindows 클래스 코드와 엑스퍼트 어드바이저의 예제가 나타나 있습니다(파일 위치는 ...\MQL5\Files)

//****** Project (Expert Advisor): project1.mq5
//+------------------------------------------------------------------+
//|        Code has been generated by MasterWindows Copyright DC2008 |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "DC2008"
//--- include files with classes
#include <ClassWin.mqh>
int Mint[][3]=
  {
     {1,0,0},
     {2,100,0},
     {1,100,0},
     {3,100,0},
     {4,100,0},
     {5,100,0},
     {6,100,50},
     {}
  };
string Mstr[][3]=
  {
     {"New window","",""},
     {"NEW1","new1",""},
     {"NEW2","new2",""},
     {"NEW3","new3",""},
     {"NEW4","new4",""},
     {"NEW5","new5",""},
     {"NEW6","new6",""},
     {}
  };
//+------------------------------------------------------------------+
//| CMasterWindows class (main unit)                                 |
//+------------------------------------------------------------------+
class CMasterWindows:public CWin
  {
private:
   long              Y_hide;          // Window shift vertical in hide mode
   long              Y_obj;           // Window shift vertical
   long              H_obj;           // Window shift horizontal
public:
   bool              on_hide;         // HIDE mode flag
   CArrayString      units;           // Main window lines
   void              CMasterWindows() {on_event=false; on_hide=false;}
   void              Run();           // Run method
   void              Hide();          // Hide method
   void              Deinit()         {ObjectsDeleteAll(0,0,-1); Comment("");}
   virtual void      OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam);
  };
//+------------------------------------------------------------------+
//| CMasterWindows class Run method                                  |
//+------------------------------------------------------------------+
void CMasterWindows::Run()
  {
   ObjectsDeleteAll(0,0,-1);
   Comment("Code has been generated by MasterWindows for MQL5 © DC2008");
//--- creating main window and launch executable module
   SetWin("project1.Exp",50,100,250,CORNER_LEFT_UPPER);
   Draw(Mint,Mstr,7);
  }
//+------------------------------------------------------------------+
//| CMasterWindows class Hide method                                 |
//+------------------------------------------------------------------+
void CMasterWindows::Hide()
  {
   Y_obj=w_ydelta;
   H_obj=Property.H;
   Y_hide=ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,0)-Y_obj-H_obj;;
//---
   if(on_hide==false)
     {
      int n_str=units.Total();
      for(int i=0; i<n_str; i++)
        {
         long y_obj=ObjectGetInteger(0,units.At(i),OBJPROP_YDISTANCE);
         ObjectSetInteger(0,units.At(i),OBJPROP_YDISTANCE,(int)y_obj+(int)Y_hide);
         if(StringFind(units.At(i),".Button0",0)>0)
            ObjectSetString(0,units.At(i),OBJPROP_TEXT,CharToString(MAX_WIN));
        }
     }
   else
     {
      int n_str=units.Total();
      for(int i=0; i<n_str; i++)
        {
         long y_obj=ObjectGetInteger(0,units.At(i),OBJPROP_YDISTANCE);
         ObjectSetInteger(0,units.At(i),OBJPROP_YDISTANCE,(int)y_obj-(int)Y_hide);
         if(StringFind(units.At(i),".Button0",0)>0)
            ObjectSetString(0,units.At(i),OBJPROP_TEXT,CharToString(MIN_WIN));
        }
     }
//---
   ChartRedraw();
   on_hide=!on_hide;
  }
//+------------------------------------------------------------------+
//| CMasterWindows class OnChartEvent event processing method        |
//+------------------------------------------------------------------+
void CMasterWindows::OnEvent(const int id,
                             const long &lparam,
                             const double &dparam,
                             const string &sparam)
  {
   if(on_event // event handling is enabled
      && StringFind(sparam,"project1.Exp",0)>=0)
     {
      //--- call of OnChartEvent handlers
      STR1.OnEvent(id,lparam,dparam,sparam);
      STR2.OnEvent(id,lparam,dparam,sparam);
      STR3.OnEvent(id,lparam,dparam,sparam);
      STR4.OnEvent(id,lparam,dparam,sparam);
      STR5.OnEvent(id,lparam,dparam,sparam);
      STR6.OnEvent(id,lparam,dparam,sparam);
      //--- creating graphic object
      if(id==CHARTEVENT_OBJECT_CREATE)
        {
         if(StringFind(sparam,"project1.Exp",0)>=0) units.Add(sparam);
        }
      //--- edit [NEW1] in Edit STR1
      if(id==CHARTEVENT_OBJECT_ENDEDIT
         && StringFind(sparam,".STR1",0)>0)
        {
        //--- event processing code
        }
      //--- edit [NEW3] : Plus button STR3
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".STR3",0)>0
         && StringFind(sparam,".Button3",0)>0)
        {
        //--- event processing code
        }
      //--- edit [NEW3] : Minus button STR3
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".STR3",0)>0
         && StringFind(sparam,".Button4",0)>0)
        {
        //--- event processing code
        }
      //--- edit [NEW4] : Plus button STR4
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".STR4",0)>0
         && StringFind(sparam,".Button3",0)>0)
        {
        //--- event processing code
        }
      //--- edit [NEW4] : Minus button STR4
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".STR4",0)>0
         && StringFind(sparam,".Button4",0)>0)
        {
        //--- event processing code
        }
      //--- edit [NEW4] : Up button STR4
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".STR4",0)>0
         && StringFind(sparam,".Button5",0)>0)
        {
        //--- event processing code
        }
      //--- edit [NEW4] : Down button STR4
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".STR4",0)>0
         && StringFind(sparam,".Button6",0)>0)
        {
        //--- event processing code
        }
      //--- [new5] button click STR5
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".STR5",0)>0
         && StringFind(sparam,".Button",0)>0)
        {
        //--- event processing code
        }
      //--- [NEW6] button click STR6
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".STR6",0)>0
         && StringFind(sparam,"(1)",0)>0)
        {
        //--- event processing code
        }
      //--- [new6] button click STR6
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".STR6",0)>0
         && StringFind(sparam,"(2)",0)>0)
        {
        //--- event processing code
        }
      //--- button click [] STR6
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".STR6",0)>0
         && StringFind(sparam,"(3)",0)>0)
        {
        //--- event processing code
        }
      //--- Close button click in the main window
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".Button1",0)>0)
        {
         ExpertRemove();
        }
      //--- Hide button click in the main window
      if(id==CHARTEVENT_OBJECT_CLICK
         && StringFind(sparam,".Button0",0)>0)
        {
         Hide();
        }
     }
  }
//--- Main module declaration
CMasterWindows MasterWin;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- launch main module
   MasterWin.Run();
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- main module deinitialization
   MasterWin.Deinit();
  }
//+------------------------------------------------------------------+
//| Expert Event function                                            |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- call OnChartEvent event handler
   MasterWin.OnEvent(id,lparam,dparam,sparam);
  }

이를 실행하면 다음과 같은 창이 나타납니다.

그림 4. 어드바이저 프로젝트1-클래스 그래픽 디자인 결과

그림 4. 어드바이저 프로젝트1-클래스 그래픽 디자인 결과

결론

  1. 클래스는 차근차근 고안되어야 합니다. 클래스를 모듈로 분해함으로써 각각에 대한 별도의 클래스가 생성됩니다. 모듈은 파생 클래스 또는 베이스 클래스의 마이크로모듈로 다시 나뉘죠.
  2. 베이스 클래스에 너무 많은 메소드를 담으려고 하지 마세요. 최소한만 포함하는 것이 좋습니다.
  3. 디자인 도구를 사용한 클래스 디자인은 매우 쉽죠. '바보'에게도 쉽습니다. 코드가 자동으로 생성되니까요.

첨부 파일 위치

  • masterwindows.mq5-...\MQL5\Experts\
  • 기타 파일-...\MQL5\Include\

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

유전 알고리즘-쉬워요 유전 알고리즘-쉬워요
이 글에서는 저자가 직접 개발한 유전 알고리즘을 이용한 진화 연산에 대해 이야기합니다. 예제를 이용해 알고리즘의 기능을 설명하고, 실제 적용 가능한 경우를 설명합니다.
뉴비들을 위한 복합 인디케이터 버퍼 만들기 뉴비들을 위한 복합 인디케이터 버퍼 만들기
복잡한 코드는 여러 개의 간단한 코드로 이루어집니다. 익숙한 코드들이라면 별로 복잡해 보이지 않죠. 이 글에서는 복수 개의 인디케이터 버퍼를 이요한 인디케이터 작성법을 알아보겠습니다. 아룬 인디케이터를 예시로 분석했으며, 두 가지 코드가 포함되어 있습니다.
다양한 대륙과 시간대에 맞춘 거래 전략의 예시 다양한 대륙과 시간대에 맞춘 거래 전략의 예시
인터넷에서 웹서핑을 하다 보면 다양한 추천, 그리고 많은 전략을 쉽게 찾을 수 있습니다. 각 대륙의 시간대 차이를 바탕으로 내부자의 접근 방식을 통해 전략 수립 프로세스에 대해 알아보겠습니다.
MetaTrader5와 MATLAB의 상호 작용 MetaTrader5와 MATLAB의 상호 작용
이 글은 MetaTrader5와 MATLAB 패키지 사이의 상호 작용에 대한 설명입니다. 데이터 변환 메커니즘과 MATLAB 데스크톱과 상호 작용이 가능한 범용 라이브러리 개발 과정에 대해 살펴볼 겁니다. MATLAB 환경에서 생성된 DLL의 사용법도 알아보겠습니다. 이 글은 C++와 MQL5를 이미 알고 있는 숙련된 프로그래머들을 위해 작성되었습니다.