vendredi 21 février 2014

Le numéro d'une période (bar) en MQL4 partie 1 / 2

Metatrader nous met à disposition une variable prédéfinie, bien pratique: Bars. Cette variable disponible n'importe où dans le code contient le nombre de périodes actuellement dans l'historique. Si on a besoin de parcourir l'historique, il faut l'utiliser pour connaître le nombre de périodes dans cet historique. Elle pose cependant un problème. J'ai vu des codes utilisant la variable prédéfinie Bars comme indicateur de changement de période. Et je l'ai même, au début, utilisée pour établir un numéro temporel à chaque période comme si c'était un numéro de référence temporelle absolu. J'ai vite déchanté.

Le problème de la variable prédéfinie Bars est qu'elle ne représente que le nombre de périodes (bar) dans l'historique. Et ce nombre varie avec la taille de l'historique, qui elle dépend de la gestion interne de Metatrader. C'est-à-dire que ce nombre ne va pas forcément augmenter. Par expérience personnelle, je peux dire qu'il faut faire attention, et notamment quand on utilise ce nombre pour déterminer un numéro de période où pour déterminer s'il y a une nouvelle période ( Bars n'augmente pas forcément ). Pour déterminer un numéro de période, il faut se baser sur les horodatages, ceux-ci proviennent du serveur et sont associés aux données du cours et indissociables de ces cotations. A chaque tick qu'on reçoit, il s'agit surtout de lire la cotation, mais cette information, le serveur la fournie liée à un horodatage indissociable. La cotation a une position temporelle bien définie, sinon elle ne veux rien dire du tout.

Le principe du calcul:


Prenons par exemple une période qui durerait 5 secondes, cela signifierait que toutes les 5 secondes il y aurait une nouvelle période. C'est-à-dire que les secondes dont l'horodatage serait 0, 1, 2, 3 et 4 appartiennent à la période n°0, que les secondes n° 5, 6, 7, 8 et 9 appartiennent à la période n° 1, et que les secondes n° 10, 11, 12, 13 et 14 appartiennent à la période n° 2, etc. Vous constatez que dans les numéros des secondes de la période n°0, il y a 0 fois le nombre 5. On ne peut pas retirer le nombre 5 de ces numéros. Pour ceux de la période n°1, on peut par contre retirer 1 fois le nombre 5: 5-5=0, 6-5=1, 7-5=2, 8-5=3 et 9-5=4. Et 2 fois le nombre 5 pour ceux de la période n°2: 10-2x5=0, 11-2x5=1, 12-2x5=2, 13-2x5=3 et 14-2x5=4. Arithmétiquement c'est faire une division euclidienne (entière) c'est-à-dire une division dont on ne prend que la partie entière du résultat, soit le nombre de fois qu'on peut retirer le diviseur entier (les résultats des soustractions précédentes sont les restes de ces divisions, c'est ce qu'il reste qui n'est plus assez grand pour y retirer encore le diviseur). Faisons les divisions décimales et voyons:
  • 15=0,2 dont la partie entière est 0.
  • 25=0,4 dont la partie entière est 0.
  • 45=0,8 dont la partie entière est 0.
  • 55=1 dont la partie entière est 1.
  • 75=1,4 dont la partie entière est 1.
  • 95=1,8 dont la partie entière est 1.
  • 105=2 dont la partie entière est 2.
  • 115=2,2 dont la partie entière est 2.
  • 375=5,4 dont la partie entière est 5.
  • 3785=75,6 dont la partie entière est 75.
Les parties entières de ces divisions sont donc les numéros des périodes, et de manière absolue si on utilise les horodatages fournis par le serveur. Et les numéros de périodes seront donc très grands car les horodatages le sont encore plus. Évidemment ces numéros de périodes dépendent de la taille desdites périodes, et c'est normal car depuis le 1er janvier 1970 il y a plus de périodes de 60 secondes que de périodes à 300 secondes (Cinq minutes). Si on divise par 300, nous obtiendrons évidemment des numéros plus petits.

La fonction MathFloor():


Certains langages de programmation proposent d'obtenir directement la partie entière de la division euclidienne et d'autres non. Ici ont peut utiliser deux méthodes:
  1. Approche triviale: division de nombres décimaux avec pour résultat un nombre décimal dont on pourra extraire la partie entière avec la fonction MathFloor().
  2.  Utilisation du casting implicite par une division entre nombres entiers donnant un résultat entier.
On voit donc de suite l'approche que je qualifie de triviale car c'est celle qui vient à l'esprit en premier (la deuxième ne m'est venue que récemment). Quand on fait une division d'un nombre par un autre on s'attend en général à obtenir un nombre avec des décimales. D'où la logique qui vient à l'esprit du programmeur d'utiliser une fonction arithmétique très souvent disponible:  MathFloor() . On passe en paramètre à cette fonction un nombre avec des décimales et elle nous retourne le même nombre sans les décimales. Donc pour notre problème, il suffit de faire la division de l'horodatage du tick, qui est le nombre de secondes depuis le 1er janvier 1970, par le nombre de secondes dans la période. On obtient alors un nombre fractionnaire qu'on passe à la fonction pour avoir en retour ce même nombre débarrassé de ses décimales, et qui se trouve donc être le numéro de la période depuis le 1er janvier 1970. Du coup ce numéro est vraiment une référence qui ne risque pas de varier pour ce tick-là, à la différence de Bars. Les lignes de code pour ceci:

double noPeriodeFractionnaire = horodatage / nbSecondesDansPeriode;
int noPeriode = MathFloor(noPeriodeFractionnaire);


divisions entre entiers:


Quand on a fait le code précédent, on a fait deux erreurs car on a oublié deux choses que le langage MQL4 fait et qui, ici se compensent:
  1. La fonction  MathFloor()  prend un nombre décimal en paramètre du genre 12.3 ou 4.157 mais retourne aussi un nombre décimal, c'est-à-dire que le résultat qu'elle retourne est du genre 12.0 ou 4.0 (type double). Or nous voulons 12 ou 4, des nombres entiers (type int).
  2. Le langage MQL4 réalise un cast implicite. Le cast est un changement de type de données opéré par le compilateur qui rajoute des instructions pour le processeur que nous n'avons pas mises et cela pour convertir le type de donnée. Dans notre cas, quand nous déclarons une variable de type int et que nous lui demandons d'y mettre la valeur 4.0 du type double, le compilateur va se débrouiller pour convertir le type et le 4.0 devient un 4. Mais ! En y réfléchissant bien, la fonction  MathFloor()  ne sert à rien ! On aurait tout aussi bien pu faire la division et demander en même temps de stocker le résultat dans une variable entière ce qui aurait provoqué la suppression des décimales ! Au final le cast implicite nous sauve la mise car nous avons comme résultat un nombre décimal et qu'on ne voulait pas ça, et que le cast convertit dans le bon type. On va continuer dans la réflexion et ceux qui l'ont testé le savent: les opérations aussi dépendent des types. Lors d'une opération arithmétique avec MQL4, s'il n'y a que des nombres entiers le résultat est entier et dès qu'un nombre décimal est en jeu, le résultat est décimal. Il y a un cast implicite aussi pour les opérations arithmétiques. Quand nous divisons deux nombres entiers, le résultat sera entier: 72=3 à la différence de la même division avec au moins un nombre décimal: 72.0=3.5 qui donne un résultat décimal. Or la division qu'on souhaite faire se fait entre deux nombres entiers ! Conclusion la fonction d'extraction de partie entière ne sert à rien ! La ligne de code que nous devons mettre est simplement
    
    int noPeriode = horodatage / nbSecondesDansPeriode;
    
    
Je me suis sérieusement fait avoir par ce genre de choses: j'avais un code assez copieux, qu'il a fallu tout décortiquer pour trouver le problème. Et c'était une division qui me donnait toujours zéro au lieu d'un nombre fractionnaire entre 0 et 1. =D Vous comprenez l'origine du bug ! Prévu initialement en un seul post, ça commence à faire long, je vais donc vous proposer la suite au post suivant. Prenez le temps d'assimiler ce qu'on vient de voir. A chaque jour suffit sa peine.

Aucun commentaire:

Enregistrer un commentaire