English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
내가 어째서 걱정을 멈추고 자가학습형 Expert Advisor를 만들었는가

내가 어째서 걱정을 멈추고 자가학습형 Expert Advisor를 만들었는가

MetaTrader 5트레이딩 | 11 10월 2021, 15:51
216 0
Roman Zamozhnyy
Roman Zamozhnyy

컨셉

Expert Advisor를 만든 후 우리는 나머지를 내장 전략 테스터(Strategy Tester)를 이용하여 최적 패러미터를 선택할 것입니다. Expert Advisor와 전략 테스터를 선택하는대로 Expert Advisor를 실행하며, 이 과정에서 중대한 변화가 발생하면 Expert Advisor를 중지하고 전략 테스터를 사용하여 계속해서 최적화합니다. 그리고 반복합니다.

혹시 굳이 Expert Advisor를 중단시키지 말고, 재최적화 판단과 재최적화 과정을 코드의 일부에 넣어 굳이 우리가 중단시키지 않고도 일련의 과정을 처리할 수 있게 할 수는 없을까요?

이에 대한 한가지 해법은 Quantum의 글 "Adaptive Trading Systems and Their Use in MetaTrader5 Terminal"에서 제시되었는데, 이는 실제 매매 시스템과 소수 (수적 제한은 없는) 가상 매매 전략을 활용하여 가장 높은 수익을 올린 것을 활용하는 방법에 관한 것이었습니다. 매매 전략을 바꾸는 판단은 특정 고정 막대 값을 넘겼을 때 이루어집니다.

저는 joo"Genetic Algorithms - It's Easy!" 문서에서 정한 유전 알고리즘(Genetic Algorithm, GA) 코드를 사용하는 것을 선택했습니다. 그런 타입의 Expert Advisor를 직접 들여다 봅시다! (한가지 예시는 Automated Trading Championship 2011에 참여한 EA입니다).


작업중

그러기에 앞서 먼저 Expert Advisor란 무얼 하는 존재인가 정의할 필요가 있습니다. 먼저, 말할 것도 없이 선택한 전략에 따라 매매를 해야겠죠. 두번째는 결정을 내려야한다는 것입니다. 혹시 지금이 재최적화를 할 타이밍인가 아닌가 (입력 패러미터를 기반으로 새로운 최적화를 해야하는가). 세번째로 GA를 이용하여 재최적화하는. 먼저 우리는 가장 간단한 재최적화를 들여다 봅니다. 어떤 전략이 있으면, 그냥 새 패러미터를 선택합니다. 그리고 그 뒤에 GA를 활용하여 변동하는 시장 환경에서 다른 전략을 선택하고, 어떻게 하고 이런 게 가능할에 대하여 지켜볼 것입니다.

또한 최적화 판단 함수의 시뮬레이션을 용이하게 하기 위해 하나의 기구에서 완성된 막대만 매매하기로 결정합니다. 포지션 추가나 부분 청산은 없습니다. 추적 손절매와 고정 손절매를 선호하는 분들은 "Tick Generation Algorithm in MetaTrader5 Strategy Tester"문서를 통해 손절매와 이익 실현의 최적화 판단 함수 체크를 구현하는 방법에 ㅇ대해 확인하시기 바랍니다. 아래 영리한 문구에 대해 말씀드리겠습니다.

제가 테스터내에서 테스트 모드를 시뮬레이션 돌릴 때에 쓰는 최적화 판단 함수는 "Open Prices Only"라고 합니다. 그러나! 이 말이 이 함수가 최적화 판단 함수 내에서 사용 가능한 유일한 테스트 함수라는 말은 아닙니다. 더 세심한 사람들은 "Every Tick" 모드를 이용하여 최적화 판단 함수를 구현하고싶을지도 모르겠습니다. 이미 있는걸 또 만들게 하고 싶지 않고, "every tick" 버전을 포기하게하고싶지도 않기때문에 MetaQuotes가 개발한 이미 있는 알고리즘을 보시라고 말씀드리겠습니다. 다르게 말하면 이 문서를 읽으면 최적화 판단 함수에서 "Every Tick" 모드를 구현할 수 있을 것이고 그 결과로 FF 내 손절과 이익 실현을 올바르게 시뮬레이션할 수 있을 것입니다.

본론, 그러니까 전략 구현으로 들어가기 전에, 새 바를 열고 포지션을 개방 혹은 청산하는 보조 함수 구현과 기술적 중점을 먼저 다루고 넘어가겠습니다.

//+------------------------------------------------------------------+
//| Define whether a new bar has opened                             |
//+------------------------------------------------------------------+
bool isNewBars()
  {
   CopyTime(s,tf,0,1,curBT);
   TimeToStruct(curBT[0],curT);
   if(tf==PERIOD_M1||
      tf==PERIOD_M2||
      tf==PERIOD_M3||
      tf==PERIOD_M4||
      tf==PERIOD_M5||
      tf==PERIOD_M6||
      tf==PERIOD_M10||
      tf==PERIOD_M12||
      tf==PERIOD_M15||
      tf==PERIOD_M20||
      tf==PERIOD_M30)
      if(curT.min!=prevT.min)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_H1||
      tf==PERIOD_H2||
      tf==PERIOD_H3||
      tf==PERIOD_H4||
      tf==PERIOD_H6||
      tf==PERIOD_H8||
      tf==PERIOD_M12)
      if(curT.hour!=prevT.hour)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_D1||
      tf==PERIOD_W1)
      if(curT.day!=prevT.day)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   if(tf==PERIOD_MN1)
      if(curT.mon!=prevT.mon)
        {
         prevBT[0]=curBT[0];
         TimeToStruct(prevBT[0],prevT);
         return(true);
        };
   return(false);
  }
//+------------------------------------------------------------------+
//|  ClosePosition                                                   |
//+------------------------------------------------------------------+
void ClosePosition()
  {
   request.action=TRADE_ACTION_DEAL;
   request.symbol=PositionGetSymbol(0);
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) request.type=ORDER_TYPE_SELL; 
   else request.type=ORDER_TYPE_BUY;
   request.type_filling=ORDER_FILLING_FOK;
   if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
      SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      request.sl=NULL;
      request.tp=NULL;
      request.deviation=100;
     }
   while(PositionsTotal()>0)
     {
      request.volume=NormalizeDouble(MathMin(PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(PositionGetSymbol(0),SYMBOL_VOLUME_MAX)),2);
      if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
     }
  }
//+------------------------------------------------------------------+
//|  OpenPosition                                                    |
//+------------------------------------------------------------------+
void OpenPosition()
  {
   double vol;
   request.action=TRADE_ACTION_DEAL;
   request.symbol=s;
   request.type_filling=ORDER_FILLING_FOK;
   if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
      SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
     {
      request.sl=NULL;
      request.tp=NULL;
      request.deviation=100;
     }
   vol=MathFloor(AccountInfoDouble(ACCOUNT_FREEMARGIN)*optF*AccountInfoInteger(ACCOUNT_LEVERAGE)
       /(SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE)*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP)))*SymbolInfoDouble(s,SYMBOL_VOLUME_STEP);
   vol=MathMax(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MIN));
   vol=MathMin(vol,GetPossibleLots()*0.95);
   if(SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)!=0) vol=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_LIMIT)),2);
   request.volume=NormalizeDouble(MathMin(vol,SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2);
   while(PositionSelect(s)==false)
     {
      if(SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(s,SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID); 
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
      PositionSelect(s);
     }
   while(PositionGetDouble(POSITION_VOLUME)<vol)
     {
      request.volume=NormalizeDouble(MathMin(vol-PositionGetDouble(POSITION_VOLUME),SymbolInfoDouble(s,SYMBOL_VOLUME_MAX)),2);
      if(SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_REQUEST||
         SymbolInfoInteger(PositionGetSymbol(0),SYMBOL_TRADE_EXEMODE)==SYMBOL_TRADE_EXECUTION_INSTANT)
        {
         if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
         else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
        }
      OrderSend(request,result);
      Sleep(10000);
      PositionSelect(s);
     }
  }
//+------------------------------------------------------------------+

신중한 고려를 마치시면 포지션 개방 함수 내에 세가지 중요한 패러미터, soptF 변수와 GetPossibleLots() 함수 호출이 있다는 것을 눈치 채셨을 것입니다.

  • s - 매매 기구이자 GA에 의해 최적화된 변수 중 하나
  • optF - 매매에 사용되는 예치금의 일부 (마찬가지로 GA에 의해 최적화된 변수 중 하나). 
  • GetPossibleLots() 함수 - 매매에 사용되는 예치금의 일부를 반환
//+------------------------------------------------------------------+
//|  GetPossibleLots                                                 |
//+------------------------------------------------------------------+
double GetPossibleLots()
  {
   request.volume=1.0;
   if(request.type==ORDER_TYPE_SELL) request.price=SymbolInfoDouble(s,SYMBOL_BID);
   else request.price=SymbolInfoDouble(s,SYMBOL_ASK);
   OrderCheck(request,check);
   return(NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)/check.margin,2));
  }

서술의 순서를 약간 어기고, 모든 Expert Advisor에게 공통적인 기능과 2단계에서 필수적인 기능 두 가지를 추가로 소개합니다.

//+------------------------------------------------------------------+
//|  InitRelDD                                                       |
//+------------------------------------------------------------------+
void InitRelDD()
  {
   ulong DealTicket;
   double curBalance;
   prevBT[0]=D'2000.01.01 00:00:00';
   TimeToStruct(prevBT[0],prevT);
   curBalance=AccountInfoDouble(ACCOUNT_BALANCE);
   maxBalance=curBalance;
   HistorySelect(D'2000.01.01 00:00:00',TimeCurrent());
   for(int i=HistoryDealsTotal();i>0;i--)
     {
      DealTicket=HistoryDealGetTicket(i);
      curBalance=curBalance+HistoryDealGetDouble(DealTicket,DEAL_PROFIT);
      if(curBalance>maxBalance) maxBalance=curBalance;
     }
  }
//+------------------------------------------------------------------+
//|  GetRelDD                                                        |
//+------------------------------------------------------------------+
double GetRelDD()
  {
   if(AccountInfoDouble(ACCOUNT_BALANCE)>maxBalance) maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);
   return((maxBalance-AccountInfoDouble(ACCOUNT_BALANCE))/maxBalance);
  }

여기에서 무엇이 보이십니까? 첫 번째 함수는 최대 계정 잔액 값을 결정하고, 두 번째 함수는 계정의 상대적인 전류 인출액을 계산합니다. 2단계 설명에서 특이함이 드러납니다.

그렇게 Expert Advisor로 넘어가서. 우리는 초보에 불과하기 때문에 Expert Advisor가 전략을 선택하게 하도록 만들 것이 아니라 이하의 전략을 따라서 두개의 Expert Advisor를 구현할 것입니다.
  • 하나는 이동 평균의 교차점을 이용하여 매매합니다 (골든 크로스(Golden Cross) - 상품을 매수합니다, 데드 크로스(Death Cross) - 상품을 매도합니다);
  • 다른 하나는 지난 다섯번의 매매 세션 중의 [0..1] 범위 내 가격 변동을 받는 간단한 신경망입니다.

알고리즘적으로 자가 최적화 하는 Expert Advisor는 아래와 같이 나타내질 수 있습니다.

  1. Expert Advisor가 사용하는 변수의 초기화: 인디케이터 버퍼를 정의 및 초기화하거나 신경 네트워크 위상을 설정합니다(레이어의 레이어/뉴런 수, 모든 레이어에서 뉴런 수가 동일한 간단한 신경 네트워크 설정 예). 작업 시간대를 설정합니다. 또한, 가장 중요한 단계인 유전적 최적화 함수, 가장 중요한 기능인 최적화 판단 함수 (Fitness Function, FF)이라고 합니다.

    중요! 각각의 매매 전략에는 각기 다른 FF가 있습니다. 예를들어 단일 이동 평균용의 FF는 이중 이동 평균용 FF와 완전히 다르며 이는 또 신경망 FF와 전혀 다릅니다.

    제 Expert Advisors의 FF 성능 결과는 상대적 단위가 외부 변수로 설정된 임계값을 초과하지 않는 한(예: 0.5) 최대 균형입니다. 즉, 다음 GA 실행에서 잔고 100,000이 있을때 상대적잔고차감액이 -0,6이면 FF=0.0입니다. 독자분의 경우엔 FF는 완전히 다른 기준을 가져오는 일 조차 있을 수 있습니다..

    유전자 알고리즘 성능 결과 수집: 이동 평균의 교차에 대해 이것들은 이동 평균 기간이 될 것이고, 신경망의 경우 시냅스 가중치가 있을 것이며, 두 가지 모두에 공통적인 결과(그리고 제 다른 Expert Advisors 경우에도)는 다음 재최적화까지 거래될 상품, 그리고 우리에게 익숙한 optF, 즉 거래에 사용될 예치금의 일부입니다. 당신이 원하는대로 당신의 FF에 추가적인 최적화 패러미터를 추가해도 됩니다. 예를 들어 타임프레임이나 다른 패러미터를 선택해도 됩니다...

    초기화 마지막 단계는 최대 계좌 잔고값을 찾는 겁니다. 왜 이것이 중요하냐고요? 그것은 이 지점이 바로 재최적화 판단을 내리는 시작점이기 때문입니다.

    중요한 것은! 재최적화에 대한 결정이 내려지는 방법: 상대적 잔고 차감액이 외부 변수로 설정된 특정 임계 값에 도달하면(우리의 예시에서는 - 0.2), 다시 최적화해야 한다는 것입니다. Expert Advisor가 중요한 제한에 도달했을 때 모든 바에서 재최적화를 구현하지 않도록 최대 잔액 값이 현재 값으로 대체됩니다.

    나의 사랑하는 독자님은 재최적화 구현에 완전히 다른 기준이 있으실지도모르겠습니다.

  2. 매매 진행중.

  3. 모든 청산된 포지션에서 잔액 인출이 임계값에 도달했는지 확인합니다. 임계값에 도달하면 GA를 실행하고 성능 결과를 수집합니다(이게 바로 재최적화입니다!).

  4. 그리고 우리는 세계를 파산시키지 말아달라는 전무이사 또는 (혹은 더 그럴싸하게) Stop Out, Margin Call, 응급 구급차를 기다리는 전화를 기다리고 있습니다...

이동 평균 전략(소스 코드도 있습니다)\나 신경망을이용하는 Expert Advisor의 구현된 모습을 보아주십시오. 신경망 쪽도 소스코드는 있습니다.

이 코드는 GPL 라이센스 약관과 조건 하에 제공됩니다.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   tf=Period();
//---for bar-to-bar test...
   prevBT[0]=D'2001.01.01';
//---... long ago
   TimeToStruct(prevBT[0],prevT);
//--- historical depth (should be set since the optimisation is based on historical data)
   depth=10000;
//--- copies at a time (should be set since the optimisation is based on historical data)
   count=2;
   ArrayResize(LongBuffer,count);
   ArrayResize(ShortBuffer,count);
   ArrayInitialize(LongBuffer,0);
   ArrayInitialize(ShortBuffer,0);
//--- calling the neural network genetic optimisation function
   GA();
//--- getting the optimised neural network parameters and other variables
   GetTrainResults();
//--- getting the account drawdown
   InitRelDD();
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(isNewBars()==true)
     {
      bool trig=false;
      CopyBuffer(MAshort,0,0,count,ShortBuffer);
      CopyBuffer(MAlong,0,0,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         if(PositionsTotal()>0)
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               ClosePosition();
               trig=true;
              }
           }
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         if(PositionsTotal()>0)
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               ClosePosition();
               trig=true;
              }
           }
        }
      if(trig==true)
        {
         //--- if the account drawdown has exceeded the allowable value:
         if(GetRelDD()>maxDD)
           {
            //--- calling the neural network genetic optimisation function
            GA();
            //--- getting the optimised neural network parameters and other variables
            GetTrainResults();
            //--- readings of the drawdown will from now on be based on the current balance instead of the maximum balance
            maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);
           }
        }
      CopyBuffer(MAshort,0,0,count,ShortBuffer);
      CopyBuffer(MAlong,0,0,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         request.type=ORDER_TYPE_SELL;
         OpenPosition();
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         request.type=ORDER_TYPE_BUY;
         OpenPosition();
        }
     };
  }
//+------------------------------------------------------------------+
//| Preparing and calling the genetic optimizer                      |
//+------------------------------------------------------------------+
void GA()
  {
//--- number of genes (equal to the number of optimised variables), 
//--- all of them should be specified in the FitnessFunction())
   GeneCount      =OptParamCount+2;    
//--- number of chromosomes in a colony
   ChromosomeCount=GeneCount*11;
//--- minimum search range
   RangeMinimum   =0.0;
//--- maximum search range
   RangeMaximum   =1.0;
//--- search pitch
   Precision      =0.0001;
//--- 1 is a minimum, anything else is a maximum
   OptimizeMethod =2;                                                 
   ArrayResize(Chromosome,GeneCount+1);
   ArrayInitialize(Chromosome,0);
//--- number of epochs without any improvement
   Epoch          =100;                                               
//--- ratio of replication, natural mutation, artificial mutation, gene borrowing, 
//--- crossingover, interval boundary displacement ratio, every gene mutation probabilty, %
   UGA(100.0,1.0,1.0,1.0,1.0,0.5,1.0);                                
  }
//+------------------------------------------------------------------+
//| Fitness function for neural network genetic optimizer:           | 
//| selecting a pair, optF, synapse weights;                         |
//| anything can be optimised but it is necessary                    |
//| to carefully monitor the number of genes                         |
//+------------------------------------------------------------------+
void FitnessFunction(int chromos)
  {
   int    b;
//--- is there an open position?
   bool   trig=false;
//--- direction of an open position
   string dir="";
//--- opening price
   double OpenPrice=0;
//--- intermediary between a gene colony and optimised parameters
   int    z;
//--- current balance
   double t=cap;
//--- maximum balance
   double maxt=t;
//--- absolute drawdown
   double aDD=0;
//--- relative drawdown
   double rDD=0.000001;
//--- fitness function proper
   double ff=0;
//--- GA is selecting a pair
   z=(int)MathRound(Colony[GeneCount-1][chromos]*12);
   switch(z)
     {
      case  0: {s="AUDUSD"; break;};
      case  1: {s="AUDUSD"; break;};
      case  2: {s="EURAUD"; break;};
      case  3: {s="EURCHF"; break;};
      case  4: {s="EURGBP"; break;};
      case  5: {s="EURJPY"; break;};
      case  6: {s="EURUSD"; break;};
      case  7: {s="GBPCHF"; break;};
      case  8: {s="GBPJPY"; break;};
      case  9: {s="GBPUSD"; break;};
      case 10: {s="USDCAD"; break;};
      case 11: {s="USDCHF"; break;};
      case 12: {s="USDJPY"; break;};
      default: {s="EURUSD"; break;};
     }
   MAshort=iMA(s,tf,(int)MathRound(Colony[1][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   MAlong =iMA(s,tf,(int)MathRound(Colony[2][chromos]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   dig=MathPow(10.0,(double)SymbolInfoInteger(s,SYMBOL_DIGITS));
   
//--- GA is selecting the optimal F
   optF=Colony[GeneCount][chromos];                                   
   
   leverage=AccountInfoInteger(ACCOUNT_LEVERAGE);
   contractSize=SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE);
   b=MathMin(Bars(s,tf)-1-count-MaxMAPeriod,depth);
   
//--- for a neural network using historical data - where the data is copied from
   for(from=b;from>=1;from--) 
     {
      CopyBuffer(MAshort,0,from,count,ShortBuffer);
      CopyBuffer(MAlong,0,from,count,LongBuffer);
      if(LongBuffer[0]>LongBuffer[1] && ShortBuffer[0]>LongBuffer[0] && ShortBuffer[1]<LongBuffer[1])
        {
         if(trig==false)
           {
            CopyOpen(s,tf,from,count,o);
            OpenPrice=o[1];
            dir="SELL";
            trig=true;
           }
         else
           {
            if(dir=="BUY")
              {
               CopyOpen(s,tf,from,count,o);
               if(t>0) t=t+t*optF*leverage*(o[1]-OpenPrice)*dig/contractSize; else t=0;
               if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
               if((maxt>0) && (aDD/maxt>rDD)) rDD=aDD/maxt;
               OpenPrice=o[1];
               dir="SELL";
               trig=true;
              }
           }
        }
      if(LongBuffer[0]<LongBuffer[1] && ShortBuffer[0]<LongBuffer[0] && ShortBuffer[1]>LongBuffer[1])
        {
         if(trig==false)
           {
            CopyOpen(s,tf,from,count,o);
            OpenPrice=o[1];
            dir="BUY";
            trig=true;
           }
         else
           {
            if(dir=="SELL")
              {
               CopyOpen(s,tf,from,count,o);
               if(t>0) t=t+t*optF*leverage*(OpenPrice-o[1])*dig/contractSize; else t=0;
               if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
               if((maxt>0) && (aDD/maxt>rDD)) rDD=aDD/maxt;
               OpenPrice=o[1];
               dir="BUY";
               trig=true;
              }
           }
        }
     }
   if(rDD<=trainDD) ff=t; else ff=0.0;
   AmountStartsFF++;
   Colony[0][chromos]=ff;
  }

//+---------------------------------------------------------------------+
//| getting the optimized neural network parameters and other variables |
//| should always be equal to the number of genes                       |
//+---------------------------------------------------------------------+
void GetTrainResults()
  {
//---  intermediary between a gene colony and optimised parameters
   int z;                                                             
   MAshort=iMA(s,tf,(int)MathRound(Chromosome[1]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   MAlong =iMA(s,tf,(int)MathRound(Chromosome[2]*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
   CopyBuffer(MAshort,0,from,count,ShortBuffer);
   CopyBuffer(MAlong,0,from,count,LongBuffer);
//--- save the best pair
   z=(int)MathRound(Chromosome[GeneCount-1]*12);                      
   switch(z)
     {
      case  0: {s="AUDUSD"; break;};
      case  1: {s="AUDUSD"; break;};
      case  2: {s="EURAUD"; break;};
      case  3: {s="EURCHF"; break;};
      case  4: {s="EURGBP"; break;};
      case  5: {s="EURJPY"; break;};
      case  6: {s="EURUSD"; break;};
      case  7: {s="GBPCHF"; break;};
      case  8: {s="GBPJPY"; break;};
      case  9: {s="GBPUSD"; break;};
      case 10: {s="USDCAD"; break;};
      case 11: {s="USDCHF"; break;};
      case 12: {s="USDJPY"; break;};
      default: {s="EURUSD"; break;};
     }
//--- saving the best optimal F
   optF=Chromosome[GeneCount];                                        
  }
//+------------------------------------------------------------------+

알고리즘 내의 주요 함수, FF를 봅시다.

GA (GA 함수())에서 최적화 변수를 입력 패러미터로 받아 FF 내에서 일정 기간 내 (예를 들면 10000 바 만큼) 매매 과정 시뮬레이션을 기반(MetaQuotes의 테스터가 그렇듯)으로한 자가 최적화 Expert Advisor의 기본 아이디어입니다. 이동 평균 교차를 기반으로 하는 알고리즘의 경우 최적화된 변수는 다음과 같습니다.

  • 상품(외환거래에서는 화폐쌍). 맞습니다, 일반적인 다중 통화 Expert Advisor이며, Genetic algorithm은 상품을 선택합니다(앞서 언급한 챔피언십 참가용 Expert Advisor에서 코드를 가져왔기 때문에, 해당 코드는 챔피언십 통화 페어와 일치하지만 일반적으로는 브로커가 의뢰한 어떠한 상품이건 가능합니다).
    비고: 아쉽게도 그 Expert Advisor는 테스트 모드에서는 MarketWatch 창에서 화폐쌍 목록을 받아올 수 없습니다 (MetaQuotes 유저들 덕분에 여기에 언급합니다 - 안돼!). 따라서 외환거래나 주식 용으로 테스터 내 Expert Advisor를 굴려보고 싶으시다면 FF와 GetTrainResults() 함수 내 상품을 직접 설정하시면 됩니다
  • 매매에 쓰이는 예치금의 일부.
  • 두 이동 평균의 기간.

아래 예시는 테스트용 Expert Advisor의 변형입니다!

실제 매매 코드는 Market Watch 창에서 상품의 목록을 받아 쓸 수 있기 때문에 훨씬 간략화될 수 있습니다.

이것을 FF와 GetTrainResults() 함수에서 처리하고 코멘트를 다음처럼 달려면, "//--- GA가 화폐쌍을 고르는 중" 그리고 "//--- 최고의 화폐쌍을 고르는 중" 아래처럼 쓰십시오.

//--- GA is selecting a pair
  z=(int)MathRound(Colony[GeneCount-1][chromos]*(SymbolsTotal(true)-1));
  s=SymbolName(z,true);

FF 시작 부분에서 과거 매매 이력 기반 매매 시뮬레이션을 위한 변수가 필요하다면 명시하고 초기화합니다. 다음 단계에서는 유전 알고리즘을 통해 각기 다른 최적화 변수 값을 수집합니다. 예를 들어,"optF=Colony[GeneCount][chromos]" 이 줄에서 예치금 값이 GA에서 FF로 넘겨집니다.

사용 가능한 막대 수를 추가로 확인하고 10000번째 막대 또는 첫 번째 막대로 시작하여 "Open Price only" 모드에서 견적을 받고 거래 결정을 내리는 과정을 시뮬레이션합니다.

  • 이동 평균 값을 버퍼로 복사합니다.
  • 이게 데드 크로스인지 확인하십시오.
  • 만약 이것이 데드 크로스고 개방 포지션이 없으면 (if(trig==false)) - 가상 매도 포지션을 개방합니다 (개방가와 방향을 기억하십시오).
  • 만약 이것이 데드 크로스고 개방된 매수 포지션이면 (if(dir=="BUY")) - 막대의 개방가를 받아서 아래의 몹시 중요한 줄 세개에 주목하시기 바랍니다.
  1. 포지션 청산 및 잔액 변동 시뮬레이션: 현재 잔액은 거래될 예금의 일부에 경상 잔액 가치를 곱하고, 공개 가격과 마감 가격 간의 차이를 곱하고, Pip 가격(개략적으로) 을 곱합니다.
  2. 매매 시뮬레이션 이력에 걸쳐 현재 잔액이 최대치에 도달했는지 확인하고, 그렇지 않은 경우 최대 잔액 차감액을 화폐로 계산합니다.
  3. 이전에 계산한 차감액을 상대잔고차감액으로 환산합니다.
  • 가상 매도 포지션을 개방합니다 (개방가와 방향을 기억하십시오).
  • 골든 크로스 용으로도 비슷한 체크와 계산을 하면 됩니다.

가상 매매 시뮬레이션에서 열람 가능한 모든 매매 이력을 되짚은 후 최종 FF 값을 산출합니다.만약 상대잔고차감액의 계산 결과가 테스트용 세팅보다 낮으면 FF=잔고이고, 아니라면 FF=0입니다. 유전 알고리즘은 FF의 최대화를 노립니다.

결국 유전 알고리즘은 다양한 상품, 예치금 일부 및 이동 평균 기간을 가지고 최소 (최소값은 유저가 결정) 상대 차감액에서 잔고를 극대화하는 값을 찾아낼 것입니다.


마치며

간단한 결론은 다음과 같습니다. 스스로 학습하는 Expert Advisor는 쉽게 만들 수 있습니다. 어려운 부분은 입력할 내용을 찾는 것입니다(중요한 것은 아이디어이며 구현은 기술적인 문제일 뿐입니다).

"이게 과연 될까" 하고 고뇌하는 비관론자들에게 저는 해줄 말이 있습니다 - 됩니다. 긍정론자들을 위한 제 조언은 - 이게 치트키는 아니라는 것입니다..

제시된 방법과 Quantum의 방법 사이의 근본적인 차이가 무엇이겠습니까? Expert Advsior를 MA의 기술을 사용하여 비교하는 것이 가장 좋은 예입니다.

  1. 적응형 매매 시스템의 MA 기간에 대한 결정은 컴파일에 들어가기 전에 이루어져야 하며 확실하게 코드화되어야하고, 선택은 이 제한된 수의 변형 선택지들에서만 내려질 수 있습니다. 우리는 유전최적화된 Expert Advisor에서 컴파일 전에 어떤 판단도 내리지 않습니다. 이 결정은 GA에 의해 이루어질 것이고, 변형의 수는 제한됩니다.상식적으로 편집이 됩니다.
  2. 적응형 매매 시스템에서의 가상 거래는 막대에서 막대로 이루어집니다.유전최적화 Expert Advisor에서는 흔치 않은 경우고, 재최적화를 할 때에만 그렇게 합니다. 증가하는 전략, 패러미터, 기구에 의해 변동하는 컴퓨터 성능이 적응형 매매 시스템의 제약 요소일지도 모르겠습니다.


첨부

다음은 2010.01.01의 일일 차트를 기반으로 아무런 최적화 없이 테스터에서 신경망을 실행할 경우 얻을 수 있는 정보입니다.

전략 테스터 보고서
MetaQuotes-Demo (Build 523)
Expert Advisor: ANNExample
심볼: EURUSD
기간: 일일 (2010.01.01 - 2011.09.30)
초기 패러미터: trainDD=0.9
maxDD=0.1
브로커: Alpari NZ Limited
화폐: USD
초기 투자량: 10 000.00
레버리지: 1:100
결과
이력 퀄리티: 100%
막대: 454 틱: 2554879
당기순이익합: -9 094.49 총이익: 29 401.09 총손실: -38 495.58
이익 팩터: 0.76 예상 성과: -20.53 마진레벨(Margin level): 732.30%
회복 팩터(Recovery factor): -0.76 샤프 율(Sharpe ratio): -0.06 OnTester 결과: 0
잔고차감액:
절대값 잔고차감액: 9 102.56 최대잔고차감액: 11 464.70 (92.74%) 상대잔고차감액: 92.74% (11 464.70)
자본차감액:
절대값 자본차감액: 9 176.99 최대자본차감액: 11 904.00 (93.53%) 상대자본차감액:: 93.53% (11 904.00)
총 매매수: 443 숏 매매 (이득을 본 경우, %): 7 (14.29%) 롱 매매 (이득을 본 경우, %): 436 (53.44%)
총 딜량: 886 이득 매매 (전체 중 %): 234 (52.82%) 손실 매매 (전체 중 %): 209 (47.18%)
최대 이득 매매: 1 095.57 최대 손실 매매: -1 438.85
평균 이득 매매: 125.65 평균 손실 매매: -184.19
최대 연쇄 수익 (잔고 이득): 8 (397.45) 최대 연쇄 손실 (잔고 손해): 8 (-1 431.44)
최대 연쇄 수익(수익의 총 경우 수): 1 095.57 (1) 최대 연쇄 손실 (손실의 총 경우 수): -3 433.21 (6)
평균 연쇄 수익: 2 평균 연쇄 손실: 2

그리고 여기에 세가지 재최적화 선택지가 있습니다.

첫번째...

시각 심볼 타입 방향 볼륨 매매가 주문 스왑 이득 잔고
2010.01.01 00:00 1   balance         0.00 10 000.00 10 000.00
2010.01.04 00:00 2 AUDUSD 매수 0.90 0.89977 2 0.00 0.00 10 000.00
2010.01.05 00:00 3 AUDUSD 매도 아웃 0.90 0.91188 3 5.67 1 089.90 11 095.57
2010.01.05 00:00 4 AUDUSD 매수 0.99 0.91220 4 0.00 0.00 11 095.57
2010.01.06 00:00 5 AUDUSD 매도 아웃 0.99 0.91157 5 6.24 -62.37 11 039.44
2010.01.06 00:00 6 AUDUSD 매수 0.99 0.91190 6 0.00 0.00 11 039.44
2010.01.07 00:00 7 AUDUSD 매도 아웃 0.99 0.91924 7 18.71 726.66 11 784.81


두번째...

시각 심볼 타입 방향 볼륨 매매가 주문 수수료 스왑 이득 잔고
2010.05.19 00:00 189 AUDUSD 매도 아웃 0.36 0.86110 189 0.00 2.27 -595.44 4 221.30
2010.05.19 00:00 190 EURAUD 매도 0.30 1.41280 190 0.00 0.00 0.00 4 221.30
2010.05.20 00:00 191 EURAUD 매수 아웃 0.30 1.46207 191 0.00 7.43 -1 273.26 2 955.47
2010.05.20 00:00 192 AUDUSD 매수 0.21 0.84983 192 0.00 0.00 0.00 2 955.47


세번째

시각 심볼 타입 방향 볼륨 매매가 주문 스왑 이득 잔고
2010.06.16 00:00 230 GBPCHF 매수 0.06 1.67872 230 0.00 0.00 2 128.80
2010.06.17 00:00 231 GBPCHF 매도 아웃 0.06 1.66547 231 0.13 -70.25 2 058.68
2010.06.17 00:00 232 GBPCHF 매수 0.06 1.66635 232 0.00 0.00 2 058.68
2010.06.18 00:00 233 GBPCHF 매도 아웃 0.06 1.64705 233 0.04 -104.14 1 954.58
2010.06.18 00:00 234 AUDUSD 매수 0.09 0.86741 234 0.00 0.00 1 954.58
2010.06.21 00:00 235 AUDUSD 매도 아웃 0.09 0.87184 235 0.57 39.87 1 995.02
2010.06.21 00:00 236 AUDUSD 매수 0.09 0.88105 236 0.00 0.00 1 995.02
2010.06.22 00:00 237 AUDUSD 매도 아웃 0.09 0.87606 237 0.57 -44.91 1 950.68
2010.06.22 00:00 238 AUDUSD 매수 0.09 0.87637 238 0.00 0.00 1 950.68
2010.06.23 00:00 239 AUDUSD 매도 아웃 0.09 0.87140 239 0.57 -44.73 1 906.52
2010.06.23 00:00 240 AUDUSD 매수 0.08 0.87197 240 0.00 0.00 1 906.52
2010.06.24 00:00 241 AUDUSD 매도 아웃 0.08 0.87385 241 1.51 15.04 1 923.07
2010.06.24 00:00 242 AUDUSD 매수 0.08 0.87413 242 0.00 0.00 1 923.07
2010.06.25 00:00 243 AUDUSD 매도 아웃 0.08 0.86632 243 0.50 -62.48 1 861.09
2010.06.25 00:00 244 AUDUSD 매수 0.08 0.86663 244 0.00 0.00 1 861.09
2010.06.28 00:00 245 AUDUSD 매도 아웃 0.08 0.87375 245 0.50 56.96 1 918.55
2010.06.28 00:00 246 AUDUSD 매수 0.08 0.87415 246 0.00 0.00 1 918.55
2010.06.29 00:00 247 AUDUSD 매도 아웃 0.08 0.87140 247 0.50 -22.00 1 897.05
2010.06.29 00:00 248 AUDUSD 매수 0.08 0.87173 248 0.00 0.00 1 897.05
2010.07.01 00:00 249 AUDUSD 매도 아웃 0.08 0.84053 249 2.01 -249.60 1 649.46
2010.07.01 00:00 250 EURGBP 매도 0.07 0.81841 250 0.00 0.00 1 649.46
2010.07.02 00:00 251 EURGBP 매수 아웃 0.07 0.82535 251 -0.04 -73.69 1 575.73
2010.07.02 00:00 252 EURGBP 매도 0.07 0.82498 252 0.00 0.00 1 575.73
2010.07.05 00:00 253 EURGBP 매수 아웃 0.07 0.82676 253 -0.04 -18.93 1 556.76
2010.07.05 00:00 254 EURGBP 매도 0.06 0.82604 254 0.00 0.00 1 556.76
2010.07.06 00:00 255 EURGBP 매수 아웃 0.06 0.82862 255 -0.04 -23.43 1 533.29


P.S. 숙제 삼아, 특정 시스템의 패러미터만 선택하지 말고 특정 순간의 시장에 가장 적합한 시스템을 선택해보십시오(힌트 - 시스템 은행에서).


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

파일 첨부됨 |
anntrainlib.mqh (9.76 KB)
matrainlib.mqh (8.94 KB)
ugalib.mqh (33.26 KB)
annexample.mq5 (4.32 KB)
maexample.mq5 (4.22 KB)
musthavelib.mqh (8.14 KB)
올인 외환거래 전략 올인 외환거래 전략
본 문서의 목적은 가장 단순한 매매 전략인 "올인" 게임 원칙을 구현하는 것입니다. 우리의 목적은 딱히 수익을 올리는 Expert Advisor를 만드는게 아닙니다. 우리의 목표는 가장 나은 확률을 검토하여 여러번에 거쳐 초기 액수를 늘리는 것입니다. 인디케이터나 기술 분석, 이런 것에 대해 하나도 모르면서 외환시장에서 대박을 터뜨리는 것이 과연 가능할까요?
MetaTrader 5 플랫폼에 새로운 UI 언어 추가하기 MetaTrader 5 플랫폼에 새로운 UI 언어 추가하기
MetaTrader 5 플랫폼의 사용자 인터페이스는 여러 언어로 번역되었습니다. 만약 당신이 사용하는 언어로는 번역되지 않았더라도 실망하기엔 이릅니다. MetaQuotes Software Corp.이 제공하는 MetaTrader 5 멀티랭귀지 팩 유틸리티를 이용하여 쉽게 번역할 수 있기 때문이죠. 심지어 공짜입니다. 본 문서에서는 MetaTrader 5 플랫폼에 새로운 사용자 인터페이스 언어를 추가하는 예시에 대해서 보여드릴 것입니다.
EA 트리를 이용하여 MQL5 Expert Advisor 뚝딱 만들기: 1부 EA 트리를 이용하여 MQL5 Expert Advisor 뚝딱 만들기: 1부
EA Tree는 최초의 드래그 앤 드랍 MetaTrader MQL5 Expert Advisor 생성기입니다. 매우 사용하기 편리한 GUI를 이용하여 복잡한 MQL5도 만들 수 있습니다. EA 트리에서는 박스들을 서로 연결하는 것으로 Expert Advisor를 만들 수 있습니다. 각 박스에는 MQL5 함수, 기술 인디케이터, 커스텀 인디케이터, 혹은 값이 들어있을 수 있습니다. "박스 트리"를 이용하여 EA 트리는 Expert Advisor MQL5 코드를 생성합니다.
UML 툴을 사용하여 Expert Advisor 개발하기 UML 툴을 사용하여 Expert Advisor 개발하기
이 문서는 객체지향 소프트웨어 시스템의 시각 모델링에 사용되는 UML 그래픽 언어를 사용하여 Expert Advisor를 개발하는 방법에 대해 다뤄볼 것입니다. 이 접근의 장점은 모델링 프로세스를 시각화할 수 있다는 점입니다.. 이 문서에서는 소프트웨어 아이디어 모델러(Software Ideas Modeler)를 통해 Expert Advisor의 구조와 속성을 모델링하는 예시를 보여드릴 것입니다.