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.

Aucun commentaire:

Enregistrer un commentaire