Manuale MQL5: Sviluppo di un indicatore multi-simbolo per l’analisi della divergenza dei prezzi
Introduzione
In questo articolo, considereremo lo sviluppo di un indicatore multi-simbolo per analizzare la divergenza dei prezzi in un determinato periodo di tempo. Gli argomenti principali sono già stati discussi nel precedente articolo sulla programmazione di indicatori multi-valuta Manuale MQL5: Sviluppo di un indicatore di volatilità multi-simbolo in MQL5. Quindi questa volta ci soffermeremo solo su quelle nuove caratteristiche e funzioni che sono state cambiate radicalmente. Se sei un neofita della programmazione di indicatori multi-valuta, ti consiglio di leggere prima l'articolo precedente.
In questo articolo prenderemo in considerazione le seguenti questioni:
- Modifica delle proprietà del grafico.
- Gestione degli eventi CHARTEVENT_OBJECT_DRAG (trascinamento di un oggetto grafico) e CHARTEVENT_CHART_CHANGE (ridimensionamento del grafico o modifica delle proprietà del grafico mediante la finestra di dialogo delle proprietà).
- Rendering dei buffer degli indicatori utilizzando più di un colore.
- Definizione di alti e bassi nei buffer degli indicatori all'interno dell'area di visibilità per impostare un grafico alto/basso.
- Inversione di una serie.
La quantità di codice risultante per il nostro indicatore è piuttosto grande, circa 1500 righe. Pertanto, distribuiremo tutte le funzioni in file separati e le collegheremo al file di progetto principale. Ci saranno tre categorie di funzioni per i file esterni:
- Checks.mqh - Funzioni per eseguire vari controlli e per scaricare i dati disponibili.
- Objects.mqh - Funzioni per la gestione di oggetti grafici.
- Chart.mqh - Funzioni per la gestione delle proprietà del grafico.
Tutte le funzioni che non appartengono alle categorie di cui sopra verranno lasciate nel file principale.
Sviluppo dell'indicatore
Passiamo alla programmazione dell'indicatore. Per prima cosa dobbiamo creare un nuovo progetto. Per fare ciò, nella directory Metatrader5\MQL5 \Indicators creare una cartella con lo stesso nome del nostro indicatore e in essa la cartella Includi in cui posizioneremo i file di include. Quindi, creare il file principale nella cartella degli indicatori. Questo può essere fatto manualmente creando un file di testo con l'estensione *.mq5 o utilizzando la procedura guidata MQL5 per modello. Oltre alle funzioni principali del programma OnInit(), OnDeinit() e OnCalculate(), useremo anche OnChartEvent() e OnTimer().
Proprio come nell'articolo precedente, oltre al simbolo corrente mostreremo i dati per 5 simboli specificati nei parametri esterni. Ma questa volta, invece di valori calcolati da qualche formula, produrremo dati grezzi dei prezzi sul grafico. L'utente è libero di scegliere il tipo di rappresentazione dei dati nei parametri esterni dall'elenco a discesa: Linea, Barre o Candele Giapponesi.
Se dovessimo visualizzare i dati solo come linee monocolore, allora sarebbe sufficiente specificare il numero di buffer pari al numero di simboli nelle proprietà dell'indicatore (#property). Ma poiché ci sono due modalità per disegnare serie come barre e candele giapponesi, abbiamo bisogno di più buffer per la modalità a due colori: quattro buffer per eseguire il rendering di ogni serie e un buffer per impostare il colore (a seconda delle condizioni) per ogni elemento di una serie grafica.
Per ogni serie è necessario specificare i colori nella sezione delle proprietà del programma. Per fare ciò, è sufficiente elencarli separati da virgole. Prima viene il colore utilizzato in modalità monocolore. In modalità a due colori, viene utilizzato per le barre / candele giapponesi. Il secondo colore verrà utilizzato solo nella modalità a due colori per le barre giù / candele giapponesi.
I codici di tutti questi parametri sono forniti di seguito:
#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'
Con la direttiva #define dichiariamo le costanti e usando la riga di comando #include includiamo i file con funzioni che sono già state descritte sopra e la classe dalla libreria Standard per lavorare con il canvas:
//--- 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"
Aggiungere le enumerazioni ENUM_DRAWTYPE e ENUM_START_POINT per creare elenchi a discesa che consentano di selezionare il tipo di disegno dei dati di prezzo e la modalità del punto di partenza della divergenza di prezzo nei parametri esterni:
//--- 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 };
I tipi di rendering dei dati sono già descritti sopra, ora parliamo un po 'di più di cosa significa il punto di partenza della divergenza di prezzo.
Complessivamente ci saranno cinque modalità: Linea verticale, Mese,Settimana,, Giorno e Ora. Per la modalità Linea verticale, verrà aggiunta una linea verticale durante il caricamento dell'indicatore sul grafico. Trascinando questa linea si specifica la barra in cui i prezzi di tutti i simboli si incontreranno in un unico punto. Il prezzo di apertura della barra specificata per il simbolo corrente sarà considerato come un punto di riferimento di questo incontro. Qualsiasi altra modalità dirà al programma che ogni volta i prezzi dovrebbero soddisfare all'inizio del periodo specificato. Cioè all'inizio di ogni mese, all'inizio di ogni settimana, all'inizio di ogni giorno o all'inizio di ogni ora.
Di seguito è possibile trovare l'elenco dei parametri di input dell'indicatore:
//--- 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
I simboli sono numerato a partire da 2, poiché 1 è il simbolo corrente sul grafico.
L'inversione può essere applicata per ogni simbolo incluso. L'inversione significa che i dati del simbolo verranno visualizzati a testa in giù. Questo può essere utile, quando l'elenco dei simboli analizzati include coppie di valute in cui la stessa valuta (ad esempio, dollaro USA) può essere sia quella base che quella contatore. Ad esempio, nella coppia di valute EURUSD, il dollaro USA è la valuta del contatore e nella coppia di valute USDCHF è quella di base. Se il simbolo corrente sul grafico è EURUSD, allora puoi attivare l'inversione per USDCHF, ciò che renderà la rappresentazione dei prezzi più conveniente per l'analisi.
Di seguito è riportato l'elenco delle variabili e delle matrici globali:
//--- 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
Successivamente, considereremo le funzioni utilizzate durante l'inizializzazione dell'indicatore. In generale, non ci sono cambiamenti importanti rispetto alla funzione OnInit() dell'articolo precedente.
Aggiungiamo il controllo su dove viene utilizzato l'indicatore. Il punto è che attualmente gli sviluppatori del terminale non hanno implementato tutte le funzionalità di controllo delle proprietà del grafico in Strategy Tester, quindi limitiamo il nostro indicatore ad essere utilizzato solo da Strategy Tester. Per implementare questo, scriveremo una semplice funzione - CheckTesterMode(). Si troverà nel file 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); }
Un'altra nuova funzione SetBarsColors() ha lo scopo di impostare i colori per barre / candele giapponesi del simbolo corrente. Si trova nel file 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); } }
Durante l'inizializzazione, è necessario determinare quale modalità è selezionata nel parametro esterno StartPriceDivergence. Se è selezionata la linea Verticale, alla variabile globale timeframe_start_point verrà assegnato un valore predefinito, ovvero l'intervallo di tempo corrente. In caso contrario, verrà applicato l'intervallo di tempo selezionato. A tale scopo, scriviamo la funzione 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; } }
La funzione CheckInputParameters() a differenza di quella dell'articolo precedente ora è simile alla 1:
//+------------------------------------------------------------------+ //| 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); }
Gli array vengono inizializzati proprio come nell'articolo precedente. Sono stati modificati solo i nomi e il numero di matrici.
//+------------------------------------------------------------------+ //| 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; }
Sono state apportate modifiche significative alla funzione SetIndicatorProperties(). In realtà, questa è una funzione completamente nuova. Ora, a seconda della modalità di rendering dei dati selezionata, le proprietà corrispondenti vengono impostate durante l'inizializzazione.
//+------------------------------------------------------------------+ //| 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); }
E infine, un'altra nuova funzione SetDivergenceLine() da utilizzare in OnInit(). Imposta la linea verde verticale per manipolare il punto iniziale della divergenza di prezzo nella modalità Linea verticale.
//+------------------------------------------------------------------+ //| 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); }
Di seguito è riportata la rappresentazione di tutto ciò che è descritto sopra all'interno della funzione OnInit(). Quando tutto è diviso in funzioni e file separati, diventa molto comodo leggere il codice del programma.
//+------------------------------------------------------------------+ //| 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); }
Nella funzione OnCalculate(), il codice del programma è rimasto praticamente invariato. Nell'articolo precedente, dopo aver effettuato tutti i controlli sulla disponibilità dei dati, il programma ha prima riempito gli array ausiliari e solo dopo ha riempito i buffer degli indicatori con i dati preparati. Questa volta cercheremo di organizzare tutto in un unico ciclo.
Ho reso più rigorose le funzioni di convalida e caricamento dei dati. Ora ogni valore che si desidera ottenere passa attraverso il numero specificato di tentativi. Se si ottiene un valore, il ciclo viene interrotto. E poiché ora abbiamo modalità in cui dobbiamo determinare l'inizio di un periodo (mese, settimana, giorno, ora), quindi otterremo l'ora di inizio del periodo attraverso l'intervallo di tempo più alto. Pertanto, ho creato una funzione aggiuntiva simile a LoadAndFormData() che ha un nome simile a LoadAndFormDataHighTF(). Il suo codice è molto simile a quello originale, quindi non lo pubblicherò qui.
La verifica della disponibilità dei dati per i tempi correnti e superiori è stata implementata in un'unica funzione 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); }
La funzione FillIndicatorBuffers() è stata notevolmente complicata per l'attività corrente. Ciò è dovuto al fatto che ora ci sono diverse modalità e ognuna di esse richiede le proprie azioni. In effetti, tutto può essere diviso in quattro passaggi.
- Ottenere i dati per il simbolo specificato.
- Ottenere dati per un intervallo di tempo più elevato e determinare il tempo e il livello dei prezzi, in cui i prezzi di tutti i simboli si incontrano.
- Calcolo dei valori e riempimento del buffer dell'indicatore.
- Verifica dei valori calcolati.
Il codice funzione viene fornito con commenti dettagliati per la vostra considerazione:
//+------------------------------------------------------------------+ //| 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; } } }
Quando si studia la funzione sopra si dovrebbe notare un'altra funzione personalizzata SetBufferColorIndex(). Questa funzione imposta il colore nel buffer di colore dell'indicatore.
//+------------------------------------------------------------------+ //| 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; }
Una volta riempiti i buffer dell'indicatore, è necessario determinare il massimo e il minimo da tutti i valori attualmente visibili nella finestra del grafico. MQL5 permette di ottenere la prima barra visibile in una finestra del grafico e il numero di barre visibili. Trarremo vantaggio da queste funzionalità in un'altra funzione personalizzata CorrectChartMaxMin(). Il flusso di codice nella funzione può essere suddiviso in diversi passaggi:
- Determinazione dei numeri della prima e dell'ultima barre visibili.
- Determinazione del massimo e del minimo delle barre visibili per il simbolo corrente.
- Determinazione del massimo e del minimo tra tutte le matrici di simboli.
- Impostazione del massimo e del minimo nelle proprietà del grafico.
Di seguito è riportato il codice della funzione CorrectChartMaxMin() che si trova nel file Chart.mqh.
//+------------------------------------------------------------------+ //| 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(); }
La funzione sopra descritta verrà utilizzata durante l'elaborazione dell'evento di trascinamento della linea verticale (e quando si calcolano i valori dell'indicatore in OnCalculate, ovviamente):
//+------------------------------------------------------------------+ //| 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(); }
Tutte le funzioni sono pronte. Puoi studiare il codice completamente commentato allegato a questo articolo.
Dimostriamo cosa abbiamo alla fine ottenuto. I simboli predefiniti GBPUSD,, AUDUSD, NZDUSD, USDCAD, USDCHF sono specificati in parametri esterni. Nello screenshot qui sotto puoi vedere il grafico settimanale per EURUSD in modalità Linea verticale con inversione disabilitata:
Fig. 1 - Intervallo di tempo settimanale in modalità "Linea verticale"
Nello screenshot qui sotto puoi vedere l'intervallo di tempo M30 in modalità Giorno, ma questa volta l'inversione è abilitata per i simboli con USD come valuta di base. Nel nostro caso si tratta di USDCAD (candele azzurre) e USDCHF (candele viola).
Fig. 2 - Intervallo di tempo M30 in modalità "Giorno"
Conclusione
Penso che abbiamo creato uno strumento piuttosto interessante e informativo per l'analisi multi-valuta della divergenza dei prezzi. Questo indicatore può essere migliorato all'infinito.
Grazie per il vostro tempo!
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/754
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso