English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
Expert Advisor에서 OnTrade() 함수를 이용한 거래 이벤트 처리

Expert Advisor에서 OnTrade() 함수를 이용한 거래 이벤트 처리

MetaTrader 5 | 1 7월 2021, 10:32
86 0
KlimMalgin
KlimMalgin

소개

MQL에 전문가를 작성하는 트레이더는 조만간 전문가가 어떻게 일하고 있는지 보고해야 할 필요성에 직면하게 됩니다. 또는 전문가의 작업에 대한 SMS 또는 이메일 알림을 구현해야 할 수도 있습니다. 어쨌든 우리는 시장에서 발생하는 특정 이벤트 나 전문가의 행동을 "잡아서"사용자에게 알려야 합니다.

이 기사에서는 거래 이벤트 처리를 구현하고 제 구현을 제공하는 방법에 대해 설명하고 싶습니다.

이 기사에서는 다음 이벤트 처리를 고려할 것입니다.

  • 위치
    1. 열기
    2. 추가
    3. 수정 (손절매 변경 및 이익 실현)
    4. 역전
    5. 전체 포지션 닫기
    6. 포지션의 일부 닫기
  • 주문을 보류
    1. 장소
    2. 수정

1. 어떻게 작동합니까? 

시작하기 전에 일반적인 용어로 거래 이벤트의 작동 방식을 설명하고 필요한 모든 세부 정보를 즉시 설명하겠습니다.

MQL5에는 사전 정의 된사용자 정의 이벤트가 있습니다. 특히 Trade 이벤트에서 사전 정의된 항목에 관심이 있습니다.

거래 이벤트는 거래가 완료될 때마다 생성됩니다. 거래 이벤트 생성 후 매번 OnTrade() 함수가 호출됩니다. 주문 및 포지션 처리는 OnTrade() 함수 내에서 정확하게 이루어집니다.

2. 전문가 템플릿

이제 새로운 Expert Advisor를 만들어 보겠습니다. MetaEditor에서 파일 -> 새로 만들기를 클릭하여 MQL5 마법사를 시작합니다. Expert Advisor를 선택하고 Next를 클릭합니다. "전문가 고문의 일반 속성"대화 상자에서 전문 고문의 이름과 필요한 경우 자신의 데이터를 입력합니다. 저는 전문가 고문을 "TradeControl"로 지명했습니다. 이 이름을 사용하거나 직접 선택할 수 있지만 중요하지 않습니다. 전문가를 작성할 때 즉석에서 생성되므로 매개 변수를 지정하지 않습니다.

끝난! Expert Advisor 템플릿이 생성되면 여기에 OnTrade() 함수를 추가해야 합니다.

결과적으로 다음 코드를 받아야 합니다.

//+------------------------------------------------------------------+
//|                                              TradeControl_en.mq5 |
//|                                             Copyright KlimMalgin |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "KlimMalgin"
#property link      ""
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| OnTrade function                                                 |
//+------------------------------------------------------------------+
void OnTrade()
  {
//---

//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

3. 포지션 작업

가장 간단한 거래 이벤트인 포지션 개시 및 마감부터 시작하겠습니다. 먼저 "판매" 및 "구매"버튼을 누른 후 어떤 프로세스가 발생하는지 이해해야 합니다.

OnTrade() 함수를 호출하면:

Alert("The Trade event occurred");

그런 다음 시장 함수 OnTrade()에 의한 개장 후 Alert가 4 번 실행되었음을 알 수 있습니다.

그림 1. 경고

그림 1. 경고

OnTrade()가 4 번 호출되는 이유와 이러한 경고에 어떻게 대응할 수 있습니까? 이를 이해하기 위해 문서를 살펴 보겠습니다.

 OnTrade

이 함수는 Trade 이벤트가 발생할 때 호출됩니다. 이는 주문한 주문, 열린 포지션, 주문 내역거래 내역의 목록이 변경 될 때 발생합니다.

여기에서 한 가지를 언급해야 합니다.

이 기사를 작성하고 개발자와 소통 할 때 역사의 변화가 OnTrade() 호출로 이어지지 않음을 발견했습니다! 사실 OnTrade() 함수는 주문 및 오픈 포지션 목록이 변경 될 때만 호출됩니다! 거래 이벤트 핸들러를 개발할 때 실행 된 주문 및 거래가 지연된 내역에 나타날 수 있다는 사실에 직면 할 수 있으며 OnTrade() 함수가 실행 중일 때 이를 처리 할 수 ​​없습니다.  

이제 이벤트로 돌아갑시다. 이미 살펴본 바와 같이 시장별로 개장하면 거래 이벤트가 4 번 발생합니다.

  1. 시장 별 오픈 주문 생성.
  2. 거래 실행.
  3. 완전한 주문을 역사에 전달합니다.
  4. 포지션 오프닝.

  터미널에서 이 프로세스를 추적하려면 MetaTrader 창의 "거래"탭에 있는 주문 목록을 확인하십시오. 

그림 2. "거래"탭의 주문 목록

그림 2. "거래"탭의 주문 목록

포지션을 오픈하면 (예 : 다운) 주문 목록에 시작됨 상태의 주문이 나타납니다 (그림 2). 이렇게 하면 주문 목록이 변경되고 거래 이벤트가 호출됩니다. OnTrade() 기능이 활성화된 것은 이번이 처음입니다. 그러면 생성된 주문으로 거래가 실행됩니다. 이 단계에서 OnTrade() 함수가 두 번째로 실행됩니다. 거래가 실행 되자마자 완료된 주문과 실행된 거래가 히스토리로 전송되고 OnTrade() 함수가 세 번째로 호출됩니다. 마지막 단계에서 포지션은 실행된 거래에 의해 열리고 OnTrade() 함수는 네 번째로 호출됩니다.

포지션 오픈의 순간을 포착하려면 OnTrade()를 호출할 때마다 주문 목록, 주문 내역 및 거래 내역을 분석해야 합니다. 이것이 우리가 지금 할 일입니다!

좋습니다. OnTrade() 함수가 호출되고 "Trade"탭에서 주문 수가 변경되었는지 알아야 합니다. 이를 위해 이전 OnTrade() 호출 당시와 현재 목록에있는 주문 수를 비교해야 합니다. 현재 목록에있는 주문 수를 확인하기 위해 OrdersTotal() 함수를 사용합니다. 그리고 이전 호출에서 얼마나 많은 주문이 나열되었는지 알기 위해 각 OnTrade() 호출에서 OrdersTotal() 값을 유지해야 합니다. 이를 위해 특수 변수를 생성합니다.

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call

OnTrade() 함수의 끝에서 OrdersPrev 변수는 OrdersTotal() 값으로 할당됩니다.

Expert Advisor를 실행할 때도 상황을 고려해야하며 목록에 이미 보류중인 주문이 있습니다. 전문가가 이를 발견 할 수 있어야하므로 OnInit() 함수에서 OrdersPrev 변수에도 OrdersTotal() 값을 할당해야 합니다. Expert에서 방금 변경한 내용은 다음과 같습니다. 

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   OrdersPrev = OrdersTotal();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| OnTrade function                                                 |
//+------------------------------------------------------------------+
void OnTrade()
  {
//---

OrdersPrev = OrdersTotal();
//---
  }

이제 현재 및 이전 호출에 대한 주문 수를 알았으므로 주문이 목록에 언제 나타나고 어떤 이유로 든 그가 사라진 시기를 알 수 있습니다. 이를 위해 다음 조건을 사용합니다.

if (OrdersPrev < OrdersTotal())
{
  // Order appeared
}
else if(OrdersPrev > OrdersTotal())
{
  // Order disappeared
}

따라서 이전 호출의 주문이 지금보다 적 으면 주문이 목록에 표시되지만 (여러 주문은 동시에 표시 될 수 없음) 반대의 경우, 즉 이전 OnTrade( ) 호출하면 어떤 이유로 주문이 실행되거나 취소됩니다. 직위와 관련된 거의 모든 작업은 이 두 가지 조건에서 시작됩니다.

손절매와 이익 실현에만 별도의 작업이 필요합니다. OnTrade() 함수에 포지션과 함께 작동하는 코드를 추가하겠습니다. 그것을 고려해봅시다:

void OnTrade()
  {
//---
Alert("Trade event occurred");

HistorySelect(start_date,TimeCurrent());

if (OrdersPrev < OrdersTotal())
{
   OrderGetTicket(OrdersTotal()-1);// Select the last order to work with
   _GetLastError=GetLastError();
   Print("Error #",_GetLastError);ResetLastError();
   //--
   if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
   {
      Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
      LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
   }
   
}
else if(OrdersPrev > OrdersTotal())
{
   state = HistoryOrderGetInteger(LastOrderTicket,ORDER_STATE);

   // If order is not found, generate an error
   _GetLastError=GetLastError();
   if (_GetLastError != 0){Alert("Error #",_GetLastError," Order is not found!");LastOrderTicket = 0;}
   Print("Error #",_GetLastError," state: ",state);ResetLastError();


   // If order is fully executed
   if (state == ORDER_STATE_FILLED)
   {
      // Then analyze the last deal
      // --
      Alert(LastOrderTicket, "Order executed, going to deal");
      switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY))
      {
         
         // Entering the market
         case DEAL_ENTRY_IN:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
               // If volumes of position and deal are equal, then position has just been opened
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Buy position has been opened on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  else
               // If volumes of position and deal are not equal, then position has been incremented
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Buy position has incremented on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
               break;
               
               case 1:
               // If volumes of position and deal are equal, then position has just been opened
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Sell position has been opened on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  else
               // If volumes of position and deal are not equal, then position has been incremented
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
                  && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
                  {
                     Alert("Sell position has incremented on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
                  }
                  
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Exiting the market
         case DEAL_ENTRY_OUT:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
               // If position, we tried to close, is still present, then we have closed only part of it
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true)
                  {
                     Alert("Part of Sell position has been closed on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  else
               // If position is not found, then it is fully closed
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false)
                  {
                     Alert("Sell position has been closed on pair ",
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
               break;
               
               case 1:
               // If position, we tried to close, is still present, then we have closed only part of it
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true)
                  {
                     Alert("Part of Buy position has been closed on pair ", 
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  else
               // If position is not found, then it is fully closed
                  if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false)
                  {
                     Alert("Buy position has been closed on pair ",
                           HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                           " with profit = ",
                           HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT));
                  }
                  
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Reverse
         case DEAL_ENTRY_INOUT:
         Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), 
         " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1));
         
            switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
            {
               case 0:
                  Alert("Sell is reversed to Buy on pair ",
                        HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                        " resulting profit = ",
                        HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); 
               break;
               
               case 1:
                  Alert("Buy is reversed to Sell on pair ",
                        HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL),
                        " resulting profit = ",
                        HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); 
               break;
               
               default:
                  Alert("Unprocessed code of type: ",
                        HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
               break;
            }
         break;
         
         // Indicates the state record
         case DEAL_ENTRY_STATE:
            Alert("Indicates the state record. Unprocessed code of type: ", 
            HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
         break;
      }
      // --
   }
}

OrdersPrev = OrdersTotal();

//---
  }

또한 프로그램 시작시 다음 변수를 선언했는지 확인하십시오.

datetime start_date = 0;   // Date, from which we begin to read history

int OrdersPrev = 0;        // Number of orders at the time of previous OnTrade() call
int PositionsPrev = 0;     // Number of positions at the time of previous OnTrade() call
ulong LastOrderTicket = 0; // Ticket of the last processed order

int _GetLastError=0;       // Error code
long state=0;              // Order state

OnTrade()의 내용으로 돌아갑시다.

경고는 처음에 주석으로 처리 할 수 ​​있지만 그대로 두겠습니다. 다음은 HistorySelect() 함수로 이동합니다. 함수의 두 매개 변수로 정의된 지정된 기간 동안 거래 및 주문 내역 목록을 생성합니다. 거래 및 주문 내역으로 이동하기 전에 이 함수를 호출하지 않으면 내역 목록이 비어 있기 때문에 정보를 얻을 수 없습니다. HistorySelect()를 호출 한 후 조건은 직전에 작성된대로 평가됩니다.

새로운 주문이 오면 먼저 주문을 선택하고 오류를 확인합니다.

OrderGetTicket(OrdersTotal()-1);// Select the last order for work
_GetLastError=GetLastError();
Print("Error #",_GetLastError);ResetLastError();

주문을 선택한 후 GetLastError() 함수를 사용하여 오류 코드를 얻습니다. 그런 다음 Print() 함수를 사용하여 코드를 저널에 인쇄하고 ResetLastError() 함수를 사용하여 오류 코드를 0으로 재설정하므로 다음 GetLastError() 다른 상황에서는 동일한 오류 코드가 표시되지 않습니다.

오류를 확인한 후 주문이 성공적으로 선택되었으면 상태를 확인합니다.

if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
{
   Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
   LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
}

주문이 시작됨 상태인 경우, 즉 정확성이 확인되었지만 아직 승인되지 않은 경우 가까운 장래에 실행될 것으로 예상되며 간단히 Alert() 주문이 처리되고 있음을 알리고 다음 OnTrade() 호출시 티켓을 저장합니다. Alert() 대신 다른 종류의 알림을 사용할 수 있습니다.

위의 코드에서

OrderGetTicket(OrdersTotal()-1)

전체 주문 목록에서 마지막 주문 티켓을 반환합니다.

OrdersTotal()-1은 최신 주문을 받아야함을 나타냅니다. OrdersTotal() 함수는 총 주문 수를 반환하므로 (예 : 목록에 주문이 1 개 있으면 OrdersTotal()은 1을 반환) 주문 인덱스 번호는 0부터 계산됩니다. 마지막 주문의 인덱스 번호를 얻으려면 총 주문 수에서 1을 빼야 합니다 (OrderTotal()이 1을 반환하면 이 주문의 인덱스 번호는 0이 됩니다). 그리고 OrderGetTicket() 함수는 순서대로 주문 티켓을 반환하고, 번호가 전달됩니다.

그것은 첫 번째 조건이었고 일반적으로 첫 번째 OnTrade() 호출시 트리거됩니다. 다음은 두 번째 OnTrade() 호출에서 충족되는 두 번째 조건입니다. 주문이 실행되면 히스토리로 내려가 포지션이 열릴 것입니다.

목록에서 주문이 누락된 경우 기록에 기록된 것입니다. 확실히 거기에 있어야 합니다! 따라서 주문 상태를 가져 오기 위해 HistoryOrderGetInteger() 함수를 사용하여 주문 내역에 호소합니다. 그리고 특정 주문에 대한 내역 데이터를 읽으려면 티켓이 필요합니다. 이를 위해 첫 번째 조건인 경우 들어오는 주문의 티켓이 LastOrderTicket 변수에 저장됩니다.

따라서 우리는 주문 티켓을 HistoryOrderGetInteger()의 첫 번째 매개 변수로, 필요한 속성의 유형을 두 번째로 나타내는 주문 상태를 얻습니다. 주문 상태를 가져 오려고 시도한 후 오류 코드를 가져와 저널에 씁니다. 우리가 함께 일해야 할 주문이 아직 역사에 들어가지 못한 경우에 필요하며 우리는 그에게 호소합니다 (경험에 따르면 이것이 가능하고 상당히 많습니다. 이 기사의 시작 부분에 이에 대해 썼습니다).

오류가 발생하면 작업할 데이터가 없고 다음 조건이 충족되지 않으므로 처리가 중지됩니다. HistoryOrderGetInteger() 호출이 성공하고 주문 상태가 "Order is fully execution"인 경우:

// If order is fully executed
if (state == ORDER_STATE_FILLED)

그런 다음 다른 알림을 제공합니다.

// Then analyze the last deal
// --
  Alert(LastOrderTicket, "Order executed, going to deal");

그리고 이 주문에 의해 호출된 거래를 처리해 보겠습니다. 먼저 거래 방향을 찾으세요 (DEAL_ENTRY 속성). 방향은 매수 또는 매도가 아니라 시장 진입, 시장 나가기, 반전입니다. 또는 주 기록 표시. 따라서 DEAL_ENTRY 속성을 사용하여 주문이 오픈 포지션, 클로즈 포지션 또는 리버스로 설정되었는지 확인할 수 있습니다. 

거래와 그 결과를 분석하기 위해 다음 구성을 사용하여 여태까지의 내역에 호소해봅니다.

switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY))
{
  ...
}

주문과 동일하게 작동합니다.

HistoryDealsTotal()은 총 거래 수를 반환합니다 최신 거래 수를 얻으려면 HistoryDealsTotal() 값에서 1을 뺍니다. 결과 거래 수는 HistoryDealGetTicket() 함수로 전달되고, 그러면 선택한 거래의 티켓이 HistoryDealGetInteger() 함수로 전달됩니다. 그리고 지정된 티켓 및 속성 유형에 의한 HistoryDealGetInteger()는 거래 방향을 반환합니다.

시장 진출 방향을 자세히 살펴 보겠습니다. 다른 방향은 거의 동일한 방식으로 처리되므로 간략하게 설명합니다.

HistoryDealGetInteger()에서 얻은 expression의 값은 일치가 발견 될 때까지 케이스 블록의 값과 비교됩니다. 우리가 시장에 진입한다고 가정합니다. 즉, 판매 주문을 시작합니다. 그런 다음 첫 번째 블록이 실행됩니다.

// Entering the market
case DEAL_ENTRY_IN:

블록이 시작되면 거래 생성에 대한 알림을 받습니다. 동시에 이 알림은 모든 것이 정상이고 거래가 처리되고 있는지 확인합니다.

알림 후 또 다른 스위치 블록이 발생하여 거래 유형을 분석합니다.

   switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE))
   {
      case 0:
      // If volumes of position and deal are equal, then position has just been opened
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Buy position has been opened on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         else
      // If volumes of position and deal are not equal, then position has been incremented
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Buy position has incremented on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
      break;
      
      case 1:
      // If volumes of position and deal are equal, then position has just been opened
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Sell position has been opened on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         else
      // If volumes of position and deal are not equal, then position has been incremented
         if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) 
         && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME)))
         {
            Alert("Sell position has incremented on pair ", 
                  HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); 
         }
         
      break;
      
      default:
         Alert("Unprocessed code of type: ",
               HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE));
      break;
   }

지정된 속성을 제외하고 이전과 동일한 방식으로 내역에서 거래에 대한 정보를 가져옵니다. 이번에는 DEAL_TYPE을 지정하여 구매 또는 판매 거래가 이루어졌는지 확인해야 합니다. 매수 및 매도 유형만 분석하지만 그 외에 네 가지가 더 있습니다. 그러나 나머지 4 가지 유형의 거래는 덜 일반적이므로 4 개의 케이스 블록 대신 하나의 기본 블록만 사용됩니다. 유형 코드와 함께 Alert()를 제공합니다.

코드에서 알 수 있듯이 Buy 및 Sell 포지션의 개시뿐만 아니라 증가분도 처리됩니다. 포지션이 증분된 시점과 오픈된 시점을 확인하려면 이 거래의 결과가 된 실행된 거래의 양과 포지션을 비교해야 합니다. 포지션 볼륨이 실행 된 딜의 볼륨과 같으면-이 포지션은 오픈되었고 포지션과 딜의 볼륨이 다른 경우-이 포지션이 증가되었습니다. 이는 매수 포지션 ( '0'블록의 경우)과 매도 포지션 ( '1'블록의 경우) 모두에 적용됩니다. 마지막 블록은 기본값이며 구매 및 판매를 제외한 모든 상황을 처리합니다. 전체 처리는 HistoryDealGetInteger() 함수에 의해 반환 된 유형 코드에 대한 알림으로 구성됩니다.

그리고 마지막으로 직위 작업에 대한 마지막 관심사입니다. 이것은 손절매와 이익 실현 가치의 변화를 처리하는 것입니다. 위치 매개 변수 중 어느 것이 변경되었는지 확인하려면 해당 매개 변수의 현재 상태와 이전 상태를 비교해야 합니다. 위치 매개 변수의 현재 값은 항상 서비스 기능을 사용하여 얻을 수 있지만 이전 값은 저장해야 합니다.

이를 위해 구조 배열에 위치 매개 변수를 저장하는 특수 함수를 작성합니다.

void GetPosition(_position &Array[])
  {
   int _GetLastError=0,_PositionsTotal=PositionsTotal();

   int temp_value=(int)MathMax(_PositionsTotal,1);
   ArrayResize(Array, temp_value);

   _ExpertPositionsTotal=0;
   for(int z=_PositionsTotal-1; z>=0; z--)
     {
      if(!PositionSelect(PositionGetSymbol(z)))
        {
         _GetLastError=GetLastError();
         Print("OrderSelect() - Error #",_GetLastError);
         continue;
        }
      else
        {
            // If the position is found, then put its info to the array
            Array[z].type         = PositionGetInteger(POSITION_TYPE);
            Array[z].time         = PositionGetInteger(POSITION_TIME);
            Array[z].magic        = PositionGetInteger(POSITION_MAGIC);
            Array[z].volume       = PositionGetDouble(POSITION_VOLUME);
            Array[z].priceopen    = PositionGetDouble(POSITION_PRICE_OPEN);
            Array[z].sl           = PositionGetDouble(POSITION_SL);
            Array[z].tp           = PositionGetDouble(POSITION_TP);
            Array[z].pricecurrent = PositionGetDouble(POSITION_PRICE_CURRENT);
            Array[z].comission    = PositionGetDouble(POSITION_COMMISSION);
            Array[z].swap         = PositionGetDouble(POSITION_SWAP);
            Array[z].profit       = PositionGetDouble(POSITION_PROFIT);
            Array[z].symbol       = PositionGetString(POSITION_SYMBOL);
            Array[z].comment      = PositionGetString(POSITION_COMMENT);
        _ExpertPositionsTotal++;
        }
     }

   temp_value=(int)MathMax(_ExpertPositionsTotal,1);
   ArrayResize(Array,temp_value);
  }

이 함수를 사용하려면 전역 변수 선언 블록에 다음 코드를 추가해야 합니다.

/*
 *
 * Structure that stores information about positions
 *
 */
struct _position
{

long     type,          // Position type
         magic;         // Magic number for position
datetime time;          // Time of position opening

double   volume,        // Position volume
         priceopen,     // Position price
         sl,            // Stop Loss level for opened position
         tp,            // Take Profit level for opened position
         pricecurrent,  // Symbol current price
         comission,     // Commission
         swap,          // Accumulated swap
         profit;        // Current profit

string   symbol,        // Symbol, by which the position has been opened
         comment;       // Comment to position
};

int _ExpertPositionsTotal = 0;

_position PositionList[],     // Array that stores info about position
          PrevPositionList[];

GetPosition() 함수 프로토 타입은 오래 전에 www.mql4.com 기사에서 발견되었지만 지금은 찾을 수 없고 소스를 지정할 수도 없습니다. 이 기능에 대한 자세한 내용은 다루지 않겠습니다. 요점은 참조에 의한 매개 변수로 _position 유형 (위치 필드에 해당하는 필드가있는 구조)의 배열을 전달했으며, 여기에 현재 열린 위치 및 해당 매개 변수 값에 대한 모든 정보가 전달됩니다.

위치 매개 변수의 변경 사항을 편리하게 추적하기 위해 _position 유형의 두 배열을 만들어 보겠습니다. PositionList[] (현재 위치 상태) 및 PrevPositionList[] (이전 위치 상태)입니다.

포지션 작업을 시작하려면 다음 호출을 OnInit() 및 OnTrade() 끝에 추가해야 합니다.

GetPosition(PrevPositionList);

또한 Ontrade() 시작 부분에 호출을 추가해야 합니다.

GetPosition(PositionList);

이제 PositionList[] 및 PrevPositionList[] 배열에는 각각 현재 및 이전 OnTrade() 호출의 위치에 대한 정보가 있습니다.

이제 sl 및 tp의 변경 사항을 추적하는 실제 코드를 고려해 보겠습니다.

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
{
   string _alerts = "";
   bool modify = false;
     
   for (int i=0;i<_ExpertPositionsTotal;i++)
   {
      if (PrevPositionList[i].sl != PositionList[i].sl)
      {
         _alerts += "On pair "+PositionList[i].symbol+" Stop Loss changed from "+ PrevPositionList[i].sl +" to "+ PositionList[i].sl +"\n";
         modify = true;
      }
      if (PrevPositionList[i].tp != PositionList[i].tp)
      {
         _alerts += "On pair "+PositionList[i].symbol+" Take Profit changed from "+ PrevPositionList[i].tp +" to "+ PositionList[i].tp +"\n";
         modify = true;
      }
      
   }
   if (modify == true)
   {
      Alert(_alerts);
      modify = false;
   }
}

보시다시피 코드가 너무 크지는 않지만 상당한 준비 작업 때문입니다. 자세히 살펴 보겠습니다.

모든 것은 조건으로 시작됩니다.

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))

여기서 우리는 주문이나 포지션이 배치되거나 삭제되지 않았음을 알 수 있습니다. 조건이 충족되면 일부 위치 또는 주문의 매개 변수가 변경되었을 가능성이 큽니다.

함수 시작 부분에 두 개의 변수가 선언됩니다.

  • _alerts - 변경 사항에 대한 모든 알림을 저장합니다.
  • 수정 - 실제로 변경된 경우에만 변경 사항에 대한 메시지를 표시 할 수 있습니다.

다음으로 루프에서 각 포지션에 대한 이전 및 현재 호출 OnTrade()에서 이익 실현 및 손절매의 값이 일치하는지 확인합니다. 모든 불일치에 대한 정보는 _alerts 변수에 저장되고 나중에 Alert() 함수에 의해 표시됩니다. 그건 그렇고, 보류 주문 수정 처리는 동일한 방식으로 수행됩니다.

지금은 포지션을 끝내고 대기 주문 배치를 진행하겠습니다.

4. 주문 작업

주문 보류 이벤트 배치부터 시작하겠습니다.

새로운 보류 주문이 나타나면 거래 이벤트가 한 번만 생성되지만 처리하기에 충분합니다! 보류중인 주문과 함께 작동하는 코드를 연산자 본문에 넣습니다.

if (OrdersPrev < OrdersTotal())

그리고 다음을 얻으십시오.

if (OrdersPrev < OrdersTotal())
{
   OrderGetTicket(OrdersTotal()-1);// Select the last order to work with
   _GetLastError=GetLastError();
   Print("Error #",_GetLastError);ResetLastError();
   //--
   if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED)
   {
      Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing");
      LastOrderTicket = OrderGetTicket(OrdersTotal()-1);    // Saving the order ticket for further work
   }
   
   state = OrderGetInteger(ORDER_STATE);
   if (state == ORDER_STATE_PLACED)
   {
      switch(OrderGetInteger(ORDER_TYPE))
      {
         case 2:
            Alert("Pending order Buy Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 3:
            Alert("Pending order Sell Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 4:
            Alert("Pending order Buy Stop #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 5:
            Alert("Pending order Sell Stop #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
         
         case 6:
            Alert("Pending order Buy Stop Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;
                 
         case 7:
            Alert("Pending order Sell Stop Limit  #", OrderGetTicket(OrdersTotal()-1)," accepted!");
         break;         
      }
   }
}

여기에서 보류중인 주문과 함께 작동하는 코드는 다음으로 시작합니다.

   state = OrderGetInteger(ORDER_STATE);
   if (state == ORDER_STATE_PLACED)
   {

먼저 주문 상태를 확인합니다. 주문은 ORDER_STATE_PLACED 상태여야 합니다. 즉, 수락되어야 합니다. 이 조건이 충족되면 주문 유형에 따라 메시지를 인쇄하는 switch 연산자가 나타납니다.

다음으로 주문이 수정 될 때 발생하는 이벤트로 작업하겠습니다 주문 수정은 위치 수정과 유사합니다. 마찬가지로 주문 속성을 저장하는 구조가 생성됩니다. 

/*
 *
 * Structure that stores information about orders
 *
 */
struct _orders
{

datetime time_setup,       // Time of order placement
         time_expiration,  // Time of order expiration
         time_done;        // Time of order execution or cancellation
         
long     type,             // Order type
         state,            // Order state
         type_filling,     // Type of execution by remainder
         type_time,        // Order lifetime
         ticket;           // Order ticket
         
long     magic,            // Id of Expert Advisor, that placed an order
                           // (intended to ensure that each Expert 
                           // must place it's own unique number)
                           
         position_id;      // Position id, that is placed on order,
                           // when it is executed. Each executed order invokes a
                           // deal, that opens new or changes existing 
                           // position. Id of that position is placed on 
                           // executed order in this moment.
                           
double volume_initial,     // Initial volume on order placement
       volume_current,     // Unfilled volume
       price_open,         // Price, specified in the order
       sl,                 // Stop Loss level
       tp,                 // Take Profit level
       price_current,      // Current price by order symbol
       price_stoplimit;    // Price of placing Limit order when StopLimit order is triggered
       
string symbol,             // Symbol, by which the order has been placed
       comment;            // Comment
                           
};

int _ExpertOrdersTotal = 0;

_orders OrderList[],       // Arrays that store info about orders
        PrevOrderList[];

구조의 각 필드는 주문 속성 중 하나에 해당합니다. 구조체 선언 후 int 형의 변수와 _orders 형의 두 배열이 선언됩니다. _ExpertOrdersTotal 변수는 총 주문수를 저장하고 OrderList[] 및 PrevOrderList[] 배열은 각각 현재 및 이전 OnTrade() 호출의 주문에 대한 정보를 저장합니다.

함수 자체는 다음과 같습니다.

void GetOrders(_orders &OrdersList[])
  {
   
   int _GetLastError=0,_OrdersTotal=OrdersTotal();

   int temp_value=(int)MathMax(_OrdersTotal,1);
   ArrayResize(OrdersList,temp_value);

   _ExpertOrdersTotal=0;
   for(int z=_OrdersTotal-1; z>=0; z--)
     {
      if(!OrderGetTicket(z))
        {
         _GetLastError=GetLastError();
         Print("GetOrders() - Error #",_GetLastError);
         continue;
        }
      else
        {
        OrdersList[z].ticket          = OrderGetTicket(z);
        OrdersList[z].time_setup      = OrderGetInteger(ORDER_TIME_SETUP);
        OrdersList[z].time_expiration = OrderGetInteger(ORDER_TIME_EXPIRATION);
        OrdersList[z].time_done       = OrderGetInteger(ORDER_TIME_DONE);
        OrdersList[z].type            = OrderGetInteger(ORDER_TYPE);
        
        OrdersList[z].state           = OrderGetInteger(ORDER_STATE);
        OrdersList[z].type_filling    = OrderGetInteger(ORDER_TYPE_FILLING);
        OrdersList[z].type_time       = OrderGetInteger(ORDER_TYPE_TIME);
        OrdersList[z].magic           = OrderGetInteger(ORDER_MAGIC);
        OrdersList[z].position_id     = OrderGetInteger(ORDER_POSITION_ID);
        
        OrdersList[z].volume_initial  = OrderGetDouble(ORDER_VOLUME_INITIAL);
        OrdersList[z].volume_current  = OrderGetDouble(ORDER_VOLUME_CURRENT);
        OrdersList[z].price_open      = OrderGetDouble(ORDER_PRICE_OPEN);
        OrdersList[z].sl              = OrderGetDouble(ORDER_SL);
        OrdersList[z].tp              = OrderGetDouble(ORDER_TP);
        OrdersList[z].price_current   = OrderGetDouble(ORDER_PRICE_CURRENT);
        OrdersList[z].price_stoplimit = OrderGetDouble(ORDER_PRICE_STOPLIMIT);
        
        OrdersList[z].symbol          = OrderGetString(ORDER_SYMBOL);
        OrdersList[z].comment         = OrderGetString(ORDER_COMMENT);
        
        _ExpertOrdersTotal++;
        }
     }

   temp_value=(int)MathMax(_ExpertOrdersTotal,1);
   ArrayResize(OrdersList,temp_value);

  }

GetPosition() 함수와 유사하게 각 주문의 속성에 대한 정보를 읽어서 배열에 넣어 입력 매개 변수로 전달합니다. 함수 코드는 전문가와 그 호출 끝에 다음과 같이 배치해야 합니다.

GetOrders(PrevOrderList);

OnInit() 및 OnTrade() 끝에 배치됩니다.

GetOrders(OrderList);

OnTrade()의 시작 부분에 배치됩니다.

이제 주문 수정을 처리하는 코드를 고려하십시오. 루프이며 위치 수정 코드를 보완합니다.

   for (int i = 0;i<_ExpertOrdersTotal;i++)
   {
      if (PrevOrderList[i].sl != OrderList[i].sl)
      {
         _alerts += "Order "+OrderList[i].ticket+" has changed Stop Loss from "+ PrevOrderList[i].sl +" to "+ OrderList[i].sl +"\n";
         modify = true;
      }
      if (PrevOrderList[i].tp != OrderList[i].tp)
      {
         _alerts += "Order "+OrderList[i].ticket+" has changed Take Profit from "+ PrevOrderList[i].tp +" to "+ OrderList[i].tp +"\n";
         modify = true;
      }
   }

루프는 모든 주문을 처리하고 현재 및 이전 OnTrade() 호출에서 손절매와 이익 실현의 값을 비교합니다. 차이가 있으면 _alerts 변수에 저장되고 루프가 완료되면 Alert() 함수에 의해 표시됩니다.

이 코드는 연산자 본문에 배치됩니다.

if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
{

루프 바로 뒤에 위치와 함께 작동합니다.

지금은 무역 이벤트 작업이 끝나지 않습니다. 이 도움말은 Trade 이벤트와 관련된 작업의 주요 원칙만을 다룹니다. 일반적으로 이 방법이 제공하는 가능성은 상당히 크며 이 문서의 범위를 벗어납니다.

결론

거래 이벤트 (MQL5 언어의 일부)로 작업 할 수 있는 능력은 잠재적으로 강력한 도구로, 주문 확인 알고리즘을 비교적 빠르게 구현하고 거래 보고서를 생성 할 수 있을뿐만 아니라 시스템 리소스 비용과 볼륨을 줄일 수 있습니다. 개발자에게 확실한 이점이 될 소스 코드입니다.

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

파일 첨부됨 |
tradecontrol_en.mq5 (20.25 KB)
MQL5에서 인디케이터를 호출하는 방법 MQL5에서 인디케이터를 호출하는 방법
새로운 버전의 MQL 프로그래밍 언어를 사용할 수 있게 됨에 따라 지표 처리 방식이 변경되었을 뿐만 아니라 지표를 만드는 새로운 방법도 있습니다. 또한 인디케이터 버퍼로 작업 할 수 있는 추가적인 유연성이 있습니다. 이제 원하는 인덱싱 방향을 지정하고 원하는 만큼의 인디케이터 값을 얻을 수 있습니다. 이 문서에서는 인디케이터를 호출하고 인디케이터의 버퍼에서 데이터를 검색하는 기본 방법을 설명합니다.
MQL5 for Newbies의 맞춤 인디케이터 MQL5 for Newbies의 맞춤 인디케이터
새로운 주제는 초보자에게 복잡하고 배우기 어려운 것 같습니다. 우리가 알고있는 주제는 우리에게 매우 간단하고 명확해 보입니다. 그러나 우리는 모든 사람이 처음부터 무언가를 심지어 우리의 모국어로 공부해야 한다는 걸 기억하지 못하는 것 같습니다. 자신의 거래 전략을 개발할 수있는 광범위한 가능성을 제공하는 MQL5 프로그래밍 언어도 마찬가지입니다. 기본 개념과 가장 간단한 예를 통해 학습을 시작할 수 있습니다. 기술 인디케이터와 MetaTrader 5 클라이언트 터미널의 상호 작용은 간단한 사용자 지정 인디케이터 SMA의 예에 대한 이 글에서 고려됩니다.
MQL5의 드로잉 스타일 MQL5의 드로잉 스타일
MQL4에서는 6가지 드로잉 스타일이, MQL5에서는 18가지 드로잉 스타일이 지원됩니다. 그러니까 MQL5의 드로잉 스타일을 알아 보면 되겠죠? 이번 글에서는 MQL5에서 지원되는 드로잉 스타일에 대해 상세히 알아 보겠습니다. 그리고 인디케이터를 생성해서 드로잉 스타일도 설명하고 플롯도 개선해 보도록 할게요.
MQL5에서 객체 포인터 사용 MQL5에서 객체 포인터 사용
기본적으로 MQL5의 모든 오브젝트는 참조로 전달되지만 오브젝트 포인터를 사용할 가능성이 있습니다. 그러나 객체가 초기화되지 않을 수 있으므로 포인터 검사를 수행해야 합니다. 이 경우 MQL5 프로그램은 심각한 오류로 종료되고 언로드됩니다. 자동으로 생성된 객체는 이러한 오류를 일으키지 않으므로 이 점에서 매우 안전합니다. 이 글에서는 개체 참조와 개체 포인터의 차이점을 이해하고 포인터를 사용하는 보안 코드를 작성하는 방법을 고려합니다.