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

Roman Zamozhnyy | 11 10월, 2021


컨셉

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() 함수 호출이 있다는 것을 눈치 채셨을 것입니다.

//+------------------------------------------------------------------+
//|  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를 구현할 것입니다.

알고리즘적으로 자가 최적화 하는 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의 변형입니다!

실제 매매 코드는 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" 모드에서 견적을 받고 거래 결정을 내리는 과정을 시뮬레이션합니다.

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