English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Rédaction d'un Expert Advisor à l'aide de l'approche de programmation  orientée-objet MQL5

Rédaction d'un Expert Advisor à l'aide de l'approche de programmation orientée-objet MQL5

MetaTrader 5Exemples | 17 novembre 2021, 15:46
540 0
Samuel Olowoyo
Samuel Olowoyo

Introduction

Dans le premier article, nous avons parcouru les étapes de base de la création, du débogage et du test d'un Expert Advisor dans MQL5.

Tout ce que nous avons fait était très simple et intéressant ; cependant, le nouveau langage MQL5 a beaucoup plus à offrir. Dans cet article, nous examinerons l'approche orientée objet pour faire ce que nous avons fait dans le premier article. La plupart des gens pensent que c'est difficile, mais je tiens à vous assurer qu'au moment où vous aurez fini de lire cet article, vous serez en mesure de d'écrire votre propre Expert Advisor qui est orienté-objet.

Nous ne répéterons pas certaines des choses que nous avez apprises dans le premier article, je vous suggère donc de lire d'abord par le biais de l'article si vous ne l'avez pas déjà fait.


1. Le paradigme orienté-objet

L'une des choses qui fait le nouveau MQL5 beaucoup plus puissant et robuste que MQL4 est sa POO (Approche de programmation orientée-objet).

Il est recommandé en POO qu'un objet n'expose aucun de ses détails d'implémentation. De cette façon, son implémentation peut être modifiée sans changer le code qu’utilise l’objet. Cela signifie qu'une classe permet à un programmeur de dissimuler (et empêche également de modifier) la manière dont la classe qu'il a écrite est implémentée. 

Pour mettre les choses au clair, attardons-nous un peu sur les termes « classe » et « objet » que nous venons d'évoquer.

  • CLASS. Une classe ressemble davantage à un concept étendu de structure de données, mais au lieu de contenir uniquement des données, elle contient à la fois des données et des fonctions. Une classe peut contenir plusieurs variables et fonctions, appelées membres de la classe. Il s'agit d'une encapsulation de membres de données et de fonctions qui manipulent les données. Une classe est beaucoup plus puissante, dans la mesure où vous pouvez regrouper toutes vos fonctions d'Expert Advisors dans une classe. Vous ne ferez référence aux fonctions que lorsque vous en aurez besoin dans votre code EA. Par ailleurs, c'est l’objet de cet article.
  • OBJET. Un objet est une instance d'une classe. Une fois qu'une classe a été créée, pour utiliser la classe, nous devons déclarer une instance de la classe Ceci est appelé un objet En d'autres termes, pour créer un objet, vous avez besoin d'une classe.

1.1. DÉCLARER UNE CLASSE

Une classe, essentiellement , contient la description des membres (propriétés et fonctions/méthodes) d'un objet que vous souhaitez créer à partir de la classe. Prenons un exemple…

Si nous voulons créer un objet qui aura des portes, des sièges, des pneus, un poids, etc. et qui pourra aussi démarrer, changer de vitesse, s'arrêter et klaxonner ; alors nous devons écrire une classe pour cela. Les portes, sièges, pneus, poids, démarrage, changement de vitesse, arrêt et klaxon seront les membres de la classe.

Bien sûr, vous remarquerez que, ces membres sont catégorisés ; certains sont juste ce que notre objet aura (propriétés) tandis que les autres sont ce que notre objet fera (actions – fonctions/méthodes). Pour déclarer notre classe, nous devons penser à un très bon nom descriptif pour celle-ci. Dans ce cas, nous appellerons notre classe CAR. Notre classe CAR aura les propriétés et les fonctions indiquées ci-dessus en tant que membres.

Pour déclarer une classe, nous commençons par taper le mot-clé class suivi du nom de la classe suivi d'une paire d'accolades qui contient les membres de la classe.

Ainsi, le format de base d'une classe est indiqué ci-dessous :

class class_name 
{
  access_keyword_1:
    members1;

  access_keyword_2:
    members2;
  ...
};

Ici, class_name est un identifiant valide pour la classe que nous voulons écrire, members1 et members2 sont les données membres de la classe.

L access_keyword indique le droit d'accès aux membres de notre classe.  Un access_keyword peut être privé, protégé ou public. Rappelez-vous que nous essayons d'écrire une classe qui peut être utilisée par nous-mêmes et par d'autres sans exposer réellement les détails de l'implémentation. C'est pourquoi des droits d'accès sont nécessaires.

Il peut y avoir des membres de notre classe auxquels nous ne souhaitons pas donner accès à notre classe depuis l’extérieur. Ceux-ci sont déclarés dans la section de l’accès privé à l’aide du mot-clé privé ou protégé . Les autres membres auxquels nous voulons donner accès depuis l'extérieur de notre classe seront alors déclarés dans la section d'accès public à l'aide du mot-clé public. Maintenant, notre nouvelle classe CAR ressemblera maintenant à ci-dessous :

class CAR 
{
  private:
    int        doors;
    int        sits;
    int        tyres;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();
  ...
};

Notre classe CAR est déclarée à l'aide du mot-clé classe. Cette classe contient huitième membres avec quatre membres ayant un accès privé et quatre membres ayant un accès public. Les quatre membres de la section privée sont des membres de données. Trois sont de type de données entier (int) et un de type de données double. Ces membres ne sont accessibles par aucune autre fonction déclarée en dehors de cette classe. 

Les quatre membres de la section publique sont des membres fonctionnels. Deux renvoient le type de données bool et deux renvoient le type void. Ce sont les membres qui sont accessibles vers n'importe quel objet de cette classe chaque fois qu'il est créé par toute personne utilisant notre classe. Une fois qu'un objet de notre classe est créé, ces membres seront facilement utilisables.

Comme vous le remarquerez à juste titre , les mots-clés d'accès (privé, public, protégé) sont toujours suivis de deux points. La déclaration de classe se terminait également par un point-virgule. Les les membres sont déclarés en utilisant leur bon type de données .

Ça devrait être noté qu'une fois que vous déclarez une classe, tous les membres de la classe bénéficient de droits d'accès privés à moins que cela ne soit explicitement indiqué comme nous l'avons fait ci-dessus. Par exemple : dans la déclaration de classe ci-dessous :

class CAR 
{
    int        doors;
    int        sits;
    int        tyres;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();
  ...
};

Les quatre membres déclarés ci-dessus mot-clé d'accès public bénéficient automatiquement d’un accès privé.

Pour que notre classe soit utilisée, il faut tout d'abord créer un objet de la classe. Maintenant, créons un objet qui est un type de notre classe. Pour ce faire, nous utiliserons notre nom de classe suivi du nom que nous voulons donner à l'objet.

VOITURE Honda;

Ou nous pouvons créer un autre objet

VOITURE Toyota;

Honda ou Toyota est maintenant un type d'une VOITURE et peut désormais avoir accès à toutes les fonctions membres de notre classe VOITURE à condition que les fonctions membres soient déclarées dans la section d'accès public. Nous y reviendrons plus tard.

Vous pouvez voir que nous pouvons créer autant d'objets de cette classe que nous voulons. C'est l'un des avantages de la programmation orientée-objet.

A ce stade, considérons en détails, le format d'une classe en MQL5.

class class_name 
{
  private:
    members1;
    members2;
    members3;

  public:
    class_name()  //Constructor;
    ~class_name() //Destructor;
    Members4();
    Members5();

  protected:
    members6;
    members7;
};

C’ est une déclaration d'une classe où class_name est le nom de la classe. Cette la classe a neuf membres, mais sur ces neuf, deux sont des membres spéciaux.

Le Fabricant:
Le fabricant (représenté par class_name()) est une fonction spéciale qui est appelée automatiquement lorsqu'un nouvel objet du type de la classe est créé. Donc dans ce cas, lorsque vous créez un objet du type de cette classe

class_name objet;

le fabricant, class_name(), est appelé automatiquement. Le nom du fabricant doit correspondre au nom de la classe, c'est pourquoi nous avons nommé le fabricant comme class_name(). En MQL5, un fabricant ne prend aucun paramètre d'entrée et n'a pas de type de retour. Les allocations de mémoire et l'initialisation des membres de la classe sont normalement effectuées lorsque le fabricant est appelé. Les fabricants ne peuvent pas être appelés explicitement comme s'il s'agissait de fonctions membres régulières. Ils ne sont exécutés que lorsqu'un nouvel objet de cette classe est créé. Une classe en MQL5 ne peut avoir qu'un seul fabricant.

The Destructeur:
Le deuxième membre spécial est représenté par ~class_name(). C'est le destructeur de classe écrit avec une marée (~) avant le nom de la classe. Il est appelé automatiquement lorsqu'un objet de classe est détruit. Tous les membres de la classe qui doivent être désinitialisés sont désinitialisés à ce stade et peu importe que vous ayez explicitement déclaré le destructeur ou non.

Membres de données :
Les membres d'une classe peuvent être n'importe quel type de données légal, le type classe ou le type struct. En d'autres termes, lors de la déclaration des variables membres d'une classe, vous pouvez utiliser n'importe quel type de données légal (int, double, chaîne, etc.), un objet d'une autre classe ou un type de structure (par exemple, le MQL5 MqlTradeRequest, etc.)

Membres de la fonction :
Ce sont des membres de la classe utilisés dans la modification des données membres et l’exécution des principales fonctions/méthodes de la classe. Le type de retour pour les membres de la fonction peut être de n'importe quel type de retour légal (bool, void, double, string, etc.).

Privé:
Les membres déclarés dans cette section ne sont accessibles que par les membres de la fonction de la classe. Ils ne sont accessibles par aucune autre fonction en dehors de la classe.

Protégée:
Les membres déclarés dans cette section sont accessibles aux membres de fonction de la classe et peuvent également être accédés par les fonctions membres d'autres classes dérivées de cette classe. Cela signifie que nous pouvons également créer une nouvelle classe à partir de cette classe. Dans ce cas, la nouvelle classe dérivée de cette classe (qui deviendra désormais la classe de base) pourra accéder aux membres protégés de la classe de base. C'est le concept d'héritage en POO. Nous en discuterons bientôt, détendez-vous…

Publics:
Membres déclarés dans cette section sont disponibles pour une utilisation en dehors de la classe par un objet de la classe. C'est ici que vous déclarez certaines des fonctions qui seront nécessaires pour utiliser la classe dans d'autres programmes.

Maintenant que nous avons examiné le format de base d'une classe, j'espère que vous ne vous ennuyez pas encore car nous avons encore d'autres aspects intéressants des classes que nous devons examiner avant de nous lancer enfin dans la création d'un format conteneur de classe pour notre Expert Advisor.

1.2. HÉRITAGE

disons que nous voulons créer une autre classe à partir de cette classe initiale base_class. Le format pour dériver une nouvelle classe à partir d'une classe initiale est le suivant :

Classe de base                                                  

class base_class 
{
  private:
    members1;
    members2;
    members3;

  public:
    class_name()  //Constructor;
    ~class_name() //Destructor;
    Members4();
    Members5();

  protected:
    members6;
    members7;
};

La Classe dérivée:

class new_class : access_keyword base_class 
{
  private:
    members8;

  public:
    new_class()  //Constructor;
    ~new_class() //Destructor;
    Members9();
};

Quelques explications ici avant de passer à la clarification des détails. La classe new_class est dérivée de la classe base_class en utilisant les deux points et un mot-clé d’accès comme indiqué ci-dessus. Désormais, la nouvelle_classe dérivée/fabriquée de base_class peut accéder (ou hériter) à la fois aux membres publics et protégés de base_class mais ne peut pas accéder (ou ne pas hériter) aux membres privés de la base_class. La new_class peut également implémenter de nouvelles méthodes/fonctions membres différentes de la base_class. En d'autres termes, la new_class peut également avoir ses propres données et membres de fonction en dehors de ceux qu'elle hérite de la base_class.

Si le mot clé public est utilisé lors de la création de la classe dérivée, cela signifie que les membres publics et protégés de la classe de base seront hérités en tant que membres publics et protégés de la classe dérivée. Si le mot clé Protected est utilisé, les membres publics et protégés de la classe de base seront hérités en tant que membres protégés de la classe dérivée. Si le mot clé privé est utilisé, les membres publics et protégés de la classe de base seront hérités en tant que membres privés de la classe dérivée.

Il est important de noter que lorsqu'un nouvel objet de la new_class (la classe dérivée) est créé, le fabricant de la base_class est appelé en premier avant le fabricant de la new_class ; tandis que lorsque l'objet est détruit, le destructeur de la new_class(la classe dérivée) est appelé en premier avant le destructeur de la base_class

Pour mieux comprendre cette notion d'héritage, revenons à notre classe initiale VOITURE.

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();

  private:
    int        tyres;
};

On peut dériver une autre classe SALON de cette classe. Notez que j'ai déclaré que trois des membres de données de la classe VOITURE sont protégés. C'est pour permettre à notre nouvelle classe SALON d'hériter de ces membres.

Aussi, je veux que vous compreniez que l'ordre dans lequel vous placez les mots-clés d'accès n'a pas d'importance. Ce qui compte, c'est que tous les membres déclarés sous un mot-clé d'accès appartiennent à ce mot-clé.

class SALOON : public CAR 
{
  private:
    int        maxspeed;

  public:
    void       runathighspeed();
};

Notre classe dérivée SALON a deux membres et hérite en même temps de sept membres (membres protégés et publics) de la classe de base VOITURE. Cela signifie qu'une fois qu'un objet de SALON est créé, il pourra accéder aux fonctions membres publiques de VOITURE qui sont start(), changegear(), arrêt() et klaxon() ainsi que sa propre fonction membre publique runathighspeed() . C'est la notion d'héritage.

Tout comme certains caractères/comportements (méthodes) de nos père/parents (classe de base) apparaissent en nous, leurs enfants (classe dérivée), parce que nous héritons d'eux de ces comportements (méthodes/fonctions) soit génétiquement ou autrement. Désolé, je ne fais pas partie du personnel médical, mais je crois que vous comprenez bien le tableau que j'essaie de brosser. D’ailleurs, MQL5 ne prend pas en charge l'héritage multiple, donc pas besoin d'en parler.

Hmm!!! J'espère que le tissu noir recouvrant la chose mystique appelée OOP ou CLASS s'enlève petit à petit… ne vous fatiguez pas, si vous sentez à ce stade que vous n'êtes toujours pas très clair avec ce dont nous discutons, vous devrez peut-être vous détendre, prenez une tasse de café, puis revenez et recommencez depuis le début. Ce n'est pas aussi sorcier que vous le pensez...

Si vous êtes maintenant de retour à ce stade, je suppose que vous suivez mon explication. Je veux que vous me disiez combien de classes supplémentaires vous pouvez dériver de notre classe de base VOITURE ? S'il vous plaît, j'ai besoin de votre réponse. Je suis sérieux. Nommez-les et écrivez leurs déclarations et envoyez-les-moi par courrier. Si vous pouvez tous les nommer, je vous emmènerai au lancement… (je plaisante ?)

Maintenant que vous êtes prêt pour plus, continuons…

C'est vrai que quand j'écris, j'écris comme mon Papa. Ses manuscrits sont très soignés et très stylés, tout comme les miens. Je suppose que c'est quelque chose que j'hérite de lui, mais devinez quoi ; il utilise sa main gauche pour écrire tandis que j'utilise ma main droite et quand vous voyez les écritures, vous pouvez à peine les différencier car elles se ressemblent. Quel est le problème ici? J'hérite d'une bonne écriture de mon père mais je n'écris pas de la main gauche comme mon père. Cela signifie que même si c'est ce dont j'hérite et que cela se ressemble, mais la façon dont je fais la mienne est différente de celle de mon père. Cela a-t-il du sens pour vous ? C'est une idée de ce qu'on appelle le polymorphisme en POO.

Une classe dérivée (moi-même, comme dans l'exemple ci-dessus) hérite d'une fonction membre (writefine() - pour mon écriture manuscrite) d'une classe de base (mon père) mais elle (je) implémente la fonction (writefine() ) d'une manière différente de la classe de base (Mon père).

Revenons à notre classe VOITURE, et à la classe dérivée SALON ;

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    bool               start();
    virtual void       changegear(){return(0);}
    void               stop();
    bool               horn();

  private:
    int        tyres;
};
class SALOON : public CAR 
{
  private:
    int        maxspeed;

  public:
    void               runathighspeed();
    virtual  void       changegear(){gear1=reverse; gear2=low; gear3=high;}
  };

class WAGON : public CAR 
{
  private:
    bool               hasliftback;

  public:
   virtual  void       changegear(){gear1=low; gear2=high; gear3=reverse;}
};

Voyons quelques modifications que nous avons apportées ici. Premièrement, nous avons déclaré une nouvelle classe dérivée de VOITURE nommée WAGON avec deux membres. Nous avons également modifié la fonction membre changegear() pour qu'elle devienne une fonction virtuelle dans la classe de base. Pourquoi avons-nous fait de changegear() une fonction virtuelle. C'est simplement parce que nous voulons que toute classe qui hérite de la fonction de la classe de base puisse l’implémenter à sa propre manière.

En d'autres termes, les fonctions membres virtuelles d'une classe sont des fonctions membres qui peuvent être remplacées ou implémentées différemment dans toute classe dérivée de la classe où elles sont déclarées. Le corps de la fonction membre peut ensuite être remplacé par un nouvel ensemble d'implémentations dans la classe dérivée. Même si nous ne pouvons pas réutiliser le mot virtuel dans les classes dérivées, il est de bonne pratique de programmation de toujours l'utiliser dans les classes dérivées.

D'après les exemples ci-dessus, les classes SALON et WAGON implémentent la fonction changegear() à leur propre manière.

1.3. DÉFINIR LES MÉTHODES DE CLASSE (FONCTIONS DES MEMBRES)

Puisque nous avons su, dans une certaine mesure, comment déclarer des classes ; allons plus loin en discutant de la définition des fonctions membres d'une classe. Après avoir déclaré la classe, la prochaine chose est de définir les fonctions membres de notre classe. Regardons à nouveau notre classe VOITURE

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    void       CAR() // Constructor
    bool       start();
    void       changegear();
    void       stop();
    bool       horn(){press horn;}

  private:
    int        tyres;
};

 void CAR::CAR()
{
 // initialize member variables here
}

bool CAR::start()
{
 // car start procedure here
}

void CAR::changegear()
{
// car changegear procedure here
}

void CAR::stop()
{
// car stop procedure here
}

Lors de la définition des fonctions membres, nous avons utilisé un opérateur double deux-points(::) appelé l’opérateur de portée. Ceci est écrit comme les fonctions normales, la seule différence est le nom de la classe et l'opérateur de portée ajouté. Vous remarquerez également qu'une des fonctions était déjà définie au sein de la classe (fonction membre klaxon()). Une fonction membre peut être définie dans la déclaration de classe ou en dehors de la déclaration de classe comme vous l'avez vu ici.

Je pense qu'il sera vital de revoir un peu le concept de fonctions avant de continuer.

1.4. FONCTIONS

Par ailleurs qu'est-ce qu'une fonction?

Parfois dans une maison où vous avez trois enfants, plutôt qu'un seul d'entre eux faisant tous les travaux de la maison ; on a demandé à l'un de faire la vaisselle tous les jours après le dîner, on a demandé à l'autre de balayer tandis que le troisième s’occupait de la tâche de faire les lits chaque matin.

Il y a des travaux à faire dans la maison, au lieu de confier tous les travaux à un seul enfant, nous les avons répartis entre les trois d'entre eux. Cela rendra la tâche très facile et légère pour chacun d'entre eux plutôt que d'être un fardeau pour un seul d'entre eux. Aussi, si l'un des enfants n'a pas fait sa tâche, on sait rapidement lequel d'entre eux à flageller. C'est l'idée derrière les fonctions.

La plupart du temps, nous voulons écrire un code qui effectuera de nombreuses tâches. C'est là qu'interviennent les fonctions. Nous pouvons décider de diviser la tâche en sous-tâches, puis d'écrire une fonction pour effectuer chacune des sous-tâches  Une fonction est un bloc de code qui exécute ou implémente un ensemble d'opérations. C'est un groupe d'instructions qui est exécuté chaque fois qu'il est appelé à partir d'un point dans un programme.

Une fonction peut être définie comme suit :

Return_type function_name (parameters1,parameters2,…)
{
  Expressions; //(actions to carry out by the function)
}
  • Type_retour : le type de données renvoyé par la fonction (doit être un type de données valide ou void s'il renvoie rien)
  • Nom_fonction : le nom de la fonction (doit être un nom valide) qui sera utilisé pour appeler la fonction
  • Paramètres : les paramètres sont des variables de type de données valides qui se comporteront au sein de la fonction comme une variable locale. Si une fonction a plusieurs paramètres, ils sont séparés par des virgules.
  • Expressions : le corps de la fonction qui contient le bloc d'instructions

Exemple de fonction :

int doaddition (int x, int y)
{
 return (x+y);
}

Le type de retour de la fonction est entier (int), doaddition est le nom de la fonction et int x et int y sont les paramètres. Ce que fait la fonction est d'ajouter deux paramètres d'entrée qui lui sont fournis et de renvoyer le résultat. Donc, si nous fournissons à la fonction deux variables entières 2 et 3, la fonction fera l'addition et renverra 5 comme résultat.

int doaddition(2,3) // returns 5

Pour plus d'informations sur les fonctions, veuillez consulter le manuel de référence MQL5.

Maintenant assez de théories, mettons-nous au travail.

L’ essence de cet article est de vous apprendre comment vous pouvez écrire une classe pour votre Expert Advisor en utilisant l'approche Orientée-Objet présentée dans MQL5.

C'est maintenant il est temps d'agir…

2. Rédaction d'un Expert Advisor

À ce stade, nous ferons référence à l'Expert Advisor que nous avons créé dans le premier article. Si vous n'avez pas lu l'article, veuillez le faire maintenant afin que la plupart des choses dont nous discuterons à partir de maintenant ne vous soient pas étrangères. Cependant, je peux encore réviser certaines choses qui peuvent être nécessaires.

Avant de pouvoir écrire votre cours, vous devez d'abord vous asseoir et développer votre stratégie de trading. Nous l'avons déjà fait ça dans le premier article. La prochaine chose est de sélectionner les fonctionnalités que nous voulons déléguer à notre classe. Ces fonctionnalités détermineront les variables membres de notre classe. Juste un récapitulatif de notre stratégie de trading du premier article.

Ce que notre EA fera :

  • Il surveillera un indicateur particulier, et lorsqu'une certaine condition est remplie (ou certaines conditions sont remplies), il placera un trade (soit un Short/Sell ou Long/Buy), en fonction de la condition actuelle qui a été remplie.

Ce qui précède est appelé une stratégie de trading. Avant de pouvoir rédiger un EA, vous devez d'abord élaborer la stratégie que vous souhaitez automatiser dans l'EA. Donc, dans ce cas, modifions la déclaration ci-dessus afin qu'elle reflète la stratégie que nous voulons élaborer en une EA.

  • Nous utiliserons un indicateur appelé Moyenne Mobile avec une période de 8 (vous pouvez choisir n'importe quelle période, mais pour les besoins de notre stratégie, nous utiliserons 8)
  • Nous voulons que notre EA place un trade long (achat) lorsque la Moyenne Mobile-8 (pour les besoins de notre discussion, je l'appellerai MA-8) augmente vers le haut et que le prix est proche de lui et qu'il se placera a Short (Sell) lorsque MA-8 diminue à la baisse et que le prix est proche en dessous.
  • Nous allons également utiliser un autre indicateur appelé Mouvement Directionnel Moyen (ADX) avec la période 8 également pour nous aider à déterminer si le marché est en tendance ou non. Nous faisons cela parce que nous voulons uniquement entrer dans le TRADE lorsque le marché est en tendance et nous détendre lorsque le marché est en range (c'est-à-dire sans tendance). Pour y parvenir, nous ne placerons notre transaction (Acheter ou Vendre) que lorsque les conditions ci-dessus sont remplies et que la valeur ADX est supérieure à 22. Si l'ADX est supérieur à 22 mais décroissant, ou si l'ADX est inférieur à 22, nous ne traderons pas, même si la condition B est remplie.
  • Nous voulons également nous protéger en fixant un arrêt loss de 30 pips, et pour notre objectif de Profit ; nous visons un profit de 100 pips.
  • Nous voulons également que notre EA recherche des opportunités d'Achat/Vente uniquement lorsqu'une nouvelle barre a été formée et nous nous assurerons également d'ouvrir une position d'Achat si les conditions d'Achat sont remplies et que nous n'en avons pas déjà une ouverte, et d'ouvrir une position Vente lorsque les conditions de Vente sont remplies et que nous n'en avons pas déjà ouvert.

De plus, nous voulons nous assurer que nous sommes en mesure de contrôler le pourcentage de notre Marge Libre qui peut être utilisé pour placer un trade et également nous assurer que nous vérifions la marge libre disponible avant de placer tout trade. Notre EA ne placera un trade que si la marge disponible est suffisante pour le trade

Maintenant vous comprenez ce que nous souhaitons faire. Les fonctions que nous voulons déléguer à notre classe sont :

  • Vérifier les conditions d'Achat et de Vente
  • Placer Achat/Vente en fonction du résultat des conditions vérifiées

Fondamentalement, c'est tout ce que nous souhaitons que notre EA fasse. Ces deux fonctionnalités sont les fonctions majeures mais il y en a encore plus. Par exemple, lors de la vérification des positions Achat/Vente, les indicateurs doivent être utilisés. Cela signifie obtenir les valeurs des indicateurs doivent aussi être dans notre classe. Ainsi, nous incluons :

  • Obtenez toutes les poignées d'indicateur (dans la section EA OnInit)
  • Obtenez tous les tampons d'indicateurs (dans la section EA OnTick)
  • Relâchez toutes les poignées de l'indicateur (dans la section EA OnDeinit)

Lors de l’obtention des valeurs des indicateurs, notre classe devra connaître les périodes MA et ADX, la période du graphique et le symbole (paire de devises avec laquelle nous travaillons), nous devons donc également inclure :

  • Obtenez les périodes ADX et MA, ainsi que d'autres paramètres importants tels que la période et le symbole du graphique.

Également pour la vérification de la marge libre avant de passer un trade, nous inclurons

  • Vérification gratuite Marge/pourcentage du compte à utiliser pour le trade

Avec cela, nous avons déjà une idée de ce que les variables et fonctions devraient être dans notre classe.

D'accord, j'ai réfléchi pour vous ; il est temps d'écrire du code.

2.1. Écrire une classe

Commençons par lancer le MetaEditor (je crois que vous le connaissez déjà). Une fois le MetaEditor ouvert, commençons un nouveau document MQL en cliquant sur la barre d'outils Nouveau ou Ctrl+N. Dans la fenêtre de l'assistant, sélectionnez "Inclure" et cliquez sur le bouton SUIVANT.

Figure 1. Démarrage d'un nouveau document

Figure 1. Commencer un nouveau document MQL5

Tapez le nom du fichier comme indiqué ci-dessous et cliquez sur Terminer :

Figure 2. Nommer un nouveau document

Figure 2. Nommer un nouveau document

Nous avons sélectionné inclure parce que notre classe va être un fichier d'inclusion qui sera inclus dans notre code EA une fois que nous sommes prêts à l'utiliser. C'est pourquoi vous n'avez pas de place pour saisir des paramètres d'entrée.

Comme d'habitude, l'éditeur vous fournit un squelette de ce  qu'il pense que vous voulez faire.


pour commencer, veuillez supprimer tout ce qui se trouve sous la ligne de code "#property link …". Vous devriez maintenant avoir quelque chose comme ça.

//+------------------------------------------------------------------+
//|                                              my_expert_class.mqh |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"

Maintenant écrivons la déclaration de notre classe, nous appellerons notre classe, MyExpert.

//+------------------------------------------------------------------+
//| CLASS DECLARATION                                                |
//+------------------------------------------------------------------+
class MyExpert
{

Analysons la déclaration de classe. La déclaration commence par le nom de la classe. Ensuite, nous avons déclaré les membres privés de la classe.

L’ Membres privés :

//+------------------------------------------------------------------+
//| CLASS DECLARATION                                                |
//+------------------------------------------------------------------+
class MyExpert
{
//--- private members
private:
   int               Magic_No;   // Expert Magic Number
   int               Chk_Margin; // Margin Check before placing trade? (1 or 0)
   double            LOTS;       // Lots or volume to Trade
   double            TradePct;   // Percentage of Account Free Margin to trade
   double            ADX_min;    // ADX Minimum value
   int               ADX_handle; // ADX Handle
   int               MA_handle;  // Moving Average Handle
   double            plus_DI[];  // array to hold ADX +DI values for each bars
   double            minus_DI[]; // array to hold ADX -DI values for each bars
   double            MA_val[];   // array to hold Moving Average values for each bars
   double            ADX_val[];  // array to hold ADX values for each bars
   double            Closeprice; // variable to hold the previous bar closed price 
   MqlTradeRequest   trequest;    // MQL5 trade request structure to be used for sending our trade requests
   MqlTradeResult    tresult;     // MQL5 trade result structure to be used to get our trade results
   string            symbol;     // variable to hold the current symbol name
   ENUM_TIMEFRAMES   period;      // variable to hold the current timeframe value
   string            Errormsg;   // variable to hold our error messages
   int               Errcode;    // variable to hold our error codes

Comme expliqué précédemment, ces variables membres privées ne sont accessibles par aucune fonction en dehors de la classe. La plupart des variables sont très claires dans leurs déclarations donc je ne perdrai pas de temps à en parler.

Cependant, vous vous rappellerez dans notre discussion que nous avons déclaré que les variables membres peuvent être n'importe quel type de données, structure ou classe.

Je pense que vous pouvez voir cela en action ici avec la déclaration des types MqlTradeRequest et MqlTradeResults.

Le Fabricant

//--- Public member/functions
public:
   void              MyExpert();                                  //Class Constructor

Le fabricant ne prend aucun paramètre d'entrée ; s'il vous plaît gardez cela à l'esprit lorsque vous écrivez votre propre classe.

Les fonctions membre

//--- Public member/functions
public:
   void              MyExpert();                                 //Class Constructor
   void              setSymbol(string syb){symbol = syb;}         //function to set current symbol
   void              setPeriod(ENUM_TIMEFRAMES prd){period = prd;} //function to set current symbol timeframe/period
   void              setCloseprice(double prc){Closeprice=prc;}   //function to set prev bar closed price
   void              setchkMAG(int mag){Chk_Margin=mag;}          //function to set Margin Check value
   void              setLOTS(double lot){LOTS=lot;}               //function to set The Lot size to trade
   void              setTRpct(double trpct){TradePct=trpct/100;}   //function to set Percentage of Free margin to use for trading
   void              setMagic(int magic){Magic_No=magic;}         //function to set Expert Magic number
   void              setadxmin(double adx){ADX_min=adx;}          //function to set ADX Minimum values

Nous avons défini ces fonctions membres pour nous permettre de définir les variables importantes dont notre classe aura besoin pour exécuter sa fonction. Sans utiliser ces fonctions, ces variables ne seront pas disponibles pour notre classe à utiliser. Comme vous le constaterez également, nous avions déjà déclaré une variable correspondante dans notre classe qui contiendra ces valeurs une fois qu'elles seront définies par ces fonctions.

Une autre chose à noter est que nous avons défini ces fonctions membres dans la déclaration de classe. Comme je l'ai expliqué plus tôt, c'est autorisé. Cela signifie que nous n'aurons pas besoin de les redéfinir lors de la définition d'autres fonctions membres comme vous le verrez très bientôt.

Au même titre que les fonctions normales, elles ont des paramètres de bon type de données en fonction des valeurs de retour de chaque fonction. Je pense que cela ne devrait pas être étrange pour vous

void              doInit(int adx_period,int ma_period);         //function to be used at our EA intialization
void              doUninit();                                  //function to be used at EA de-initializatio
bool              checkBuy();                                  //function to check for Buy conditions
bool              checkSell();                                 //function to check for Sell conditions
void              openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,
                         double TP,int dev,string comment="");   //function to open Buy positions
void              openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,
                          double TP,int dev,string comment="");  //function to open Sell positions

Nous déclarons seulement ces fonctions membres mais nous ne les avons pas définies. Ceci est dû au fait que nous le ferons plus tard. Ce sont des fonctions qui manipuleront la plupart des valeurs stockées dans les variables membres de notre classe et en même temps elles forment les fonctions pour le rôle majeur de notre classe. Nous en discuterons plus tard.

Les Membres protégés

Ces membres seront hérités par toute classe dérivée de notre classe. Ce n'est pas vraiment nécessaire si vous n'avez pas l'intention de dériver une autre classe de cette classe. Vous pouvez aussi les placer en tant que membres privés. Je ne fais cela que pour vous permettre de comprendre les différentes questions que nous avons discutées plus tôt au sujet des classes.

//--- Protected members
protected:
   void              showError(string msg, int ercode);   //function for use to display error messages
   void              getBuffers();                       //function for getting Indicator buffers
   bool              MarginOK();                         //function to check if margin required for lots is OK

Ces trois fonctions sont également très importantes bien qu’elles soient internes à notre classe. Le showErroraffichera nos erreurs et les getBuffers seront utilisés pour obtenir les tampons indicateurs. MarginOK vérifie s'il y a suffisamment de marge libre pour ouvrir une position.

Une fois que vous ayez terminé la déclaration de la classe, n'oubliez pas le point-virgule. Il est capital.

};   // end of class declaration

La prochaine chose à faire immédiatement après avoir déclaré la classe est de définir les fonctions membre qui n'ont pas été définies dans la section de déclaration.

//+------------------------------------------------------------------+
// Definition of our Class/member functions
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|  This CLASS CONSTRUCTOR
//|  *Does not have any input parameters
//|  *Initilizes all the necessary variables                                          
//+------------------------------------------------------------------+
void MyExpert::MyExpert()
  {
//initialize all necessary variables
   ZeroMemory(trequest);
   ZeroMemory(tresult);
   ZeroMemory(ADX_val);
   ZeroMemory(MA_val);
   ZeroMemory(plus_DI);
   ZeroMemory(minus_DI);
   Errormsg="";
   Errcode=0;
  }

C’ est notre fabricant de classe.  Ici, nous avons utilisé le deux-points (::) (l'opérateur de portée) entre le nom de la classe et le nom de la fonction membre. Ce que nous essayons de dire c’est :

Bien que nous définissions cette fonction membre en dehors de la déclaration de classe, elle est toujours dans la portée de la classe. C'est un membre de la classe dont le nom précède les deux points (opérateur de portée).

 Il n'a pas de paramètres d'entrée. Il est à ce stade, nous initialisons la plupart des variables membres nécessaires et nous utilisons la fonction ZeroMemory pour accomplir cela.

void ZeroMemory(
   void & variable // réinitialiser la variable
   );

Cette fonction redéfinit les valeurs des variables qui lui sont transmises. Dans ce cas, nous l'utilisons pour redéfinir les valeurs de nos types de structure (MqlTradeRequest et MqlTradeResult) et de nos tableaux.

Le showError fonction:

 

//+------------------------------------------------------------------+
//|  SHOWERROR FUNCTION
//|  *Input Parameters - Error Message, Error Code                                                               
//+------------------------------------------------------------------+
void MyExpert::showError(string msg,int ercode)
  {
   Alert(msg,"-error:",ercode,"!!"); // display error
  }
 

C’ est une fonction membre protégée utilisée pour afficher toutes les erreurs rencontrées pendant les opérations de tout objet de notre classe. Il faut avoir deux arguments/paramètres – Description de l'erreur et code d'erreur.

Les getBuffers fonction:

//+------------------------------------------------------------------+
//|  GETBUFFERS FUNCTION                                                                
//|  *No input parameters
//|  *Uses the class data members to get indicator's buffers
//+------------------------------------------------------------------+
void MyExpert::getBuffers()
  {
   if(CopyBuffer(ADX_handle,0,0,3,ADX_val)<0 || CopyBuffer(ADX_handle,1,0,3,plus_DI)<0
      || CopyBuffer(ADX_handle,2,0,3,minus_DI)<0 || CopyBuffer(MA_handle,0,0,3,MA_val)<0)
     {
      Errormsg="Error copying indicator Buffers";
      Errcode = GetLastError();
      showError(Errormsg,Errcode);
     }
  }
  

Cette fonction est utilisée pour copier tous nos tampons d'indicateurs dans les tableaux que nous avons indiqués dans les variables membres à l'aide du descripteur d'indicateur respectif.

La fonction CopyBuffer a été expliquée dans le premier article. La fonction getBuffers n'a pas de paramètres d'entrée car nous utilisons les valeurs des variables membres de la classe.

Nous avons utilisé notre fonction erreur interne ici pour afficher toute erreur qui peut se produire dans le processus de copiage des tampons.

La MargeOK fonction:

//+------------------------------------------------------------------+
//|  MARGINOK FUNCTION
//| *No input parameters
//| *Uses the Class data members to check margin required to place a trade
//|  with the lot size is ok
//| *Returns TRUE on success and FALSE on failure
//+------------------------------------------------------------------+
bool MyExpert::MarginOK()
  {
   double one_lot_price;                                                        //Margin required for one lot
   double act_f_mag     = AccountInfoDouble(ACCOUNT_FREEMARGIN);                //Account free margin
   long   levrage       = AccountInfoInteger(ACCOUNT_LEVERAGE);                 //Leverage for this account
   double contract_size = SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);  //Total units for one lot
   string base_currency = SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE);        //Base currency for currency pair
                                                                                //
   if(base_currency=="USD")
     {
      one_lot_price=contract_size/levrage;
     }
   else
     {
      double bprice= SymbolInfoDouble(symbol,SYMBOL_BID);
      one_lot_price=bprice*contract_size/levrage;
     }
// Check if margin required is okay based on setting
   if(MathFloor(LOTS*one_lot_price)>MathFloor(act_f_mag*TradePct))
     {
      return(false);
     }
   else
     {
      return(true);
     }
  }
  
 

Cette fonction fait en fait deux tâches. Il vérifie que nous avons suffisamment de marge libre pour placer le trade et vérifie également que nous n'utilisons pas plus d'un pourcentage indiqué de la marge libre disponible pour placer le trade. De cette façon, nous pouvons contrôler combien d'argent nous utilisons pour chaque trade.

Nous utilisons la fonction AccountInfoDouble() avec l'identifiant ENUM_ACCOUNT_INFO_DOUBLE identifiant   pour obtenir la marge gratuite pour le compte.  Nous utilisons également la fonction AccountInfoInteger() avec l'identifiant ENUM_ACCOUNT_INFO_INTEGER pour obtenir l'effet de levier du compte. Le AccountInfoInteger() et AccountInfoDouble() sont des fonctions de compte utilisées pour obtenir les détails du compte courant en utilisant l'EA.

double  AccountInfoDouble(
   int  property_id      // identifier of the property
   );

Nous avons également utilisé les fonctions de propriétés du symbole SymbolInfoDouble() et SymbolInfoString() pour obtenir respectivement la taille du contrat et la devise de base pour le symbole actuel (paire de devises). La fonction SymbolInfoDouble() prend le nom du symbole et un ENUM_SYMBOL_INFO_DOUBLE identifiant comme paramètres tandis que la fonction SymbolInfoString() prend le nom du symbole et un identifiant ENUM_SYMBOL_INFO_STRING comme paramètres. Les résultats de ces fonctions sont stockés dans les variables déclarées pour chaque type de données.

double  SymbolInfoDouble(
   string  name,        // symbol
   int     prop_id      // identifier of the property
   );

Le calcul que nous avons fait ici est très simple.

Pour obtenir la marge requise pour passer un trade, nous considérons deux situations :

  1. La devise de base est l'USD (USD/CAD, USD/CHF, USD/JPY, etc.)

Marge requise = Taille du contrat par lot / Effet de levier

     2.  La devise de base n'est pas l'USD (EUR/USD, etc.)

    Marge requise = prix actuel du symbole * taille du contrat par lot/effet de levier.

    Nous décidons maintenant de vérifier si la marge requise pour trader la taille du lot indiquée le volume est supérieur au pourcentage de marge libre que vous souhaitez utiliser pour un trade Si la marge requise est inférieure, la fonction renvoie VRAI et le trade est placé, sinon, il renvoie FAUX et le trade ne sera pas placé.

    La doInit fonction:

    //+-----------------------------------------------------------------------+
    // OUR PUBLIC FUNCTIONS                                                   |
    //+-----------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //| DOINIT FUNCTION
    //| *Takes the ADX indicator's Period and Moving Average indicator's 
    //| period as input parameters 
    //| *To be used in the OnInit() function of our EA                                                               
    //+------------------------------------------------------------------+
    void MyExpert::doInit(int adx_period,int ma_period)
      {
    //--- Get handle for ADX indicator
       ADX_handle=iADX(symbol,period,adx_period);
    //--- Get the handle for Moving Average indicator
       MA_handle=iMA(symbol,period,ma_period,0,MODE_EMA,PRICE_CLOSE);
    //--- What if handle returns Invalid Handle
       if(ADX_handle<0 || MA_handle<0)
         {
          Errormsg="Error Creating Handles for indicators";
          Errcode=GetLastError();
          showError(Errormsg,Errcode);
         }
    // Set Arrays as series
    // the ADX values arrays
       ArraySetAsSeries(ADX_val,true);
    // the +DI value arrays
       ArraySetAsSeries(plus_DI,true);
    // the -DI value arrays
       ArraySetAsSeries(minus_DI,true);
    // the MA values arrays
       ArraySetAsSeries(MA_val,true);
      }

    Il s'agit d'une fonction publique que nous avons l'intention d'utiliser dans la fonction OnInit() de notre EA que nous écrirons bientôt et il va faire deux choses.

    Tout d'abord, il définira les poignées de nos indicateurs et effectuera également l'action array-set-as-series sur les variables du tableau. Il a deux paramètres d'entrée qui seront fournis à partir de notre code EA.

    La doUninit fonction:

    //+------------------------------------------------------------------+
    //|  DOUNINIT FUNCTION
    //|  *No input parameters
    //|  *Used to release ADX and MA indicators handleS                 
    //+------------------------------------------------------------------+
    void MyExpert::doUninit()
      {
    //--- Release our indicator handles
       IndicatorRelease(ADX_handle);
       IndicatorRelease(MA_handle);
      }
      

    Cette fonction est également une fonction membre publique qui sera utilisée dans la fonction UnDeInit de notre EA pour libérer tous les descripteurs des indicateurs que nous avons utilisés. ça n'a pas de paramètres d'entrée.

    La checkBuy fonction:

    //+------------------------------------------------------------------+
    //| CHECKBUY FUNCTION
    //| *No input parameters
    //| *Uses the class data members to check for Buy setup based on the
    //|  the defined trade strategy 
    //| *Returns TRUE if Buy conditions are met or FALSE if not met
    //+------------------------------------------------------------------+
    bool MyExpert::checkBuy()
      {
    /*
        Check for a Long/Buy Setup : MA increasing upwards, 
        previous price close above MA, ADX > ADX min, +DI > -DI
    */
       getBuffers();
    //--- Declare bool type variables to hold our Buy Conditions
       bool Buy_Condition_1=(MA_val[0]>MA_val[1]) && (MA_val[1]>MA_val[2]); // MA Increasing upwards
       bool Buy_Condition_2=(Closeprice>MA_val[1]);         // previous price closed above MA
       bool Buy_Condition_3=(ADX_val[0]>ADX_min);          // Current ADX value greater than minimum ADX value
       bool Buy_Condition_4=(plus_DI[0]>minus_DI[0]);       // +DI greater than -DI
    //--- Putting all together   
       if(Buy_Condition_1 && Buy_Condition_2 && Buy_Condition_3 && Buy_Condition_4)
         {
          return(true);
         }
       else
         {
          return(false);
         }
      }

      

    Cette fonction sera utilisée pour vérifier si une condition d'achat a été définie ou non. C'est pourquoi son type de retour est bool. Cela signifie qu'il renverra un VRAI ou un FAUX. C'est là que nous avons défini notre stratégie trading d’Achat Si une condition d'achat est remplie en fonction de la stratégie que nous avons défini, il renverra TRUE ; cependant, si la condition d'achat n'est pas remplie, elle renverra FAUX. Lors de l'utilisation de cette fonction dans notre code, nous placerons alors un achat si elle renvoie VRAI.

    La première chose que nous avons faite ici est d'appeler la fonction membre interne getBuffers(), qui copiera toutes les valeurs de tableau nécessaires à la fonction checkBuy dans les variables de tableau correspondantes.

    Les conditions codées ici avaient été expliquées dans le premier article.

    La checkSell fonction:

    //+------------------------------------------------------------------+
    //| CHECKSELL FUNCTION
    //| *No input parameters
    //| *Uses the class data members to check for Sell setup based on the
    //|  the defined trade strategy 
    //| *Returns TRUE if Sell conditions are met or FALSE if not met
    //+------------------------------------------------------------------+
    bool MyExpert::checkSell()
      {
    /*
        Check for a Short/Sell Setup : MA decreasing downwards, 
        previous price close below MA, ADX > ADX min, -DI > +DI
    */
       getBuffers();
    //--- Declare bool type variables to hold our Sell Conditions
       bool Sell_Condition_1=(MA_val[0]<MA_val[1]) && (MA_val[1]<MA_val[2]);  // MA decreasing downwards
       bool Sell_Condition_2=(Closeprice <MA_val[1]);                         // Previous price closed below MA
       bool Sell_Condition_3=(ADX_val[0]>ADX_min);                            // Current ADX value greater than minimum ADX
       bool Sell_Condition_4=(plus_DI[0]<minus_DI[0]);                        // -DI greater than +DI
    
    //--- Putting all together
       if(Sell_Condition_1 && Sell_Condition_2 && Sell_Condition_3 && Sell_Condition_4)
         {
          return(true);
         }
       else
         {
          return(false);
         }
      }

    Au même titre que le checkBuy, cette fonction sera utilisée pour vérifier si une condition de Vente a été définie ou non. C'est pourquoi son type de retour est également bool. Cela signifie qu'il renverra un VRAI ou un FAUX.  C'est où nous avons défini notre stratégie de trade de vente. Si une condition de vente est remplie sur la base de la stratégie que nous avons définie, elle retournera TRUE ; cependant, si la condition de vente n'est pas remplie, il renverra FALSE.

    Lorsque vous utilisez cette fonction dans notre code, nous placerons alors une vente s'il renvoie VRAI. Tout comme dans checkBuy, nous appelé d'abord la fonction interne getBuffers(). Les conditions codées ici avaient également été expliquées dans le premier article.

    La openBuy fonction:

    //+------------------------------------------------------------------+
    //| OPENBUY FUNCTION
    //| *Has Input parameters - order type, Current ASK price, Stop Loss,
    //|  Take Profit, deviation, comment
    //| *Checks account free margin before pacing trade if trader chooses
    //| *Alerts of a success if position is opened or shows error
    //+------------------------------------------------------------------+
    void MyExpert::openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,double TP,int dev,string comment="")
      {
    //--- do check Margin if enabled
       if(Chk_Margin==1)
         {
          if(MarginOK()==false)
            {
             Errormsg= "You do not have enough money to open this Position!!!";
             Errcode =GetLastError();
             showError(Errormsg,Errcode);
            }
          else
            {
             trequest.action=TRADE_ACTION_DEAL;
             trequest.type=otype;
             trequest.volume=LOTS;
             trequest.price=askprice;
             trequest.sl=SL;
             trequest.tp=TP;
             trequest.deviation=dev;
             trequest.magic=Magic_No;
             trequest.symbol=symbol;
             trequest.type_filling=ORDER_FILLING_FOK;
             // send
             OrderSend(trequest,tresult);
             // check result
             if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
               {
                Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!");
               }
             else
               {
                Errormsg= "The Buy order request could not be completed";
                Errcode =GetLastError();
                showError(Errormsg,Errcode);
               }
            }
         }
       else
         {
          trequest.action=TRADE_ACTION_DEAL;
          trequest.type=otype;
          trequest.volume=LOTS;
          trequest.price=askprice;
          trequest.sl=SL;
          trequest.tp=TP;
          trequest.deviation=dev;
          trequest.magic=Magic_No;
          trequest.symbol=symbol;
          trequest.type_filling=ORDER_FILLING_FOK;
          //--- send
          OrderSend(trequest,tresult);
          //--- check result
          if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
            {
             Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!");
            }
          else
            {
             Errormsg= "The Buy order request could not be completed";
             Errcode =GetLastError();
             showError(Errormsg,Errcode);
            }
         }
      }

    C'est la fonction qui ouvre une position d'achat chaque fois qu'elle est appelée dans notre EA. Il a, comme paramètres d'entrée, la plupart des variables qui seront nécessaires pour placer le trade; et certaines des variables seront fournies par notre code EA. Vous remarquerez, comme expliqué dans le premier article, que nous avons utilisé ici les variables de type MqlTraderequest.

    Nous n'aurons pas besoin de les utiliser dans notre code EA. Avant qu’un trade ne soit placé, nous voulons confirmer si l'utilisateur veut vérifier la marge, si la valeur de Chk_Margin (qui sera obtenue à partir de l'EA) est 1, alors nous appelons la fonction MarginOK() pour le faire pour nous. Le résultat de cette fonction détermine la prochaine étape à franchir.  Cependant, si l'utilisateur ne souhaite pas vérifier la marge, alors nous continuons et plaçons le trade.

    La openSell fonction:

    //+------------------------------------------------------------------+
    //| OPENSELL FUNCTION
    //| *Has Input parameters - order type, Current BID price, Stop Loss,
    //|  Take Profit, deviation, comment
    //| *Checks account free margin before pacing trade if trader chooses
    //| *Alerts of a success if position is opened or shows error
    //+------------------------------------------------------------------+
    void MyExpert::openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,double TP,int dev,string comment="")
      {
    //--- do check Margin if enabled
       if(Chk_Margin==1)
         {
          if(MarginOK()==false)
            {
             Errormsg= "You do not have enough money to open this Position!!!";
             Errcode =GetLastError();
             showError(Errormsg,Errcode);
            }
          else
            {
             trequest.action=TRADE_ACTION_DEAL;
             trequest.type=otype;
             trequest.volume=LOTS;
             trequest.price=bidprice;
             trequest.sl=SL;
             trequest.tp=TP;
             trequest.deviation=dev;
             trequest.magic=Magic_No;
             trequest.symbol=symbol;
             trequest.type_filling=ORDER_FILLING_FOK;
             // send
             OrderSend(trequest,tresult);
             // check result
             if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
               {
                Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!");
               }
             else
               {
                Errormsg= "The Sell order request could not be completed";
                Errcode =GetLastError();
                showError(Errormsg,Errcode);
               }
            }
         }
       else
         {
          trequest.action=TRADE_ACTION_DEAL;
          trequest.type=otype;
          trequest.volume=LOTS;
          trequest.price=bidprice;
          trequest.sl=SL;
          trequest.tp=TP;
          trequest.deviation=dev;
          trequest.magic=Magic_No;
          trequest.symbol=symbol;
          trequest.type_filling=ORDER_FILLING_FOK;
          //--- send
          OrderSend(trequest,tresult);
          //--- check result
          if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
            {
             Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!");
            }
          else
            {
             Errormsg= "The Sell order request could not be completed";
             Errcode =GetLastError();
             showError(Errormsg,Errcode);
            }
         }
      }

    A juste titre que la fonction openBuy, cette fonction ouvre une position de vente chaque fois qu'elle est appelée dans notre EA. EIle a comme paramètres d'entrée, la plupart des variables qui seront nécessaires pour placer le trade ; et certaines des variables seront fournies par notre Code EA.

    Tout comme nous l'avons fait lors de l'ouverture d'une position d'achat, avant qu'une transaction ne soit placée, nous voulons confirmer si l'utilisateur veut vérifier la marge, si la valeur de Chk_Margin (qui sera obtenue de l'EA) est 1, alors nous appelons MarginOK () fonction pour le faire pour nous.

    Le résultat de cette fonction détermine la prochaine étape à franchir. Cependant, si l'utilisateur ne veut pas vérifier la marge, nous continuons simplement et passons le trade.

    Nous avons maintenant terminé la déclaration et la définition de notre classe et des fonctions membres, cependant, nous avons laissé de côté d'autres tâches que nous avons l'intention de traiter dans notre code EA. Ceux-ci incluent la vérification des barres disponibles, la vérification des nouvelles barres et la vérification des positions ouvertes disponibles. Ils seront traités dans notre code EA.

    Pour voir une liste de toutes les fonctions et méthodes de notre classe, cliquez sur la commande/le menu des fonctions sur le MetaEditor comme indiqué ci-dessous. La fonction affiche toutes les fonctions membres, y compris le destructeur que nous n'avons pas explicitement déclaré dans notre code.

    Les membres protégés sont pointés par des flèches vertes tandis que le fabricant et le destructeur sont pointés par des flèches bleues.

     

    fonctions  membres de classe

    Figure 3. Nos fonctions membres de classe montrant le destructeur de classe

    Alors, quelle est la prochaine étape ? 

    Est-ce que je vous ai entendu dire, déboguer ? Peut-être que vous avez raison. Il est toujours bon de tester et de voir si votre code contient des erreurs, sinon vous serez déçu lorsque vous le publiez. Le problème ici est que c'est juste un fichier d'inclusion, ce n'est pas un code ou un script de conseiller expert ou code indicateur qui peut être joint au graphique. À ce stade, vous avez deux options (d'après mon expérience),,

    • soit vous risquez d'appuyer sur le bouton de débogage de votre éditeur pour que le débogueur signale toute erreur dans votre code à l'exception d'une erreur "pas de fichier exécutable produit", qui s'affichera car un fichier .mqh ne peut pas être compilé dans un .ex5 déposer.  OR
    • Allez-y et écrivez le code de l'EA qu’utilisera votre classe. Une fois que vous avez commencé à déboguer l'EA, le fichier inclus sera vérifié avec lui. En fait, c'est la façon la meilleure et la plus acceptable de le faire.

    Figure 4. Les fichiers .mqh ne peuvent pas être compilés

    2.2. REDACTION THE Expert Advisor

    Je suppose que votre éditeur est toujours ouvert. Démarrez un nouveau document mais cette fois sélectionnez Expert Advisor. (Veuillez consulter le premier article pour plus de détails). Mais cette fois, nommez votre EA 'my_oop_ea' .

    C’ est l'endroit où vous devriez être maintenant :


    Maintenant nous sommes prêts à écrire notre EA basé sur la POO.

    La première chose que nous allons faire ici est d'inclure la classe que nous venons d’ écrire à l'aide de la commande de préprocesseur #include. Inclure la classe juste immédiatement après la dernière commande de propriété du préprocesseur

    //+------------------------------------------------------------------+
    //|                                                    my_oop_ea.mq5 |
    //|                        Copyright 2010, MetaQuotes Software Corp. |
    //|                                              http://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2010, MetaQuotes Software Corp."
    #property link      "http://www.mql5.com"
    #property version   "1.00"
    // Include  our class
    #include <my_expert_class.mqh>

    Il y a deux façons d'inclure un fichier,

    // Include  using angle brackets
    #include <my_expert_class.mqh>
    // Include  using quotations
    #include "my_expert_class.mqh"

    Lorsque nous utilisons le guillemet simple (< ... >), cela signifie que le fichier à inclure sera pris depuis le répertoire d'inclusion standard (c'est-à-dire le dossier d'inclusion dans le répertoire MQL5).  Le répertoire courant (qui est le dossier Experts dans le répertoire MQL5 ne sera pas considéré comme un endroit possible pour rechercher le fichier). Cependant, si le fichier est mis entre guillemets (" ... "), le fichier sera considéré comme étant dans l’actuel répertoire (qui est le dossier Experts) et le répertoire standard (dossier Inclure) ne sera pas vérifié.

    Si votre classe est enregistrée dans le dossier Include (répertoire standard) et que vous utilisez les guillemets au lieu des guillemets simples ou vice versa, vous obtiendrez une erreur lors de la compilation du code.


    Figure 5. Un message d'erreur s'affiche lorsque le fichier d'inclusion est introuvable

    EA PARAMÈTRES D’ENTRÉE

    //--- input parameters
    input int      StopLoss=30;      // Stop Loss
    input int      TakeProfit=100;   // Take Profit
    input int      ADX_Period=14;    // ADX Period
    input int      MA_Period=10;     // Moving Average Period
    input int      EA_Magic=12345;   // EA Magic Number
    input double   Adx_Min=22.0;     // Minimum ADX Value
    input double   Lot=0.2;          // Lots to Trade
    input int      Margin_Chk=0;     // Check Margin before placing trade(0=No, 1=Yes)
    input double   Trd_percent=15.0; // Percentage of Free Margin To use for Trading

    La plupart des paramètres d'entrée ici ne sont pas nouveaux. Discutons des nouveaux.

    Nous avons introduit une variable entière pour contenir une valeur de 1 si nous voulons utiliser le contrôle de marge ou 0 si nous ne le faisons pas. Nous avons également déclaré une autre variable pour contenir le pourcentage maximum de marge libre à utiliser pour ouvrir une position. Ces valeurs seront plus tard utilisées dans notre objet de classe lors de sa création.

    Immédiatement après les paramètres d'entrée, nous définissons deux autres paramètres (STP et TKP) que nous souhaitons pouvoir manipuler (pour couvrir aux prix à 5 et 3 chiffres) car nous ne pouvons pas modifier les valeurs des variables d'entrée. Ensuite, nous créons un objet de notre classe à utiliser dans notre code EA.

    //--- Other parameters
    int STP,TKP;   // To be used for Stop Loss & Take Profit values
    // Create an object of our class
    MyExpert Cexpert;

    Comme expliqué précédemment, pour créer un objet d'une classe, vous utilisez le nom de la classe suivi du nom de l'objet que vous souhaitez créer. Ici, nous avons créé un objet Cexpert qui est un type de classe MyExpert. Cexpert peut maintenant être utilisé pour accéder à toutes les fonctions membres publiques de la classe MyExpert.

    EA SECTION INITIALISATION

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    
    //--- Run Initialize function
       Cexpert.doInit(ADX_Period,MA_Period);
    //--- Set all other necessary variables for our class object
       Cexpert.setPeriod(_Period);     // sets the chart period/timeframe
       Cexpert.setSymbol(_Symbol);     // sets the chart symbol/currency-pair
       Cexpert.setMagic(EA_Magic);    // sets the Magic Number
       Cexpert.setadxmin(Adx_Min);    // sets the ADX miniumm value
       Cexpert.setLOTS(Lot);          // set the Lots value
       Cexpert.setchkMAG(Margin_Chk); // set the margin check variable
       Cexpert.setTRpct(Trd_percent); // set the percentage of Free Margin for trade
    //--- Let us handle brokers that offers 5 digit prices instead of 4
       STP = StopLoss;
       TKP = TakeProfit;
       if(_Digits==5 || _Digits==3)
         {
          STP = STP*10;
          TKP = TKP*10;
         }  
    //---
       return(0);
      }

    À ce stade, nous avons appelé la fonction doInit de notre classe et lui avons passé les variables de période ADX et MA. Ensuite, nous définissons toutes les autres variables qui seront nécessaires à l'objet que nous venons de créer afin qu'elles soient stockées dans les variables membres de l'objet en utilisant les fonctions que nous avons déjà décrites lors de l'écriture de notre classe.

    La prochaine ligne de codes ne devrait pas être étrange, nous décidons simplement d'ajuster nos valeurs Arrêt de Perte et Prise de Bénéficie pour les prix à trois et cinq chiffres.

    EA SECTION DÉINITIALISATION

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- Run UnIntilialize function
       Cexpert.doUninit();
      }

    Nous avons appelé la fonction doUninit de la classe afin de libérer toutes les poignées de l’indicateur qui doivent avoir été créées dans la fonction d'initialisation EA.

    EA SECTION ONTICK

    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick()
      {
    //--- Do we have enough bars to work with
       int Mybars=Bars(_Symbol,_Period);
       if(Mybars<60) // if total bars is less than 60 bars
         {
          Alert("We have less than 60 bars, EA will now exit!!");
          return;
         }
    
    //--- Define some MQL5 Structures we will use for our trade
       MqlTick latest_price;      // To be used for getting recent/latest price quotes
       MqlRates mrate[];          // To be used to store the prices, volumes and spread of each bar
    /*
         Let's make sure our arrays values for the Rates
         is store serially similar to the timeseries array
    */
    // the rates arrays
       ArraySetAsSeries(mrate,true);

    La première chose que nous faisons ici est de vérifier le nombre total de barres disponibles. Si c'est suffisant pour que notre trade EA , sinon il ne tradera pas que si on a assez de barres (c'est-à-dire 60 barres). Ensuite, nous avons déclaré deux variables de la structure MQL5 (MqlTick et MqlRates). Et enfin, nous utilisons la fonction ArraySetAsSeries sur le tableau des taux.

    //--- Get the last price quote using the MQL5 MqlTick Structure
       if(!SymbolInfoTick(_Symbol,latest_price))
         {
          Alert("Error getting the latest price quote - error:",GetLastError(),"!!");
          return;
         }
    
    //--- Get the details of the latest 3 bars
       if(CopyRates(_Symbol,_Period,0,3,mrate)<0)
         {
          Alert("Error copying rates/history data - error:",GetLastError(),"!!");
          return;
         }
    
    //--- EA should only check for new trade if we have a new bar
    // lets declare a static datetime variable
       static datetime Prev_time;
    // lest get the start time for the current bar (Bar 0)
       datetime Bar_time[1];
    // copy time
       Bar_time[0] = mrate[0].time;
    // We don't have a new bar when both times are the same
       if(Prev_time==Bar_time[0])
         {
          return;
         }
    //copy time to static value, save
       Prev_time = Bar_time[0]; 
       

    Ici, nous avons utilisé la fonction SymbolInfoTick pour obtenir la dernière offre et utilisé CopyRates pour obtenir les derniers taux des trois dernières barres (barre actuelle incluse). Les prochaines lignes de vérification de code si nous avons une nouvelle barre. Nous avons déclaré deux variables datetime, l'une est une variable statique (Prev_Time) et l'autre est Bar_Time.

    Si nous avons une nouvelle barre, le temps de la barre est stocké dans la variable statique Prev_Time afin que nous puissions comparer sa valeur avec la valeur de Bar_Time dans la prochaine coche. Dans la coche suivante, si Prev_Time est égal à Bar_Time, donc il s'agit toujours de la même barre dont l'heure a été stockée. Donc, notre EA va se détendre.

    Si toutefois Bar_Time n'est pas égal à Prev_Time, alors nous avons une nouvelle barre. Nous décidons de stocker la nouvelle heure de début de la barre dans la variable datetime statique, Prev_Time et notre EA peut maintenant procéder à vérifier les nouvelles opportunités d' ACHAT ou de VENTE.

    //--- we have no errors, so continue
    //--- Do we have positions opened already?
        bool Buy_opened = false, Sell_opened=false; // variables to hold the result of the opened position
        
        if (PositionSelect(_Symbol) ==true)  // we have an opened position
        {
             if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
             {
                Buy_opened = true;  //It is a Buy
             }
             else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
             {
                Sell_opened = true; // It is a Sell
             }
        }

    Nous décidons de vérifier si nous avons déjà une position ouverte. Nous souhaitons simplement s’assurer que nous avons ouvert un trade d'achat lorsqu'il n'y a pas d'achat ouvert et un trade de vente quand il n'y a pas de vente ouverte.

    // Copy the bar close price for the previous bar prior to the current bar, that is Bar 1
       Cexpert.setCloseprice(mrate[1].close);  // bar 1 close price
    //--- Check for Buy position
       if(Cexpert.checkBuy()==true)
         {
          // Do we already have an opened buy position
          if(Buy_opened)
            {
             Alert("We already have a Buy Position!!!");
             return;    // Don't open a new Buy Position
            }
          double aprice = NormalizeDouble(latest_price.ask,_Digits);              // current Ask price
          double stl    = NormalizeDouble(latest_price.ask - STP*_Point,_Digits); // Stop Loss
          double tkp    = NormalizeDouble(latest_price.ask + TKP*_Point,_Digits); // Take profit
          int    mdev   = 100;                                                    // Maximum deviation
          // place order
          Cexpert.openBuy(ORDER_TYPE_BUY,aprice,stl,tkp,mdev);
         }

    Maintenant, nous sommes de retour à l'objet que nous avons créé, pourquoi ? Parce que nous avons pu faire toutes les vérifications nécessaires pour que notre objet fasse son travail.

    La première chose que nous faisons est d'obtenir le prix de clôture de la barre précédente en utilisant notre fonction membre d'objets setCloseprice.

    Ensuite, nous appelons la fonction checkBuy pour savoir si une condition pour achat est définie, s'il renvoie VRAI, alors nous souhaitons nous assurer que nous n'avons pas de position d'achat déjà ouverte. Si nous n'avons pas de position d'achat déjà ouverte, puis nous préparons les variables requises à utiliser pour notre commande (le type de commande, le prix ASK actuel, arrêt de perte prise de bénéfice et déviation maximale ) et appelez la fonction openBuy. Constatez combien il est si simple d’utiliser la classe que nous avons écrite. 

    //--- Check for any Sell position
       if(Cexpert.checkSell()==true)
         {
          // Do we already have an opened Sell position
          if(Sell_opened)
            {
             Alert("We already have a Sell position!!!");
             return;    // Don't open a new Sell Position
            }
          double bprice=NormalizeDouble(latest_price.bid,_Digits);                 // Current Bid price
          double bstl    = NormalizeDouble(latest_price.bid + STP*_Point,_Digits); // Stop Loss
          double btkp    = NormalizeDouble(latest_price.bid - TKP*_Point,_Digits); // Take Profit
          int    bdev=100;                                                         // Maximum deviation
          // place order
          Cexpert.openSell(ORDER_TYPE_SELL,bprice,bstl,btkp,bdev);
         }

    C'est la même chose que ce que nous avons fait ci-dessus. Puisque nous recherchons une vente, nous avons appelé la fonction checkSell et si elle renvoie VRAI et que nous n'avons pas encore de position de vente ouverte, nous préparons les variables requises pour passer notre commande (le type de commande, le prix ASK actuel, arrêt de perte, prise de bénéfice et écart maximum), puis appelez la fonction openSell.

    Assez facile, n'est-ce pas ? Nous avons fini d'écrire les codes. Il est maintenant temps de déboguer notre code. Si vous ne savez pas comment utiliser le débogueur, veuillez lire le premier article pour une meilleure compréhension.

    Lorsque vous appuyez sur F5 ou appuyez sur le bouton de débogage, le fichier inclus (notre classe) sera inclus et vérifié, et s'il y a une erreur, il le signalera. Une fois que vous constatez l'erreur, vous devez revenir au code et corriger l'erreur.

    Figure 6. Notre fichier d'inclusion est inclus lors du débogage du code EA principal

    Si tout va bien, vous avez bien fait. Il est maintenant temps de tester notre EA à l'aide du testeur de stratégie. Nous devons compiler notre EA avant de le tester avec le Strategy Tester. Pour ce faire, cliquez sur le bouton Compile ou appuyez sur F7 sur le clavier de votre ordinateur.

    Figure 7. Cliquez sur le bouton du menu Compiler pour compiler notre code

    Depuis la barre de menu du terminal de trading, accédez à Affichage -> Testeur de stratégie ou appuyez sur CONTROLE+R pour démarrer le testeur de stratégie. (Pour plus de détails sur l'utilisation du testeur, veuillez lire le premier article).

    Pour que vous puissiez tester l'EA avec le testeur de stratégie, vous devez tout d'abord le compiler. Si vous ne le compilez pas, vous obtiendrez une erreur lorsque vous sélectionnerez l'Expert Advisor dans la barre des paramètres du testeur de stratégie. (Je viens de découvrir cela dans la nouvelle version du terminal.)

    Figure 8. Le code d'EA doit être compilé avant son utilisation dans le Testeur de Stratégie

    Retrouvez ci-dessous les résultats du Testeur de Stratégie tester pour notre Expert Advisor basé sur la POO.

     Figure 9. Les résultats du trade pour notre Expert Advisor Orienté- Objet

    Le Graphique:

    Figure 10. Les résultats du graphique pour notre Expert Advisor Orienté-Objet

    Le rapport/le journal d'activité trading


    Figure 11. Les résultats de l'activité commerciale pour notre Expert-Advisor Orienté Objet


    Le tableau pour le test :

    Figure 12. Les résultats du graphique du trading pour notre Expert-Advisor orienté objet

    Conclusion

    Dans cet article, nous avons abordé, à un certain niveau, les bases d'une classe et de la façon de l'utiliser pour rédiger un simple Expert Advisor. Nous n’avons pas trop creusé en profondeur sur les domaines avancés des classes, mais ce dont nous avons discuté dans cet article est suffisant pour vous aider à vous avancer vers un niveau où vous serez en mesure d'écrire votre propre code Expert Advisor orienté-objet. 

    Nous avons également discuté de la façon dont nous pouvons vérifier la marge libre de sorte que notre EA trade pas lorsque la marge libre disponible n'est pas suffisante pour la position que nous voulons ouvrir.

    Vous conviendrez maintenant avec moi que le nouveau langage MQL5 a beaucoup plus à offrir et vous n'avez pas besoin d'être un programmeur gourou pour profiter de ce nouveau langage C'est la raison principale derrière la rédaction des guides étape par étape.


    Traduit de l’anglais par MetaQuotes Ltd.
    Article original : https://www.mql5.com/en/articles/116

    Fichiers joints |
    my_expert_class.mqh (17.52 KB)
    my_oop_ea.mq5 (6.77 KB)
    La Méthode Optimale pour le calcul du volume total de la position par Nombre Magique Indiqué La Méthode Optimale pour le calcul du volume total de la position par Nombre Magique Indiqué
    Le problème du calcul du volume total de position du symbole indiqué et du nombre magique est examiné dans cet article. La méthode suggérée ne demande que la partie minimale nécessaire de l'historique des deals, trouve le moment le plus proche où la position totale était égale à zéro et effectue les calculs avec les récents deals. Le travail avec des variables globales du terminal client est également envisagé.
    Une solution sans DLL pour communiquer entre les terminaux MetaTrader 5 à l'aide de Canaux Nommés Une solution sans DLL pour communiquer entre les terminaux MetaTrader 5 à l'aide de Canaux Nommés
    L'article décrit comment implémenter la communication inter-processus entre les terminaux clients MetaTrader 5 à l'aide de Canaux Nommés. Pour l'utilisation des Canaux Nommés, la classe CNamedPipes est élaborée. Pour tester son utilisation et mesurer le débit de la connexion, l'indicateur de coche, les scripts serveur et client sont présentés. L'utilisation de Canaux Nommés est suffisante pour les offres en temps réel.
    Utilisation de la fonction TesterWithdrawal() pour modéliser les retraits de bénéfice. Utilisation de la fonction TesterWithdrawal() pour modéliser les retraits de bénéfice.
    Cet article décrit l’utilisation de la fonction TesterWithDrawal() pour estimer les risques dans les systèmes de trade qui impliquent le retrait d’une certaine partie des actifs pendant leur fonctionnement. En outre, il décrit l’effet de cette fonction sur l’algorithme de calcul du prélèvement d’actions dans le testeur de stratégie. Cette fonction est utile lors de l’optimisation des paramètres de vos Expert Advisors.
    Une bibliothèque pour créer un graphique via l'API Google Chart Une bibliothèque pour créer un graphique via l'API Google Chart
    La création de différents types de diagrammes est une partie essentielle des analyses de l’état du marché et du test d'un système de trading. Fréquemment, afin de créer un beau diagramme, il est nécessaire d'organiser les données de sortie dans un fichier, après quoi elles sont utilisées dans des applications telles que MS Excel. Ce n'est pas très pratique et nous prive de la possibilité de mettre à jour dynamiquement les données. L'API Google Charts a fourni les moyens de créer des graphiques en mode en ligne, en envoyant une requête spéciale au serveur. Dans cet article, nous tentons d'automatiser le processus de création d'une telle demande et d'obtention d'un graphique du serveur Google.