
Le MQL5 Cookbook : Développement d'un indicateur multi-symboles pour analyser la divergence des prix
Introduction
Dans cet article, nous examinerons le développement d'un indicateur multi-symboles pour analyser la divergence des prix dans une période de temps spécifiée. Les principaux sujets ont déjà été abordés dans l'article précédent sur la programmation d'indicateurs multi-devises du livre de recettes MQL5k : Développement d'un indicateur de volatilité multi-symboles dans MQL5. Cette fois, nous nous attarderons donc uniquement sur les nouvelles fonctionnalités et fonctions qui ont été radicalement modifiées. Si vous débutez dans la programmation d'indicateurs multi-devises, je vous recommande de lire dans un premier temps l'article précédent.
Dans cet article, nous examinerons les questions suivantes :
- Modification des propriétés du graphique.
- Gestion des événements CHARTEVENT_OBJECT_DRAG (faire glisser un objet graphique) et CHARTEVENT_CHART_CHANGE (redimensionner le graphique ou modifier les propriétés du graphique à l'aide de la fenêtre de dialogue des propriétés).
- Tampons d'indicateur de rendu utilisant plus d'une couleur.
- Définition des hauts et des bas dans les tampons d'indicateurs dans la zone de visibilité pour définir un graphique haut/bas.
- Inversion d'une série.
La quantité de code résultante pour notre indicateur est assez importante, environ 1500 lignes. Par conséquent, nous distribuerons toutes les fonctions dans des fichiers séparés et les lierons au fichier principal du projet. Il y aura trois catégories de fonctions pour les fichiers externes :
- Checks.mqh - Fonctions permettant d'effectuer diverses vérifications et de télécharger les données disponibles.
- Objects.mqh - Fonctions de gestion des objets graphiques.
- Chart.mqh - Fonctions de gestion des propriétés du graphique.
Toutes les fonctions qui n'appartiennent pas aux catégories ci-dessus seront laissées dans le fichier principal.
Développement de l'indicateur
Procédez ensuite à la programmation de l'indicateur. Nous devons d'abord créer un nouveau projet. Pour ce faire, dans le répertoire Metatrader 5\MQL5\Indicators, créez un dossier nommé comme notre indicateur, et dans celui-ci le dossier Inclus dans lequel nous placerons les fichiers d'inclusion. Ensuite, créez le fichier principal dans le dossier indicateur. Cela peut être fait manuellement en créant un fichier texte avec l'extension *.mq5 ou en utilisant l'assistant MQL5 Wizardpar modèle. En plus des fonctions de base du programme OnInit(), OnDeinit() et OnCalculate(), nous utiliserons également OnChartEvent() et OnTimer().
Tout comme dans l'article précédent, en plus du symbole actuel, nous afficherons les données de 5 symboles spécifiés dans les paramètres externes. Mais cette fois, au lieu de valeurs calculées par une formule, nous afficherons des données de prix brutes sur le graphique. L'utilisateur est libre de choisir le type de représentation des données dans les paramètres externes dans la liste déroulante : Ligne, Barres ou Chandeliers.
Si nous ne devions afficher les données que sous forme de lignes unicolores, il suffirait de spécifier le nombre de tampons égal au nombre de symboles dans les propriétés de l'indicateur (#property). Mais comme il existe deux modes pour dessiner des séries sous forme de barres et de chandelles, nous avons besoin de plus de tampons pour le mode bicolore : quatre tampons pour rendre chaque série et un tampon pour définir la couleur (selon la condition) pour chaque élément d'une série graphique .
Pour chaque série, il est nécessaire de spécifier les couleurs dans la section des propriétés du programme. Pour cela, il suffit de les lister en les séparant par des virgules. Vient d'abord la couleur utilisée en mode monochrome. En mode bicolore, il est utilisé pour les barres/bougeoirs hauts. La deuxième couleur ne sera utilisée qu'en mode bicolore pour les barres descendantes/Chandeliers.
Les codes de tous ces paramètres sont fournis ci-dessous :
#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'
Avec la directive#définir déclarons des constantes et en utilisant la ligne de commande #inclure, incluons des fichiers avec des fonctions qui ont déjà été décrites ci-dessus, et la classe de la bibliothèque Standard pour travailler avec le canevas :
//--- 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"
Ajoutez les énumérations ENUM_DRAWTYPE et ENUM_START_POINT pour créer des listes déroulantes qui permettent de sélectionner le type de dessin des données de prix et le mode du point de départ de divergence de prix dans les paramètres externes :
//--- 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 };
Les types de rendu des données sont déjà décrits ci-dessus, parlons maintenant un peu plus de ce que signifie le point de départ de la divergence des prix.
Au total, il y aura cinq modes : Ligne verticale, Mois ,Semaine, Jour et Heure. Pour le mode Ligne verticale, une ligne verticale sera ajoutée lors du chargement de l'indicateur sur le graphique. En faisant glisser cette ligne, vous spécifiez la barre où les prix de tous les symboles se rencontreront en un seul point. Le prix d'ouverture de la barre spécifiée pour le symbole actuel sera considéré comme un point de référence de cette réunion. Tout autre mode indiquera au programme que chaque fois les prix doivent se rencontrer au début de la période spécifiée. C'est-à-dire au début de chaque mois, au début de chaque semaine, au début de chaque jour ou au début de chaque heure.
Vous trouverez ci-dessous la liste des paramètres d'entrée de l'indicateur :
//--- 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
Les symboles sont numérotés à partir de 2, puisque 1 est le symbole actuel sur la carte.
L'inversion peut être appliquée pour chaque symbole inclus. L'inversion signifie que les données du symbole seront rendues à l'envers. Cela peut être utile lorsque la liste des symboles analysés comprend des paires de devises dans lesquelles la même devise (par exemple, le dollar américain) peut être à la fois celle de base et celle du compteur. Par exemple, dans la paire de devises EURUSD , le dollar américain est la devise de contrepartie, et dans la paire de devises USDCHF, c'est la devise de base. Si le symbole actuel sur le graphique est EURUSD, alors vous pouvez activer l'inversion pour USDCHF, ce qui rendra la représentation des prix plus pratique pour l'analyse.
Voici la liste des variables globales et des tableaux :
//--- 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
Ensuite, nous considérerons les fonctions utilisées lors de l'initialisation de l'indicateur. En général, il n'y a pas de changements majeurs par rapport à la fonction OnInit() de l'article précédent.
Ajoutons la vérification de l'endroit où l'indicateur est utilisé. Le fait est qu'actuellement, les développeurs de terminaux n'ont pas implémenté toutes les fonctionnalités de contrôle des propriétés du graphique dans Strategy Tester, nous limitons donc notre indicateur à être utilisé uniquement en dehors de Strategy Tester. Pour implémenter cela, nous écrirons une fonction simple - CheckTesterMode(). Il sera situé dans le fichier 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); }
Une autre nouvelle fonction SetBarsColors() est destinée à définir les couleurs des barres/chandeliers du symbole courant. Il se trouve dans le fichier 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); } }
Lors de l'initialisation, nous devons déterminer quel mode est sélectionné dans le paramètre externe StartPriceDivergence Si la ligne verticale est sélectionnée, alors la variable globale timeframe_start_point sera affectée avec la valeur par défaut, c'est-à-dire la période actuelle. Sinon, la période sélectionnée sera appliquée. Pour cela, écrivons la fonction 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; } }
Contrairement à celle de l'article précédent, la fonction CheckInputParameters() ressemble maintenant à ceci :
//+------------------------------------------------------------------+ //| 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); }
Les tableaux sont initialisés comme dans l'article précédent. Seuls les noms et le nombre de baies ont été modifiés.
//+------------------------------------------------------------------+ //| 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; }
Des modifications importantes ont été apportées à la fonction SetIndicatorProperties() En fait, il s'agit d'une fonction complètement nouvelle. Maintenant, selon le mode de rendu des données sélectionné, les propriétés correspondantes sont définies lors de l'initialisation.
//+------------------------------------------------------------------+ //| 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); }
Et enfin, une autre nouvelle fonction SetDivergenceLine() à utiliser dans OnInit(). Il définit la ligne verte verticale pour manipuler le point de départ de la divergence des prix en mode Ligne 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); }
Vous trouverez ci-dessous la représentation de tout ce qui est décrit ci-dessus dans la fonction OnInit() Lorsque tout est divisé en fonctions et fichiers séparés, il devient très pratique de lire le code du programme.
//+------------------------------------------------------------------+ //| 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); }
Dans la fonction OnCalculate(), le code du programme est resté pratiquement inchangé. Dans l'article précédent, une fois que toutes les vérifications de la disponibilité des données ont été effectuées, le programme a d'abord rempli les tableaux auxiliaires et ensuite seulement les tampons d'indicateurs avec les données préparées. Cette fois, nous allons essayer de tout organiser en une seule boucle.
J'ai rendu plus strictes les fonctions de validation et de chargement des données. Maintenant, chaque valeur que vous souhaitez obtenir passe par un nombre spécifié de tentatives. Si la valeur est obtenue, la boucle est arrêtée. Et puisque maintenant nous avons des modes où nous devons déterminer le début d'une période (mois, semaine, jour, heure), alors nous obtiendrons l'heure de début de la période via le délai le plus élevé. Par conséquent, j'ai créé une fonction supplémentaire similaire à LoadAndFormData() qui porte le même nom de LoadAndFormDataHighTF(). Son code est très similaire à celui d'origine, je ne le posterai donc pas ici.
La vérification de la disponibilité des données pour les périodes actuelles et supérieures a été implémentée dans une fonction 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 fonction FillIndicatorBuffers() a été considérablement compliquée pour la tâche en cours. Cela est dû au fait qu'il existe maintenant plusieurs modes et que chacun d'eux nécessite ses propres actions. En fait, tout peut être divisé en quatre étapes.
- Obtenir des données pour le symbole spécifié.
- Obtenir des données pour une période plus longue et déterminer l'heure et le niveau de prix, où les prix de tous les symboles se rencontrent.
- Calcul des valeurs et remplissage du tampon indicateur.
- Vérification des valeurs calculées.
Le code de fonction est fourni avec des commentaires détaillés pour votre considération :
//+------------------------------------------------------------------+ //| 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; } } }
Lorsque vous étudiez la fonction ci-dessus, vous devriez remarquer une autre fonction personnalisée SetBufferColorIndex(). Cette fonction définit la couleur dans le tampon de couleur de l'indicateur.
//+------------------------------------------------------------------+ //| 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; }
Une fois les tampons indicateurs remplis, nous devons déterminer le maximum et le minimum à partir de toutes les valeurs actuellement visibles dans la fenêtre graphique. MQL5 permet d'obtenir la première barre visible dans une fenêtre graphique et le nombre de barres visibles. Nous bénéficierons de ces fonctionnalités dans une autre fonction personnalisée CorrectChartMaxMin(). Le flux de code dans la fonction peut être divisé en plusieurs étapes :
- Détermination des numéros des premières et dernières barres visibles.
- Déterminer le maximum et le minimum de barres visibles pour le symbole actuel.
- Déterminer le maximum et le minimum parmi tous les tableaux de symboles.
- Définition du maximum et du minimum dans les propriétés du graphique.
Vous trouverez ci-dessous le code de la fonction CorrectChartMaxMin() dans le fichier 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 fonction décrite ci-dessus sera utilisée lors du traitement de l'événement de déplacement de la ligne verticale (et lors du calcul des valeurs de l'indicateur dans 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(); }
Toutes les fonctions sont prêtes. Vous pouvez étudier le code entièrement commenté joint à cet article.
Démontrons ce que nous avons finalement obtenu. Par défaut, les symboles GBPUSD, AUDUSD, NZDUSD, USDCAD, USDCHF sont spécifiés dans des paramètres externes. Sur la capture d'écran ci-dessous, vous pouvez voir le graphique hebdomadaire de l'EURUSD en mode Ligne verticale avec inversion désactivée :
Fig. 1 - Délai hebdomadaire en mode «Ligne verticale»
Sur la capture d'écran ci-dessous, vous pouvez voir le délai M30 en mode Jour, mais cette fois l'inversion est activée pour les symboles avec USDcomme devise de base. Dans notre cas, il s'agit de l'USDCAD (chandelles bleu clair) et de l'USDCHF(chandelles violettes).
Fig. 2 - Horaire M30 en mode «Jour»
Conclusion
Je pense que nous avons créé un outil assez intéressant et informatif pour l'analyse multi-devises de la divergence des prix. Cet indicateur peut être amélioré à l'infini.
Merci pour votre temps!
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/754





- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation