English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
MQL5 쿡북: 커스텀 차트 이벤트 핸들링

MQL5 쿡북: 커스텀 차트 이벤트 핸들링

MetaTrader 5 | 12 10월 2021, 15:14
122 0
Denis Kirichenko
Denis Kirichenko

개요

해당 글은 MQL5 쿡북: 전형적인 차트 이벤트 핸들링과 논리적으로 이어집니다. 커스텀 차트 이벤트 핸들링 메소드에 대한 내용입니다. 커스텀 이벤트 개발 및 핸들링 예제가 포함되어 있습니다. 본문에 언급된 아이디어는 객체 지향 도구를 이용해 구현되었습니다.

커스텀 이벤트는 굉장히 광범위하기 때문에 개발자가 창의력을 발휘하기 좋은 분야입니다.


1. 커스텀 차트 이벤트

이름만 봐도 사용자가 정하는 이벤트라는 걸 알 수 있죠. 정확히 어떤 이벤트가 발생하는 지는 프로그래머가 결정합니다. 원하는 경우 MQL5 개발자는 언어 능력을 얼마든지 활용해 복잡한 알고리즘을 구현할 수 있죠.

커스텀 이벤트는 차트 이벤트의 두 번째 형태인데요. 첫 번째는 전형적인 이벤트입니다. 관련 자료에서 '전형적인 차트 이벤트'라는 말은 사용된 적 없지만 처음 열 가지 차트 이벤트는 여기에 해당한다고 봐요.

전체 차트 이벤트에 대해 오직 한 가지의 열거형이 적용됩니다. 바로 ENUM_CHART_EVENT죠.

커스텀 이벤트에는 65535개의 식별자가 있다고 하네요. 커스텀 이벤트의 첫 번째와 마지막 식별자는 CHARTEVENT_CUSTOM과 CHARTEVENT_CUSTOM_LAST의 값으로 설정됩니다. 각각 1000과 66534에 해당하죠(그림 1).

그림 1. 커스텀 이벤트 첫 번째와 마지막 식별자

그림 1. 커스텀 이벤트 첫 번째와 마지막 식별자

간단한 계산 하나면 식별자 개수를 알 수 있죠. 66534-1000+1=65535입니다.

커스텀 이벤트는 우선 만들어야 쓸 수가 있죠. 따라서 개발자가 EA 알고리즘으로 구현될 이벤트의 컨셉을 고안합니다. 커스텀 이벤트를 분류할 수 있으면 편할 텐데요. 그렇다고 모호성을 식별할 수 있는 건 아니지만 줄일 수는 있고 인수 구조도 정렬시킬 수 있죠.

이걸 기준으로 삼아볼게요. 사용자명이 sergeev인 개발자가 제안한 트레이딩 로봇 원형을 예로 들겠습니다. 해당 개발자는 전체 이벤트를 세 개의 그룹으로 나눕니다(그림 2).

그림 2. 커스텀 이벤트 소스 그룹

그림 2. 커스텀 이벤트 소스 그룹

그리고 모든 커스텀 이벤트는 분류 그룹을 기준으로 개발되죠.

간단하게 시작해 볼까요? 인디케이터 이벤트로 구성된 첫 번째 그룹을 한번 보죠. 해당 그룹에는 인디케이터 생성 및 삭제, 포지션 오픈 및 청산 신호 수신이 포함됩니다. 두 번째 그룹은 주문 및 포지션 상태 변경 이벤트를 포함하는데요. 우리 예제에서는 포지션 오픈 및 청산이 여기에 포함됩니다. 굉장히 간단하죠. 마지막은 형식화하기 가장 어려운 그룹입니다. 외부 이벤트 그룹이죠.

수동 거래 활성화 및 비활성화의 두 가지 이벤트를 설정할게요.

그림 3. 커스텀 이벤트 소스

그림 3. 커스텀 이벤트 소스

첫 번째 패턴은 연역적 추론을 통해 얻어집니다. 일반적인 것에서 특수한 것을 제외하는 거죠(그림 3). 다음은 향후 해당 클래스에 포함되는 이벤트 생성에 사용할 패턴입니다(표 1).

표 1. 커스텀 이벤트

표 1. 커스텀 이벤트

아직 '컨셉'이라고 부르기는 부족하지만 그래도 잘 시작했네요. 다른 방식으로 접근해 보겠습니다. 추상적 거래 시스템에는 세 개의 서브시스템, 즉 기본 모듈이 포함된다는 걸 다들 잘 알고 계시죠(그림 4).

그림 4. 추상적 거래 시스템 모델

그림 4. 추상적 거래 시스템 모델

'소스' 기준을 기반으로 하는 커스텀 이벤트는 다음에서 생성된 이벤트로 분류될 수 있습니다.

  1. 신호 서브시스템
  2. 오픈 포지션 추적 서브시스템
  3. 자금 관리 서브시스템

자금 관리 서브시스템의 경우 허용 가능한 드로우다운 레벨 도달, 설정 값 만큼 거래량 증가, 손절 한도(%) 증가 등을 포함하게 되죠.


2. ChartEvent 핸들러 및 생성기

다음은 차트이벤트 핸들러와 생성기에 대한 라인을 작성할 차례입니다. 커스텀 차트 이벤트 핸들링 원칙은 일반 차트 이벤트 핸들링 원칙과 비슷합니다.

핸들러인 OnChartEvent() 함수가 네 개의 정수형 변수를 갖게 되죠. 개발자들이 해당 메커니즘을 이용해서 이벤트를 구별하고 추가 정보를 획득할 수 있도록 만들었습니다. 제 생각에는 아주 간단하면서도 편리한 프로그램 메커니즘 같군요.

EventChartCustom() 함수는 커스텀 차트 이벤트를 생성합니다. 커스텀 차트 이벤트는 '원래' 차트와 '새로운' 차트에 생성될 수 있는데요. '원래'와 '새로운'의 의미를 가장 흥미롭게 설명한 아티클은 MetaTrader5에서 다중 통화 모드 구현하기가 아닐까 싶네요.

제 생각에는 이벤트 식별자는 ushort형인데 비해 핸들러는 int형인 게 조금 별로인 것 같아요. 핸들러도 ushort형인 게 좀 더 논리적이겠죠.


3. 커스텀 이벤트 클래스

다시 한번 말하지만 이벤트 컨셉은 EA 개발자에게 달려 있습니다. 이번에는 표 1의 이벤트를 이용해 볼 겁니다. 우선 커스텀 이벤트 클래스 CEventBase와 그 파생 클래스를 정리해보겠습니다.

그림 5. 이벤트 클래스 계층 구조

그림 5. 이벤트 클래스 계층 구조

기본 클래스는 다음과 같이 나타납니다.

//+------------------------------------------------------------------+
//| Class CEventBase.                                                |
//| Purpose: base class for a custom event                           |
//| Derives from class CObject.                                      |
//+------------------------------------------------------------------+
class CEventBase : public CObject
  {
protected:
   ENUM_EVENT_TYPE   m_type;
   ushort            m_id;
   SEventData        m_data;

public:
   void              CEventBase(void)
     {
      this.m_id=0;
      this.m_type=EVENT_TYPE_NULL;
     };
   void             ~CEventBase(void){};
   //--
   bool              Generate(const ushort _event_id,const SEventData &_data,
                              const bool _is_custom=true);
   ushort            GetId(void) {return this.m_id;};

private:
   virtual bool      Validate(void) {return true;};
  };
-->

이벤트 형식은 ENUM_EVENT_TYPE 열거형으로 설정합니다.

//+------------------------------------------------------------------+
//| A custom event type enumeration                                  |
//+------------------------------------------------------------------+
enum ENUM_EVENT_TYPE
  {
   EVENT_TYPE_NULL=0,      // no event
   //---
   EVENT_TYPE_INDICATOR=1, // indicator event
   EVENT_TYPE_ORDER=2,     // order event
   EVENT_TYPE_EXTERNAL=3,  // external event
  };
-->

여기에는 이벤트 식별자와 데이터 구조가 포함됩니다.

CEventBase 기본 클래스의 Generate() 메소드는 이벤트 발생을 담당합니다. GetId() 메소드는 이벤트 id를 반환하며 가상 메소드 Validate() 는 이벤트 식별자 값을 확인합니다. 처음에는 이벤트 핸들링 메소드도 클래스에 포함시켰었는데요. 생각해 보니 각각의 이벤트는 그 특성이 달라 추상적 메소드는 여기에 맞지 않겠더라고요. 그래서 그 대신 커스텀 이벤트를 담당하는 CEventProcessor 클래스로 넘겼습니다.


4. 커스텀 이벤트 핸들러 클래스

CEventProcessor 클래스는 8개의 이벤트를 생성하고 핸들링합니다. 클래스 데이터 멤버는 다음과 같습니다.

//+------------------------------------------------------------------+
//| Class CEventProcessor.                                           |
//| Purpose: base class for an event processor EA                    |
//+------------------------------------------------------------------+
class CEventProcessor
  {
//+----------------------------Data members--------------------------+
protected:
   ulong             m_magic;
   //--- flags
   bool              m_is_init;
   bool              m_is_trade;
   //---
   CEventBase       *m_ptr_event;
   //---
   CTrade            m_trade;
   //---
   CiMA              m_fast_ema;
   CiMA              m_slow_ema;
   //---
   CButton           m_button;
   bool              m_button_state;
//+------------------------------------------------------------------+
  };
-->

그 속성 가운데에는 초기화 및 거래 플래그가 포함됩니다. EA가 올바르게 시작하지 않은 경우 첫 번째 멤버가 EA의 거래를 막습니다. 두 번째 멤버는 거래 허가 여부를 확인하죠.

CEventBase형 객체에 대한 포인터도 포함되는데요. 이는 다형성을 갖기 때문에 여러 형태의 이벤트를 담당하게 됩니다. CTrade 클래스 인스턴스는 거래 오퍼레이션에 대한 액세스를 제공합니다.

CiMA형 객체는 인디케이터로부터 수신한 데이터의 핸들링을 돕습니다. 간단한 설명을 위해 거래 시그널을 수신할 예정인 두 개의 MA 인디케이터를 예로 들겠습니다. 'CButton' 클래스의 인스턴스가 EA의 수동 활성화 및 비활성화에 사용될 겁니다.

클래스 메소드는 '모듈-프로시저-함수-매크로' 원칙을 따라 분리됩니다.

//+------------------------------------------------------------------+
//| Class CEventProcessor.                                           |
//| Purpose: base class for an event processor EA                    |
//+------------------------------------------------------------------+
class CEventProcessor
  {
//+-------------------------------Methods----------------------------+
public:
   //--- constructor/destructor
   void              CEventProcessor(const ulong _magic);
   void             ~CEventProcessor(void);

   //--- Modules
   //--- event generating
   bool              Start(void);
   void              Finish(void);
   void              Main(void);
   //--- event processing
   void              ProcessEvent(const ushort _event_id,const SEventData &_data);

private:
   //--- Procedures
   void              Close(void);
   void              Open(void);

   //--- Functions
   ENUM_ORDER_TYPE   CheckCloseSignal(const ENUM_ORDER_TYPE _close_sig);
   ENUM_ORDER_TYPE   CheckOpenSignal(const ENUM_ORDER_TYPE _open_sig);
   bool              GetIndicatorData(double &_fast_vals[],double &_slow_vals[]);

   //--- Macros
   void              ResetEvent(void);
   bool              ButtonStop(void);
   bool              ButtonResume(void);
  };
-->

모듈 가운데 이벤트만을 생성하는 모듈은 세 가지가 있습니다. 이벤트를 시작하는 Start(), 이벤트를 끝내는 Finish() , 그리고 메인 이벤트인 Main()가 여기에 해당하죠. 네 번째 모듈인 ProcessEvent()는 이벤트 핸들러인 동시에 생성기가 됩니다.


4.1 시작 모듈

해당 모듈은 OnInit() 핸들러에서 호출되도록 고안되었습니다.

//+------------------------------------------------------------------+
//| Start module                                                     |
//+------------------------------------------------------------------+
bool CEventProcessor::Start(void)
  {
//--- create an indicator event object
   this.m_ptr_event=new CIndicatorEvent();
   if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
     {
      SEventData data;
      data.lparam=(long)this.m_magic;
      //--- generate CHARTEVENT_CUSTOM+1 event
      if(this.m_ptr_event.Generate(1,data))
         //--- create a button
         if(this.m_button.Create(0,"Start_stop_btn",0,25,25,150,50))
            if(this.ButtonStop())
              {
               this.m_button_state=false;
               return true;
              }
     }

//---
   return false;
  }
-->

이 모듈에서는 인디케이터 이벤트 객체에 대한 포인터가 생성됩니다. 그리고 나면 '인디케이터 생성' 이벤트가 생성되죠. 가장 마지막으로 버튼이 만들어지고요. '정지' 모드로 변경됩니다. 버튼이 눌리는 경우 EA가 멈추게 되는 것이죠.

해당 메소드 정의에는 SEventData 구조 또한 포함됩니다. 커스텀 이벤트 생성기로 전달되는 매개 변수들의 컨테이너가 되죠. 해당 구조 내 한 개의 필드만 채워지는데요. long형 필드입니다. EA의 매직 넘버를 나타낼 겁니다.


4.2 종료 모듈

해당 모듈은 OnDeinit() 핸들러에서 호출되도록 고안되었습니다.

//+------------------------------------------------------------------+
//| Finish  module                                                   |
//+------------------------------------------------------------------+
void CEventProcessor::Finish(void)
  {
//--- reset the event object
   this.ResetEvent();
//--- create an indicator event object
   this.m_ptr_event=new CIndicatorEvent();
   if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
     {
      SEventData data;
      data.lparam=(long)this.m_magic;
      //--- generate CHARTEVENT_CUSTOM+2 event
      bool is_generated=this.m_ptr_event.Generate(2,data,false);
      //--- process CHARTEVENT_CUSTOM+2 event
      if(is_generated)
         this.ProcessEvent(CHARTEVENT_CUSTOM+2,data);
     }
  }
-->

앞서 생성된 이벤트 포인터가 삭제되며 '인디케이터 삭제' 이벤트가 생성됩니다. OnDeinit() 핸들러에서 커스텀 이벤트가 생성되는 경우 런타임 에러 4001(예기치 않은 외부 오류)이 발생하므로 주의하세요. 해당 메소드에서 이벤트 생성 및 핸들링은 OnChartEvent()를 호출하지 않고 실행됩니다.

앞서 말했듯이 EA의 매직 넘버가 SEventData 구조에 저장됩니다.


4.3 메인 모듈

해당 모듈은 OnTick() 핸들러에서 호출되도록 고안되었습니다.

//+------------------------------------------------------------------+
//| Main  module                                                     |
//+------------------------------------------------------------------+
void CEventProcessor::Main(void)
  {
//--- a new bar object
   static CisNewBar newBar;

//--- if initialized     
   if(this.m_is_init)
      //--- if not paused   
      if(this.m_is_trade)
         //--- if a new bar
         if(newBar.isNewBar())
           {
            //--- close module
            this.Close();
            //--- open module
            this.Open();
           }
  }
-->

해당 모듈에서는 Open()과 Close() 프로시저가 호출됩니다. 첫 번째 프로시저는 '오프닝 시그널 수신' 이벤트를 생성하고, 두 번째 프로시저는 '청산 시그널 수신' 이벤트를 생성합니다. 현재 버전의 모듈은 새로운 바가 추가되어도 제대로 작동하는 것으로 나타납니다. Konstantin Gruzdev가 앞서 새로운 바를 탐지하는 클래스를 구현한 바 있습니다.


4.4 이벤트 핸들링 모듈

해당 모듈은 OnChartEvent() 핸들러에서 호출되도록 고안되었습니다. 크기와 기능면에서 가장 큰 모듈입니다.

//+------------------------------------------------------------------+
//| Process event module                                             |
//+------------------------------------------------------------------+
void CEventProcessor::ProcessEvent(const ushort _event_id,const SEventData &_data)
  {
//--- check event id
   if(_event_id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- button click
      if(StringCompare(_data.sparam,this.m_button.Name())==0)
        {
         //--- button state
         bool button_curr_state=this.m_button.Pressed();
         //--- to stop
         if(button_curr_state && !this.m_button_state)
           {
            if(this.ButtonResume())
              {
               this.m_button_state=true;
               //--- reset the event object
               this.ResetEvent();
               //--- create an external event object
               this.m_ptr_event=new CExternalEvent();
               //---
               if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
                 {
                  SEventData data;
                  data.lparam=(long)this.m_magic;
                  data.dparam=(double)TimeCurrent();
                  //--- generate CHARTEVENT_CUSTOM+7 event
                  ushort curr_id=7;
                  if(!this.m_ptr_event.Generate(curr_id,data))
                     PrintFormat("Failed to generate an event: %d",curr_id);
                 }
              }
           }
         //--- to resume
         else if(!button_curr_state && this.m_button_state)
           {
            if(this.ButtonStop())
              {
               this.m_button_state=false;
               //--- reset the event object
               this.ResetEvent();
               //--- create an external event object
               this.m_ptr_event=new CExternalEvent();
               //---
               if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
                 {
                  SEventData data;
                  data.lparam=(long)this.m_magic;
                  data.dparam=(double)TimeCurrent();
                  //--- generate CHARTEVENT_CUSTOM+8 event
                  ushort curr_id=8;
                  if(!this.m_ptr_event.Generate(curr_id,data))
                     PrintFormat("Failed to generate an event: %d",curr_id);
                 }
              }
           }
        }
     }
//--- user event 
   else if(_event_id>CHARTEVENT_CUSTOM)
     {
      long magic=_data.lparam;
      ushort curr_event_id=this.m_ptr_event.GetId();
      //--- check magic
      if(magic==this.m_magic)
         //--- check id
         if(curr_event_id==_event_id)
           {
            //--- process the definite user event 
            switch(_event_id)
              {
               //--- 1) indicator creation
               case CHARTEVENT_CUSTOM+1:
                 {
                  //--- create a fast ema
                  if(this.m_fast_ema.Create(_Symbol,_Period,21,0,MODE_EMA,PRICE_CLOSE))
                     if(this.m_slow_ema.Create(_Symbol,_Period,55,0,MODE_EMA,PRICE_CLOSE))
                        if(this.m_fast_ema.Handle()!=INVALID_HANDLE)
                           if(this.m_slow_ema.Handle()!=INVALID_HANDLE)
                             {
                              this.m_trade.SetExpertMagicNumber(this.m_magic);
                              this.m_trade.SetDeviationInPoints(InpSlippage);
                              //---
                              this.m_is_init=true;
                             }
                  //---
                  break;
                 }
               //--- 2) indicator deletion
               case CHARTEVENT_CUSTOM+2:
                 {
                  //---release indicators
                  bool is_slow_released=IndicatorRelease(this.m_fast_ema.Handle());
                  bool is_fast_released=IndicatorRelease(this.m_slow_ema.Handle());
                  if(!(is_slow_released && is_fast_released))
                    {
                     //--- to log?
                     if(InpIsLogging)
                        Print("Failed to release the indicators!");
                    }
                  //--- reset the event object
                  this.ResetEvent();
                  //---
                  break;
                 }
               //--- 3) check open signal
               case CHARTEVENT_CUSTOM+3:
                 {
                  MqlTick last_tick;
                  if(SymbolInfoTick(_Symbol,last_tick))
                    {
                     //--- signal type
                     ENUM_ORDER_TYPE open_ord_type=(ENUM_ORDER_TYPE)_data.dparam;
                     //---
                     double open_pr,sl_pr,tp_pr,coeff;
                     open_pr=sl_pr=tp_pr=coeff=0.;
                     //---
                     if(open_ord_type==ORDER_TYPE_BUY)
                       {
                        open_pr=last_tick.ask;
                        coeff=1.;
                       }
                     else if(open_ord_type==ORDER_TYPE_SELL)
                       {
                        open_pr=last_tick.bid;
                        coeff=-1.;
                       }
                     sl_pr=open_pr-coeff*InpStopLoss*_Point;
                     tp_pr=open_pr+coeff*InpStopLoss*_Point;

                     //--- to normalize prices
                     open_pr=NormalizeDouble(open_pr,_Digits);
                     sl_pr=NormalizeDouble(sl_pr,_Digits);
                     tp_pr=NormalizeDouble(tp_pr,_Digits);
                     //--- open the position
                     if(!this.m_trade.PositionOpen(_Symbol,open_ord_type,InpTradeLot,open_pr,
                        sl_pr,tp_pr))
                       {
                        //--- to log?
                        if(InpIsLogging)
                           Print("Failed to open the position: "+_Symbol);
                       }
                     else
                       {
                        //--- pause
                        Sleep(InpTradePause);
                        //--- reset the event object
                        this.ResetEvent();
                        //--- create an order event object
                        this.m_ptr_event=new COrderEvent();
                        if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
                          {
                           SEventData data;
                           data.lparam=(long)this.m_magic;
                           data.dparam=(double)this.m_trade.ResultDeal();
                           //--- generate CHARTEVENT_CUSTOM+5 event
                           ushort curr_id=5;
                           if(!this.m_ptr_event.Generate(curr_id,data))
                              PrintFormat("Failed to generate an event: %d",curr_id);
                          }
                       }
                    }
                  //---
                  break;
                 }
               //--- 4) check close signal
               case CHARTEVENT_CUSTOM+4:
                 {
                  if(!this.m_trade.PositionClose(_Symbol))
                    {
                     //--- to log?
                     if(InpIsLogging)
                        Print("Failed to close the position: "+_Symbol);
                    }
                  else
                    {
                     //--- pause
                     Sleep(InpTradePause);
                     //--- reset the event object
                     this.ResetEvent();
                     //--- create an order event object
                     this.m_ptr_event=new COrderEvent();
                     if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC)
                       {
                        SEventData data;
                        data.lparam=(long)this.m_magic;
                        data.dparam=(double)this.m_trade.ResultDeal();
                        //--- generate CHARTEVENT_CUSTOM+6 event
                        ushort curr_id=6;
                        if(!this.m_ptr_event.Generate(curr_id,data))
                           PrintFormat("Failed to generate an event: %d",curr_id);
                       }
                    }
                  //---
                  break;
                 }
               //--- 5) position opening
               case CHARTEVENT_CUSTOM+5:
                 {
                  ulong ticket=(ulong)_data.dparam;
                  ulong deal=(ulong)_data.dparam;
                  //---
                  datetime now=TimeCurrent();
                  //--- check the deals & orders history
                  if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now))
                     if(HistoryDealSelect(deal))
                       {
                        double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME);
                        ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY);
                        //---
                        if(deal_entry==DEAL_ENTRY_IN)
                          {
                           //--- to log?
                           if(InpIsLogging)
                             {
                              Print("\nNew position for: "+_Symbol);
                              PrintFormat("Volume: %0.2f",deal_vol);
                             }
                          }
                       }
                  //---
                  break;
                 }
               //--- 6) position closing
               case CHARTEVENT_CUSTOM+6:
                 {
                  ulong ticket=(ulong)_data.dparam;
                  ulong deal=(ulong)_data.dparam;
                  //---
                  datetime now=TimeCurrent();
                  //--- check the deals & orders history
                  if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now))
                     if(HistoryDealSelect(deal))
                       {
                        double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME);
                        ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY);
                        //---
                        if(deal_entry==DEAL_ENTRY_OUT)
                          {
                           //--- to log?
                           if(InpIsLogging)
                             {
                              Print("\nClosed position for: "+_Symbol);
                              PrintFormat("Volume: %0.2f",deal_vol);
                             }
                          }
                       }
                  //---
                  break;
                 }
               //--- 7) stop trading
               case CHARTEVENT_CUSTOM+7:
                 {
                  datetime stop_time=(datetime)_data.dparam;
                  //---
                  this.m_is_trade=false;                  
                  //--- to log?                  
                  if(InpIsLogging)
                     PrintFormat("Expert trading is stopped at: %s",
                                 TimeToString(stop_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS));
                  //---
                  break;
                 }
               //--- 8) resume trading 
               case CHARTEVENT_CUSTOM+8:
                 {
                  datetime resume_time=(datetime)_data.dparam;
                  this.m_is_trade=true;                  
                  //--- to log?                  
                  if(InpIsLogging)                     
                     PrintFormat("Expert trading is resumed at: %s",
                                 TimeToString(resume_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS));
                  //---
                  break;
                 }
              }
           }
     }
  }
-->

두 가지 파트로 구성되죠. 첫 번째 파트는 '버튼' 객체를 이용한 이벤트 핸들링을 담당합니다. 클릭하면 외부 커스텀 이벤트가 발생하며 이는 차후 핸들링됩니다.

두 번째는 생성된 커스텀 이벤트 프로세싱을 담당하죠. 두 개의 블록으로 구성되며 관련 이벤트가 핸들된 후 새로운 블록이 생성됩니다. '오프닝 시그널 수신' 이벤트는 첫 번째 블록에서 프로세스됩니다. 성공적으로 핸들링될 경우 새로운 주문 이벤트인 '포지션 오픈'이 생성됩니다. '청산 시그널 수신' 이벤트는 두 번째 블록에서 프로세스되는데요. 신호가 제대로 핸들링되면 '포지션 청산' 이벤트가 발생합니다.

CustomEventProcessor.mq5CEventProcessor 클래스를 이용한 EA의 아주 좋은 예제입니다. 해당 EA는 이벤트를 생성하고 해당 이벤트에 알맞게 대응하도록 고안되었습니다. OPP 패러다임 덕분에 소스 코드를 최소한의 라인으로 제한할 수 있었습니다. EA 소스 코드는 본문의 첨부 파일에 포함되어 있습니다.

제 생각에는 커스텀 이벤트 메커니즘을 항상 참조할 필요는 없는 것 같아요. 전략적인 면에서 크게 중요하지 않은 다른 형태의 이벤트도 많거든요.


결론

MQL5 환경에서 커스텀 이벤트를 다루는 방법을 설명했습니다. 개발자 여러분의 숙련도와 상관없이 관심을 가질만 한 내용이길 바랍니다.

MQL5 언어가 성장하고 있어 참 기쁩니다. 아마 곧 클래스 템플릿과 함수 포인터도 생길 겁니다. 그러면 임의의 객체 메소드에 대한 대리자도 작성할 수 있겠죠.

아래의 소스 파일은 프로젝트 폴더에 넣을 수 있습니다. 제 경우에는 MQL5\Projects\ChartUserEvent에 저장했죠.

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

파일 첨부됨 |
랜덤 포레스트로 추세 예측하기 랜덤 포레스트로 추세 예측하기
본문은 Rattle 패키지를 이용한 외환 시장 내 롱 또는 숏 포지션 예측 패턴 자동 검색에 대해 다룹니다. 모든 투자자에게 도움이 될만 한 글입니다.
MQL5 쿡북: TradeTransaction 이벤트 프로세싱 MQL5 쿡북: TradeTransaction 이벤트 프로세싱
본문은 이벤트 기반 프로그래밍의 관점에서 본 MQL5의 가능성에 대해 다룹니다. 이벤트 기반 프로그래밍의 최대 장점은 프로그램이 거래 오퍼레이션에 대한 단계적인 구현 정보를 수신할 수 있다는 거죠. TradeTransaction 이벤트 핸들러를 이용해 진행 중인 거래 오퍼레이션에 대한 정보를 수신하고 프로세스하는 법에 대해서도 알아볼 겁니다. 제 생각에 이 방법은 터미널 간 거래 카피에 이용할 수 있을 것 같아요.
일반 VPS보다 MetaTrader4, MetaTrader5 가상 호스팅이 더 나은 이유 일반 VPS보다 MetaTrader4, MetaTrader5 가상 호스팅이 더 나은 이유
가상 클라우드 호스팅 네트워크는 MetaTrader4와 MetaTrader5 전용으로 개발되었습니다. 네이티브 솔루션으로서 다양한 장점을 가지고 있죠. 24시간 무료 체험을 통해 가상 서버를 이용해 보세요.
3세대 신경망: 심층 신경망 3세대 신경망: 심층 신경망
본문은 머신러닝의 새로운 관점에 대해 다룹니다. 딥러닝, 정확히 말하면 심층 신경망에 대한 글이죠. 2세대 신경망도 간략하게 살펴볼 겁니다. 연결 구조, 종류, 학습 메소드 및 규칙, 단점을 다룬 후 3세대 신경망 개발의 역사, 종류, 특성 및 학습 메소드에 대해 알아보겠습니다. 실제 데이터를 이용한 적층 오토인코더를 이용한 심층 신경망 구축 및 학습 실험도 할 겁니다. 인풋 데이터 선택부터 편차 메트릭까지 자세히 다룰 겁니다. 본문의 마지막 부분에서는 MQL4/R 기반 인디케이터가 탑재된 EA를 이용해 심층 신경망을 구현해 보도록 하겠습니다.