English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
MQL5 Cookbook: 과적합의 영향 줄이기 및 따옴표 부족 처리

MQL5 Cookbook: 과적합의 영향 줄이기 및 따옴표 부족 처리

MetaTrader 5 | 2 9월 2021, 17:12
68 0
Anatoli Kazharski
Anatoli Kazharski

소개

나는 많은 거래자들이 거래 시스템에 대한 최적의 매개변수에 대해 반복적으로 의아해했다고 생각합니다. 실제로 거래 알고리즘만으로는 충분하지 않습니다. 어떻게 더 사용할 수 있는지 볼 필요가 있습니다. 당신이 사용하는 거래 전략이 단순하든 복잡하든, 단일 또는 다중 상품으로 미래 이익을 보장하기 위해 어떤 매개변수를 선택해야 하는지에 대한 질문을 피할 수 없습니다.

우리는 최적화 기간(백테스팅)과 이후 기간(포워드 테스팅) 동안 좋은 결과를 보인 매개변수로 거래 시스템을 확인하는 경향이 있습니다. 순방향 테스트는 사실 그렇게 필요하지 않습니다. 관련 결과는 과거 데이터를 사용하여 얻을 수 있습니다.

이 방법은 확실한 답을 줄 수 없는 거대한 질문을 낳습니다. 거래 시스템을 최적화하기 위해 어느 정도의 과거 데이터를 사용해야 할까요? 문제는 옵션이 많다는 것입니다. 그것은 모두 당신이 활용할 것으로 예상되는 가격 변동의 범위에 달려 있습니다.

최적화에 필요한 기록의 양으로 돌아가서 사용 가능한 데이터가 시간 내 거래에 충분할 수 있다는 결론을 내립니다. 이것은 더 긴 시간 프레임에 대해 항상 사실이 아닙니다. 일관된 패턴의 반복 횟수가 많을수록, 즉 더 많은 거래가 있을수록 미래에 볼 수 있는 테스트된 거래 시스템의 성능이 더 진실됩니다.

특정 악기의 가격 데이터가 충분한 반복 횟수를 얻고 더 확실하게 느끼기에 충분하지 않다면 어떻게 될까요? 답변: 사용 가능한 모든 도구의 데이터를 사용하세요.


NeuroShell DayTrader Professional의 예

MetaTrader 5에서 프로그래밍을 진행하기 전에 NeuroShell DayTrader Professional의 예를 검토해 보겠습니다. 여러 기호에 대한 거래 시스템(생성자로 컴파일된) 매개변수를 최적화하기 위한 훌륭한 기능을 제공합니다. 거래 모듈 설정에서 필요한 매개변수를 설정하거나, 각 기호에 대한 매개변수를 개별적으로 최적화하거나, 모든 기호에 대한 최적의 매개변수 세트를 한 번에 찾을 수 있습니다. 이 옵션은 최적화 탭에서 찾을 수 있습니다.

NeuroShell DayTrader Professional 거래 모듈의 최적화 탭

그림 1. NeuroShell DayTrader Professional 거래 모듈의 최적화 탭

우리의 경우 간단한 거래 시스템은 두 가지 최적화 방법의 결과만 비교하면 되므로 시스템 선택은 현재 거의 중요하지 않습니다.

블로그의 다른 글에서 NeuroShell DayTrader Professional에서 거래 전략을 컴파일하는 방법에 대한 정보를 찾을 수 있습니다(검색하거나 태그를 사용하여 관련 정보를 찾을 수 있음). 또한 스크립트를 사용하여 NeuroShell DayTrader Professional과 호환되는 형식으로 MetaTrader 5에서 견적을 다운로드할 수 있는 방법을 설명하고 보여주는 "MetaTrader 5 Quotes for Other Applications를 준비하는 방법"이라는 제목의 글을 읽는 것이 좋습니다.

이 테스트를 수행하기 위해 2000년부터 2013년 1월까지 8개 기호에 대한 일일 바에서 얻은 데이터를 준비했습니다.

NeuroShell DayTrader Professional의 테스트 기호 목록

그림 2. NeuroShell DayTrader Professional의 테스트 기호 목록

아래 그림은 두 가지 최적화 결과를 보여줍니다. 상단은 각 기호가 고유한 매개변수를 가져오는 최적화 결과를 표시하고 하단은 매개변수가 모든 기호에 대해 공통인 결과를 표시합니다.

두 매개변수 최적화 모드의 결과 비교

그림 3. 두 매개변수 최적화 모드의 결과 비교

공통 매개변수를 보여주는 결과는 각 기호마다 매개변수가 다른 결과보다 좋지 않습니다. 그러나 거래 시스템이 모든 기호에 대해 동일한 매개변수를 사용하여 다양한 가격 행동 패턴(변동성, 추세/플랫 수)을 거치면서 더 많은 자신감을 불러일으킵니다.

동일한 주제에 대해 계속해서 더 많은 양의 데이터를 사용하는 최적화에 찬성하는 또 다른 주장을 논리적으로 찾을 수 있습니다. 특정 통화 쌍의 가격 행동이 예를 들어 EURUSD는 이후(2년, 5년 또는 10년 후) 상당히 달라질 것입니다. 예를 들어, GBPUSD 가격 추세는 EURUSD의 과거 가격 행동과 유사하거나 그 반대의 경우도 마찬가지입니다. 이것은 모든 악기에 해당되므로 이에 대한 준비가 되어 있어야 합니다.


MetaTrader 5의 예

이제 MetaTrader 5에서 제공되는 매개변수 최적화 모드를 살펴보겠습니다. 아래에서 최적화 모드의 드롭다운 목록에서 화살표로 표시된 시장 감시에서 선택된 모든 기호 최적화 모드를 볼 수 있습니다.

그림 4. MetaTrader 5 전략 테스터의 최적화 모드

그림 4. MetaTrader 5 전략 테스터의 최적화 모드

이 모드를 사용하면 각 기호의 현재 매개변수로 하나씩만 EA를 테스트할 수 있습니다. 테스트에 사용된 기호는 현재 Market Watch 창에서 선택된 기호입니다. 즉, 이 경우에는 매개변수 최적화가 수행되지 않습니다. 그러나 MetaTrader 5 및 MQL5를 사용하면 이 아이디어를 스스로 구현할 수 있습니다.

이제 이러한 EA를 구현하는 방법을 살펴봐야 합니다. 기호 목록은 텍스트 파일(*.txt)로 제공됩니다. 또한 여러 세트의 기호 목록을 저장할 수 있는 가능성을 구현할 것입니다. 각 세트는 섹션 번호가 포함된 자체 헤더가 있는 별도의 섹션에 있습니다. 시각적 확인을 용이하게 하기 위해 숫자가 필요합니다.

기호 배열을 채울 때 Expert Advisor가 올바른 데이터 세트를 얻을 수 있도록 숫자 앞에 #이 있어야 합니다. 일반적으로 헤더에는 기호가 포함될 수 있지만 항상 #이 있어야 합니다. 숫자 기호는 Expert Advisor가 섹션을 결정/계산하는 데 따라 다른 기호로 대체될 수 있습니다. 이 경우 교체는 코드에 반영되어야 합니다.

아래에서 테스트를 위한 세 가지 기호 집합이 포함된 SymbolsList.txt 파일을 볼 수 있습니다. 표시된 이 파일은 메소드를 테스트할 때 추가로 사용됩니다.

그림 5. 테스트를 위해 텍스트 파일에 제공되는 여러 기호 집합

그림 5. 테스트를 위해 텍스트 파일에 제공되는 여러 기호 집합

외부 매개변수에서 Expert Advisor가 현재 테스트에서 사용해야 하는 기호 집합을 나타내기 위해 다른 매개변수 SectionOfSymbolList를 추가합니다. 이 매개변수는 기호 세트를 정의하는 값(0부터 위로)을 취합니다. 값이 사용 가능한 세트 수를 초과하는 경우 Expert Advisor는 해당 항목을 로그에 기록하고 현재 기호에 대해서만 테스트가 수행됩니다.

SymbolsList.txtMetatrader 5\MQL5\Files 아래의 로컬 터미널 디렉토리에 있어야 합니다. 또한 common 폴더에 배치할 수도 있지만 이 경우 MQL5 Cloud Network의 매개변수 최적화에 사용할 수 없습니다. 또한 테스트를 위해 파일 및 관련 사용자 지정 지표에 대한 액세스를 허용하려면 파일 시작 부분에 다음 줄을 작성해야 합니다.

//--- Allow access to the external file and indicator for optimization in the cloud
#property tester_file      "SymbolsList.txt"
#property tester_indicator "EventsSpy.ex5"

우리의 Expert Advisor은 "MQL5 Cookbook: 매개변수가 무제한인 다중 통화 Expert Advisor 개발" 글에 실린 기성품 다중 통화 Expert Advisor를 기반으로 합니다. 기본 거래 전략은 매우 간단하지만 방법의 효율성을 테스트하기에 충분합니다. 불필요한 부분을 제거하고 필요한 것을 추가하고 기존 관련 코드를 수정하면 됩니다. 우리는 "MQL5 Cookbook: 파일에 거래 내역 기록 및 Excel의 각 기호에 대한 잔액 차트 생성" 시리즈의 이전 글에서 광범위하게 설명된 보고서 저장 기능으로 Expert Advisor를 확실히 향상시킬 것입니다. . 고려 중인 방법의 효율성을 평가하려면 모든 기호에 대한 대차 대조표도 필요합니다.

Expert Advisor의 외부 매개변수는 다음과 같이 수정해야 합니다.

//--- External parameters of the Expert Advisor
sinput int    SectionOfSymbolList = 1;     // Section number in the symbol lists
sinput bool   UpdateReport        = false; // Report update
sinput string delimeter_00="";   // --------------------------------
sinput long   MagicNumber         = 777;   // Magic number
sinput int    Deviation           = 10;    // Slippage
sinput string delimeter_01="";   // --------------------------------
input  int    IndicatorPeriod     = 5;     // Indicator period
input  double TakeProfit          = 100;   // Take Profit
input  double StopLoss            = 50;    // Stop Loss
input  double TrailingStop        = 10;    // Trailing Stop
input  bool   Reverse             = true;  // Position reversal
input  double Lot                 = 0.1;   // Lot
input  double VolumeIncrease      = 0.1;   // Position volume increase
input  double VolumeIncreaseStep  = 10;    // Volume increase step

외부 매개변수와 연결된 모든 배열은 필요하지 않으며 전체 코드에서 외부 변수로 추가로 대체되어야 하므로 삭제해야 합니다. 심볼의 동적 배열인 InputSymbols[]만 남겨두어야 합니다. 이 배열의 크기는 SymbolsList.txt 파일의 세트 중 하나에서 사용되는 심볼 수에 따라 달라집니다. Expert Advisor가 Strategy Tester 외부에서 사용되는 경우 Expert Advisor가 하나의 기호로만 작동하는 실시간 모드에서와 같이 해당 배열의 크기는 1과 같습니다.

배열 초기화 파일(InitializeArrays.mqh)에서도 해당 변경을 수행해야 합니다. 즉, 외부 변수 배열의 초기화를 담당하는 모든 함수를 삭제해야 합니다. InitializeArraySymbols() 함수는 이제 아래와 같이 표시됩니다.

//+------------------------------------------------------------------+
//| Filling the array of symbols                                     |
//+------------------------------------------------------------------+
void InitializeArraySymbols()
  {
   int    strings_count  =0;   // Number of strings in the symbol file
   string checked_symbol ="";  // To check the accessibility of the symbol on the trade server
//--- Test mode message
   string message_01="<--- All symbol names in the <- SymbolsList.txt -> file are incorrect ... --->\n"
                     "<--- ... or the value of the \"Section of List Symbols\" parameter is greater, "
                     "than the number of file sections! --->\n"
                     "<--- Therefore we will test only the current symbol. --->";
//--- Real-time mode message
   string message_02="<--- In real-time mode, we only work with the current symbol. --->";
//--- If in real-time mode
   if(!IsRealtime())
     {
      //--- Get the number of strings from the specified symbol set in the file and fill the temporary array of symbols
      strings_count=ReadSymbolsFromFile("SymbolsList.txt");
      //--- Iterate over all symbols from the specified set
      for(int s=0; s<strings_count; s++)
        {
         //--- If the correct string is returned following the symbol check
         if((checked_symbol=GetSymbolByName(temporary_symbols[s]))!="")
           {
            //--- increase the counter
            SYMBOLS_COUNT++;
            //--- set/increase the array size
            ArrayResize(InputSymbols,SYMBOLS_COUNT);
            //--- index with the symbol name
            InputSymbols[SYMBOLS_COUNT-1]=checked_symbol;
           }
        }
     }
//--- If all symbol names were not input correctly or if currently working in real-time mode
   if(SYMBOLS_COUNT==0)
     {
      //--- Real-time mode message
      if(IsRealtime())
         Print(message_02);
      //--- Test mode message
      if(!IsRealtime())
         Print(message_01);
      //--- We will work with the current symbol only
      SYMBOLS_COUNT=1;
      //--- set the array size and
      ArrayResize(InputSymbols,SYMBOLS_COUNT);
      //--- index with the current symbol name
      InputSymbols[0]=_Symbol;
     }
  }

ReadSymbolsFromFile() 함수 코드도 수정해야 합니다. 전체 기호 목록을 읽는 데 사용되었지만 이제는 지정된 기호 집합만 읽기를 원합니다. 다음은 수정된 기능 코드입니다.

//+------------------------------------------------------------------+
//| Returning the number of strings (symbols) from the specified     |
//| set in the file and filling the temporary array of symbols       |
//+------------------------------------------------------------------+
//--- When preparing the file, symbols in the list should be separated with a line break
int ReadSymbolsFromFile(string file_name)
  {
   ulong  offset         =0;   // Offset for determining the position of the file pointer
   string delimeter      ="#"; // Identifier of the section start
   string read_line      ="";  // For the check of the read string
   int    limit_count    =0;   // Counter limiting the number of the possibly open charts
   int    strings_count  =0;   // String counter
   int    sections_count =-1;  // Section counter
   
//--- Message 01
   string message_01="<--- The <- "+file_name+" -> file has not been prepared appropriately! --->\n"
                     "<--- The first string does not contain the section number identifier ("+delimeter+")! --->";
//--- Message 02
   string message_02="<--- The <- "+file_name+" -> file has not been prepared appropriately! --->\n"
                     "<--- There is no line break identifier in the last string, --->\n"
                     "<--- so only the current symbol will be involved in testing. --->";
//--- Message 03
   string message_03="<--- The <- "+file_name+" -> file could not be found! --->"
                     "<--- Only the current symbol will be involved in testing. --->";
                     
//--- Open the file (get the handle) for reading in the local directory of the terminal
   int file_handle=FileOpen(file_name,FILE_READ|FILE_ANSI,'\n');
//--- If the file handle has been obtained
   if(file_handle!=INVALID_HANDLE)
     {
      //--- Read until the current position of the file pointer
      //    reaches the end of the file or until the program is deleted
      while(!FileIsEnding(file_handle) || !IsStopped())
        {
         //--- Read until the end of the string or until the program is deleted
         while(!FileIsLineEnding(file_handle) || !IsStopped())
           {
            //--- Read the whole string
            read_line=FileReadString(file_handle);
            //--- If the section number identifier has been found
            if(StringFind(read_line,delimeter,0)>-1)
               //--- Increase the section counter
               sections_count++;
            //--- If the section has been read, exit the function
            if(sections_count>SectionOfSymbolList)
              {
               FileClose(file_handle); // Close the file
               return(strings_count);  // Return the number of strings in the file
              }
            //--- If this is the first iteration and the first string does not contain the section number identifier
            if(limit_count==0 && sections_count==-1)
              {
               PrepareArrayForOneSymbol(strings_count,message_01);
               //--- Close the file
               FileClose(file_handle);
               //--- Return the number of strings in the file
               return(strings_count);
              }
            //--- Increase the counter limiting the number of the possibly open charts
            limit_count++;
            //--- If the limit has been reached
            if(limit_count>=CHARTS_MAX)
              {
               PrepareArrayForOneSymbol(strings_count,message_02);
               //--- Close the file
               FileClose(file_handle);
               //--- Return the number of strings in the file
               return(strings_count);
              }
            //--- Get the position of the pointer
            offset=FileTell(file_handle);
            //--- If this is the end of the string
            if(FileIsLineEnding(file_handle))
              {
               //--- Go to the next string if this is not the end of the file
               //    For this purpose, increase the offset of the file pointer
               if(!FileIsEnding(file_handle))
                  offset++;
               //--- move it to the next string
               FileSeek(file_handle,offset,SEEK_SET);
               //--- If we are not in the specified section of the file, exit the loop
               if(sections_count!=SectionOfSymbolList)
                  break;
               //--- Otherwise,
               else
                 {
                  //--- if the string is not empty
                  if(read_line!="")
                    {
                     //--- increase the string counter
                     strings_count++;
                     //--- increase the size of the array of strings,
                     ArrayResize(temporary_symbols,strings_count);
                     //--- write the string to the current index
                     temporary_symbols[strings_count-1]=read_line;
                    }
                 }
               //--- Exit the loop
               break;
              }
           }
         //--- If this is the end of the file, terminate the entire loop
         if(FileIsEnding(file_handle))
            break;
        }
      //--- Close the file
      FileClose(file_handle);
     }
   else
      PrepareArrayForOneSymbol(strings_count,message_03);
//--- Return the number of strings in the file
   return(strings_count);
  }

위 코드의 일부 문자열이 강조 표시된 것을 볼 수 있습니다. 이러한 문자열에는 오류가 발생할 경우 하나의 (현재) 기호에 대한 배열을 단순히 준비하는 PrepareArrayForOneSymbol() 함수가 포함되어 있습니다.

//+------------------------------------------------------------------+
//| Preparing an array for one symbol                                |
//+------------------------------------------------------------------+
void PrepareArrayForOneSymbol(int &strings_count,string message)
  {
//--- Print the message to the log
   Print(message);
//--- Array size
   strings_count=1;
//--- Set the size of the array of symbols
   ArrayResize(temporary_symbols,strings_count);
//--- Write the string with the current symbol name to the current index
   temporary_symbols[0]=_Symbol;
  }

이제 모든 것이 매개변수 최적화 방법을 테스트할 준비가 되었습니다. 그러나 테스트를 진행하기 전에 보고서에 다른 데이터 시리즈를 추가해 보겠습니다. 이전에는 모든 기호의 잔액 외에도 보고서 파일에 백분율로 표시된 로컬 최대값의 모든 감소가 포함되었습니다. 이제 보고서는 금전적 측면에서 모든 손실을 다룰 것입니다. 동시에 보고서가 생성되는 CreateSymbolBalanceReport() 함수를 수정합니다.

CreateSymbolBalanceReport() 함수 코드는 다음과 같습니다.

//+------------------------------------------------------------------+
//| Creating test report on deals in .csv format                     |
//+------------------------------------------------------------------+
void CreateSymbolBalanceReport()
  {
   int    file_handle =INVALID_HANDLE; // File handle
   string path        ="";             // File path

//--- If an error occurred when creating/getting the folder, exit
   if((path=CreateInputParametersFolder())=="")
      return;
//--- Create a file to write data in the common folder of the terminal
   file_handle=FileOpen(path+"\\LastTest.csv",FILE_CSV|FILE_WRITE|FILE_ANSI|FILE_COMMON);
//--- If the handle is valid (file created/opened)
   if(file_handle>0)
     {
      int           digits           =0;   // Number of decimal places in the price
      int           deals_total      =0;   // Number of deals in the specified history
      ulong         ticket           =0;   // Deal ticket
      double        drawdown_max     =0.0; // Drawdown
      double        balance          =0.0; // Balance
      string        delimeter        =","; // Delimiter
      string        string_to_write  ="";  // To generate the string for writing
      static double percent_drawdown =0.0; // Drawdown expressed as percentage
      static double money_drawdown   =0.0; // Drawdown in monetary terms

      //--- Generate the header string
      string headers="TIME,SYMBOL,DEAL TYPE,ENTRY TYPE,VOLUME,"
                     "PRICE,SWAP($),PROFIT($),DRAWDOWN(%),DRAWDOWN($),BALANCE";
      //--- If more than one symbol is involved, modify the header string
      if(SYMBOLS_COUNT>1)
        {
         for(int s=0; s<SYMBOLS_COUNT; s++)
            StringAdd(headers,","+InputSymbols[s]);
        }
      //--- Write the report headers
      FileWrite(file_handle,headers);
      //--- Get the complete history
      HistorySelect(0,TimeCurrent());
      //--- Get the number of deals
      deals_total=HistoryDealsTotal();
      //--- Resize the array of balances according to the number of symbols
      ArrayResize(symbol_balance,SYMBOLS_COUNT);
      //--- Resize the array of deals for each symbol
      for(int s=0; s<SYMBOLS_COUNT; s++)
         ArrayResize(symbol_balance[s].balance,deals_total);
      //--- Iterate in a loop and write the data
      for(int i=0; i<deals_total; i++)
        {
         //--- Get the deal ticket
         ticket=HistoryDealGetTicket(i);
         //--- Get all the deal properties
         GetHistoryDealProperties(ticket,D_ALL);
         //--- Get the number of digits in the price
         digits=(int)SymbolInfoInteger(deal.symbol,SYMBOL_DIGITS);
         //--- Calculate the overall balance
         balance+=deal.profit+deal.swap+deal.commission;
         //--- Calculate the max drawdown from the local maximum
         TesterDrawdownMaximum(i,balance,percent_drawdown,money_drawdown);
         //--- Generate a string for writing using concatenation
         StringConcatenate(string_to_write,
                           deal.time,delimeter,
                           DealSymbolToString(deal.symbol),delimeter,
                           DealTypeToString(deal.type),delimeter,
                           DealEntryToString(deal.entry),delimeter,
                           DealVolumeToString(deal.volume),delimeter,
                           DealPriceToString(deal.price,digits),delimeter,
                           DealSwapToString(deal.swap),delimeter,
                           DealProfitToString(deal.symbol,deal.profit),delimeter,
                           DrawdownToString(percent_drawdown),delimeter,
                           DrawdownToString(money_drawdown),delimeter,
                           DoubleToString(balance,2));
         //--- If more than one symbol is involved, write their balance values
         if(SYMBOLS_COUNT>1)
           {
            //--- Iterate over all symbols
            for(int s=0; s<SYMBOLS_COUNT; s++)
              {
               //--- If the symbols are equal and the deal result is non-zero
               if(deal.symbol==InputSymbols[s] && deal.profit!=0)
                 {
                  //--- Display the deal in the balance for the corresponding symbol
                  //    Take into consideration swap and commission
                  symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1]+
                                               deal.profit+
                                               deal.swap+
                                               deal.commission;
                  //--- Add to the string
                  StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                 }
               //--- Otherwise write the previous value
               else
                 {
                  //--- If the deal type is "Balance" (the first deal)
                  if(deal.type==DEAL_TYPE_BALANCE)
                    {
                     //--- the balance is the same for all symbols
                     symbol_balance[s].balance[i]=balance;
                     StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                    }
                  //--- Otherwise write the previous value to the current index
                  else
                    {
                     symbol_balance[s].balance[i]=symbol_balance[s].balance[i-1];
                     StringAdd(string_to_write,","+DoubleToString(symbol_balance[s].balance[i],2));
                    }
                 }
              }
           }
         //--- Write the generated string
         FileWrite(file_handle,string_to_write);
         //--- Mandatory zeroing out of the variable for the next string
         string_to_write="";
        }
      //--- Close the file
      FileClose(file_handle);
     }
//--- If the file could not be created/opened, print the appropriate message
   else
      Print("Error creating the file! Error: "+IntegerToString(GetLastError())+"");
  }

DrawdownMaximumToString() 함수에서 드로다운을 계산하는 데 사용했습니다. 이것은 이제 TesterDrawdownMaximum() 함수에 의해 수행되는 반면, 드로다운 값은 기본 DrawdownToString() 함수를 사용하여 문자열로 변환됩니다.

TesterDrawdownMaximum() 함수 코드는 다음과 같습니다.

//+------------------------------------------------------------------+
//| Returning the max drawdown from the local maximum                |
//+------------------------------------------------------------------+
void TesterDrawdownMaximum(int deal_number,
                           double balance,
                           double &percent_drawdown,
                           double &money_drawdown)
  {
   ulong         ticket =0;   // Deal ticket
   string        str    ="";  // The string to be displayed in the report
//--- To calculate the local maximum and drawdown
   static double max    =0.0;
   static double min    =0.0;
   
//--- If this is the first deal
   if(deal_number==0)
     {
      //--- There is no drawdown yet
      percent_drawdown =0.0;
      money_drawdown   =0.0;
      //--- Set the initial point as the local maximum
      max=balance;
      min=balance;
     }
   else
     {
      //--- If the current balance is greater than in the memory, then...
      if(balance>max)
        {
         //--- Calculate the drawdown using the previous values:
         //    in monetary terms
         money_drawdown=max-min;
         //    expressed as percentage
         percent_drawdown=100-((min/max)*100);
         //--- Update the local maximum
         max=balance;
         min=balance;
        }
      //--- Otherwise
      else
        {
         //--- Return zero value of the drawdown
         money_drawdown=0.0;
         percent_drawdown=0.0;
         //--- Update the minimum
         min=fmin(min,balance);
         //--- If the deal ticket by its position in the list has been obtained, then...
         if((ticket=HistoryDealGetTicket(deal_number))>0)
           {
            //--- ...get the deal comment
            GetHistoryDealProperties(ticket,D_COMMENT);
            //--- Flag of the last deal
            static bool last_deal=false;
            //--- The last deal in the test can be identified by the "end of test" comment
            if(deal.comment=="end of test" && !last_deal)
              {
               //--- Set the flag
               last_deal=true;
               //--- Update the drawdown values:
               //    in monetary terms
               money_drawdown=max-min;
               //    expressed as percentage
               percent_drawdown+=100-((min/max)*100);
              }
           }
        }
     }
  }

TesterDrawdownMaximum() 함수 코드는 다음과 같습니다.

//+------------------------------------------------------------------+
//| Converting drawdown to a string                                  |
//+------------------------------------------------------------------+
string DrawdownToString(double drawdown)
  {
   return((drawdown<=0) ? "" : DoubleToString(drawdown,2));
  }

이제 Expert Advisor를 테스트하고 결과를 분석할 수 있도록 모든 것이 설정되고 준비되었습니다. 글 초반부에서 미리 만들어진 파일의 예를 보았습니다. 다음과 같이 합시다. 두 번째 세트의 기호에 대한 매개변수를 최적화하고(EURUSD, AUDUSD 및 USDCHF의 세 가지 기호가 있음) 최적화에 따라 세 번째 세트의 모든 기호(총 7개 기호)를 사용하여 테스트를 실행하여 다음을 확인합니다. 데이터가 매개변수 최적화에 포함되지 않은 기호에 대한 결과입니다.


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

Strategy Tester는 아래와 같이 설정해야 합니다.

그림 6. 최적화를 위한 전략 테스터 설정

그림 6. 최적화를 위한 전략 테스터 설정

매개변수 최적화를 위한 Expert Advisor 설정은 다음과 같습니다.

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

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

최적화는 3개의 심볼을 포함하고 각각에 대해 포지션 볼륨 증가가 활성화되어 있기 때문에 포지션을 열고 포지션 볼륨을 증가시키기 위해 최소 랏을 설정합니다 이 경우 값은 0.01입니다.

최적화 후 최대 회수율로 상위 결과를 선택하고 랏에 대해 VolumeIncrease 매개변수를 0.1로 설정합니다. 결과는 아래와 같습니다.

그림 8. MetaTrader 5의 테스트 결과

그림 8. MetaTrader 5의 테스트 결과

아래에서 Excel 2010과 같은 결과를 볼 수 있습니다.

Excel 2010에 표시된 세 가지 기호에 대한 테스트 결과

그림 9. Excel 2010에 표시된 세 가지 기호에 대한 테스트 결과

금전적 측면에서 하락은 두 번째(보조) 척도 측면에서 하단 차트에 녹색 표시로 표시됩니다.

Excel 2010의 차트 제한도 알고 있어야 합니다(사양 및 제한의 전체 목록은 Microsoft Office 웹사이트의 Excel 사양 및 제한 페이지에서 찾을 수 있음).

그림 10. Excel 2010의 차트 사양 및 제한 사항

그림 10. Excel 2010의 차트 사양 및 제한 사항

이 표는 255개의 기호에 대한 테스트를 동시에 실행할 수 있고 모든 결과를 차트에 표시할 수 있음을 보여줍니다! 우리는 컴퓨터 리소스에 의해서만 제한됩니다.

이제 현재 매개변수를 사용하여 세 번째 세트의 7개 기호에 대한 테스트를 실행하고 결과를 확인합니다.

Excel 2010에 표시된 7개 기호에 대한 테스트 결과

그림 11. Excel 2010에 표시된 7개 기호에 대한 테스트 결과

7개의 기호를 고려하여 6901개의 거래가 있습니다. 차트의 데이터는 Excel 2010에서 매우 빠르게 업데이트됩니다.


결론

우리가 사용했던 것과 같은 간단한 거래 전략도 좋은 결과를 보였다는 점에서 도입된 방법이 주목할 만하다고 생각합니다. 여기에서 최적화는 7개 중 3개 기호에 대해서만 수행되었다는 점을 염두에 두어야 합니다. 한 번에 모든 기호에 대한 매개변수를 최적화하여 결과를 개선할 수 있습니다. 그러나 무엇보다도 먼저 거래 시스템을 개선하는 것을 목표로 해야 합니다. 우리는 나중에 이 아이디어로 돌아올 것입니다.

그게 다입니다. 우리는 다중 통화 거래 전략의 결과를 연구하는 데 상당히 유용한 도구를 가지고 있습니다. 아래는 귀하가 고려할 수 있는 Expert Advisor 파일이 포함된 다운로드 가능한 zip 파일입니다.

파일 압축을 푼 후 ReduceOverfittingEA 폴더를 MetaTrader 5\MQL5\Experts 디렉토리에 넣습니다. 또한 EventsSpy.mq5 지표는 MetaTrader 5\MQL5\Indicators에 배치해야 합니다. SymbolsList.txtMetaTrader 5\MQL5\Files 아래에 있어야 합니다.

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

파일 첨부됨 |
symbolslist.txt (0.1 KB)
eventsspy__1.mq5 (7.59 KB)
MQL5 프로그램 디버깅 MQL5 프로그램 디버깅
이 글은 주로 언어를 이미 배웠지만 아직 프로그램 개발을 완전히 마스터하지 못한 프로그래머를 대상으로 합니다. 그것은 몇 가지 디버깅 기술을 보여주고 저자와 다른 많은 프로그래머의 결합된 경험을 보여줍니다.
MQL5 Cookbook: 파일에 거래 내역 쓰기 및 Excel의 각 기호에 대한 대차 대조표 생성 MQL5 Cookbook: 파일에 거래 내역 쓰기 및 Excel의 각 기호에 대한 대차 대조표 생성
다양한 포럼에서 커뮤니케이션할 때 Microsoft Excel 차트의 스크린샷으로 표시되는 테스트 결과의 예를 자주 사용했습니다. 그러한 차트를 만드는 방법을 설명하라는 요청을 여러 번 받았습니다. 마지막으로, 이제 이 글에서 모든 것을 설명할 시간이 있습니다.
포인트 앤 피겨 차트 지표 포인트 앤 피겨 차트 지표
현재 시장 상황에 대한 정보를 제공하는 차트 유형이 많이 있습니다. Point and Figure 차트와 같은 많은 것들은 먼 과거의 유산입니다. 이 글에서는 실시간 지표를 사용한 점 및 그림 차트의 예를 설명합니다.
MQL5 Cookbook: 매개변수 수에 제한이 없는 다중 통화 Expert Advisor 개발 MQL5 Cookbook: 매개변수 수에 제한이 없는 다중 통화 Expert Advisor 개발
이 글에서는 무제한의 매개변수를 허용하면서 거래 시스템 최적화를 위해 단일 매개변수 세트를 사용하는 패턴을 만들 것입니다. 기호 목록은 표준 텍스트 파일(*.txt)로 생성됩니다. 각 기호에 대한 입력 매개변수도 파일에 저장됩니다. 이렇게 하면 Expert Advisor의 입력 매개변수 수에 대한 터미널 제한을 피할 수 있습니다.