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.

1 commentaire:

  1. Bonjour,
    J'aimerais discuter avec un passionné de E.A sur un projet que j'ai à coeur et du forex en général.
    Mon mail est : clecob@hotmail.fr
    Bien cordialement
    Julien

    RépondreSupprimer