Affichage des articles dont le libellé est mql4. Afficher tous les articles
Affichage des articles dont le libellé est mql4. Afficher tous les articles

mardi 1 avril 2014

Les objets graphiques

MetaTrader nous permet de créer en manuel des objets graphiques pour nous aider dans l'analyse des cours. Ces objets sont divers et standardisés dans le monde du trading: des droites de tendances, des canaux, des lignes de supports ou résistances, des niveaux de Fibonacci, des angles de Gann, des marques d'ouverture de position, des annotations de texte, etc. Ces objets nous aident à visualiser, à analyser les cours et à gérer notre trading manuel. Tous ces objets graphiques peuvent être créés, modifiés et effacés par programmation avec le langage MQL4. Nous voyons une introduction à tout cela dans ce post.

Les types d'objets


Les objets sont de divers types et ces types sont relativement nombreux. la seule chose qu'on ne puisse pas créer, c'est un segment de ligne. Cela pourrait nous servir à tracer une courbe, mais ceci est déjà géré par MetaTrader, et uniquement par le logiciel quand on créé un indicateur. C'est mieux ainsi, car MetaTrader peut ainsi gérer la mémoire importante nécessaire et la rapidité d'exécution. Nous devons nous contenter de symboles,, de texte, de primitives géométriques et d'objets d'analyse technique des cours. Voici une liste non exhaustive avec les constantes prédéfinies MQL4 à utiliser pour les obtenir:
  • Le texte
    • Labels  OBJ_LABEL 
    • Textes  OBJ_TEXT 
    • Zones d'édition de texte  OBJ_EDIT 
    • Label Prix gauche  OBJ_ARROW_LEFT_PRICE 
    • Label Prix droite  OBJ_ARROW_RIGHT_PRICE 
  • Les primitives géométriques
    • Rectangles  OBJ_RECTANGLE 
    • Triangles  OBJ_TRIANGLE 
    • Ellipses  OBJ_ELLIPSE 
  • Les symboles
    • Stop  OBJ_ARROW_STOP 
    • Check  OBJ_ARROW_CHECK 
    • Main avec pouce vers le haut  OBJ_ARROW_THUMB_UP 
    • Main avec pouce vers le bas  OBJ_ARROW_THUMB_DOWN 
    • Achat  OBJ_ARROW_BUY 
    • Vente  OBJ_ARROW_SELL 
    • Flèche vers le haut  OBJ_ARROW_UP 
    • Flèche vers le bas  OBJ_ARROW_DOWN 
  • Les objets d'analyse technique
    • Ligne verticale de période  OBJ_VLINE 
    • Ligne horizontale de support ou résistance  OBJ_HLINE 
    • Ligne oblique de tendance  OBJ_TREND 
    • Ligne oblique de tendance définie par l'angle  OBJ_TRENDBYANGLE 
    • Cycles temporels  OBJ_CYCLES 
    • Canal équidistant  OBJ_CHANNEL 
    • Canal de déviation standard  OBJ_STDDEVCHANNEL 
    • Ligne de Gann  OBJ_GANNLINE 
    • Angles de Gann  OBJ_GANNFAN 
    • Grille de Gann  OBJ_GANNGRID 
    • Retracements de Fibonacci  OBJ_FIBO 
    • Zones temporelles de Fibonacci  OBJ_FIBOTIMES 
    • Angles de Fibonacci  OBJ_FIBOFAN 
    • Arcs de Fibonacci  OBJ_FIBOARC 
Et il y existe d'autres objets non cités ici. Nous ne les verrons pas tous dans ce post, seulement le label et comment les objets sont créés et gérés.

Le système de coordonnées des graphiques


Avant toute chose, il faut préciser le système de coordonnées utilisé dans les graphiques. Un graphique en trading est temporel, c'est-à-dire que les abscisses sont exprimées en temps: du type datetime. En horizontal nous avons le temps. Pour les ordonnées, on se place verticalement en fonction du cours, c'est-à-dire du prix du sous-jacent (ici pour le forex, c'est le taux de conversion de la paire de devises). C'est donc un nombre décimal, du type double. Il y a certains objets qui ne doivent pas être placés sur un temps et un prix précis, mais de manière fixe sur la fenêtre, de sorte qu'on la voit toujours. Un temps ou un prix pouvant être hors affichage. On va d'ailleurs commencer à utiliser de tels objets.

Les sous-graphiques


Autre détail important: un graphique peut posséder des sous graphiques pour y placer des indicateurs. Un indicateur pouvant se placer dans le graphique principal de la paire de devises, ou bien dans une fenêtre à part, qui est sous-fenêtre de la principale. Ces fenêtres sont numérotées dans leur ordre d'apparition: 0 pour la principale, 1 pour la première sous-fenêtre, 2 pour la deuxième, etc.

La création d'objets


Créer un objet graphique se fait à l'aide d'une fonction prédéfinie en MQL4:  ObjectCreate() . Cette fonction retourne un booléen (type bool), c'est-à-dire vrai ou faux pour dire si la création de l'objet a réussi ou non. Depuis la nouvelle version du langage MQL4 mise à jour récemment en février 2014, il y a deux versions de cette fonction, (elle est surchargée, pour les connaisseurs): il y a une version où on indique la référence de la fenêtre principale où on veut créer l'objet, puis le numéro de la sous-fenêtre (commençant à zéro), et une version où on n'indique pas la fenêtre principale car c'est celle où est exécutée le programme (indicateur ou expert advisor). Seule la deuxième était existante avant.
Il faut lui fournir de nombreux arguments et dans cet ordre:
  •  long identifiantDeGraphique : C'est le pointeur de la fenêtre pour identifier la fenêtre principale sur laquelle on veut créer l'objet et qui donc peut être différente de celle où le programme fonctionne. Paramètre nouveau et optionnel.
  •  string nomDeLobjet : Nom de l'objet à créer. Il faut évidemment lui donner un nom pour pourvoir ensuite le modifier ou le supprimer.
  •  ENUM_OBJECT typeDeLobjet : On utilise ici une constante précédemment listée.
  •  int noSousFenetre : Le numéro de la sous-fenêtre graphique dans laquelle créer l'objet. 0 pour la principale, 1 pour la première si elle existe, 2 pour la deuxième si elle existe, etc. La fenêtre principale est celle où est exécutée le programme ou bien celle désignée par l'identifiant de fenêtre en option.
  •  datetime premiereCoordonneeTemps : l'horodatage du premier point qui place l'objet.
  •  double premiereCoordonneePrix : position veticale (prix) du premier point qui place l'objet.
  • On peut répéter ces deux coordonnées jusqu'à trois fois pour la vieille version de la fonction, parce qu'il peut falloir jusqu'à trois points pour définir la position, l'orientation et la taille de l'objet sur le graphique. Mais toujours par paire. Avec la nouvelle version, lorsqu'on précise une fenêtre par son identifiant, on n'est plus limité à trois, mais autant de points qu'on veut.
On créé ainsi un objet, mais sans préciser de contenu, car beaucoup n'en n'ont pas besoin. Ceux du type texte ou label, on besoin qu'on leur ajoute un contenu texte. Ce sera fait avec une modification de l'objet dans le paragraphe suivant. Une précision supplémentaire: les objets label, ne prennent pas en compte les coordonnées fournies à la création car un label va rester fixe par rapport à l'affichage, alors qu'un point placé sur un horodatage précis pourra ne pas être vu si l'horodatage est en dehors de l'intervalle affiché, de même pour la coordonnée verticale du prix. Un label devant être toujours affiché, il faut lui préciser dans un second temps des coordonnées en pixels par rapport à un coin de la fenêtre. Ce sera fait avec la fonction de modification.

La modification d'objets


Pour modifier un objet, c'est pas compliqué, il suffit d'utiliser la fonction spécialement prévue à cet effet:  ObjectSet() . Cette fonction change une propriété de l'objet. On doit lui donner le nom de l'objet à modifier, la propriété à changer sous forme de nombre entier soit une constante prédéfinie et la nouvelle valeur sous forme de nombre décimal. Elle retourne un booléen vrai ou faux pour dire si la modification a réussi ou pas. Les propriétés sont désignées par ces constantes (liste non exhaustive):
  •  OBJPROP_TIME1  horodatage du premier point de positionnement
  •  OBJPROP_PRICE1  prix du premier point de positionnement
  •  OBJPROP_COLOR  couleur de l'objet
  •  OBJPROP_STYLE  style de traçage pour les lignes uniquement
  •  OBJPROP_WIDTH  epaisseur du trait pour les lignes uniquement
  •  OBJPROP_ANGLE  angle de l'objet
  •  OBJPROP_FONTSIZE  taille de la fonte (police) pour les objet avec du texte.
  •  OBJPROP_CORNER  coin de la fenêtre origine de coordonnées pour placer le label (objets label uniquement), c'est un numéro de 0 à 3 pour les quatre coins du graphique.
  •  OBJPROP_XDISTANCE  position horizontale en pixels pour placer l'objet de manière fixe par rapport à la fenêtre, et non pas par rapport au temps et au prix. (pour les labels)
  •  OBJPROP_YDISTANCE  position verticale en pixels. (même commentaire)
Il y en a bien d'autres, voir la documentation fournie, dans l'aide de MetaEditor. On peut aussi simplement obtenir la valeur d'une propriété d'un objet avec la fonction  ObjectGet()  à qui on fournit le nom de l'objet et la propriété voulue et qui nous retourne sa valeur.
Nous allons dans le prochain post créer des labels, à qui il faut évidemment fournir un texte, et la fonction  ObjectSet()  ne peut le faire car la valeur transmise n'est pas un texte, mais est numérique. Il y a pour cela une fonction spéciale pour fixer le texte d'objets contenant du texte:  ObjectSetText() . On doit lui passer le nom de l'objet, le texte à mettre et en option car il y a des valeurs par défaut: la taille de la fonte (0 par défaut pour ne pas la changer), le nom de la fonte à utiliser (chaine de caractères pour désigner une police de caractères, par défaut NULL) et la couleur du texte (par défaut: clrNone). Elle retourne un booléen pour dire si la mise à jour du texte à réussi.

La suppression d'objets


Enfin, lors de la déinitialisation, il faut supprimer les objets créés pour libérer de la place sur le graphique. Il y a deux fonctions pour cela:  ObjectDelete()  qui prend un nom d'objet en argument et qui efface seulement celui-là. La fonction  ObjectsDeleteAll()  qui prend en option un numéro de sous-fenêtre et/ou un type d'objet pour se limiter à une sous-fenêtre et/ou un type d'objets et qui les efface tous. Ces deux fonctions retournent le nombre d'objets effacés et existent aussi maintenant en version avec la référence de la fenêtre principale en premier argument pour viser une fenêtre autre que celle sur laquelle le programme s'exécute.

Voilà pour une présentation qui permet de démarrer l'utilisation des objets graphiques, nous allons commencer dès le prochain post un fichier include d'outils pour les objets graphiques. Des fonctions pour les créer et les gérer et on commencera avec les labels.

vendredi 28 mars 2014

Test du fichier include d'outils pour les indicateurs

Dans le post précédent nous avons créé un fichier include avec des fonctions d'initialisation d'indicateur et d'index. Le résultat de ces fonctions ne peut pas être analysé par un programme, d'ailleurs les fonctions ne retournent rien, elles sont du type void. Le résultat ne peut être que visuel, et il faut en plus initialiser plusieurs fois l'indicateur avec des paramètres différents pour voir comment il réagit.

Une petite précision: il y a eu de gros changements dans MQL4 avec une mise à jour récente. Les nouveautés seront en partie utilisées et d'autres non utilisées pour l'instant car je ne les ai pas testées. Notamment les arguments fournis à la fonction  OnCalculate()  qui font double emploi avec les variables prédéfinies du même nom mais avec une majuscule. Ces arguments sont présents pour une meilleure compatibilité avec le langage C et MQL5. L'argument &spread[] m'a étonné, alors que jusqu'à présent les spreads des périodes n'étaient pas disponibles. En réalité, il est présent, au cas où le broker les fournisse, ce qui a peu de chances de se produire. L'argument rates_total serait équivalent à Bars et l'argument prev_calculated à  IndicatorCounted() . Je mets bien ça au conditionnel car je ne connais pas la gestion interne de ces arguments par MetaTrader, notamment vis-à-vis de la gestion mémoire. Il y aurait une différence car on doit désormais retourner à la fin de nos calculs le nombre de valeurs calculées, pour que Metatrader le donne à nouveau lors de l'appel suivant dans l'argument prev_calculated. Il faudra tester tout ceci à l'avenir. Pour le moment nous utiliserons toujours les anciennes méthodes et toute l'armada très «dirty code» (code sale) des arguments ne nous servira pas. (les variables globales sont aussi du code sale, mais elles ne gênent pas la lisibilité au moins !) Nous pourrions tout simplement utiliser encore la fonction  start() , mais on va commencer à s'habituer aux nouveautés juste avec  OnCalculate() . On constate aussi, que des paramètres des indicateurs et des index peuvent être fixés par des  #property  au début du code. Cela n'empêche en rien de les déclarer à nouveau par nos fonctions d'initialisation, d'autant plus qu'ils n'y sont pas tous et que cela permet de les modifier à la volée lors de l'initialisation, ce que ne permet pas les  #property .

Un signal d'index vite fait pour remplir l'historique:


Vite fait, c'est vite dit ! Nous allons utiliser le signal d'un indicateur déjà présent dans MetaTrader. Donc dans la boucle principale de l'indicateur nous récupérons simplement la valeur d'un indicateur de moyenne mobile  iMA()  et nous la mettons dans notre tableau d'historique. Pour rappel, nous devons à chaque appel de la fonction  OnCalculate()  (on utilisait avant  start()  et on peut toujours) parcourir toutes les périodes à recalculer comme le demande MetaTrader. Le nombre de périodes total dans l'historique est donné par  Bars  et le nombre de périodes depuis le début de l'historique à ne pas recalculer est donné par  IndicatorCounted (). Donc le nombre de périodes à traiter est  Bars - IndicatorCounted()  et le numéro de la première période à traiter est alors  Bars - IndicatorCounted - 1  car la numérotation des cases du tableau est décalée d'une unité. Un schéma vaut mieux qu'un long discours:
tableau d'historique
supposons un historique de 10 périodes au total et 2 périodes déjà calculées
9876543210
Bars - 1  =  10 - 1  =  9       et       Bars - IndicatorCounted - 1  =  10 - 2 - 1  =  7
On commence bien à traiter la période n°7 qui est la première à devoir être calculée!

 Dans notre code nous calculons donc le numéro de la première période à traiter et nous commençons la boucle for à ce numéro et on la continue en décrémentant (diminuant) de 1 à chaque passage le numéro de la période. La boucle s'arrête selon les nécessités des calculs des indicateurs au n°1 ou 0. A chaque passage on récupère la valeur de la moyenne mobile et on l'affecte à la case correspondante de notre tableau, c'est tout ! Pour les besoins de calculs de cet indicateur nous ne devons pas commencer les calculs dès le début de l'historique, puisque pour faire une moyenne mobile de 12 périodes par exemples, il nous faut 12 périodes précédentes. On commence donc à  Bars-1-nbPeriodesMoyenne . Le code de l'indicateur de test est simple:
  1. Initialisation du nombre de périodes à traiter
  2. Si ce nombre est supérieur à  Bars-nbPeriodesMoyenne-1  alors fixer le début à cette valeur.
  3. Sinon fixer le début  nbPeriodesATraiter-1 
  4. Initialiser la boucle For
    1. récupérer la moyenne mobile de l'indicateur déjà existant dans MetaTrader
    2. mettre cette valeur dans notre tableau d'historique.
  5. C'est nouveau: retourner  rates_total  !
Voici le code:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[]) {
   double moyenneMobile;
   int noPeriode, noPeriodeDebut;
   int nbPeriodeATraiter = Bars - IndicatorCounted();
   int noPeriodeLimiteCalcul = Bars - nbPeriodesMoyenne - 1;
   if(nbPeriodesATraiter>noPeriodeLimiteCalcul) {
      noPeriodeDebut = noPeriodeLimiteCalcul;
   }
   else { noPeriodeDebut = nbPeriodesATraiter - 1; }
   for(noPeriode=noPeriodeDebut ; noPeriode>=0 ; noPeriode--) {
      moyenneMobile = iMA(NULL, 0, nbPeriodesMoyenne, shift,
                           MODE_SMA, PRICE_MEDIAN, noPeriode);
      bufferMoyenne[noPeriode] = moyenneMobile;
   }
   return(rates_total);
}
Quelques commentaires: variables colorées en rouge sont des variables globales qui servent de paramètre à l'indicateur. Elles sont déclarées désormais input au début du code au lieu de extern jusqu'à la récente mise à jour du langage. Ces paramètres externes permettent de modifier des valeurs pour le programme lorsqu'on le lance sur un graphique. Il suffit de les modifier dans la fenêtre qui s'ouvre, dans l'onglet Paramètres. Autre détail, de taille ! Tous ces arguments à la fonction ! Ils font double emploi avec les variables prédéfinies, et nous ne les utilisons pas pour l'instant. Nous devons impérativement retourner cette valeur désormais. La fonction iMA nous donne la moyenne mobile, et elle a besoin de nombreux paramètres que nous expliquerons dans un post ultérieur. Cette moyenne, à peine récupérée, elle est mise dans notre buffer: C'est le seul traitement que nous faisons dans cet indicateur, car le but n'est pas de calculer une valeur, mais bien de vérifier si nos fonctions réalisent bien leur travail.

Tester l'initialisation de l'indicateur:


La fonction d'initialisation d'indicateurs effectue deux choses: fixer le nombre de décimales et donner le nom court de l'indicateur. Nous allons passer des valeurs à la fonction, en l'occurrence seulement le nom court, et vérifier que l'indicateur la prend bien en compte. Il faudra fournir ce nom en paramètre externe, c'est-à-dire fixé lors de l'initialisation de l'indicateur dans la fenêtre qui s'ouvre. Et il faudra constater le changement sur l'indicateur. Il faudra aussi vérifier que le nombre de décimales de l'indicateur est bien celui de la paire. La plupart des brokers travaillent avec 5 décimales désormais. On va donc tester sur une paire à 5 décimales très probablement et on le fera sur une paire avec le yen qui n'en a que deux ou trois.
En résumé, nous devons choisir un nom en paramètre externe de l'indicateur et vérifier qu'il apparait bien dans le menu contextuel que nous donne MetaTrader avec un clic droit sur un signal de cet indicateur. Nous devons aussi tester cet indicateur sur un paire sans le Yen et une paire avec le Yen et constater dans l'infobulle de MetaTrader lorsqu'on pointe sur le signal graphique qu'il y a 5 décimales pour une paire sans le Yen et 2 ou 3 décimales pour une paire avec le Yen.
Sans oublier la déclaration du paramètre externe nomCourtIndicateur avec sa valeur par défaut, ni oublier l'inclusion du fichier d'outils Indicateurs, la ligne de code de l'initialisation est simplement dans la fonction  OnInit() :

input string nomCourtIndicateur="indicateur-test";

#include <OutilsIndicateurs.mqh>

int OnInit() {
   initIndicateur(nomCourtIndicateur);
}
Le paramètre externe du nom court de l'indicateur en rouge est déclaré avec une valeur par défaut mais pourra être modifié à la volée lors de l'initialisation de l'indicateur voire même après. Un paramètre externe est simplement une variable que MetaTrader présente dans la fenêtre d'initialisation pour qu'on puisse la modifier quand on utlise le programme. Avec la nouvelle version du langage, elles sont déclarées en tant qu'entrées du programme (input) et non plus comme variables externes. C'est juste un changement de dénomination, ça reste la même chose. Maintenant l'avantage, c'est qu'elles sont colorées en rouge par l'éditeur.

Tester l'initialisation d'un index:


Pour initialiser un index, notre fonction transmet de nombreux paramètres à MetaTrader, dont la plupart en valeur par défaut. Nous transmettrons le numéro de l'index et le tableau qui lui sert de buffer et le simple fait que MetaTrader trace le signal suffit à confirmer que ça fonctionne car ces deux éléments sont primordiaux pour faire fonctionner l'indicateur, sans ça, pas de signal.

Nous utiliserons le décalage traditionnellement appelé shift dans Metatrader en tant que paramètre externe pour le décalage des périodes de l'indicateur par rapport à l'historique du cours. Comme ça nous pourrons le modifier et voir le décalage modifié en direct.
input int shift=0;
Concernant le début du traçage, nous allons aussi utiliser un paramètre externe pour le modifier à la volée, mais il nous faut savoir combien il y a de périodes dans l'historique car nous risquons sinon de chercher longtemps le début de l'historique. Eh oui ! il y en a beaucoup dans l'historique. Nous allons donc faire afficher à l'initialisation le nombre de périodes dans l'historique pour savoir quel nombre mettre afin de voir le début du traçage vers la fin de l'historique:
input int debutTracage=0;
Print("nombre de périodes dans l'historique: ", Bars);
Il y a ensuite le nom donné à l'index, celui-ci apparaitra dans les infobulles de MetaTrader, il suffit de lui en donner un par une constante déclarée au début que nous concatènerons pendant l'initialisation avec le nombre de périodes de calculs de la moyenne elle aussi en paramètre externe (concaténer signifie mettre les chaines de caractères bout à bout):
input int nbPeriodesMoyenne=12;

string NOM_INDEX = "moy-mobile-";

string nomIndex;

dans OnInit() nomIndex = StringConcatenate(NOM_INDEX, nbPeriodesMoyenne);
Puis vient le tour des caractéristiques de traçage, dont la couleur. Première chose, ces caractéristiques sont aussi définies de manière statique par des  #property  au début du code, et c'est l'assitant de création de nouveau fichier qui nous l'a mis. Le problème, c'est qu'avec ces définitions, statiques, nos tentatives de définitions des caractéristiques de manière dynamique dans le code échoueront et même il n'y aura pas de traçage. Nous devons donc supprimer ces définitions statiques car nous les définissons de manière dynamique avec notre fonction d'initialisation d'index. Dans l'appel de la fonction d'initialisation d'index, nous mettons simplement le nom de la couleur nouvellement défini dans la mise à jour du langage: les noms de couleur commencent tous par le préfixe «clr». Ici nous mettons  clrBlue .

La même chose pour le style, le mode de traçage et l'épaisseur du trait qu'on définit en paramètres externes histoire de les changer un peu pour voir le résultat.
input int styleDeTracage=STYLE_SOLID;
input int modeDeTracage=DRAW_LINE;
input int epaisseurDuTrait=EMPTY;

Dernier paramètre et c'est celui qui a le moins de chances d'être modifié (c'est pour ça qu'il est placé en dernier): la valeur signifiant «pas de valeur calculée». C'est la valeur vide de l'indicateur. Quand elle est présente dans une case du tableau d'historique de l'index, elle signifie pour MetaTrader qu'aucune valeur n'est calculée pour cette période, et MetaTrader ne trace rien à cette période. Normalement on y place la valeur  EMPTY_VALUE  mais on peut la modifier. Nous la mettons en paramètre externe, avec sa valeur par défaut, mais lorsque l'indicateur fonctionnera on y placera une valeur particulière qui est utilisée sur le moment dans l'indicateur pour voir que MetaTrader ne trace plus lorsqu'il y a cette valeur. (voir la vidéo pour bien comprendre)
input double valeurNonCalculee=EMPTY_VALUE;

Le tutoriel et le code de test:




//+------------------------------------------------------------------+
//|                                        testsOutilsIndicateurs.mq4|
//|                   Copyright 2014, argent-facile-avec-robots-forex|
//|                     http://argent-facile-robots-forex.blogspot.fr|
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"
#property version   "1.00"
#property strict
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots 1
//--plot moyenne
//#property indicator_label1 "moyenne"
//#property indicator_type2 DRAW_LINE
//#property indicator_color1 clrRed
//#property indicator_style1 STYLE_SOLID
//#property indicator_witdh1 1

//+------------------------------------------------------------------+
//| Tampons d'historique des index                                   |
//+------------------------------------------------------------------+

double bufferMoyenne[];

//+------------------------------------------------------------------+
//| Paramètres externes                                              |
//+------------------------------------------------------------------+

input string nomCourtIndicateur="indicateur-test";
input int shift=0;
input int debutTracage=0;
input int nbPeriodesMoyenne=12;
input int styleDeTracage=STYLE_SOLID;
input int modeDeTracage=DRAW_LINE;
input int epaisseurDuTrait=EMPTY;
input double valeurNonCalculee=EMPTY_VALUE;

//+------------------------------------------------------------------+
//| Includes                                                         |
//+------------------------------------------------------------------+

#include <OutilsIndicateurs.mqh>

//+------------------------------------------------------------------+
//| Constantes globales                                              |
//+------------------------------------------------------------------+

string NOM_INDEX = "moy-mobile-";

//+------------------------------------------------------------------+
//| Variables globales                                               |
//+------------------------------------------------------------------+

string nomIndex;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

int OnInit() {
   nomIndex = StringConcatenate(NOM_INDEX, nbPeriodesMoyenne);
   Print("nombre de périodes dans l'historique: ", Bars);
   initIndicateur(nomCourtIndicateur);
   initIndex(0, bufferMoyenne, shift, debutDuTracage, nomIndex, clrBlue,
            styleDeTracage, modeDeTracage, epaisseurDuTrait, valeurNonCalculee);
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[]) {
   double moyenneMobile;
   int noPeriode, noPeriodeDebut;
   int nbPeriodeATraiter = Bars - IndicatorCounted();
   int noPeriodeLimiteCalcul = Bars - nbPeriodesMoyenne - 1;
   if(nbPeriodesATraiter>noPeriodeLimiteCalcul) {
      noPeriodeDebut = noPeriodeLimiteCalcul;
   }
   else { noPeriodeDebut = nbPeriodesATraiter - 1; }
   for(noPeriode=noPeriodeDebut ; noPeriode>=0 ; noPeriode--) {
      moyenneMobile = iMA(NULL, 0, nbPeriodesMoyenne, shift,
                           MODE_SMA, PRICE_MEDIAN, noPeriode);
      bufferMoyenne[noPeriode] = moyenneMobile;
   }
   return(rates_total);
}

//+------------------------------------------------------------------+

vendredi 7 mars 2014

Un fichier include .mqh d'outils pour les indicateurs

Après un fichier de fonctions pour les calculs temporels et les calculs sur les cotations, que nous étofferons par la suite, nous allons faire un fichier pour nous faciliter la tache lors de la création d'indicateurs. Il n'y aura que deux fonctions pour l'instant, très simples algorithmiquement, elles nous soulageront des nombreuses lignes de codes à répéter pour créer des indicateurs. Et les valeurs par défaut nous éviteront de les préciser à chaque fois.

Ce que fait un indicateur:


Un indicateur va chercher les valeurs du cours dans l'historique et fait des calculs pour donner un résultat chiffré par période. Ce résultat doit être stocké dans un historique spécialement créé pour l'indicateur, et MetaTrader se sert de cet historique pour tracer une courbe ou un histogramme correspondant à l'évolution de notre valeur calculée. La courbe ainsi créée se rajoute au cours sur le graphique pour nous donner l'évolution de notre valeur. C'est cette valeur qui va nous donner des indications pour trader, ou bien qui va servir à des experts advisors pour prendre des décisions.

Les initialisations nécessaires


L'historique de cet indicateur est simplement un tableau, dont la taille est gérée par MetaTrader. Nous n'avons qu'à déclarer son existence et le remplir. MetaTrader doit, pour effectuer ce travail, être averti de certaines choses. Pour l'indicateur globalement:
  • Donner un nom à l'indicateur.
  • Fixer la précision de l'indicateur: le nombre de décimales.
Dans chaque indicateur il peut y avoir jusqu'à huit valeurs basées sur huit calculs différents avec chacune leur historique. Chaque valeur calculée avec son historique est appelée un index. Pour chacun des huit index possibles, il faut:
  • Préciser le tableau à utiliser pour l'historique.
  • Fixer le décalage entre les périodes de l'indicateur et celle du cours.
  • Fixer le style graphique pour le traçage de la valeur.
  • Préciser la période de début de traçage.
  • Préciser le label à afficher pour l'index.
  • Préciser la valeur signifiant aucune donnée pour la période.

Une fonction d'initialisation de l'indicateur


Nous devons préciser à MetaTrader le nombre de décimales que l'indicateur doit fournir, en général, le même nombre de décimales que celui de la paire sur laquelle il s'exécute. Nous utilisons donc la fonction  MarketInfo()  qui va nous donner cette information grâce aux paramètres  Symbol()  qui donne le nom de la paire de devises courante. (sur laquelle on est) et  MODE_DIGITS  qui est un code signifiant à la fonction  MarketInfo()  qu'on veut le nombre de décimales (digits). Et ce nombre de décimales est fourni à la fonction  IndicatorDigits()  qui va le fixer pour notre indicateur.

string paireCourante = Symbol();
int nombreDecimales = MarketInfo(paireCourante, MODE_DIGITS);
IndicatorDigits(nombreDecimales);

On doit également donner un nom court à l'indicateur, utilisable par MetaTrader notamment pour l'afficher dans les graphiques et pour être appelé par un expert advisor. Ce nom on le donne à la fonction IndicatorShortName() qui va le fixer pour cet indicateur.

IndicatorShortName(nomCourt);

La fonction d'initialisation de l'indicateur reçoit alors juste le nom court de l'indicateur et exécute les lignes de code précédentes. Elle ne retourne aucune valeur, elle est donc du type void:

void initIndicateur(string nomCourt) {
   string paireCourante = Symbol();
   int nombreDecimales = MarketInfo(paireCourante, MODE_DIGITS);
   IndicatorDigits(nombreDecimales);
   IndicatorShortName(nomCourt);
}

Une fonction d'initialisation d'un index


L'initialisation d'un index doit indiquer le numéro de l'index sur les huit possibles: de 0 à 7. Il faut donc transmettre ce numéro en argument à la fonction et le fournir à toutes les fonctions que nous allons appeler pour paramétrer cet index.
Nous devons déclarer le tableau qui nous sert d'historique pour l'indicateur, il y a en au moins un. Nous pouvons en créer jusqu'à huit par indicateur. Pour ces déclarations, nous ne précisons pas la taille du tableau, MetaTrader s'en chargera. Cette déclaration de tableau sera faite manuellement pour chaque indicateur, car nous ne pouvons pas retourner un tableau avec une fonction, donc pas de déclaration de ces tableaux dans une fonction. Nous devrons par contre passer le tableau à la fonction et par référence (pas de copie) pour qu'elle puisse passer cette référence à MetaTrader qui saura alors quel tableau utiliser pour l'historique. C'est la fonction SetIndexBuffer() qui transmet la référence.

SetIndexBuffer(noIndex, tamponHistorique);

Nous devons ensuite régler le décalage de l'indicateur par rapport aux périodes des courts. C'est-à-dire le décalage voulu entre la valeur calculée pour une période et la période qu'elle représente. Normalement il n'y en a pas, on calcule une valeur pour une période et cette valeur ressort quand on demande cette période précisément. Mais on peut vouloir décaler toutes les valeurs de chaque période pour avancer ou reculer le signal par rapport aux cotations de la paire. Ce décalage est fourni en argument, sans valeur par défaut, car elle peut être modifiée à chaque initialisation de l'indicateur. On doit donc la transmettre à chaque fois:

SetIndexShift(noIndex, decalagePeriodes);

Le calcul d'une valeur d'index pour une période, demande presque tout le temps d'utiliser les cours antérieurs à la période calculée. Or pour la toute première période et les quelques suivantes de l'historique, il n'y a pas suffisamment de cotations antérieures pour faire ce calcul. On ne doit donc pas tracer l'indicateur pour ces toutes premières périodes car s'il on y calcule des valeurs, elles ne seront pas bonnes par manque de cotations antérieures. On peut préciser à MetaTrader de ne pas tracer l'index pour un certain nombre de périodes au début de l'historique:

SetIndexDrawBegin(noIndex, debutTracage);

Pour afficher le nom de l'index lorsqu'on pointe dessus avec la souris, ou pour avoir un intitulé dans la fenêtre de description, on peut assigner un label à un index:

SetIndexLabel(noIndex, intitule);

Ensuite, on peut, et c'est conseillé de donner un style de traçage à l'index. On peut modifier
  • le mode (ligne, section, histogramme, flèches, zigzag, remplissage ou aucun traçage).
  • le style du tracé (continu, traits interrompus, points, trait-point et trait-point-point).
  • l'épaisseur du trait en pixel (de 1 à 5) ou EMPTY pour aucun changement d'épaisseur.
  • la couleur (clrNONE pour aucun changement de couleur).

SetIndexStyle(noIndex, modeTracage, style, epaisseur, couleur);

Pour finir, il faut indiquer la valeur vide à utiliser pour signaler à MetaTrader qu'il n'y a pas de valeur calculée dans une période, lui indiquant ainsi qu'il ne faut pas tracer pour cette période. Cette valeur vide par défaut pour chaque index est  EMPTY_VALUE . On la reçoit en argument avec cette valeur par défaut.

SetIndexEmptyValue(noIndex, valeurVide);

La fonction d'initialisation de l'index reçoit tous ces paramètres et exécute les lignes de code précédentes. Elle ne retourne aucune valeur, elle est donc du type void:

void initIndex(int noIndex, double &tamponHistorique[], int decalagePeriodes,
               int debutTracage=0, string intitule="",
               color couleur=clrNONE, int style=STYLE_SOLID,
               int modeTracage=DRAW_LINE, int epaisseur=EMPTY,
               double valeurVide=EMPTY_VALUE) {
   SetIndexBuffer(noIndex, tamponHistorique);
   SetIndexShift(noIndex, decalagePeriodes);
   SetIndexDrawBegin(noIndex, debutTracage);
   SetIndexLabel(noIndex, intitule);
   SetIndexStyle(noIndex, modeTracage, style, epaisseur, couleur);
   SetIndexEmptyValue(noIndex, valeurVide);
}

Le tutoriel vidéo et le code complet de l'include




Voici le fichier include «OutilsIndicateurs.mqh» que nous créons dans sa première version:

//+------------------------------------------------------------------+
//|                                            OutilsIndicateurs.mqh |
//|                  Copyright 2014, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, argent-facile-avec-robots-forex"
#property  link  "http://argent-facile-avec-robots-forex.blogspot.fr"
#property  strict
//+------------------------------------------------------------------+
//| Defines                                                          |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Constantes                                                       |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Fonctions d'initialisation                                       |
//+------------------------------------------------------------------+

void initIndicateur(string nomCourt) {
   string paireCourante = Symbol();
   int nombreDecimales = MarketInfo(paireCourante, MODE_DIGITS);
   IndicatorDigits(nombreDecimales);
   IndicatorShortName(nomCourt);
}

void initIndex(int noIndex, double &tamponHistorique[], int decalagePeriodes,
               int debutTracage=0, string intitule="",
               color couleur=clrNONE, int style=STYLE_SOLID,
               int modeTracage=DRAW_LINE, int epaisseur=EMPTY,
               double valeurVide=EMPTY_VALUE) {
   SetIndexBuffer(noIndex, tamponHistorique);
   SetIndexShift(noIndex, decalagePeriodes);
   SetIndexDrawBegin(noIndex, debutTracage);
   SetIndexLabel(noIndex, intitule);
   SetIndexStyle(noIndex, modeTracage, style, epaisseur, couleur);
   SetIndexEmptyValue(noIndex, valeurVide);
}

//+------------------------------------------------------------------+
Et voilà de quoi nous soulager pour programmer des indicateurs ! Nous allons évidemment tester ce code dès le prochain post pour garantir que le tout fonctionne !

dimanche 23 février 2014

Le numéro d'une période (bar) en MQL4 partie 2 / 2

Suite du post précédent, nous allons réaliser une fonction qui donne le numéro d'une période selon le calcul (très réfléchi) que je vous ai présenté précédemment. Comme je l'ai expliqué, nous n'utilisons pas la valeur de la variable prédéfinie Bars car cette valeur qui est sensée augmenter de 1 à chaque nouvelle période, peut ne pas augmenter pour des raisons de gestion de mémoire, voire elle peut même diminuer brutalement. Il y a donc des bugs potentiels, latents dans cette façon de faire (et que beaucoup utilisent). Nous utiliserons donc l'horodatage fourni avec la cotation par le serveur, là, au moins il n'y a pas de risque d'arrêt du temps ou de retour dans le passé 

Une fonction donnant un numéro de période:


Pour rappel, pour obtenir le numéro d'une période depuis l'epoch, nous faisons la division euclidienne, c'est-à-dire, prendre la partie entière du résultat de la division de l'horodatage (nombre de secondes depuis l'epoch) par le nombre de secondes dans une période. Et pour cela on exploite les subtilités du langage MQL4 en effectuant simplement la division entre des nombres entiers, ce qui nous donne un résultat entier. Notre fonction sera construite de la même manière que les deux précédentes, et autour de ce calcul. Nous recevons donc en argument obligatoire un horodatage et en argument optionnel le nombre de secondes dans la période voulue, et ce nombre doit être positif. Nous l'initialisons à zéro avec la constante  NULL . La valeur de l'argument ne doit pas être négative. Dans le cas contraire, on ne fera pas le calcul et nous renvoyons la valeur  ERREUR_INT_POSITIF , constante définie précédemment dans un include. La séquence des instructions est alors comme celle des deux fonctions précédentes:
  1. Initialisation de la variable à retourner à la valeur  ERREUR_INT_POSITIF 
  2. Test si la valeur de l'argument est celle par défaut:  NULL , et dans l'affirmative remplacement par la valeur pour la période courante, à demander à la fonction  obtenirNbSecsDsPeriode()  précédemment définie.
  3. Test si l'argument est positif.
  4. Si l'argument est positif, alors faire le calcul et l'affecter à la variable à retourner.
  5. Sinon, afficher une alerte avec un message explicatif et la valeur erronée.
  6. Retourner la variable.
Ce qu'il faut craindre en priorité lorsqu'on fait une division en programmation, c'est la division par zéro. Ici elle est évitée car le nombre de secondes n'est valide que s'il est strictement supérieur à 0, donc différent de zéro. Pour le message d'erreur, on réutilise le message pour les secondes erronées. Voici le code de la fonction à ajouter au fichier header ArithmetiqueTemporelle.mqh:

int obtenirNoPeriode(datetime horodatage, int nbSecsDansPeriode=NULL) {
   int noPeriode = ERREUR_INT_POSITIF;
   if(nbSecsDansPeriode == NULL) { nbSecsDansPeriode = obtenirNbSecsDsPeriode(); }
   bool nbSecsEstValide = (nbSecsDansPeriode > 0);
   if(nbSecsEstValide) { noPeriode = horodatage / nbSecsDansPeriode; }
   else { Alert(" ObtenirNoPeriode() ", msgErrNbSecsNonValide, nbSecsDansPeriode); }
   return(noPeriode);
}

Code de test de cette fonction:


Comme pour les fonctions précédentes, et comme pour toutes les suivantes, nous créons le code de test de la fonction pour s'assurer qu'elle répond aux spécifications qu'on lui demande au travers de différents cas de tests. Nous devons donc lui fournir différents horodatages ( 0 et un autre horodatage ) et soit aucune valeur soit des valeurs de nombre de secondes dans la période aussi bien positifs que négatifs et aussi nul. Les cas proposés sont donc:

Cas de tests:
n° cashorodatagenbSecsDsPerioderésultat retournécommentaires
10000 divisé par le nombre de secondes dans la période courante
2030000 / 300 = 0
30-10ERREUR_INT_POSITIFle mauvais argument compte en priorité
40ARG_INT_EMPTY00 divisé par le nombre de secondes dans la période courante
5120701207 / (60 * Period())1207 divisé par le nombre de secondes dans la période courante
6120730041207 / 300 = 4 + un reste fractionnaire non pris en compte
71207-10ERREUR_INT_POSITIFmauvais argument
81207ARG_INT_EMPTY1207 / (60 * Period())1207 divisé par le nombre de secondes dans la période courante

Dans le code de test, on ne doit pas oublier de déclarer le nombre de cas de tests en début de code dans la section defines. Et dans la section constantes: le nom de la fonction pour les messages affichés. On créé les deux fonctions: une pour exécuter la fonction testée avec ou sans le paramètre optionnel et l'autre pour définir les cas de tests et les faire exécuter et analyser en boucle. On inscrit une ligne dans la fonction init() pour exécuter les tests de cette fonction nouvellement créée. Voici les lignes à ajouter dans le fichier testsArithmetiqueTemporelle.mq4:

// section defines
#define NB_CAS_NO_PERIOD 8

// section constantes
string nomObtenirNoPeriode = "obtenirNoPeriode()"

// section fonctions
int execObtenirNoPeriode(datetime argumentHorodatage, int argumentNbSecsDsPeriode) {
   int resultat;
   if(argumentNbSecsDsPeriode == ARG_INT_EMPTY) {
      resultat = obtenirNoPeriode(argumentHorodatage); }
   else { resultat = obtenirNoPeriode(argumentHorodatage, argumentNbSecsDsPeriode); }
   return(resultat);
}

int testObtenirNoPeriode() {
   int noCas, nbErreurs = 0;
   int resultat;
   datetime argumentsHorodatages[NB_CAS_NO_PERIOD] = {0,
                                                        0,
                                                        0,
                                                        0,
                                                        1207,
                                                        1207,
                                                        1207,
                                                        1207};
   int argumentsNbSecsDsPeriode[NB_CAS_NO_PERIOD] = {0,
                                                                    300,
                                                                    -10,
                                                                    ARG_INT_EMPTY,
                                                                    0,
                                                                    300,
                                                                    -10,
                                                                    ARG_INT_EMPTY};
   string parametresCas[NB_CAS_NO_PERIOD] = {" 0, 0 => ",
                                                     " 0, 300 => ",
                                                     " 0, -10 => ",
                                                     " 0, => ",
                                                     " 1207, 0 => ",
                                                     " 1207, 300 => ",
                                                     " 1207, -10 => ",
                                                     " 1207, => "};
   int resultatsAttendus[NB_CAS_NO_PERIOD];
   resultatsAttendus[0] = 0;
   resultatsAttendus[1] = 0;
   resultatsAttendus[2] = ERREUR_INT_POSITIF;
   resultatsAttendus[3] = 0;
   resultatsAttendus[4] = (1207 / (60 * Period()));
   resultatsAttendus[5] = 4;
   resultatsAttendus[6] = ERREUR_INT_POSITIF;
   resultatsAttendus[7] = (1207 / (60 * Period()));
   for(noCas=0 ; noCas<NO_CAS_NB_SEC_IN_PERIOD ; noCas++) {
      resultat = execObtenirNoPeriode(argumentsHorodatages[noCas],
                                            argumentsNbSecsDsPeriode[noCas]);
      nbErreurs += analyserResultatIntDuTest(resultat,
                                             resultatsAttendus[noCas],
                                             nomObtenirNoPeriode,
                                             parametresCas[noCas]);
   }
   return(nbErreurs);
}

// ajouter dans la fonction init()
   nbErreurs += testObtenirNoPeriode();


Le tutoriel vidéo et les codes complets de l'include et de l'expert de test:




//+------------------------------------------------------------------+
//|                                       ArithmetiqueTemporelle.mqh |
//|                  Copyright 2014, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"

//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| DLL imports                                                      |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| EX4 imports                                                      |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| includes                                                         |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| variables globales                                               |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| constantes globales                                              |
//+------------------------------------------------------------------+

string msgErrNbMinsNonValide = " a reçu un nombre de minutes non valide: ";
string msgErrNbSecsNonValide = " a reçu un nombre de secondes non valide: ";

//+------------------------------------------------------------------+
//| fonctions comptage                                               |
//+------------------------------------------------------------------+

datetime obtenirNoDerniereSecConnue(string paire="NULL") {
   datetime horodatage;
   if(paire=="NULL") { horodatage = TimeCurrent(); }
   else { horodatage = MarketInfo(paire, MODE_TIME); }
   return(horodatage);
}

int obtenirNbSecsDsPeriode(int nbMinsDansPeriode=NULL) {
   int nbSecsDsPeriode = ERREUR_INT_POSITIF;
   if(nbMinsDansPeriode == NULL) { nbMinsDansPeriode = Period(); }
   bool nbMinsEstValide = (nbMinsDansPeriode > 0);
   if(nbMinsEstValide) { nbSecsDsPeriode = 60 * nbMinsDansPeriode; }
   else { Alert("ObtenirNbSecsDsPeriode() ", msgErrNbMinsNonValide, nbMinsDansPeriode); } 
   return(nbSecsDsPeriode);
}

int obtenirNoSecDsPeriode(datetime horodatageSec, int nbSecsDansPeriode=NULL) {
   int noSecDansPeriode = ERREUR_INT_POSITIF;
   if(nbSecsDansPeriode == NULL) { nbSecsDansPeriode = obtenirNbSecsDsPeriode(); }
   bool nbSecsEstValide = (nbSecsDansPeriode > 0);
   if(nbSecsEstValide) { noSecDansPeriode = horodatageSec % nbSecsDansPeriode; }
   else { Alert("ObtenirNoSecDsPeriode() ", msgErrNbSecsNonValide, nbSecsDansPeriode); }
   return(noSecDansPeriode);
}

int obtenirNoPeriode(datetime horodatage, int nbSecsDansPeriode=NULL) {
   int noPeriode = ERREUR_INT_POSITIF;
   if(nbSecsDansPeriode == NULL) { nbSecsDansPeriode = obtenirNbSecsDsPeriode(); }
   bool nbSecsEstValide = (nbSecsDansPeriode > 0);
   if(nbSecsEstValide) { noPeriode = horodatage / nbSecsDansPeriode; }
   else { Alert("ObtenirNoPeriode() ", msgErrNbSecsNonValide, nbSecsDansPeriode); }
   return(noPeriode);
}

//+------------------------------------------------------------------+



//+------------------------------------------------------------------+
//|                                  testsArithmetiqueTemporelle.mqh |
//|                  Copyright 2014, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"

#include <Constantes.mqh>
#include <UtilitairesTests.mqh>
#include <ArithmetiqueTemporelle.mqh>

//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+

#define NB_CAS_LAST_KNOWN_SEC_NO 5
#define NB_CAS_NB_SECS_IN_PERIOD 5
#define NB_CAS_NO_SEC_IN_PERIOD 7
#define NB_CAS_NO_PERIOD 8

//+------------------------------------------------------------------+
//| constantes de tests                                              |
//+------------------------------------------------------------------+

string nomObtenirNoDerniereSecConnue = "obtenirNoDerniereSecConnue()";
string nomObtenirNbSecsDsPeriode = "obtenirNbSecsDsPeriode()"
string nomObtenirNoSecDsPeriode = "obtenirNoSecDsPeriode()"
string nomObtenirNoPeriode = "obtenirNoPeriode()"

//+------------------------------------------------------------------+
//| fonctions de tests                                               |
//+------------------------------------------------------------------+

datetime execObtenirNoDerniereSecConnue(string argument) {
   datetime resultat;
   if(argument == ARG_STRING_EMPTY) { resultat = obtenirNoDerniereSecConnue(); }
   else { resultat = obtenirNoDerniereSecConnue(argument); }
   return(resultat);
}

int testObtenirNoDerniereSecConnue() {
   int noCas, nbErreurs = 0;
   datetime resultat;
   string arguments[NB_CAS_LAST_KNOWN_SEC_NO] = {ARG_STRING_EMPTY,
                                                 "EURUSD",
                                                 "GBPJPY",
                                                 "EUR",
                                                 ""};
   string parametresCas[NB_CAS_LAST_KNOWN_SEC_NO] = {" sans paramètre => ",
                                                     " \"EURUSD\" => ",
                                                     " \"GBPJPY\" => ",
                                                     " \"EUR\" => ",
                                                     " \"\" => "};
   datetime resultatsAttendus[NB_CAS_LAST_KNOWN_SEC_NO];
   resultatsAttendus[0] = TimeCurrent();
   resultatsAttendus[1] = MarketInfo("EURUSD", MODE_TIME);
   resultatsAttendus[2] = MarketInfo("GBPJPY", MODE_TIME);
   resultatsAttendus[3] = 0;
   resultatsAttendus[4] = 0;
   for(noCas=0 ; noCas<NB_CAS_LAST_KNOWN_SEC_NO ; noCas++) {
      resultat = execObtenirNoDerniereSecConnue(arguments[noCas]);
      nbErreurs += analyserResultatDatetimeDuTest(resultat,
                                                   resultatsAttendus[noCas],
                                                   nomObtenirNoDerniereSecConnue,
                                                   parametresCas[noCas]);
   }
   return(nbErreurs);
}

int execObtenirNbSecsDsPeriode(int argument) {
   int resultat;
   if(argument == ARG_INT_EMPTY) { resultat = obtenirNbSecsDsPeriode(); }
   else { resultat = obtenirNbSecsDsPeriode(argument); }
   return(resultat);
}

int testObtenirNbSecsDsPeriode() {
   int noCas, nbErreurs = 0;
   int resultat;
   int arguments[NB_CAS_NB_SECS_IN_PERIOD] = {ARG_INT_EMPTY, 5, 240, -2, 0};
   string parametresCas[NB_CAS_NB_SECS_IN_PERIOD] = {" sans paramètres => ",
                                                     " 5 => ",
                                                     " 240 => ",
                                                     " -2 => ",
                                                     " 0 => "};
   int resultatsAttendus[NB_CAS_NB_SECS_IN_PERIOD];
   resultatsAttendus[0] = 60 * Period();
   resultatsAttendus[1] = 300;
   resultatsAttendus[2] = 14400;
   resultatsAttendus[3] = ERREUR_INT_POSITIF;
   resultatsAttendus[4] = 60 * Period();
   for(noCas=0 ; noCas<NB_CAS_NB_SECS_IN_PERIOD ; noCas++) {
      resultat = execObtenirNbSecsDsPeriode(arguments[noCas]);
      nbErreurs += analyserResultatIntDuTest(resultat,
                                             resultatsAttendus[noCas],
                                             nomObtenirNbSecsDsPeriode,
                                             parametresCas[noCas]);
   }
   return(nbErreurs);
}

int execObtenirNoSecDsPeriode(datetime argumentHorodatage, int argumentNbSecsDsPeriode) {
   int resultat;
   if(argumentNbSecsDsPeriode == ARG_INT_EMPTY) {
      resultat = obtenirNoSecDsPeriode(argumentHorodatage); }
   else { resultat = obtenirNoSecDsPeriode(argumentHorodatage, argumentNbSecsDsPeriode); }
   return(resultat);
}

int testObtenirNoSecDsPeriode() {
   int noCas, nbErreurs = 0;
   int resultat;
   datetime argumentsHorodatages[NB_CAS_NO_SEC_IN_PERIOD] = {127,
                                                        123456789,
                                                        123456789,
                                                        123456789,
                                                        127,
                                                        127,
                                                        127};
   int argumentsNbSecsDsPeriode[NB_CAS_NO_SEC_IN_PERIOD] = {ARG_INT_EMPTY,
                                                                 ARG_INT_EMPTY,
                                                                 240,
                                                                 900,
                                                                 60,
                                                                 0,
                                                                 -4};
   string parametresCas[NB_CAS_NO_SEC_IN_PERIOD] = {" 127, EMPTY => ",
                                                     " 123456789, EMPTY => ",
                                                     " 123456789, 240 => ",
                                                     " 123456789, 900 => ",
                                                     " 127, 60 => ",
                                                     " 127, 0 => ",
                                                     " 127, -4 => "};
   int resultatsAttendus[NB_CAS_NO_SEC_IN_PERIOD];
   resultatsAttendus[0] = 127 % (60 * Period());
   resultatsAttendus[1] = 123456789 % (60 * Period());
   resultatsAttendus[2] = 69;
   resultatsAttendus[3] = 189;
   resultatsAttendus[4] = 7;
   resultatsAttendus[5] = 127 % (60 * Period());
   resultatsAttendus[6] = ERREUR_INT_POSITIF;
   for(noCas=0 ; noCas<NO_CAS_NB_SEC_IN_PERIOD ; noCas++) {
      resultat = execObtenirNbSecsDsPeriode(argumentsHorodatages[noCas],
                                            argumentsNbSecsDsPeriode[noCas]);
      nbErreurs += analyserResultatIntDuTest(resultat,
                                             resultatsAttendus[noCas],
                                             nomObtenirNoSecDsPeriode,
                                             parametresCas[noCas]);
   }
   return(nbErreurs);
}

int execObtenirNoPeriode(datetime argumentHorodatage, int argumentNbSecsDsPeriode) {
   int resultat;
   if(argumentNbSecsDsPeriode == ARG_INT_EMPTY) {
      resultat = obtenirNoPeriode(argumentHorodatage); }
   else { resultat = obtenirNoPeriode(argumentHorodatage, argumentNbSecsDsPeriode); }
   return(resultat);
}

int testObtenirNoPeriode() {
   int noCas, nbErreurs = 0;
   int resultat;
   datetime argumentsHorodatages[NB_CAS_NO_PERIOD] = {0,
                                                        0,
                                                        0,
                                                        0,
                                                        1207,
                                                        1207,
                                                        1207,
                                                        1207};
   int argumentsNbSecsDsPeriode[NB_CAS_NO_PERIOD] = {0,
                                                                    300,
                                                                    -10,
                                                                    ARG_INT_EMPTY,
                                                                    0,
                                                                    300,
                                                                    -10,
                                                                    ARG_INT_EMPTY};
   string parametresCas[NB_CAS_NO_PERIOD] = {" 0, 0 => ",
                                                     " 0, 300 => ",
                                                     " 0, -10 => ",
                                                     " 0, => ",
                                                     " 1207, 0 => ",
                                                     " 1207, 300 => ",
                                                     " 1207, -10 => ",
                                                     " 1207, => "};
   int resultatsAttendus[NB_CAS_NO_PERIOD];
   resultatsAttendus[0] = 0;
   resultatsAttendus[1] = 0;
   resultatsAttendus[2] = ERREUR_INT_POSITIF;
   resultatsAttendus[3] = 0;
   resultatsAttendus[4] = (1207 / (60 * Period()));
   resultatsAttendus[5] = 4;
   resultatsAttendus[6] = ERREUR_INT_POSITIF;
   resultatsAttendus[7] = (1207 / (60 * Period()));
   for(noCas=0 ; noCas<NO_CAS_NB_SEC_IN_PERIOD ; noCas++) {
      resultat = execObtenirNoPeriode(argumentsHorodatages[noCas],
                                            argumentsNbSecsDsPeriode[noCas]);
      nbErreurs += analyserResultatIntDuTest(resultat,
                                             resultatsAttendus[noCas],
                                             nomObtenirNoPeriode,
                                             parametresCas[noCas]);
   }
   return(nbErreurs);
}

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

int init()
  {
   int nbErreurs = 0;
   nbErreurs += testObtenirNoDerniereSecConnue();
   nbErreurs += testObtenirNbSecsDsPeriode();
   nbErreurs += testObtenirNoSecDsPeriode();
   nbErreurs += testObtenirNoPeriode();
   Print("-------------------------------------------------------------------");
   Print("TESTS DE ArithmetiqueTemporelle.mqh TERMINES AVEC ", nbErreurs, " ERREUR(S).");
   return(0);
  }

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+

int deinit()
  {
   return(0);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+

int start()
  {
   return(0);
  }

//+------------------------------------------------------------------+