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 !

Aucun commentaire:

Enregistrer un commentaire