OnTester

테스트 후 필요한 작업을 수행하기 위해 Tester 이벤트가 발생하면 Expert Advisor에서 이 기능을 호출합니다.

double  OnTester(void);

반환 값

테스트 결과 평가를 위한 커스텀 기준 최적화 값.  

참고

OnTester() 함수는 EA를 테스트할 때만 사용할 수 있으며, 주로 입력 파라미터를 최적화할 때 'Custom max' 기준으로 사용되는 값을 계산하기 위한 것입니다.

유전 최적화 중에는 한 세대 내의 결과 정렬이 내림차순으로 수행됩니다. 즉, 최적화 기준 관점에서 값이 가장 높은 결과가 가장 우수하다고 판단됩니다. 이러한 정렬에 대한 최악의 값은 마지막에 배치되고 나중에 삭제됩니다. 그러므로, 그들은 다음 세대를 형성하는데 참여하지 않습니다.

따라서 OnTester() 함수를 통해 테스트 결과 보고서를 직접 생성하고 저장할 수 있을 뿐만 아니라 최적화 프로세스를 제어하여 거래 전략의 최적의 파라미터를 찾을 수 있습니다.

다음은 커스텀 기준 최적화를 계산하는 예제입니다. 아이디어는 밸런스 그래프의 선형 회귀를 계산하는 것입니다. 밸런스 그래프를 이용한 전략 최적화 및 "밸런스 + 최대 Sharpe Ratio" 기준과 결과 비교 기고글에 설명되어 있습니다.

//+------------------------------------------------------------------+
//|                                              OnTester_Sample.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property description "OnTester() 핸들러 포함 샘플 EA"
#property description "커스텀 최적화 기준으로, "
#property description "밸런스 그래프 선형 회귀의 비율"
#property description "평균 제곱근 편차로 나눈 값이 반환됩니다."
//--- 거래 작업을 위한 등급을 포함
#include <Trade\Trade.mqh>
//--- EA 입력 파라미터
input double Lots               = 0.1;     // 볼륨
input int    Slippage           = 10;      // 허용가능 저하
input int    MovingPeriod       = 80;      // 이동 평균 기간
input int    MovingShift        = 6;       // 이동 평균 시프트
//--- 글로벌 변수
int    IndicatorHandle=0;  // 지표 처리
bool   IsHedging=false;    // 계정의 플래그
CTrade trade;              // 거래 작업 수행용
//--- 
#define EA_MAGIC 18052018
//+------------------------------------------------------------------+
//| 포지션 오프닝 조건 점검                            |
//+------------------------------------------------------------------+
void CheckForOpen(void)
  {
   MqlRates rt[2];
//--- 새 막대의 시작점에서만 거래
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
//--- 틱 볼륨
   if(rt[1].tick_volume>1)
      return;
//--- 이동 평균 값 수신
   double   ma[1];
   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- 신호 유무 점검
   ENUM_ORDER_TYPE signal=WRONG_VALUE;
//--- 캔들이 더 고점에서 열렸지만, 이동평균보다 다 저점에서 닫혔음
   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=ORDER_TYPE_BUY;    // 매수 시그널
   else // 캔들이 더 저점에서 열렸지만, 이동평균보다 더 고점에서 닫혔음
     {
      if(rt[0].open<ma[0] && rt[0].close>ma[0])
         signal=ORDER_TYPE_SELL;// 매도 시그널
     }
//--- 추가 점검
   if(signal!=WRONG_VALUE)
     {
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
        {
         double price=SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK);
         trade.PositionOpen(_Symbol,signal,Lots,price,0,0);
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| 포지션 클로징 조건 점검                            |
//+------------------------------------------------------------------+
void CheckForClose(void)
  {
   MqlRates rt[2];
//--- 새 막대의 시작점에서만 거래
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
   if(rt[1].tick_volume>1)
      return;
//--- 이동 평균 값 수신
   double   ma[1];
   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- PositionSelect()를 사용하여 이전에 이미 포지션이 선택되었음
   bool signal=false;
   long type=PositionGetInteger(POSITION_TYPE);
//--- 캔들이 더 고점에서 열렸지만, 이동평균보다 더 저점에서 닫혔음 - 숏 포지션 마감
   if(type==(long)POSITION_TYPE_SELL && rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=true;
//--- 캔들이 더 저점에서 열렸지만, 이동평균보다 더 고점에서 닫혔음 - 롱 포지션 마감
   if(type==(long)POSITION_TYPE_BUY && rt[0].open<ma[0] && rt[0].close>ma[0])
      signal=true;
//--- 추가 점검
   if(signal)
     {
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
         trade.PositionClose(_Symbol,Slippage);
     }
//---
  }
//+-------------------------------------------------------------------+
//| 계정 유형을 고려하여 포지션 선택: Netting 또는 Hedging |
//+-------------------------------------------------------------------+
bool SelectPosition()
  {
   bool res=false;
//--- Hedging 계정용 포지션 선택
   if(IsHedging)
     {
      uint total=PositionsTotal();
      for(uint i=0; i<total; i++)
        {
         string position_symbol=PositionGetSymbol(i);
         if(_Symbol==position_symbol && EA_MAGIC==PositionGetInteger(POSITION_MAGIC))
           {
            res=true;
            break;
           }
        }
     }
//--- Netting 계정용 포지션 선택
   else
     {
      if(!PositionSelect(_Symbol))
         return(false);
      else
         return(PositionGetInteger(POSITION_MAGIC)==EA_MAGIC); //---check Magic number
     }
//--- 실행 결과
   return(res);
  }
//+------------------------------------------------------------------+
//| Expert 초기화 함수                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- 거래 유형 설정: Netting 또는 Hedging
   IsHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
//--- 올바른 포지션 제어를 위해 개체 초기화
   trade.SetExpertMagicNumber(EA_MAGIC);
   trade.SetMarginMode();
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetDeviationInPoints(Slippage);
//--- 이동 평균 지표 생성
   IndicatorHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(IndicatorHandle==INVALID_HANDLE)
     {
      printf("iMA 지표 생성 에러");
      return(INIT_FAILED);
     }
//--- ok
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert 틱 함수                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//--- 이미 포지션이 오픈된 경우 마감 조건을 점검하십시오
   if(SelectPosition())
      CheckForClose();
// 포지션 오프닝 조건 점검
   CheckForOpen();
//---
  }
//+------------------------------------------------------------------+
//| 테스터 함수                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//--- 사용자 정의 기준 최적화 값(높을수록 좋음)
   double ret=0.0;
//--- 거래 결과를 배열로 얻기 
   double array[];
   double trades_volume;
   GetTradeResultsToArray(array,trades_volume);
   int trades=ArraySize(array);
//--- 거래가 10번 미만인 경우, 양성 결과가 나오지 않습니다
   if(trades<10)
      return (0);
//--- 거래당 평균 실적
   double average_pl=0;
   for(int i=0;i<ArraySize(array);i++)
      average_pl+=array[i];
   average_pl/=trades;
//--- 단일 테스트 모드에 대한 메시지를 표시합니다
   if(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_OPTIMIZATION))
      PrintFormat("%s: Trades=%d, Average profit=%.2f",__FUNCTION__,trades,average_pl);
//--- 이익 그래프에 대한 선형 회귀 비율을 계산
   double a,b,std_error;
   double chart[];
   if(!CalculateLinearRegression(array,chart,a,b))
      return (0);
//--- 회귀선에서 차트 편차의 오차를 계산
   if(!CalculateStdError(chart,a,b,std_error))
      return (0);
//--- 표준 편차에 대한 추세 이익 비율을 계산
   ret=(std_error == 0.0) ? a*trades : a*trades/std_error;
//--- 사용자 정의 기준 최적화 값 반환
   return(ret);
  }
//+------------------------------------------------------------------+
//| 거래에서의 수익/손실 배열 얻기                       |
//+------------------------------------------------------------------+
bool GetTradeResultsToArray(double &pl_results[],double &volume)
  {
//--- 전체 거래 내역 요청
   if(!HistorySelect(0,TimeCurrent()))
      return (false);
   uint total_deals=HistoryDealsTotal();
   volume=0;
//--- 내역에서 거래 수에 따라 마진으로 배열의 초기 크기를 설정
   ArrayResize(pl_results,total_deals);
//--- 거래 결과를 확정하는 거래 카운터 - 수익 또는 손실
   int counter=0;
   ulong ticket_history_deal=0;
//--- 모든 거래 살펴보기
   for(uint i=0;i<total_deals;i++)
     {
      //--- 딜 선택 
      if((ticket_history_deal=HistoryDealGetTicket(i))>0)
        {
         ENUM_DEAL_ENTRY deal_entry  =(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_history_deal,DEAL_ENTRY);
         long            deal_type   =HistoryDealGetInteger(ticket_history_deal,DEAL_TYPE);
         double          deal_profit =HistoryDealGetDouble(ticket_history_deal,DEAL_PROFIT);
         double          deal_volume =HistoryDealGetDouble(ticket_history_deal,DEAL_VOLUME);
         //--- 저희는 오직 거래 작업에만 관심이 있습니다        
         if((deal_type!=DEAL_TYPE_BUY) && (deal_type!=DEAL_TYPE_SELL))
            continue;
         //--- 수익/손실을 확정하는 거래만
         if(deal_entry!=DEAL_ENTRY_IN)
           {
            //--- 거래 결과를 배열에 기록하고 거래량을 늘림
            pl_results[counter]=deal_profit;
            volume+=deal_volume;
            counter++;
           }
        }
     }
//--- 배열의 최종 규모 설정
   ArrayResize(pl_results,counter);
   return (true);
  }
//+------------------------------------------------------------------+
//| 선형 회귀 계산 y=a*x+b                          |
//+------------------------------------------------------------------+
bool CalculateLinearRegression(double  &change[],double &chartline[],
                               double  &a_coef,double  &b_coef)
  {
//--- 자료가 충분한지 확인
   if(ArraySize(change)<3)
      return (false);
//--- 누적 차트 배열 생성
   int N=ArraySize(change);
   ArrayResize(chartline,N);
   chartline[0]=change[0];
   for(int i=1;i<N;i++)
      chartline[i]=chartline[i-1]+change[i];
//--- 이제 회귀 비율을 계산
   double x=0,y=0,x2=0,xy=0;
   for(int i=0;i<N;i++)
     {
      x=x+i;
      y=y+chartline[i];
      xy=xy+i*chartline[i];
      x2=x2+i*i;
     }
   a_coef=(N*xy-x*y)/(N*x2-x*x);
   b_coef=(y-a_coef*x)/N;
//---
   return (true);
  }
//+------------------------------------------------------------------+
//|  지정된 a 및 b에 대한 평균 제곱근 편차 계산 오류     |
//+------------------------------------------------------------------+
bool  CalculateStdError(double  &data[],double  a_coef,double  b_coef,double &std_err)
  {
//--- 오차 제곱합
   double error=0;
   int N=ArraySize(data);
   if(N<=2)
      return (false);
   for(int i=0;i<N;i++)
      error+=MathPow(a_coef*i+b_coef-data[i],2);
   std_err=MathSqrt(error/(N-2));
//--- 
   return (true);
  }

더 보기

Testing trading strategies, TesterHideIndicators, Working with optimization results, TesterStatistics, OnTesterInit, OnTesterDeinit, OnTesterPass, MQL_TESTER, MQL_OPTIMIZATION, FileOpen, FileWrite, FileLoad, FileSave