vendredi 16 août 2013

Factorisation du code de l'indicateur Taux Moyen

Nous reprenons immédiatement l'indicateur réalisé juste avant pour restructurer un peu le code. Restructurer le code par des fonctions pour le rendre réutilisable s'appelle factoriser, comme en mathématiques quand vous factorisez une expression. Cela a le même sens. Nous allons nous focaliser uniquement sur l'initialisation de l'indicateur car il contient de nombreuses lignes qui sont des appels à des fonctions d'initialisation. Il y a deux types de fonctions d'initialisation: celles qui concernent l'indicateur au niveau global et celles qui concernent un index dans l'indicateur. Un index étant un signal à calculer et à tracer. Il peut y en avoir huit maximum par indicateur. Nous n'en utilisons qu'un seul pour l'instant, mais à l'avenir sur cet indicateur on en utilisera trois. (selon ce que je projette de faire pour le moment) Nous allons donc faire deux fonctions, une pour initialiser l'indicateur au niveau global et une autre pour initialiser un index. Et cette dernière pourra être utilisée jusqu'à huit fois dans l'indicateur pour initialiser les huit signaux possibles.

L'initialisation de l'indicateur:

Dans l'état actuel des choses, l'indicateur est initialisé seulement avec deux fonctions: une pour préciser le nombre de décimales à utiliser et l'autre pour préciser le nom court de l'indicateur. Ce nom sera passé en paramètre de la fonction qui ne retourne aucune valeur, donc déclarée en void.

void initIndicateur(string shortName) {
   IndicatorDigits(MarketInfo(Symbol(), MODE_DIGITS));
   IndicatorShortName(shortName);
}


L'initialisation d'un index:

Un index ou signal, s'initialise avec quatre fonctions pour l'instant. Chacune de ces fonctions prend en premier paramètre le numéro de l'index. On passera donc ce numéro en premier paramètre de notre fonction. Puis nous aurons besoin de passer le tableau qui sert de buffer. Il faudra alors le passer par référence, avec un esperluette devant son nom (&) et bien mettre la paire de crochets pour préciser que c'est un tableau, et le déclarer en tableau de nombres décimaux (double). Ensuite il faut passer le décalage à appliquer sur le signal (un nombre entier), puis l'indice de commencement de traçage (shift). Pour finir il faut le style de traçage qu'on met par défaut à DRAW_LINE. Les noms des variables buffer et shift utilisées dans cette fonction sont les mêmes que les variables globales utilisées dans l'indicateur. Il faut impérativement changer les noms des variables globales pour éviter toute collision de noms. Nous mettrons 'tampon' et 'decalage'.

void initIndex(int noIndex, double &buffer[], int shift, int begin, int style=DRAW_LINE) {
   SetIndexStyle(noIndex,style);
   SetIndexBuffer(noIndex,buffer);
   SetIndexShift(noIndex, shift);
   SetIndexDrawBegin(noIndex, begin);
}


Je vous fais faire tout ça en vidéo, puis vous avez le code complet juste après.



//+------------------------------------------------------------------+
//|                                                    tauxMoyen.mq4 |
//|                  Copyright 2013, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2013, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1 Aqua
//--- input parameters
extern int decalage = 0;
//--- buffers
double tampon[];
int ancienCompteBars, nbTicksDansBar;
double sommeCours;

double obtenirCoursActuel() {
   double cours = (Bid + Ask) / 2.0;
   return(cours);
}

void initIndicateur(string shortName) {
   IndicatorDigits(MarketInfo(Symbol(), MODE_DIGITS));
   IndicatorShortName(shortName);
}

void initIndex(int noIndex, double &buffer[], int shift, int begin, int style=DRAW_LINE) {
   SetIndexStyle(noIndex,style);
   SetIndexBuffer(noIndex,buffer);
   SetIndexShift(noIndex, shift);
   SetIndexDrawBegin(noIndex, begin);
}

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
  {
   ancienCompteBars = IndicatorCounted();
   initIndicateur("TM");
   initIndex(0, tampon, decalage, 0);
   sommeCours = 0.0;
   nbTicksDansBar = 0;
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit()
  {
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
   double coursActuel = obtenirCoursActuel();
   double moyenneCours = coursActuel;
   int nbBarsActuel = IndicatorCounted();
   if(nbBarsActuel > ancienCompteBars) {
      if(nbTicksDansBar > 0) { moyenneCours = sommeCours / nbTicksDansBar; }
      tampon[1] = moyenneCours;
      sommeCours = 0.0;
      nbTicksDansBar = 0;
      ancienCompteBars = nbBarsActuel;
   }
   sommeCours += coursActuel;
   nbTicksDansBar++;
   tampon[0] = sommeCours / nbTicksDansBar;
   return(0);
  }
//+------------------------------------------------------------------+


Nous continuerons dans les posts suivants à améliorer et faire évoluer cet indicateur.

jeudi 15 août 2013

Création d'un premier indicateur pour MetaTrader 4

Toujours avec le même algorithme, nous allons maintenant faire un indicateur. Un indicateur, à la différence d'un expert advisor, ne peut pas passer d'ordres au broker. Il est là pour donner la valeur d'un calcul sur les cours qui nous sert pour trader. En général, on l'affiche dans un graphique. On s'en sert aussi dans les experts advisors car la (ou les) valeur(s) qu'ils donne sert à la décision pour ouvrir ou clôturer une position. L'indicateur que nous allons faire, va nous donner simplement la moyenne du cours pour chaque chandelier. Toutes les minutes pour un timeframe M1, ou toutes les quatre heures pour un timeframe H4, etc. Cette moyenne sera affichée sous forme de courbe et non pas comme valeur brute dans un label sur le graphique. Vous commencez à connaître l'algorithme qu'on va utiliser sur le bout des doigts. On va utiliser le même que le précédent mais adapté à un traçage de courbe d'indicateur.

Le traçage de courbes avec MetaTrader 4:

Je vais vous décevoir, mais il n'existe pas de fonction toute prête pour tracer une ligne entre deux points ! On peut tracer pleins d'objets graphiques avec les fonctions appropriées, mais pas de ligne toute simple ! Cela pour une raison principale: MetaTrader gère lui même en interne le traçage de courbes avec des lignes pour gérer les performances et la mémoire et avoir un contrôle complet sur ce processus, car si on le fait nous même c'est plus lent, et on risque de consommer bien trop de mémoire. Les concepteurs de MetaTrader ont préféré que le logiciel garde tout le contrôle à ce niveau. Et donc nous n'avons rien à tracer ! INCROYABLE ! Pour tracer une courbe, nous ne traçons rien du tout !! Il suffit juste de donner à MetaTrader les valeurs et il s'en charge. Ces valeurs sont stockées dans ce que les programmeurs appellent un buffer, ou tampon en français. Il s'agit d'une zone mémoire qui contient les données les unes à la suite des autres. Et si vous avez bien suivi, c'est typiquement un tableau ! Nous n'avons même pas à gérer la taille du tableau ! C'est pas beau ça ! :) Nous mettons simplement la valeur la plus récente au début du tableau, et toutes les valeurs se décaleront au fur et à mesure. Il faut donc déclarer un tableau au niveau global du fichier sans la taille, et le remplir, en général, que la ou les premières cases. Il faudra aussi préciser à l'initialisation de l'indicateur que ce tableau déclaré dans le programme est le buffer (mémoire tampon) des valeurs de la courbe grâce à la fonction SetIndexBuffer(). Cette fonction prend deux paramètres: l'index de la ligne à tracer (elles sont numérotées de 0 à 7 car il ne peut y en avoir que huit maximum) et le tableau créé pour servir de tampon. Les deux choses à faire sont: déclarer un tableau sans indication de taille qui sera le tampon et indiquer avec la fonction SetIndexBuffer() que ce tableau est le tampon pour la courbe numéro 0.


   double buffer[];
   SetIndexBuffer(0, buffer);


Les paramètres des indicateurs:

Indiquer juste le tampon des données à MetaTrader 4 n'est pas suffisant, il lui faut d'autres renseignements. Par exemple le nombre de signaux à tracer (courbes), et comment les tracer (courbe de ligne brisée ou histogramme, ou section, ou zigzag etc.). Il faut aussi indiquer le décalage par rapport au temps réel (on peut tracer en décalé temporellement), où on commence le traçage, le nom court de l'indicateur, et la précision du calcul en nombre de décimales. Il en existe d'autres, mais on va s'arrêter sur celles-là. Je vous donne le tout en code MQL4 car les noms des fonctions qui réalisent cela sont explicites sachant que "shift", c'est le décalage et "digits" ce sont les chiffres significatifs de la précision.

   SetIndexStyle(0,DRAW_LINE);
   SetIndexShift(0,shift);
   IndicatorDigits(MarketInfo(Symbol(),MODE_DIGITS));
   IndicatorShortName("TM");
   SetIndexDrawBegin(0,0);

Dans ce code: "TM" sont les initiales de "Taux Moyen", c'est le nom court que je donne à l'indicateur. 'shift' est une variable qui contient le décalage sous forme de nombre entier (0 par défaut). Cette variable est déclarée comme externe avec le mot-clef extern, ce qui signifie qu'elle devient un paramètre ajustable du programme lorsqu'on va le démarrer en le glissant sur le graphique d'un cours. Ce paramètre s'ajustera dans un onglet de la fenêtre qui s'ouvre lors du lancement de l'expert ou indicateur.

   extern shift = 0;

Ensuite, dans ce même code, pour obtenir le nombre de décimales servant à la précision des calculs, on fait appel à la fonction MarketInfo() qui donne le nombre de décimales fournies par le broker (MODE_DIGITS) pour la paire sur laquelle l'indicateur a été lancé (Symbol()). Au final on indique que le dessin du signal numéro 0 commence à l'indice 0 du tableau tampon. Les tableaux étant inversé: plus l'indice grandit et plus on remonte dans l'historique. L'indice zéro étant celui du chandelier en cours. L'indice du chandelier complètement terminé et le plus récent est le 1. C'est bien joli tout ça, mais il y a des informations supplémentaires à indiquer et une aurte fonction à connaître. Ces informations sont: où dessiner la ou les courbes (dans la fenêtre principale ou une autre), combien de buffers sont à réserver (un par signal,  autrement appelé courbe, et huit maximum) et quelles sont les couleurs des courbes.

   #property indicator_chart_window
   #property indicator_buffers 1
   #property indicator_color1 Aqua

Ces lignes ne sont pas du code, mais des instructions pour le préprocesseur car elles commencent par le signe dièse. Et c'est pour ça qu'il n'y a pas de point-virgule à la fin de chaque ligne. MetaTrader saura ce que ces lignes veulent dire. La dernière fonction à voir est celle-ci: IndicatorCounted(). Cette fonction nous donne le nombre de chandeliers qui n'ont pas changé depuis la dernière fois que l'indicateur a été lancé. Il faut l'utiliser pour éviter à MetaTrader de faire trop de calculs inutiles. Les tampons contiennent toujours les valeurs, et il est inutile de tout recalculer si ça n'a pas changé. On récupère donc ce nombre pour savoir le nombre de chandeliers qu'il ne faut pas refaire. Et ce nombre est sûrement différent du nombre fourni par la variable prédéfinie Bars qui est le nombre de chandeliers ou barres (bars) dans l'historique du graphique, modifiés ou non. Ça suffit largement, le compte est bon pour aujourd'hui, ça fait pas mal d'informations à digérer. Je vous le fais faire en tutoriel vidéo, puis je vous donne le code complet.



//+------------------------------------------------------------------+
//|                                              testsIndicateur.mq4 |
//|                  Copyright 2013, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2013, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"

#property indicator_chart_window
#property indicator_buffers 1
#property indicator_color1 Aqua

extern int shift = 0;

double buffer[];
int ancienCompteBars, nbTicksDansBar;
double sommeCours;

double obtenirCoursActuel() {
   double cours = (Bid + Ask) / 2.0;
   return(cours);
}

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
  {
   ancienCompteBars = IndicatorCounted();
   IndicatorDigits(MarketInfo(Symbol(),MODE_DIGITS));
   IndicatorShortName("TM");
   SetIndexStyle(0,DRAW_LINE);
   SetIndexBuffer(0,buffer);
   SetIndexShift(0,shift);
   SetIndexDrawBegin(0,0);
   sommeCours = 0.0;
   nbTicksDansBar = 0;
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit()
  {
   
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
   double coursActuel = obtenirCoursActuel();
   double moyenneCours = coursActuel;
   int nbBarsActuel = IndicatorCounted();
   if(nbBarsActuel < ancienCompteBars) {
      if(nbTicksDansBar > 0) { moyenneCours = sommeCours / nbTicksDansBar; }
      buffer[1] = moyenneCours;
      sommeCours = 0.0;
      nbTicksDansBar = 0;
      ancienCompteBars = nbBarsActuel;
   }
   sommeCours += coursActuel;
   nbTicksDansBar++;
   buffer[0] = sommeCours / nbTicksDansBar;
   return(0);
  }
//+------------------------------------------------------------------+  

Nous allons améliorer cet indicateur dès le prochain post. J'espère que celui-ci vous a été profitable, à la prochaine ;)

mardi 13 août 2013

Débuggage et reformulation d'algorithme

L'initiation à la programmation en MQL4 est finie, mais l'apprentissage ne finit jamais. Je reviens donc sur l'expert advisor que nous avons fait et restructuré: celui de la moyenne du cours à chaque seconde. Son code a été amélioré, il est plus clair et explicite. Mais il contient des bugs potentiels. Nous allons y réfléchir et aussi repenser la manière de faire la moyenne. Comme ça vous verrez qu'il n'y a pas qu'une seule façon de faire et que la façon dont nous avons procédé est maladroite !

Le problème du choix de la taille du tableau:

Réfléchissons à propos du tableau utilisé. Nous fixons sa taille à 10 car nous ne savons pas combien nous avons de ticks dans la seconde, et surtout, ça change à chaque seconde: c'est variable. Nous avons donc choisi une taille de 10 considérant que ce sera largement suffisant. En effet il y a très peu de risque d'avoir plus de 10 ticks dans une seconde. Très peu de risque ne signifie pas risque zéro. Il y a tout de même une probabilité, même faible. Alors on peut choisir d'agrandir le tableau pour réduire encore plus cette probabilité de débordement de tableau. Cette façon de faire n'est pas la bonne, il faut revoir tout ça et faire autrement. Même si on sait qu'en choisissant une taille doublée, nous pourrons rester une vie à faire fonctionner le programme sans que le débordement ne se produise. Ce n'est pas la manière la plus élégante de résoudre le problème puisqu'on laisse théoriquement un bug possible de débordement. Et normalement un débordement de tableau produit un crash du programme. Le système l'arrête immédiatement car le programme essaie d'écrire dans une zone qui n'est plus le tableau et qu'il n'a pas le droit d'écrire. S'il s’avérait que le programme ait le droit d'écrire dans la mémoire juste après le tableau, c'est que c'est une zone qu'il a réservé auprès du système, et donc on écrit et on écrase une donnée du programme ou le programme lui même. Et ceci va provoquer d'autres bugs, un comportement anormal voire un crash du programme. Il faut être très prudent avec les tableaux, il ne faut surtout pas écrire ou lire en débordant du tableau. Nous allons régler le problème en changeant la façon de faire la moyenne.

Traitements inutiles:

Voyons un peu ce que fait le programme. Pour obtenir la moyenne, il enregistre les cours de chaque tick, puis lorsque la seconde n'est plus la même, il parcours tous les enregistrements pour les additionner. Et au final on fait la division de la somme par le nombre d'éléments parcourus. Regardons un peu ce qu'on fait avec les enregistrements dans le tableau: juste la somme des enregistrements, et rien d'autre. Alors dans ce cas, pourquoi les enregistrer dans le tableau. On pourrait directement les additionner dans une variable qui contiendra la somme. Comme ça quand la seconde n'est plus la même: on a juste à effectuer la division. On s'épargne ainsi les étapes d'enregistrement, de parcours de tableau pour lire tous ses éléments L'algorithme est donc plus rapide, car moins d'étapes et en plus: fini le problème de la taille du tableau. Sans tableau il n'y a plus de débordement possible. Cet algorithme est donc plus fiable et plus rapide (bien qu'on ne s'apercevra pas de la différence). C'est un code plus robuste car on s'est affranchi d'un problème théorique. La somme des cours pourra en accumuler autant que nécessaire, et plus besoin de réserver de la place pour chacun en mémoire.

Améliorations:

On peut pousser plus loin les améliorations en n'écrivant les constantes qu'une seule fois dans le code. Et en plus ça rassemble les déclarations en un lieu unique. Par exemple, le nom du label ("moyenne") est indiqué deux fois à deux endroits différents dans le code. On le passe dans une variable dont la valeur ne change pas: une constante. En MQL4, rien ne différencie une variable d'une constante. Si on veut faire une constante, il faut une variable, initialiser dès la déclaration et faire attention à ne pas la modifier, car du point de vue du langage, c'est une variable comme les autres, dont la valeur peut changer en y affectant une nouvelle. On va donc créer la variable constante 'NOM_LABEL' au début du fichier. L'avantage c'est que si on doit la modifier pour faire évoluer le code, on ne le fait qu'à un seul endroit, et une seule fois. Nous n'aurons pas à répéter les modifications à différents endroits du code. On va faire de même pour la taille du texte du label avec une variable constante nommée TAILLE_TEXTE. (On fait une différentiation dans le nommage pour reconnaitre les variables des constantes, les constantes sont en majuscules avec un souligné pour séparer les mots. Ce n'est qu'une convention que j'applique à mes codes). On en profite aussi pour mettre le texte dans une autre couleur car il est peu visible en l'état. Les couleurs du web sont déjà inscrites dans le langage. On va utiliser 'White'. Attention, il faut mettre la majuscule, car l'auto-complétion ne reconnait pas ces constantes, et pourtant l'éditeur de code en changera la couleur, ce qui signifie qu'il reconnait le mot. Cette couleur est à préciser en dernier paramètre de la fonction ObjectSetText(). Il y a un paramètre de police de caractères juste avant qu'il faut alors préciser. On le met "Arial" dans une constante nommée FONTE qu'on utilisera comme paramètre. Maintenant je vais vous le faire faire en vidéo. Je vous montre étape par étape. Après la vidéo le code complet. Nous ferons un nouvel expert advisor pour pouvoir garder l'autre, vous pourrez ainsi comparer.



//+------------------------------------------------------------------+
//|                                           moyenneParSeconde2.mq4 |
//|                  Copyright 2013, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2013, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"

datetime tempsEnCoursTraitement, tempsTickActuel;
double sommeCours;
int nbTicksDansSeconde;
string NOM_LABEL = "moyenne";
int TAILLE_TEXTE = 10;
string FONTE = "Arial";

double obtenirCoursActuel() {
   double cours = (Bid + Ask) / 2.0;
   return(cours);
}

void creerLabel(string nom, int x, int y) {
   bool aReussi = ObjectCreate(nom, OBJ_LABEL, 0, x, y);
   if(aReussi) { ObjectSetText(nom, "###", TAILLE_TEXTE, FONTE, White); }
   else { Print("Erreur à la création du texte: ", GetLastError()); }
}

void effacerObjetsGraphiques() {
   int nbObjetsEffaces = ObjectsDeleteAll();
   Print(nbObjetsEffaces, " objets graphiques effacés !");
}

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
   tempsEnCoursTraitement = MarketInfo(Symbol(), MODE_TIME);
   sommeCours = 0.0;
   nbTicksDansSeconde = 0;
   creerLabel(NOM_LABEL, 1,1);
   return(0);
  }
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
   effacerObjetsGraphiques();
   return(0);
  }
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   double moyenneCours = 0.0;
   tempsTickActuel = MarketInfo(Symbol(), MODE_TIME);
   if(tempsTickActuel > tempsEnCoursTraitement) {
      if(nbTicksDansSeconde > 0) { moyenneCours = sommeCours / nbTicksDansSeconde; }
      ObjectSetText(NOM_LABEL, DoubleToStr(moyenneCours, 5), TAILLE_TEXTE, FONTE, White);
      sommeCours = 0.0;
      nbTicksDansSeconde = 0;
      tempsEnCoursTraitement = tempsTickActuel;
   }
   sommeCours += obtenirCoursActuel();
   nbTicksDansSeconde++;
   return(0);
  }
//+------------------------------------------------------------------+

J'insiste sur ce code car nous allons en faire la base d'un indicateur. Cet indicateur sera simplement le cours sans bruit, sans artifices psychologiques et dénués de sens mathématique telles les clôtures. Ce sera une moyenne par chandelier, et avec bien plus de sens mathématique pour le traitement du signal que ce dont on dispose actuellement sur les graphiques. Nous y ajouterons même une information supplémentaire: l'écart-type et vous constaterez un signal dénué de bruit en le comparant avec les cours selon les clôtures. Nous allons le faire dès le prochain post.

vendredi 9 août 2013

Passage de valeur par référence en MQL4: initiation à la programmation d'experts advisors

Enfin ! Le dernier post sur l'initiation au langage MQL4. Nous allons poursuivre l'amélioration de l'expert advisor qui calcule la moyenne du cours à chaque seconde. Pour cela nous allons continuer à le restructurer avec des fonctions. On va faire deux fonctions ici. Lors de la déinitialisation nous avons trois lignes de code qui ne servent qu'à une seule chose: effacer les objets graphiques. Une seule tâche en fait ! Comme il n'y a qu'une tâche que réalisent ensemble ces instructions, c'est typiquement ce que nous devrions mettre dans une fonction. Il y a aussi la fonction start() qui est surchargée, et au milieu un groupe d'instructions a pour but de calculer la moyenne. Ça doit aussi aller dans une fonction, et en plus c'est un calcul qui se fait en plusieurs étapes. Donc à isoler dans une fonction spécialisée.

La suppression des objets graphiques:

Pour supprimer les objets graphiques, il y a une fonction MQL4 spécialisée: ObjectsDeleteAll(). Mais il faut faire quelques étapes supplémentaires: récupérer le nombre d'éléments effacés que la fonction nous renvoie et afficher ce nombre avec un message pour constater ce qui a été fait. C'est ce que font les trois lignes de code dans la fonction deinit() pour l'instant. On va isoler ces trois lignes dans une fonction, comme ça dans la fonction deinit() on dit clairement qu'on efface les objets graphiques en un appel de fonction. Il y a un avantage supplémentaire: on peut réutiliser cette fonction ailleurs, dans un autre programme. Aucun paramètre n'est nécessaire, donc la paire de parenthèses restera vide, et la fonction ne retourne aucune valeur, on la déclare alors de type 'void'. On va en plus réduire la quantité de code en condensant la première ligne (déclaration de la variable) avec la seconde (exécution de la fonction d'effacement et récupération de la valeur de retour) en une seule ligne. Le code de la nouvelle fonction est alors le suivant:

void effacerObjetsGraphiques() {
   int nbObjetsEffaces = ObjectsDeleteAll();
   Print(nbObjetsEffaces, " objets graphiques effacés !");
}

Et le code dans la fonction deinit() pour appeler cette fonction sera simplement: effacerObjetsGraphiques(); c'est tout !

Le calcul de la moyenne des valeurs d'un tableau:

Et maintenant la dernière fonction à faire, la plus attendue et la plus nécessaire: faire le calcul de la moyenne des cours à part dans sa propre fonction pour éclaircir ce code confus. Mais il y a un problème: il faut passer le tableau qui contient les valeurs en paramètre de la fonction. Il y a une chose à savoir quand on programme: c'est comment sont passés les paramètres d'une fonction. En général avec une copie de la valeur originale. Oui, une copie parce que les paramètres qu'on passe à une fonction doivent être stockés temporairement dans la pile d'appel des fonctions. Le mécanisme interne est le suivant: on exécute du code, et à un moment donné il faut aller dans une fonction et son code est ailleurs en mémoire. Il faut donc mémoriser où on en est dans le code, on place donc l'adresse mémoire où on doit revenir après la fonction dans une zone spécialisée: la pile des appels. On dit qu'on empile l'adresse de retour. Mais une fois qu'on passe au code de la fonction, elle ne sait pas où aller prendre les valeurs dont elle a besoin, car on peut venir de n'importe où. Alors on copie les paramètres de la fonction dans la pile aussi, et la fonction sait combien elle doit en prendre puisque ça la concerne. On copie donc les valeurs des paramètres dans la pile, on les empile aussi. (comme une pile d'assiettes) Et la fonction les dépile pour les utiliser. Elle dépilera à la fin l'adresse de retour qui s'était retrouvée en dessous pour pouvoir revenir là où on en était avant de passer à la fonction. Avec ce mécanisme on peur imbriquer les sauts à des fonctions: les adresses de retour et paramètres vont s'empiler les uns sur les autres, il suffira de dépiler dans l'ordre pour revenir. Mais la pile a une taille finie, on ne peut pas faire ça indéfiniment. Et avec les tableaux ça pose problème: ils sont en général très grands et peuvent facilement saturer la pile d'appel. Normal, ils sont prévus pour stocker de grandes quantités de données ! Ça pose d'autant plus problème qu'il faut en plus copier toutes les valeurs et ça prend du temps. Avec le MQL4, les tableaux seront la plupart du temps très grands: ils contiennent toutes les valeurs de l'historique des cours, des indicateurs etc. Il y a donc une autre manière de passer des paramètres à une fonction, et qui en plus peut s'avérer pratique même si le paramètre n'est pas un tableau: le passer par référence. C'est-à-dire qu'on passe non pas la valeur, mais l'adresse de l'endroit où est stocké le paramètre. On empile donc un nombre entier de 4 octets (pour nos ordinateurs actuels. Si la mémoire venait à augmenter très fortement en taille, il faudrait augmenter ça). La fonction sait que c'est une adresse qu'elle reçoit car on lui a indiqué, elle ira donc chercher la valeur au bon endroit. Comme cela pas de soucis de saturation de la pile des appels et pas de perte de temps à tout recopier inutilement. Il y a un effet intéressant en plus: comme normalement on passe un paramètre par copie, si on fait des changements dessus, ce sera sur la copie interne à la fonction et les changements ne se verront pas à l'extérieur sur la variable originale une fois que la fonction aura fini. Si le paramètre est passé par référence, la fonction va travailler sur l'original et les changements seront fait sur l'orignal qui reste une fois que la fonction a fini. Cet effet est intéressant pour tout type de paramètre. Voilà pour les explications. On indique à la fonction qu'on lui passe un paramètre par référence en mettant un esperluette (&) devant le nom. Par exemple: void nomFonction(int &parametre) { bloc de la fonction } Pour notre cas à nous, le tableau n'est pas très grand, même petit car il a seulement dix éléments à 8 octets chacun (double) et ça fait déjà 10x8 = 80 octets à empiler pour l'appel de la fonction, sans compter les autres paramètres. On va alors le passer par référence (il n'y aura que les 4 octets de l'adresse à empiler). On doit également passer un nombre qui est la quantité d'éléments à prendre dans le tableau à partir du début car on n'utilise pas toutes les cases.

double calculerMoyenne(double &valeurs[], int effectif) {
   int noElement;
   double somme = 0, moyenne = 0;
   for(noElement=0 ; noElement<effectif ; noElement++) {
      somme += valeurs[noElement];
   }
   if(noElement != 0) { moyenne = somme / effectif; }
   return(moyenne);
}

Et le code dans la fonction start() pour appeler cette fonction sera simplement:

moyenneCours = calculerMoyenne(ticksDeLaSeconde, nbTickDansSeconde);

Voilà pour cette restructuration du code. On va y voir bien plus clair. Vous allez le faire en vidéo avec moi: je vous montre et vous faites pareil. Le code complet en exemple après la vidéo.



//+------------------------------------------------------------------+
//|                                            moyenneParSeconde.mq4 |
//|                  Copyright 2013, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2013, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"

double ticksDeLaSeconde[10];
int nbTickDansSeconde;
datetime tempsEnCoursTraitement, tempsTickActuel;

double obtenirCoursActuel() {
   double cours = (Bid + Ask) / 2.0;
   return(cours);
}

void creerLabel(string nom, int x, int y) {
   bool aReussi = ObjectCreate(nom, OBJ_LABEL, 0, x, y);
   if(aReussi) { ObjectSetText(nom, "###", 10); }
   else { Print("Erreur à la création du texte: ", GetLastError()); }
}

void effacerObjetsGraphiques() {
   int nbObjetsEffaces = ObjectsDeleteAll();
   Print(nbObjetsEffaces, " objets graphiques effacés !");
}

double calculerMoyenne(double &valeurs[], int effectif) {
   int noElement;
   double somme = 0, moyenne = 0;
   for(noElement=0 ; noElement<effectif ; noElement++) {
      somme += valeurs[noElement];
   }
   if(noElement != 0) { moyenne = somme / effectif; }
   return(moyenne);
}


//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
   nbTickDansSeconde = 0;
   tempsEnCoursTraitement = MarketInfo(Symbol(), MODE_TIME);
   creerLabel("moyenne", 2, 5);
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
   effacerObjetsGraphiques();
   return(0);
  }
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   tempsTickActuel = MarketInfo(Symbol(), MODE_TIME);
   if(tempsTickActuel > tempsEnCoursTraitement) {
      double moyenneCours = calculerMoyenne(ticksDeLaSeconde, nbTickDansSeconde);
      ObjectSetText("moyenne", DoubleToStr(moyenneCours, 5), 10);
      tempsEnCoursTraitement = tempsTickActuel;
      nbTickDansSeconde = 0;
   }
   ticksDeLaSeconde[nbTickDansSeconde] = obtenirCoursActuel();
   nbTickDansSeconde++;
   return(0);
  }
//+------------------------------------------------------------------+
La fonction start() est désormais bien plus claire à lire et comprendre. Il faudrait pour parfaire ce code déplacer et classer les fonctions créées dans d'autres fichiers, du type librairie pour pouvoir être utilisées partout. Ce que nous ferons plus tard, quand nous maîtriserons mieux les bases du langage MQL4. Il faut pour l'instant pratiquer, c'est important. Nous allons donc dans les prochains posts créer des petits experts et des indicateurs pour obtenir des outils interressants avant de faire un robot complet. Cette façon de faire nous permettra premièrement de maîtriser le langage et deuxièmement de faire un peu de recherches de pistes pour l'analyse des cours et de trouver de nouvelles façons de faire. Il faut innover un peu et dépoussierer le domaine ! Je rappelle l'objectif: être rentier par le forex, ce ne sont pas des mots en l'air, jettés pour attirer du monde mais bel et bien l'objectif accessible à toute personne qui se donnera la peine d'apprendre tout ceci. Il y aura les codes de robots fonctionnels et qui rapportent de manière exponentielle fournis grauitement dans les vidéos et les posts. Mais il faudra comprendre et maîtriser le sujet pour être sûr de les faire fonctionner dans la durée et pouvoir réagir en cas de problème. Regardez bien sur web: dans les forums ceux qui achètent des robots sans réellement comprendre ce que les robots font et comment ils le font, n'arrivent pas en général à les faire fonctionner. Par contre les développeurs qui savent en programmer, en achètent aussi et arrivent à les faire fonctionner. (dans les blogs ou sites spécialisés) C'est pour cela que je vous fais apprendre tout ça: que vous soyez capable de faire fonctionner les robots que nous allons faire ici et de réagir face aux problèmes. En plus ces robots achetés ne donnent pas de miracles mais ils rapportent déjà beaucoup plus que l'intérêt miséreux annuel que vous donne votre banquier. Qui vous ment par ailleurs, il se fout de votre gueule en disant qu'il ne peut pas vous donner plus parce que c'est la crise, et que tout s'agite en bourse. Si vous avez bien compris comment ça fonctionne vous savez alors que plus ça bouge et plus ça rapporte: les gains se font avec les variations, et plus elles sont nombreuses et fortes et plus ça tombe dans la poche !

jeudi 8 août 2013

Les fonctions en MQL4: initiation à la programmation d'experts advisors

Et voilà la dernière notion à connaître pour compléter l'initiation à la programmation en MQL4: les fonctions. Vous les avez déjà vu en écrivant le code dans les fonctions init(), deinit() et start(). Vous écrivez le code de ces fonctions mais, vous ne les utilisez pas ! C'est MetaTrader 4 qui le fait au moment voulu. Vous en avez utilisé d'autres toutes prêtes qui se coloraient en violet quand vous écriviez leur nom. Elles vous font un travail, un calcul sans que vous ayez le besoin d'écrire tout le code qui fait ce travail ou calcul. Désormais vous allez écrire vos propres fonctions pour que votre code soit structuré et mieux organisé. Pour que vous puissiez réutiliser un bout de code dans d'autres programmes. Si vous deviez tout mettre dans un seul bloc d'instructions, ce serait illisible et incompréhensible tellement il y aurait des instructions à la chaine. Le principe est de fractionner les instructions, de les regrouper en fonctions de leur objectif. Une série d'instructions qui servent à un but unique, à réaliser une chose ou un calcul doit se mettre dans une fonction pour éclaircir le code et le rendre réutilisable.

Une fonction doit se déclarer, s'écrire (toutes les instructions qu'elle contient) et s'utiliser. D'abord on les utilise en écrivant leur nom avec une paire de parenthèses qui encadrent zéro, un ou plusieurs paramètres qu'elle peut recevoir pour faire son travail. Elle peut retourner une valeur, surtout si elle fait un calcul car il nous faut le résultat. Et donc le nom de la fonction avec sa paire de parenthèses et ses paramètres deviennent alors la valeur qu'elle retourne, une fois le travail ou calcul fait. Cette valeur il faut donc la récupérer dans une variable ou la placer dans un calcul. resultat = nomFonction(parametres); Dans cet exemple 'nomFonction(parametres)' est devenu la valeur de retour, et on l'affecte donc à la variable resultat.

Comme je le disais précédemment, une fonction se déclare et s'écrit. Puisque la fonction, quand on l'utilise, devient la valeur de retour, il faut la déclarer comme une variable. C'est-à-dire avec un type devant, celui de la valeur de retour. Si elle ne retourne aucune valeur, il faut mettre le type 'void', qu'on pourrait considérer comme le type 'vide'. Il faut aussi déclarer les paramètres qu'elle reçoit, si elle doit en recevoir. Ces paramètres sont des variables valables uniquement à l'intérieur de la fonction, et se déclarent comme des variables normales entre les parenthèses. On peut même leur mettre une valeur par défaut avec un égal et la valeur. Il n'y a pas obligation de passer un paramètre lors de l'appel de la fonction s'il a une valeur par défaut. resultat = nomFonction(int parametre1, string parametre2, bool parametre3=false); Dans cet exemple il sera obligatoire de fournir les deux premiers paramètres mais pas le troisième. Les paramètres ayant une valeur par défaut doivent être à la fin de la liste des paramètres. Les noms parametre1, parametre2 et parametre3 seront des variables utilisables avec les instructions de la fonction à l'intérieur de celle-ci uniquement. Et il vaut mieux les utiliser si on veut récupérer ces paramètres. En Programmation les paramètres s'appellent les arguments de la fonction.

Et une fonction s'écrit. Il faut évidemment écrire les instructions qui composent la fonction, puisqu'elle à un travail à faire, et il faut lui dire comment le faire. Après la parenthèse fermante des arguments on place une accolade ouvrante pour commencer le bloc des instructions. On peut déclarer toutes les variables dont on aura besoin, et elles seront utilisables uniquement dans la fonction, dans ce bloc. On doit aussi utiliser les arguments qui sont des variables comme les autres, s'il y en a et si on veut accéder aux paramètres qu'on a fourni à la fonction pour s'exécuter. On place évidemment les instructions qui vont faire le travail ou calcul. Puis là où on veut, en général à la fin, on place une instruction 'return' pour retourner la valeur calculée. Ce n'est pas obligatoire si la fonction est du type void. Le return signifie d'arrêter l'exécution de la fonction et de renvoyer la valeur qu'on place entre les parenthèses du return. Et à la fin on ferme l'accolade pour terminer le bloc de la fonction.
 
int nomFonction(int parametre1, string parametre2, bool parametre3=false) {
   déclarations variables si besoin;
   instruction 1;
   instruction 2;
   ...
   instruction n;
   return(valeur calculée); //(optionnel)
}

Nous allons de suite voir deux exemples sur l'expert advisor créé précédemment et qui calcule la moyenne du cours à chaque seconde. Il y a au moins quatre fonctions à faire. Nous allons en faire deux pour ce post, les deux autres seront pour le post suivant. Première chose: le calcul du cours actuel qui est la moyenne du bid et du ask, est un calcul que nous avons fait à plusieurs reprises. Nous allons le mettre dans une fonction, comme ça nous n'aurons plus qu'à appeler cette fonction, et comme son nom sera explicite, ça nous dira clairement ce qu'on fait dans le code. Il y a aussi lors de l'initialisation trois lignes de codes qui ont le même objectif ensemble: créer un label sur le graphique. Nous allons aussi en faire une fonction, et comme ça dans le code nous aurons une seule ligne pour appeler la fonction ce qui nous dira clairement ce qu'on fait. Et en plus cette création de label sur le graphique sera réutilisable dans d'autres programmes.

Le calcul du cours réel actuel par la moyenne du bid et du ask:

Cette fonction est très simple: elle ne prend pas de paramètres puisqu'elle n'utilise que Bid et Ask, les variables prédéfinies partout. Elle renvoie évidemment une valeur, le réusltat qu'on attend. On va donc la déclarer comme une valeur de type décimal: double et créer à l'intérieur une variable cours qui contient le résultat. On y affecte le résultat du calcul et on renvoie cette variable cours dans le return. La fonction est très courte:
 
double obtenirCoursActuel() {
   double cours = (Bid + Ask) / 2.0;
   return(cours);
}
Le résultat du calcul de la moyenne du bid et du ask est affecté à la variable cours (déclarée en même temps sur la ligne). Puis on renvoie cette valeur avec le return. C'est tout. Et la ligne qui avait ce calcul avant:
ticksDeLaSeconde[nbTickDansSeconde] = (Bid + Ask) / 2;
devient:
ticksDeLaSeconde[nbTickDansSeconde] = obtenirCoursActuel();

La création d'un label sur un graphique:

Cette fonction utilise les trois lignes de codes où on crée le label avec une fonction spéciale qui nous renvoie un vrai ou faux pour dire si ça a réussi. On récupère cette valeur booléenne dans une variable déclarée en même temps. Le tout sur la même ligne. Puis on teste si c'est vrai, on met alors un texte par défaut, et si c'est faux on met un message d'erreur. Mais cette fonction doit pouvoir être utilisable dans d'autres programmes, alors le nom du label "moyenne" ne peut pas être utilisé, ce sera donc un paramètre de la fonction, tout comme les coordonnées x et y. La valeur du texte par défaut doit changer aussi, car "0.00000" est très spécifique à notre cas et ce ne sera pas forcément un nombre qu'on voudra afficher. Il faut donc mettre quelque chose de neutre comme "###" pour montrer que le label est bien là. (on aurait pu faire aussi un paramètre, mais trop de paramètres tue la lisibilité de l'appel à la fonction. On peut se passer de celui-là).
 
void creerLabel(string nom, int x, int y) {
   bool aReussi = ObjectCreate(nom, OBJ_LABEL, 0, x, y);
   if(aReussi) { ObjectSetText(nom, "###", 10); }
   else { Print("Erreur à la création du texte: ", GetLastError()); }
}
La fonction ObjectCreate réalise le travail principal pour nous en utilisant la variable 'nom' qui contient le nom de l'objet qu'on veut créer passé en paramètre. On lui passe aussi les coordonnées x et y passées en paramètres. Nous récupérons juste la valeur vrai ou faux pour réagir en fonction du résultat. On crée donc la variable booléenne aReussi sur la même ligne et on lui affecte directement la valeur en sortie de fonction. On teste ensuite avec le if si ça a réussi, si oui on met le texte par défaut, les trois dièses sur l'objet graphique qui porte évidemment le nom contenu dans la variable 'nom'. Et la dernière ligne qui est le cas contraire où on pet le message d'erreur. Rien n'a changé dans cette ligne. Pour utiliser la fonction on l'appelle simplement sans récupérer aucune valeur car elle n'en retourne pas :
creerLabel("moyenne", 2, 5);

Je vous montre ces modifications en vidéo, vous les faites avec moi. Ensuite le code complet.



//+------------------------------------------------------------------+
//|                                            moyenneParSeconde.mq4 |
//|                  Copyright 2013, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2013, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"

double ticksDeLaSeconde[10];
int nbTickDansSeconde;
datetime tempsEnCoursTraitement, tempsTickActuel;

double obtenirCoursActuel() {
   double cours = (Bid + Ask) / 2.0;
   return(cours);
}

void creerLabel(string nom, int x, int y) {
   bool aReussi = ObjectCreate(nom, OBJ_LABEL, 0, x, y);
   if(aReussi) { ObjectSetText(nom, "###", 10); }
   else { Print("Erreur à la création du texte: ", GetLastError()); }
}


//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
   nbTickDansSeconde = 0;
   tempsEnCoursTraitement = MarketInfo(Symbol(), MODE_TIME);
   creerLabel("moyenne", 2, 5);
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
   int nbObjetsEffaces;
   nbObjetsEffaces = ObjectsDeleteAll();
   Print(nbObjetsEffaces, " objets graphiques effaces !");
   return(0);
  }
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   tempsTickActuel = MarketInfo(Symbol(), MODE_TIME);
   if(tempsTickActuel > tempsEnCoursTraitement) {
      int noTick;
      double sommeCours = 0, moyenneCours = 0;
      for(noTick=0 ; noTick<nbTickDansSeconde ; noTick++) {
         sommeCours += ticksDeLaSeconde[noTick];
      }
      if(noTick != 0) { moyenneCours = sommeCours / noTick; }
      ObjectSetText("moyenne", DoubleToStr(moyenneCours, 5), 10);
      tempsEnCoursTraitement = tempsTickActuel;
      nbTickDansSeconde = 0;
   }
   ticksDeLaSeconde[nbTickDansSeconde] = obtenirCoursActuel();
   nbTickDansSeconde++;
   return(0);
  }
//+------------------------------------------------------------------+
La fonction init() s'est bien allégée, elle est bien plus claire à lire et comprendre. On lit les intentions dans les noms des fonctions qu'on appelle. C'est pour cela que le nommage est très, très important. On continuera pour la fonction deinit() qui se simplifiera à l'extrême et la fonction start() qui en a bien besoin. Nous mettrons le calcul de la moyenne dans une fonction évidemment, ça allègera beaucoup. Nous verrons à l'occasion un point délicat à comprendre et à connaitre: comment passer un tableau en paramètre à une fonction, et plus généralement les deux manières de passer un paramètre à une fonction.

mercredi 7 août 2013

Les boucles for en MQL4: initiation à la programmation d'experts advisors

Enfin la dernière instruction du langage à voir ! Ce post sera plus court, et la vidéo bien plus courte aussi. Nous allons utiliser cette instruction: la boucle for pour modifier l'expert précédemment fait et qui est bien compliqué. La boucle for remplacera la boucle while dans cet expert parce qu'elle est adaptée au parcours de tableaux. Elle sert aussi s'il ny a pas de tableau, et sert surtout si on a besoin d'un compteur ou indice quelconque qui doit évoluer à chaque répétition.

L'instruction for:

L'instruction for est donc une boucle pour répéter un bloc d'instruction, avec un détail particulier: il y a dans l'histoire un indice ou un compteur qui doit évoluer à chaque répétition. Dans notre cas de l'expert précédent c'est le numéro de l'élément du tableau. Cette boucle for est pratique dans ces cas-là parce qu'elle prend en charge le compteur. Nous n'avons pas besoin de le faire évoluer par une instruction spécifique dans la boucle. On indique dans l'instruction for trois choses:

  1. l'initialisation du compteur: on donne sa valeur de départ.
  2. la condition sur ce compteur pour répéter la boucle: elle doit être vraie pour exécuter le bloc d'instructions. Dès qu'elle est fausse, le bloc n'est pas exécuté et le programme suit son court après le bloc de l'instruction for.
  3. la manière de faire évoluer ce compteur: une expression de calcul pour changer la valeur du compteur à chaque fois que le bloc d'instruction de la boucle est fini.

Voici directement un exemple: for(i=0 ; i<10 ; i=i+1) { bloc d'instructions }. Dans cet exemple on veut répéter le bloc d'instruction en commençant avec la variable i qui doit être d'abord égale à 0 et tant que cette variable i est inférieure à 10, et on fait varier i selon le calcul i = i + 1. C'est-à-dire qu'on prend i, on lui ajoute 1 et on remet le résultat dans i. En clair on augmente i de 1 à chaque fois. On peut si on veut, enlever 1 ou même ajouter 2 ou 7.3 ou faire ce qu'on veut pour le changer. Mais attention, il faut écrire quelque chose qui finira par rendre la condition fausse pour pouvoir s'arrêter ! Dans cet exemple, si on met le calcul de changement: i = i - 1, on atteindra jamais le 10 en partant de 0, puisque i ne fera que diminuer. Il vaudra -1, -2, -3 etc . La condition d'infériorité à 10 sera toujours vraie et donc on exécutera la boucle sans jamais s'arrêter.

La boucle for s'utilise donc dès qu'il faut faire une boucle avec une variable qui va évoluer avec les répétitions de la boucle. Dans ce cas, on donne trois règle à l'instruction for: la valeur de départ, la condition pour répéter et comment l'évolution doit se faire, et l'instruction for se charge de tout. Maintenant je voudrais ajouter deux types d'écritures de calculs que nous n'avons pas encore vu et qui sont très pratiques: l'incrémentation condensée avec l'affectation et la post-incrémentation. Ces écritures vont êtres très pratiques pour la boucle for, et pour d'autres cas aussi.

L'opérateur d'incrémentation et l'opérateur de décrémentation:

Vous avez remarqué, nous avons souvent besoin de faire évoluer une variable en l'augmentant de 1 ou plus. Et on fait donc l'opération avec une affectation dans la même variable. Du genre i = i + 1. Il existe pour cela un opérateur dédié: += et l'exemple s'écrit alors i += 1. Cela se traduit littéralement par ajouter 1 et réaffecter dans la même variable. Nous avons la même chose avec la décrémentation: i -= 1. On pourrait augmenter ou diminuer de n'importe quelle quantité: i += 3.

L'opérateur de post-incrémentation ou décrémentation:

Il y a parmi les incrémentations ou décrémentations précédentes un cas qui est très très utilisé: l'incrémentation ou décrémentation de 1. Il y a alors l'opérateur dédié: ++, et on note l'opération sur la variable comme ça: i++ ce qui est très condensé. (Il y a la même en i--) Cette notation est très souvent utilisée pour la troisième règle de la boucle for, à la place de i = i + 1. i++ signifie tout simplement "augmenter i de 1". De même pour i-- qui signifie diminuer i de 1.

Amélioration de l'expert des moyennes du cours par seconde:

Nous allons commencer à modifier l'expert précédemment fait pour rendre le code plus compact et compréhensible. (nous continuerons encore dans les posts suivants) La boucle while va donc être remplacée par une boucle for puisqu'elle contient un compteur qui doit être incrémenté à chaque passage. C'est typiquement ce que gère une boucle for. Du coup l'instruction d'incrémentation de noTick dans la boucle ne sert plus à rien, et il vaut mieux l'enlever car elle va même perturber en incrémentant une fois de trop à chaque passage. On va donc la supprimer. On en profite aussi pour remplacer les incrémentations dans tout le code par les opérateurs qui conviennent. La boucle while sera donc remplacée par:

for(noTick=0 ; noTick<nbTickDansSeconde ; noTick++) {
   sommeCours += ticksDeLaSeconde[noTick];
}
Du coup lors de la déclaration de noTick deux lignes plus haut, nous n'avons plus besoin de la mettre à zéro, car la boucle for s'en charge. Remarquez que j'ai utilisé la post-incrémentation dans la troisième règle de la boucle for et l'opérateur d'incrémentation pour la somme des cours dans la boucle. La dernière instruction de la fonction start(), juste avant le return passe de:

   nbTickDansSeconde = nbTickDansSeconde + 1;
à ceci :

   nbTickDansSeconde++;
Les améliorations ne sont pas très nombreuses pour le moment, mais c'est déjà ça ! Vous allez le pratiquer avec moi en vidéo, ce sera assez rapide.


La prochaine fois nous ferons de plus grosses améliorations avec une dernière notion à travailler pour faire le tour complet de l'initiation à MQL4: les fonctions.

mardi 6 août 2013

Les tableaux en MQL4: initiation à la programmation d'experts advisors

Avant de passer à la dernière instruction, on va voir l'une des deux dernières notions à connaître en programmation, et appliquée ici au langage MQL4. La dernière instruction à voir est très liée aux tableaux, et j'ai beau chercher des exemples pratiques (qui ont du sens, pas les exemples bidons classiques pour apprendre et qui ne servent strictement à rien), mais ceux qui me viennent avec cette instruction sans les tableaux, sont trop compliqués pour une initiation. Déjà que les posts précédents ont eu des exemples assez copieux. J'ai donc décidé de vous présenter les tableaux avant.

Un tableau qu'est-ce-que c'est ? Il s'agit d'une variable, c'est-à-dire un emplacement en mémoire pour stocker des données, qui contient plusieurs données du même type alignées les unes à côté des autres. Donc dans une même zone de la mémoire, on place un petit ou grand nombre de valeurs du même type. Toutes ces valeurs portent donc le même nom puisqu'elles sont regroupées dans la même variable, et elles sont accessibles individuellement avec un numéro. Ce numéro, ou indice est un nombre entier qui commence à zéro ! Attention, IL COMMENCE A ZERO ! C'est une faute classique de débutants et même des expérimentés, que d'oublier ça quand on code (et c'est facile car on a plein de choses à penser). On appelle donc une valeur bien précise en appelant la variable avec un indice entre crochets: nom_tableau[indice]. La création et l'initialisation des tableaux se fait de plusieurs manières. Nous commencerons avec la plus simple: on déclare le type, le nom, des crochets dans lequels on indique le nombre d'éléments qu'on veut y stocker. Par exemple: double ticks[100]; Ici on déclare un tableau appelé ticks, qui pourra contenir 100 nombres décimaux (double) et ils seront numérotés de 0 à ... 99 ! Et on l'utilise de cette manière: ticks[4] = 1.2345; où on fixe la valeur du cinquième (eh oui, la numérotation commence à zéro) à 1,2345.

Comme nous utiliserons souvent, très souvent les tableaux j'aurai le temps de bien vous expliquer en long en large et en travers ! Donc nous allons passer immédiatement à l'exemple d'un EA. Celui que je vais vous présenter est un peu complexe pour débuter, mais nous y reviendrons plusieurs fois et ce, dès le post suivant, pour l'améliorer, et pour que vous puissiez mieux le comprendre et le maîtriser. Je considère cet exemple important car il nous conduira à un indicateur très utile et mathématiquement plus valable que ceux existants. (j'expliquerai pourquoi). Le programme que nous allons faire va nous donner la moyenne arithmétique des ticks de chaque seconde. En général, dans la journée, il y a plusieurs ticks par seconde (2 à 3), nous allons donc ne retenir que la moyenne du cours à chaque seconde. Nous aurons alors un découpage temporel par seconde du cours filtré des bruits internes à la seconde. l'EA que nous allons faire n'enregistrera pas la moyenne de chaque seconde, il ne fera que les afficher. Ce qui nous permettra de voir des fonctions d'affichage. Alors ... comment résoudre ce problème ...?

Premier réflexe: nous allons enregistrer les cours de la même seconde dans un tableau. Nous en prendrons un de 10 éléments ce qui devrait être suffisant. C'est le problème avec les tableaux en général: il faut fixer la taille à l'avance, même si on ne sait pas encore la taille qu'il nous faut. Et les langages qui permettent de modifier la taille en cours de traitement ne sont pas nombreux et s'ils le font, il faut savoir que ça coûte cher en temps pour le processeur et en mémoire. Agrandir un tableau, ça demande de réserver une autre place en mémoire (en général beaucoup) et de copier toutes les valeurs. (ça prend du temps s'il y en a beaucoup). Il vaut donc mieux éviter de s'amuser à ce genre de choses, surtout que pour le forex, les tableaux sont très grands: ils contiennent les cours et autres valeurs calculées par les indicateurs.

double ticksDeLaSeconde[10];

Je disais donc qu'il faut enregistrer les valeurs du cours de la même seconde dans un tableau. Mais il faut pouvoir déterminer dans quelle seconde on est ! Il y a pour cela une fonction qui nous donnera le dernier temps envoyé par le serveur, celui qui correspond à la dernière mise à jour du cours. Ces valeurs de temps sont du type datetime. Ces valeurs sont des nombres entiers et elles correspondent au nombre de secondes écoulées depuis le 1er janvier 1970. (en informatique on nomme ça un timestamp, soit en français: horodatage.) Donc quand cette valeur de type datetime augmente de 1, cela signifie qu'il y a une seconde de plus. Il suffit donc de stocker la valeur du temps de la seconde en cours de traitement et de la comparer à celle du tick qui vient d'arriver, si c'est la même, on enregistre dans le tableau une valeur supplémentaire du cours. Dans le cas où elle n'est pas la même, elle est forcément plus grande ( ... quoi que ? Imaginons qu'un paquet qui transite sur internet fasse un chemin bien plus long et arrive après le tick suivant !!! Il faudra veiller à ça ) Je disais qu'elle est normalement plus grande, donc dans ce cas on calcule la moyenne des valeurs dans le tableau, ensuite on remet les compteurs à zéro et on enregistre la nouvelle valeur du cours de la nouvelle seconde qui vient d'arriver au début du tableau, et surtout on dit que le datetime en cours de traitement c'est celui qui vient d'arriver. Ne pas oublier ce détail!

datetime tempsEnCoursTraitement, tempsTickActuel;

tempsEnCoursTraitement = MarketInfo(Symbol(), MODE_TIME);

Dernier point du traitement principal: il faut nécessairement un compteur pour dire où nous en sommes dans le tableau, et ce compteur indiquera lorsque nous ne serons plus dans la même seconde combien de tick il y aura eu dans la seconde qui était en cours de traitement, ce qui nous permettra de calculer la moyenne.

int nbTickDansSeconde;

La moyenne ! Parlons-en, il faut la calculer. Comment faire ? Nous avons donc un tableau de 10 places maximum, qui contient des valeurs décimales en partant de l'indice 0. Nous avons aussi un compteur qui dit combien de valeurs il y a eu dans la seconde. On va donc additionner toutes ces valeurs puis diviser la somme par le nombre de valeurs additionnées. Ça c'est facile, voyons juste comment les additionner. On va prendre une somme qu'on initialise à zéro, puis on parcours le tableau et on additionne à cette somme la valeur rencontrée à chaque fois dans la case du tableau. Cette case du tableau est désignée par un compteur, et pas le même que le précédent car le compteur dont on a parlé juste avant contient le nombre de valeurs de la seconde et il doit nous servir pour la division. Il nous faut donc un autre compteur qu'on initialise à zéro et qui va parcourir les cases du tableau de zéro jusqu'au nombre de valeurs à additionner, c'est le compteur précédent qui la contient. Attention on ne doit pas aller jusqu'à l'égalité avec cette valeur car les numéros des cases du tableau sont décalés, ils commencent à zéro. Pour calculer cette moyenne on prendra une boucle while qu'on a vu précédemment, la condition pour répéter sera que le compteur qui part de zéro doit être inférieur au nombre de ticks de la seconde en cours de traitement. Dans la boucle on additionne la somme partielle avec la valeur de la case désignée par le compteur, et on n'oublie surtout pas d'incrémenter le compteur de 1, sinon on reste sur la même case et la boucle est infinie. Un détail supplémentaire, il n'est pas sûr qu'on rentre dans la boucle, donc si on effectue le calcul après, ce sera peut-être avec une division par zéro donc on met une condition if pour pouvoir calculer. (j'ai ajouté ça après parce que j'avais des bugs et des divisions par zéro !) On fait donc notre somme par une boucle while, puis le calcul de la moyenne en divisant la somme par le nombre de valeurs, si on ne divise pas par zéro.

int noTick = 0;
double sommeCours = 0, moyenneCours = 0;
while(noTick < nbTickDansSeconde) {
   sommeCours = sommeCours + ticksDeLaSeconde[noTick];
   noTick = noTick + 1;
}
if(noTick != 0) { moyenneCours = sommeCours / noTick; }

Précision pour la fontion marketInfo(), elle demande pour donner une valeur, de préciser sur quelle paire on veut la valeur et quelle type de valeur on désire. Pour la paire, on utilise la fonction Symbol() qui donne la paire sur laquelle on a lancé l'expert. Pour le type de valeur, c'est une constante, ici MODE_TIME. C'est une variable prédéfinie dont la valeur ne change pas, donc ici MODE_TIME est le nom d'une valeur numérique de type entier et cette valeur signifie pour la fonction qu'on veut l'horodatage du tick. On pourrait et on le fera, fournir d'autres constantes pour avoir d'autres types de valeur de la part de la fonction MarketInfo(). On pourrait tout aussi bien demander une valeur sur une autre paire de devises que celle sur laquelle l'expert a été lancé. Il faudrait mettre le symbole de la paire sous forme de texte.

La valeur de la moyenne nous allons pour l'instant seulement l'afficher sur le graphique. Nous allons donc créer un objet de type Label lors de l'initialisation de l'expert, y mettre une valeur 0.00000 pour commencer, puis dans la fonction start(), à chaque fois qu'une moyenne est calculée, on la met dans l'objet Label. Il faudra au passage convertir un nombre de type double en texte avec la fonction doubleToStr() qui demande deux arguments: la valeur à convertir et le nombre de décimales à prendre, ici 5. Les fonctions employées ici ont des noms très explicites. Pour créer un objet graphique la fonction ObjectCreate() demande un nom unique à l'objet, le type d'objet, le numéro de la fenêtre dans laquelle le faire ( dans chaque graphique de paire, il y a une fenêtre principale numéro 0 et on peut ajouter, ce que font certains indicateurs, d'autres fenêtres qui prendront les numéros suivants), la position Y et X en pixels pour placer ce texte à partir du coin gauche haut. Ces coordonnées seront transformées par des multiplicateurs, donc ne vous étonnez pas de voir le texte ailleurs qu'à la position (2,5). La fonction de création renvoie une valeur vrai ou faux pour dire si ça a réussi. On déclare donc une variable pour la recevoir en même temps qu'on appelle la fonction. Après on teste si ça a réussi, si oui, on y entre un texte avec la fonction ObjectSetText() qui demande le nom de l'objet, la valeur sous forme de texte et la taille des caractères à afficher. Si non, on affiche qu'il y a eu une erreur et on précise le numéro de cette erreur avec la fonction GetLastError() qui nous le donne. (une fois que cette fonction a donné le numéro de l'erreur, elle ne le redonnera plus ! Attention !).

bool aReussi = ObjectCreate("moyenne", OBJ_LABEL, 0, 2, 5);
if(aReussi) { ObjectSetText("moyenne", "0.00000", 10); }
else { Print("Erreur à la création du texte: ", GetLastError()); }

ObjectSetText("moyenne", DoubleToStr(moyenneCours, 5), 10);

Ouf! Ça fait déjà beaucoup tout ça ! Je vous propose de le faire avec moi en vidéo, où je redonne toutes les explications au fur et à mesure. Ensuite vous avez le code complet en modèle. Accrochez-vous, même si vous êtes un peu perdus car de toute façon on le reprendra dans les posts suivants pour le rendre plus clair, et avec des explications supplémentaires vous comprendrez mieux. Il n'y a pas de secrets, il faut faire un peu d'efforts devant la difficulté pour progresser. Je peux vous le garantir par expérience. Quand c'est plus difficile et qu'on fait les efforts pour y arriver, on progresse vite.


Et voici le code complet comme promis:


//+------------------------------------------------------------------+
//|                                            moyenneParSeconde.mq4 |
//|                  Copyright 2013, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2013, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"

double ticksDeLaSeconde[10];
int nbTickDansSeconde;
datetime tempsEnCoursTraitement, tempsTickActuel;

//+------------------------------------------------------------------+
//| expert initialization function                                   |
//+------------------------------------------------------------------+
int init()
  {
   nbTickDansSeconde = 0;
   tempsEnCoursTraitement = MarketInfo(Symbol(), MODE_TIME);
   bool aReussi = ObjectCreate("moyenne", OBJ_LABEL, 0, 2, 5);
   if(aReussi) { ObjectSetText("moyenne", "0.00000", 10); }
   else { Print("Erreur à la création du texte: ", GetLastError()); }
   return(0);
  }
//+------------------------------------------------------------------+
//| expert deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
   int nbObjetsEffaces;
   nbObjetsEffaces = ObjectsDeleteAll();
   Print(nbObjetsEffaces, " objets graphiques effaces !");
   return(0);
  }
//+------------------------------------------------------------------+
//| expert start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   tempsTickActuel = MarketInfo(Symbol(), MODE_TIME);
   if(tempsTickActuel > tempsEnCoursTraitement) {
      int noTick = 0;
      double sommeCours = 0, moyenneCours = 0;
      while(noTick < nbTickDansSeconde) {
         sommeCours = sommeCours + ticksDeLaSeconde[noTick];
         noTick = noTick + 1;
      }
      if(noTick != 0) { moyenneCours = sommeCours / noTick; }
      ObjectSetText("moyenne", DoubleToStr(moyenneCours, 5), 10);
      tempsEnCoursTraitement = tempsTickActuel;
      nbTickDansSeconde = 0;
   }
   ticksDeLaSeconde[nbTickDansSeconde] = (Bid + Ask) / 2;
   nbTickDansSeconde = nbTickDansSeconde + 1;
   return(0);
  }
//+------------------------------------------------------------------+

Rassurez-vous, dès le prochain post sur la dernière instruction à connaître, nous remanierons ce code un peu confus, et on continuera encore sur le suivant. Vous aurez l'occasion de le voir et revoir ce code; vous le connaîtrez par cœur, et il sera plus agréable à lire et plus facile à comprendre.