
뉴비들을 위한 복합 인디케이터 버퍼 만들기
개요
'뉴비들을 위한 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



