mardi 21 janvier 2014

un fichier include d'arithmétique temporelle et horodatages

Je vous propose dès à présent d'organiser votre code et en même temps je vous fourni du code réutilisable dans vos projets. Le but est de mettre dans un fichier des fonctions qui peuvent servir à plusieurs experts advisors ou indicateurs de Metatrader que vous créez. Ces fichiers sont des fichiers "include". Nous allons en faire plusieurs à l'avenir, et je vous propose ici de débuter un fichier "include" consacré à l'arithmétique temporelle. Il contiendra de nombreuses fonctions qui serviront à des calculs ou traitements sur le temps et le type datetime.

Les fichiers include .mqh:


On les nomme ainsi car on fait appel à la directive du préprocesseur #include pour les inclure dans un programme qui va utiliser une ou des fonctions intégrées à cet "include". Ces fichiers portent l'extension ".mqh" pour MetaQuotes Header, car du point de vue programmation, il s'agit de fichiers headers qui normalement ne devraient contenir que des déclarations. Et là j'ai fait un choix personnel, critiquable et j'attends les remarques, d'utiliser ces fichiers pour non seulement déclarer les fonctions mais aussi y mettre tout le code des fonctions. Et ceci au lieu d'utiliser la directive #import qui permet d'importer des fonctions une par une. Je ne jette pas la pierre aux programmeurs de MetaQuotes car créer un langage est quelque chose de très très conséquente. C'est un gros projet pas facile du tout et je leur tire mon chapeau pour la création d'un langage. Mais le fait est que Le langage MQL4 n'est pas très aboutit. Ils ont fait des choix techniques en fonction de leurs moyens humains et temporels, et il y a quelques défauts notables, dont celui de la création de bibliothèques de fonctions qui normalement devrait aboutir à l'utilisation de la directive #import pour sélectionner juste la fonction qu'on veut, mais je n'ai pas trouvé comment forcer le compilateur à produire l'exécutable alors qu'il n'y a pas de fonction init(), deinit() et start() dans le code. Oui, lorsqu'on créé une librairie de fonctions, on écrit un fichier qui ne contient que des fonctions à utiliser séparément et qui n'est pas destiné à être exécuté dans son ensemble sur une paire de devises comme un expert advisor ou un indicateur. Il n'y a donc pas à mettre les fonctions init(), deinit() et start(). Mais Le soucis c'est que le compilateur les réclame pour produire l'exécutable. Et en plus quand on réussit à produire l'exécutable quand je mets ces fonctions (enfin surtout start()) qui ne doivent surtout pas être là, il faut charger en mémoire la totalité de la librairie pour utiliser qu'une seule fonction, quand un expert advisor ou un indicateur y fait appel. Mon choix est de placer ces fonctions dans un header qui normalement n'est pas fait pour ça, mais le compilateur me signalera en warning seulement toutes les fonctions qui ne sont pas utilisées par le code et qu'il n'a pas mis dans l'exécutable. Du coup ce sera plus léger en mémoire lors du fonctionnement. Je dois juste faire attention aux collisions de noms en choisissant des noms de fonctions dont je suis sûr qu'ils ne seront pas utilisés ailleurs, donc des noms suffisamment précis. Ah ! ... Le nommage ! =D (Autre défaut de ce langage: la limitation des noms à 31 caractères ! Ce qui m'oblige à faire des entorses à ce qu'on appelle le "clean code", voir le livre du même nom pour les expérimentés en programmation). Peut-être quelque chose m'échappe sur l'utilisation des fichiers librairies qui doivent normalement être utilisés à la place des fichiers headers dans ce cas. Je finirai par le comprendre. Un fichier include se réalise en sélectionnant "include" dans l'assistant de nouveau fichier.

Le préprocesseur:


J'en reviens aux fichiers headers. Ils contiennent donc des déclarations de constantes, des inclusions d'autres fichiers, des imports etc. Le principe de ces fichiers est d'être inclus dans d'autres. Pour cela il y a ce qu'on appelle le préprocesseur. Il s'agit d'un programme qui va être lancé automatiquement juste avant le compilateur pour scanner le fichier source qu'on veut compiler à la recherche de directives qui sont des instructions pour lui. Ces directives doivent être seules sur une ligne et commencer par le signe dièse: #include <fichier-a-inclure.mqh> et il n'y a pas de point virgule à la fin d'une directive car ce n'est pas une instruction pour le compilateur, et elle ne sera plus là quand le compilateur passera. Il y aura à la place le code du fichier inclus. Cette directive indique au préprocesseur d'y mettre à la place le contenu du fichier entre les chevrons.

horodatage et MarketInfo():


Je le rappelle car on l'a vu dans le post précédent, l'horodatage est un nombre entier qui désigne le nombre de secondes écoulées depuis le 1er janvier 1970 à 00:00:00 GMT. Ce nombre on ne l'invente pas ! On peut le calculer pour une date précise, mais il faudra tenir compte des années bisextiles, et autres détails gênants du même type, et ça ... je ne pense pas que vous y teniez ! :D Surtout que les horodatages, on ne peut pas les deviner, ils doivent être liés à une cotation et c'est seulement le serveur qui peut nous les donner. Il y a donc une fonction qu'on a vu qui peut récupérer le dernier temps reçu avec la dernière cotation. C'est
MarketInfo(Symbol(), MODE_TIME);

Le premier paramètre pourrait tout aussi bien être remplacé par "EURUSD" ou "GBPJPY" par exemple car Symbol() donne la même chose pour la paire de devise sur lequel est lancé le programme. Le deuxième paramètre est une constante définie par Metatrader, c'est-à-dire un nombre fixe qui indique qu'on veut le dernier horodatage reçu.

La fonction TimeCurrent():


La fonction précédente est à connaitre car on va l'utiliser à de nombreuses reprises pour diverses informations sur le marché, mais il y a plus simple pour obtenir le dernier horodatage reçu du serveur pour la paire de devises courante: c'est la fonction TimeCurrent(). On va donc créer une fonction qui va l'exploiter pour nous donner le dernier temps reçu pour une paire de devise. Mais aussi exploiter la possibilité de demander cette information pour n'importe quelle paire. Cette paire de devise sera passée en paramètre avec une valeur par défaut. Avec une valeur par défaut, nous ne sommes pas obligés de fournir une valeur en paramètre à la fonction pour l'appeler, et ce sera souvent le cas. Attention on serait tenté de mettre Symbol() à cette valeur par défaut, mais le compilateur ne va pas l'accepter car il lui faut quelque chose de constant et cette fonction donne nécessairement une valeur non constante. On va donc mettre la valeur "NULL" par défaut et si c'est la valeur du paramètre alors on utilise TimeCurrent() sinon on utilise MarketInfo() avec la paire passée en argument. Et cette fonction doit retourner un nombre entier qui est le numéro du dernier temps connu. On ne peut même pas utiliser la constante NULL parce que le compilateur ne l'accepte pas. Et pourtant ce serait bien pratique, c'est un beau défaut du langage MQL4. On utilise alors la même chose sous forme de chaine de caractères, mais ce n'est pas une bonne façon de régler le problème et si quelqu'un a une idée, qu'il la propose en commentaire. Les types int et datetime ne sont pas complètement compatibles, on va donc utiliser le type le plus précis puisqu'il y a un problème avec les valeurs négatives des entiers qui n'existent pas pour les horodatages. Les entiers vont de environ -2 milliards à +2 milliards, alors que les horodatages vont de 0 à 4 milliards environ. Si on laisse faire le cast, il y aura sûrement un problème à la conversion avec les horodatages supérieurs à 2 milliards qui seront mis en négatif. Voici le code de la fonction:

datetime obtenirNoDerniereSecConnue(string paire="NULL") {
   datetime horodatage;
   if(paire=="NULL") { horodatage = TimeCurrent(); }
   else { horodatage = MarketInfo(paire, MODE_TIME); }
   return(horodatage);
}

Le tutoriel vidéo et le code du fichier include:




//+------------------------------------------------------------------+
//|                                       ArithmetiqueTemporelle.mqh |
//|                  Copyright 2014, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"

//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| DLL imports                                                      |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| EX4 imports                                                      |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| includes                                                         |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| variables globales                                               |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| constantes globales                                              |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| fonctions comptage                                               |
//+------------------------------------------------------------------+

datetime obtenirNoDerniereSecConnue(string paire="NULL") {
   datetime horodatage;
   if(paire=="NULL") { horodatage = TimeCurrent(); }
   else { horodatage = MarketInfo(paire, MODE_TIME); }
   return(horodatage);
}

//+------------------------------------------------------------------+

Nous n'avons qu'une fonction pour l'instant dans cet include mais il va très vite s'étoffer, et ce, dès les posts suivants. Nous allons d'abord créer un fichier de tests pour les fonctions de ce fichier include. Et nous créerons d'autres fonctions pour celui-là et d'autres fichiers include dans la foulée.

 PS: Cette fonction peut être critiquée aussi, car elle n'existe que pour nous donner un nombre qu'on aurait pu avoir plus simplement en utilisant directement les fonctions MQL4 qu'elle utilise. D'autant plus que son code n'est là que pour vérifier si on a passé le paramètre et choisir la fonction MQL4 correspondante. Son avantage unique est qu'elle permet de renommer une action grâce à son nom ce qui rend le code extrêmement lisible et compréhensible, ce qui nous arrange pour résoudre notre problème de programmer un robot qui rapporte des sous. Du point de vue traitements informatiques, elle n'arrange rien du tout, et alourdit même le travail du microprocesseur.

lundi 20 janvier 2014

Le type Datetime de MQL4

Chaque langage de programmation a ses types de données, mais rares sont ceux qui ont le temps en type de données. ici le temps est très important, on le manipule tout le temps car nos données sont des valeurs de cours qui ont toutes une position dans le temps. Le type associé à ces positions est le type Datetime.

Le type Datetime


il s'agit en réalité d'un nombre entier codé sur 4 octets, soit environ 4 milliards de valeurs différentes possibles. Ce nombre entier représente le nombre de secondes écoulées depuis le 1er janvier 1970. C'est un horodatage du type EPOCH des systèmes d'exploitations UNIX (prononcez "épok", il s'agit de la transcription phonétique anglaise du mot français "époque"). Et actuellement en 2014, nous en sommes à environ 1,3 milliards de secondes écoulées, presque 1,4 milliards. Facile à estimer: 60 sec / min, soit 60 x 60 = 3 600 sec / h, soit 24 x 3 600 = 86 400 sec / jour, soit 86 400 x 365 = 31 536 000 sec / an, soit finalement pour 44 ans du 1er janvier 1970 au 1er janvier 2014: 31 536 000 x 44 = 1 387 584 000 secondes. On le déclare en MQL4 avec le mot-clé datetime.

Les constantes littérales Datetime


Mais ! C'est pas du tout pratique à manipuler, surtout si on veut créer une date précise ! Rassurez-vous, vous n'avez aucune conversion à faire, donc rien à calculer dans ce genre-là, et pour deux raisons:
  1. La plupart du temps vous n'avez pas de date à créer, vous utilisez les dates fournies par MetaTrader déjà sous forme de nombre entier à environ 1,4 milliard.
  2. Au cas où vous avez besoin de créer une date précise, vous utiliser une forme littérale dans laquelle vous exprimez la date sous forme courante et MetaTrader fait lui-même les conversions. C'est ce qu'on va voir de suite.
Pour écrire une date que vous décidez, il faut écrire du texte entre guillemets simples (obligatoirement, pas de doubles !) avec un D majuscule accolé devant. Et cette date est simplement écrite de façon lisible comme l'afficherait une horloge digitale. Attention les dates sont écrite dans l'ordre du poids le plus fort d'abord (année, mois puis jour). Si la fin de l'heure ou toute l'heure est omise, la fin ou toute l'heure est considérée comme égale à zéro, et si la date est omise, elle est considérée comme celle du jour de compilation (quand vous cliquez sur le bouton Compiler). Exemples:
  •  D'2014.01.17 13:29:47' pour le 17 janvier 2014 à 13h29min47sec.
  • D'1980.05.14 03:54' pour le 14 mai 1980 à 3h du matin 54 min et 0 seconde.
  • D'2000.02.01 08' pour le 1er février 2000 à 8 heures 0 min et 0 secondes.
  • D'2010.07.14' pour le 14 juillet 2010 à minuit pile.
  • D'18:40:34' pour la date du jour de compilation du programme à 18h40min34sec.
  • D'' pour la date du jour de compilation du programme à minuit.

Metatrader fournit les horodatages: Time[] et MarketInfo()


Comme je l'ai précisé, la plupart du temps (voire quasiment tout le temps), on ne créé pas d'horodatage, on prend ceux fournis par MetaTrader par deux moyens:
  • Time[] Ce tableau d'historique prédéfini par MetaTrader nous donne les horodatages des ouvertures de chaque période. Il suffit de mettre entre les crochets le numéro de la période désirée. 0 étant celle en cours, 1 celle qui vient de finir et 2 celle d'avant, etc. Je précise de suite qu'on a tendance à penser que ces horodatages sont ceux du début de chaque période, eh zou ! raté ! Ils correspondent aux horodatages des premiers ticks de chaque période, qui eux n'arrivent pas forcément à la première seconde de chaque période ! :D
  • MarketInfo() est une fonction qui peut donner plusieurs informations sur le marché. (d'où son nom). Il faut lui préciser en premier paramètre la paire voulue avec quelque chose comme "EURUSD" ou bien Symbol() qui donnera pareil sur l'euro-dollar, et le type d'information en deuxième paramètre. Le type d'information est une constante qui commence par MODE_. Vous commencez à la saisir et l'éditeur vous suggère avec l'auto-complétion les possibilités. Dans notre cas on termine de taper TIME pour écrire la constante MODE_TIME. Et là, la fonction nous donne le dernier temps connu du serveur. Celui que le serveur a donné dans le dernier tick reçu.

Du code exemple pour Time[] et MarketInfo()


Nous allons utiliser le tableau prédéfini Time[] pour lire dans l'historique le temps de début de la période en cours, et la fonction MarketInfo() pour obtenir l'horodatage du dernier tick reçu et étudier un peu les résultats obtenus. Nous allons placer ce code juste dans la fonction Init(), où nous déclarons deux variables du type datetime pour contenir les deux nombres.
int init()
  {
   datetime horodatageDebutPeriode = Time[0];
   datetime horodatageTickActuel = MarketInfo(Symbol(),MODE_TIME);
   datetime ecartEnSecondes = horodatageTickActuel - horodatageDebutPeriode;
   Print("horodatage début période: ", horodatageDebutPeriode);
   Print("horodatage tick actuel: ", horodatageTickActuel);
   Print("écart en secondes: ", ecartEnSecondes);
   return(0);
  }

Et le tutoriel en vidéo !


En application de cette initiation au type datetime, nous ferons des calculs arithmétiques sur le temps dans le prochain post, tout en commençant à faire des bibliothèques de fonctions utilisables dans les experts advisors et les indicateurs.

samedi 18 janvier 2014

IndicatorCounted() et le remplissage de l'historique de l'indicateur 3 / 3.

Et voici finalement un exemple simple d'indicateur stable dans le temps, sans artefacts. Cet indicateur reprend ce qui est expliqué dans les deux posts précédents et donnera une valeur moyenne approchée du cours à chaque période. Nous ne pouvons pas réaliser une moyenne exacte du cours pour les périodes avant l'exécution de l'indicateur, car l'historique ne garde pas en mémoire tous les ticks: il n'y a que les high, low, open et close, soit quatre valeurs seulement du cours à chaque période. Et ces valeurs sont celles du bid, le ask n'est pas du tout sauvegardé dans l'historique. On ne peut avoir le ask qu'au moment où on reçoit le tick car MetaTrader ne l'enregistre pas. Six valeurs par période quatre du bid plus le temps et le volume, c'est mieux que rien et nous permet d'avoir une moyenne qui devrait être relativement proche de la moyenne réelle du cours sur la période. D'autant plus qu'on a la plus haute et la plus basse dont on va se servir. Il faut bien comprendre qu'avoir ces deux valeurs ne nous permet pas de savoir ce qu'a fait le cours durant la période. Nous savons seulement qu'il est resté entre les deux. Nous allons juste utiliser ces deux valeurs et faire ce qu'en trading on appelle la valeur médiane. Une moyenne des quatre bid (high, low open et close) sera faite ultérieurement dans un autre post, et elle sera plus proche de la vraie moyenne que la valeur médiane.

La valeur médiane:


Il s'agit de faire un simple calcul de moyenne entre deux valeurs, mais nous n'allons pas le laisser comme ça nu dans le code de l'indicateur: on va l'habiller dans une fonction ce qui nous permettra de le réutiliser dans d'autres indicateurs, voire experts advisors. Nous la placerons même dans un fichier header qui contiendra de nombreuses fonctions de calculs sur les cours, mais ce fichier c'est pour plus tard. Pour l'instant voici comment faire la fonction:
  • il faut savoir sur quelle période de l'historique on doit la faire. Il nous faut donc un paramètre qui est le numéro de période dans l'historique. C'est un nombre entier (int)
  • on fait le calcul (high + low) / 2 qu'on stocke dans une variable nommée coursMedian déclarée en même temps comme une valeur à virgule (double). Pour être sûr d'obtenir un nombre à virgule, on fait la division par un nombre à virgule sûr et certain: 2.0.
  • on retourne cette valeur au code appelant la fonction.
Voici donc le code de la fonction:

double obtenirCoursMedian(int noPeriode) {
   double coursMedian = (High[noPeriode] + Low[noPeriode]) / 2.0;
   return(coursMedian);
}

L'initialisation de l'indicateur:


Nous reprenons le code du tout premier indicateur que je vous ai proposé, en version améliorée: celui de l'initialisation de l'indicateur dans une fonction: factorisation du code d'initialisation de l'indicateur. Il y a deux choses dans cette fonction:
  1. On fixe le nombre de décimales de précision des valeurs données par l'indicateur avec la fonction IndicatorDigits(). On doit lui fournir un paramètre qui est un nombre entier indiquant le nombre de décimale, normalement 4 ou 5. On va prendre le nombre de décimales de la paire sur laquelle a été lancé l'indicateur. C'est la fonction MarketInfo() qui nous donnera ça, à condition de lui indiquer deux choses:
    1. La paire sur laquelle nous travaillons, qui est donnée par la fonction Symbol().
    2. et ce qu'on lui demande sur cette paire de devises: le nombre de chiffres décimaux. Ce qui est indiqué par la constante MQL4 : MODE_DIGITS. Cette constante nommée cache un nombre entier défini par MetaTrader, mais peut importe, ce nombre aura pour la fonction la signification de vouloir le nombre de décimales.
  2. et notre fonction fixe le nom court de l'indicateur avec la fonction IndicatorShortName(). Cette fonction prend évidemment un paramètre qui est le nom qu'on donne à l'indicateur sous forme de chaine de caractères (string). Il faut que ce nom soit passé en paramètre à notre fonction, sinon elle ne saura pas quoi mettre. Ou bien elle peut définir un nom elle même et le donner à l'indicateur, mais ce serait toujours le même, et ça n'aurait pas d'intérêt. MetaTrader a besoin de ce nom pour pouvoir l'afficher sur les graphiques et indiquer que l'indicateur est présent.
Voici le code complet de notre fonction déjà faite précédemment. J'ai changé le nom du paramètre, car avec un nom en français, c'est plus lisible et on fait bien la différence entre un nom que nous avons donné et un nom fixé par MetaTrader. (eh oui ! il faut toujours améliorer un code dès qu'on voit une amélioration possible ! Ne jamais cesser de le faire.)

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

L'initialisation d'un index:


Nous reprenons aussi le code du tout premier indicateur que je vous ai proposé en version améliorée pour initialiser un index d'indicateur: factorisation du code d'initialisation de l'indicateur. C'est la fonction InitIndex qui fait pour l'instant quatre choses:
  1. initialiser le style de traçage de l'index avec ce style passé en paramètre de notre fonction. Ce style possède une valeur par défaut car le plus souvent ce sera une ligne. Comme ça, le plus souvent, nous n'auront pas besoin de le préciser.
  2. initialiser le buffer de l'index avec le tableau qu'on aura déclaré au début du code de l'indicateur. Ce tableau se passe en paramètre de notre fonction par référence, et non par copie (voir le post consacré à ce sujet: passer une valeur par référence.
  3. initialiser le décalage de l'index (shift) qui est un décalage du traçage de cet index seulement. Nombre entier positif ou négatif. J'ajoute une valeur par défaut par rapport à la première version car c'est très souvent 0.
  4. initialiser le début de traçage: c'est un nombre qui indique le nombre de périodes à ne pas tracer en partant de la période la plus ancienne. Utile lorsque les premières valeurs anciennes dans l'historique sont entachées de grosses erreurs car les périodes concernées n'ont pas suffisamment de périodes précédentes pour donner une valeur correcte. J'ajoute ici une valeur par défaut, car le plus souvent ce sera zéro.
Pour toutes ces taches il faut évidemment préciser le numéro de l'index concerné (entre 0 et 7) qu'on aura reçu en paramètre. Voici le code complet de la fonction (la première ligne a été coupée car trop longue pour rentrer entière dans la largeur du post):

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

Tutoriel vidéo pour le code complet de l'indicateur:


Nous reprenons la boucle principale étudiée dans le post précédent sur le remplissage de l'historique. Nous déclarons également sous forme de constante au début du code le nom court de l'indicateur, ainsi que le numéro d'index que nous utiliserons (le 0). Nous plaçons les deux fonctions d'initialisation dans la fonction init() et rien dans la fonction deinit(). Dans la fonction start() on déclare un numéro de période qui va parcourir toutes les périodes de l'historique et un nombre décimal double qui va contenir le résultat du calcul de chaque période avant de le stocker dans le buffer de l'index. En résumé, à chaque passage de la boucle de remplissage de l'historique, on demande le calcul de la valeur médiane à la fonction créée pour ça et on le stocke dans notre variable coursMedian. Puis cette valeur, on la copie dans le buffer à la bonne case, celle numérotée par la valeur de noPeriode. La variable de boucle noPeriode prendra toutes les valeurs de numéro des périodes de l'historique, la première fois, puis seulement les valeurs à récentes à recalculer les fois suivantes. Stocker les valeurs dans le buffer de l'index c'est tout ce qu'il y a faire pour qu'elles soient disponibles pour tout expert advisor qui la demande et qu'elle soit tracées automatiquement sur les graphiques par MetaTrader. (J'aurais pu mettre directement le résultat du calcul dans le tampon, plutôt que de passer par une variable intermédiaire. Mais je préfère comme ça pour bien vous montrer l'extraction de la valeur du calcul puis l'affectation dans le tampon en deux étapes séparées, c'est plus visible pour les débutants) Voici le tutoriel vidéo:


Et le code complet:

//+------------------------------------------------------------------+
//|                                                  CoursMedian.mq4 |
//|                  Copyright 2014, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, 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 tamponCoursMedian[];

//--- constantes
string NOM_COURT_INDICATEUR = "Cours Median";
int    NO_INDEX = 0;

//--- fonctions
double obtenirCoursMedian(int noPeriode) {
   double coursMedian = (High[noPeriode] + Low[noPeriode]) / 2.0;
   return(coursMedian);
}

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

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

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
  {
   initIndicateur(NOM_COURT_INDICATEUR);
   initIndex(NO_INDEX, tamponCoursMedian, decalage);
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
int deinit()
  {
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
  {
   double coursMedian;
   int noPeriode;
   int nbPeriodesATraiter = Bars - IndicatorCounted();
   for(noPeriode=nbPeriodesATraiter-1 ; noPeriode>=0 ; noPeriode--) {
      coursMedian = obtenirCoursMedian(noPeriode);
      tamponCoursMedian[noPeriode] = coursMedian;
   }
   return(0);
  }
//+------------------------------------------------------------------+

vendredi 17 janvier 2014

IndicatorCounted() et le remplissage de l'historique de l'indicateur 2 / 3.

Suite de la gestion de l'historique d'un indicateur. Nous allons maintenant voir comment gérer ça. Dans le post où je vous fais faire un premier indicateur, la gestion est mauvaise et il se produit des artéfacts dans le traçage de la courbe de l'indicateur de temps en temps. Cela signifie qu'il y a un problème dans la continuité des valeurs calculées. Premièrement nous devons remplir l'historique de l'indicateur simplement parce que notre indicateur doit être valide pour toutes les périodes dans l'historique géré par MetaTrader. Il faut que notre indicateur propose une valeur pour chaque période, comme ça, il y a continuité temporelle pour pouvoir analyser le passé et tenter d'anticiper le futur des cours. Un problème supplémentaire vient se mêler à tout ça: la mémoire de l'ordinateur est limitée. Or Metatrader, doit gérer l'historique des cours en remontant loin dans le passé, en général plus de 50 000 périodes du passé chargées en mémoire. Et cela pour cinq valeurs enregistrées par période pour une seule paire de devises: time, volume, high, low, open et close. Cela fait 5 valeurs de 8 octets chacune plus le temps sur 4 octets: 5*8+4=44 octets par période, à multiplier par un nombre de périodes entre 50 000 et 100 000, nous obtenons une mémoire nécessaire entre 2 200 000 et 4 400 00 octets pour une seule paire de devises. Et c'est sans compter les éventuels indicateurs à ajouter sur la paire. Chaque signal (ou index) d'indicateur doit enregistrer pour ce même nombre de périodes des nombres sur 8 octets, soit entre 400 000 et 800 000 octets par index d'indicateur (un indicateur pouvant aller jusqu'à huit index, on comprend pourquoi c'est limité). MetaTrader a donc besoin pour une paire de devises avec un indicateur mono-index de 2 600 000 octets à 5 200 000 octets, soit entre 2,5 et 5Mo. Si le logiciel MetaTrader fonctionne jour, et nuit sans discontinuer (et il est sensé le faire, il est la pour ça, parce qu'un trader manuel doit quand même dormir un peu !) le nombre de périodes de l'historique va gonfler et faire monter la note en coût d'utilisation mémoire. Alors MetaTrader fait des recyclages de mémoire tampon, et des réallocations des tampons, bref, il fait sa tambouille pour gérer la mémoire dont il dispose. Nous avons donc des recyclages de mémoire, et de temps en temps, et, si nous ne remplissons pas automatiquement l'historique, nous voyons apparaitre des vielles valeurs du passé dans le présent. Sur le graphique nous avons de grands sauts brutaux de la courbe. Ce sont les artéfacts qui apparaissent avec le premier indicateur que je vous ai proposé.

La variable prédéfinie Bars:


La taille de l'historique en nombre de périodes est donnée par la variable Bars. Il faut donc parcourir tout l'historique du cours, faire les calculs et mettre un résultat dans le buffer de l'indicateur pour chaque période. Le buffer a une taille gérée par MetaTrader et donc il a la même que celle des tableaux de l'historique du cours. Donc Bars nous donne sa taille. Alors la dernière case du tableau possède le numéro Bars-1 (la numérotation commence à zéro), et doit contenir la toute première valeur de l'historique à calculer par notre indicateur évidemment. C'est la valeur la plus ancienne que l'indicateur calculera et donnera.

L'historique du cours:


Je l'ai déjà précisé: l'historique du cours de la paire de devise est stocké dans des tableaux gérés par MetaTrader. Ces tableaux sont mis à notre disposition pour pouvoir lire dedans et obtenir les cours pour la paire de devise sur laquelle nous avons lancé l'indicateur (ou l'expert). Nous disposons grâce à MetaTrader, des plus-hauts des périodes (tableau High[]), des plus-bas (tableau Low[]), des cours d'ouverture des périodes (tableau Open[]), des cours de fermeture des périodes (tableau Close[]), des horodatages d'ouvertures des périodes (tableau Time[]) et des volumes d'échanges des périodes (tableau Volume[]). On lit donc les valeurs de la période en cours avec l'indice 0, celle qui vient de se terminer avec l'indice 1 et celle d'avant avec l'indice 2. Etc, jusqu'à l'indice Bars-1. A chaque changement de période, il y a une nouvelle période en cours n°0, celle qui était en cours (donc ex n°0) devient la n°1 , l'ex n°1 devient la n°2 etc, de manière automatique et géré par MetaTrader.

La fonction IndicatorCounted():


Puisque qu'il faut remplir l'historique, MetaTrader nous indique à chaque fois que la fonction start() de l'indicateur est exécutée, le nombre de périodes qui n'ont pas changées depuis la dernière exécution. Cette fonction nous donne le nombre de périodes déjà calculées, qui sont restées inchangées, et donc qui n'ont pas besoin d'être recalculées. C'est-à-dire que la première fois que l'indicateur est exécuté, IndicatorCounted() donne un nombre de périodes déjà calculées de zéro: il faut tout calculer, tout l'historique. La taille de l'historique en nombre de périodes est donnée par la variable Bars. Il faut donc parcourir tout l'historique du cours, faire les calculs et mettre un résultat dans le buffer de l'indicateur pour chaque période.

Respecter la demande de calcul de MetaTrader:


Par l'intermédiaire de la fonction IndicatorCounted() MetaTrader nous demande de calculer certaines périodes. La première fois, la fonction nous donne la valeur de 0, ce qui signifie qu'il y a 0 valeurs déjà calculées. On devra alors parcourir tout l'historique du cours et effectuer les calculs et écritures des résultats dans le buffer de l'indicateur pour toutes les périodes de la plus ancienne n°Bars-1 à la plus récente n°1, ou même n°0 si notre calcul nous permet d'utiliser une période qui n'est pas finie. La fois suivante, si la période en cours n'est pas une nouvelle, IndicatorCounted() nous donnera la valeur de Bars, c'est-à-dire Bars valeurs qui n'ont pas besoin d'être recalculées, car toutes les valeurs auront été calculées et n'auront pas changé, sauf la période n°0, celle en cours, car on vient de recevoir un tick, qui fait partie de cette période, et donc les high, low, open, close et volume de cette période sont susceptibles de changer et il faut alors la recalculer. Il vient un moment ou la période en cours est nouvelle et du coup IndicatorCounted() donne la valeur Bars-1 car pour éviter des problèmes de transition d'une période à l'autre, MetaTrader demande de recalculer une période de plus en remontant dans l'historique: celle qui vient de finir.
De temps en temps MetaTrader opère des recyclages et réallocations de mémoire tampon, et demande alors de recalculer des valeurs en remontant plus dans l'historique car suite au recyclage, un certain nombre de périodes ne sont plus bonnes (toujours les plus récentes). La fonction IndicatorCounted() donnera la valeur nécessaire. C'est à nous de parcourir l'historique en conséquence et de faire les calculs demandés.

L'algorithme nécessaire pour chaque indicateur programmé:


Le logiciel MetaTrader nous indique donc le nombre de périodes dans l'historique avec la variable prédéfinie Bars, et le nombre de périodes les plus anciennes à ne pas recalculer avec la fonction IndicatorCounted(). l'algorithme de base de chaque indicateur devra donc être une boucle de parcours de l'historique de la période à calculer la plus ancienne à la période la plus récente. Des deux valeurs précédentes fournies par MetaTrader on peut en déduire une nouvelle qui est le nombre de période à traiter par le calcul suivant (différence entre le nombre total de périodes et celles qu'on ne doit pas recalculer):
nbPeriodesATraiter = Bars - IndicatorCounted();
Les numéros des cases des tableaux étant inférieurs d'une unité on parcourra les tableaux d'historique de nbPeriodesATraiter-1 jusqu'à 1 ou 0 selon l'indicateur avec une boucle for ou une boucle while:

int noPeriode;
for(noPeriode=nbPeriodesATraiter-1 ; noPeriode>0 ; noPeriode--) {
   instructions de lecture de l'historique,
   de calculs et d'écriture dans le buffer de l'indicateur
}
Dans cette boucle for: le numéro de période commence comme prévu à nbPeriodesATraiter-1, est décrémenté de 1 à chaque passage de boucle par noPeriode-- et la boucle continue tant que noPeriode>0. Ou bien utiliser la boucle while sans oublier qu'il faut gérer manuellement les changements de numéro de période:

int noPeriode = nbPeriodesATraiter - 1 ;
while(noPeriode>0) {
   instructions de lecture de l'historique,
   de calculs et d'écriture dans le buffer de l'indicateur
   noPeriode--;
}
Voilà pour l'algorithme principal d'un indicateur. Un exemple au prochain post, histoire de vous laissez digérer un peu ce qu'on vient de voir.

jeudi 16 janvier 2014

IndicatorCounted() et le remplissage de l'historique de l'indicateur 1 / 3.

Concernant les indicateurs nous allons nous pencher sur un détail important qui pose des problèmes lorsqu'on débute et qu'il faut régler tout de suite: celui du remplissage de l'historique de l'index. Je vais donc vous détailler les notions avant d'expliquer ce qu'il faut faire pour chaque indicateur qu'on créé.

Historique:


Pour nous permettre de faire des anticipations de l'évolution du cours, MetaTrader conserve en mémoire les valeurs du cours pour constituer un historique du cours. On peut alors lire ces valeurs et faire des calculs avec pour prévoir, selon nos méthodes, ce que va faire le cours. Cet historique est entièrement géré par MetaTrader qui nous met à disposition un tableau de ces valeurs. Lorsqu'on fait un indicateur, le but est de faire une série de valeurs calculées pour indiquer quelque chose sur le cours. Il faut donc remplir aussi l'historique de l'indicateur, ce que nous allons voir ici.

Indicateur:


Un indicateur est un programme tout comme les experts advisors, qui va étudier le signal, mais dont le but n'est pas de passer les ordres mais de calculer une ou des valeurs caractéristiques d'une information sur ce cours. Grâce à cette information, les experts advisors vont pouvoir déterminer s'il faut acheter, vendre ou ne rien faire en temps réel. Donc un indicateur va être utilisé par les experts advisors, ou même simplement par un trader manuel qui recevra ainsi en temps réel et de manière automatique le résultat d'un calcul pour l'aider dans ses prises de décisions.

Index:


Il faut bien comprendre le concept d'index lorsqu'on créé un indicateur dans MetaTrader. Un index est un signal, c'est à dire une suite de valeurs calculées (une par période temporelle) qui vont tracer une courbe. On peut en faire huit par indicateur, chacune possédant sa série de valeurs. Une valeur pour chaque période temporelle de l'historique entier de la paire de devises. C'est-à-dire qu'on remonte tout l'historique, il va falloir toutes les calculer. Et à chaque nouvelle période qui rentre dans l'historique il faut en calculer une.

Buffer:


Pour tracer l'évolution de l'indicateur au cours du temps, il faut stocker toutes ses valeurs pour pouvoir les lire, faire encore des calculs avec ou tout simplement les afficher sur un graphique. On va stocker ces valeurs dans un tableau, qu'en programmation on nomme un buffer, "tampon" en français. C'est-à-dire une zone suffisamment vaste en mémoire qui sert de tampon de stockage. Tampon parce que vu sa taille, elle ne débordera pas. Et si elle venait à être pleine, MetaTrader gère le problème et demande au système une zone supplémentaire. De notre côté, nous avons juste à écrire et lire des valeurs dans ce tableau et rien d'autre. MetaTrader tracera tout seul le graphique avec, sans que nous nous en occupions. Il faut juste le remplir avec des valeurs ! MetaTrader ne les invente pas pour nous, puisque ce sont celles de notre indicateur, issues de nos calculs. MetaTrader s'occupe déjà de maintenir l'historique du cours, de gérer la taille de nos tampons d'indicateurs et de tracer nos courbes.

Timeserie:


Comme on l'a vu précédemment, on accède aux valeurs d'un tableau par le nom du tableau et accolé au nom, une paire de crochets encadrant le numéro de la case du tableau voulue. Chaque case du tableau correspondant à une place en mémoire numérotée. Le processeur y accède par un numéro qui est appelée adresse mémoire. Lorsqu'on créé dans un programme un tableau, le compilateur réserve une place de 4 octets en mémoire pour contenir l'adresse où le tableau sera réellement placé. Ce sera l'adresse du début du tableau. Toutes les cases du tableau seront les unes à côté des autres dans la mémoire, à l'endroit désigné par l'adresse contenue dans la variable dans le programme (nom donné au tableau). Par exemple si je créé un tableau appelé "moyenne" de 500 cases, je tape les instructions:
double moyenne[500];
Avec cette ligne de code, nous créons deux places en mémoire: celle de la variable moyenne, de quatre octets qui contient uniquement l'adresse de la véritable place du tableau, par exemple 0xA53BC970 (notation hexadécimale pour être moins grosse, si si c'est un nombre, malgré les lettres !). Et une deuxième place de 500x8=4000 octets pour contenir 500 nombres décimaux (de type double) de 8 octets chacun, commençant à l'adresse 0xA53BC970. Un petit schéma vaut mieux qu'un grand discours:
contenu de moyenne0xA5...3B...C9...70
adresse:
n° octet en mémoire
octets n°0xA53BA000 à 0xA53BA003
Et un peu plus loin dans la mémoire:
case n°012...499
adresse:
n° octet en mémoire
0xA53BC9700xA53BC9780xA53BC980...0xA53CB58
Le premier emplacement sert uniquement à retrouver où se situe le deuxième emplacement: le tableau. Et lorsqu'on veut y accéder pour lire la valeur de la case n°2 avec par exemple l'instruction:
Print(moyenne[2]);
Le compilateur va déterminer l'adresse exacte de la valeur cherchée en prenant l'adresse de base du tableau dans le premier emplacement (la variable), et en ajoutant deux fois la taille en mémoire d'une valeur de type double, c'est-à-dire 2x8 octets, on obtient donc 0xA53BC970 + 2x8 = 0xA53BC970 + 16 = 0xA53BC970 + 0x10 (en hexadécimal) = 0xA53BC980. Ce qui est l'adresse de la deuxième case mémoire du tableau. Le compilateur ne vérifiera pas si on dépasse le tableau, et si on demande la case n°500, c'est la 501ème, le calcul fera tomber en dehors du tableau dans un emplacement qui ne nous est pas attribué et le système fera arrêter le programme d'un coup car il aura tenté d'accéder à une place qui ne lui était pas réservée.
Vous constatez que la numérotation des cases du tableau suit la progression des adresses mémoire. C'est l'ordre naturel en programmation. Mais pour des considérations temporelles, l'éditeur MetaQuotes a décidé que MetaTrader devait gérer la numérotation dans l'ordre contraire: les timeseries. Il faut imaginer qu'on remonte dans l'historique, et donc quand on veut la période temporelle n°0, c'est la période en cours. La n°1, c'est celle qui vient de se terminer, et la n°2, c'est celle d'avant, etc. Or le compilateur quand il écrit ou lit dans un tableau c'est toujours dans l'ordre naturel de la programmation: la numérotation des cases augmente avec les adresses mémoire. Et là, il faut que la dernière case qui ait été remplie soit la plus récente, donc avec le numéro 0. Je vous montre avec un schéma. Les cases colorées sont celles qui ont été remplies:
n° timeserie210inutilisée...
adresse:
n° octet en mémoire
0xA53BC9700xA53BC9780xA53BC9800xA53BC988...
Dès qu'il y a une nouvelle période (celle en cours vient de finir), la numérotation est alors décalée, et elle se décale toutes les minutes si la période est M1, ou toutes les quatre heures si la période est H4, etc. La première case inutilisée devenant la n°0. Vous devez juste retenir qu'à tout moment la période en cours, c'est la n°0, celle qui vient de finir la n°1, celle d'avant c'est la n°2, etc. Rassurez-vous, c'est MetaTrader qui gère cette différence. Vous avez juste à savoir qu'il faut remonter dans le temps en augmentant le numéro des cases. Puisque MetaTrader gère tout, y compris la taille des tableaux en mémoire, vous ne devez même pas indiquer de taille lors de la création de votre buffer pour votre courbe d'indicateur:
double moyenne[];
Par contre, il faut indiquer à MetaTrader que ce tableau créé, est un buffer de signal qu'il devra gérer, et qu'on remplira avec nos valeurs. On choisit pour ce buffer, le numéro 0, et on peut aller jusqu'au numéro 7 (huit index):
SetIndexBuffer(0, moyenne);

Ça suffira pour aujourd'hui. Il y a suffisamment d'informations à digérer et je tenais à faire ces précisions pour mieux comprendre les indicateurs avant de voir une partie technique indispensable pour tous les indicateurs: remplir l'historique, même celui déjà présent avant l'exécution de l'indicateur ! Nous verrons ça au post suivant.

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 ;)