vendredi 28 février 2014

Création d'un fichier include .mqh d'arithmétique des cotations


L'objectif:


Nous avons commencé la création de fichiers include (.mqh) pour y mettre des fonctions réutilisables et dans lesquelles on peut avoir confiance car les fonctions sont testées et nous n'avons plus qu'à nous concentrer sur notre code principal et plus sur les petits détails. Ces fonctions pour le premier include concernent les calculs sur le temps: c'est le fichier «ArithmetiqueTemporelle.mqh», et nous y avons mis quatre fonctions pour l'instant. Nous allons maintenant attaquer un fichier include pour l'arithmétique des cotations: «ArithmetiqueCotations.mqh». Nous y mettrons dès le présent post, des fonctions de moyennes et d'écarts.
  • obtenirCoursReelActuel()
  • obtenirCoursMedian()
  • obtenirCoursPondere()
  • obtenirSpreadActuel()
Nullement besoin d'expliquer longtemps car les noms sont assez expressifs. Je vais tout de même donnez des détails techniques sur ces fonctions.

Une fonction de contrôle du décalage de l'historique:


Nous aurons besoin pour deux des fonctions de cet include, d'un code de contrôle du décalage dans l'historique. Nous allons passer à deux des fonctions, un nombre entier qui représente un numéro de période en remontant dans l'historique. Mais ce numéro ne doit pas être négatif, ni être supérieur ou égal au nombre de périodes dans l'historique. Le nombre de périodes de l'historique est stocké dans la variable prédéfinie  Bars , et l'historique va de la période n°0 (celle en cours) à la période n°Bars-1 (la plus vieille de l'historique). On va donc créer cette fonction qui reçoit le décalage à contrôler et retourne une valeur booléenne pour dire s'il est valide ou non. Le code de cette fonction est simplement un test de ces conditions dont le résultat vrai ou faux est stockée dans une variable booléenne. Si le décalage est non valide, on affiche le message d'erreur avec le nom de la fonction reçu aussi en argument et la valeur erronée. Ensuite la valeur du test est retournée au code appelant:


bool leDecalageEstIlValide(int decalage, string nomFonction) {
   bool decalageEstValide = (decalage >= 0) && (decalage < Bars);
   if(!decalageEstValide) { Alert(nomFonction, msgErrDecalageNonValide, decalage); }
   return(decalageEstValide);
}

obtenirCoursReelActuel():


Il s'agit ici d'obtenir le dernier cours réel estimé reçu par MetaTrader. Je fais la simple moyenne du Bid et du Ask, car le cours réel est grosso modo au milieu des deux. Ceci est en conflit avec les cotations dans l'historique qui ne concernent que le Bid. Si on veut juste le dernier Bid reçu, pas besoin de fonction: il suffit simplement d'utiliser la variable prédéfinie  Bid  ! Et c'est bien plus court :) Pour cette fonction nous n'avons aucun paramètre à lui fournir et nous demandons qu'une seule chose: le dernier cours reçu. Elle utilise les deux variables prédéfinies  Bid  et  Ask , les additionne et divise par deux la somme. Et il n'y a qu'à retourner ce résultat. Elle est très courte:

double obtenirCoursReelActuel() {
   double coursReelActuel = (Bid + Ask) / 2.0;
   return(coursReelActuel);
}

obtenirCoursMedian():


Par cours médian, les traders comprennent la moyenne du plus bas et du plus haut de la période. Nous voulons ici, non pas avoir le tout dernier cours médian reçu, cela n'a aucun sens, mais avoir le cours médian d'une période dans l'historique. Elle peut être n'importe laquelle de l'historique ou bien celle qui est en cours et qui n'est pas encore finie, et dans ce cas le cours médian évolue en temps réel, au fur et à mesure que les cotations arrivent. Dès que la période est finie, ce cours est fixé et ne changera plus pour cette période. Elle était numérotée 0, et devient d'un coup, la numéro 1 en remontant l'historique.
Il faut au moins un paramètre: le décalage à faire dans l'historique pour tomber sur la période voulue. 0 étant la période actuelle. C'est un nombre entier positif ou nul qui grandit en remontant l'historique des périodes. Ce décalage doit aussi être inférieur au nombre de périodes contenues dans l'historique, et ce nombre est stocké dans la variable prédéfinie  Bars . La numérotation des périodes de l'historique allant de 0 à  Bars-1 . On doit alors tester que ce paramètre ne soit pas négatif et aussi qu'il ne soit pas strictement supérieur à  Bars . S'il ne l'est pas, on peut récupérer le  High[]  et le  Low[]  de la période. (ce sont des tableaux dont l'indice est le décalage pour remonter l'historique.) On additionne ces deux valeurs et on divise par deux. Si le décalage est  négatif ou bien supérieur à  Bars , on ne peut pas faire ça, et l'alerte avec un message d'erreur et la valeur erronée a déjà été levée dans la fonction de test du décalage. On doit alors retourner un code d'erreur. Ce code d'erreur est placé dès le départ, à l'initialisation de la variable à retourner, et si le calcul normal est fait, ce code est écrasé par le résultat que la fonction doit retourner. Il ne reste plus qu'à retourner cette variable:

double obtenirCoursMedian(int decalage) {
   double coursMedian = ERREUR_DOUBLE;
   bool decalageEstValide = leDecalageEstIlValide(decalage, nomObtenirCoursMedian);
   if(decalageEstValide) { coursMedian = (High[decalage] + Low[decalage]) / 2.0; }
   return(coursMedian);
}

obtenirCoursPondere():


Par pondéré, j'entends pondéré par les quatre valeurs de cotations de chaque période: le plus haut, le plus bas, l'ouverture et la fermeture. Cette valeur que j'appelle pondérée est plus précise que la valeur médiane si on vise la valeur moyenne de la période. (les traders ont l'habitude de n'utiliser que la valeur médiane en cherchant à avoir une pseudo-valeur moyenne !). J'utilise et vous propose un calcul plus proche de la vraie valeur moyenne. Pour une vraie moyenne, il faudrait avoir toutes les valeurs du cours de la période, or il n'en reste que quatre. Autant faire le calcul avec ces quatre, ce sera plus proche de la vraie moyenne. (Les traders utilisent même des moyennes pondérées par une valeur psychologique: il mettent 2 fois le close, en pensant que cette valeur à plus d'importance parce qu'elle clôt la période et donne le sens d'évolution des cours de la période. Ce qui est totalement faux, car le close d'une période est juste à côté de l'open de la suivante, et n'a pas de position privilégiée. La preuve, si on décale l'horloge d'une seconde, l'open devient le close de la période précédente et le close n'est plus considérée car oubliée comme la plupart des valeurs de la période. Autant faire le trading avec un gri-gri pour porter chance =D En fait seul les chandeliers journaliers pour les actions ont un close qui a une signification plus importante car à la fin de la journée de trading il y a en général une impulsion plus forte qui donne un mouvement représentatif de toute la séance et de plus il y a une coupure temporelle entre un close journalier et l'open suivant provoquant une psychologie de la foule des traders différente pour le chandelier suivant puisque demain est un autre jour, ce qui n'est pas le cas pour le forex qui ne ferme pas la nuit, mais seulement en fin de semaine. On observe tout de même une psychologie de marché sur le forex différente d'un jour à l'autre.)
Sur le même modèle que la fonction précédente, on fait le calcul avec les quatre valeurs  High[], Low[], Open[] et Close[] . On les additionne et on divise par quatre. Même algorithme que la précédente:
  • on reçoit en argument qui doit être positif ou nul, le décalage dans l'historique.
  • on initialise la variable qui contiendra le résultat avec le code d'erreur
  • on teste la validité du décalage reçu en argument
  • s'il l'est, on fait le calcul qu'on met dans la variable en écrasant le code d'erreur
  • on retourne le contenu de la variable.

double obtenirCoursPondere(int decalage) {
   double coursPondere = ERREUR_DOUBLE;
   bool decalageEstValide = leDecalageEstIlValide(decalage, nomObtenirCoursPondere);
   if(decalageEstValide) {
      coursPondere = (High[decalage]+Low[decalage]+Open[decalage]+Close[decalage]) / 4.0;
   }
   return(coursPondere);
}

obtenirSpreadActuel():


Contrairement aux trois précédentes fonctions qui étaient des moyennes, le spread est un écart: la différence entre le Bid et le Ask. C'est la marge du broker proportionnelle au capital mis en jeu dans la position ouverte. Il est important d'avoir ce spread, car lorsqu'on ouvre une position, il faut impérativement fermer lorsque le cours a eu une variation supérieure au spread. Eh oui, car le broker prend la valeur du spread quoi qu'il arrive, et donc il nous faut un écart de cours supérieur au spread pour être gagnant (et que le cours ait varié dans le bon sens, ça c'est l'évidence !) Étant donné que l'historique ne contient pas les valeurs Ask, nous ne pouvons le calculer que sur la dernière cotation reçue.
C'est une fonction très simple, sans paramètre, car il n'y a pas de choix à faire: le spread de la dernière cotation reçue est le spread actuel, rien d'autre ! On fait donc la différence entre les variables prédéfinies:  Ask - bid  et on retourne ce résultat !

double obtenirSpreadActuel() {
   double spread = Ask - Bid;
   return(spread);
}

Les constantes de messages:


Pour les besoins de ces fonctions, nous devons écrire quelques constantes de message d'erreur et des noms de fonctions:

string nomObtenirCoursReelActuel = "obtenirCoursReelActuel()";
string nomObtenirCoursMedian = "obtenirCoursMedian()";
string nomObtenirCoursPondere = "obtenirCoursPondere()";
string nomObtenirSpreadActuel = "obtenirSpreadActuel()";

string msgErrDecalageNonValide = " a reçu un argument de
// ligne coupée pour des raisons de largeur du post, ne pas la couper:
   décalage historique négatif ou alors supérieur à Bars: ";

Modification de l'include UtilitairesTests.mqh:


Pour les besoins des tests, nous allons améliorer notre fichier include d'utilitaires des tests et rectifier un petit bug insignifiant. Première chose, nous allons mutualiser des constantes de chaines de texte pour ne pas avoir à les écrire à chaque fois et pour centraliser les données. La première sert à faire un bilan de test par fonction, les deux autres pour servir de séparateurs dans l'affichage du journal pour s'y retrouver plus facilement. Ces constantes sont:

string msgBilanTestFonction = " a terminé ses tests avec un total d'erreurs de ";
// la nouvelle version de l'éditeur qui vient d'arriver cette semaine:
// nous permet d'ouvrir une chaine avec un type de guillemet (simple ou double)
// pour la refermer avec le même et d'insérer l'autre type de guillemet sans
// l'antislash pour échapper la signification de fermeture de chaine.
string SEPARATEUR = "-------------------------------------------------------";
string SEPARATEUR_DOUBLE = "================================================";
Nous rectifions également dans ce fichier une fonction d'analyse de résultat de test: celle du type double. En effet, lors de l'affichage de l'erreur, le dernier paramètre passé à la fonction  Print()  est le nombre décimal tel quel ! Or si la fonction  Print()  l'accepte sans problème, elle tronque tout de même les décimales à la cinquième et arrondi. Vous verriez sans la modification suivante qu'un arrondi et non la valeur exacte de notre constante  ERREUR_DOUBLE  avec 10 décimales. C'est un problème mineur d'affichage qui ne gène pas le fonctionnement du programme. La modification est la suivante: on ne transmet pas la variable resultat directement à la fonction  Print() , mais à une fonction de conversion  DoubleToStr()  qui comme son nom le suggère, va nous prendre un type   double  pour en faire un   string  qui est le type normalement demandé par la fonction  Print() . L'avantage est qu'elle demande le nombre de décimales à fournir en second paramètre. La ligne à modifier est celle du deuxième  Print() :

Print(nomFonction, msgErreur, parametresCas, DoubleToStr(resultatTest, 10));

Les tests de ces fonctions:


Nous réalisons immédiatement le test des fonctions sans attendre dans le fichier d'un expert advisor nommé testsArithmetiqueCotations.mq4. Il n'y aura pas grand chose à tester d'autant plus que la plupart des valeurs utilisées par ces fonctions sont récupérées dans les variables prédéfinies. Mais nous faisons les tests par principe, même si certains sont pour le moment pas très utiles. Du moins on a l'impression que certains seront inutiles, mais à l'avenir, si on modifie une fonction, il y aura déjà le test auquel on ajoute des cas. En plus, même s'il ne teste pas la réaction de la fonction face à un paramètre, il permet d'affirmer qu'il n'y a pas d'erreur de frappe et que le code fonctionne.

obtenirCoursReelActuel()
n°casrésultat attenducommentaires
1(Bid+Ask)/2.0étant donné que le résultat attendu est calculé avec les même valeurs que celles prises par la fonction, ils doit concorder avec celui de la fonction. Ce test est utile pour prouver qu'il n'y a pas d'erreur de syntaxe et que le code fonctionne bien qu'il soit minimaliste.

obtenirCoursMedian()
n°casdecalagerésultat attenducommentaires
10(High[0]+Low[0]) /2.0Période actuelle. Dans tous les cas nous ne pouvons pas prévoir le résultat du calcul, d'où le même calcul que dans la fonction en résultat attendu.
210(High[10]+Low[10]) /2.0Idem.
3Bars-1(High[Bars-1]+Low[Bars-1]) /2.0Décalage valide car strictement inférieur à Bars, c'est la première période chronologique contenue dans l'historique.
4-10ERREUR_DOUBLEDécalage négatif, donc alerte levée et retour d'un code d'erreur.
5BarsERREUR_DOUBLEDécalage égal à Bars, donc alerte levée et retour d'un code d'erreur.
61 000 000ERREUR_DOUBLEDécalage supérieur à Bars, donc alerte levée et retour d'un code d'erreur.

obtenirCoursPondere()
n°casdecalagerésultat attenducommentaires
10(High[0]+Low[0]+ Open[10]+Close[10]) /4.0Période actuelle. Dans tous les cas nous ne pouvons pas prévoir le résultat du calcul d'où le même calcul que dans la fonction en résultat attendu.
210(High[10]+Low[10]+ Open[10]+Close[10]) /4.0Idem.
3Bars-1(High[Bars-1]+Low[Bars-1]+ Open[Bars-1]+Close[Bars-1]) /4.0Décalage valide car strictement inférieur à Bars, c'est la première période chronologique contenue dans l'historique.
4-10ERREUR_DOUBLEDécalage négatif, donc alerte levée et retour d'un code d'erreur.
5BarsERREUR_DOUBLEDécalage égal à Bars, donc alerte levée et retour d'un code d'erreur.
61 000 000ERREUR_DOUBLEDécalage supérieur à Bars, donc alerte levée et retour d'un code d'erreur.

obtenirSpreadActuel()
n°casrésultat attenducommentaires
1Ask-Bidétant donné que le résultat attendu est calculé avec les même valeurs que celles prises par la fonction, il doit concorder avec celui de la fonction. Ce test est utile pour prouver qu'il n'y a pas d'erreur de syntaxe et que le code fonctionne bien qu'il soit minimaliste.

Il y a quelques différences avec le premier module de tests réalisé précédemment. Premièrement les valeurs de résultats sont du type  double  au lieu de  datetime . Il faut les changer tous dans la valeur de retour des fonctions d'exécution et leurs variables resultat à l'intérieur et dans les tableaux des résultats attendus de chaque fonction de test.
Ensuite, la fonction du cours actuel et celle du spread actuel n'ont qu'un seul test, mais surtout elle ne prennent aucun argument. Nous laissons pour elles, le code de test sous forme d'une fonction de test et d'une fonction d'exécution car à l'avenir, il est possible d'ajouter un paramètre optionnel à ces deux fonctions et donc d'avoir besoin de la fonction d'exécution. Le code de la fonction d'exécution pour ces deux-là change: la variable résultat prend une valeur par défaut qui est  ERREUR_DOUBLE . Dans ces deux fonctions d'exécution nous réalisons l'exécution de la fonction testée que si l'argument est la constante  ARG_STRING_EMPTY  (l'argument est du type  string  car s'il venait à y avoir un argument optionnel à la fonction testée, ce serait une chaine de caractères pour désigner la paire de devises).
Il y a aussi les fonctions de tests des deux autres, qui ont un argument mais obligatoire, donc dans ces deux cas l'exécution de la fonction testée ne se fait que si l'argument est différent de  ARG_INT_EMPTY  et il n'y a pas de cas alternatif sans argument, juste une clause  if  comme pour les deux précédentes.
Puis pour les fonctions de cours médian et de cours pondéré, les arguments  Bars  et  Bars-1  ne peuvent être mis dans la déclaration du tableau avec les autres valeurs car le compilateur, malgré sa version nouvelle, n'accepte toujours pas une variable lors de la déclaration d'un tableau. Nous devons donc y mettre une valeur, par exemple  1  et les remplacer desuite après par  Bars  et  Bars-1 .
Enfin nous ajoutons pour une question de lisibilité des résultats de tests, un  Print()  bilan du nombre d'erreurs de la fonction testée à la fin de chaque fonction de test, et encadré par deux  Print()  de lignes de séparation. Pour une question de code clair et non redondant, nous plaçons ces trois lignes de code dans une fonction spéciale dans le fichier UtilitairesTests.mqh. On la nomme afficherBilanTestFonction() et on l'appellera en une ligne en lui passant le nom de la fonction testée et le nombre d'erreurs du test. Voici son code:

void afficherBilanTestFonction(string nomFonction, int nbErreurs) {
   Print(SEPARATEUR);
   Print(nomFonction, msgBilanTestFonction, nbErreurs);
   Print(SEPARATEUR_DOUBLE);
}
Pour rappel les constantes des noms de fonctions sont désormais définies dans les fichiers où les fonctions sont codées, et non plus dans le fichier de tests. Le code correspondant à tout ça est donné en intégralité à la fin du post.

Le tutoriel vidéo et les trois codes complets:




//+------------------------------------------------------------------+
//|                                             UtilitairesTests.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                                                          |
//+------------------------------------------------------------------+

#define  ARG_STRING_EMPTY "-#-EMPTY-#-"
#define  ARG_INT_EMPTY 2147483647
#define  ARG_BOOL_EMPTY -1
#define  ARG_DOUBLE_EMPTY 123456789.123456789
#define  ARG_DATETIME_EMPTY 4294967295
#define  ARG_COLOR_EMPTY 4294967295

#define MESSAGE_ERREUR " a produit une erreur de résultat avec le cas "
#define MESSAGE_SUCCES " a passé le test sans erreurs "

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

string msgBilanTestFonction = " a terminé ses tests avec un total d'erreurs de ";
string SEPARATEUR = "-------------------------------------------------------";
string SEPARATEUR_DOUBLE = "================================================";

//+------------------------------------------------------------------+
//| Fonctions d'affichage                                            |
//+------------------------------------------------------------------+

void afficherBilanTestFonction(string nomFonction, int nbErreurs) {
   Print(SEPARATEUR);
   Print(nomFonction, msgBilanTestFonction, nbErreurs);
   Print(SEPARATEUR_DOUBLE);
}

//+------------------------------------------------------------------+
//| Fonctions d'analyse                                              |
//+------------------------------------------------------------------+

int analyserResultatDatetimeDuTest(
         datetime resultatTest, datetime resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

int analyserResultatIntDuTest(
         int resultatTest, int resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

int analyserResultatBoolDuTest(
         bool resultatTest, bool resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

int analyserResultatDoubleDuTest(
         double resultatTest, double resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, DoubleToStr(resultatTest, 10));
      nbErreurs++;
   }
   return(nbErreurs);
}

int analyserResultatStringDuTest(
         string resultatTest, string resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

int analyserResultatColorDuTest(
         color resultatTest, color resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

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



//+------------------------------------------------------------------+
//|                                        ArithmetiqueCotations.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                                                       |
//+------------------------------------------------------------------+

string nomObtenirCoursReelActuel = "obtenirCoursReelActuel()";
string nomObtenirCoursMedian = "obtenirCoursMedian()";
string nomObtenirCoursPondere = "obtenirCoursPondere()";
string nomObtenirSpreadActuel = "obtenirSpreadActuel()";

string msgErrDecalageNonValide = " a reçu un argument de décalage
ligne coupée pour des raisons de largeur de post ! Recollez-là
 historique négatif ou alors supérieur à Bars: ";

//+------------------------------------------------------------------+
//| Fonctions de contrôle                                            |
//+------------------------------------------------------------------+

bool leDecalageEstIlValide(int decalage, string nomFonction) {
   bool decalageEstValide = (decalage >= 0) && (decalage < Bars);
   if(!decalageEstValide) { Alert(nomFonction, msgErrDecalageNonValide, decalage); }
   return(decalageEstValide);
}

//+------------------------------------------------------------------+
//| Fonctions de Moyennes                                            |
//+------------------------------------------------------------------+

double obtenirCoursReelActuel() {
   double coursReelActuel = (Bid + Ask) / 2.0;
   return(coursReelActuel);
}

double obtenirCoursMedian(int decalage) {
   double coursMedian = ERREUR_DOUBLE;
   bool decalageEstValide = leDecalageEstIlValide(decalage, nomObtenirCoursMedian);
   if(decalageEstValide) { coursMedian = (High[decalage] + Low[decalage]) / 2.0; }
   return(coursMedian);
}

double obtenirCoursPondere(int decalage) {
   double coursPondere = ERREUR_DOUBLE;
   bool decalageEstValide = leDecalageEstIlValide(decalage, nomObtenirCoursPondere);
   if(decalageEstValide) {
      coursPondere = (High[decalage]+Low[decalage]+Open[decalage]+Close[decalage]) /4.0;
   }
   return(coursPondere);
}

//+------------------------------------------------------------------+
//| Fonctions d'écarts                                               |
//+------------------------------------------------------------------+

double obtenirSpreadActuel() {
   double spread = Ask - Bid;
   return(spread);
}

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


//+------------------------------------------------------------------+
//|                                   testsArithmetiqueCotations.mq4 |
//|                  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  version   "1.00"
#property  strict
//+------------------------------------------------------------------+
//| Includes                                                         |
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Defines                                                          |
//+------------------------------------------------------------------+

#define NB_CAS_COURS_REEL_ACTUEL 1
#define NB_CAS_COURS_MEDIAN 6
#define NB_CAS_COURS_PONDERE 6
#define NB_CAS_SPREAD_ACTUEL 1

//+------------------------------------------------------------------+
//| Fonctions de tests                                               |
//+------------------------------------------------------------------+

double execObtenirCoursReelActuel(string argument) {
   double resultat = ERREUR_DOUBLE;
   if(argument == ARG_STRING_EMPTY) { resultat = obtenirCoursReelActuel(); }
   return(resultat);
}

int testObtenirCoursReelActuel() {
   int noCas, nbErreurs = 0;
   double resultat;
   string arguments[NB_CAS_COURS_REEL_ACTUEL] = {ARG_STRING_EMPTY};
   string parametresCas[NB_CAS_COURS_REEL_ACTUEL] = {" => "};
   double resultatsAttendus[NB_CAS_COURS_REEL_ACTUEL];
   resultatsAttendus[0] = (Bid + Ask) / 2.0;
   for(noCas=0 ; noCas<NB_CAS_COURS_REEL_ACTUEL ; noCas++) {
      resultat = execObtenirCoursReelActuel(arguments[noCas]);
      nbErreurs += analyserResultatDoubleDuTest(resultat, resultatsAttendus[noCas],
                                        nomObtenirCoursReelActuel, parametresCas[noCas]);
   }
   afficherBilanTestFonction(nomObtenirCoursReelActuel, nbErreurs);
   return(nbErreurs);
}

double execObtenirCoursMedian(int argument) {
   double resultat = ERREUR_DOUBLE;
   if(argument != ARG_INT_EMPTY) { resultat = obtenirCoursMedian(argument); }
   return(resultat);
}

int testObtenirCoursMedian() {
   int noCas, nbErreurs = 0;
   double resultat;
   int arguments[NB_CAS_COURS_MEDIAN] = {0, 10, 1, -10, 1, 1000000};
   arguments[2] = Bars-1;
   arguments[4] = Bars;
   string parametresCas[NB_CAS_COURS_MEDIAN] = {" 0 => "," 10 => "," Bars-1 => ",
                                                " -10 => "," Bars => "," 1000000 => "};
   double resultatsAttendus[NB_CAS_COURS_MEDIAN];
   resultatsAttendus[0] = (High[0] + Low[0]) / 2.0;
   resultatsAttendus[1] = (High[10] + Low[10]) / 2.0;
   resultatsAttendus[2] = (High[Bars-1] + Low[Bars-1]) / 2.0;
   resultatsAttendus[3] = ERREUR_DOUBLE;
   resultatsAttendus[4] = ERREUR_DOUBLE;
   resultatsAttendus[5] = ERREUR_DOUBLE;
   for(noCas=0 ; noCas<NB_CAS_COURS_MEDIAN ; noCas++) {
      resultat = execObtenirCoursMedian(arguments[noCas]);
      nbErreurs += analyserResultatDoubleDuTest(resultat, resultatsAttendus[noCas],
                                        nomObtenirCoursMedian, parametresCas[noCas]);
   }
   afficherBilanTestFonction(nomObtenirCoursMedian, nbErreurs);
   return(nbErreurs);
}

double execObtenirCoursPondere(int argument) {
   double resultat = ERREUR_DOUBLE;
   if(argument != ARG_INT_EMPTY) { resultat = obtenirCoursPondere(argument); }
   return(resultat);
}

int testObtenirCoursPondere() {
   int noCas, nbErreurs = 0;
   double resultat;
   int arguments[NB_CAS_COURS_PONDERE] = {0, 10, 1, -10, 1, 1000000};
   arguments[2] = Bars-1;
   arguments[4] = Bars;
   string parametresCas[NB_CAS_COURS_PONDERE] = {" 0 => "," 10 => "," Bars-1 => ",
                                                " -10 => "," Bars => "," 1000000 => "};
   double resultatsAttendus[NB_CAS_COURS_MEDIAN];
   resultatsAttendus[0] = (High[0] + Low[0] + Open[0] + Close[0]) / 4.0;
   resultatsAttendus[1] = (High[10] + Low[10] + Open[10] + Close[10]) / 4.0;
   resultatsAttendus[2] = (High[Bars-1] + Low[Bars-1] + Open[Bars-1] + Close[Bars-1]) / 4.0;
   resultatsAttendus[3] = ERREUR_DOUBLE;
   resultatsAttendus[4] = ERREUR_DOUBLE;
   resultatsAttendus[5] = ERREUR_DOUBLE;
   for(noCas=0 ; noCas<NB_CAS_COURS_PONDERE ; noCas++) {
      resultat = execObtenirCoursPondere(arguments[noCas]);
      nbErreurs += analyserResultatDoubleDuTest(resultat, resultatsAttendus[noCas],
                                        nomObtenirCoursPondere, parametresCas[noCas]);
   }
   afficherBilanTestFonction(nomObtenirCoursPondere, nbErreurs);
   return(nbErreurs);
}

double execObtenirSpreadActuel(string argument) {
   double resultat = ERREUR_DOUBLE;
   if(argument == ARG_STRING_EMPTY) { resultat = obtenirSpreadActuel(); }
   return(resultat);
}

int testObtenirSpreadActuel() {
   int noCas, nbErreurs = 0;
   double resultat;
   string arguments[NB_CAS_SPREAD_ACTUEL] = {ARG_STRING_EMPTY};
   string parametresCas[NB_CAS_SPREAD_ACTUEL] = {" => "};
   double resultatsAttendus[NB_CAS_SPREAD_ACTUEL];
   resultatsAttendus[0] = Ask - Bid;
   for(noCas=0 ; noCas<NB_CAS_SPREAD_ACTUEL ; noCas++) {
      resultat = execObtenirSpreadActuel(arguments[noCas]);
      nbErreurs += analyserResultatDoubleDuTest(resultat, resultatsAttendus[noCas],
                                        nomObtenirSpreadActuel, parametresCas[noCas]);
   }
   afficherBilanTestFonction(nomObtenirSpreadActuel, nbErreurs);
   return(nbErreurs);
}

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

int OnInit() {
   int nbErreurs = 0;
   nbErreurs += testObtenirCoursReelActuel();
   nbErreurs += testObtenirCoursMedian();
   nbErreurs += testObtenirCoursPondere();
   nbErreurs += testObtenirSpreadActuel();
   Print(SEPARATEUR_DOUBLE);
   Print(" TESTS DE ArithmetiqueCotations.mqh TERMINES AVEC ", nbErreurs, " ERREUR(S).");
   Print(SEPARATEUR_DOUBLE);
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+

void OnDeinit(const int reason) {
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+

void OnTick() {
}

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

Vous pouvez constater qu'il y a des différences avec les fichiers précédemment réalisés. Il y a deux  #property  en plus au début du fichier: un qui donne la version de l'expert qu'on créé et un autre qui dit que nos lignes de code sont en version stricte. (on pourrait utiliser des variantes de code qui s'éloignent du standard!) Il y a un autre changement de taille: le nom des fonctions prédéfinies: elles ne s'appelent plus  init() ,  deinit()  et  start() , mais  OnInit() ,  OnDeinit()  et  OnTick() . Ce nouveau nommage est compatible avec MQL5, et correspond au nommage standard dans tous les langages de programmation pour des fonctions déclenchées sur des évènements (initialisation de l'expert, arrêt de l'expert avec d'ailleurs un paramètre entier fourni par MetaTrader4 lors de l'appel pour indiquer la raison de l'arrêt et l'arrivée d'un tick, d'où un nom plus cohérent avec ce qu'il représente: OnTick = SurUnTick). Il y a aussi les valeurs des retours: celle de la fonction d'initialisation est indiquée par une constante  INIT_SUCCEEDED  qui signifie succès d'initialisation et qui vaut zéro comme la valeur qu'on retournait avant pour dire qu'il n'y a pas eu d'erreur. Pour les deux autres fonctions, apparemment nous ne sommes plus obligés de retourner une valeur, car il n'y a pas d'instruction  return  préparées et surtout leur type de retour est  void , donc ne pas retourner de valeur.
C'est un post marathon qui se termine, ouf ! Ça nous fait quatre nouvelles fonctions de calcul dans un nouveau fichier header (include) et des modfications dans l'include des utilitaires de tests et un expert de tests pour ces quatre fonctions. Comme vous pouvez le constater, nous accélérons le rythme pour la création de code utilitaire pour nos futurs indicateurs et experts advisors. Avec la nouvelle mise à jour de cette semaine, MetaTrader4, MetaEditor et son aide sont traduits en français, la compatibilité avec le MQL5 est améliorée, et l'éditeur nous propose une nouvelle ergonomie. Nous ne pouvons plus taper les noms prédéfinis sans la majuscule et attendre que l'autocomplétion nous propose un nom, maintenant il faut mettre la majuscule pour que l'éditeur le reconnaisse et propose l'autocomplétion (une liste de possibilités). C'est dommage, mais en contrepartie, l'autocomplétion fonctionne désormais avec nos propres noms: toutes nos variables et fonctions, et ça c'est bien mieux ! La raison est qu'on peut maintenant inclure des fonctions du langage C. Par exemple, si vous ne mettez pas la majuscule à Print(), l'éditeur vous propose en autocomplétion printf(), une fonction du langage C, nos formatages de chaines à afficher en seraient facilités. Nous l'utiliserons peut-être à l'avenir. En tout cas, bon courage pour étudier et pratiquer tout ce qu'on vient de voir, et à la prochaine !

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);
  }

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