English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
예시를 통해 보는 MQL5 의 OOP: 프로세싱 경고와 에러 코드

예시를 통해 보는 MQL5 의 OOP: 프로세싱 경고와 에러 코드

MetaTrader 5 | 5 7월 2021, 15:24
78 0
KlimMalgin
KlimMalgin

OOP에 대한 간략한 설명

개발을 시작하기 전에 이 문서에서 다룰 OOP의 일부 특징에 익숙해져봅시다. 

당연히 스트럭쳐와 클래스를 사용할 것입니다. 이들은 객체 지향 프로그래밍의 기초니까요. 스트럭쳐란 무엇일까, 클래스는 뭐고 어떻게 다를까?

스트럭쳐 는 각기 다른 함수와 변수들을 담을 수 있게 해주는 구조입니다 (void제외).

클래스처럼 스트럭쳐는 자료 필드의 집합입니다. 하지만 클래스는 좀 더 복잡하고 보다 "유연한" 구조입니다. 클래스들은 OOP의 기초 컨셉트입니다. 클래스와 스트럭쳐의 차이는 저 문서에서 알아볼 수 있습니다. 반복하겠습니다:

  • 키워드 클래스가 선언에서 쓰입니다;
  • 따로 명시되지않는 한 모든 클래스 멤버의 액세스 조건은 private입니다. 따로 명시되지않는 한 스트럭쳐의 데이터 멤버는 기본적으로 액세스 조건이 public입니다;
  • 클래스 객체는 언제나 가상 함수 테이블을 가지고 있으며, 클래스 내에 이들 가상 함수가 선언되지 않았을 때에도 마찬가지입니다. 스트럭쳐는 가상 함수를 가질 수 없습니다;
  • 오퍼레이터 new는 클래스 객체에 붙을 수 있지만 스트럭쳐엔 붙지 못합니다;
  • 클래스는 다른 클래스로부터만 상속받을 수 있으며, 스트럭쳐는 다른 스트럭쳐로부터만 상속받을 수 있습니다.

 

이제 클래스를 봅시다. 모든 클래스 필드는 두가지 타입으로 나뉩니다. 이들은 데이터 멤버 (변수, 어레이, 등) 그리고 클래스 내에 정의된 함수입니다.

데이터 멤버는 흔히들 클래스 속성이라 불리는데, 이는 클래스에서 객체가 생성되었을 때, 데이터 멤버들이 해당 객체의 속성을 반영하기 때문입니다. 예를들어 기하학 도형 - 원이 있다면, 속성에는 반지름, 너비, 색, 등 객체 속성이 있을 것입니다

클래스 내에서 정의된 함수메소드라고 불립니다. 이것들은 클래스 속성을 정의하거나 다른 알고리즘에서 받아오는 것, 어느것에나 쓰일 수 있습니다.

객체 지향 프로그래밍에는 캡슐화라는 개념이 있습니다. 이는 데이터와 객체의 구현 방식을 유저 (어플리케이션 개발자)의 직접적인 영향에서 숨길 수 있다는 것입니다. 프로그래머는 어떠 메소드가 어떤 객체 속성을 바꾸는지 알게되는 문서를 가지게될 뿐, 직접 그 속성을 변환하는 것은 불가능합니다.

이러한 보호 조치는 속성 값을 변경하기 전에 여러 가지 검사가 필요한 경우에 필요합니다. 필요한 모든 검사는 메소드로 구현되며 성공적으로 수행되는 경우 속성 값을 변경할 수 있습니다. 사용자가 속성에 직접 액세스할 수 있는 경우 이러한 검사가 수행되지 않으므로 속성 값이 잘못 설정될 수 있으며 MQL 프로그램이 제대로 작동하지 않습니다. .

클래스 속성 및 메소드에 대해 우리는 3개의 액세스 레벨변경자: private, protectedpublic을 통해 설정가능합니다.

만약 private 변경자가 클래스 필드에 쓰였다면 같은 클래스의 메소드로만 액세스 가능하며 외부에서는 변경될 수 없습니다. protected 변경자 또한 외부에서 필드에 접근하는 것에 약간의 제약을 부과하지만, 이는 같은 클래스의 메소드 혹은 하위클래스 메소드로 액세스 가능합니다. 반면 public은 모든 액세스 제한을 치우고 해당 클래스 필드에 자유로운 액세스를 허락합니다

Include용 mqh-file 만들기

우리가 지금부터 짤 클래스는 추후에 짤 프로그램에 포함시킬 수 있도록 (Expert Advisor, 스크립트, 인디케이터), 별도의 mqh 파일에 담겨야합니다.

이 파일을 만들기 위해 MQL5 Wizard를 씁니다. 메뉴에서 File -> Create Include file (*.mqh)을 선택하고 다음 Next. 창이 나타나면 이름을 넣고 (저는 ControlErrors라고 했습니다) Done을 누르세요. mqh 파일 템플릿이 열릴 것입니다. 바로 이 파일로 작업합니다.

시작하기

이제 이 문서를 공부하는 과정에서 유용할 수 있는 OOP의 모든 이론적 기본을 알게 되었습니다. 그러니 넘어갑시다.

클래스 코드를 모든 속성 및 메소드의 선언과 함께 고려하겠습니다.

class ControlErrors
{
private:

   // Flags determining what types of statements should be introduced
   bool _PlaySound;    // Play or don't play a sound when an error occurs
   bool _PrintInfo;    // Enter error data the the journal of Expert Advisors
   bool _AlertInfo;    // Generate Alert alert with error data
   bool _WriteFile;    // Write reports on errors onto a file or not
   
   // A structure for storing error data and elements that use this structure
   struct Code
   {
      int code;      // Error code
      string desc;   // Description of an error code
   };
   Code Errors[];    // Array that contains error codes and their descriptions
   Code _UserError;  // Stores information about a custom error
   Code _Error;      // Stores information about the last error of any type   
   
   // Different service properties
   short  _CountErrors;     // Number of errors stored in array Errors[]
   string _PlaySoundFile;   // File that will be played for an alert sound
   string _DataPath;        // Path to the log storing directory

   
public:
   // Constructor
   ControlErrors(void);
   
   // Methods for setting flags
   void SetSound(bool value);          // Play or don't play a sound when an error occurs
   void SetPrint(bool value);          // Enter error data the the journal of Expert Advisors or not
   void SetAlert(bool value);          // Generate an Alert message or not
   void SetWriteFlag(bool flag);       // Set the writing flag. true - keep logs, false - do not keep
   
   // Methods for working with errors
   int  mGetLastError();            // Returns contents of the system variable _LastError
   int  mGetError();                // Returns code of the last obtained error
   int  mGetTypeError();            // Returns error type (Custom = 1 ore predefined = 0)
   void mResetLastError();          // Resets the contents of the system variable _LastError
   void mSetUserError(ushort value, string desc = "");   // Sets the custom error
   void mResetUserError();          // Resets class fields that contain information about the custom error
   void mResetError();              // Resets the structure that contains information about the last error
   string mGetDesc(int nErr = 0);   // Returns error description by the number, or that of the current error of no number
   int Check(string st = "");       // Method to check the current system state for errors
   
   // Alert methods(Alert, Print, Sound)
   void mAlert(string message = "");
   void mPrint(string message = "");
   void mSound();
      
   // Various service methods
   void SetPlaySoundFile(string file); // Method sets the file name to play an sound
   void SetWritePath(string path);     // Set the path to store logs  
   int mFileWrite();                   // Record into a file the available information about the last error
};

클래스 속성

먼저 클래스 속성을 선언하고 모든 속성에 private 변경자가 적용되므로 클래스 외부에서 직접 속성을 작업할 수 없습니다. 속성은 편의상 3개로 나뉘어집니다:

  1. 플래그는 어떤 타입의 리포트를 보존해야하는가를 결정합니다. 모든 플래그는 두가지 값만 받을 수 있습니다: true 이 타입의 리포트(알림)이 활성화되었다는 것, false 이 타입에선 비활성화되었다는 것.
    • _PlaySound - 에러 발생시 선택된 멜로디 또는 소리를 켤지 말지에 대한 변수.
    • _PrintInfo - Expert Advisor 저널에 에러 디테일을 추가할지를 결정합니다.
    • _AlertInfo - 에러 정보를 담은 Alert 출력의 활성화여부.
    • _WriteFile - 에러 정보를 파일에 저장하는 기능의 활성화 여부.
  2. 에러 데이터를 담는 구조나 이 구조를 사용하는 요소.
    • Code - 스트럭쳐 그 자체. 이것은 어레이에 에러 데이터를 담는 편의성을 위해 만들어졌습니다.
    • Errors - Code 타입의 어레이, 즉 각각 어레이 요소는 Code 스트럭쳐입니다.
    • _UserError - Code 타입 변수. 커스텀 에러를 다루는데 쓰입니다.
    • _Error - Code 타입 변수. 마지막으로 발생한 오류가 이 변수에 배치되고 이 변수를 통해 오류에 대한 추가 작업이 구현됩니다.
  3. 다른 서비스 속성
    • _CountErrors - Errors 어레이에 담겨있는 에러의 숫자를 담고있는 변수. 이는 어레이 크기를 명시하는데에 쓰입니다.
    • _PlaySoundFile - 알람 소리 파일을 담고 있습니다.
    • _DataPath - 로그 파일 에러 데이터가 쓰일 파일의 이름과 경로를 담고 있습니다.

첫 속성 그룹은 그냥 명료하죠: 특정 알림을 활성화시키거나 비활성화 시키기입니다. 두번째 Code 스트럭쳐 그룹이 흥미롭습니다. 그건 뭐고 이 스트럭쳐는 왜 어레이 요소로 쓰이는 걸까요? 전부 쉽습니다! 오류 코드와 해당 설명에 대해 별도의 어레이를 설정하는 것보다 필요한 모든 데이터를 단일 어레이 요소에 저장하는 것이 훨씬 편리합니다. 그러한 가능성을 구현하기 위해 스트럭쳐가 사용된다. 모든 필드는 스트럭쳐 내에서 선언됩니다. 우리 경우엔:

  • code - 에러 코드를 담는 int 타입 필드;
  • desc - string 타입 필드. 이것이 에러 설명을 담고 있습니다.

실제로 이 구조는 복합 데이터 타입입니다. 즉, 변수와 어레이를 선언하는 데 사용할 수 있습니다. 그 결과로 Code 타입의 각 변수가 이 스트럭쳐의 필드들을 담고 있게됩니다. Code 타입의 각 어레이 요소에는 코드와 설명을 저장하는 두 개의 필드가 포함됩니다. 따라서 여러 유형의 객체 데이터를 단일 위치에 편리하게 저장할 수 있는 방법이 MQL5에 구현됩니다.

_UserError 및 _Error 변수가 있습니다. 둘 다 최근에 발생한 오류에 대한 정보를 포함하고 있지만 _UserError는 사용자 지정 오류에 대한 정보를 저장하는 반면 _Error는 모든 오류에 대한 정보를 저장합니다.

속성의 세번째 그룹. 여기엔 첫번째나 두번째 그룹에 묶기 힘든 나머지를 다 담았습니다. 3개가 있습니다. 제일 먼저 _CountErrors, 이것은 _Errors 어레이에 담긴 에러의 숫자를 담고 있습니다. 이 속성은 _Errors 어레이를 생성자 내에서 생성하는데에, 그리고 어레이 요소를 부르는 몇개 메소드에 쓰입니다. 두번째 속성은 _PlaySoundFile 입니다. 이 속성은 에러 발생시 실행되는 소리 파일의 이름을 명시합니다. 3번째 속성은 _DataPath 입니다. 이것은 로그 보존을 위해 경로와 파일명을 저장합니다.

클래스 메소드 생성자

다음은 생성자와 클래스 메소드의 정의입니다 생성자를 고려해보고 그것이 무엇인지 이해해봅시다. 메소드와 마찬가지로, 이는 클래스 내에서 정의되는 일반적인 함수이지만 몇 가지 특정한 특징을 가지고 있습니다.

  • 생성자의 이름은 클래스명과 동일합니다.
  • 생성자에는 리턴 타입이 없습니다 (void타입으로 명시).
  • 생성자엔 입력 패러미터가 없습니다.

클래스 멤버는 대부분 생성자에서 초기화됩니다. 예를 들어, 클래스 생성자에서 보고서 유지를 사용하지 않도록 설정하는 모든 플래그가 설정되고, 사운드 파일 이름과 로그 파일이 지정되며, _Errors 어레이의 크기가 설정되고 이 어레이는 데이터로 채워집니다. 아래는 생성자 코드의 일부만 게시하겠습니다. 생성자 코드가 너무 길고 타입이 같기 때문입니다. 대부분은 코드와 설명으로 _Errors 어레이를 채우는 것입니다. 코드 전체는 문서에 첨부되어있습니다.

void ControlErrors::ControlErrors(void)
{
   SetAlert(false);
   SetPrint(false);
   SetSound(false);
   SetWriteFlag(false);
   SetPlaySoundFile("alert.wav");
   SetWritePath("LogErrors.txt");
   
   _CountErrors = 150;
   ArrayResize(Errors, _CountErrors);

   // Return codes of a trade server
   Errors[0].code = 10004;Errors[0].desc = "Requote";
   Errors[1].code = 10006;Errors[1].desc = "Request rejected";
   Errors[2].code = 10007;Errors[2].desc = "Request canceled by trader";
   Errors[3].code = 10008;Errors[3].desc = "Order placed";
   Errors[4].code = 10009;Errors[4].desc = "Request is completed";
   Errors[5].code = 10010;Errors[5].desc = "Request is partially completed";
   Errors[6].code = 10011;Errors[6].desc = "Request processing error";
   Errors[7].code = 10012;Errors[7].desc = "Request canceled by timeout";
   Errors[8].code = 10013;Errors[8].desc = "Invalid request";
   Errors[9].code = 10014;Errors[9].desc = "Invalid volume in the request";
   Errors[10].code = 10015;Errors[10].desc = "Invalid price in the request";
   Errors[11].code = 10016;Errors[11].desc = "Invalid stops in the request";
   Errors[12].code = 10017;Errors[12].desc = "Trade is disabled";
   Errors[13].code = 10018;Errors[13].desc = "Market is closed";
   Errors[14].code = 10019;Errors[14].desc = "There is not enough money to fulfill the request";
   Errors[15].code = 10020;Errors[15].desc = "Prices changed";
   Errors[16].code = 10021;Errors[16].desc = "There are no quotes to process the request";
   Errors[17].code = 10022;Errors[17].desc = "Invalid order expiration date in the request";
   Errors[18].code = 10023;Errors[18].desc = "Order state changed";
   Errors[19].code = 10024;Errors[19].desc = "Too frequent requests";
   Errors[20].code = 10025;Errors[20].desc = "No changes in request";
   Errors[21].code = 10026;Errors[21].desc = "Autotrading disabled by server";
   Errors[22].code = 10027;Errors[22].desc = "Autotrading disabled by client terminal";
   Errors[23].code = 10028;Errors[23].desc = "Request locked for processing";
   Errors[24].code = 10029;Errors[24].desc = "Order or position frozen";
   Errors[25].code = 10030;Errors[25].desc = "Invalid order filling type";

   // Common Errors
   Errors[26].code = 4001;Errors[26].desc = "Unexpected internal error";
   Errors[27].code = 4002;Errors[27].desc = "Wrong parameter in the inner call of the client terminal function";
   Errors[28].code = 4003;Errors[28].desc = "Wrong parameter when calling the system function";
   Errors[29].code = 4004;Errors[29].desc = "Not enough memory to perform the system function";
   Errors[30].code = 4005;Errors[30].desc = "The structure contains objects of strings and/or dynamic arrays and/or structure of such objects and/or classes";
   Errors[31].code = 4006;Errors[31].desc = "Array of a wrong type, wrong size, or a damaged object of a dynamic array";
   Errors[32].code = 4007;Errors[32].desc = "Not enough memory for the relocation of an array, or an attempt to change the size of a static array";
   Errors[33].code = 4008;Errors[33].desc = "Not enough memory for the relocation of string";
   Errors[34].code = 4009;Errors[34].desc = "Not initialized string";
   Errors[35].code = 4010;Errors[35].desc = "Invalid date and/or time";
   Errors[36].code = 4011;Errors[36].desc = "Requested array size exceeds 2 GB";
   Errors[37].code = 4012;Errors[37].desc = "Wrong pointer";
   Errors[38].code = 4013;Errors[38].desc = "Wrong type of pointer";
   Errors[39].code = 4014;Errors[39].desc = "System function is not allowed to call";

}

구현 설명은 클래스 외부에서 수행된다는 점에 유의하십시오. 메소드만이 클래스 안에서 선언됩니다! 물론 무조건은 아닙니다. 원한다면 클래스 안에서 각 메소드의 내용을 설명할 수 있지만, 제 생각에는 불편하고 이해하기 어렵습니다.

말씀드린 것처럼 메소드 함수의 헤더만 클래스 본문에 선언되고 설명은 클래스 외부에서 수행됩니다. 메소드를 설명할 땐 그게 어느 클래스에서 온건지 반드시 명시해야합니다. 컨텍스트 허가 오퍼레이션 ::이 그를 위해 사용됩니다. 위의 코드에서 볼 수 있듯이, 먼저 메소드의 반환 유형이 지정되고(생성자의 경우 void), 클래스 이름(메소드가 속한 컨텍스트의 이름), 클래스 이름 뒤에 컨텍스트 허용 연산을 거쳐 메소드 이름이 입력 패러미터와 함께 지정됩니다. 이것들이 다 끝나고 나서야 메소드 알고리즘 설명이 시작됩니다.

우선 생성자 안에서 모든 플래그가 설정되고 소리와 로그 파일이 명시됩니다:: 

SetAlert(false);
SetPrint(false);
SetSound(false);
SetWriteFlag(false);
SetPlaySoundFile("alert.wav");
SetWritePath("LogErrors.txt"); 

이들 각각의 메소드는 특정 클래스 속성과 상호작용합니다. 이건 유저가 속성에 정해둔 값을 거르기 위해 일부러 이루어집니다. 예를들어 파일명과 경로 설정을 설정가능한 마스크를 설정하거나 할 수 있습니다. 만약 마스크가 있으면 유저에게 알려집니다. 

앞에서 언급했듯이 모든 플래그는 false로 세팅됩니다. 부연하자면 클래스 샘플이 생성되었을때 리포트는 저장되지 않습니다. 사용자는 보관할 리포트를 선택하고 기능 OnInit() 함수에서 동일한 "Set" 메소드를 사용하여 활성화해야 한다. 마찬가지로 로그파일의 경로와 이름을 변경할 수 있습니다 (경로는 'MetaTrader 5\MQL5\Files\' 디렉토리에 상대적으로 생성). 소리 파일도 마찬가지입니다 (경로는 'MetaTrader 5\Sounds\' 디렉토리에 상대적으로 생성).

플래그들이 전부 세팅된 후 _CountErrors 변수를 초기화하고 150을 값으로 줍니다 (149개의 어레이에 대한 정보가 어레이에 담깁니다) 그리고 어레이 사이즈를 ArrayResize() 함수를 통해 세팅합니다. 이제 어레이를 채우기 시작합니다.

플래그 세팅 메소드

생성자 설명 뒤에 플래그 설정 방법과 사운드 및 로그 파일 이름 설정에 대한 설명이 나옵니다:

void ControlErrors::SetAlert(bool value)
{
   _AlertInfo = value;
}

void ControlErrors::SetPrint(bool value)
{
   _PrintInfo = value;
}

void ControlErrors::SetSound(bool value)
{
   _PlaySound = value;
}

void ControlErrors::SetWriteFlag(bool flag)
{
   _WriteFile = flag;
}

void ControlErrors::SetWritePath(string path)
{
   _DataPath = path;
}

void ControlErrors::SetPlaySoundFile(string file)
{
   _PlaySoundFile = file;
}

코드에서 볼 수 있듯이, 패러미터 메소드인 클래스 속성에 전달되는 간단한 할당입니다. 플래그에는 두 개의 값만 사용할 수 있으므로 확인이 필요하지 않습니다. 그러나 할당하기 전에 파일 이름과 경로를 확인해야 합니다.

이들 메소드의 호출은 다음과 같습니다: 

type Class_name::Function_Name(parameters_description)
{
   // function body
}

다음은 에러를 다루는 메소드들에 대한 설명이며, 그것들 중 처음으로 소개할 것은 mGetLastError() 와 mResetLastError() 입니다.

mGetLastError() 및 mResetLastError() 메소드 

mGetLastError()는 이름만 봐도 뭘 하는지 알 수 있습니다. 바로 GetLastError() 함수를 복제하는 것입니다. 그러나 GetLastError() 호출 외에도, 수집된 오류 코드를 _Errors 어레이에서 검색하고 에러의 상세한 정보(코드 및 설명)는 변수 _Error 변수에 저장되므로 매번 GetLastError() 호출 대신 저장된 값을 더 사용할 수 있습니다.

메소드 코드:

int ControlErrors::mGetLastError(void)
{
   _Error.code = GetLastError();
   _Error.desc = mGetDesc(_Error.code);
   return _Error.code;
}

메소드 mResetLastError()는 함수 ResetLastError()를 복제합니다:

void ControlErrors::mResetLastError(void)
{
   ResetLastError();
}

마지막 에러 메세지 작업용 메소드

두 메소드가 있습니다: mGetError() 및 mResetError().

메소드 mGetError()는 _Error.code에 담긴 코드를 반환합니다:

int ControlErrors::mGetError(void)
{
   return _Error.code;
}

메소드 mResetError()는 _Error 변수의 컨텐츠를 초기화합니다:

void ControlErrors::mResetError(void)
{
   _Error.code = 0;
   _Error.desc = "";
}

에러 타입 판단 메소드 mGetTypeError()

다음 메소드는 mGetTypeError() 입니다. 이는 마지막으로 발생한 에러가 커스텀 에러인지 아니면 기존에 정의된 (_Errors 어레이 안에 들어있는) 것인지 확인합니다.

메소드 코드:

int ControlErrors::mGetTypeError(void)
{
   if (mGetError() < ERR_USER_ERROR_FIRST)
   {
      return 0;
   }
   else if (mGetError() >= ERR_USER_ERROR_FIRST)
   {
      return 1;
   }
   return -1;
}

상수 ERR_USER_ERROR_FIRST의 값은 65536 입니다. 이 코드들로부터 커스텀 에러가 시작됩니다. 메소드 본문 안에서 마지막으로 받은 에러 코드를 확인합니다. 만약 메소드가 0을 반환한다면 이는 사전에 정의된 에러입니다. 만약 1이 반환된다면 커스텀 에러입니다.

커스텀 에러를 다루는 메소드

MQL5에서는 유저들이 프로그램을 짜는 과정에서 자신만의 에러를 만들 수 있습니다. 커스텀 코드를 적절한 설명에 넣기 위하여 클래스에_UserError 속성이 준비되어 있습니다. 이 속성을 다루는 데엔 2개의 메소드가 있습니다.

mSetUserError() 메소드는 코드를 세팅하고 커스텀 에러를 설명하는데 쓰입니다:

void ControlErrors::mSetUserError(ushort value, string desc = "")
{
   SetUserError(value);
   _UserError.code = value;
   _UserError.desc = desc;
}

먼저 SetUserError() 함수는 사전 정의된 변수 _LastErrorERR_USER_ERROR_FIRST + 값 으로 설정합니다. 그러고나서 과 적절한 설명은 _UserError 변수에 저장됩니다.

두번째 메소드 mResetUserError()는 _UserError 변수의 필드를 초기화합니다:

void ControlErrors::mResetUserError(void)
{
   _UserError.code = 0;
   _UserError.desc = "";
}

이 메소드는 _UserError 변수로만 작업가능합니다. 시스템 변수_LastError의 값을 초기화하려면, 다른 함수 mResetLastError()이 사용됩니다. 이것에 대해선 위에서 설명했습니다.

에러 코드 설명을 받아오는 메소드

또한 클래스에는 특별한 mGetDesc() 메소드가 있습니다. 이 메소드는 사용자가 에러를 설정한 경우 Errors 어레이 혹은 _UserError 변수의 필드 설명에서 에러 코드 설명을 반환합니다.

string ControlErrors::mGetDesc(int nErr=0)
{
   int ErrorNumber = 0;
   string ReturnDesc = "";
   
   ErrorNumber = (mGetError()>0)?mGetError():ErrorNumber;
   ErrorNumber = (nErr>0)?nErr:ErrorNumber;
   
   if ((ErrorNumber > 0) && (ErrorNumber < ERR_USER_ERROR_FIRST))
   {
      for (int i = 0;i<_CountErrors;i++)
      {
         if (Errors[i].code == ErrorNumber)
         {
            ReturnDesc = Errors[i].desc;
            break;
         }
      }
   }
   else if (ErrorNumber > ERR_USER_ERROR_FIRST)
   {
      ReturnDesc = (_UserError.desc=="")?"Cusrom error":_UserError.desc;
   }
      
   if (ReturnDesc == ""){return "Unknown error code: "+(string)ErrorNumber;}
   return ReturnDesc;
}

이 메소드는 하나의 패러미터 nErr를 가지고 있습니다. 기본값은 0입니다. 메소드 호출 중에 값이 패러미터로 설정된 경우, 설정된 값에 대한 설명이 검색됩니다. 만약 패러미터가 설정되지 않았다면, 마지막으로 받은 에러 코드에서 설명을 탐색하게됩니다..

처음에는 두 변수가 선언됩니다: ErrorNumber - 이 변수를 사용하면 오류 코드가 처리됨, 그리고 ReturnDesc - ErrorNumber에서 받은 설명이 이 메소드에 저장됨. 다음 두 줄에서 ErrorNumber에 값을 설정할 때, 조건 오퍼레이터?:가 사용됩니다.. 이는 단순한if-else 구조입니다.

ErrorNumber = (mGetError()>0)?mGetError():ErrorNumber;
ErrorNumber = (nErr>0)?nErr:ErrorNumber;

첫 번째 줄에서는: 에러가 수정되었음, 즉 mGetError()가 0이 아닌 결과를 반환한 경우, 획득된 에러 코드(mGetError() 메소드가 반환된 값)가 ErrorNumber 변수에 할당되고, 그렇지 않으면 변수 ErrorNumber의 값이 할당됩니다. 두번째 줄에서도 같은 확인이 이루어지지만 이번엔 mGetError() 메소드의 패러미터에 대해 이루어집니다. 만약 nErr 값이 0이 아니라면 이는 ErrorNumber 변수에 할당됩니다.

에러 코드를 얻었다면 이 코드에 대한 설명을 탐색합니다. 만약 받아온 코드가 0보단 크고 ERR_USER_ERROR_FIRST 보다는 작다면 (커스텀이 아니란 의미) 루프를 통해 설명을 찾습니다. 만약 받아온 코드가 ERR_USER_ERROR_FIRST보다 크다면 _UserError 변수에서 필드 설명을 받아옵니다.

마침내 우리가 원하던 설명을 찾았습니다. 만약 아니라면, 알려지지 않은 에러 코드에 대한 메세지를 반환하십시오.

신호 메소드

신호 메소드엔 mAlert(), mPrint() 및 mSound()이 속합니다. 정렬하는 방식만 놓고 보면 이들 메소드는 매우 비슷합니다:

void ControlErrors::mAlert(string message="")
{
   if (_AlertInfo == true)
   {
      if (message == "")
      {
         if (mGetError() > 0)
         {
            Alert("Error №",mGetError()," - ",mGetDesc());
         }
      }
      else
      {
         Alert(message);
      }   
   }
}

void ControlErrors::mPrint(string message="")
{
   if (_PrintInfo == true)
   {
      if (message == "")
      {
         if (mGetError() > 0)
         {
            Print("Error №",mGetError()," - ",mGetDesc());
         }
      }
      else
      {
         Print(message);
      }
   }
}

void ControlErrors::mSound(void)
{
   if (_PlaySound == true)
   {
      PlaySound(_PlaySoundFile);
   }
}

이 세개의 메소드 모두에서 시작파트에 리포트를 허가하는 것과 신호를 허락하는 플래그를 확인합니다. 그 다음 mAlert() 및 mPrint() 메소드에서 Alert 다이얼로그나 저널에 추가될 메세지를 찾기 위해 인풋 패러미터message를 체크합니다. 만약 message 내에 메세지가 설정되어있고, 마지막 에러 코드가 0보다 크다면 이는 표시됩니다. 만약 아니면 표준 메세지가 표기됩니다. mSound() 메소드에는 패러미터가 없습니다. 따라서 플래그를 체크하자마자 PlaySound() 함수가 호출되어 소리를 발생시킵니다.

Check() 메소드

이 방법은 단순히 이 클래스의 모든 기능을 올바른 순서로 호출하며, 따라서 새로운 오류의 발생을 확인하고, 모든 허용된 보고서를 발행하고, 그 직후 에러코드와 설명이 지워집니다. 따라서 Check() 메소드는 다음과 같이 종합적인 점검을 수행합니다.

int ControlErrors::Check(string st="")
{
   int errNum = 0;
   errNum = mGetLastError();
   mFileWrite();
   mAlert(st);
   mPrint(st);
   mSound();
   mResetError();
   mResetLastError();
   mResetUserError();
   return errNum;
}

Check() 에는 단 하나의 string 타입 패러미터가 있습니다. 리포트에 기록할 mAlert() 및 mPrint() 메소드에 전달되는 사용자 지정 메시지입니다.

메세지를 로그 파일에 기록하는 메소드

이 메소드는 mFileWrite()라고 불립니다. 로그 파일 보존이 허용되고 파일 경로가 올바르게 지정된 경우 - 이 방법을 사용하면 지정된 파일에 레코드가 생성됩니다..

int ControlErrors::mFileWrite(string message = "")
{
   int      handle  = 0,
            _return = 0;
   datetime time    = TimeCurrent();
   string   text    = (message != "")?message:time+" - Error №"+mGetError()+" "+mGetDesc();
   
   if (_WriteFile == true)
   {
      handle = FileOpen(_DataPath,FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI);
      if (handle != INVALID_HANDLE)
      {
         ulong size = FileSize(handle);
         FileSeek(handle,size,SEEK_SET);
         _return = FileWrite(handle,text);
         FileClose(handle);
      }
   }
   return _return;
}

시작 부분에서 총 4개의 변수가 선언됩니다: handle - 열린 파일의 핸들을 저장, _return - 반환된 값을 저장, time - 현재 시각(파일에 기록할 시간)을 유지, text - 파일에 저장할 메세지 텍스트. mFileWrite() 메소드에는 오직 하나의 입력 패러미터 message가 있는데, 이것에 파일에다 저장할 아무런 string을 유저가 전달합니다.

이 기능은 인디케이터 값, 매매가, 혹은 특정 시각에 필요한 자료를 저장하는데에 쓸 수 있습니다.

모든 변수를 선언했고 flag _WriteFile 를 체크했습니다. 만약 로그 파일을 저장하는 것이 허락되어있다면 그 로그 파일은FileOpen() 함수를 통해 다시 쓰기용으로 오픈됩니다. FileOpen()의 첫 번째 패러미터는 파일 이름과 경로를 포함하는 속성 DataPath입니다. 두 번째 패러미터는 플래그 작업 모드를 결정하는플래그 세트입니다. 우리의 경우 4개의 플래그를 씁니다:

  • FILE_READ와 FILE_WRITE는 데이터를 추가할 가능성이 있는 비어 있지 않은 파일을 함께 엽니다.
  • FILE_TXT는 작업이 간단한 txt 파일에서 이루어질 것을 보여줍니다.
  • FILE_ANSI는 파일에 쓰일 string이 ANSI 타입(싱글 바이트 심볼)일 것을 보여줍니다.

다음 단계에서는 파일이 성공적으로 열렸는지 여부를 확인합니다. 만약 아니라면 핸들의 값이 INVALID_HANDLE이 되며 메소드 작업은 여기서 종료됩니다. 만약 맞다면 우리는 FileSize(), FileSeek()를 통해 파일의 크기를 알게되고,FileWrite()를 통해 파일 포인터의 위치를 파일 끝으로 이동하고 메시지를 파일 끝에 추가합니다. 이다음 파일을 FileClose() 함수를 통해 닫습니다.

우리가 사이즈를 반환해야하는 파일의 핸들을 FileSize() 함수의 입력 패러미터로 전달하세요. 그 함수의 유일한 패러미터입니다.

FileSeek()을 다루려면 3개의 패러미터가 필요합니다:

  • 우리가 작업할 파일의 핸들.
  • 파일 포인터의 시프트.
  • 시프트의 레퍼런스 포인트. ENUM_FILE_POSITION의 값이 필요합니다.

FileWrite() 함수 작업에는 최소 2개의 패러미터가 필요합니다. 이들은 텍스트 데이터를 쓰는 데 필요한 파일의 핸들입니다. 두 번째 줄은 작성해야 하는 텍스트 줄이며, 다음 줄은 모두 파일에 기록됩니다. 패러미터의 수는 63을 넘겨선 안됩니다.

FileClose() 함수에 파일 핸들이 있어야 닫을 수 있습니다.

예시

이제부터 우리가 여태 짠 클래스를 이용하여 몇가지 일반적인 예시를 추가하고자 합니다. 객체 생성부터 시작하여 필요한 리포트를 보관하도록 하겠습니다.

클래스 객체를 만들어봅시다:

#include <ControlErrors.mqh>

ControlErrors mControl;

객체를 생성하기 전에 Expert Advisor에 클래스 설명이 포함된 파일을 추가해야 합니다. 이것은 프로그램 제일 위에 #include를 써서 할 수 있습니다. 그리고 객체가 생성된 이후엔 새 변수를 생성하는 것과 동일하게 보입니다. 그러나 데이터 유형 대신 클래스 이름이 삽입됩니다. 

이제 수신하고자 하는 보고서를 생성하겠습니다. 이건 OnInit() 함수 안에서 처리됩니다: 

int OnInit()
{
//---
mControl.SetAlert(true);
mControl.SetPrint(true);
mControl.SetSound(false);
mControl.SetWriteFlag(true);
mControl.SetPlaySoundFile("news.wav");
//---
return(0);
}

기본적으로 객체가 생성되면 모든 허용 플래그가 false로 설정됩니다. 즉, 모든 리포트가 비활성화됩니다. 그렇기 때문에 OnInit()에서는 위의 예(SetSound() 메소드)에서 이미 처리되었되므로 false 값을 사용하여 메소드를 호출할 필요가 없습니다. 이 메소드들은 다른 프로그램 파트에서 호출될 수도 있습니다. 예를 들어 특정 조건에서 리포트 보관을 실행 중지해야 하는 경우 이러한 조건을 프로그래밍하고 조건이 충족될 때 필요한 값으로 플래그를 설정할 수 있습니다.

여기서 한 가지 더 언급해야 할 사항은 프로그램을 실행하는 동안 메소드를 호출하고 오류를 "포착"하는 것입니다. 여기서는 단일 Check() 방법을 사용하여 모든 플래그를 설정할 수 있으므로 이 부분은 어렵지 않습니다.

mControl.Check();

위에서 설명한 대로 이 방법은 발생한 오류의 코드를 식별하고, 보고서를 보관하는 모든 방법을 호출한 다음, 마지막 오류에 대한 정보를 포함하는 모든 변수의 값을 재설정합니다. Check()에서 제공하는 에러 처리 방법이 만약 적합하지 않으면 사용 가능한 클래스 메소드를 사용하여 보고서를 생성할 수 있습니다.

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

파일 첨부됨 |
controlerrors.mqh (19.82 KB)
인디케이터의 경제적 계산 원칙 인디케이터의 경제적 계산 원칙
사용자 및 기술 인디케이터에 대한 호출은 자동 거래 시스템의 프로그램 코드에서 매우 적은 공간을 차지합니다. 종종 단순히 몇 줄의 코드 일뿐입니다. 그러나 가장 많은 시간을 사용하는 몇 줄의 코드로 Expert Advisor를 테스트하는 데 소비해야 하는 경우가 종종 있습니다. 따라서 인디케이터 내 데이터 계산과 관련된 모든 것은 언뜻 보기 보다 훨씬 더 철저히 고려되어야 합니다. 이 글은 이것에 대해 정확하게 이야기 할 것입니다.
MQL5에서 이동 평균 계산 성능 테스트 MQL5에서 이동 평균 계산 성능 테스트
첫 번째 이동 평균 인디케이터 생성 이후 여러 많은 인디케이터가 나타났습니다. 그들 중 다수는 유사한 평활 방법을 사용하지만 다른 이동 평균 알고리즘의 성능은 연구되지 않았습니다. 이 글에서는 MQL5에서 이동 평균을 사용하는 가능한 방법을 고려하고 성능을 비교합니다.
ORDER_MAGIC을 사용하여 단일 상품에서 여러 Expert Advisors와의 거래 ORDER_MAGIC을 사용하여 단일 상품에서 여러 Expert Advisors와의 거래
이 글에서는 매직 식별을 사용한 정보 코딩 문제와 다양한 Expert Advisors의 자동 거래의 분할, 조립 및 동기화에 대해 설명합니다. 이 글은 Expert Advisors와 다양한 전략의 복잡한 동기화 시스템을 구현하는 데 유용할 수 있는 가상 포지션 문제를 다루기 때문에 초보자는 물론 숙련된 거래자에게도 흥미로울 것입니다.
여러 상품을 거래하는 Expert Advisor 생성 여러 상품을 거래하는 Expert Advisor 생성
금융 시장에서 자산 다각화의 개념은 꽤 오래된 것인데 항상 초보 트레이더를 끌어 들였습니다. 이 글에서 저자는 이러한 거래 전략 방향에 대한 초기 소개를 위해 다중 통화 Expert Advisor의 구성에 대한 최대한 간단한 접근 방식을 제안합니다.