English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
"New Bar" 이벤트 핸들러

"New Bar" 이벤트 핸들러

MetaTrader 5 | 5 7월 2021, 16:52
170 0
Konstantin Gruzdev
Konstantin Gruzdev

소개

인디케이터 작성자와 전문가는 실행 시간 측면에서 압축 코드를 작성하는데 항상 관심이 있었습니다. 다른 각도에서 이 문제에 접근할 수 있습니다. 이 기사의이 광범위한 주제에서 우리는 이미 해결 된 것처럼 보이는 문제를 다룰 것입니다. 새로운 바를 확인하십시오. 모든 계산 및 거래 작업이 차트에 새로운 바를 생성하는 동안 한 번 수행되기 때문에 이것은 계산 루프를 제한하는 매우 인기있는 방법입니다. 따라서 논의할 내용은 다음과 같습니다.  

  • 새로운 바를 감지하는 방법.
  • 새로운 바 감지의 기존 알고리즘의 단점.
  • 새로운 바 감지의 보편적인 방법을 만듭니다.
  • 이 방법을 적용하는 미묘함과 방법.
  • NewBar 이벤트 및이 이벤트의 핸들러 - OnNewBar().

새로운 바를 감지하는 방법

이제 새로운 바를 감지하는 방법에 대한 몇 가지 수용 가능한 솔루션이 있습니다. 예를 들어 Expert Advisor의 제한 및 검증, 인디케이터의 경제적 계산 원칙 기사 또는 여기에서 찾을 수 있습니다. 그건 그렇고, 저는 이러한 자료를 연구하는 것이 좋습니다. 내가 말하는 것을 이해하는 것이 더 쉬울 것입니다.  

이러한 자료는 현재 미완성된 바의 개봉 시간을 추적하는 원리를 사용합니다. 이것은 매우 쉽고 신뢰할 수 있는 방법입니다. 새로운 바를 감지하는 다른 방법이 있습니다.

예를 들어 사용자 지정 인디케이터에서 이러한 목적으로 OnCalculate() 함수의 두 입력 매개 변수 인 rates_total 및 prev_calculated를 사용할 수 있습니다. 이 방법의 한계 - 기본적으로 현재 차트에서 새로운 바를 감지하고 인디케이터에서만 사용할 수 있다는 사실입니다. 다른 마침표나 기호에서 새로운 바를 찾으려면 추가 기술을 사용해야 합니다.

또는 예를 들어 첫 번째 틱, 틱 볼륨 = 1 또는 모든 바 가격이 같을 때 새로운 바를 잡으려고 할 수 있습니다. 시가 = 고가 = 저가 = 종가. 이러한 방법은 테스트에 잘 사용될 수 있지만 실제 거래에서는 종종 결함이 있습니다. 이것은 첫 번째와 두 번째 틱 사이의 순간이 때때로 생성된 바를 잡기에 충분하지 않기 때문입니다. 이는 특히 강력한 시장 움직임이나 인터넷 연결 품질이 좋지 않을 때 두드러집니다.  

TimeCurrent() 함수를 기반으로 새로운 바를 감지하는 방법이 있습니다. 그런데 현재 차트에 대한 새로운 바를 감지해야 하는 경우 좋은 방법입니다. 이 기사의 끝에서 사용할 것입니다.

음, 이웃에게 물어볼 수도 있습니다. "이봐, 새 술집이 있나요?" 그가 뭐라고 대답 할까? 좋습니다. 현재 완료되지 않은 바를 여는 시간을 추적하여 새로운 바를 감지하는 원칙에 대한 선택을 중단하겠습니다. 그것의 단순성과 신뢰성은 진정으로 검증된 것입니다. 

출발점

위에서 언급한 자료에서 새로운 바를 감지하는 것은 나쁘지 않습니다. 그러나...  

이 "하지만" 이 무엇인지 이해하기 위해 시작점 (또는 프로토 타입)으로서 Expert Advisor의 제한 사항 및 검증 기사에서 새 기준을 감지하는 간단하고 좋은 기능을 사용합니다. 여기 있습니다:

//+------------------------------------------------------------------+
//| Returns true if a new bar has appeared for a symbol/period pair  |
//+------------------------------------------------------------------+
bool isNewBar()
  {
//--- memorize the time of opening of the last bar in the static variable
   static datetime last_time=0;
//--- current time
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set the time and exit
      last_time=lastbar_time;
      return(false);
     }

//--- if the time differs
   if(last_time!=lastbar_time)
     {
      //--- memorize the time and return true
      last_time=lastbar_time;
      return(true);
     }
//--- if we passed to this line, then the bar is not new; return false
   return(false);
  }

이 프로토 타입 기능은 실제로 작동하며 생명에 대한 완전한 권리를 가지고 있습니다. 그러나... 

프로토 타입 기능 분석

이 기능을 내 (물론) 최고의 Expert Advisor의 소스 코드에 복사했습니다. 작동하지 않았습니다. 저는 조사를 시작했습니다. 이 기능에 대한 내 생각은 다음과 같습니다.

함수 헤더. 결과적으로 모든 것을 살펴 보겠습니다. 함수 헤더부터 시작하겠습니다.

bool isNewBar()

저는 함수 헤더가 마음에 들어 매우 간단하고 직관적이며 전달된 매개 변수를 처리할 필요가 없습니다. 앞으로 이 형태로 사용하면 좋을 것 같습니다.

호출 수 제한. 헤더 다음은 정적 변수를 초기화하는 첫 번째 명령문입니다.

//---  memorize the time of opening of the last bar in the static variable
   static datetime last_time=0;

모든 것이 잘 보입니다. 그러나...

문제는 정적 변수를 사용한다는 것입니다. 도움말 항목은 다음을 알려줍니다. 도움말 항목은 다음을 알려줍니다.

정적 변수 는 프로그램 실행 시점부터 존재하며 특수 OnInit() 함수가 호출되기 전에 한 번만 초기화됩니다. 초기 값이 지정되지 않은 경우 정적 스토리지 클래스의 변수는 초기 값을 0으로 사용합니다. 

static 키워드로 선언된 지역 변수는 함수 lifetime 동안 해당 값을 유지합니다. 다음 함수 호출마다 이러한 지역 변수에는 이전 호출 중에 보유한 값이 포함됩니다.

이 프로토 타입 함수를 한 곳에서 호출하면 필요한 것이 있습니다. 그러나 예를 들어 동일한 계산 루프의 다른 위치에서이 함수를 사용하려면 항상 false를 반환합니다. 즉, 바가 없음을 의미합니다. 그리고 이것은 항상 사실이 아닙니다. 이 경우 정적 변수는 프로토 타입 함수 호출 수를 인위적으로 제한합니다.

보편성의 질문. 프로토 타입 함수의 다음 문장은 다음과 같습니다.

//--- current time
   datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);

SERIES_LASTBAR_DATE 한정자와 함께 SeriesInfoInteger() 함수를 사용하여 가장 최근에 미완성된 바의 시작 시간을 가져 오는 것은 논리적입니다.

우리의 프로토 타입 함수인 isNewBar()는 원래 단순한 것으로 생각되었고 기본적으로 현재 차트의 거래 도구와 기간을 사용합니다. 현재 차트에서만 새로운 바를 추적하려는 경우 허용됩니다. 하지만 현재 차트뿐만 아니라 기간과 도구를 사용하면 어떻게 해야 합니까? 또한 복잡한 차트가 있으면 어떻게 합니까? 예를 들어 Renko 또는 Kagi를 플롯하기로 결정하면 어떻게 됩니까?

부족은 우리를 심각하게 제한할 수 있습니다. 나중에 이를 수정하는 방법을 다룰 것입니다.  

오류 처리. 이제 SeriesInfoInteger() 함수를 살펴보겠습니다. 차트가 아직 형성되지 않은 상태에서 실행된다면 무엇을 반환할 것이라고 생각하십니까? 예를 들어 Expert Advisor 또는 인디케이터를 차트에 첨부하고 기간 또는 기호를 변경하기로 결정한 경우 또는 터미널을 다시 시작할 때 이러한 상황이 발생할 수 있습니다. 그리고 timeseries 업데이트 중에 어떤 일이 발생합니까? 참고로 도움말 항목에는 다음과 같은 경고가 있습니다.

데이터 가용성

HCC 형식 또는 즉시 사용 가능한 HC 형식의 데이터 존재가 차트에 표시되거나 MQL5 프로그램에서 사용할 수있는 이러한 데이터의 절대적인 가용성을 항상 나타내는 것은 아닙니다.

MQL5 프로그램에서 가격 데이터 또는 인디케이터 값에 액세스 할 때 특정 시간 또는 특정 시간부터의 가용성이 보장되지 않는다는 점을 기억하십시오. 이는 시스템 자원을 절약하기 위해 mql5 프로그램에 필요한 전체 데이터 사본이 MetaTrader 5에 저장되지 않기 때문입니다. 터미널 데이터베이스에 대한 직접 액세스만 제공됩니다.

모든 기간에 대한 가격 내역은 HCC 형식의 공통 데이터로 작성되며 서버에서 데이터를 업데이트하면 모든 기간에 대한 데이터가 업데이트되고 인디케이터가 다시 계산됩니다. 이로 인해 데이터가 잠시 전에 사용 가능하더라도 데이터에 대한 액세스가 거부 될 수 있습니다.

그렇다면 이 함수는 무엇을 반환할까요? 이러한 불확실성을 피하기 위해서는 마지막으로 미완성된 바의 여는 시간에 대한 쿼리 오류를 잡기 시작해야 합니다.  

초기화 가능성. 계속 진행하겠습니다. 프로토 타입 함수에 대한 다음 설명을 고려하십시오.

//--- if it is the first call of the function
   if(last_time==0)
     {
      //--- set the time and exit
      last_time=lastbar_time;
      return(false);
     }

여기 모든 것이 확실히 좋습니다. 그러나 뉘앙스가 있습니다. 도움말에서 위의 문장을 보셨나요? "정적 변수는 프로그램 실행 순간부터 존재하며 특수한 OnInit() 함수 이전에 한 번만 초기화됩니다." 그리고 last_time 변수를 초기화하는 데 더 많은 시간이 필요하다면 어떨까요? 더 정확하게는 첫 번째 호출의 상황을 인위적으로 만들려면 어떻게 해야 합니까? 아니면 다른 상황? 답을 알면 질문하기 쉽습니다. 그러나 나중에 더 자세히 설명합니다.

바 수다음으로 프로토 타입 함수에는 다음 코드가 있습니다.

//--- if the time differs
   if(last_time!=lastbar_time)
     {
      //--- memorize the time and return true
      last_time=lastbar_time;
      return(true);
     }

저와 같은 프로그래머는 if 연산자가 클라이언트 터미널과 전략 테스터를 "놀라게" 할 수 있도록 할 수 있습니다. 사실 논리적으로 과거 시간은 항상 현재보다 적습니다. 그것은 last_time < lastbar_time입니다. 우연한 프로그램 오류로 인해 타임머신이 있거나 더 정확하게 - 그 반대가 발생했습니다. lastbar_time < last_time. 놀랍네요! 일반적으로 이러한 시간 역설은 감지하기 쉽고 오류 메시지를 표시하기 쉽습니다.

그러나 모든 구름에는 은색 안감이 있습니다. 내 "타임머신"을 보면서 isNewBar() 호출 중에 하나의 새로운 바만 나타날 수 있다는 것을 발견했습니다. 차트 기간이 짧을수록 함수 호출 사이에 여러 바가 발생할 확률이 높아집니다. 그 이유는 여러 가지가 있을 수 있습니다. 긴 계산 시간에서 시작하여 서버와의 일시적인 연결 부족으로 끝납니다. 새로운 바의 신호 뿐만 아니라 바의 수도 수신할 수 있는 기회는 확실히 유용할 것입니다.

프로토 타입 함수는 다음과 같이 끝납니다.

//--- if we passed to this line, then the bar is not new; return false
   return(false);

예, 우리가 이 라인을 통과했다면 - 바는 새로운 것이 아닙니다.

새로운 isNewBar() 함수 만들기 

여기서 흥미로운 일이 시작됩니다. 감지된 약점을 해결하겠습니다. 아시다시피, 저는 "새로운 isNewBar() 함수 생성"이라고 부르는 부분을 지나치게 겸손했습니다. 우리는 더 확실한 것을 할 것입니다.

함수 호출 수에 대한 제한을 없애는 것으로 시작합니다.

다섯 번째로 떠오르는 것은 인디케이터의 경제적 계산 원리 기사 또는 여기 isNewBar의 isNewBar()와 동일한 이름의 함수를 사용할 수 있다는 것입니다. 즉, 여러 last_time 값을 함수 본문에 저장하는 배열을 포함하려면 다른 위치에서 isNewBar() 함수 호출의 카운터를 배치하는 식입니다. 물론 이들은 모두 작동하는 버전이며 구현할 수 있습니다. 그러나 우리가 12 개의 통화 쌍에 대해 작업하기 위해 다중 통화 Expert Advisor을 작성한다고 가정해 보십시오. 고려해야 할 필요한 뉘앙스가 너무 많고 혼동하지 않을 것입니까?

우리는 무엇을 해야 합니까? 답은 여기입니다!

객체 지향 프로그래밍의 장점은 객체 또는 일부 클래스의 인스턴스가 동일한 클래스의 다른 인스턴스와 독립적으로 "자신의 삶을 살"수 있다는 것입니다. 이제 CisNewBar 클래스를 생성하기 시작하여 Expert Advisor 또는 Indicator의 어느 위치에서나 이 클래스의 인스턴스를 몇 번이나 생성할 수 있습니다. 그리고 모든 인스턴스가 "자신의 삶을 살도록" 하십시오.

이것이 우리가 시작해야 하는 것입니다.

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Time of opening last bar
      
   public:
      void              CisNewBar();      // CisNewBar constructor
      //--- Methods of detecting new bars:
      bool              isNewBar();       // First type of request for new bar
  };  

bool CisNewBar::isNewBar()
  {
   //--- here is the definition of static variable

   //--- here will be the rest of method's code   
   ...

   //--- if we've reached this line, then the bar is not new; return false
   return(false);
  }

isNewBar() 함수는 이제 메소드입니다. 이제 정적 변수 last_time이 없습니다. 대신 보호 된 클래스 변수 m_lastbar_time이 있습니다. isNewBar() 메소드에 정적 변수를 남겨두면 isNewBar() 함수에서 이전과 동일한 문제에 직면하게 되므로 모든 노력이 잘못될 것입니다. 이는 정적 변수의 기능입니다.

그리고 이제 마지막 바의 시간이 클래스의 m_lastbar_time 보호 변수에 저장되고 각 클래스 인스턴스에서 이 변수에 메모리가 할당됩니다. 따라서 우리는 프로토 타입 함수에있는 호출 수에 대한 제한을 제거할 수 있었습니다. MQL 프로그램의 여러 위치에서 isNewBar() 메소드를 원하는만큼 호출하여 각 위치에 대한 클래스 인스턴스를 만들 수 있습니다.

이것은 우리가 성공한 것입니다. 이제 보편성에 대해 알아 보겠습니다. 새로운 클래스에 뭔가를 추가하기 전에 한 가지 재미있는 아이디어를 소개하고 싶습니다.

추론합시다. 무엇을 원합니까? 새로운 바에 대한 신호를 받고 싶습니다. 어떻게 할까요? 마지막 틱 (또는 마지막 순간)에 현재 미완성 바의 여는 시간이 여는 시간보다 더 많은 경우 이전 틱 (또는 이전 순간)에서 현재 완료되지 않은 바의 새로운 바가 형성됩니다. 복잡한 문구이지만 정확합니다. 결론은 시간을 비교해야 한다는 것입니다. 따라서 현재 미완성 바 newbar_time의 개장 시간을 isNewBar() 메소드에 전달하는 것이 논리적이라고 판단했습니다. 그러면 메소드 헤더는 다음과 같습니다.

bool isNewBar(datetime newbar_time)

newbar_time을 어디에서 가져올 지 아직 묻지 마십시오. 이미 알고 있다고 가정합니다. 나중에 살펴보겠습니다.  

그런데 시간을 isNewBar() 메소드에 전달하면 새로운 바를 감지하는 매우 유연한 도구를 얻을 수 있습니다. 우리는 모든 종류의 거래 도구로 모든 표준 차트 기간을 다룰 수 있습니다. 이제 우리는 심볼 이름과 기간의 크기에 의존하지 않도록 발생했습니다.  

비표준 차트도 사용할 수 있습니다. 예를 들어 틱 캔들 스틱, Renko 또는 Kagi 차트를 플로팅하는 경우 바가 열리는 시간은 표준 차트 기간과 거의 일치하지 않습니다. 이 경우 우리의 기능은 필수 불가결합니다.

글쎄, 이제 다용도로 괜찮습니다. 우리의 아이디어에 따라 CisNewBar 클래스를 보완하겠습니다.

class CisNewBar 
  {
   protected:
      datetime          m_lastbar_time;   // Time of opening last bar
      uint              m_retcode;        // Result code of detecting new bar
      int               m_new_bars;       // Number of new bars
      string            m_comment;        // Comment of execution
      
   public:
      void              CisNewBar();      // CisNewBar constructor
      //--- Methods of detecting new bars:
      bool              isNewBar(datetime new_Time); // First type of request for new bar
  };
   
//+------------------------------------------------------------------+
//| First type of request for new bar                     |
//| INPUT:  newbar_time - time of opening (hypothetically) new bar   |
//| OUTPUT: true   - if new bar(s) has(ve) appeared                  |
//|         false  - if there is no new bar or in case of error      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
bool CisNewBar::isNewBar(datetime newbar_time)
  {
   //--- Initialization of protected variables
   m_new_bars = 0;      // Number of new bars
   m_retcode  = 0;      // Result code of detecting new bar: 0 - no error
   m_comment  =__FUNCTION__+" Successful check for new bar";
   //---
   
   //--- Just to be sure, check: is the time of (hypothetically) new bar m_newbar_time less than time of last bar m_lastbar_time? 
   if(m_lastbar_time>newbar_time)
     { // If new bar is older than last bar, print error message
      m_comment=__FUNCTION__+" Synchronization error: time of previous bar "+TimeToString(m_lastbar_time)+
                                                  ", time of new bar request "+TimeToString(newbar_time);
      m_retcode=-1;     // Result code of detecting new bar: return -1 - synchronization error
      return(false);
     }
   //---
        
   //--- if it's the first call
   if(m_lastbar_time==0)
     {  
      m_lastbar_time=newbar_time; //--- set time of last bar and exit
      m_comment   =__FUNCTION__+" Initialization of lastbar_time = "+TimeToString(m_lastbar_time);
      return(false);
     }   
   //---

   //--- Check for new bar:
   if(m_lastbar_time<newbar_time)       
     { 
      m_new_bars=1;               // Number of new bars
      m_lastbar_time=newbar_time; // remember time of last bar
      return(true);
     }
   //---
   
   //--- if we've reached this line, then the bar is not new; return false
   return(false);
  }

우리 클래스의 소스 코드를 살펴보면 런타임 오류 추적을 고려했으며 새로운 바 수를 저장하는 변수를 도입했습니다.

모든 것이 좋지만 우리의 보편적인 방법인 isNewBar (datetime newbar_time)에는 한 가지 큰 불편이 있습니다. 이러한 불편함은 우리 전문가 또는 인디케이터의 소스 코드에서 (가설적으로) newbar_time의 시간을 계산하는 것에 대해 항상 걱정해야 한다는 것입니다.  

다행히도 어떤 경우에는 우리 클래스의 새로운 추가 메소드에 이 기능을 맡기고 생활을 단순화할 수 있습니다. 프로토 타입 함수의 표준 마침표 및 기호의 경우 SERIES_LASTBAR_DATE 수정 자와 함께 SeriesInfoInteger() 함수의 두 번째 버전을 사용하고 다른 모든 경우에는 일반 메소드를 사용하여 수행할 수 있습니다. 그래서 여기에 내가 가진 것이 있습니다.

//+------------------------------------------------------------------+
//| Second type of request for new bar                     |
//| INPUT:  no.                                                      |
//| OUTPUT: m_new_bars - Number of new bars                          |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
int CisNewBar::isNewBar()
  {
   datetime newbar_time;
   datetime lastbar_time=m_lastbar_time;
      
   //--- Request time of opening last bar:
   ResetLastError(); // Set value of predefined variable _LastError as 0
   if(!SeriesInfoInteger(m_symbol,m_period,SERIES_LASTBAR_DATE,newbar_time))
     { // If request has failed, print error message:
      m_retcode=GetLastError();  // Result code of detecting new bar: write value of variable _LastError
      m_comment=__FUNCTION__+" Error when getting time of last bar opening: "+IntegerToString(m_retcode);
      return(0);
     }
   //---
   
   //---Next use first type of request for new bar, to complete analysis:
   if(!isNewBar(newbar_time)) return(0);
   
   //---Correct number of new bars:
   m_new_bars=Bars(m_symbol,m_period,lastbar_time,newbar_time)-1;
   
   //--- If we've reached this line - then there is(are) new bar(s), return their number:
   return(m_new_bars);
  }

그래서 지금 우리는 무엇을 가지고 있습니까? 이제 표준 기간 동안 우리는 마지막 미완성 바의 개장 시간을 결정하는데 신경 쓸 필요가 없습니다. 우리는 간단한 호출과 단점 없이 프로토 타입 함수에 접근했습니다. 또한 오류 코드, 런타임 주석 및 새로운 바 수를 포함한 추가 이점도 얻었습니다.   

남은 것이 있습니까? 예. 마지막 순간이 있습니다 - 초기화. 이를 위해 우리는 클래스 생성자와 여러 Set-method를 사용할 것입니다. 클래스 생성자는 다음과 같습니다.  

//+------------------------------------------------------------------+
//| CisNewBar constructor.                                           |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CisNewBar::CisNewBar()
  {
   m_retcode=0;         // Result code of detecting new bar
   m_lastbar_time=0;    // Time of opening last bar
   m_new_bars=0;        // Number of new bars
   m_comment="";        // Comment of execution
   m_symbol=Symbol();   // Symbol name, by default - symbol of current chart
   m_period=Period();   // Chart period, by default - period of current chart
  }

그리고 다음과 같은 Set-methods:

//--- Methods of initializing protected data:
void              SetLastBarTime(datetime lastbar_time){m_lastbar_time=lastbar_time;                            }
void              SetSymbol(string symbol)             {m_symbol=(symbol==NULL || symbol=="")?Symbol():symbol;  }
void              SetPeriod(ENUM_TIMEFRAMES period)    {m_period=(period==PERIOD_CURRENT)?Period():period;      }

클래스 생성자 덕분에 현재 차트의 심볼과 기간의 초기화에 신경 쓸 필요가 없습니다. 프로토 타입 기능과 마찬가지로 기본적으로 사용됩니다. 그러나 다른 심볼이나 차트 기간을 사용해야 하는 경우 생성 된 Set-methods를 사용할 수 있습니다. 또한 SetLastBarTime(datetime lastbar_time)을 사용하여 "첫 번째 호출"의 상황을 재현할 수 있습니다.

결론적으로 Expert Advisor 및 Indicators의 클래스에서 데이터를 가져오는 몇 가지 Get 메소드를 만들 수 있습니다. 

      //--- Methods of access to protected data:
uint              GetRetCode()     const  {return(m_retcode);     }  // Result code of detecting new bar 
datetime          GetLastBarTime() const  {return(m_lastbar_time);}  // Time of opening last bar
int               GetNewBars()     const  {return(m_new_bars);    }  // Number of new bars
string            GetComment()     const  {return(m_comment);     }  // Comment of execution
string            GetSymbol()      const  {return(m_symbol);      }  // Symbol name
ENUM_TIMEFRAMES   GetPeriod()      const  {return(m_period);      }  // Chart period

이제 mql5 프로그램에서 필요한 모든 정보를 얻을 수 있습니다. 지금은 CisNewBar 클래스 생성을 완전히 중단할 수 있습니다.

클래스의 전체 소스 코드는 Lib CisNewBar.mqh 첨부 파일에 있습니다.

CisNewBar 클래스 사용의 예

우리가 만든 것의 모든 미묘함을 이해하기 위해 클래스 사용의 예를 고려할 것을 제안합니다. 장점 뿐 아니라 단점도 있을 수 있습니다.

예 1. 시작하려면 Expert Advisor의 제한 사항 및 확인 문서에서 isNewBar() 함수에 대해 완전히 동일한 Expert Advisor을 만들어 보겠습니다.

//+------------------------------------------------------------------+
//|                                               Example1NewBar.mq5 |
//|                                            Copyright 2010, Lizar |
//|                                               Lizar-2010@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar current_chart; // instance of the CisNewBar class: current chart

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(current_chart.isNewBar()>0)
     {     
      PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

쌍과 기간이 동일한 차트에서 두 Expert Advisor를 실행해보겠습니다. 우리가 가진 것을 보자:


첫째, 두 Expert Advisor가 동시에 새로운 바에 대해 보고합니다. 그런 다음 그들은 침묵하고 4 분 후에 새로운 바가 있음을 알립니다 (이 시간은 1로 표시됨). 괜찮습니다. 몇 분 동안 인터넷 연결이 끊겼고 어떤 일이 일어날지 보기로 결정했습니다. 바가 거의 형성되지 않았음에도 불구하고 이 정보를 받지 못했습니다. 새로운 Expert Advisor에서는 isNewBar() 메소드가 이러한 작업을 수행 할 수 있기 때문에 이 단점을 수정할 수 있습니다.

다음으로 차트 기간을 M2로 변경했습니다. Expert Advisors의 반응은 달랐습니다. CheckLastBar는 2 분마다 새로운 바에 대해보고하기 시작했으며 Example1NewBar 는 마치 기간이 변경되지 않은 것처럼 (2로 표시됨) 1 분마다 새로운 바에 대해 알려줍니다.

Expert Advisor가 차트에 연결되었을 때 current_chart 인스턴스가 클래스 생성자에 의해 초기화되었다는 사실. 이미 차트에 첨부된 Expert Advisor의 기간을 변경하면 클래스 생성자가 시작되지 않고 Expert Advisor는 M1 기간으로 계속 작업합니다. 이것은 우리의 클래스 인스턴스가 자신의 삶을 살며 환경 변화에 영향을 받지 않음을 알려줍니다. 찬반 양론이 될 수 있습니다 - 모두 작업에 따라 다릅니다.  

Expert Advisor가 CheckLastBar로 작동하려면 OnInit() 함수에서 m_symbol 및 m_period 보호 클래스 변수를 초기화해야 합니다. 해보자.

예제 2. Expert Advisor에 대한 몇 가지 추가 사항을 소개하고 다시 CheckLastBar와 성능을 비교해보겠습니다. Expert Advisor의 소스 코드는 Example2NewBar.mq5 파일로 첨부됩니다. 쌍과 기간이 동일한 차트에서 Expert Advisors를 실행합니다. 지난번과 같은 장애물을 만들어봅시다. 우리가 가진 것을 보자:


지난번과 마찬가지로 Expert Advisors는 먼저 새로운 바에 대해 동기식으로 보고합니다. 그런 다음 몇 분 동안 인터넷 연결을 끊습니다... 그런 다음 켜십시오. 우리의 새로운 Expert Advisor는 새로운 바에 대해 보고 했을 뿐만 아니라 얼마나 많은 바가 나타났는지 (1로 표시)보고했습니다. 대부분의 인디케이터와 전문가에게이 숫자는 계산되지 않은 바의 수를 의미합니다. 따라서 비용 효율적인 재계산 알고리즘을 위한 좋은 기반이 있습니다.  

다음으로 차트 기간을 M2로 변경했습니다. 예 1과 달리 Expert Advisor는 동기식으로 작동합니다 (2로 표시됨). OnInit() 함수에서 m_symbol 및 m_period 보호 클래스 변수의 초기화가 도움이 되었습니다! 기호 (3으로 표시)를 변경할 때 Expert Advisors도 동일한 방식으로 작동합니다. 

예제 3. CisNewBar 클래스에는 오류를 추적할 수 있는 가능성이 있습니다. Expert Advisor는 오류를 추적할 필요가 없도록 설계되었을 수 있습니다. 그럼 이 가능성을 사용하지 마세요. 우리는 오류가 발생할 수 있는 상황을 인위적으로 만들어 포착하려고 노력할 것입니다. 이를 위해 Expert Advisor의 소스 코드 (Example3NewBar.mq5 파일)를 약간 보완합니다.

저는 무엇을 할 것인가? 평소처럼 분 차트에서 Example3NewBar를 실행하겠습니다 그런 다음 Expert Advisor의 요청이 있기 전에 터미널이 시계열을 구축 할 시간이 없는 상황이 발생하기를 바라며 차트의 도구를 변경하기 시작합니다. 일반적으로 클라이언트 터미널을 괴롭히고 어떤 일이 발생하는지 살펴보겠습니다...  

몇 번의 시도 끝에 Expert Advisor가 오류를 발견했습니다.

 

이제 우리는 런타임 오류를 포착할 수 있다고 자신있게 말할 수 있습니다. 그들을 다루는 방법 - 맛의 문제입니다. 이 오류를 네 번 추적했습니다. 다운로드가 완료되고 차트가 작성되면 Expert Advisor는 바가 1 개만 빠졌다고 제안했습니다.

그런데 Expert Advisor의 소스 코드를 살펴본 사람들은 isNewBar() 메소드가 0보다 작거나 같은 값을 반환 할 때만 오류를 확인하는 것이 합리적이라는 것을 알았을 것입니다.

경고: 이 실험 중에 차트 기간을 변경하기 시작하면 차트 기간을 조금에서 더 크게 변경하면 동기화 오류가 발생합니다. 이는 H1의 바 개방 시간 (예: 59 건)이 M1보다 빠르기 때문입니다. 차트 기간을 전환 할 때이 오류를 방지하려면 SetLastBarTime (datetime lastbar_time) 메소드를 사용하여 OnInit() 함수에서 m_lastbar_time 변수를 올바르게 초기화해야 합니다.

예제 4.이 예에서는 Expert Advisor의 작업을 복잡하게 합시다. M1의 EURUSD, M1의 GBPUSD 및 M2의 USDJPY의 세 가지 통화 쌍을 사용하십시오. 첫 번째 쌍이 있는 차트는 최신 차트이며 새로운 바를 관찰 할 것입니다. 두 번째 쌍으로 Expert Advisor 시작 후 형성된 바 수를 계산합니다. 첫 번째 쌍이 새로운 바가 있다는 신호를 보낼 때까지 계산합니다. 그리고 세 번째 쌍에서 우리는 지속적으로 (EURUSD에 바가 나타날 때) m_lastbar_time 보호 클래스 변수의 초기화를 수행합니다. Expert Advisor의 소스 코드는 Example4NewBar.mq5 파일로 첨부됩니다.

이 예제를 작성하여 CisNewBar 클래스가 다중 통화 모드에서 어떻게 작동하는지 알아보고 싶습니다. 음, 시작합니다... 내가 가진 것은 다음과 같습니다.


결과에 의문이 제기된다. 저는 불에 연료를 추가하고 전략 테스터에서 바로 이 시간 간격을 실행할 것입니다. 전략 테스터 결과:


그런 다음 "열 가지 차이점 찾기"게임을 할 수 있습니다. 데모 계정에 대한 Expert Advisor의 작업의 이상한 점 외에도 데모 계정과 Strategy Tester 사이에 차이점이 있으며 명확하게 볼 수 있습니다. 올바른 접근 방식과 유사한 비교를 통해 Expert Advisor의 단점을 드러낼 뿐만 아니라 이를 제거할 수도 있습니다. 아마도 저는 그것이 왜 발생했는지, 어떻게 발생했는지, Expert Advisor에서 수정해야 할 사항을 분석하지 않을 것입니다.  

예제 5. 예에서 새로운 바를 감지하는 가장 보편적인 방법인 isNewBar (datetime newbar_time)를 명시적으로 사용한 적이 없습니다. 이를 위해 MQL5에서 눈금 인디케이터 만들기 기사에서 틱 캔들 스틱을 가져 와서 바 열기 시간을 저장할 버퍼를 추가합니다 (파일 TickColorCandles v2.00.mq5). 새로운 티크 캔들스틱의 시간에 대해 알려주는 아주 짧은 Expert Advisor를 작성할 것입니다 (파일 Example5NewBar.mq5):

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include <Lib CisNewBar.mqh>

CisNewBar newbar_ind; // instance of the CisNewBar class: detect new tick candlestick
int HandleIndicator;  // indicator handle
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Get indicator handle:
   HandleIndicator=iCustom(_Symbol,_Period,"TickColorCandles v2.00",16,0,""); 
   if(HandleIndicator==INVALID_HANDLE)
     {
      Alert(" Error when creating indicator handle, error code: ",GetLastError());
      Print(" Incorrect initialization of Expert Advisor. Trade is not allowed.");
      return(1);
     }

//--- Attach indicator to chart:  
   if(!ChartIndicatorAdd(ChartID(),1,HandleIndicator))
     {
      Alert(" Error when attaching indicator to chart, error code: ",GetLastError());
      return(1);
     }
//--- If you passed until here, initialization was successful
   Print(" Successful initialization of Expert Advisor. Trade is allowed.");
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double iTime[1];

//--- Get time of opening last unfinished tick candlestick:
   if(CopyBuffer(HandleIndicator,5,0,1,iTime)<=0)
     {
      Print(" Failed to get time value of indicator. "+
            "\nNext attempt to get indicator values will be made on the next tick.",GetLastError());
      return;
     }
//--- Detect the next tick candlestick:
   if(newbar_ind.isNewBar((datetime)iTime[0]))
     {
      PrintFormat("New bar. Opening time: %s  Time of last tick: %s",
                  TimeToString((datetime)iTime[0],TIME_SECONDS),
                  TimeToString(TimeCurrent(),TIME_SECONDS));
     }
  }

확실히 당신은 우리가 틱 캔들스틱이 열리는 시간을 얻는 방법을 눈치 챘을 것입니다. 아주 쉽죠? 인디케이터와 Expert Advisor를 폴더에 넣고 Expert Advisor를 컴파일하고 실행합니다. 작동하며 결과는 다음과 같습니다.  

 

"New Bar"이벤트 핸들러


이 글의 마지막 부분에서 다른 아이디어를 공유하고 싶습니다. 포럼 (러시아어)에서 표준 "new bar" 이벤트 핸들러가 있으면 좋겠다는 생각이 있었습니다. 한 번 개발자가 여기에 올 수도 있지만 그렇지 않을 수도 있습니다. 그러나 가장 놀라운 아이디어를 우아하고 간단하게 구현할 수 있다는 MQL5의 아름다움.

"new bar" 이벤트 핸들러 (또는 NewBar)를 갖고 싶다면 - 그럼 만들어 봅시다! 특히 우리 클래스를 이용하면 이 이벤트를 쉽게 잡을 수 있습니다. 이것이 우리의 전문가 (NewBar 이벤트 핸들러 OnNewBar () 사용)가 다음과 같은 모습입니다.

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar-2010@mail.ru"
#property version   "1.00"

#include "OnNewBar.mqh" // Here is the secret of launching the "new bar" event handler

//+------------------------------------------------------------------+
//| New bar event handler function                                   |
//+------------------------------------------------------------------+
void OnNewBar()
  {
   PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS));
  }

꽤 좋아보인다. Expert Advisor는 매우 간단해 보입니다. 이 핸들러는 새로운 바에 대한 문자열을 인쇄합니다. 그게 그가 하는 전부입니다. NewBar 이벤트를 추적하는 방법과 핸들러를 실행하는 방법을 이해하려면 OnNewBar.mqh 파일을 살펴 봐야 합니다.

#property copyright "Copyright 2010, Lizar"
#property link      "Lizar@mail.ru"

#include <Lib CisNewBar.mqh>
CisNewBar current_chart; // instance of the CisNewBar class: current chart

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   int period_seconds=PeriodSeconds(_Period);                     // Number of seconds in current chart period
   datetime new_time=TimeCurrent()/period_seconds*period_seconds; // Time of bar opening on current chart
   if(current_chart.isNewBar(new_time)) OnNewBar();               // When new bar appears - launch the NewBar event handler
  }

보시다시피 여기에도 복잡한 것이 없습니다. 하지만 여러분의 관심을 끌고 싶은 몇 가지 순간이 있습니다.

첫 번째. 아시다시피 TimeCurrent() 함수를 사용하여 바가 열리는 시간을 계산하고 클래스에서 NewBar 이벤트를 확인하는 첫 번째 방법을 사용합니다. 이것은 좋은 장점입니다. SERIES_LASTBAR_DATE 수정 자와 함께 SeriesInfoInteger()를 사용할 때와 같이 이러한 메소드는 오류 처리가 필요하지 않다는 사실에 있습니다. 우리에게는 OnNewBar () 핸들러가 가능한 한 신뢰할 수 있어야 하기 때문에 중요합니다.

. TimeCurrent() 함수를 사용하여 바가 열리는 시간을 계산하는 것이 가장 빠른 방법입니다. 같은 목적으로 오류 제어 없이도 SeriesInfoInteger() 함수를 사용하는 것은 더 느린 방법입니다.

핸들러의 결과:

   

결론

  자료 발표 과정에서 우리는 새로운 바를 감지하는 방법을 잘 분석했습니다. 새로운 바를 감지하는 기존 방법의 장단점을 공개했습니다. 우리가 가지고있는 것을 기반으로 CisNewBar 클래스를 만들었습니다. 프로그래밍에 추가 비용없이 거의 모든 작업에서 "새로운 바"이벤트를 포착 할 수 있습니다. 동시에 우리는 이전 솔루션의 대부분의 불편함을 제거했습니다.    

이러한 예는 우리가 발명한 방법의 장단점을 이해하는 데 도움이 되었습니다. 올바른 작업 측면에서 특별한 주의를 기울이려면 다중 통화 모드가 필요합니다. 식별된 비효율성을 철저히 분석하고 이를 해결하는 방법을 개발해야 합니다.

  생성된 "새로운 바" 이벤트 핸들러는 단일 통화 Expert Advisor에게만 적합합니다. 그러나 우리는 이 목적을 위해 가장 안정적이고 빠른 방법을 사용하는 법을 배웠습니다. 이제 계속해서 다중 통화 NewBar 이벤트 핸들러를 만들 수 있습니다. 그러나 이것은 다른 기사의 주제입니다.  

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

성장하는 신경 가스: MQL5 구현 성장하는 신경 가스: MQL5 구현
이 글은 성장 신경 가스 (GNG)라고하는 클러스터링의 적응 알고리즘을 구현하는 MQL5 프로그램을 개발하는 방법의 예를 보여줍니다. 이 글은 언어 문서를 공부하고 신경 정보학 분야에서 특정 프로그래밍 기술과 기본 지식을 보유한 사용자를 대상으로 합니다.
Simulink: Expert Advisor 개발자를 위한 가이드 Simulink: Expert Advisor 개발자를 위한 가이드
저는 전문 프로그래머가 아닙니다. 따라서 거래 시스템 개발 작업을 할 때 "단순한 것에서 복잡한 것으로가는 것"의 원칙이 가장 중요합니다. 나에게 정확히 무엇이 간단합니까? 우선 그것은 시스템을 만드는 과정과 그 작업의 논리를 시각화하는 것입니다. 또한 최소한의 수기 코드입니다. 이 기사에서는 Matlab 패키지를 기반으로 거래 시스템을 만들고 테스트한 다음 MetaTrader 5에 대한 Expert Advisor를 작성하려고 합니다. MetaTrader 5의 과거 데이터는 테스트 프로세스에 사용됩니다.
MQL5 마법사: 프로그래밍 없이 Expert Advisor 만들기 MQL5 마법사: 프로그래밍 없이 Expert Advisor 만들기
프로그래밍에 시간을 낭비하지 않고 거래 전략을 시도하고 싶습니까? MQL5 마법사에서 거래 신호 유형을 선택하고 추적 포지션 및 자금 관리 모듈을 추가하면 작업이 완료됩니다! 고유한 모듈 구현을 생성하거나 작업 서비스를 통해 주문하고 새 모듈을 기존 모듈과 결합합니다.
오류 찾기 및 로깅 오류 찾기 및 로깅
MetaEditor 5에는 디버깅 기능이 있습니다. 그러나 MQL5 프로그램을 작성할 때 종종 개별 값이 아니라 테스트 및 온라인 작업 중에 나타나는 모든 메시지를 표시하려고 합니다. 로그 파일 내용이 큰 경우 필요한 메시지의 빠르고 쉬운 검색을 자동화하는 것이 분명합니다. 이 기사에서는 MQL5 프로그램에서 오류를 찾는 방법과 로깅 방법을 고려할 것입니다. 또한 파일 로그인을 단순화하고 로그를 편안하게 볼 수 있는 간단한 프로그램인 LogMon을 알게 될 것입니다.