English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
MQL5 Cookbook: 거래 수준을 설정/수정할 때 오류를 피하는 방법

MQL5 Cookbook: 거래 수준을 설정/수정할 때 오류를 피하는 방법

MetaTrader 5 | 2 9월 2021, 17:01
80 0
Anatoli Kazharski
Anatoli Kazharski

소개

"MQL5 Cookbook: MetaTrader 5 Strategy Tester의 포지션 속성 분석" 시리즈의 이전 글에서 Expert Advisor에 대한 작업을 계속하면서 기존의 기능들과 더불어 이를 많은 유용한 기능으로 이를 향상시킬 것입니다.

거래 수준(손절매, 이익실현 및 보류 주문)을 설정/수정할 때 발생하는 오류에 대한 초보자의 질문은 MQL 프로그래밍 포럼에서 드문 일이 아닙니다. 많은 분들이 [Invalid stops]으로 끝나는 저널 메시지에 이미 익숙할 것입니다. 이 글에서는 포지션을 개설/수정하기 전에 거래 수준 값을 정규화하고 정확성을 확인하는 함수를 만들 것입니다.

Expert Advisor는 이번에 MetaTrader 5 전략 테스터에서 최적화할 수 있는 외부 매개변수를 가지며 어떤 면에서는 단순한 거래 시스템과 유사합니다. 실제 거래 시스템을 개발하려면 아직 갈 길이 멉니다. 그러나 로마는 하루아침에 이루어지지 않았습니다. 그래서 우리는 아직 할 일이 많습니다.

글이 펼쳐지면서 기존 기능의 코드 최적화가 고려됩니다. 표준 식별자를 사용하여 얻을 수 없는 일부 포지션 속성을 살펴봐야 하므로 이 시점에서 정보 패널을 다루지 않습니다(거래 내역을 사용해야 함). 그럼에도 불구하고 이 주제는 시리즈의 다음 글 중 하나에서 다룰 것입니다.


Expert Advisor 개발

시작하겠습니다. 평소와 같이 파일 시작 부분에 추가 열거, 변수, 배열 및 보조 함수를 삽입하는 것으로 시작합니다. 심볼 속성을 쉽게 얻을 수 있는 함수가 필요합니다. 포지션 속성을 가져올 때도 동일한 간단한 접근 방식이 필요합니다.

이전 글에서 GetPositionProperties 함수에서 전역 변수에 모든 포지션 속성이 한 번에 할당되는 것을 보았습니다. 이번에는 각 속성을 개별적으로 얻을 수 있는 가능성을 제공하려고 합니다. 다음은 위의 구현을 위한 두 가지 열거입니다. 기능 자체는 잠시 후에 검토할 것입니다.

//--- Enumeration of position properties
enum ENUM_POSITION_PROPERTIES
  {
   P_SYMBOL        = 0,
   P_MAGIC         = 1,
   P_COMMENT       = 2,
   P_SWAP          = 3,
   P_COMMISSION    = 4,
   P_PRICE_OPEN    = 5,
   P_PRICE_CURRENT = 6,
   P_PROFIT        = 7,
   P_VOLUME        = 8,
   P_SL            = 9,
   P_TP            = 10,
   P_TIME          = 11,
   P_ID            = 12,
   P_TYPE          = 13,
   P_ALL           = 14
  };
//--- Enumeration of symbol properties
enum ENUM_SYMBOL_PROPERTIES
  {
   S_DIGITS       = 0,
   S_SPREAD       = 1,
   S_STOPSLEVEL   = 2,
   S_POINT        = 3,
   S_ASK          = 4,
   S_BID          = 5,
   S_VOLUME_MIN   = 6,
   S_VOLUME_MAX   = 7,
   S_VOLUME_LIMIT = 8,
   S_VOLUME_STEP  = 9,
   S_FILTER       = 10,
   S_UP_LEVEL     = 11,
   S_DOWN_LEVEL   = 12,
   S_ALL          = 13
  };

ENUM_SYMBOL_PROPERTIES 열거형에 모든 기호 속성이 포함되어 있지는 않지만 필요에 따라 언제든지 추가할 수 있습니다. 열거형에는 다른 기호 속성을 기반으로 계산되는 사용자 정의 속성(10, 11, 12)도 포함됩니다. 포지션 속성의 열거와 같이 열거에서 한 번에 모든 속성을 가져오는 데 사용할 수 있는 식별자가 있습니다.

다음은 Expert Advisor의 외부 매개변수입니다.

//--- External parameters of the Expert Advisor
input int            NumberOfBars=2;     // Number of Bullish/Bearish bars for a Buy/Sell
input double         Lot         =0.1;   // Lot
input double         StopLoss    =50;    // Stop Loss
input double         TakeProfit  =100;   // Take Profit
input double         TrailingStop=10;    // Trailing Stop
input bool           Reverse     =true;  // Position reverse

외부 매개변수를 자세히 살펴보겠습니다.

  • NumberOfBars - 이 매개변수는 포지션을 여는 방향 바의 수를 설정합니다.
  • Lot - 포지션 볼륨;
  • TakeProfit - 이익 수준을 포인트로 가져옵니다. 값이 0이면 이익실현을 설정할 필요가 없습니다.
  • StopLoss - 손절매 수준(포인트). 0 값은 Stop Loss를 설정할 필요가 없음을 의미합니다.
  • TrailingStop - 후행 중지 값(포인트)입니다. BUY 포지션의 경우 계산은 바의 최소값(최소값에서 StopLoss 매개변수의 포인트 수를 뺀 값)을 기반으로 합니다. SELL 포지션의 경우 계산은 바의 최대값(최대값에 StopLoss 매개변수의 포인트 수를 더한 값)을 기반으로 합니다. 0 값은 Trailing Stop이 꺼져 있음을 나타냅니다.
  • 역방향 역방향 포지션을 활성화/비활성화합니다.

추가 설명이 필요한 것은 NumberOfBars 매개변수 뿐입니다. 예를 들어, 이 매개변수 값을 5 이상으로 설정하는 것은 매우 드물고 그러한 이동 후에 포지션을 여는 것은 이미 늦기 때문에 의미가 없습니다. 따라서 이 매개변수의 값을 조정하는 데 도움이 되는 변수가 필요합니다.

//--- To check the value of the NumberOfBars external parameter
int                  AllowedNumberOfBars=0;

이 매개변수는 가격 배열에 저장될 바 데이터의 양도 결정합니다. 이것은 우리가 커스텀 함수를 수정하게 되면 잠시 후에 논의될 것입니다.

포지션 속성의 경우와 마찬가지로 모든 함수에서 액세스를 제공하기 위해 기호 속성에 대한 전역 범위에서 변수를 선언합니다.

//--- Symbol properties
int                  sym_digits=0;           // Number of decimal places
int                  sym_spread=0;           // Spread in points
int                  sym_stops_level=0;      // Stops level
double               sym_point=0.0;          // Point value
double               sym_ask=0.0;            // Ask price
double               sym_bid=0.0;            // Bid price
double               sym_volume_min=0.0;     // Minimum volume for a deal
double               sym_volume_max=0.0;     // Maximum volume for a deal
double               sym_volume_limit=0.0;   // Maximum permissible volume for a position and orders in one direction
double               sym_volume_step=0.0;    // Minimum volume change step for a deal
double               sym_offset=0.0;         // Offset from the maximum possible price for a transaction
double               sym_up_level=0.0;       // Upper Stop level price
double               sym_down_level=0.0;     // Lower Stop level price

Trailing Stop은 바의 높낮이를 기반으로 계산해야 하므로 이러한 바 데이터에 대한 배열이 필요합니다.

//--- Price data arrays
double               close_price[]; // Close (closing prices of the bar)
double               open_price[];  // Open (opening prices of the bar)
double               high_price[];  // High (bar's highs)
double               low_price[];   // Low (bar's lows)

이제 함수 수정 및 생성을 진행해 보겠습니다. 우리는 이미 바의 시가와 종가를 price 배열에 복사하는 GetBarsData 함수를 가지고 있습니다. 이제 우리에게도 고점과 저점이 필요합니다. 또한 NumberOfBars 매개변수에서 얻은 값을 조정해야 합니다. 수정 후의 기능은 다음과 같습니다.

//+------------------------------------------------------------------+
//| Getting bar values                                               |
//+------------------------------------------------------------------+
void GetBarsData()
  {
//--- Adjust the number of bars for the position opening condition
   if(NumberOfBars<=1)
      AllowedNumberOfBars=2;              // At least two bars are required
   if(NumberOfBars>=5)
      AllowedNumberOfBars=5;              // but no more than 5
   else
      AllowedNumberOfBars=NumberOfBars+1; // and always more by one
//--- Reverse the indexing order (... 3 2 1 0)
   ArraySetAsSeries(close_price,true);
   ArraySetAsSeries(open_price,true);
   ArraySetAsSeries(high_price,true);
   ArraySetAsSeries(low_price,true);
//--- Get the closing price of the bar
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyClose(_Symbol,Period(),0,AllowedNumberOfBars,close_price)<AllowedNumberOfBars)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the Close price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Get the opening price of the bar
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyOpen(_Symbol,Period(),0,AllowedNumberOfBars,open_price)<AllowedNumberOfBars)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the Open price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Get the bar's high
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyHigh(_Symbol,Period(),0,AllowedNumberOfBars,high_price)<AllowedNumberOfBars)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the High price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Get the bar's low
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyLow(_Symbol,Period(),0,AllowedNumberOfBars,low_price)<AllowedNumberOfBars)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the Low price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
  }

인덱스 [1]로 시작하는 완성된 바만 살펴보기 때문에 최소한 두 개의 바가 필요하고 항상 하나 이상의 바가 필요한 조건이 있습니다. 사실 이 경우 조정은 CopyOpen, CopyClose, CopyHighCopyLow 기능. 5개의 바 제한도 사용자의 재량에 따라 변경(위/아래)할 수 있습니다.

NumberOfBars 매개변수에 지정된 바의 수에 따라 조건이 다르게 생성되므로 GetTradingSignal 함수가 이제 조금 더 복잡해졌습니다. 또한 이제 반환된 값의 보다 정확한 유형인 주문 유형을 사용합니다.

//+------------------------------------------------------------------+
//| Determining trading signals                                      |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE GetTradingSignal()
  {
//--- A Buy signal (ORDER_TYPE_BUY) :
   if(AllowedNumberOfBars==2 && 
      close_price[1]>open_price[1])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars==3 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars==4 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2] && 
      close_price[3]>open_price[3])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars==5 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2] && 
      close_price[3]>open_price[3] && 
      close_price[4]>open_price[4])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars>=6 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2] && 
      close_price[3]>open_price[3] && 
      close_price[4]>open_price[4] && 
      close_price[5]>open_price[5])
      return(ORDER_TYPE_BUY);
//--- A Sell signal (ORDER_TYPE_SELL) :
   if(AllowedNumberOfBars==2 && 
      close_price[1]<open_price[1])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars==3 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars==4 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2] && 
      close_price[3]<open_price[3])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars==5 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2] && 
      close_price[3]<open_price[3] && 
      close_price[4]<open_price[4])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars>=6 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2] && 
      close_price[3]<open_price[3] && 
      close_price[4]<open_price[4] && 
      close_price[5]<open_price[5])
      return(ORDER_TYPE_SELL);
//--- No signal (WRONG_VALUE):
   return(WRONG_VALUE);
  }

이제 GetPositionProperties 함수를 수정해 보겠습니다. 이전 글에서는 모든 속성을 한 번에 가져올 수 있었습니다. 그러나 때로는 하나의 속성만 얻어야 할 수도 있습니다. 이렇게 하려면 해당 언어에서 제공하는 표준 기능을 확실히 사용할 수 있지만 우리가 원하는 만큼 편리하지는 않습니다. 다음은 수정된 GetPositionProperties 함수의 코드입니다. 이제 ENUM_POSITION_PROPERTIES 열거에서 특정 식별자를 전달할 때 특정 단일 포지션 속성 또는 모든 속성을 한 번에 가져올 수 있습니다.

//+------------------------------------------------------------------+
//| Getting position properties                                      |
//+------------------------------------------------------------------+
void GetPositionProperties(ENUM_POSITION_PROPERTIES position_property)
  {
//--- Check if there is an open position
   pos_open=PositionSelect(_Symbol);
//--- If an open position exists, get its properties
   if(pos_open)
     {
      switch(position_property)
        {
         case P_SYMBOL        : pos_symbol=PositionGetString(POSITION_SYMBOL);                  break;
         case P_MAGIC         : pos_magic=PositionGetInteger(POSITION_MAGIC);                   break;
         case P_COMMENT       : pos_comment=PositionGetString(POSITION_COMMENT);                break;
         case P_SWAP          : pos_swap=PositionGetDouble(POSITION_SWAP);                      break;
         case P_COMMISSION    : pos_commission=PositionGetDouble(POSITION_COMMISSION);          break;
         case P_PRICE_OPEN    : pos_price=PositionGetDouble(POSITION_PRICE_OPEN);               break;
         case P_PRICE_CURRENT : pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT);           break;
         case P_PROFIT        : pos_profit=PositionGetDouble(POSITION_PROFIT);                  break;
         case P_VOLUME        : pos_volume=PositionGetDouble(POSITION_VOLUME);                  break;
         case P_SL            : pos_sl=PositionGetDouble(POSITION_SL);                          break;
         case P_TP            : pos_tp=PositionGetDouble(POSITION_TP);                          break;
         case P_TIME          : pos_time=(datetime)PositionGetInteger(POSITION_TIME);           break;
         case P_ID            : pos_id=PositionGetInteger(POSITION_IDENTIFIER);                 break;
         case P_TYPE          : pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); break;
         case P_ALL           :
            pos_symbol=PositionGetString(POSITION_SYMBOL);
            pos_magic=PositionGetInteger(POSITION_MAGIC);
            pos_comment=PositionGetString(POSITION_COMMENT);
            pos_swap=PositionGetDouble(POSITION_SWAP);
            pos_commission=PositionGetDouble(POSITION_COMMISSION);
            pos_price=PositionGetDouble(POSITION_PRICE_OPEN);
            pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT);
            pos_profit=PositionGetDouble(POSITION_PROFIT);
            pos_volume=PositionGetDouble(POSITION_VOLUME);
            pos_sl=PositionGetDouble(POSITION_SL);
            pos_tp=PositionGetDouble(POSITION_TP);
            pos_time=(datetime)PositionGetInteger(POSITION_TIME);
            pos_id=PositionGetInteger(POSITION_IDENTIFIER);
            pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);                     break;
         default: Print("The passed position property is not listed in the enumeration!");               return;
        }
     }
//--- If there is no open position, zero out variables for position properties
   else
      ZeroPositionProperties();
  }

마찬가지로 기호 속성을 가져오기 위해 GetSymbolProperties 함수를 구현합니다.

//+------------------------------------------------------------------+
//| Getting symbol properties                                        |
//+------------------------------------------------------------------+
void GetSymbolProperties(ENUM_SYMBOL_PROPERTIES symbol_property)
  {
   int lot_offset=1; // Number of points for the offset from the Stops level
//---
   switch(symbol_property)
     {
      case S_DIGITS        : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);                   break;
      case S_SPREAD        : sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);                   break;
      case S_STOPSLEVEL    : sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);   break;
      case S_POINT         : sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);                           break;
      //---
      case S_ASK           :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits);                       break;
      case S_BID           :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits);                       break;
         //---
      case S_VOLUME_MIN    : sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);                 break;
      case S_VOLUME_MAX    : sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);                 break;
      case S_VOLUME_LIMIT  : sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);             break;
      case S_VOLUME_STEP   : sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);               break;
      //---
      case S_FILTER        :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits);        break;
         //---
      case S_UP_LEVEL      :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits);
         sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits);                     break;
         //---
      case S_DOWN_LEVEL    :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits);
         sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits);                   break;
         //---
      case S_ALL           :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);
         sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits);
         sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits);
         sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
         sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
         sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);
         sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
         sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits);
         sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits);
         sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits);                   break;
         //---
      default: Print("The passed symbol property is not listed in the enumeration!"); return;
     }
  }

일부 기호 속성은 먼저 다른 속성을 가져와야 할 수 있습니다.

CorrectValueBySymbolDigits라는 새로운 기능이 있습니다. 가격의 소수 자릿수에 따라 관련 값을 반환합니다. 정수 또는 실수를 함수에 전달할 수 있습니다. 전달된 데이터의 유형은 사용할 함수의 버전을 결정합니다. 이 기능을 함수 오버로딩이라고 합니다.

//+------------------------------------------------------------------+
//| Adjusting the value based on the number of digits in the price (int)|
//+------------------------------------------------------------------+
int CorrectValueBySymbolDigits(int value)
  {
   return (sym_digits==3 || sym_digits==5) ? value*=10 : value;
  }
//+------------------------------------------------------------------+
//| Adjusting the value based on the number of digits in the price (double)|
//+------------------------------------------------------------------+
double CorrectValueBySymbolDigits(double value)
  {
   return (sym_digits==3 || sym_digits==5) ? value*=10 : value;
  }

우리의 Expert Advisor는 오프닝 포지션의 볼륨(Lot)을 지정하기 위한 외부 매개변수를 가질 것입니다. 기호 사양(CalculateLot)에 따라 랏을 조정하는 함수를 생성해 보겠습니다.

//+------------------------------------------------------------------+
//| Calculating position lot                                         |
//+------------------------------------------------------------------+
double CalculateLot(double lot)
  {
//--- To adjust as per the step
   double corrected_lot=0.0;
//---
   GetSymbolProperties(S_VOLUME_MIN);  // Get the minimum possible lot
   GetSymbolProperties(S_VOLUME_MAX);  // Get the maximum possible lot
   GetSymbolProperties(S_VOLUME_STEP); // Get the lot increase/decrease step
//--- Adjust as per the lot step
   corrected_lot=MathRound(lot/sym_volume_step)*sym_volume_step;
//--- If less than the minimum, return the minimum
   if(corrected_lot<sym_volume_min)
      return(NormalizeDouble(sym_volume_min,2));
//--- If greater than the maximum, return the maximum
   if(corrected_lot>sym_volume_max)
      return(NormalizeDouble(sym_volume_max,2));
//---
   return(NormalizeDouble(corrected_lot,2));
  }

이제 글 제목과 직접적으로 관련된 기능으로 넘어가 보겠습니다. 그것들은 매우 간단하고 간단하며 코드의 주석을 사용하는 데 어려움 없이 목적을 이해할 수 있습니다.

CalculateTakeProfit 함수는 이익실현 값을 계산하는 데 사용됩니다.

//+------------------------------------------------------------------+
//| Calculating the Take Profit value                                |
//+------------------------------------------------------------------+
double CalculateTakeProfit(ENUM_ORDER_TYPE order_type)
  {
//--- If Take Profit is required
   if(TakeProfit>0)
     {
      //--- For the calculated Take Profit value
      double tp=0.0;
      //--- If you need to calculate the value for a SELL position
      if(order_type==ORDER_TYPE_SELL)
        {
         //--- Calculate the level
         tp=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits);
         //--- Return the calculated value if it is lower than the lower limit of the Stops level
         //    If the value is higher or equal, return the adjusted value
         return(tp<sym_down_level ? tp : sym_down_level-sym_offset);
        }
      //--- If you need to calculate the value for a BUY position
      if(order_type==ORDER_TYPE_BUY)
        {
         //--- Calculate the level
         tp=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits);
         //--- Return the calculated value if it is higher that the upper limit of the Stops level
         //    If the value is lower or equal, return the adjusted value
         return(tp>sym_up_level ? tp : sym_up_level+sym_offset);
        }
     }
//---
   return(0.0);
  }

CalculateStopLoss 함수는 손절매 값을 계산하는 데 사용됩니다.

//+------------------------------------------------------------------+
//| Calculating the Stop Loss value                                  |
//+------------------------------------------------------------------+
double CalculateStopLoss(ENUM_ORDER_TYPE order_type)
  {
//--- If Stop Loss is required
   if(StopLoss>0)
     {
      //--- For the calculated Stop Loss value
      double sl=0.0;
      //--- If you need to calculate the value for a BUY position
      if(order_type==ORDER_TYPE_BUY)
        {
         // Calculate the level
         sl=NormalizeDouble(sym_ask-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- Return the calculated value if it is lower that the lower limit of the Stops level
         //    If the value is higher or equal, return the adjusted value
         return(sl<sym_down_level ? sl : sym_down_level-sym_offset);
        }
      //--- If you need to calculate the value for a SELL position
      if(order_type==ORDER_TYPE_SELL)
        {
         //--- Calculate the level
         sl=NormalizeDouble(sym_bid+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- Return the calculated value if it is higher than the upper limit of the Stops level
         //    If the value is lower or equal, return the adjusted value
         return(sl>sym_up_level ? sl : sym_up_level+sym_offset);
        }
     }
//---
   return(0.0);
  }

CalculateTrailingStop 함수는 후행 중지 값을 계산하는 데 사용됩니다.

//+------------------------------------------------------------------+
//| Calculating the Trailing Stop value                              |
//+------------------------------------------------------------------+
double CalculateTrailingStop(ENUM_POSITION_TYPE position_type)
  {
//--- Variables for calculations
   double            level       =0.0;
   double            buy_point   =low_price[1];    // The Low value for a Buy
   double            sell_point  =high_price[1];   // The High value for a Sell
//--- Calculate the level for a BUY position
   if(position_type==POSITION_TYPE_BUY)
     {
      //--- Bar's low minus the specified number of points
      level=NormalizeDouble(buy_point-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
      //--- If the calculated level is lower than the lower limit of the Stops level, 
      //    the calculation is complete, return the current value of the level
      if(level<sym_down_level)
         return(level);
      //--- If it is not lower, try to calculate based on the bid price
      else
        {
         level=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- If the calculated level is lower than the limit, return the current value of the level
         //    otherwise set the nearest possible value
         return(level<sym_down_level ? level : sym_down_level-sym_offset);
        }
     }
//--- Calculate the level for a SELL position
   if(position_type==POSITION_TYPE_SELL)
     {
      // Bar's high plus the specified number of points
      level=NormalizeDouble(sell_point+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
      //--- If the calculated level is higher than the upper limit of the Stops level, 
      //    the calculation is complete, return the current value of the level
      if(level>sym_up_level)
         return(level);
      //--- If it is not higher, try to calculate based on the ask price
      else
        {
         level=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- If the calculated level is higher than the limit, return the current value of the level
         //    Otherwise set the nearest possible value
         return(level>sym_up_level ? level : sym_up_level+sym_offset);
        }
     }
//---
   return(0.0);
  }

이제 거래 작업에 대해 올바른 값을 반환하는 데 필요한 모든 기능이 있습니다. Trailing Stop을 수정하기 위한 조건을 확인하고 지정된 조건이 충족되면 같은 것을 수정하는 함수를 만들어 보겠습니다. ModifyTrailingStop. 아래는 자세한 설명이 있는 이 함수의 코드입니다.

위에서 생성/수정된 모든 기능의 사용에 주의하시기 바랍니다. switch 스위치는 현재 위치의 유형에 따라 해당 조건을 결정하고 조건 결과는 condition 변수에 저장됩니다. 포지션을 수정하려면 표준 라이브러리의 CTrade 클래스에서 PositionModify 메소드를 사용합니다.

//+------------------------------------------------------------------+
//| Modifying the Trailing Stop level                                |
//+------------------------------------------------------------------+
void ModifyTrailingStop()
  {
//--- If the Trailing Stop and Stop Loss are set
   if(TrailingStop>0 && StopLoss>0)
     {
      double         new_sl=0.0;       // For calculating the new Stop Loss level
      bool           condition=false;  // For checking the modification condition
      //--- Get the flag of presence/absence of the position
      pos_open=PositionSelect(_Symbol);
      //--- If the position exists
      if(pos_open)
        {
         //--- Get the symbol properties
         GetSymbolProperties(S_ALL);
         //--- Get the position properties
         GetPositionProperties(P_ALL);
         //--- Get the Stop Loss level
         new_sl=CalculateTrailingStop(pos_type);
         //--- Depending on the position type, check the relevant condition for the Trailing Stop modification
         switch(pos_type)
           {
            case POSITION_TYPE_BUY  :
               //--- If the new Stop Loss value is higher
               //    than the current value plus the set step
               condition=new_sl>pos_sl+CorrectValueBySymbolDigits(TrailingStop*sym_point);
               break;
            case POSITION_TYPE_SELL :
               //--- If the new Stop Loss value is lower
               //    than the current value minus the set step
               condition=new_sl<pos_sl-CorrectValueBySymbolDigits(TrailingStop*sym_point);
               break;
           }
         //--- If there is a Stop Loss, compare the values before modification
         if(pos_sl>0)
           {
            //--- If the condition for the order modification is met, i.e. the new value is lower/higher 
            //    than the current one, modify the Trailing Stop of the position
            if(condition)
              {
               if(!trade.PositionModify(_Symbol,new_sl,pos_tp))
                  Print("Error modifying the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
              }
           }
         //--- If there is no Stop Loss, simply set it
         if(pos_sl==0)
           {
            if(!trade.PositionModify(_Symbol,new_sl,pos_tp))
               Print("Error modifying the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
           }
        }
     }
  }

이제 위의 모든 변경 사항에 따라 TradingBlock 기능을 조정해 보겠습니다. ModifyTrailingStop 함수와 마찬가지로 거래 주문에 대한 모든 변수 값은 switch 스위치를 사용하여 결정됩니다. 두 포지션 유형에 대해 하나의 분기 대신 하나만 남아 있기 때문에 코드의 양을 크게 줄이고 추가 수정을 단순화합니다.

//+------------------------------------------------------------------+
//| Trading block                                                    |
//+------------------------------------------------------------------+
void TradingBlock()
  {
   ENUM_ORDER_TYPE      signal=WRONG_VALUE;                 // Variable for getting a signal
   string               comment="hello :)";                 // Position comment
   double               tp=0.0;                             // Take Profit
   double               sl=0.0;                             // Stop Loss
   double               lot=0.0;                            // Volume for position calculation in case of reverse position
   double               position_open_price=0.0;            // Position opening price
   ENUM_ORDER_TYPE      order_type=WRONG_VALUE;             // Order type for opening a position
   ENUM_POSITION_TYPE   opposite_position_type=WRONG_VALUE; // Opposite position type
//--- Get a signal
   signal=GetTradingSignal();
//--- If there is no signal, exit
   if(signal==WRONG_VALUE)
      return;
//--- Find out if there is a position
   pos_open=PositionSelect(_Symbol);
//--- Get all symbol properties
   GetSymbolProperties(S_ALL);
//--- Determine values for trade variables
   switch(signal)
     {
      //--- Assign values to variables for a BUY
      case ORDER_TYPE_BUY  :
         position_open_price=sym_ask;
         order_type=ORDER_TYPE_BUY;
         opposite_position_type=POSITION_TYPE_SELL;
         break;
         //--- Assign values to variables for a SELL
      case ORDER_TYPE_SELL :
         position_open_price=sym_bid;
         order_type=ORDER_TYPE_SELL;
         opposite_position_type=POSITION_TYPE_BUY;
         break;
     }
//--- Calculate the Take Profit and Stop Loss levels
   sl=CalculateStopLoss(order_type);
   tp=CalculateTakeProfit(order_type);
//--- If there is no position
   if(!pos_open)
     {
      //--- Adjust the volume
      lot=CalculateLot(Lot);
      //--- Open a position
      //    If the position failed to open, print the relevant message
      if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment))
        {
         Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
        }
     }
//--- If there is a position
   else
     {
      //--- Get the position type
      GetPositionProperties(P_TYPE);
      //--- If the position is opposite to the signal and the position reverse is enabled
      if(pos_type==opposite_position_type && Reverse)
        {
         //--- Get the position volume
         GetPositionProperties(P_VOLUME);
         //--- Adjust the volume
         lot=pos_volume+CalculateLot(Lot);
         //--- Open the position. If the position failed to open, print the relevant message
         if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment))
           {
            Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
           }
        }
     }
//---
   return;
  }

또한 SetInfoPanel 기능에서 또 다른 중요한 수정을 해야 하지만 먼저 프로그램이 현재 사용되는 방법/포지션을 나타내는 몇 가지 보조 기능을 준비하겠습니다.

//+------------------------------------------------------------------+
//| Returning the testing flag                                       |
//+------------------------------------------------------------------+
bool IsTester()
  {
   return(MQL5InfoInteger(MQL5_TESTER));
  }
//+------------------------------------------------------------------+
//| Returning the optimization flag                                  |
//+------------------------------------------------------------------+
bool IsOptimization()
  {
   return(MQL5InfoInteger(MQL5_OPTIMIZATION));
  }
//+------------------------------------------------------------------+
//| Returning the visual testing mode flag                           |
//+------------------------------------------------------------------+
bool IsVisualMode()
  {
   return(MQL5InfoInteger(MQL5_VISUAL_MODE));
  }
//+------------------------------------------------------------------+
//| Returning the flag for real time mode outside the Strategy Tester|
//| if all conditions are met                                        |
//+------------------------------------------------------------------+
bool IsRealtime()
  {
   if(!IsTester() && !IsOptimization() && !IsVisualMode())
      return(true);
   else
      return(false); 
  }

SetInfoPanel 함수에 추가해야 하는 유일한 것은 정보 패널이 시각화 및 실시간 모드에서만 표시되어야 함을 프로그램에 나타내는 조건입니다. 이를 무시하면 테스트 시간이 4-5배 길어집니다. 이는 매개변수를 최적화할 때 특히 중요합니다.

//+------------------------------------------------------------------+
//| Setting the info panel                                           |
//|------------------------------------------------------------------+
void SetInfoPanel()
  {
//--- Visualization or real time modes
   if(IsVisualMode() || IsRealtime())
     {
     // The remaining code of the SetInfoPanel() function
     // ...
     }
  }

이제 매개변수 최적화 및 Expert Advisor 테스트를 진행할 수 있도록 주요 프로그램 기능을 약간만 변경하면 됩니다.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Initialize the new bar
   CheckNewBar();
//--- Get the properties and set the panel
   GetPositionProperties(P_ALL);
//--- Set the info panel
   SetInfoPanel();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- If the bar is not new, exit
   if(!CheckNewBar())
      return;
//--- If there is a new bar
   else
     {
      GetBarsData();          // Get bar data
      TradingBlock();         // Check the conditions and trade
      ModifyTrailingStop();   // Modify the Trailing Stop level
     }
//--- Get the properties and update the values on the panel
   GetPositionProperties(P_ALL);
//--- Update the info panel
   SetInfoPanel();
  }
//+------------------------------------------------------------------+
//| Trade event                                                      |
//+------------------------------------------------------------------+
void OnTrade()
  {
//--- Get position properties and update the values on the panel
   GetPositionProperties(P_ALL);
//--- Update the info panel
   SetInfoPanel();
  }


매개변수 최적화 및 Expert Advisor 테스트

이제 매개변수를 최적화해 보겠습니다. 우리는 아래와 같이 전략 테스터 설정을 할 것입니다::

그림 1. 매개변수 최적화를 위한 전략 테스터 설정.

그림 1. 매개변수 최적화를 위한 전략 테스터 설정.

Expert Advisor 매개변수에는 다양한 값이 제공됩니다.

그림 2. 매개변수 최적화를 위한 Expert Advisor 설정.

그림 2. 매개변수 최적화를 위한 Expert Advisor 설정.

최적화는 듀얼 코어 프로세서(Intel Core2 Duo P7350 @ 2.00GHz)에서 약 7분이 걸렸습니다. 최대 회복 계수 테스트 결과는 다음과 같습니다.

그림 3. 최대 회복 계수 테스트 결과.

그림 3. 최대 회복 계수 테스트 결과.


결론

지금은 여기까지입니다. 연구, 테스트, 최적화, 실험 그리고 와우. 글에 소개된 Expert Advisor의 소스 코드는 아래 링크를 통해 다운로드하여 더 공부할 수 있습니다.

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

MQL5 Cookbook: 거래의 역사 및 직위 속성 가져오기를 위한 기능 라이브러리 MQL5 Cookbook: 거래의 역사 및 직위 속성 가져오기를 위한 기능 라이브러리
포지션 속성에 대한 이전 글에서 제공한 정보를 간략하게 요약할 시간입니다. 이 글에서는 거래 내역에 액세스한 후에만 얻을 수 있는 속성을 가져오는 몇 가지 추가 함수를 만듭니다. 또한 보다 편리한 방법으로 포지션 및 기호 속성에 액세스할 수 있는 데이터 구조에 익숙해질 것입니다.
MQL5 Cookbook: MetaTrader 5 전략 테스터의 포지션 속성 분석 MQL5 Cookbook: MetaTrader 5 전략 테스터의 포지션 속성 분석
이전 글 "MQL5 Cookbook: 사용자 지정 정보 패널의 포지션 속성"에서 수정된 버전의 Expert Advisor를 소개합니다. 우리가 다룰 문제 중 일부는 바에서 데이터 가져오기, 파일에 대한 표준 라이브러리의 거래 클래스를 포함하여 현재 기호에 대한 새로운 바 이벤트 확인, 거래 신호를 검색하는 기능 및 거래 작업을 실행하는 기능 만들기를 포함합니다. OnTrade() 함수에서 거래 이벤트를 결정하는 것 외에도 말이죠.
MQL5 Cookbook: 지표를 사용하여 Expert Advisor의 거래 조건 설정 MQL5 Cookbook: 지표를 사용하여 Expert Advisor의 거래 조건 설정
이 글에서는 MQL5 Cookbook 시리즈의 이전 글에서 작업한 Expert Advisor를 계속 수정할 것입니다. 이번에는 Expert Advisor가 포지션 개방 조건을 확인하는 데 사용할 값의 지표로 향상됩니다. 재미를 더하기 위해 외부 매개변수에 드롭다운 목록을 만들어 세 가지 거래 지표 중 하나를 선택할 수 있습니다.
MQL5 Cookbook: 사용자 지정 정보 패널의 포지션 속성 MQL5 Cookbook: 사용자 지정 정보 패널의 포지션 속성
이번에는 현재 기호에 대한 포지션 속성을 가져와 수동 거래 중에 사용자 지정 정보 패널에 표시하는 간단한 Expert Advisor를 만들 것입니다. 정보 패널은 그래픽 개체를 사용하여 생성되며 표시된 정보는 틱마다 새로 고쳐집니다. 이것은 "MQL5 Cookbook: Get Position Properties" 시리즈의 이전 글에서 설명한 스크립트를 수동으로 실행해야 하는 모든 시간보다 훨씬 더 편리할 것입니다.