English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
뉴비들을 위한 복합 인디케이터 버퍼 만들기

뉴비들을 위한 복합 인디케이터 버퍼 만들기

MetaTrader 5 | 5 7월 2021, 13:24
125 0
Nikolay Kositsin
Nikolay Kositsin

개요

'뉴비들을 위한 MQL5 커스텀 인디케이터 가이드' 와 '초보자를 위한 실용적인 MQL5 디지털 필터 구현'에서는 단일 인디케이터 버퍼를 갖는 인디케이터 구조에 대해 설명했는데요.

물론 그런 구조를 이용한 커스텀 인디케이터 작성법이 널리 쓰이기는 하지만 실제로는 한계가 있으므로 조금 더 복잡한 인디케이터 코드 작성법을 알아보겠습니다. 다행히도 컴퓨터나 메모리의 성능만 따라준다면 MQL5 자체의 가능성은 무궁무진하거든요.


아룬 인디케이터로 보는 코드 클론

해당 인디케이터의 공식에는 두 가지 요소가 포함되어 있는데요. 바로 불리쉬 인디케이터와 베어리쉬 인디케이터로, 각각 별도의 창에 그려집니다.

BULLS=(1-(bar-SHIFT(MAX(HIGH(), AroonPeriod)))/AroonPeriod)*100
BEARS=(1-(bar-SHIFT(MIN(LOW (), AroonPeriod)))/AroonPeriod)*100

좀 더 설명해 볼게요.

  • BULLS-불스 파워
  • BEARS-베어스 파워
  • SHIFT()-바의 인덱스 포지션을 결정하는 함수
  • MAX()-AroonPeriod 기간 동안의 최대값을 찾는 함수
  • MIN() -AroonPeriod 기간 동안의 최소값을 찾는 함수
  • HIGH() 및 LOW()-해당 가격 배열 표시 함수

인디케이터의 공식만 보아도 이전 글에서 다루어진 SMA_1.mp5와는 인디케이터 구조에는 별 차이가 없음을 알 수 있습니다..

사실 인디케이터 버퍼의 개수만 빼면 동일한 코드나 마찬가지입니다. MetaEditor에서 해당 코드를 불러와 Aroon.mq5라는 파일명으로 저장합니다. 이제 저작권과 버전명을 설명하는 처음 11줄 내 인디케이터명을 바꾸겠습니다.

//+------------------------------------------------------------------+
//|                                                        Aroon.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//---- copyright
#property copyright "2010, MetaQuotes Software Corp."
//---- link to the author's site
#property link      "http://www.mql5.com"
//---- version number
#property version   "1.00"

다음으로, 12번째 줄에서 인디케이터가 기본 창이 아닌 별도의 창에 그려지도록 설정합니다.

//---- plot indicator in the separate window
#property indicator_separate_window

이번 인디케이터는 값의 범위가 완전히 다르므로 별개의 창에서 플롯을 실행합니다.

그 다음 4줄(일반 인디케이터 속성)에서 사용되는 인디케이터 버퍼 개수를 두 개로 바꿉니다.

//---- two buffers are used
#property indicator_buffers 2
//---- two plots are used 
#property indicator_plots   2

그 다음 10줄은 특정 인디케이터 버퍼를 이용한 인디케이터 플로팅에 관한 것인데요. 라벨을 중복 처리해야 하므로 모든 인덱스를 1에서 2로 바꿉니다. 인디케이터 버퍼의 라벨 또한 모두 바꿔야 합니다.

//+----------------------------------------------+
//| bullish strength indicator parameters        |
//+----------------------------------------------+
//---- drawing style = line
#property indicator_type1   DRAW_LINE
//---- drawing color = Lime
#property indicator_color1  Lime
//---- line style = solid line
#property indicator_style1  STYLE_SOLID
//---- line width = 1
#property indicator_width1  1
//---- label of the BullsAroon indicator
#property indicator_label1  "BullsAroon"
//+----------------------------------------------+
//|  bearish strength indicator parameters       |
//+----------------------------------------------+
//---- drawing style = line
#property indicator_type2   DRAW_LINE
//---- drawing color = Red
#property indicator_color2  Red
//---- line style = solid line
#property indicator_style2  STYLE_SOLID
//---- line width = 1
#property indicator_width2  1
//---- label of the BearsAroon indicator
#property indicator_label2  "BearsAroon"

해당 인디케이터는 30, 50, 70의 세 가지 높이를 사용합니다.

이 세 가지 높이를 나타내려면 인디케이터 코드에 5줄을 추가해야 합니다.

//+----------------------------------------------+
//| Horizontal levels                            |
//+----------------------------------------------+
#property indicator_level1 70.0
#property indicator_level2 50.0
#property indicator_level3 30.0
#property indicator_levelcolor Gray
#property indicator_levelstyle STYLE_DASHDOTDOT

직전 인디케이터와 비교했을 때 제목의 몇 가지 수정 사항을 제외하고는 인디케이터 입력 변수가 동일합니다.

//+----------------------------------------------+
//| Indicator input parameters                   |
//+----------------------------------------------+
input int AroonPeriod = 9; // Period 
input int AroonShift = 0// Horizontal shift of the indicator in bars 

 하지만 이제 배열은 2개가 되었고, 이 배열들은 각각 새로운 이름을 가지고 인디케이터 버퍼로 사용될 것입니다.

//--- declare the dynamic arrays used further as indicator buffers
double BullsAroonBuffer[];
double BearsAroonBuffer[]; 

OnInit() 함수 코드도 동일한 방법으로 작업합니다.

우선 0번째 버퍼 코드를 수정합니다.

//--- set BullsAroonBuffer dynamic array as indicator buffer 
SetIndexBuffer(0, BullsAroonBuffer, INDICATOR_DATA);
//--- horizontal shift (AroonShift) of the indicator 1
PlotIndexSetInteger(0, PLOT_SHIFT, AroonShift);
//--- plot draw begin (AroonPeriod) of the indicator 1
PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, AroonPeriod);
//--- label shown in DataWindow
PlotIndexSetString(0, PLOT_LABEL, "BearsAroon"); 

다음으로 전체 코드를 클립보드에 복사하여 복사된 코드 바로 뒤에 붙여 넣기합니다.

붙여 넣기한 코드의 인디케이터 버퍼 개수를 0에서 1로 변경하고, 인디케이터 배열 이름과 인디케이터 라벨도 변경합니다.

//--- set BearsAroonBuffer dynamic array as indicator buffer 
SetIndexBuffer(1, BearsAroonBuffer, INDICATOR_DATA); 
//--- horizontal shift (AroonShift) of the indicator 2 
PlotIndexSetInteger(1, PLOT_SHIFT, AroonShift); 
//--- plot draw begin (AroonPeriod) of the indicator 2 
PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, AroonPeriod); 
//--- label shown in DataWindow 
PlotIndexSetString(1, PLOT_LABEL, "BullsAroon");  

인디케이터 별명도 약간 바뀌죠.

//--- initialization of the variable for a short indicator name
string shortname;
StringConcatenate(shortname, "Aroon(", AroonPeriod, ", ", AroonShift, ")"); 

이제 인디케이터 플로팅의 정확도를 알아봅시다. 인디케이터의 치역은 0부터 100까지이며 항상 표시됩니다.

차트에 그려진 인디케이터의 정수 값만 사용할 수 있겠죠. 따라서 플로팅 시 소수점 뒤의 숫자는 0으로 대체합니다.

//--- set accuracy of drawing of indicator values
IndicatorSetInteger(INDICATOR_DIGITS, 0);

SMA_1.mq5 인디케이터에서는 OnCalculate() 함수 호출의 첫 번째 폼을 이용했습니다.

아룬 인디케이터에는 high[]나 low[] 가격 배열이 없으므로 다른 방법을 사용해야 합니다. 동일한 함수의 두 번째 호출 폼을 이용하면 되는데요. 그러려면 함수의 헤더를 수정해야 합니다.

int OnCalculate( 
                const int rates_total,    // total bars on the current tick
                const int prev_calculated,// total bars on the previous tick
                const datetime& time[],
                const double& open[],    
                const double& high[],     // price array of the maximum prices for the indicator calculations
                const double& low[],      // price array of the minimum prices for the indicator calculations
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[]
              )

헤더 수정 후에는 첫 매개 변수들이 의미를 잃게 되므로 코드에서 제거합니다.

연산 주기의 변수 변경 한계 및 연산 충분성 계산 데이터 확인 코드는 변경되지 않았습니다.

//--- checking the number of bars
if (rates_total < AroonPeriod - 1) return(0);
   
//--- declare the local variables 
int first, bar;
double BULLS, BEARS; 

//--- calculation of the first (staring index) for the main loop
if (prev_calculated == 0)          // checking for the first call of the OnCalculate function
    first = AroonPeriod - 1;       // starting index for calculating all of the bars 
else first = prev_calculated - 1// starting index for calculating new bars

그러나 인디케이터 값 연산 알고리즘에는 문제가 발생합니다. 문제는 MQL5에 최대값과 최솟값의 인덱스를 결정하는 함수가 탑재되지 않았다는 겁니다. 현재 바 기간 내 인덱스 감소 방향으로는 말이죠.

이런 문제를 피하는 방법 중 하나는 함수를 직접 작성하는 겁니다. 다행히 이런 함수들은 이미 MetaTrader5\MQL5\Indicators\Examples에 위치한 ZigZag.mq5 커스텀 인디케이터에 포함되어 있습니다.

가장 쉬운 해결 방법은 ZigZag.mq5 인디케이터에서 해당 함수 코드를 복사해서 우리가 만든 코드에 붙여 넣기하는 거죠. 예를 들어 OnInit() 함수를 전역 수준에서 출력한 다음에 말이죠.

//+------------------------------------------------------------------+
//|  searching index of the highest bar                              |
//+------------------------------------------------------------------+
int iHighest(const double &array[], // array for searching for the index of the maximum element
             int count,            // number of the elements in the array (in the decreasing order), 
             int startPos          // starting index
             )                     
  {
//---+
   int index = startPos;
   
   //---- checking the starting index
   if (startPos < 0)
     {
      Print("Incorrect value in the function iHighest, startPos = ", startPos);
      return (0);
     } 
   //---- checking the startPos values
   if (startPos - count < 0) count = startPos;
    
   double max = array[startPos];
   
   //---- index search
   for(int i = startPos; i > startPos - count; i--)
     {
      if(array[i] > max)
        {
         index = i;
         max = array[i];
        }
     }
//---+ return of the index of the largest bar
   return(index);
  }
//+------------------------------------------------------------------+
//|  searching index of the lowest bar                               |
//+------------------------------------------------------------------+
int iLowest(
            const double &array[], // array for searching for the index of the maximum element
            int count,            // number of the elements in the array (in the decreasing order),
            int startPos          // starting index
            ) 
{
//---+
   int index = startPos;
   
   //--- checking the stating index
   if (startPos < 0)
     {
      Print("Incorrect value in the iLowest function, startPos = ",startPos);
      return(0);
     }
     
   //--- checking the startPos value
   if (startPos - count < 0) count = startPos;
    
   double min = array[startPos];
   
   //--- index search
   for(int i = startPos; i > startPos - count; i--)
     {
      if (array[i] < min)
        {
         index = i;
         min = array[i];
        }
     }
//---+ return of the index of the smallest bar
   return(index);
  }

이제 OnCalculate() 함수 코드는 다음과 같은 형태로 나타납니다.

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate( const int rates_total,    // total number of bars on the current tick
               const int prev_calculated,// number of calculated bars on the previous tick
               const datetime& time[],
               const double& open[],    
               const double& high[],     // price array for the maximum price for the indicator calculation
               const double& low[],      // price array for the minimum price for the indicator calculation
               const double& close[],
               const long& tick_volume[],
               const long& volume[],
               const int& spread[]
             )
  {
//---+   
   //--- checking the number of bars
   if (rates_total < AroonPeriod - 1)
    return(0);
   
   //--- declare the local variables 
   int first, bar;
   double BULLS, BEARS;
   
   //--- calculation of the starting bar number
   if (prev_calculated == 0// checking for the first start of the indicator calculation
     first = AroonPeriod - 1; // starting number for the calculation of all of the bars

   else first = prev_calculated - 1; // starting number for the calculation of new bars

   //--- main loop
   for(bar = first; bar < rates_total; bar++)
    {
     //--- calculation of values
     BULLS = 100 - (bar - iHighest(high, AroonPeriod, bar) + 0.5) * 100 / AroonPeriod;
     BEARS = 100 - (bar - iLowest (low,  AroonPeriod, bar) + 0.5) * 100 / AroonPeriod;

     //--- filling the indicator buffers with the calculated values 
     BullsAroonBuffer[bar] = BULLS;
     BearsAroonBuffer[bar] = BEARS;
    }
//---+     
   return(rates_total);
  }
//+------------------------------------------------------------------+

축의 대칭을 맞추기 위해 코드를 약간 수정하여 0.5값으로 수직 이동을 실행했습니다.

차트에 나타나는 인디케이터는 다음과 같습니다.

                                                                              

현재 바에서 AroonPeriod 만큼 떨어진 곳에서 최대값 또는 최솟값을 가진 요소의 위치를 찾기 위해서는 MQL5의 ArrayMaximum() 함수와 ArrayMinimum() 함수를 이용하면 됩니다. 이 두 함수는 극한값도 찾아주죠. 하지만 인덱스 값이 작은 것부터 탐색을 시작합니다.

인덱스 값이 큰 것부터 탐색을 해야 하는데 말입니다. 이 경우 가장 간단한 해결책은 ArraySetAsSeries() 함수를 이용해 인디케이터와 가격 버퍼의 인덱싱 방향을 바꾸는 것입니다.

연산 루프 내 바 정렬 순서와 첫 번째 변수 연산 알고리즘 또한 수정해야 하겠네요.

이 경우 OnCalculate() 함수는 다음과 같습니다.

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(
                const int rates_total,    // total number of bars on the current tick
                const int prev_calculated,// number of calculated bars on the previous tick
                const datetime& time[],
                const double& open[],    
                const double& high[],     // price array for the maximum price for the indicator calculation
                const double& low[],      // price array for the minimum price for the indicator calculation
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[]
              )
  {
//---+   
   //--- checking the number of bars
   if (rates_total < AroonPeriod - 1)
    return(0);
    
   //--- set indexation as timeseries
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low,  true);
   ArraySetAsSeries(BullsAroonBuffer, true);
   ArraySetAsSeries(BearsAroonBuffer, true);
   
   //--- declare the local variables 
   int limit, bar;
   double BULLS, BEARS;
   
   //--- calculation of the starting bar index
   if (prev_calculated == 0)                      // check for the first call of OnCalculate function
       limit = rates_total - AroonPeriod - 1// starting index for the calculation of all of the bars
   else limit = rates_total - prev_calculated; // starting index for the calculation of new bars
   
   //--- main loop
   for(bar = limit; bar >= 0; bar--)
    {
     //--- calculation of the indicator values
     BULLS = 100 + (bar - ArrayMaximum(high, bar, AroonPeriod) - 0.5) * 100 / AroonPeriod;
     BEARS = 100 + (bar - ArrayMinimum(low,  bar, AroonPeriod) - 0.5) * 100 / AroonPeriod;

     //--- filling the indicator buffers with the calculated values 
     BullsAroonBuffer[bar] = BULLS;
     BearsAroonBuffer[bar] = BEARS;
    }
//----+     
   return(rates_total);
  }
//+------------------------------------------------------------------+

'first'에서 'limit'으로 변수 이름을 바꿨습니다. 그게 더 적절한 것 같아서요.

메인 루프의 코드가 MQL4에서 사용하던 것과 비슷해 보이네요. 다시 말해 OnCalculate() 함수를 이용하면 최소한의 수정만으로 MQL4에서 MQL5로 인디케이터를 변환할 수 있습니다.


결론

완성했습니다! 인디케이터를 작성했어요. 그것도 두 가지 버전으로요.

보수적이고 영리하고 올바른 방법을 선택한 것 치고는 해결책이 굉장히 쉽죠? 아이들 레고 조립하는 것에 비해 약간 어렵네요.

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

파일 첨부됨 |
sma_1.mq5 (3.92 KB)
aroon.mq5 (8.04 KB)
aroons.mq5 (6.28 KB)
zigzag.mq5 (9.17 KB)
바보도 할 수 있는 MQL: 객체 클래스 디자인 및 생성 방법 바보도 할 수 있는 MQL: 객체 클래스 디자인 및 생성 방법
그래픽 디자인 샘플 프로그램을 생성해 보면 MQL5로는 어떻게 클래스를 고안하고 생성하는지 알 수 있습니다. 이 글은 MT5 애플리케이션을 이용하는 초보 프로그래머들을 위해 작성되었습니다. 객체 지향 프로그래밍 이론을 깊이 파고들지 않아도 클래스를 생성할 수 있도록 간단하고 쉬운 방법을 알려드리겠습니다.
MetaTrader5와 MATLAB의 상호 작용 MetaTrader5와 MATLAB의 상호 작용
이 글은 MetaTrader5와 MATLAB 패키지 사이의 상호 작용에 대한 설명입니다. 데이터 변환 메커니즘과 MATLAB 데스크톱과 상호 작용이 가능한 범용 라이브러리 개발 과정에 대해 살펴볼 겁니다. MATLAB 환경에서 생성된 DLL의 사용법도 알아보겠습니다. 이 글은 C++와 MQL5를 이미 알고 있는 숙련된 프로그래머들을 위해 작성되었습니다.
유전 알고리즘-쉬워요 유전 알고리즘-쉬워요
이 글에서는 저자가 직접 개발한 유전 알고리즘을 이용한 진화 연산에 대해 이야기합니다. 예제를 이용해 알고리즘의 기능을 설명하고, 실제 적용 가능한 경우를 설명합니다.
그래픽 컨트롤 옵션이 있는 인디케이터 만들기 그래픽 컨트롤 옵션이 있는 인디케이터 만들기
시장 분위기가 무엇인지 안다면 MACD 인디케이터(이동 평균 수렴 확산 지수)도 아실 겁니다. 컴퓨터 분석이 가능해지면서 투자자들이 사용하기 시작한 가격 변동을 파악할 수 있는 아주 뛰어난 분석 도구죠. 이 글에서는 MACD 지표를 어떤 식으로 변형할 수 있는지 알아보고 그래픽 설정 변경이 가능한 하나의 인디케이터로 변형된 MACD 지표를 구현해 보겠습니다.