English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
최적화 몇 가지 아이디어

최적화 몇 가지 아이디어

MetaTrader 5 | 12 10월 2021, 14:56
119 0
Jose Miguel Soriano
Jose Miguel Soriano

개요

EA가 이용할 만한 적절한 전략을 찾아 EURUSD 차트에 적용했었죠? 동일한 전략이 다른 통화쌍에서 더 높은 수익률을 보일 수도 있을까요? 등비 수열을 이용해 랏 사이즈를 키우지 않고도 말입니다.

EURUSD에 표준 H1 타임프레임을 적용한 결과가 마음에 들지 않아 EURJPY에 H4 타임프레임을 적용해 보면 어떨까요?

아니면, 64비트 운영체제를 이용해 테스트 시스템의 속도를 걱정하지 않아도 된다면, 입력 매개 변수의 조합에 신경을 쓰지 않게 될 수도 있을까요? 최적화 과정에서는 하나하나 나열되지만 최종 리포트에서는 필요 없는 내용이잖아요.

이런 문제들에 대한 효과적인 해결책을 찾았습니다. 제가 찾은 방법을 공유해 드릴게요. 물론 이 밖에도 더 좋은 해결 방안이 있을 수도 있어요.


타임프레임 단위 최적화

MQL5는 다양한 타임프레임을 제공합니다. M1, M2, M3부터 H1, H2 등의 월간 차트까지 말이죠. 총 21개의 타임프레임이 있습니다. 최적화 시에는 어떤 타임프레임이 여러분이 택한 전략에 가장 적합한지 알아야 합니다. M1, M5와 같은 단기 타임프레임인지, H2나 H4 같은 중기 타임프레임인지, D1이나 W1 같은 장기 타임프레임인지 말이예요.

사실 이렇게 많은 옵션이 필요하지는 않습니다. 예를 들어, M5 타임프레임에 전략을 적용해 보고, 다음 단계에서 M3나 M6에 적용할 수 있는지 확인해 보면 되는 거죠.

다음은 ENUM_TIMEFRAMES 형식 변수를 입력 변수로 이용하는 경우입니다.

input ENUM_TIMEFRAMES marcoTF= PERIOD_M5; 
-->

이 경우 총 21개의 변형 최적화가 제공되죠. 이게 정말 다 필요할까요?

표준 타임프레임 옵션

처음에는 필요가 없습니다. 최적화 과정을 어떻게 간략화할 수 있을까요? 우선 열거를 정의할 수 있겠습니다.

enum mis_MarcosTMP
{
   _M1= PERIOD_M1,
   _M5= PERIOD_M5,
   _M15=PERIOD_M15,
//   _M20=PERIOD_M20,
   _M30=PERIOD_M30,
   _H1= PERIOD_H1,
   _H2= PERIOD_H2,
   _H4= PERIOD_H4,
//   _H8= PERIOD_H8,
   _D1= PERIOD_D1,
   _W1= PERIOD_W1,
   _MN1=PERIOD_MN1
};
-->

마음에 드는 타임프레임을 추가하거나 삭제할 수 있죠. 최적화를 위해서는 코드 앞부분에 입력 변수를 정의합니다.

input mis_MarcosTMP timeframe= _H1;
-->

그리고는 library.mqh 파일에 새로운 함수를 정의하죠.

//----------------------------------------- DEFINE THE TIMEFRAME ----------------------------------------------------------
ENUM_TIMEFRAMES defMarcoTiempo(mi_MARCOTMP_CORTO marco)
{
   ENUM_TIMEFRAMES resp= _Period;
   switch(marco)
   {
      case _M1: resp= PERIOD_M1; break;
      case _M5: resp= PERIOD_M5; break;
      case _M15: resp= PERIOD_M15; break;
      //case _M20: resp= PERIOD_M20; break;
      case _M30: resp= PERIOD_M30; break;
      case _H1: resp= PERIOD_H1; break;
      case _H2: resp= PERIOD_H2; break;
      case _H4: resp= PERIOD_H4; break;
      //case _H8: resp= PERIOD_H8; break;
      case _D1: resp= PERIOD_D1; break;
      case _W1: resp= PERIOD_W1; break;
      case _MN1: resp= PERIOD_MN1;
   }
return(resp);
}
-->

전역 변수 수준에서 새로운 변수를 선언합니다.

ENUM_TIMEFRAMES marcoTmp= defMarcoTiempo(marcoTiempo);          //timeframe is defined as a global variable
-->

'marcoTmp'라는 전역 변수를 이용해 EA에서 특정 타임프레임을 정의하겠습니다. 최적화 매개 변수 표를 통해 'marcroTiempo' 변수 인터벌을 설정할 수 있습니다. 덕분에 M6나 M12 분석에 리소스를 소모하지 않고 우리가 필요로 하는 단계만 실행할 수 있죠. 이런 방식으로 여러 타임프레임에 대한 EA 실행 결과를 분석할 수 있습니다.

사용자 지정 타임프레임 옵션

이것도 또 한 방법입니다.

ENUM_TIMEFRAMES marcoTmp= (ENUM_TIMEFRAMES)marcoTiempo;
-->

몇 달이면 자연스레 깨닫게 되죠. 완벽주의자 성향이 있으면 조금 더 오래 걸릴 수도 있겠네요. VPS로 컴퓨터 퍼포먼스를 최적화해 비용을 절감하려는 경우에도 조금 오래 걸릴 겁니다.


단일 심볼 또는 심볼쌍 최적화

MetaTrader5 전략 테스터에는 최적화 모드가 있습니다. 마켓워치 창에서 선택된 모든 통화쌍에 대한 EA 실행을 도와주죠. 하지만 선택된 심볼을 매개 변수로 취급하지는 않습니다. 따라서 15개의 심볼이 선택된 경우 테스터가 15번 실행되죠. EA에게 가장 잘 맞는 심볼은 어떻게 찾을 수 있을까요? 다중통화를 이용하는 EA인 경우, 어떤 매개 변수를 갖는 어떤 심볼 그룹이 최적의 결과를 낼까요? MQL5에서 문자열 변수는 최적화되지 않습니다. 그럼 어떻게 해야 할까요?

다음 방법을 따라 단일 심볼 또는 다중 심볼을 입력 변수로 코딩하세요.

input int selecDePar= 0;

string cadParesFX= selecPares(selecDePar);
-->

'selecDePar' 변수는 최적화 매개 변수로 이용되며 문자열로 변환됩니다. EA에서 'cadParesFX' 변수를 이용하세요. 심볼은 일반 최적화 매개 변수와 함께 해당 변수에 저장됩니다.

//------------------------------------- SELECT THE SET OF PAIRS -------------------------------------
string selecPares(int combina= 0)
{
   string resp="EURUSD";
   switch(combina)               
      {
         case 1: resp= "EURJPY"; break;
         case 2: resp= "USDJPY"; break;
         case 3: resp= "USDCHF"; break;      
         case 4: resp= "GBPJPY"; break;
         case 5: resp= "GBPCHF"; break;      
         case 6: resp= "GBPUSD"; break;
         case 7: resp= "USDCAD"; break;
         case 8: resp= "CADJPY"; break;      
         case 9: resp= "XAUUSD"; break;
       
         case 10: resp= "EURJPY;USDJPY"; break;
         case 11: resp= "EURJPY;GBPJPY"; break;
         case 12: resp= "GBPCHF;GBPJPY"; break;
         case 13: resp= "EURJPY;GBPCHF"; break;
         case 14: resp= "USDJPY;GBPCHF"; break;

         case 15: resp= "EURUSD;EURJPY;GBPJPY"; break;
         case 16: resp= "EURUSD;EURJPY;GBPCHF"; break;
         case 17: resp= "EURUSD;EURJPY;USDJPY"; break;
         case 18: resp= "EURJPY;GBPCHF;USDJPY"; break;
         case 19: resp= "EURJPY;GBPUSD;GBPJPY"; break;
         case 20: resp= "EURJPY;GBPCHF;GBPJPY"; break;
         case 21: resp= "USDJPY;GBPCHF;GBPJPY"; break;
         case 22: resp= "EURUSD;USDJPY;GBPJPY"; break;
       
         case 23: resp= "EURUSD;EURJPY;USDJPY;GBPUSD;USDCHF;USDCAD"; break;
         case 24: resp= "EURUSD;EURJPY;USDJPY;GBPUSD;USDCHF;USDCAD;AUDUSD"; break;
      }
   return(resp);
}
-->

목적에 따라 통화쌍 조합을 정의하고 테스터에 분석 인터벌을 설정하면 됩니다. 전략 테스터로 'selecDePar' 매개 변수를 15~22 인터벌로 최적화해 보겠습니다. 아래 그림을 참조하세요. 단일 통화 최적화 결과는 어떻게 비교할까요? 0~9 인터벌로 최적화를 실행하면 됩니다.

통화쌍 최적화

EA가 cadParesFX="EURUSD;EURJPY;GBPCHF" 변수를 받았다고 가정해 볼게요. OnInit() 함수에서 'cargaPares()' 함수를 호출하면 arrayPares[] 동적 배열이 문자열로 채워집니다. 이때 문자열은 cadParesFX 매개 변수의 ','로 구분됩니다. 모든 전역 변수는 동적 배열에 포함되며 여기에는 각 심볼의 값과 바 생성 조건이 포함됩니다. 심볼이 하나인 경우 1차원 배열이 됩니다.

//-------------------------------- STRING CONVERSION FROM CURRENCY PAIRS INTO AN ARRAY  -----------------------------------------------
int cargaPares(string cadPares, string &arrayPares[])
{            //convierte "EURUSD;GBPUSD;USDJPY" a {"EURUSD", "GBPUSD", "USDJPY"}; devuelve el número de paresFX
   string caract= "";
   int i= 0, k= 0, contPares= 1, longCad= StringLen(cadPares);
   if(cadPares=="")
   {
      ArrayResize(arrayPares, contPares);
      arrayPares[0]= _Symbol;
   }
   else
   {
      for (k= 0; k<longCad; k++) if (StringSubstr(cadPares, k, 1)==";") contPares++;
      ArrayResize(arrayPares, contPares);    
      ZeroMemory(arrayPares);
      for(k=0; k<longCad; k++)
      {
         caract= StringSubstr(cadPares, k, 1);
         if (caract!=";") arrayPares[i]= arrayPares[i]+caract;
         else i++;
      }
    }
   return(contPares);
}
-->

OnInit()에서 해당 함수는 다음의 방법으로 구현됩니다.

string ar_ParesFX[];    //array, containing names of the pairs for the EA to work with
int numSimbs= 1;        //variable, containing information about the number of symbols it works with

int OnInit()
{
   
   //...
   numSimbs= cargaPares(cadParesFX, ar_ParesFX);     //returns the ar_ParesFX array with pairs for work in the EA
   //...
   
}
-->

numSimbs>1인 경우 OnChartEvent() 함수가 호출됩니다. 다중 통화 시스템에 적용되는 방법입니다. 다른 경우 OnTick() 함수가 사용되죠.

void OnTick()
{
   string simb="";
   bool entrar= (nSimbs==1);
   if(entrar)
   {   
      .../...
      simb= ar_ParesFX[0];
      gestionOrdenes(simb);
      .../...
   }
   return;
}

//+------------------------------------------------------------------+
//| EVENT HANDLER                                                   |
//+-----------------------------------------------------------------+
void OnChartEvent(const int idEvento, const long& lPeriodo, const double& dPrecio, const string &simbTick)
{
   bool entrar= nSimbs>1 && (idEvento>=CHARTEVENT_CUSTOM);
   if(entrar)      
   {
      .../...
      gestionOrdenes(simbTick);
      .../...
   }
}
  
-->

따라서 입력 매개 변수는 반드시 해당 통화쌍을 포함하게 됩니다. 예를 들어, Digits() 함수 대신 다음을 이용하는 것이죠.

//--------------------------------- SYMBOLS OF A SYMBOL ---------------------------------------
int digitosSimb(string simb= NULL)
{
   int numDig= (int)SymbolInfoInteger(simb, SYMBOL_DIGITS);
   return(numDig);
}
-->

다시 말해, Symbol()이나 Point() 함수 및 MetaTrader4에서 전통적으로 사용되는 매매 호가 등의 변수는 모두 잊는 겁니다.

//----------------------------------- POINT VALUE in price (Point())---------------------------------
double valorPunto(string simb= NULL) 
{
   double resp= SymbolInfoDouble(simb, SYMBOL_POINT);
   return(resp);
}
-->
//--------------------------- precio ASK-BID  -----------------------------------------
double precioAskBid(string simb= NULL, bool ask= true)
{
   ENUM_SYMBOL_INFO_DOUBLE precioSolic= ask? SYMBOL_ASK: SYMBOL_BID;
   double precio= SymbolInfoDouble(simb, precioSolic);
   return(precio);
}
-->

바 생성에 대해서도 신경쓰지 않습니다. EURUSD 차트의 틱이 새로운 바의 생성을 알리는 경우, 다음 2초간 USDJPY 틱이 수신되지 않을 수 있습니다. 그 결과 다음 USDJPY 틱 생성 시 EA는 EURUSD의 경우 2초 전에 동일한 이벤트에 대한 틱이 발생했음을 알게 되죠.

//------------------------------------- NEW MULTI-CURRENCY CANDLESTICK -------------------------------------
bool nuevaVelaMD(string simb= NULL, int numSimbs= 1, ENUM_TIMEFRAMES marcoTmp= PERIOD_CURRENT)
{
        static datetime arrayHoraNV[];
        static bool primVez= true;
        datetime horaVela= iTime(simb, marcoTmp, 0);    //received opening time of the current candlestick
        bool esNueva= false;
        int codS= buscaCadArray(simb, nombreParesFX);      
        if(primVez)
        {
           ArrayResize(arrayHoraNV, numSimbs);
           ArrayInitialize(arrayHoraNV, 0);     
           primVez= false;
        }
        esNueva= codS>=0? arrayHoraNV[codS]!= horaVela: false;
        if(esNueva) arrayHoraNV[codS]= horaVela;
        return(esNueva); 
}
-->

덕분에 다음의 사실을 발견했습니다.

  • 해당 EA는 EURUSD에서 잘 작동합니다.
  • EURJPY의 경우 전혀 제대로 작동하지 않죠.
  • USDJPY의 경우 괜찮게 작동합니다.
  • EURUSD, GBPCHF, EURJPY가 쌍을 이루는 경우 꽤 괜찮게 작동합니다.

타임프레임이 M5일 때와 몇몇 최적화 변수 조합에 대해서는 동일한 결과가 나오지만 타임프레임이 H1이나 H2인 경우에는 그렇지 않습니다.

좀 이상하죠. 그래서 기술지원팀에 문의해 두었습니다. 이유는 모르겠지만 전략 테스터에서 선택한 통화쌍에 따라 최적화 결과가 다르게 나타납니다. 따라서 전략을 개발하는 동안 최적화에서 올바른 분석이 진행되도록 현재 선택한 통화쌍을 유지할 겁니다.


매개 변수 조합 최적화

가끔은 말도 안되는 매개 변수 조합이 최적화를 통해 적절한 것으로 나타나는 경우도 있습니다. 오히려 전략 자체를 이상하게 보이게 할 정도죠. 예를 들어 'maxSpread' 변수가 거래 오퍼레이션의 스프레드 세트 값을 정의한다고 가정하고, 해당 변수를 브로커의 스프레드가 30 미만, XAUUSD가 400인 경우에 대해 최적화한다고 생각해보죠. 스프레드가 50을 초과하고 XAUUSD가 200 미만인 경우에 해당하는 통화쌍은 분석할 필요가 없겠죠. '인터벌 값이 20인 0과 600사이의 maxSpread'를 평가할 것을 설정합니다. 하지만 이 경우 말이 안되는 조합이 상당수 생성되죠.

앞서 selecPares() 함수를 이용해 최적화 대상이 되는 통화쌍을 설정했습니다. EURUSD가 옵션 0, XAUUSD가 옵션 9죠. 불리언 자료형 paramCorrect의 전역 변수를 설정합니다.

bool paramCorrect= (selecDePar<9 && maxSpread<50) ||
                   (selecDePar==9 && maxSpread>200);
-->

paramCorrect가 참값을 갖는 경우 OnInit()을 실행합니다.

int OnInit()
{   
   ENUM_INIT_RETCODE resp= paramCorrect? INIT_SUCCEEDED: INIT_PARAMETERS_INCORRECT;
   if (paramCorrect)
   {
      //...
      nSimbs= cargaPares(cadParesFX, nombreParesFX);     //return the array nombreParesFX containing pairs for work in the EA
      //... function of the EA initialization
   }
   return(resp);
}
-->

paramCorrect가 거짓값을 갖는 경우 EA는 OnInit()을 실행하지 않으며 전략 테스터에 INIT_PARAMETERS_INCORRECT를 반환합니다. 이는 입력 데이터 세트가 올바르지 않음을 의미하죠. 이 경우 해당 데이터 세트는 더이상 다른 에이전트에게 전달되지 않으며 최적화 결과 표는 0으로 채워지고 붉은색으로 표시됩니다(아래 그림 참조).

잘못된 매개 변수 사용 결과

또한 프로그램이 종료된 이유가 OnDeinit()의 입력 변수로 전달되어 EA 종료 원인에 대한 이해를 돕습니다. 사실 이건 조금 다른 문제입니다.

void OnDeinit(const int motivo)
{
   if(paramCorrect)
   {
      
      //functions of the program shutdown
      
   }
   infoDeInit(motivo);
   return;
}

//+-------------------------------------- INFORMATION ABOUT THE PROGRAM SHUTDOWN----------------------------
string infoDeInit(int codDeInit)
{                       //informs of the reason of the program shutdown
   string texto= "program initialization...", text1= "CIERRE por: ";
   switch(codDeInit)
   {
      case REASON_PROGRAM:     texto= text1+"The EA finished its work with the ExpertRemove() function"; break;  //0
      case REASON_ACCOUNT:     texto= text1+"The account was changed"; break;                                    //6
      case REASON_CHARTCHANGE: texto= text1+"Symbol or timeframe change"; break;                                 //3
      case REASON_CHARTCLOSE:  texto= text1+"The chart was closed"; break;                                       //4
      case REASON_PARAMETERS:  texto= text1+"Input parameters changed by the user"; break;                       //5
      case REASON_RECOMPILE:   texto= text1+"The program was recompiled"; break //2
      case REASON_REMOVE:      texto= text1+"The program was deleted from the chart"; break;                     //1
      case REASON_TEMPLATE:    texto= text1+"Another chart template was used"; break;                            //7
      case REASON_CLOSE:       texto= text1+"The terminal was closed"; break;                                    //9
      case REASON_INITFAILED:  texto= text1+"The OnInit() handler returned non-zero value"; break;               //8
      default:                 texto= text1+"Other reason";
   }
   Print(texto);
   return(texto);
}
-->

paramCorrect가 거짓값을 갖는 경우 EA를 실행하지 않는 게 낫죠. 그러면 자동으로 최적화 단계 값이 0이 됩니다. 아니면 괜히 컴퓨터 리소스와 MQL5.community 계정에 비용을 낭비하게 되니까요.

물론 이 과정을 OnTesterInit(), ParameterGetRange(), ParametersSetRange() 함수로 대체할 수도 있지만 위의 과정이 더 간단합니다. 더 확실하기도 하고요. OnTesterInit() 결과는 일정하지 않거든요.


결론

MetaTrader5에서 최적화 타임프레임 검색 속도를 높이는 법을 알아봤습니다. '통화쌍' 변수를 최적화하는 방법도 알아봤죠. MetaTrader5는 문자열 변수를 최적화하지 않습니다. 로직이 충돌하는 입력 변수 세트를 미리 제외시켜 최적화 단계를 줄이는 방법도 알아봤습니다. 컴퓨터 리소스와 부가 비용을 아낄 수 있는 방법이죠.

어느 정도의 경험이 있는 프로그래머라면 누구나 사용할 수 있는 방법입니다. 새로운 기법은 아니고 디버깅 프로그램에 대한 오랜 정보 검색의 결과로 찾은 것들입니다. 굉장히 간단하면서도 효과적인 방법들이죠. MQL5가 잘됐으면 하면서 왜 이런 정보를 공유하냐고요? 프로그래머들이 겪는 외로움을 이겨내기 위해서랄까요.

읽어 주어 고맙습니다. 혹시 숙련된 개발자시라도 너무 혹되게 평가하지는 마세요.

MetaQuotes 소프트웨어 사를 통해 스페인어가 번역됨.
원본 기고글: https://www.mql5.com/es/articles/1052

MetaTrader 마켓 개요(인포그래픽) MetaTrader 마켓 개요(인포그래픽)
몇 주 전에 프리랜서 서비스에 대한 인포그래픽을 게시했습니다. MetaTrader 마켓 관련 통계 자료도 곧 올리겠다고 말씀드렸었는데요. 함께 살펴보시죠.
Johnpaul77 시그널 프로바이더: "3년 째 수익을 올리고 있는 전략을 왜 바꿔야 하나요?" Johnpaul77 시그널 프로바이더: "3년 째 수익을 올리고 있는 전략을 왜 바꿔야 하나요?"
MQL5.com 이용자들이 대부분의 시간을 Johnpaul77의 시그널 페이지를 보며 보낸다는 걸 아셨나요? 약 900명의 구독자가 총 570만 달러에 달하는 자금을 투자 중입니다. 그래서 시그널 프로바이더를 인터뷰해봤습니다. 총 네 분이시더라고요! 팀원 간 업무 배분은 어떻게 하는지? 어떤 기술적 도구를 이용하는지? 왜 John Paul이라는 이름을 지었는지? 그리고 마지막으로, 어떻게 인도네시아의 일반 게이머들이 MQL5.com의 최상위 시그널 프로바이더가 되었는지? 본문에서 모두 알아보겠습니다.
회귀 분석으로 거시경제 데이터가 통화 가격 변동에 미치는 영향 알아보기 회귀 분석으로 거시경제 데이터가 통화 가격 변동에 미치는 영향 알아보기
본문은 거시 경제 통계에 대한 다중 회귀 분석 적용법을 다룹니다. EURUSD를 이용해 환율 변동에 대한 통계의 영향에 대해서도 평가해 보겠습니다. 해당 평가를 통해 기본적 분석을 자동화하여 초보 투자자들도 이용할 수 있습니다.
소셜 테크 스타트업 만들기 Pt. 2: MQL5 REST 클라이언트 프로그래밍 소셜 테크 스타트업 만들기 Pt. 2: MQL5 REST 클라이언트 프로그래밍
앞서 이야기한 PHP 기반 트위터를 만들어 보겠습니다. SDSS의 여러 부분을 함께 묶을 겁니다. 클라이언트 측 시스템 구조에는 MQL5의 WebRequest() 함수를 이용해 HTTP로 거래 시그널을 전송하도록 하겠습니다.