시스템트레이딩 게시글

MetaTrader 5의 이동평균 EA: 초보자도 쉽게 이해하는 트레이딩 시스템

첨부파일
1921.zip (1.81 KB, 다운로드 0회)

이동평균 EA는 MetaTrader 5 클라이언트 터미널의 기본 패키지에 포함되어 있으며, 이동평균 지표를 사용하여 거래하는 EA의 예시입니다.

이 EA 파일은 "terminal_data_folder\MQL5\Experts\Examples\Moving Average\" 폴더에 위치하고 있습니다. 이 EA는 기술 지표, 거래 이력 함수 및 표준 라이브러리의 거래 클래스 사용 예시입니다. 또한, EA에는 거래 결과를 기반으로 한 자금 관리 시스템이 포함되어 있습니다.

이제 전문가 어드바이저의 구조와 작동 방식을 살펴보겠습니다.

1. EA 속성

//+------------------------------------------------------------------+
//|                                              이동평균.mq5 |
//|                                              Copyright 2009-2013, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2009-2013, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

첫 5줄은 주석으로 되어 있으며, 이후 3줄은 MQL5 프로그램의 속성(copyright, link, version)을 설정하는 #property 지시어를 사용합니다.

전문가 어드바이저를 실행하면 "공통" 탭에 표시됩니다:


그림 1. 이동평균 EA의 공통 매개변수

1.2. 포함 파일

다음으로 #include 지시어는 컴파일러에게 "Trade.mqh" 파일을 포함하라고 지시합니다.

이 파일은 표준 라이브러리의 일부분으로, 거래 함수에 쉽게 접근할 수 있는 CTrade 클래스를 포함하고 있습니다.

#include <Trade\Trade.mqh>

포함 파일의 이름은 괄호 "<>;" 안에 표시되어 있으므로 경로는 "terminal_data_folder\Include\" 디렉토리를 기준으로 설정됩니다.

1.3 입력값

그 다음은 타입, 이름, 기본값 및 주석이 나옵니다. 이들의 역할은 그림 2에 표시되어 있습니다.

input double MaximumRisk        = 0.02;    // 최대 위험도 (%)
input double DecreaseFactor     = 3;       // 감소 계수
input int    MovingPeriod       = 12;      // 이동평균 기간
input int    MovingShift        = 6;       // 이동평균 시프트

MaximumRisk 및 DecreaseFactor 매개변수는 자금 관리에 사용되며, MovingPeriod와 MovingShift는 거래 조건 점검을 위해 사용될 이동평균 지표의 기간과 시프트를 설정합니다.

입력 매개변수 줄의 주석과 기본값은 "옵션" 탭에 표시되며, 입력 매개변수의 이름 대신 나타납니다:


그림 2. 이동평균 EA의 입력 매개변수

1.4. 전역 변수


그 다음으로 전역 변수 ExtHandle이 선언됩니다. 이 변수는 이동평균 지표의 핸들을 저장하는 데 사용됩니다.

//---
int   ExtHandle=0;

그 뒤에는 6개의 함수가 있습니다. 각 함수의 목적은 함수 본문 앞의 주석에 설명되어 있습니다:

  1. TradeSizeOptimized() - 최적의 로트 크기 계산;
  2. CheckForOpen() - 포지션 오픈 조건 점검;
  3. CheckForClose() - 포지션 클로즈 조건 점검;
  4. OnInit() - 전문가 초기화 함수;
  5. OnTick() - 전문가 틱 함수;
  6. OnDeinit() - 전문가 비초기화 함수;

마지막 세 개의 함수는 이벤트 처리 함수이며, 첫 세 개의 서비스 함수는 해당 코드에서 호출됩니다.

2. 이벤트 처리 함수

2.1. OnInit() 초기화 함수

OnInit() 함수는 전문가 어드바이저가 처음 시작될 때 한 번 호출됩니다. 일반적으로 OnInit() 이벤트 핸들러에서 EA가 작업을 준비합니다: 입력 매개변수를 확인하고, 지표 및 매개변수를 초기화합니다. 심각한 오류가 발생하면 함수는 반환 코드 INIT_FAILED와 함께 종료됩니다.

//+------------------------------------------------------------------+
//| 전문가 초기화 함수                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//---
   ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(ExtHandle==INVALID_HANDLE)
     {
      printf("MA 지표 생성 오류");
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }

EA 거래는 이동평균 지표를 기반으로 하므로, iMA()를 호출하여 이동평균 지표가 생성되고, 그 핸들이 전역 변수 ExtHandle에 저장됩니다.

오류 발생 시 OnInit() 함수는 INIT_FAILED 반환 코드와 함께 종료됩니다 - 이는 초기화가 실패한 경우 EA/지표 작업을 올바르게 마치는 방법입니다.


2.2. OnTick() 함수

OnTick() 함수는 EA가 실행되는 차트의 심볼에 대해 새로운 인용이 수신될 때마다 호출됩니다.

//+------------------------------------------------------------------+
//| 전문가 틱 함수                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//---
   if(PositionSelect(_Symbol))
      CheckForClose();
   else
      CheckForOpen();
//---
  }

PositionSelect() 함수는 현재 심볼에 대한 열린 포지션이 있는지 확인하는 데 사용됩니다.

열린 포지션이 있는 경우, CheckForClose() 함수가 호출되어 시장의 현재 상태를 분석하고 열린 포지션을 종료합니다. 그렇지 않으면, CheckForOpen() 함수가 호출되어 시장 진입 조건을 확인하고 해당 조건이 발생하면 새로운 포지션을 엽니다.


2.3. OnDeInit() 비초기화 함수

OnDeInit() 함수는 EA가 차트에서 제거될 때 호출됩니다. 프로그램이 작동 중 그래픽 객체를 배치한 경우, 이 객체들은 차트에서 제거될 수 있습니다.

//+------------------------------------------------------------------+
//| 전문가 비초기화 함수                                   |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
  }
//+------------------------------------------------------------------+

이 경우, 전문가 어드바이저 비초기화 동안 어떠한 작업도 수행되지 않습니다.


3. 서비스 함수

3.1. TradeSizeOptimized() 함수

이 함수는 지정된 위험 수준 및 거래 결과에 대해 포지션 오픈에 최적의 로트 크기를 계산하고 반환합니다.

//+------------------------------------------------------------------+
//| 최적 로트 크기 계산                                       |
//+------------------------------------------------------------------+
double TradeSizeOptimized(void)
  {
   double price=0.0;
   double margin=0.0;
//--- 로트 크기 계산
   if(!SymbolInfoDouble(_Symbol,SYMBOL_ASK,price))
      return(0.0);
   if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,margin))
      return(0.0);
   if(margin<=0.0)
      return(0.0);

   double lot=NormalizeDouble(AccountInfoDouble(ACCOUNT_FREEMARGIN)*MaximumRisk/margin,2);
//--- 연속 손실 거래 시퀀스 길이 계산
   if(DecreaseFactor>0)
     {
      //--- 전체 거래 이력 요청
      HistorySelect(0,TimeCurrent());
      //--
      int    orders=HistoryDealsTotal();  // 총 거래 수
      int    losses=0                    // 손실 거래 수

      for(int i=orders-1;i>=0;i--)
      {
        ulong ticket=HistoryDealGetTicket(i);
         if(ticket==0)
           {
            Print("HistoryDealGetTicket 실패, 거래 이력 없음");
            break;
           }
         //--- 거래 심볼 확인
         if(HistoryDealGetString(ticket,DEAL_SYMBOL)!=_Symbol)
            continue;
         //--- 수익 확인
         double profit=HistoryDealGetDouble(ticket,DEAL_PROFIT);
         if(profit>0.0)
            break;
         if(profit<0.0)
            losses++;
    }
      //---
      if(losses>1)
         lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
     }
//--- 거래량의 허용 값을 정규화 및 확인
   double stepvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   lot=stepvol*NormalizeDouble(lot/stepvol,0);

   double minvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   if(lot<minvol)
      lot=minvol;

   double maxvol=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
   if(lot>maxvol)
      lot=maxvol;
//--- 거래량 값 반환
   return(lot);
  }

SymbolInfoDouble() 함수는 현재 심볼의 가격 유무를 확인하는 데 사용되며, 다음으로 OrderCalcMargin() 함수는 주문을 배치하는 데 필요한 마진을 요청합니다(이 경우 매수 주문). 초기 로트 크기는 주문을 배치하는 데 필요한 마진 값, 계좌의 자유 마진(AccountInfoDouble(ACCOUNT_FREEMARGIN)) 및 입력 매개변수 MaximumRisk에 지정된 최대 허용 위험 값에 따라 결정됩니다.

입력 매개변수 DecreaseFactor 값이 양수인 경우, 이력에 있는 거래를 분석하고 최대 연속 손실 거래 수에 대한 정보를 반영하여 로트 크기를 조정합니다: 초기 로트 크기는 (1-losses/DecreaseFactor)로 곱해집니다.

그 후 거래량은 현재 심볼의 최소 허용 거래량 단계(stepvol)로 나누어 정규화됩니다. 또한 거래량의 최소(minvol) 및 최대(maxvol) 값을 요청하고, 로트 값이 허용 한계를 초과하면 조정됩니다. 최종적으로 이 함수는 계산된 거래량 값을 반환합니다.


3.2. CheckForOpen() 함수

CheckForOpen() 함수는 포지션 오픈 조건을 확인하고, 거래 조건이 발생할 경우 포지션을 엽니다(이 경우 가격이 이동평균을 교차할 때).

//+------------------------------------------------------------------+
//| 포지션 오픈 조건 확인                               |
//+------------------------------------------------------------------+
void CheckForOpen(void)
  {
   MqlRates rt[2];
//--- 가격 값을 복사
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates 실패, 이력 없음",_Symbol);
      return;
     }
//--- 새로운 바의 첫 틱에서만 거래
   if(rt[1].tick_volume>1)
      return;
//--- 이동평균 지표의 현재 값 가져오기 
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer 실패, 데이터 없음");
      return;
     }
//--- 신호 확인
   ENUM_ORDER_TYPE signal=WRONG_VALUE;

   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=ORDER_TYPE_SELL;    // 매도 조건
   else
      {
         if(rt[0].open<ma[0] && rt[0].close>ma[0])
           signal=ORDER_TYPE_BUY;  // 매수 조건
     }
//--- 추가 확인
   if(signal!=WRONG_VALUE)
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
         if(Bars(_Symbol,_Period)>100)
           {
            CTrade trade;
            trade.PositionOpen(_Symbol,signal,TradeSizeOptimized(),
                               SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK),
                               0,0);
           }
//---
  }

이동평균을 사용하는 거래 시에는 가격이 이동평균을 교차하는지 확인해야 합니다. CopyRates() 함수를 사용하여 현재 가격의 두 값을 복사하여 rt[] 구조체 배열에 저장합니다. rt[1]은 현재 바, rt[0]은 완료된 바에 해당합니다.

새로운 바가 시작되면 현재 바의 틱 볼륨이 1인지 확인하여 새로운 바가 시작된 것입니다. 이 방법은 일부 경우(인용이 몰려올 때) 새로운 바를 감지하는 데 실패할 수 있으므로, 현재 인용의 시간을 저장하고 비교하여 새로운 바의 형성을 확인해야 합니다(IsNewBar 참고).

이동평균 지표의 현재 값을 요청하기 위해 CopyBuffer() 함수를 사용하고, ma[] 배열에 단 하나의 값만 저장합니다. 그 후, 프로그램은 가격이 이동평균을 교차했는지 확인하고, 추가 확인(EA로 거래 가능한지 및 이력에 바가 존재하는지)을 수행합니다. 성공할 경우, 거래 객체의 PositionOpen() 메서드를 호출하여 해당 심볼에 대한 적절한 포지션을 엽니다.

포지션 오픈 가격은 SymbolInfoDouble() 함수를 호출하여 Bid 또는 Ask 가격을 반환합니다. 포지션 볼륨은 위에서 설명한 TradeSizeOptimized()를 호출하여 결정됩니다.


3.3. CheckForClose() 함수

CheckForClose() 함수는 포지션 클로즈 조건을 체크하고 조건이 발생할 경우 포지션을 종료합니다.

//+------------------------------------------------------------------+
//| 포지션 클로즈 조건 확인                              |
//+------------------------------------------------------------------+
void CheckForClose(void)
  {
   MqlRates rt[2];
//--- 가격 값 복사
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates 실패, 이력 없음",_Symbol);
      return;
     }
//--- 새로운 바의 첫 틱에서 거래
   if(rt[1].tick_volume>1)
      return;
//--- 이동평균 지표의 현재 값 가져오기
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer 실패, 데이터 없음");
      return;
     }
//--- 이전에 선택한 포지션의 타입 가져오기
   bool signal=false;
   long type=PositionGetInteger(POSITION_TYPE);

   if(type==(long)POSITION_TYPE_BUY   && rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=true;
   if(type==(long)POSITION_TYPE_SELL  && rt[0].open<ma[0] && rt[0].close>ma[0])
      signal=true;
//--- 추가 확인
   if(signal)
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
         if(Bars(_Symbol,_Period)>100)
           {
            CTrade trade;
            trade.PositionClose(_Symbol,3);
           }
//---
  }

CheckForClose() 함수의 알고리즘은 CheckForOpen() 함수의 알고리즘과 유사합니다. 현재 열린 포지션의 방향에 따라 클로즈 조건을 확인합니다(매수의 경우 아래로, 매도의 경우 위로 이동평균을 교차함). 열린 포지션은 거래 객체의 PositionClose() 메서드를 호출하여 닫힙니다.


4. 백테스팅

최적의 파라미터 값을 찾기 위해 MetaTrader 5 터미널의 전략 테스터를 사용할 수 있습니다.

예를 들어, 2012.01.01-2013.08.01 기간 동안 MovingPeriod 매개변수를 최적화했을 때, MovingPeriod=45에서 최상의 결과를 얻었습니다:

이동평균 전문가 어드바이저의 백테스팅 결과

이동평균 전문가 어드바이저의 백테스팅 결과

결론:

MetaTrader 5 터미널의 기본 패키지에 포함된 이동평균 전문가 어드바이저는 기술 지표, 거래 이력 함수 및 거래 클래스의 사용 예시입니다. 또한, EA에는 거래 결과를 기반으로 한 자금 관리 시스템이 포함되어 있습니다.


연관 포스트

댓글 (0)