
MQL5 Cookbook: 가격 다이버전스를 분석하기 위한 다중 기호 지표 개발
소개
이 글에서는 지정된 기간의 가격 다이버전스를 분석하기 위해 다중 기호 지표의 개발을 고려할 것입니다. 핵심 주제는 다중 통화 표시기 프로그래밍에 대한 이전 글 MQL5 Cookbook: MQL5의 다중기호 변동성 지표 개발에서 이미 논의되었습니다. 따라서 이번에는 극적으로 변경된 새로운 기능에 대해서만 설명하겠습니다. 다중 통화 표시기 프로그래밍이 처음이라면 먼저 이전 글을 읽는 것이 좋습니다.
이 글에서는 다음 질문을 고려할 것입니다.
- 차트 속성 변경.
- CHARTEVENT_OBJECT_DRAG(차트 개체 끌기) 및 CHARTEVENT_CHART_CHANGE(차트 크기 조정 또는 속성 대화 상자 창을 사용하여 차트 속성 수정) 이벤트 처리.
- 둘 이상의 색상을 사용하는 렌더링 표시기 버퍼.
- 차트 고/저를 설정하기 위해 가시성 영역 내의 표시기 버퍼에서 고점과 저점을 정의합니다.
- 시리즈의 반전.
결과적으로 표시기에 대한 코드 양은 약 1500줄로 상당히 큽니다. 따라서 모든 기능을 별도의 파일로 배포하고 메인 프로젝트 파일에 연결합니다. 외부 파일에는 세 가지 범주의 기능이 있습니다.
- Checks.mqh - 다양한 검사를 수행하고 사용 가능한 데이터를 다운로드하는 기능입니다.
- Objects.mqh - 그래픽 객체를 관리하기 위한 함수입니다.
- Chart.mqh - 차트 속성을 관리하기 위한 함수입니다.
위의 카테고리에 속하지 않는 모든 기능은 메인 파일에 남게 됩니다.
지표 개발
다음으로 표시기 프로그래밍을 진행합니다. 먼저 새 프로젝트를 만들어야 합니다. 이렇게 하려면 Metatrader 5\MQL5\Indicators 디렉토리에 지표로 이름이 지정된 폴더를 만들고 그 안에 포함 파일을 저장할 Include 폴더를 만드십시오. 다음으로, 표시기 폴더에 메인 파일을 생성합니다. 이것은 *.mq5 확장자를 가진 텍스트 파일을 생성하거나 템플릿으로 MQL5 마법사를 사용하여 수동으로 수행할 수 있습니다. 프로그램 OnInit(), OnDeinit() 및 OnCalculate()의 핵심 기능 외에도 OnChartEvent () 및 OnTimer().
이전 글과 마찬가지로 현재 기호에 추가하여 외부 매개변수에 지정된 5개 기호에 대한 데이터를 표시합니다. 그러나 이번에는 어떤 공식으로 계산된 값 대신 차트에 원시 가격 데이터를 출력합니다. 사용자는 드롭다운 목록에서 외부 매개변수의 데이터 표현 유형을 자유롭게 선택할 수 있습니다: 선, 바 또는 촛대.
데이터를 단색 선으로만 표시해야 하는 경우 표시기 속성(#property)의 기호 수와 동일한 버퍼 수를 지정하는 것으로 충분합니다. 그러나 바와 양초로 시리즈를 그리기 위한 두 가지 모드가 있기 때문에 2색 모드에 대해 더 많은 버퍼가 필요합니다. 각 시리즈를 렌더링하는 4개의 버퍼와 그래픽 시리즈의 각 요소에 대한 색상(조건에 따라 다름)을 설정하는 1개의 버퍼 .
각 시리즈에 대해 프로그램 속성 섹션에서 색상을 지정해야 합니다. 이렇게 하려면 쉼표로 구분하여 나열하면 됩니다. 먼저 단색 모드에서 사용되는 색상이 나옵니다. 2색 모드에서는 위쪽 바/촛대에 사용됩니다. 두 번째 색상은 아래 바/촛대의 2색 모드에서만 사용됩니다.
이러한 모든 매개변수의 코드는 다음과 같습니다.
#property indicator_chart_window // Indicator is in the main window #property indicator_buffers 25 // Number of buffers for indicator calculation #property indicator_plots 5 // Number of plotting series //--- Indicator buffers colors #property indicator_color1 clrDodgerBlue,C'0,50,100' #property indicator_color2 clrLimeGreen,C'20,80,20' #property indicator_color3 clrGold,C'160,140,0' #property indicator_color4 clrAqua,C'0,140,140' #property indicator_color5 clrMagenta,C'130,0,130'-->
#define 지시문을 사용하여 상수를 선언하고 #include 명령줄을 사용하여 위에서 이미 설명한 기능이 있는 파일과 캔버스에서 작업하기 위해 표준 라이브러리의 클래스를 포함해봅시다.
//--- Constants #define RESET 0 // Returning the indicator recalculation command to the terminal #define SYMBOLS_COUNT 5 // Number of symbols //--- Include the class for working with the canvas #include <Canvas\Canvas.mqh> //--- Include the class for working with the canvas #include "Include/Checks.mqh" #include "Include/Chart.mqh" #include "Include/Objects.mqh"-->
ENUM_DRAWTYPE 및 ENUM_START_POINT 열거형을 추가하여 가격 데이터의 그리기 유형과 외부 매개변수에서 가격 차이 시작점의 모드를 선택할 수 있는 드롭다운 목록을 만듭니다.
//--- Drawing type of the price data enum ENUM_DRAWTYPE { LINE =0, // Line BARS =1, // Bars CANDLES=2 // Candlesticks }; //--- Mode of the price divergence starting point enum ENUM_START_POINT { VERTICAL_LINE=0, // Vertical line MONTH =1, // Month WEEK =2, // Week DAY =3, // Day HOUR =4 // Hour };-->
데이터 렌더링의 유형은 이미 위에서 설명했습니다. 이제 가격 다이버전스 시작점이 무엇을 의미하는지 조금 더 이야기해 보겠습니다.
총 5가지 모드가 있습니다: 수직선, 월, 주, 일 및 시. 수직선 모드의 경우 차트에 표시기를 로드할 때 수직선이 추가됩니다. 이 선을 끌어 모든 기호의 가격이 단일 지점에서 만나는 바를 지정합니다. 현재 심볼에 대해 지정된 바의 시가는 이 회의의 기준점으로 간주됩니다. 다른 모드는 지정된 기간이 시작될 때마다 가격이 충족되어야 한다고 프로그램에 알립니다. 즉, 매월 초와 매주 초와 매일의 시작과 매시 초와 같은 때.
아래에서 표시기의 입력 매개변수 목록을 찾을 수 있습니다.
//--- External parameters input ENUM_DRAWTYPE DrawType =CANDLES; // Drawing type input ENUM_START_POINT StartPriceDivergence =VERTICAL_LINE; // Start of price divergence input bool TwoColor =false; // Two-color bars/candlesticks sinput string dlm01=""; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - input string Symbol02 ="GBPUSD"; // Symbol 2 input bool Inverse02 =false; // Inverse symbol 2 input string Symbol03 ="AUDUSD"; // Symbol 3 input bool Inverse03 =false; // Inverse symbol 3 input string Symbol04 ="NZDUSD"; // Symbol 4 input bool Inverse04 =false; // Inverse symbol 4 input string Symbol05 ="USDCAD"; // Symbol 5 input bool Inverse05 =false; // Inverse symbol 5 input string Symbol06 ="USDCHF"; // Symbol 6 input bool Inverse06 =false; // Inverse symbol 6-->
1이 차트의 현재 기호이기 때문에 기호는 2부터 번호가 매겨집니다.
포함된 각 기호에 대해 반전을 적용할 수 있습니다. 반전은 기호 데이터가 거꾸로 렌더링됨을 의미합니다. 이는 분석된 기호 목록에 동일한 통화(예: 미국 달러)가 기준 통화일 수도 있고 카운터 통화일 수도 있는 통화 쌍이 포함된 경우에 유용할 수 있습니다. 예를 들어, EURUSD 통화 쌍에서 미국 달러는 상대 통화이고 USDCHF 통화 쌍에서는 기준 통화입니다. 차트의 현재 기호가 EURUSD인 경우 USDCHF에 대한 반전을 켤 수 있습니다. 그러면 분석에 가격 표시가 더 편리해집니다.
다음은 전역 변수 및 배열 목록입니다.
//--- Structure of the indicator buffers arrays struct buffers { double open[]; // Open prices buffer double high[]; // High prices buffer double low[]; // Low prices buffer double close[]; // Close prices buffer double icolor[]; // Buffer to determine the color of element }; buffers buffer_data[SYMBOLS_COUNT]; //--- Load the class CCanvas canvas; //--- Variables/arrays for copying data from OnCalculate() int OC_rates_total =0; // Size of input time series int OC_prev_calculated =0; // Bars processed at the previous call datetime OC_time[]; // Opening time double OC_open[]; // Open prices double OC_high[]; // High prices double OC_low[]; // Low prices double OC_close[]; // Close prices long OC_tick_volume[]; // Tick volumes long OC_volume[]; // Real volumes int OC_spread[]; // Spread //--- For the purpose of storing and checking the time of the first bar in the terminal datetime series_first_date[SYMBOLS_COUNT]; datetime series_first_date_last[SYMBOLS_COUNT]; //--- Time array of the bar from which we will start drawing datetime limit_time[SYMBOLS_COUNT]; //--- Symbol names array string symbol_names[SYMBOLS_COUNT]; //--- Array of symbol inverse flags bool inverse[SYMBOLS_COUNT]; //--- Colors of indicator lines color line_colors[SYMBOLS_COUNT]={clrDodgerBlue,clrLimeGreen,clrGold,clrAqua,clrMagenta}; //--- String representing the lack of the symbol string empty_symbol="EMPTY"; //--- Chart properties int window_number =WRONG_VALUE; // Indicator window number int chart_width =0; // Chart width int chart_height =0; // Chart height int last_chart_width =0; // Last saved chart width int last_chart_height =0; // Last saved chart height int chart_center_x =0; // Horizontal center of chart int chart_center_y =0; // Vertical center of chart color color_bar_up =clrRed; // Up bar color color color_bar_down =C'100,0,0'; // Down bar color string indicator_shortname ="MS_PriceDivergence"; // Short name of the indicator string prefix =indicator_shortname+"_"; // Prefix for objects //--- Name of vertical line of the price divergence starting point string start_price_divergence=prefix+"start_price_divergence"; //--- Canvas properties string canvas_name =prefix+"canvas"; // Canvas name color canvas_background =clrBlack; // Canvas background color uchar canvas_opacity =190; // Opacity int font_size =16; // Font size string font_name ="Calibri"; // Font ENUM_COLOR_FORMAT clr_format =COLOR_FORMAT_ARGB_RAW; // Color components should be correctly set by the user //--- Canvas messages string msg_prepare_data ="Preparing data! Please wait..."; string msg_not_synchronized ="Unsynchronized data! Please wait..."; string msg_load_data =""; string msg_sync_update =""; string msg_last =""; //--- ENUM_TIMEFRAMES timeframe_start_point =Period(); // Timeframe for the price divergence starting point datetime first_period_time =NULL; // Time of the first specified period on chart double divergence_price =0.0; // Price of the price divergence starting point datetime divergence_time =NULL; // Time of the price divergence starting point double symbol_difference[SYMBOLS_COUNT]; // Difference in price relative to the current symbol double inverse_difference[SYMBOLS_COUNT]; // Difference that is formed when calculating inversion-->
다음으로 표시기 초기화 중에 사용되는 기능에 대해 살펴보겠습니다. 일반적으로 이전 글의 OnInit() 함수와 비교하면 큰 변화는 없습니다.
표시기가 사용되는 위치에 대한 확인을 추가해 보겠습니다. 요점은 현재 터미널 개발자들이 Strategy Tester에서 차트 속성을 제어하는 모든 기능을 구현하지 않았기 때문에 우리 지표를 Strategy Tester 외부에서만 사용하도록 제한한다는 것입니다. 이를 구현하기 위해 간단한 함수인 CheckTesterMode()를 작성합니다. Checks.mqh 파일에 있습니다.
//+------------------------------------------------------------------+ //| Checks if indicator is used in Strategy Tester | //+------------------------------------------------------------------+ bool CheckTesterMode() { //--- Report that indicator is not intended to be used in Strategy Tester if(MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE) || MQLInfoInteger(MQL_OPTIMIZATION)) { Comment("Currently, the <- "+MQLInfoString(MQL_PROGRAM_NAME)+" -> indicator is not intended to be used in Strategy Tester!"); return(false); } //--- return(true); }-->
또 다른 새로운 함수 SetBarsColors()는 현재 기호의 바/촛대의 색상을 설정하기 위한 것입니다. Chart.mqh 파일에 있습니다.
//+------------------------------------------------------------------+ //| Sets colors for the current symbol bars | //+------------------------------------------------------------------+ void SetBarsColors() { //--- Color for the up bar, shadows and body borders of bull candlesticks ChartSetInteger(0,CHART_COLOR_CHART_UP,color_bar_up); //--- Body color of a bull candlestick ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,color_bar_up); //--- Line chart color and color of "Doji" Japanese candlesticks ChartSetInteger(0,CHART_COLOR_CHART_LINE,color_bar_up); //--- For two-color mode if(TwoColor) { //--- Color for the down bar, shadows and body borders of bear candlesticks ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_down); //--- Body color of a bear candlestick ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_down); } //--- If two-color mode is turned off else { //--- Color for the down bar, shadows and body borders of bear candlesticks ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_up); //--- Body color of a bear candlestick ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_up); } }-->
초기화하는 동안 StartPriceDivergence 외부 매개변수에서 어떤 모드가 선택되었는지 확인해야 합니다. 수직선이 선택되면 timeframe_start_point 전역 변수가 기본값, 즉 현재 시간 프레임으로 할당됩니다. 그렇지 않으면 선택한 기간이 적용됩니다. 이를 위해 InitStartPointTF() 함수를 작성해 보겠습니다.
//+------------------------------------------------------------------+ //| Identifies timeframe for the price starting point mode | //+------------------------------------------------------------------+ void InitStartPointTF() { //--- Exit if vertical line mode is selected if(StartPriceDivergence==VERTICAL_LINE) return; //--- Otherwise define the timeframe switch(StartPriceDivergence) { case MONTH : timeframe_start_point=PERIOD_MN1; break; case WEEK : timeframe_start_point=PERIOD_W1; break; case DAY : timeframe_start_point=PERIOD_D1; break; case HOUR : timeframe_start_point=PERIOD_H1; break; } }-->
이전 글과 달리 CheckInputParameters() 함수는 이제 다음과 같이 보입니다.
//+------------------------------------------------------------------+ //| Checks input parameters for correctness | //+------------------------------------------------------------------+ bool CheckInputParameters() { //--- For all other modes except the 'Vertical Line' if(StartPriceDivergence!=VERTICAL_LINE) { //--- If the current period is greater than or equal to the specified period of the price divergence starting point, report of it and exit if(PeriodSeconds()>=PeriodSeconds(timeframe_start_point)) { Print("Current timeframe should be less than one specified in the Start Price Divergence parameter!"); Comment("Current timeframe should be less than one specified in the Start Price Divergence parameter!"); return(false); } } //--- return(true); }-->
배열은 이전 글과 마찬가지로 초기화됩니다. 배열의 이름과 개수만 변경되었습니다.
//+------------------------------------------------------------------+ //| First initialization of arrays | //+------------------------------------------------------------------+ void InitArrays() { ArrayInitialize(limit_time,NULL); ArrayInitialize(symbol_difference,0.0); ArrayInitialize(inverse_difference,0.0); ArrayInitialize(series_first_date,NULL); ArrayInitialize(series_first_date_last,NULL); //--- for(int s=0; s<SYMBOLS_COUNT; s++) { ArrayInitialize(buffer_data[s].open,EMPTY_VALUE); ArrayInitialize(buffer_data[s].high,EMPTY_VALUE); ArrayInitialize(buffer_data[s].low,EMPTY_VALUE); ArrayInitialize(buffer_data[s].close,EMPTY_VALUE); ArrayInitialize(buffer_data[s].icolor,EMPTY_VALUE); } } //+------------------------------------------------------------------+ //| Initializes array of symbols | //+------------------------------------------------------------------+ void InitSymbolNames() { symbol_names[0]=AddSymbolToMarketWatch(Symbol02); symbol_names[1]=AddSymbolToMarketWatch(Symbol03); symbol_names[2]=AddSymbolToMarketWatch(Symbol04); symbol_names[3]=AddSymbolToMarketWatch(Symbol05); symbol_names[4]=AddSymbolToMarketWatch(Symbol06); } //+------------------------------------------------------------------+ //| Initializes array of inversions | //+------------------------------------------------------------------+ void InitInverse() { inverse[0]=Inverse02; inverse[1]=Inverse03; inverse[2]=Inverse04; inverse[3]=Inverse05; inverse[4]=Inverse06; }-->
SetIndicatorProperties() 함수가 크게 변경되었습니다. 사실 이것은 완전히 새로운 기능입니다. 이제 선택한 데이터 렌더링 모드에 따라 초기화 중에 해당 속성이 설정됩니다.
//+------------------------------------------------------------------+ //| Sets indicator properties | //+------------------------------------------------------------------+ void SetIndicatorProperties() { //--- Set the short name IndicatorSetString(INDICATOR_SHORTNAME,indicator_shortname); //--- Set the number of decimal digits IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- In the 'Line' mode we need only one buffers that displays the open price if(DrawType==LINE) { for(int s=0; s<SYMBOLS_COUNT; s++) SetIndexBuffer(s,buffer_data[s].close,INDICATOR_DATA); } //--- In other modes we use all prices for drawing // bars/candlesticks and additional buffer for the two-color mode else if(DrawType==BARS || DrawType==CANDLES) { for(int s=0; s<SYMBOLS_COUNT; s++) { static int buffer_number=0; SetIndexBuffer(buffer_number,buffer_data[s].open,INDICATOR_DATA); buffer_number++; SetIndexBuffer(buffer_number,buffer_data[s].high,INDICATOR_DATA); buffer_number++; SetIndexBuffer(buffer_number,buffer_data[s].low,INDICATOR_DATA); buffer_number++; SetIndexBuffer(buffer_number,buffer_data[s].close,INDICATOR_DATA); buffer_number++; SetIndexBuffer(buffer_number,buffer_data[s].icolor,INDICATOR_COLOR_INDEX); buffer_number++; } } //--- Set labels for the current timeframe // In the 'Line' mode only opening price is used if(DrawType==LINE) { for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetString(s,PLOT_LABEL,symbol_names[s]+",Close"); } //--- In other modes all prices of bars/candlesticks // ";" is used as a separator else if(DrawType==BARS || DrawType==CANDLES) { for(int s=0; s<SYMBOLS_COUNT; s++) { PlotIndexSetString(s,PLOT_LABEL, symbol_names[s]+",Open;"+ symbol_names[s]+",High;"+ symbol_names[s]+",Low;"+ symbol_names[s]+",Close"); } } //--- Set the type of lines for indicator buffers //--- Line if(DrawType==LINE) for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_LINE); //--- Bars if(DrawType==BARS) for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_BARS); //--- Candlesticks if(DrawType==CANDLES) for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_CANDLES); //--- Set the type of lines for data of current symbol //--- Line if(DrawType==LINE) ChartSetInteger(0,CHART_MODE,CHART_LINE); //--- Bars if(DrawType==BARS) ChartSetInteger(0,CHART_MODE,CHART_BARS); //--- Candlesticks if(DrawType==CANDLES) ChartSetInteger(0,CHART_MODE,CHART_CANDLES); //--- Set the line width for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_LINE_WIDTH,1); //--- Set the line color for the 'Line' mode if(DrawType==LINE) for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetInteger(s,PLOT_LINE_COLOR,line_colors[s]); //--- Display data in Data Window only for existing symbols for(int s=0; s<SYMBOLS_COUNT; s++) { if(symbol_names[s]!=empty_symbol) PlotIndexSetInteger(s,PLOT_SHOW_DATA,true); else PlotIndexSetInteger(s,PLOT_SHOW_DATA,false); } //--- Empty value for plotting where nothing will be drawn for(int s=0; s<SYMBOLS_COUNT; s++) PlotIndexSetDouble(s,PLOT_EMPTY_VALUE,EMPTY_VALUE); }-->
마지막으로 OnInit()에서 사용할 또 다른 새로운 함수 SetDivergenceLine()입니다. 수직선 모드에서 가격 차이 시작점을 조작하기 위해 수직 녹색 선을 설정합니다.
//+------------------------------------------------------------------+ //| Sets vertical line for price divergence starting point | //+------------------------------------------------------------------+ void SetDivergenceLine() { //--- If there is no vertical line yet, set it if(StartPriceDivergence==VERTICAL_LINE && ObjectFind(0,start_price_divergence)<0) //--- Place a vertical line on the true bar CreateVerticalLine(0,0,TimeCurrent()+PeriodSeconds(),start_price_divergence, 2,STYLE_SOLID,clrGreenYellow,true,true,false,"","\n"); //--- For all other modes except the 'Vertical Line' if(StartPriceDivergence!=VERTICAL_LINE) DeleteObjectByName(start_price_divergence); }-->
아래는 OnInit() 함수 내에서 위에서 설명한 모든 것을 표현한 것입니다. 모든 것이 별도의 기능과 파일로 분할되면 프로그램 코드를 읽는 것이 매우 편리해집니다.
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Check if indicator is currently being used in Strategy Tester if(!CheckTesterMode()) return(INIT_FAILED); //--- Set the color for bars/candlesticks SetBarsColors(); //--- Define the timeframe for the price divergence starting point InitStartPointTF(); //--- Check input parameters for correctness if(!CheckInputParameters()) return(INIT_PARAMETERS_INCORRECT); //--- Set the timer at 1-second intervals EventSetMillisecondTimer(1000); //--- Set the font to be displayed on the canvas canvas.FontSet(font_name,font_size,FW_NORMAL); //--- Initialization of arrays InitArrays(); //--- Initialize the array of symbols InitSymbolNames(); //--- Initialize the array of inversions InitInverse(); //--- Set indicator properties SetIndicatorProperties(); //--- Set vertical line of the price divergence start SetDivergenceLine(); //--- Clear the comment Comment(""); //--- Refresh the chart ChartRedraw(); //--- Initialization completed successfully return(INIT_SUCCEEDED); }-->
OnCalculate() 함수에서 프로그램 코드는 거의 변경되지 않았습니다. 이전 글에서 데이터 가용성에 대한 모든 확인이 완료된 후 프로그램은 먼저 보조 배열을 채운 다음 준비된 데이터로 표시기 버퍼를 채웠습니다. 이번에는 모든 것을 단일 루프로 정렬하려고 합니다.
데이터 유효성 검사 및 로드 기능을 보다 엄격하게 만들었습니다. 이제 얻고자 하는 각 값이 지정된 시도 횟수를 통과합니다. 값을 얻으면 루프가 중지됩니다. 그리고 이제 기간의 시작(월, 주, 일, 시간)을 결정해야 하는 모드가 있으므로 더 높은 기간을 통해 기간 시작 시간을 얻습니다. 따라서 LoadAndFormDataHighTF()라는 유사한 이름을 가진 LoadAndFormData()와 유사한 추가 함수를 만들었습니다. 코드는 원본 코드와 매우 유사하므로 여기에 게시하지 않겠습니다.
현재 및 더 높은 기간에 대한 데이터 가용성 확인이 하나의 함수 CheckAvailableData()에서 구현되었습니다.
//+------------------------------------------------------------------+ //| Checks the amount of available data for all symbols | //+------------------------------------------------------------------+ bool CheckAvailableData() { int attempts=100; //--- for(int s=0; s<SYMBOLS_COUNT; s++) { //--- If this symbol is available if(symbol_names[s]!=empty_symbol) { datetime time[]; // Array for checking the number of bars int total_period_bars =0; // Number of bars of the current period datetime terminal_first_date =NULL; // First date of the current time frame data available in the terminal //--- Get the first date of the current time frame data in the terminal terminal_first_date=(datetime)SeriesInfoInteger(symbol_names[s],Period(),SERIES_TERMINAL_FIRSTDATE); //--- Get the number of available bars from the date specified total_period_bars=Bars(symbol_names[s],Period(),terminal_first_date,TimeCurrent()); //--- Check the readiness of bar data for(int i=0; i<attempts; i++) { //--- Copy the specified amount of data if(CopyTime(symbol_names[s],Period(),0,total_period_bars,time)) { //--- If the required amount has been copied, terminate the loop if(ArraySize(time)>=total_period_bars) break; } } //--- If the amount of data copied is not sufficient, one more attempt is required if(ArraySize(time)==0 || ArraySize(time)<total_period_bars) { msg_last=msg_prepare_data; ShowCanvasMessage(msg_prepare_data); OC_prev_calculated=0; return(false); } } } //--- Exit if current mode is vertical line of the price divergence starting point if(StartPriceDivergence==VERTICAL_LINE) return(true); else { datetime time[]; // Array for checking the number of bars int total_period_bars =0; // Number of bars of the current period datetime terminal_first_date =NULL; // First date of the current time frame data available in the terminal //--- Get the first date of the current time frame data in the terminal for(int i=0; i<attempts; i++) if((terminal_first_date=(datetime)SeriesInfoInteger(Symbol(),Period(),SERIES_FIRSTDATE))>0) break; //--- Get the number of available bars from the date specified for(int i=0; i<attempts; i++) if((total_period_bars=(int)SeriesInfoInteger(Symbol(),timeframe_start_point,SERIES_BARS_COUNT))>0) break; //--- Check the readiness of bar data for(int i=0; i<attempts; i++) //--- Copy the specified amount of data if(CopyTime(Symbol(),timeframe_start_point, terminal_first_date+PeriodSeconds(timeframe_start_point),TimeCurrent(),time)>0) break; //--- If the amount of data copied is not sufficient, one more attempt is required if(ArraySize(time)<=0 || total_period_bars<=0) { msg_last=msg_prepare_data; ShowCanvasMessage(msg_prepare_data); OC_prev_calculated=0; return(false); } } //--- return(true); }-->
FillIndicatorBuffers() 함수는 현재 작업에 대해 상당히 복잡했습니다. 이것은 이제 몇 가지 모드가 있고 각 모드에는 자체 작업이 필요하기 때문입니다. 사실, 모든 것은 네 단계로 나눌 수 있습니다.
- 지정된 기호에 대한 데이터를 가져옵니다.
- 더 높은 시간대에 대한 데이터를 얻고 모든 기호의 가격이 만나는 시간과 가격 수준을 결정합니다.
- 값 계산 및 표시기 버퍼 채우기.
- 계산된 값의 확인.
기능 코드는 고려 사항에 대한 자세한 설명과 함께 제공됩니다.
//+------------------------------------------------------------------+ //| Fills indicator buffers | //+------------------------------------------------------------------+ void FillIndicatorBuffers(int i,int s,datetime const &time[]) { MqlRates rates[]; // Data structure double period_open[]; // Opening price for bar at the price divergence starting point datetime period_time[]; // Time of the price divergence starting point int attempts=100; // Number of copying attempts datetime high_tf_time=NULL; // Time of higher timeframe's bar //--- Exit if we are out of "true" bars zone if(time[i]<limit_time[s]) return; //--- Reset the last error ResetLastError(); //--- Get data of current bar for the specified symbol for(int j=0; j<attempts; j++) if(CopyRates(symbol_names[s],Period(),time[i],1,rates)==1) { ResetLastError(); break; } //--- Exit if failed to get data if(ArraySize(rates)<1 || GetLastError()!=0) return; //--- If the current time is before the first timeframe's time or // bar time is not equal to the bar time of the current symbol or // empty values are fetched if(rates[0].time==NULL || time[i]!=rates[0].time || time[i]<first_period_time || rates[0].low==EMPTY_VALUE || rates[0].open==EMPTY_VALUE || rates[0].high==EMPTY_VALUE || rates[0].close==EMPTY_VALUE) { //--- Write empty value if(DrawType!=LINE) { buffer_data[s].low[i] =EMPTY_VALUE; buffer_data[s].open[i] =EMPTY_VALUE; buffer_data[s].high[i] =EMPTY_VALUE; } buffer_data[s].close[i]=EMPTY_VALUE; return; } //--- If current mode is vertical line of the price divergence starting point if(StartPriceDivergence==VERTICAL_LINE) { //--- Get the time of the line divergence_time=(datetime)ObjectGetInteger(0,start_price_divergence,OBJPROP_TIME); //--- Get the time of the first bar first_period_time=time[0]; } //--- For all other modes, we will keep track the beginning of period else { //--- If we are here for the first time, store data of the first bar of higher timeframe if(divergence_time==NULL) { ResetLastError(); //--- Get opening time of the first bar of higher timeframe for(int j=0; j<attempts; j++) if(CopyTime(Symbol(),timeframe_start_point,time[0]+PeriodSeconds(timeframe_start_point),1,period_time)==1) { ResetLastError(); break; } //--- Exit if failed to get price/time if(ArraySize(period_time)<1 || GetLastError()!=0) return; //--- Otherwise store time of the first bar of higher timeframe else first_period_time=period_time[0]; } //--- If current bar's time on the current timeframe is before the first bar's time on higher timeframe if(time[i]<first_period_time) high_tf_time=first_period_time; //--- Otherwise we will receive data of the last bar of the higher timeframe with respect to the current bar on the current timeframe else high_tf_time=time[i]; //--- Reset the last error ResetLastError(); //--- Get the opening price of the first bar of the higher timeframe for(int j=0; j<attempts; j++) if(CopyOpen(Symbol(),timeframe_start_point,high_tf_time,1,period_open)==1) { ResetLastError(); break; } //--- Get opening time of the first bar of higher timeframe for(int j=0; j<attempts; j++) if(CopyTime(Symbol(),timeframe_start_point,high_tf_time,1,period_time)==1) { ResetLastError(); break; } //--- Exit if failed to get price/time if(ArraySize(period_open)<1 || ArraySize(period_time)<1 || GetLastError()!=0) return; //--- If the current timeframe's time is before the first period's time or // time of specified period is not equal to the one in memory if(time[i]<first_period_time || divergence_time!=period_time[0]) { symbol_difference[s] =0.0; // Zero out difference in symbol prices inverse_difference[s] =0.0; // Zero our difference of inversion //--- Store time of the price divergence starting point divergence_time=period_time[0]; //--- Store price of the price divergence starting point divergence_price=period_open[0]; //--- Set vertical line in the beginning of the price divergence start CreateVerticalLine(0,0,period_time[0],start_price_divergence+"_"+TimeToString(divergence_time), 2,STYLE_SOLID,clrWhite,false,false,true,TimeToString(divergence_time),"\n"); } } //--- If current mode is 'Vertical Line' and bar's time is less than line's time if(StartPriceDivergence==VERTICAL_LINE && time[i]<divergence_time) { //--- Keep zero values of difference symbol_difference[s] =0.0; inverse_difference[s] =0.0; //--- For the 'Line' drawing mode only opening price is used if(DrawType==LINE) buffer_data[s].close[i]=rates[0].close-symbol_difference[s]; //--- For all other modes all prices are used else { buffer_data[s].low[i] =rates[0].low-symbol_difference[s]; buffer_data[s].open[i] =rates[0].open-symbol_difference[s]; buffer_data[s].high[i] =rates[0].high-symbol_difference[s]; buffer_data[s].close[i] =rates[0].close-symbol_difference[s]; //--- Set color for the current element of indicator buffer SetBufferColorIndex(i,s,rates[0].close,rates[0].open); } } //--- For all other modes else { //--- If inversion of symbol data is required if(inverse[s]) { //--- If new period has started, recalculate variables if(symbol_difference[s]==0.0) { //--- For the 'Vertical Line' mode if(StartPriceDivergence==VERTICAL_LINE) { //--- Calculate the difference symbol_difference[s] =rates[0].open-OC_open[i]; inverse_difference[s] =OC_open[i]-(-OC_open[i]); } //--- For all other modes else { //--- Calculate the difference symbol_difference[s] =rates[0].open-divergence_price; inverse_difference[s] =divergence_price-(-divergence_price); } } //--- In the 'Line' mode only opening price is used if(DrawType==LINE) buffer_data[s].close[i]=-(rates[0].close-symbol_difference[s])+inverse_difference[s]; //--- For all other modes all prices are used else { buffer_data[s].low[i] =-(rates[0].low-symbol_difference[s])+inverse_difference[s]; buffer_data[s].open[i] =-(rates[0].open-symbol_difference[s])+inverse_difference[s]; buffer_data[s].high[i] =-(rates[0].high-symbol_difference[s])+inverse_difference[s]; buffer_data[s].close[i] =-(rates[0].close-symbol_difference[s])+inverse_difference[s]; //--- Set color for the current element of indicator buffer SetBufferColorIndex(i,s,rates[0].close,rates[0].open); } } //--- If inversion is not used, then we need to calculate only the difference between symbol prices at the beginning of period else { //--- If new period has started if(symbol_difference[s]==0.0) { //--- For the 'Vertical Line' mode if(StartPriceDivergence==VERTICAL_LINE) symbol_difference[s]=rates[0].open-OC_open[i]; //--- For all other modes else symbol_difference[s]=rates[0].open-divergence_price; } //--- For the 'Line' drawing mode only opening price is used if(DrawType==LINE) buffer_data[s].close[i]=rates[0].close-symbol_difference[s]; //--- For all other modes all prices are used else { buffer_data[s].low[i] =rates[0].low-symbol_difference[s]; buffer_data[s].open[i] =rates[0].open-symbol_difference[s]; buffer_data[s].high[i] =rates[0].high-symbol_difference[s]; buffer_data[s].close[i] =rates[0].close-symbol_difference[s]; //--- Set color for the current element of indicator buffer SetBufferColorIndex(i,s,rates[0].close,rates[0].open); } } } //--- Verification of the calculated values // In the 'Line' mode only opening price is used if(DrawType==LINE) { //--- If the current time is before the first timeframe's time or // bar time is not equal to the bar time, write empty value if(time[i]!=rates[0].time || time[i]<first_period_time) buffer_data[s].close[i]=EMPTY_VALUE; } //--- For all other modes all prices are used else { //--- If the current time is before the first timeframe's time or // bar time is not equal to the bar time of the current symbol or // empty values are fetched if(rates[0].time==NULL || time[i]!=rates[0].time || time[i]<first_period_time || rates[0].low==EMPTY_VALUE || rates[0].open==EMPTY_VALUE || rates[0].high==EMPTY_VALUE || rates[0].close==EMPTY_VALUE) { //--- Write empty value buffer_data[s].low[i] =EMPTY_VALUE; buffer_data[s].open[i] =EMPTY_VALUE; buffer_data[s].high[i] =EMPTY_VALUE; buffer_data[s].close[i] =EMPTY_VALUE; } } }-->
위의 함수를 공부할 때 또 다른 사용자 정의 함수 SetBufferColorIndex()를 확인해야 합니다. 이 함수는 표시기 색상 버퍼의 색상을 설정합니다.
//+------------------------------------------------------------------+ //| Sets the color for buffer element by condition | //+------------------------------------------------------------------+ void SetBufferColorIndex(int i,int symbol_number,double close,double open) { //--- For two-color mode, check condition if(TwoColor) { //--- If the closing price is more than the opening price, this is up bar, so we use the first color if(close>open) buffer_data[symbol_number].icolor[i]=0; //--- otherwise it is down bar, so we use the second color else buffer_data[symbol_number].icolor[i]=1; } //--- For one-color mode we use the first color for all bars/candlesticks else buffer_data[symbol_number].icolor[i]=0; }-->
표시기 버퍼가 채워지면 현재 차트 창에 표시되는 모든 값에서 최대값과 최소값을 결정해야 합니다. MQL5를 사용하면 차트 창에서 첫 번째로 보이는 바와 보이는 바의 수를 얻을 수 있습니다. 우리는 다른 사용자 정의 함수 CorrectChartMaxMin()에서 이러한 기능의 이점을 얻을 것입니다. 함수의 코드 흐름은 여러 단계로 나눌 수 있습니다.
- 첫 번째와 마지막 보이는 바의 수를 결정합니다.
- 현재 기호에 대해 표시되는 바의 최대값과 최소값을 결정합니다.
- 모든 기호 배열 중에서 최대값과 최소값을 결정합니다.
- 차트 속성에서 최대값과 최소값을 설정합니다.
다음은 Chart.mqh 파일에 있는 CorrectChartMaxMin() 함수의 코드입니다.
//+------------------------------------------------------------------+ //| Corrects chart's high/low with respect to all buffers | //+------------------------------------------------------------------+ void CorrectChartMaxMin() { double low[]; // Array of lows double high[]; // Array of highs int attempts =10; // Number of attempts int array_size =0; // Array size for drawing int visible_bars =0; // Number of visible bars int first_visible_bar =0; // Number of the first visible bar int last_visible_bar =0; // Number of the last visible bar double max_price =0.0; // Highest price double min_price =0.0; // Lowest price double offset_max_min =0.0; // Offset from chart's high/low //--- Reset the last error ResetLastError(); //--- Number of visible bars visible_bars=(int)ChartGetInteger(0,CHART_VISIBLE_BARS); //--- Number of the first visible bar first_visible_bar=(int)ChartGetInteger(0,CHART_FIRST_VISIBLE_BAR); //--- Number of the last visible bar last_visible_bar=first_visible_bar-visible_bars; //--- Exit in case of error if(GetLastError()!=0) return; //--- Fix incorrect value if(last_visible_bar<0) last_visible_bar=0; //--- Get the current symbol high/low in visible area of chart for(int i=0; i<attempts; i++) if(CopyHigh(Symbol(),Period(),last_visible_bar,visible_bars,high)==visible_bars) break; for(int i=0; i<attempts; i++) if(CopyLow(Symbol(),Period(),last_visible_bar,visible_bars,low)==visible_bars) break; //--- Exit if failed to get data if(ArraySize(high)<=0 || ArraySize(low)<=0) return; //--- If succeeded to get data, identify high and low in the current symbol arrays else { min_price=low[ArrayMinimum(low)]; max_price=high[ArrayMaximum(high)]; } //--- Get high and low prices in all price arrays for(int s=0; s<SYMBOLS_COUNT; s++) { //--- If current symbol is not present, go to the next one if(symbol_names[s]==empty_symbol) continue; //--- datetime time[]; // Time array int bars_count=0; // Number of bars for calculation //--- Set zero size for arrays ArrayResize(high,0); ArrayResize(low,0); //--- Get the time of the first bar visible on chart for(int i=0; i<attempts; i++) if(CopyTime(Symbol(),Period(),last_visible_bar,visible_bars,time)==visible_bars) break; //--- Exit if the amount of data is less than number of visible bars on chart if(ArraySize(time)<visible_bars) return; //--- If time of the first "true" bar is greater than // time of the first visible bar on the chart, then // get available number of bars of the current symbol in loop if(limit_time[s]>time[0]) { //--- Get the array size array_size=ArraySize(time); //--- Get the number of bars from the first "true" one if((bars_count=Bars(Symbol(),Period(),limit_time[s],time[array_size-1]))<=0) return; } //--- Else get number of visible bars on chart else bars_count=visible_bars; //--- Index elements in indicator buffers as timeseries ArraySetAsSeries(low,true); ArraySetAsSeries(high,true); //--- Copy data from the indicator buffer // All modes except 'Line' if(DrawType!=LINE) { ArrayCopy(low,buffer_data[s].low); ArrayCopy(high,buffer_data[s].high); } //--- For the 'Line' mode else { ArrayCopy(low,buffer_data[s].close); ArrayCopy(high,buffer_data[s].close); } //--- Get the array size array_size=ArraySize(high); //--- Fill empty values, // so they are not considered when calculating high/low for(int i=0; i<array_size; i++) { if(high[i]==EMPTY_VALUE) high[i]=max_price; if(low[i]==EMPTY_VALUE) low[i]=min_price; } //--- Get high/low with respect to inversion if(inverse[s]) { //--- If no errors occur, store values if(ArrayMaximum(high,last_visible_bar,bars_count)>=0 && ArrayMinimum(low,last_visible_bar,bars_count)>=0) { max_price=fmax(max_price,low[ArrayMaximum(low,last_visible_bar,bars_count)]); min_price=fmin(min_price,high[ArrayMinimum(high,last_visible_bar,bars_count)]); } } else { //--- If no errors occur, store values if(ArrayMinimum(low,last_visible_bar,bars_count)>=0 && ArrayMaximum(high,last_visible_bar,bars_count)>=0) { min_price=fmin(min_price,low[ArrayMinimum(low,last_visible_bar,bars_count)]); max_price=fmax(max_price,high[ArrayMaximum(high,last_visible_bar,bars_count)]); } } } //--- Calculate offset (3%) form chart's top and bottom offset_max_min=((max_price-min_price)*3)/100; //--- Turn on the fixed chart scale mode. ChartSetInteger(0,CHART_SCALEFIX,true); //--- Set high/low ChartSetDouble(0,CHART_FIXED_MAX,max_price+offset_max_min); ChartSetDouble(0,CHART_FIXED_MIN,min_price-offset_max_min); //--- Refresh the chart ChartRedraw(); }-->
위에서 설명한 함수는 세로선을 드래그하는 이벤트를 처리할 때 사용됩니다(물론 OnCalculate에서 표시기 값을 계산할 때도).
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Event of dragging a graphical object if(id==CHARTEVENT_OBJECT_DRAG) { //--- If current mode is vertical line for the price divergence starting point, then update indicator buffers if(StartPriceDivergence==VERTICAL_LINE) OnCalculate(OC_rates_total, 0, OC_time, OC_open, OC_high, OC_low, OC_close, OC_tick_volume, OC_volume, OC_spread); } //--- Event of resizing the chart or modifying the chart properties using the properties dialog window. if(id==CHARTEVENT_CHART_CHANGE) //--- Correct the maximum and minimum of chart with respect to the indicator buffers' values CorrectChartMaxMin(); }-->
모든 기능이 준비되었습니다. 이 글에 첨부된 전체 주석 코드를 연구할 수 있습니다.
우리가 결국 무엇을 얻었는지 보여줍시다. 기본적으로 GBPUSD, AUDUSD, NZDUSD, USDCAD, USDCHF 기호가 지정됩니다. 외부 매개변수에서. 아래 스크린샷에서 반전이 비활성화된 수직선 모드에서 EURUSD의 주간 차트를 볼 수 있습니다.
그림 1 - "수직선" 모드의 주간 시간대
아래 스크린샷에서 일 모드의 M30 시간대를 볼 수 있지만, 기본 통화로 USD를 사용하는 기호에 대해 시간 반전이 활성화됩니다. 이 경우 USDCAD(하늘색 양초)와 USDCHF(보라색 양초)가 있습니다.
그림 2 - "주간" 모드의 M30 시간 프레임
결론
저는 우리가 가격 다이버전스의 다중 통화 분석을 위한 매우 흥미롭고 유익한 도구를 만들었다고 생각합니다. 이 지표는 무한히 향상될 수 있습니다.
시간 내어 읽어주셔서 감사합니다!
MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/754



