
바보도 할 수 있는 MQL: 객체 클래스 디자인 및 생성 방법
객체 지향 프로그래밍(OOP) 소개
'바보들'이 자주 하는 질문: 절차적 프로그래밍에 대한 지식이 거의 없는데 OOP를 사용한 자동 트레이딩 전략 개발이 가능한가요? 아니면 일반 사용자는 못 하는 건가요?
대부분의 경우, 객체 지향 프로그래밍 원칙을 사용하지 않아도 객체 지향 언어를 이용해 MQL5 엑스퍼트 어드바이저 또는 인디케이터를 작성할 수 있습니다. 게다가 반드시 새로운 기능을 사용해야 하는 건 아닙니다. 가장 간단하다고 생각되는 방법을 선택하세요. OOP를 더 사용한다고 해서 트레이딩 로봇의 수익성이 올라가는 건 아니니까요.
그러나 새로운 접근법(객체 지향)으로의 전환은 보다 복잡한 수식의 적용을 가능케 하며, 외부 변화에 민감하면서 시장과 동시에 움직이는 엑스퍼트 어드바이저를 만들 수 있게 해 줍니다.
OOP가 무엇을 기반으로 하는지 살펴볼게요.
- 이벤트
- 객체 클래스
이벤트는 OOP의 토대가 됩니다. 프로그램의 전체 로직이 계속해서 발생하는 이벤트에 대한 처리를 기반으로 하니까요. 이벤트에 대한 반응은 클래스를 통해 정의됩니다. 즉, 클래스 객체는 이벤트의 흐름을 인터셉트하거나 처리하는 역할을 하는 것이죠.
OOP의 두 번째 토대는 클래스인데요. 클래스는 다음의 '세 개의 기둥'이 받쳐 줍니다.
- 캡슐화-'블랙박스' 원칙을 기반으로 하는 클래스 보호. 객체는 이벤트에 반응하나 실제 구현 여부는 알 수 없음.
- 상속-'조상' 클래스의 속성 및 메소드를 유지하면서 기존의 클래스에서 새로운 클래스를 생성.
- 다형성-'자손' 클래스에 상속된 구현 변경.
기본 컨셉들은 엑스퍼트 어드바이저 코드에 가장 잘 나타나 있습니다.
//+------------------------------------------------------------------+ //| 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. 클래스 생성자 형식(스케치)
행과 셀(필드)의 개수가 참 많은데요. 사실 OBJ_EDIT와 OBJ_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. 셀 클래스 라이브러리
효율성과 이벤트에 대한 객체 반응을 확인해 봅니다.
- 편집 필드에 디폴트 값 대신 다른 변수를 입력합니다. 다른 값이 나타나면 테스트가 성공한 것입니다.
- 버튼을 한번 누르면 다음에 누를 때까지 계속 눌린 채로 있는데요. 이건 우리가 원하는 바가 아닙니다. 버튼이 한번 눌리고 나면 자동으로 제자리로 복귀하도록 만들어야 합니다. 바로 여기에서 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. 그래픽 디자인 프로세스
이제 개발자는 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-클래스 그래픽 디자인 결과
결론
- 클래스는 차근차근 고안되어야 합니다. 클래스를 모듈로 분해함으로써 각각에 대한 별도의 클래스가 생성됩니다. 모듈은 파생 클래스 또는 베이스 클래스의 마이크로모듈로 다시 나뉘죠.
- 베이스 클래스에 너무 많은 메소드를 담으려고 하지 마세요. 최소한만 포함하는 것이 좋습니다.
- 디자인 도구를 사용한 클래스 디자인은 매우 쉽죠. '바보'에게도 쉽습니다. 코드가 자동으로 생성되니까요.
첨부 파일 위치
- masterwindows.mq5-...\MQL5\Experts\
- 기타 파일-...\MQL5\Include\
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/53



