English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Création d’Expert Advisors Multiples sur la base de Modèles de Trading

Création d’Expert Advisors Multiples sur la base de Modèles de Trading

MetaTrader 5Tester | 12 janvier 2022, 14:15
369 0
Vasiliy Sokolov
Vasiliy Sokolov


Introduction

Les capacités techniques du terminal MetaTrader 5 et de son testeur de stratégie déterminent le travail et les tests des systèmes de trading multi-devises. La complexité d’élaboration de tels systèmes pour MetaTrader 4 conditionnée, tout d'abord, par l'impossibilité de tester simultanément tick par tick de plusieurs outils de trading. De plus, les ressources linguistiques limitées du langage MQL4 ne permettaient pas l'organisation de structures de données complexes et la gestion efficace des données.

Avec la sortie de MQL5, la situation a changé. Désormais, MQL5 prend en charge l’approche orientée-objet, repose sur un mécanisme élaboré de fonctions auxiliaires, et dispose même d'un ensemble de classes de base Standard Library pour simplifier les tâches quotidiennes des utilisateurs - allant de l'organisation des données aux interfaces de travail pour les fonctions du système.

Et bien que les spécifications techniques du testeur de stratégie et du terminal permettent l'utilisation d' EA multi-devises, ils ne disposent pas de méthodes intégrées pour la parallélisation du travail d'un seul EA simultanément sur plusieurs instruments ou intervalles de temps. Comme auparavant, pour le travail d'un EA dans le cas le plus simple, vous devez l'exécuter dans la fenêtre du symbole, qui détermine le nom de l'instrument de trading et son intervalle de temps. De ce fait, la méthodologie de travail, acceptée dès l'époque de MetaTrader 4, ne permet pas de profiter pleinement du testeur de stratégie et du terminal MetaTrader 5.

La situation est compliquée par le fait qu'une seule position cumulée pour chaque instrument, égale au montant total des deals sur cet instrument, est autorisée. Certes, la transition vers une position nette est correcte et opportune. La position nette se rapproche le plus de la représentation parfaite de l'intérêt du trader sur un marché particulier.

Cependant, une telle organisation des deals ne rend pas le processus de trading simple et facilement visualisable. Auparavant, il suffisait à un EA de sélectionner sa commande en cours (par exemple, la commande pouvait être identifiée à l'aide du nombre magique) et de mettre en œuvre l'action requise. Or, même l'absence de position nette sur un instrument ne signifie pas qu'une instance particulière d'un EA sur celui-ci pour le moment n'est pas sur le marché !

Les développeurs tiers proposent différentes manières de résoudre le problème de la position nette - allant de l'écriture d'un gestionnaire spécial de commandes virtuelles (voir l'article A Virtual Order Manager pour suivre les commandes dans la position centrique de l’environnement MT5) vers l’intégration des entrées de la position agrégée à l’aide d’un nombre magique voir ( La méthode de Calcul Optimal du Volume Total de la Position par un Nombre Magique Spécifié ou L’usage du ORDER_MAGIC pour le Trading avec Expert Advisors sur un Instrument Unique).

Cependant, en plus des problèmes liés à la position agrégée, il existe un problème de ce qu'on appelle la multidevise, lorsque le même EA est requis pour trader sur plusieurs instruments. La solution de ce problème peut être trouvée dans l'articleCréer un Expert Advisor qui trade avec plusieurs instruments.

Toutes les méthodes proposées fonctionnent et ont leurs propres avantages. Cependant, leur défaut fatal est que chacune de ces méthodes essaie d'aborder la question de sa propre perspective, en proposant des solutions qui sont, par exemple, bien adaptées au trading simultané de plusieurs EA sur un seul instrument, mais ne sont pas adaptées aux solution multi-devise.

Cet article vise à résoudre tous les problèmes avec une seule solution. L'utilisation de cette solution peut résoudre le problème des tests multi-devises et même multi-systèmes de l'interaction entre différents EA sur un seul instrument. Cela semble difficile, voire impossible à réaliser, mais en réalité, c'est beaucoup plus facile.

Imaginez simplement votre unique EA trade simultanément sur plusieurs dizaines de stratégies de trading, sur tous les instruments disponibles et sur tous les éventuels intervalles de temps !  De plus, l'EA est facilement testé dans le testeur, et pour toutes les stratégies, comprises dans sa composition, dispose d'un ou plusieurs systèmes opérationnels de gestion d’argent.

Voici donc les principales tâches que nous devrons résoudre :

  1. L'EA doit trader sur la base de plusieurs systèmes de trading en même temps. De plus, il doit aussi facilement trader sur un seul que sur plusieurs systèmes de trading ;
  2. Tout le système de trading, implémenté dans l'EA, ne doit pas entrer en conflit les uns avec les autres. Chaque système de trading doit gérer uniquement sa propre contribution à la position nette totale, et uniquement ses propres commandes ;
  3. Chaque système doit être également facile à trader sur une seule période de l'instrument, ainsi que sur tous les intervalles de temps à la fois.
  4. Chaque système doit être également facile à trader sur un seul instrument de trading, ainsi que sur tous les instruments disponibles à la fois.

Si nous examinons attentivement la liste des tâches que nous devons gérer, nous arriverons à un tableau à trois dimensions. La première dimension du tableau - le nombre de systèmes de trading, la deuxième dimension - le nombre d’intervalles de temps sur lesquels le TS spécifique doit fonctionner, et le troisième - le nombre d'instruments de trading pour le TS. Un simple calcul indique que même un EA aussi simple que l'échantillon MACD, lorsqu'il travaille simultanément sur 8 paires de devises majeures, aura 152 solutions indépendantes : 1 EA * 8 paires * 19 intervalles de temps (les intervalles hebdomadaires et mensuels ne sont pas compris).

Si le système de trading est beaucoup plus vaste et le portefeuille trading des EA considérablement plus étendu, alors le nombre de solutions pourrait facilement dépasser 500, et dans certains cas, plus de 1000 ! Il est clair qu'il est impossible de configurer manuellement puis de télécharger chaque combinaison séparément. Par conséquent, il est nécessaire de construire un système de telle sorte qu'il ajuste automatiquement chaque combinaison, la charge dans la mémoire de l'EA, et l'EA trade ensuite, sur la base des règles d'une instance spécifique de cette combinaison.


Termes et concepts

Ici et plus loin,la notion de "stratégie de trading" sera remplacée par un terme spécifique modèle de trading ou simplement modèle. Un modèle de trading est une classe spéciale, construite selon des règles spécifiques, qui décrit pleinement la stratégie de trading: indicateurs, utilisés dans le trade, les conditions d'entrée et de sortie du trade, les méthodes de gestion d’argent, etc. Chaque modèle de trading est abstrait et ne définit pas de paramètres spécifiques pour son fonctionnement.

Un exemple simple est la tactique de trading, basée sur le croisement de deux moyennes mobiles. Si la moyenne mobile rapide croise la moyenne lente à la hausse, elle ouvre un deal pour acheter, si au contraire, à la baisse, elle ouvre un deal pour vendre. Cette formulation est suffisante pour écrire un modèle de trading qui négocie sur ses bases.

Cependant, une fois qu'un tel modèle sera décrit, il est nécessaire de déterminer les modalités des moyennes mobiles, avec leur période de moyennage, la période de la fenêtre de données, et l'instrument sur lequel ce modèle va se négocier. En général, ce modèle abstrait contiendra des paramètres qui devront être remplis une fois que vous aurez besoin de créer un modèle d’instance spécifique. Il est évident que selon cette approche, un modèle abstrait peut être un parent de plusieurs instances des modèles, qui diffèrent par leurs paramètres.


Rejet total de la comptabilisation de la position nette 

De nombreux développeurs essaient de garder une trace de la position agrégée. Cependant, nous pouvons constater à partir de ce qui précède, que ni la taille de la position agrégée, ni sa dynamique, ne sont pertinentes pour une instance particulière du modèle. Le modèle peut être court, alors que la position agrégée peut ne pas exister du tout (position agrégée neutre). Au contraire, la position agrégée peut être courte, tandis que le modèle disposera une position longue.

En fait, examinons ces cas plus en détail. Supposons qu'un instrument trade avec trois tactiques de trading différentes, chacune ayant son propre système indépendant de gestion d’argent. Admettons également que le premier des trois systèmes ait décidé de vendre trois contrats sans couverture, ou tout simplement de prendre une position courte, avec un volume de trois contrats. Après l'achèvement des deals, la position nette sera constituée uniquement des deals du premier système de négociation, son volume sera de moins trois contrats, ou simplement trois contrats courts sans couverture. Après un certain temps, le deuxième système de trading prend la décision d'acheter 4 contrats du même actif.

En conséquence, la position nette changera et sera composée d’ 1 contrat long. Cette fois, il inclura les contributions de deux systèmes de trading. De plus, le troisième système de trading entre en scène et prend une position courte avec le même actif, avec un volume d'un contrat standard. La position nette deviendra neutre -3 short+ 4 long - 1 short = 0.

L'absence de position nette indique-t-elle que les trois systèmes de trading ne sont pas tous sur le marché ? Pas du tout. Deux d'entre eux détiennent quatre contrats sans couverture, ce qui indique que la couverture se fera dans le temps. En revanche, le troisième système détient 4 contrats longs, qui n'ont pas encore été revendus. Ce n'est que lorsque le remboursement intégral des quatre contrats courts est achevé et qu'une vente à découvert de quatre contrats longs est effectuée que la position neutre signifie un réel manque de positions dans les trois systèmes.

Nous pouvons,bien entendu, reconstruire à chaque fois toute la séquence d'actions pour chacun des modèles, et ainsi déterminer sa contribution spécifique dans la taille de la position courante, mais une méthode beaucoup plus simple existe. Cette méthode est simple - il est nécessaire d'abandonner complètement la comptabilisation de la position agrégée, qui peut être de n'importe quelle taille et qui peut dépendre à la fois de facteurs externes (par exemple, le trading manuel), ainsi que de facteurs internes (le travail d'autres modèles de la EA sur un instrument). Étant donné que nous pouvons pas compter sur la position agrégée actuelle, alors comment comptabilisons-nous les actions d'une instance de modèle spécifique ?

Le moyen le plus simple et le plus efficace serait de doter chaque instance d'un modèle de sa propre table de commandes, qui prendrait en compte toutes les commandes - à la fois, en cours, et celles initiées par un deal ou supprimées. Des informations détaillées sur les commandes sont stockées sur le serveur de trading. Connaissant le ticket de la commande, nous pouvons obtenir presque toutes les informations sur une commande, allant de l'heure de son ouverture à son volume.

La seule chose que nous devons faire est de lier la commande de ticket à une instance spécifique du modèle. Chaque instance de modèle devra contenir son instance individuelle d'une classe spéciale - le tableau des commandes, qui comporterait une liste des commandes en cours, définies par l'instance de modèle.


Projeter un modèle de trading abstrait

Essayons maintenant de décrire la classe abstraite commune du modèle, sur laquelle des tactiques de trading spécifiques seront basées. Étant donné que l'EA devrait utiliser plusieurs modèles (ou illimités), il est évident que cette classe devrait avoir une interface uniforme à travers laquelle un expert externe en énergie donnera les signaux.

Par exemple, cette interface peut être lafonction Processing(). Simplement mettez chaque classe CModel aura sa propre fonction deProcessing(). Cette fonction sera appelée à chaque tick ou à chaque minute, ou à la survenance d'un nouvel événement de typeTrade.

Voici un exemple simple de résolution de cette tâche :

class CModel
{
protected:
   string            m_name;
public:
   void             CModel(){m_name="Model base";}
   bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

cmodel_macd     *macd;
cmodel_moving   *moving;

Voyons comment fonctionne ce code. La classe de base CModel comprend une variable protégée de type stringappeléem_name. Le mot-cléprotected" permet l'utilisation de cette variable par les héritiers de la classe, donc ses descendants comporteront déjà cette variable. De plus, la classe de base définit la fonctionProcessing() virtuelle. Dans ce cas, le mot 'virtuel' indique qu'il s'agit d'un wrapper ou de l'interface entre l'Expert Advisor et l'instance spécifique du modèle.

Toute classe, héritée du CModel, aura la garantie d'avoir l'interfaceProcessing() pour l'interaction. L'implémentation du code de cette fonction est déléguée à ses descendants. Cette délégation est évidente, car le fonctionnement interne des modèles peut différer considérablement les uns des autres, et donc il n'y a pas de généralisations communes qui pourraient être localisées au niveau généralCModel.

En outre, la description de deux classes cmodel_macd et cmodel_moving. Les deux sont générés depuis la classe CModel et ainsi , les deux disposent de leurs propre instances de la fonction et la variableProcessing() m_name. Notez que l'implémentation interne de la fonction Processing() des deux modèles est différente. Dans le premier modèle, il se compose du Print ("C'est cmodel_macd. Nom du modèle est ", m_name), dans le second de Print("It is cmodel_moving. Nom du modèle est ", m_nom). Ensuite, deux pointeurs sont créés, chacun d'eux peut pointer vers une instance spécifique du modèle, l'un vers la classe de type cmodel_macd et l’autre vers le type cmodel_moving.

Dans la fonction OnInit, ces pointeurs héritent des modèles de classes créés dynamiquement, après quoi, dans la fonctionOnEvent() , un a Processing() est appelée, qui est comprise dans chaque classe. Les deux pointeurs sont annoncés au niveau global, donc même après avoir quitté la fonction OnInit(), les classes qui y sont créées ne sont pas supprimées, mais continuent d'exister au niveau global. Désormais, toutes les cinq secondes, la fonction OnTimer() échantillonnera les deux modèles à tour de rôle, en appelant la fonction Processing() appropriée.

Ce système primitif d'échantillonnage des modèles, que nous venons de créer, manque de souplesse et d'évolutivité. Que fait-on si on souhaite travailler avec plusieurs dizaines de pareils modèles ? Travailler avec chacun d'eux séparément n'est pas pratique. Il serait beaucoup plus facile de rassembler tous les modèles dans une seule communauté, par exemple un tableau, puis d'itérer sur tous les éléments de ce tableau en appelant la fonction Processing() de chacun de ces éléments.

Mais le problème est que l'organisation des tableaux nécessite que les données, qui y sont stockées, soient du même type. Dans notre cas, bien que les modèles cmodel_macd et cmodel_moving soient très similaires, ils ne sont pas identiques, ce qui rend automatiquement impossible leur utilisation dans des tableaux.

Heureusement, les tableaux ne sont pas le seul moyen de résumer les données, il existe d'autres généralisations plus flexibles et évolutives. L'une d'elles est la technique des listes chaînées. Son schéma de fonctionnement est simple. Chaque élément compris dans la liste globale doit comprendre deux pointeurs. Un pointeur pointe vers l'élément de liste précédent, le second - vers le suivant. 

Aussi, connaissant le numéro d'index de l'article, vous pouvez toujours vous y référer. Lorsque vous souhaitez ajouter ou supprimer un élément, il suffit de reconstruire ses pointeurs, et les pointeurs des éléments voisins, pour qu'ils se réfèrent systématiquement les uns aux autres. Connaître l'organisation interne de telles communautés n'est pas nécessaire, il suffit de comprendre leur dispositif commun.

L'installation standard de MetaTrader 5 comprend une classe auxiliaire spéciale CList qui offre la possibilité de travailler avec des listes chaînées. Cependant, l'élément de cette liste ne peut être qu'un objet de type CObject, car eux seuls disposent des pointeurs spéciaux pour travailler avec des listes chaînées. En soi, la classe CObject est plutôt primitive, étant simplement une interface pour interagir avec la classe CList

Vous pouvez le constater en examinant son implémentation :

//+------------------------------------------------------------------+
//|                                                       Object.mqh |
//|                      Copyright © 2010, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//|                                              Revision 2010.02.22 |
//+------------------------------------------------------------------+
#include "StdLibErr.mqh"
//+------------------------------------------------------------------+
//| Class CObject.                                                   |
//| Purpose: Base class element storage.                             |
//+------------------------------------------------------------------+
class CObject
  {
protected:
   CObject          *m_prev;               // previous list item
   CObject          *m_next;               // next list item

public:
                     CObject();
   //--- methods of access to protected data
   CObject          *Prev()                { return(m_prev); }
   void              Prev(CObject *node)   { m_prev=node;    }
   CObject          *Next()                { return(m_next); }
   void              Next(CObject *node)   { m_next=node;    }
   //--- methods for working with files
   virtual bool      Save(int file_handle) { return(true);   }
   virtual bool      Load(int file_handle) { return(true);   }
   //--- method of identifying the object
   virtual int       Type() const          { return(0);      }

protected:
   virtual int       Compare(const CObject *node,int mode=0) const { return(0); }
  };
//+------------------------------------------------------------------+
//| Constructor CObject.                                             |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CObject::CObject()
  {
//--- initialize protected data
   m_prev=NULL;
   m_next=NULL;
  }
//+------------------------------------------------------------------+

Comme on peut le constater, la base de cette classe est constituée de deux pointeurs, pour lesquels les fonctionnalités typiques sont implémentées.

Maintenant la partie la plus importante. Grâce au mécanisme d'héritage, il est possible d’inclure cette classe dans le modèle de trading, ce qui signifie que la classe du modèle de trading peut être incluse dans une liste de type CList type! Essayons de le faire.

Et nous allons donc faire notre classe abstraite CModel comme descendant de la classe CObject :

class CModel : public CObject

Puisque nos classes cmodel_moving and cmodel_averagesont héritées de la classe CModel, elles comportent les données et les méthodes de la classe CObject, elles peuvent donc être comprises dans la liste de type CList. Le code source, qui crée les deux modèles de trading conditionnels, les place dans la liste et échantillonne successivement chaque tick, est présenté ci-dessous :

//+------------------------------------------------------------------+
//|                                            ch01_simple_model.mq5 |
//|                            Copyright 2010, Vasily Sokolov (C-4). |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Vasily Sokolov (C-4)."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Arrays\List.mqh>

// Base model
class CModel:CObject
{
protected:
   string            m_name;
public:
        void              CModel(){m_name="Model base";}
        bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

//Create list of models
CList *list_models;

void OnInit()
{
   int rezult;
   // Great two pointer
   cmodel_macd          *m_macd;
   cmodel_moving        *m_moving;
   list_models =        new CList();
   m_macd   =           new cmodel_macd();
   m_moving =           new cmodel_moving();
   //Check valid pointer
   if(CheckPointer(m_macd)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_macd);
      if(rezult!=-1)Print("Model MACD successfully created");
      else          Print("Creation of Model MACD has failed");
   }
   //Check valid pointer
   if(CheckPointer(m_moving)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_moving);
      if(rezult!=-1)Print("Model MOVING AVERAGE successfully created");
      else          Print("Creation of Model MOVING AVERAGE has failed");
   }
}

void OnTick()
{
   CModel               *current_model;
   for(int i=0;i<list_models.Total();i++){
      current_model=list_models.GetNodeAtIndex(i);
      current_model.Processing();
   }
}

void OnDeinit(const int reason)
{
   delete list_models;
}

Une fois ce programme compilé et exécuté, des lignes similaires, indiquant le fonctionnement normal de l'EA, devraient apparaître dans le journal "Experts".

2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Prosessing Moving Average...
2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Processing MACD Model...
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MOVING AVERAGE was created successfully
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MACD was created successfully  

Analysons en détail le fonctionnement de ce code. Donc, comme mentionné ci-dessus, notre modèle de trading de base CModel est dérivé depuis la classe CObject, qui confère le droit d’inclure les descendants du modèle de base dans la liste du type CList:

rezult=list_models.Add(m_macd);
rezult=list_models.Add(m_moving);

L'organisation des données nécessite de travailler avec des pointeurs. Une fois que les pointeurs de modèles spécifiques sont créés au niveau local de la fonction OnInit() et sont entrés dans la liste globale list_models, leur besoin disparaît et ils peuvent être détruits en toute sécurité, ainsi que d'autres variables de cette fonction.

En général, une caractéristique distinctive du modèle proposé est que la seule variable globale (en plus des classes de modèles elles-mêmes) est une liste liée dynamiquement de ces modèles. Ainsi, dès le début, il y a un soutien d'un haut degré d'encapsulation du projet.

Si la création du modèle a échoué pour une raison quelconque (par exemple, les valeurs des paramètres requis ont été répertoriées de manière incorrecte), ce modèle ne sera pas ajouté à la liste. Cela n'affectera pas le travail global de l'EA, car il ne traitera que les modèles qui ont été ajoutés avec succès à la liste.

L'échantillonnage des modèles créés se fait dans la fonction OnTick() Il se compose d'une boucle pour. Dans cette boucle le nombre d'éléments est déterminé, après quoi il y a un passage en série du premier élément du (i = 0) to the last (i <list_models.Total();i++):

CModel               *current_model;
for(int i=0;i<list_models.Total();i++){
   current_model=list_models.GetNodeAtIndex(i);
   current_model.Processing();
}

Le pointeur vers la classe de baseCModelest utilisé comme adaptateur universel. Cela assure que toute fonction prise en charge par cet indicateur sera disponible pour les modèles dérivés. Dans ce cas, nous avons besoin de la seule fonction Processing() Chaque modèle a sa propre version de Processing(), dont l'implémentation interne peut différer des fonctions similaires d'autres modèles. La surcharge de cette fonction n'est pas nécessaire, elle ne peut exister que sous une seule forme : ne pas avoir de paramètres d'entrée et retourner la valeur de type bool.

Les tâches qui incombent aux « épaules » de cette fonction sont vastes :

  1. La fonction devrait déterminer de manière indépendante la situation actuelle du marché sur la base de ses propres modèles de trading.
  2. Une fois la décision prise d'entrer sur le marché, la fonction doit calculer indépendamment le montant de garantie requis, impliqué dans le deal (la marge), le volume du deal, la valeur de la perte maximale possible ou le niveau de profit.
  3. Le comportement du modèle doit être corrélé avec ses actions précédentes. Par exemple, s'il existe une position courte, initiée par le modèle, il peut être impossible de l’élaborer davantage dans le futur. Toutes ces vérifications doivent être effectuées au sein de la fonction Processing() .
  4. Chacune de ces fonctions doit avoir accès aux paramètres communs, tels que l'état du compte. Sur la base de ces données, cette fonction devrait effectuer sa propre gestion d’argent, en utilisant les paramètres intégrés dans son modèle. Par exemple, si la gestion d’argent dans l'un des modèles se fait au moyen de la formule optimale f, alors sa valeur devrait être différente pour chacun de ses modèles.

Evidemment la fonction Processing() , suite à l’ampleur des tâchés posées dessus, elle, s’appuiera sur un appareil conçu des classes auxiliaires qui sont comprises dans MetaTrader 5, ainsi que celles qui sont spécialement conçues pour cette solution..

Comme on peut le constater, la plupart du travail est délégué à des instances spécifiques du modèle. Le niveau externe de l'EA donne le contrôle à tour de rôle à chaque modèle, et son travail s'achève sur celui-ci. Ce qui sera fait par le modèle spécifique dépendra de sa logique interne.

En général, le système d'interaction que nous avons construit peut être décrit par le schéma suivant :

Notez que bien que le tri des modèles, tel que présenté dans le code ci-dessus, se produise à l'intérieur de la fonctionOnTick() , il n'est pas nécessaire qu'il en soit ainsi. Le cycle de tri peut être facilement placé dans n'importe quelle autre fonction souhaitée, telle que OnTrade() ou OnTimer()

 

Le tableau des commandes virtuelles - la base du modèle

Une fois que nous avons combiné tous les modèles de trading en une seule liste, il est temps de décrire le processus de trading. Revenons à la classe CModel et essayons de la compléter avec des données et des fonctions supplémentaires, qui pourraient être basées sur le processus de trading.

Comme mentionné ci-dessus, le nouveau paradigme de la position nette définit les différentes règles de travail avec les commandes et les deals. DansMetaTrader 4, chaque deal est accompagné de sa commande, qui existait sur l'onglet "Trade" depuis le moment de son émission et jusqu'à la résiliation de la commande ou la clôture du deal, initié par lui.

Dans MetaTrader 5 les commandes en cours existent seulement jusqu’à la conclusion réelle du deal. Une fois que le deal, ou l'entrée du marché sur celle-ci, est implémenté, ces commandes passent à l'historique des commandes, qui est stocké sur le serveur de trading. Cette situation crée de l'incertitude. Supposons que l'AE donne une commande, qui a été exécutée. La position agrégée a changé. Après un certain temps, l'EA doit clôturer sa position.

Clôturer une commande spécifique, comme il peut être fait dans MetaTrader 4, ne peut pas être fait vu que le concept de clôture des commandes manque , nous pouvons clôturer la position ou une partie de celle-ci. La question est de savoir quelle partie de la position doit être fermée. Alternativement, nous pouvons parcourir toutes les commandes historiques, sélectionner ceux qui ont été émis par l'EA, puis corréler ces commandes avec l’état actuel du marché et, si nécessaire, bloquer leurs contre-commandes. Cette méthode présente de nombreuses difficultés.

Par exemple, comment peut-on déterminer que les commandes n'étaient pas déjà bloquées dans le passé ? Nous pouvons prendre une autre voie, en supposant que la position actuelle appartient exclusivement à l’ actuelle EA. Cette option ne peut être utilisée que si vous avez l'intention de trader avec un seul EA, en tradant sur une seule stratégie. Toutes ces méthodes ne sont pas en mesure de résoudre avec élégance les défis auxquels nous sommes confrontés.

La plus évidente et simples des solutions serait de stocker toutes les informations nécessaires sur les commandes du modèle actuel (c'est-à-dire les commandes qui ne sont pas bloquées par des deals opposées) dans le modèle lui-même.

Par exemple, si le modèle passe une commande, son ticket est enregistré dans une zone spéciale de la mémoire de ce modèle, par exemple, il peut être organisé à l'aide d'un système de listes chaînées que nous connaissons déjà.

Connaissant la commande de ticket, vous pouvez trouver presque toutes les informations à son sujet, donc tout ce dont nous avons besoin - c'est de lier la commande de ticket avec le modèle qui l'a émis. Que la commande de billets être stockée dans la classe spécialeCTableOrder . En plus du ticket, il peut accueillir les informations les plus essentielles, par exemple, le volume de commandes, l'heure de son installation, le nombre magique, etc.

Voyons comment cette classe est structurée :

#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"

#include <Trade\_OrderInfo.mqh>
#include <Trade\_HistoryOrderInfo.mqh>
#include <Arrays\List.mqh>
class CTableOrders : CObject
{
private:
   ulong             m_magic;       // Magic number of the EA that put out the order
   ulong             m_ticket;      // Ticket of the basic order
   ulong             m_ticket_sl;    // Ticket of the simulated-Stop-Loss order, assigned with the basic order
   ulong             m_ticket_tp;    // Ticket of the simulated-Take-Profit, assigned with the basic order
   ENUM_ORDER_TYPE   m_type;         // Order type
   datetime          m_time_setup;  // Order setup time
   double            m_price;       // Order price
   double            m_sl;          // Stop Loss price
   double            m_tp;          // Take Profit price
   double            m_volume_initial;  // Order Volume
public:
                     CTableOrders();
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);
   double            StopLoss(void){return(m_sl);}
   double            TakeProfit(void){return(m_tp);}
   ulong             Magic(){return(m_magic);}
   ulong             Ticket(){return(m_ticket);}
   int               Type() const;
   datetime          TimeSetup(){return(m_time_setup);}
   double            Price(){return(m_price);}
   double            VolumeInitial(){return(m_volume_initial);}
};

CTableOrders::CTableOrders(void)
{
   m_magic=0;
   m_ticket=0;
   m_type=0;
   m_time_setup=0;
   m_price=0.0;
   m_volume_initial=0.0;
}

bool CTableOrders::Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit)
{
   if(HistoryOrderSelect(history_order_info.Ticket())){
      m_magic=history_order_info.Magic();
      m_ticket=history_order_info.Ticket();
      m_type=history_order_info.Type();
      m_time_setup=history_order_info.TimeSetup();
      m_volume_initial=history_order_info.VolumeInitial();
      m_price=history_order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

bool CTableOrders::Add(COrderInfo &order_info, double stop_loss, double take_profit)
{
   if(OrderSelect(order_info.Ticket())){
      m_magic=order_info.Magic();
      m_ticket=order_info.Ticket();
      m_type=order_info.Type();
      m_time_setup=order_info.TimeSetup();
      m_volume_initial=order_info.VolumeInitial();
      m_price=order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

int   CTableOrders::Type() const
{
   return((ENUM_ORDER_TYPE)m_type);
}

Semblable à la classe CModel , la classe CTableOrders class est héritée de CObject. Tout comme les classes des modèles, nous placerons des instances deCTableOrders dans la ListTableOrders liste de type CList

En plus de son propre ticket de commande (m_tiket), la classe comprend des informations sur le nombre magique (ORDER_MAGIC) de l’EA qui le fait sortir , son type , ouvrant leprix, volume,et le niveau de chevauchement des commandes: stoploss (m_sl) and takeprofit (m_tp). Sur les deux dernières valeurs, nous devons parler séparément. Il est évident que tout deal devrait tôt ou tard être conclu par un deal opposé. Le deal inverse peut être initié sur la base de l’état actuel du marché ou de la clôture partielle de la position à un prix prédéterminé, au moment de sa conclusion.

Dans MetaTrader4, ces "sorties inconditionnelles de la position" sont des types spéciaux de sorties : StopLoss et TakeProfit. La caractéristique distinctive de MetaTrader 4 est le fait que ces niveaux s'appliquent à une commande spéciale. Par exemple, si un stop se produit dans l'une des commandes actives, cela n'affectera pas les autres commandes ouvertes sur cet instrument.

Dans MetaTrader 5, c'est quelque peu différent. Bien que pour chacune des commandes définies, entre autres, vous puissiez spécifier un prix du StopLoss and the TakeProfit, ces niveaux n'agiront pas contre une commande spéciale, dans laquelle ces prix ont été fixés, mais par rapport à l'ensemble de la position sur ce instrument.

Supposons qu'il y ait une position de BUY ouverte pourEURUSD de 1 lot standard sans les niveaux de StopLoss and TakeProfit. Quelque temps plus tard, une autre commande est émise pour EURUSD acheter 0.1 lot, avec les niveaux définis de StopLoss et TakeProfit - chacun à une distance de 100 points depuis le prix actuel. Après un certain temps, le prix atteint le niveau deStopLossou le niveau TakeProfit. Lorsque cela se produit, la position entière avec une taille de 1,1 lot EURUSDsera clôturée

En d'autres termes, leStopLoss and TakeProfit ne peuvent être définis que par rapport à la position agrégée, et non pas par rapport à une commande spéciale. Sur cette base, il devient impossible d'utiliser ces commandes dans des EA multi-systèmes. Ceci est évident, car si un système émettra ses propres StopLoss et TakeProfit , alors cela s'appliquera à tous les autres systèmes, dont les intérêts sont déjà compris dans la position agrégée de l'instrument !

Par conséquent, chacun des sous-systèmes de l' EA de trading ne doit utiliser que ses propres StopLoss and TakeProfit internes pour chaque commande individuellement. En outre, ce concept peut être dérivé du fait que même au sein du même système de trading, différents commandes peuvent avoir différents niveaux de StopLoss et TakeProfit, et comme précédemment mentionné dans MetaTrader 5, ces sorties ne peuvent pas être conçues comme commandes individuelles.

Si nous plaçons, au sein des commandes virtuelles, des niveaux synthétiques deStopLoss and TakeProfit , l'EA pourra bloquer indépendamment les commandes existantes une fois que le prix atteint ou dépasse ces niveaux. Après avoir bloqué ces commandes, elles peuvent être supprimées en toute sécurité de la liste des commandes actives. La façon dont cela est fait est décrite ci-dessous.

La classe CTableOrders, sans compter ses propres données, comprend une fonction hautement importanteAdd() . Cette fonction reçoit le ticket de commande, qui doit être enregistré dans la table. En plus du ticket de commande, cette fonction reçoit les niveaux des StopLoss and TakeProfit.virtuels Tout d'abord, la fonctionAdd() tente d'allouer la commande parmi les commandes historiques, qui sont stockées sur le serveur. S'il est en mesure de le faire, il saisit les informations du ticket dans l'instance de la classehistory_order_info, et commence en suite à saisir les informations à travers cela dans le nouvel élément TableOrders. De plus, cet élément est ajouté à la liste des commandes. Si la sélection de la commande n'a pas pu être terminée, alors, peut-être, nous avons affaire à une commande en cours, nous devons donc essayer d'allouer cette commande à partir des commandes en cours via la fonction OrderSelect(). En cas de sélection réussie de cette commande, les mêmes actions sont entreprises que pour la commande historique.

À l'heure actuelle, avant l'introduction de lastructure, qui décrit l'événementTrade , il est difficile de travailler avec les commandes en cours pour les EA aux systèmes multiples. Certes, après l'introduction de cette structure, il deviendra possible de concevoir des EA, sur la base des commandes en cours. De plus, si une table de commandes est présente, pratiquement n'importe quelle stratégie de trading avec des commandes en cours peut être déplacée vers la performance sur le marché. Pour ces raisons, tous les modèles de trading présentés dans l'article disposeront de market execution (ORDER_TYPE_BUY or ORDER_TYPE_SELL).


CModel - la classe de base du modèle de trading

Et donc, lorsque le tableau des commandes est entièrement conçu, vient le temps de décrire la version complète du modèle de base CModel:

class CModel : public CObject
{
protected:
   long              m_magic;
   string            m_symbol;
   ENUM_TIMEFRAMES   m_timeframe;
   string            m_model_name;
   double            m_delta;
   CTableOrders      *table;
   CList             *ListTableOrders;
   CAccountInfo      m_account_info;
   CTrade            m_trade;
   CSymbolInfo       m_symbol_info;
   COrderInfo        m_order_info;
   CHistoryOrderInfo m_history_order_info;
   CPositionInfo     m_position_info;
   CDealInfo         m_deal_info;
   t_period          m_timing;
public:
                     CModel()  { Init();   }
                     ~CModel() { Deinit(); }
   string            Name(){return(m_model_name);}
   void              Name(string name){m_model_name=name;}
   ENUM_TIMEFRAMES    Timeframe(void){return(m_timeframe);}
   string            Symbol(void){return(m_symbol);}
   void              Symbol(string set_symbol){m_symbol=set_symbol;}
   bool virtual      Init();
   void virtual      Deinit(){delete ListTableOrders;}
   bool virtual      Processing(){return (true);}
   double            GetMyPosition();
   bool              Delete(ENUM_TYPE_DELETED_ORDER);
   bool              Delete(ulong Ticket);
   void              CloseAllPosition();
   //bool virtual      Trade();
protected:
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);

   void              GetNumberOrders(n_orders &orders);
   bool              SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, double lot,
                              double price, double stop_loss, double take_profit, string comment);
};

Les données de cette classe comportent les constantes fondamentales de tout modèle de trading.

Ceci est le nombre magique (m_magic), le symbole sur lequel le modèle sera lancé, (m_symbol) la plage de temps (m_timeframe), et le nom du modèle le plus négocié (m_name).

De plus, le modèle comprend la classe, déjà connue par nous, de la table des commandes, (CTableOrders * table) et la liste, dans laquelle seront conservées les instances de cette table, une copie pour chaque commande (CList*ListTableOrders). Étant donné que toutes les données seront créées dynamiquement, au fur et à mesure des besoins, le travail avec ces données sera effectué à l'aide de pointeurs.

Elle est suivie de la variable m_delta Cette variable devrait tenir un coefficient spécial pour le calcul du lot courant dans les formules de gestion d’argent. Par exemple, pour la formule de capitalisation fractionnaire fixe, cette variable peut mémoriser une part du compte, qui peut être risquée, par exemple, pour le risque de 2% du compte, cette variable doit être égale à 0,02. Pour les méthodes les plus agressives, par exemple, pour la méthode optimale,f cette variable peut être plus grande.

Ce qui est important dans cette variable, c'est qu'elle permet la sélection individuelle du risque pour chaque modèle, qui fait partie d'une seule EE. Si la formule de capitalisation n'est pas utilisée, il n'est pas nécessaire de la remplir. Par défaut, elle est égale à 0,0.

Vient ensuite l'inclusion de toutes les classes de trading auxiliaires, conçues pour faciliter la réception et le traitement de toutes les informations requises, allant des informations de compte et les informations sur la position. Il est entendu que les dérivés de modèles spéciaux de trading doivent utiliser activement ces classes auxiliaires, et non pas les fonctionnalités habituelles de type OrderSelectouOrderSend.

La variablem_timingdoit être décrite séparément. Au cours du processus de travail de l'EA, il est nécessaire d'appeler certains événements à certains intervalles de temps. La fonction OnTimer() ne convient pas pour cela, car les différents modèles peuvent exister à des intervalles de temps différents.

Par exemple, certains événements doivent être appelés à chaquenouvelle barre.. Pour le modèle, le trading sur un graphique horaire, de tels événements doivent être appelés chaque heure, pour un modèle, le trading sur un graphique quotidien - chaque nouvelle barre de jour. Il est clair que ces modèles ont des réglages de temps différents, et chacun doit être stocké, respectivement, dans son propre modèle. La structure t_periodcomprise dans la classeCModel, permet de stocker ces paramètres séparément, chacun dans son modèle.

Voici à quoi ressemble la structure :

struct t_period
{
   datetime m1;
   datetime m2;
   datetime m3;
   datetime m4;
   datetime m5;
   datetime m6;
   datetime m10;
   datetime m12;
   datetime m15;
   datetime m20;
   datetime m30;
   datetime h1;
   datetime h2;
   datetime h3;
   datetime h4;
   datetime h6;
   datetime h8;
   datetime h12;
   datetime d1;
   datetime w1;
   datetime mn1;  
   datetime current; 
};

Comme nous pouvons le constater,il comprend laliste habituelle des intervalles de temps. Pour voir siune nouvelle barre s'est produite, vous devez comparer l'heure de la dernière barre, avec celle qui enregistrée dans la structuret_period. Si les heures ne correspondent pas, une nouvelle barre s'est produite et l'heure dans la structure doit être mise à jour à l'heure de la barre actuelle et renvoyer un résultat positif (vrai). Si l'heure de la dernière barre et la structure sont identiques, cela signifie que la nouvelle barre n'a pas encore eu lieu et qu'un résultat négatif doit être renvoyé (faux).

Voici une fonction qui fonctionne, basée sur l'algorithme décrit :

bool timing(string symbol, ENUM_TIMEFRAMES tf, t_period &timeframes)
{
   int rez;
   MqlRates raters[1];
   rez=CopyRates(symbol, tf, 0, 1, raters);
   if(rez==0)
   {
      Print("Error timing");
      return(false);
   }
   switch(tf){
      case PERIOD_M1:
         if(raters[0].time==timeframes.m1)return(false);
         else{timeframes.m1=raters[0].time; return(true);}
      case PERIOD_M2:
         if(raters[0].time==timeframes.m2)return(false);
         else{timeframes.m2=raters[0].time; return(true);}
      case PERIOD_M3:
         if(raters[0].time==timeframes.m3)return(false);
         else{timeframes.m3=raters[0].time; return(true);}
      case PERIOD_M4:
         if(raters[0].time==timeframes.m4)return(false);
         else{timeframes.m4=raters[0].time; return(true);}
     case PERIOD_M5:
         if(raters[0].time==timeframes.m5)return(false);
         else{timeframes.m5=raters[0].time; return(true);}
     case PERIOD_M6:
         if(raters[0].time==timeframes.m6)return(false);
         else{timeframes.m6=raters[0].time; return(true);}
     case PERIOD_M10:
         if(raters[0].time==timeframes.m10)return(false);
         else{timeframes.m10=raters[0].time; return(true);}
     case PERIOD_M12:
         if(raters[0].time==timeframes.m12)return(false);
         else{timeframes.m12=raters[0].time; return(true);}
     case PERIOD_M15:
         if(raters[0].time==timeframes.m15)return(false);
         else{timeframes.m15=raters[0].time; return(true);}
     case PERIOD_M20:
         if(raters[0].time==timeframes.m20)return(false);
         else{timeframes.m20=raters[0].time; return(true);}
     case PERIOD_M30:
         if(raters[0].time==timeframes.m30)return(false);
         else{timeframes.m30=raters[0].time; return(true);}
     case PERIOD_H1:
         if(raters[0].time==timeframes.h1)return(false);
         else{timeframes.h1=raters[0].time; return(true);}
     case PERIOD_H2:
         if(raters[0].time==timeframes.h2)return(false);
         else{timeframes.h2=raters[0].time; return(true);}
     case PERIOD_H3:
         if(raters[0].time==timeframes.h3)return(false);
         else{timeframes.h3=raters[0].time; return(true);}
     case PERIOD_H4:
         if(raters[0].time==timeframes.h4)return(false);
         else{timeframes.h4=raters[0].time; return(true);}
     case PERIOD_H6:
         if(raters[0].time==timeframes.h6)return(false);
         else{timeframes.h6=raters[0].time; return(true);}
     case PERIOD_H8:
         if(raters[0].time==timeframes.h8)return(false);
         else{timeframes.h8=raters[0].time; return(true);}
     case PERIOD_H12:
         if(raters[0].time==timeframes.h12)return(false);
         else{timeframes.h12=raters[0].time; return(true);}
     case PERIOD_D1:
         if(raters[0].time==timeframes.d1)return(false);
         else{timeframes.d1=raters[0].time; return(true);}
     case PERIOD_W1:
         if(raters[0].time==timeframes.w1)return(false);
         else{timeframes.w1=raters[0].time; return(true);}
     case PERIOD_MN1:
         if(raters[0].time==timeframes.mn1)return(false);
         else{timeframes.mn1=raters[0].time; return(true);}
     case PERIOD_CURRENT:
         if(raters[0].time==timeframes.current)return(false);
         else{timeframes.current=raters[0].time; return(true);}
     default:
         return(false);
   }
}

Pour le moment, il n'y a aucune possibilité de tri séquentiel des structures. Un tel tri peut être nécessaire lorsque vous devez créer plusieurs instances dans un cycle du même modèle de trading, en tradant sur des intervalles de temps différents.. J'ai donc dû écrire une fonction spéciale-trieurs concernant la structure t_period.

Voici le code source de cette fonction: :

int GetPeriodEnumerator(uchar n_period)
{
   switch(n_period)
   {
      case 0: return(PERIOD_CURRENT);
      case 1: return(PERIOD_M1);
      case 2: return(PERIOD_M2);
      case 3: return(PERIOD_M3);
      case 4: return(PERIOD_M4);
      case 5: return(PERIOD_M5);
      case 6: return(PERIOD_M6);
      case 7: return(PERIOD_M10);
      case 8: return(PERIOD_M12);
      case 9: return(PERIOD_M15);
      case 10: return(PERIOD_M20);
      case 11: return(PERIOD_M30);
      case 12: return(PERIOD_H1);
      case 13: return(PERIOD_H2);
      case 14: return(PERIOD_H3);
      case 15: return(PERIOD_H4);
      case 16: return(PERIOD_H6);
      case 17: return(PERIOD_H8);
      case 18: return(PERIOD_H12);
      case 19: return(PERIOD_D1);
      case 20: return(PERIOD_W1);
      case 21: return(PERIOD_MN1);
      default:
         Print("Enumerator period must be smallest 22");
         return(-1);
   }
}

Toutes ces fonctions sont commodément combinées dans un seul fichier dans le dossier \\Include. Appelons-le Time.mqh.

Voici ce qui sera inclus dans notre classe de base CModel :

#incude <Time.mqh>

En plus des fonctions simples get/set de type Name(), Timeframe() et Symbol(), class CModel comportent des fonctions complexes du type  Init(), GetMyPosition(), Delete(), CloseAllPosition() и Processing(). La désignation de la dernière fonction devrait déjà vous être familière, nous discuterons plus en détail de sa structure interne plus tard, mais pour l'instant commençons par une description des principales fonctions de la classe de baseCModel.

La fonctionCModel::Add() créé dynamiquement une instance de classe CTableOrders, et en suite il le remplit en utilisant la fonction appropriée CTabeOrders::Add() fonction. Son principe de fonctionnement a été décrit ci-dessus. Après avoir été rempli, cet élément est compris dans la liste générale de toutes les commandes du modèle actuel (ListTableOrders.Add (t)).

La CModel::Delete() fonction, d’autre part a supprimé l’élément deCTableOrders type la liste active des commandes Pour ce faire, vous devez spécifier le ticket des commandes, qui doivent être supprimés. Les principes de son fonctionnement est simple. La fonction trie successivement l'ensemble du tableau des commandes à la recherche de la commande avec le bon ticket. S'il trouve une telle commande, il la supprime.

La fonction CModel::GetNumberOrders() compte le nombre de commandes actives. Elle remplit la structure spéciale n_orders:

struct n_orders
{
   int all_orders;
   int long_orders;
   int short_orders;
   int buy_sell_orders;
   int delayed_orders;
   int buy_orders;
   int sell_orders;
   int buy_stop_orders;
   int sell_stop_orders;
   int buy_limit_orders;
   int sell_limit_orders;
   int buy_stop_limit_orders;
   int sell_stop_limit_orders;
};

Comme on peut le constater, après son appel, nous pouvons savoir combien de types spécifiques de commandes ont été définis. Par exemple, pour obtenir le nombre de toutes les commandes courtes, vous devez lire toutes les valeurs de commandes_courtesde l’instance n_commandes.

La CModel::SendOrder() fonction  est la base et l’unique fonction de l’envoi factuel des commandes dans le serveur du trading. Au lieu que chaque modèle particulier ait son propre algorithme pour envoyer les commandes au serveur,la SendOrder() fonction définit la procédure générale pour cette soumission. Quel que soit le modèle, le processus de passation des commandes est associé aux mêmescontrôles, qui sont effectués efficacement dans un lieu centralisé

familiarisons-nous avec le code source de cette fonction:

bool CModel::SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, 
                          double lot, double price, double stop_loss, double take_profit, string comment)
{
   ulong code_return=0;
   CSymbolInfo symbol_info;
   CTrade      trade;
   symbol_info.Name(symbol);
   symbol_info.RefreshRates();
   mm send_order_mm;
   
   double lot_current;
   double lot_send=lot;
   double lot_max=m_symbol_info.LotsMax();
   //double lot_max=5.0;
   bool rez=false;
   int floor_lot=(int)MathFloor(lot/lot_max);
   if(MathMod(lot,lot_max)==0)floor_lot=floor_lot-1;
   int itteration=(int)MathCeil(lot/lot_max);
   if(itteration>1)
      Print("The order volume exceeds the maximum allowed volume. It will be divided into ", itteration, " deals");
   for(int i=1;i<=itteration;i++)
   {
      if(i==itteration)lot_send=lot-(floor_lot*lot_max);
      else lot_send=lot_max;
      for(int i=0;i<3;i++)
      {
         //Print("Send Order: TRADE_RETCODE_DONE");
         symbol_info.RefreshRates();
         if(op_type==ORDER_TYPE_BUY)price=symbol_info.Ask();
         if(op_type==ORDER_TYPE_SELL)price=symbol_info.Bid();
         m_trade.SetDeviationInPoints(ulong(0.0003/(double)symbol_info.Point()));
         m_trade.SetExpertMagicNumber(m_magic);
         rez=m_trade.PositionOpen(m_symbol, op_type, lot_send, price, 0.0, 0.0, comment); 
         // Sleeping is not to be deleted or moved! Otherwise the order will not have time to get recorded in m_history_order_info!!!
         Sleep(3000);
         if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE_PARTIAL||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
         {
               //Print(m_trade.ResultComment());
               //rez=m_history_order_info.Ticket(m_trade.ResultOrder());
               if(op_mode==ORDER_ADD){
                  rez=Add(m_trade.ResultOrder(), stop_loss, take_profit);
               }
               if(op_mode==ORDER_DELETE){
                  rez=Delete(ticket);
               }
               code_return=m_trade.ResultRetcode();
               break;
         }
         else
         {
            Print(m_trade.ResultComment());
         }
         if(m_trade.ResultRetcode()==TRADE_RETCODE_TRADE_DISABLED||
            m_trade.ResultRetcode()==TRADE_RETCODE_MARKET_CLOSED||
            m_trade.ResultRetcode()==TRADE_RETCODE_NO_MONEY||
            m_trade.ResultRetcode()==TRADE_RETCODE_TOO_MANY_REQUESTS||
            m_trade.ResultRetcode()==TRADE_RETCODE_SERVER_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_CLIENT_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_ORDERS||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_VOLUME)
         {
            break;
         }
      }
   }
   return(rez);
}

La première chose que fait cette fonction est de vérifier la possibilité d'exécuter le volume indiqué du serveur de trading. Pour le faire, elle utilise la fonction CheckLot() . Il peut y avoir destrading restrictions sur la taille de la position. Elles doivent être prises en compte.

Examinons le cas suivant : la taille d'une position de trading est limitée à 15 lots standard dans les deux sens. La position actuelle est longue et équivaut à 3 lots. Le modèle de trading, basé sur gestion d’argent souhaite ouvrir une position longue d’un volume de 18.6 lots.la fonction CheckLot() renverra le volume corrigé du deal. Dans ce cas, il sera égal à 12 lots (puisque 3 lots sur 15 sont déjà occupés par d'autres deals). Si l’actuelle position ouverte était courte plutôt que longue, la fonction renverrait 15 lots au lieu de 18,6. C'est le volume maximum possible de positions.

Après avoir sorti 15 lots d'achat, la position nette, dans ce cas, sera de 12 lots (3 -vente, 15 - achat).). Lorsqu'un autre modèle annule sa courte position initiale de 3 lots, acheter la position agrégée atteindra le maximum possible - 15 lots. Les autres signaux d'achat ne seront pas traités tant que le modèle n'aura pas remplacé tout ou partie de ses 15 lots acheter. Un éventuel volume de deal demandé a été dépassé, la fonction renvoie une constanteEMPTY_VALUE, un tel signal doit être transmis.

Si la vérification de la possibilité du volume défini a abouti, des calculs sont effectués sur la valeur de la marge requise. Les fonds peuvent ne pas être suffisants sur le compte pour le volume indiqué. À ces fins, il existe une fonction CheckMargin() . Si la marge n'est pas suffisante, elle tentera de corriger le volume de la commande afin que l’actuelle marge actuelle lui permette de s'ouvrir. Si la marge n'est pas suffisante même pour ouvrir le montant minimum, nous sommes dans un état d’appel-de marge.

S'il n'y a actuellement aucune position et que la marge n'est pas utilisée, cela ne signifie qu'une seule chose - l'appel de marge technique -- un état où il est impossible d'ouvrir un deal. Sans ajouter d'argent sur le compte, nous ne pouvons pas continuer. Si une marge est encore utilisée, il ne nous reste plus qu'à attendre que la transaction, qui utilise cette marge, soit clôturée. Dans tous les cas, l'absence de marge renverra une constante EMPTY_VALUE.

Une caractéristique distinctive de cette fonction est la possibilité de diviser la commande en cours en plusieurs deals indépendants. Si les modèles de trading utilisent un système de capitalisation du compte, alors le montant requis peut facilement dépasser toutes les limites imaginables (par exemple, le système de capitalisation peut nécessiter l'ouverture d'un deal d'un volume de plusieurs centaines, voire milliers, de lots standard). Il est clairement impossible d'assurer un tel montant pour un seul deal En règle générale, les conditions de trading déterminent la taille d'un deal maximal de cent lots, mais certains serveurs de trading ont d'autres restrictions, par exemple sur le serveur MetaQuotesChampionship 2010 server cette limitation était de 5 lots. Il est clair que de telles restrictions doivent être prises en compte et, sur cette base, calculer correctement le volume réel de la transaction.

Tout d'abord, le nombre de commandes, nécessaires pour implémenter le volume défini, est compté. Si le montant fixé ne dépasse pas le montant maximum de la transaction, il ne nécessite qu'un seul passage pour lancer cette commande. Si le volume souhaité des transactions dépasse le volume maximum possible, alors ce volume est divisé en plusieurs parties. Par exemple, vous souhaitez acheter 11,3 lots de EURUSD. La taille maximale de la transaction sur cet instrument est de 5,0 lots. Ensuite, la fonction OrderSend divise ce volume en trois commandes : une commande - de 5,0 lots, la deuxième commande - 5,0 lots, la troisième commande - 1,3 lot.

Ainsi, au lieu d'une commande, il y en aura jusqu'à trois. Chacun d'eux sera répertorié dans le tableau des commandes et aura ses propres paramètres indépendants, tels que les valeurs virtuelles du Stop Loss et du Take Profit, le nombre magique et d'autres paramètres. Dans le traitement de ces commandes, il ne devrait pas y avoir de difficultés, car les modèles de trading sont conçus de manière à pouvoir gérer n'importe quel nombre de commandes dans leurs listes.

En effet, tous les commandes disposeront des mêmes valeurs TakeProfitet StopLoss. Chacun d'eux sera trié successivement par les fonctions LongClose and ShortClose . Une fois que les bonnes conditions pour leur fermeture se seront réunies, ou qu'elles auront atteint leurs seuils SL et TPelles seront toutes clôturées..

Chaque commande est envoyée au serveur à l’aide de la fonction OrderSend de la classe CTrade . Le détail le plus intéressant du travail est caché ci-dessous.

Le fait est que l'attribution d'une commande peut être double. La commande d'achat ou de vente peut être envoyée lors de l'apparition du signal, ou il peut s'agir d'une commande de blocage de celui existant précédemment. La fonction OrderSend doit connaître le type de la commande envoyée, car c'est la fonction qui place réellement toutes les commandes dans la table des commandes, ou les supprime de la table lors de l'occurrence de certains évènements.

Si le type de commande que vous souhaitez ajouter est ADD_ORDER.. C'est-à-dire qu'il s'agit d'une commande indépendante, qui doit être placée dans le tableau des commandes, puis la fonction ajoute des informations sur cette commande dans le tableau des commandes. Si la commande est émise pour remplacer la commande précédemment placée (par exemple, en cas d'occurrence d'un virtuel stop-loss), alors, elle doit avoir un type DELETE_ORDER. Après sa création, la fonction OrderSend supprime manuellement de la liste des commandes les informations relatives à la commande avec laquelle elle est liée. Pour cela la fonction, en plus du type de commande, hérite d'un ticket de commande, avec lequel elle est liée. S’il s’agit de ADD_ORDER, alors, le ticket peut être rempli d’ un simple zéro.


Le premier modèle de trading, basé sur le croisement de moyennes mobiles

Nous avons discuté des principaux éléments de la classe de baseCModel Il est temps d'envisager une classe de trading spécifique.

À ces fins, nous allons d'abord créer un modèle de trading simple, basé sur un simple indicateur MACD.

Ce modèle aura toujours la position longue ou courte. Dès que la ligne rapide croise la ligne lente vers le bas, nous ouvrirons une position courte, tandis qu'une position longue, s'il y en a une, sera fermée. Dans le cas du croisement vers le haut, nous ouvrirons une position longue, tandis qu'une position courte, s'il y en a une, sera fermée. Dans ce modèle, nous n'utilisons pas de stops de protection et de niveaux de profit.

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model uses MACD indicator.                                      |
//| Buy when it crosses the zero line downward                           |
//| Sell when it crosses the zero line upward                            |
//+----------------------------------------------------------------------+  
struct cmodel_macd_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               fast_ema;
   int               slow_ema;
   int               signal_ema;
};
   
class cmodel_macd : public CModel
{
private:
   int               m_slow_ema;
   int               m_fast_ema;
   int               m_signal_ema;
   int               m_handle_macd;
   double            m_macd_buff_main[];
   double            m_macd_current;
   double            m_macd_previous;
public:
                     cmodel_macd();
   bool              Init();
   bool              Init(cmodel_macd_param &m_param);
   bool              Init(string symbol, ENUM_TIMEFRAMES timeframes, int slow_ma, int fast_ma, int smothed_ma);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_macd_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
};

cmodel_macd::cmodel_macd()
{
   m_handle_macd=INVALID_HANDLE;
   ArraySetAsSeries(m_macd_buff_main,true);
   m_macd_current=0.0;
   m_macd_previous=0.0;
}
//this default loader
bool cmodel_macd::Init()
{
   m_magic      = 148394;
   m_model_name =  "MACD MODEL";
   m_symbol     = _Symbol;
   m_timeframe  = _Period;
   m_slow_ema   = 26;
   m_fast_ema   = 12;
   m_signal_ema = 9;
   m_delta      = 50;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_macd::Init(cmodel_macd_param &m_param)
{
   m_magic      = 148394;
   m_model_name = "MACD MODEL";
   m_symbol     = m_param.symbol;
   m_timeframe  = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_fast_ema   = m_param.fast_ema;
   m_slow_ema   = m_param.slow_ema;
   m_signal_ema = m_param.signal_ema;
   if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}


bool cmodel_macd::CheckParam(cmodel_macd_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT))
   {
      Print("Symbol ", m_symbol, " selection has failed. Check symbol name");
      return(false);
   }
   if(m_fast_ema == 0)
   {
      Print("Fast EMA must be greater than 0");
      return(false);
   }
   if(m_slow_ema == 0)
   {
      Print("Slow EMA must be greater than 0");
      return(false);
   }
   if(m_signal_ema == 0)
   {
      Print("Signal EMA must be greater than 0");
      return(false);
   }
   return(true);
}

bool cmodel_macd::InitIndicators()
{
   if(m_handle_macd==INVALID_HANDLE)
   {
      Print("Load indicators...");
      if((m_handle_macd=iMACD(m_symbol,m_timeframe,m_fast_ema,m_slow_ema,m_signal_ema,PRICE_CLOSE))==INVALID_HANDLE)
      {
         printf("Error creating MACD indicator");
         return(false);
      }
   }
   return(true);
}

bool cmodel_macd::Processing()
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   return(true);
}

bool cmodel_macd::LongOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   //Print("LongOpened");
   if(m_macd_current>0&&m_macd_previous<=0&&m_orders.buy_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_ADD, 0, lot, m_symbol_info.Ask(), 0, 0, "MACD Buy");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::ShortOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   if(m_macd_current<=0&&m_macd_previous>=0&&m_orders.sell_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_ADD, 0, lot, m_symbol_info.Bid(), 0, 0, "MACD Sell");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close buy stop-loss");
      }
      if(m_macd_current<0&&m_macd_previous>=0)
      {
         //Print("Long position closed by Order Send");
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close by signal");
      }
   }
   return(rez);
}

bool cmodel_macd::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close buy stop-loss");
      }
      if(m_macd_current>0&&m_macd_previous<=0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close by signal");
      }
   }
   return(rez);
}

La classe de base CModel n'impose aucune restriction sur le contenu interne de ses descendants. La seule chose qu'il rend obligatoire, c'est l'utilisation de la fonction Processing() interface. Tous les problèmes d'organisation interne de cette fonction sont délégués à une classe spécifique de modèles. Il n'y a pas d'algorithme universel qui puisse être placé dans une fonction Processing(), par conséquent, il n'y a aucune raison d'imposer aux descendants sa méthode de comment un modèle particulier doit être organisé. Cependant, la structure interne de presque n'importe quel modèle peut être standardisée. Une standardisation pareille simplifiera considérablement la compréhension d'un code externe et même de votre code, et rendra le modèle plus "formulaire".

Chaque modèle doit avoir son propre amorceur. L'amorceur du modèle est responsable du chargement des paramètres corrects, qui sont nécessaires à son fonctionnement, par exemple, pour que notre modèle fonctionne, nous devons sélectionner les valeurs , obtenir une poignée du tampon correspondant et en plus, bien entendu,déterminer l’instrument du trading et l’intervalle de tempspour le modèle. Tout cela doit être fait par l'amorceur

Les amorceurs de modèles - sont de simples méthodes surchargées de leurs classes. Ces méthodes ont un nom commun Init. Le fait est que MQL5 ne prend pas en charge les constructeurs surchargés, il n'y a donc aucun moyen de créer l'amorceur du modèle dans son constructeur, car la surcharge sera nécessaire pour les paramètres d'entrée. Bien que personne ne nous empêche d'indiquer dans le constructeur du modèle ses paramètres de base.

Chaque modèle doit avoir trois amorceurs. Premièrement - l'amorceur par défaut. Il doit configurer et charger le modèle par défaut, sans demander les paramètres. Cela peut être très pratique pour tester en mode « tel quel ». Par exemple, pour notre modèle, l'amorceur par défaut en tant qu'outil d'instrument et intervalle de temps du modèle, sélectionnera le graphique actuel et l’actuel intervalle de temps

Les réglages de MACD indicateur seront standard rapide EMA = 12, lent EMA = 26, signal MA = 9; S’il est nécessaire de configurer le modèle d’une certaine manière, un tel amorceur ne sera plus convenable. Nous aurons besoin d'amorceurs avec des paramètres. Il est souhaitable (mais pas nécessairement) d'en faire deux types. Le premier recevra ses paramètres comme une fonction classique : Init (tapez param1, tapez param2, ..., tapez paramN).. Le second découvrira les paramètres du modèle à l'aide d'une structure spéciale, qui enregistre ces paramètres. Cette option est parfois plus préférable car parfois le nombre de paramètres peut être important, auquel cas il serait pratique de les passer à travers les structures.

Chaque modèle dispose de la structure de ses paramètres. Cette structure peut avoir n'importe quel nom, mais il est préférable de l'appeler par le modèle modelname_param. La configuration du modèle - est une étape capitale dans l'utilisation des opportunités de trading multi-time-frame/multi-system/multi-devises. C'est à ce stade qu'il est déterminé, comment et sur quel instrument ce modèle sera tradé.

Notre modèle de trading n'a que quatre fonctions de trading. La fonction pour ouvrir une position longue : LongOpen,la fonction pour l’ouverture d’une position courte: ShortOpen, la fonction pour la clôture d’une position longue: LongClosed, et la fonction de clôturer une courteposition: ShortClosed. Le travail des fonctions LongOpen и ShortOpen est trivial. Les deux reçoivent la valeur de l'indicateur de la barre précédente MACD comparée à la valeur des deux barres précédentes. Pour éviter le "re-traçage", l’actuelle barre (zéro) ne sera pas utilisée.

s’il y a croisement vers le bas, donc la fonction ShortOpencalcule en utilisant les fonctions comprises dans le fichier en-tête mm.mqh, après quoi le lot envoie sa commande vers la fonction OrderSend . The LongClose à ce moment, au contraire, clôture toutes les longues positions du modèle. Cela se produit parce que la fonction trie successivement toutes les commandes en cours dans la table des commandes du modèle. S'il y a une longue commande trouvée, alors la fonction la clôture avec une contre-commande La même chose, bien que dans la direction opposée, est effectuée par la fonction ShortClose() .. Le travail de ces fonctions peut être trouvé dans la liste fournie ci-dessus.

Analysons plus en détail comment le lot actuel est calculé dans le modèle de trading.

Comme mentionné ci-dessus, à ces fins, nous utilisons des fonctions spéciales pour la capitalisation du compte. En plus des formules de capitalisations, ces fonctions comportent la vérification du calcul du lot, basé sur le niveau de la marge utilisée et par la restriction de la taille des positions de trading. Il peut y avoir certaines restrictions de trading sur la taille de la position. Ils doivent être pris en compte.

Examinons le cas suivant : la taille d'une position de trading est limitée à 15 lots standard dans les deux sens. La position actuelle est longue et équivaut à 3 lots. Le modèle de trading, basé sur son système de gestion du capital, souhaite ouvrir une longue position avec un volume de 18,6 lots. La fonction CheckLot() renverra le montant corrigé du volume de la commande. Dans ce cas, il sera égal à 12 lots (puisque 3 lots sur 15 sont déjà occupés par d'autres deals). Si l’actuelle position ouverte était courte et non longue, alors la fonction aurait renvoyé 15 lots au lieu de 18,6. C'est le volume maximum possible des positions.

Après avoir mis en place 15 lots à acheter, la position nette, dans ce cas, sera de 12 lots (3 - vendre, 15 -acheter ). Lorsqu'un autre modèle annule sa courte position initiale de 3 lots, acheter la position agrégée atteindra le maximum possible - 15 lots. Les autres signaux d'achat ne seront pas traités tant que le modèle n'aura pas remplacé tout ou une partie de ses 15 lots acheter Si le volume disponible pour la transaction demandée est épuisé, donc la fonction renverra une valeur constante EMPTY_ . Ce signal doit être transmis.

Si la vérification de la possibilité du volume défini a abouti, des calculs sont effectués sur la valeur de la marge requise. Les fonds du compte peuvent être insuffisants pour le volume défini. À ces fins, il existe une fonction CheckMargin(). Si la marge n'est pas suffisante, elle tentera de corriger le montant indiqué de la transaction afin que la marge libre actuelle lui permette de s'ouvrir. Si la marge n'est même pas suffisante pour ouvrir le volume minimum, alors nous sommes dans un état de Margin-Call.Margin-Call.

S'il n'y a actuellement aucune position et que la marge n'est pas utilisée, cela ne signifie qu'une seule chose - l'appel de marge technique -- un état où il est impossible d'ouvrir un deal. Sans ajouter d'argent sur le compte, nous ne pouvons pas continuer. Si une marge est toujours en usage, alors nous n'avons pas d'autre choix que d'attendre la clôture de la position qui utilise cette marge. Dans tous les cas, le manque de marge renverra une valeur constante EMPTY_.

Les fonctions de contrôle de la taille du lot et des marges ne sont généralement pas utilisées directement. Elles sont appelées par des fonctions spéciales de gestion du capital. Ces fonctions mettent en œuvre les formules de capitalisation des comptes. Le fichier mm..mqh ne comprend que deux fonctions de base de la gestion du capital, l'une est calculée sur la base d'une fraction fixe du compte, et l'autre, sur la base de la méthode proposée par Ryan Jones, dite méthode des proportions fixes.

Le but de la première méthode est de définir une partie fixe du compte, qui peut être risquée. Par exemple, si le risque autorisé est de 2% du compte et que le compte est égal à 10 000 dollars, le montant maximal à risque est de 200 $. Afin de calculer quel lot doit être utilisé pour un stop de 200 dollars, vous devez savoir exactement quelledistance maximale peut être atteinte par le prix par rapport à la position ouverte. Par conséquent, pour calculer le lot à l'aide de cette formule, nous devons déterminer avec précision le Stop Loss et le niveau de prix auquel le deal sera conclu.

La méthode proposée par Ryan Jones est différente de la précédente. Son essence est que la capitalisation se fait par la fonction, définie par un cas particulier d'une équation quadratique.

Voici sa solution :

x=((1.0+MathSqrt(1+4.0*d))/2)*Step;

où: x - La limite inférieure de la transition vers le niveau suivant d = (Profit / delta) * 2.0 Step- une étape de delta telle que 0.1 lots.

Plus la taille du delta est faible, plus la fonction essaie d'augmenter le nombre de positions de manière agressive. Pour plus de détails sur la façon dont cette fonction est construite, vous pouvez vous référer au livre de Ryan Jones : Le jeu du Trading: Jouer par les Chiffres pour Gagner des Millions.

Si l’usage des fonctions de gestion du capital n’est pas prévu, il faut appeler directement les fonctions de contrôle du lot et de la marge.

Nous avons donc passé en revue tous les éléments de notre EA de base. Il est temps de récolter les fruits de notre travail.

Pour commencer, créons quatre modèles. Laissez un modèle se trader par des EURUSD paramètres par défaut, le second tradera également sur EURUSD, mais sur un intervalle de temps de 15 minutes. Le troisième modèle sera lancé sur le graphiqueGBPUSD avec les paramètres par défaut. Le quatrième - sur USDCHF sur un graphique de deux heures, avec les paramètres suivants : SlowEMA= 6 FastEMA = 12 SignalEMA = 9. La période de test - H1, le mode de test - tous les ticks, la date du 01.01.2010 au 01.09.2010.

Mais avant d'exécuter ce modèle dans quatre modes différents, nous allons d'abord essayer de le tester pour chaque instrument et chaque intervalle de temps séparément.

Voici le tableau sur lequel sont établis les principaux indicateurs de test :

Système
Le nombre de Deals
bénéfice
MACD(9,12,26)H1 EURUSD
123
1092
MACD (9,12,26) EURUSD M15
598
-696
MACD(9,6,12) USDCHF H2
153
-1150
MACD(9,12,26) GBPUSD S1
139
-282
Tous les systèmes
1013
-1032

Le tableau indique que le nombre total de deals pour tous les modèles doit être de 1013 et que le bénéfice total doit être de -1032 $.

Par conséquent, ce sont les mêmes valeurs que nous devrions obtenir si nous testons ces systèmes en même temps. Les résultats ne devraient pas différer, bien que quelques écarts mineurs se produisent encore.

Voici donc le test final :


Comme on peut le constater, il n'y a qu'un deal de moins, et les gains ne diffèrent que de 10 $, ce qui correspond à seulement 10 points de différence avec un lot de 0,1. Il convient de noter que les résultats des tests combinés, dans le cas de l'utilisation du système de gestion de l'argent, seront radicalement différents de la quantité de résultats des tests de chaque modèle individuellement. C’est parce que , la dynamique de l’équilibre affecte chacun des systèmes, de sorte que les valeurs calculées des lots varient. 

Malgré le fait qu'à eux seuls, les résultats ne présentent pas d'intérêt, nous avons créé une structure compliquée, mais très flexible et gérable de l'EA. Considérons brièvement à nouveau sa structure.

Pour cela, nous allons nous tourner vers le schéma ci-dessous:

Référence de lasse

Le schéma montre la structure de base de l'instance du modèle.

Une fois qu'une instance de classe du modèle de trading est créée, nous appelons la fonction surchargée Init (). EIle initialise les paramètres nécessaires, prépare les données, charge les poignées desIndicateurs , s'ils sont utilisés. Tout cela se passe lors de l'initialisation de l'EA, c'est-à-dire à l'intérieur de la fonctionOnInit() . Notez que les données comportent des instances de classes de base, conçues pour simplifier le trade. On suppose que les modèles de trading doivent utiliser activement ces classes au lieu des fonctions standard de MQL5. Une fois que la classe du modèle est créée avec succès et initialisée, elle tombe dans la liste des modèles CList La communication ultérieure avec lui s'effectue via un adaptateur universelCObject..

Après l'occurrence des événementsOnTrade() ou OnTick() , il y a un tri successif de toutes les instances des modèles de la liste. La communication avec eux s'effectue en appelant la fonction Processing(). De plus, il appelle les fonctions de trading de son propre modèle (le groupe de fonctions bleu). Leurs liste et noms ne sont pas strictement définis, mais il est pratique d'utiliser les noms standard tels que LongOpened(), ShortClosed(), etc. Ces fonctions, en fonction de leur logique intégrée, choisissaient l'heure pour conclure le deal, puis envoient une demande spécialement formée pour l'ouverture ou la clôture du deal de la fonction SendOrder().

Ce dernier effectue les vérifications nécessaires, puis sort les commandes sur le marché. Les fonctions de trading reposent sur les fonctions auxiliaires du modèle (groupe vert), qui, à leur tour, utilisent activement les classes auxiliaires de base (groupe violet). Toutes les classes auxiliaires sont représentées comme des instances de classes dans la section de données (groupe rose). L'interaction entre les groupes est représentée par des flèches bleu foncé.


Le modèle de trading, basé sur l'indicateur des bandes de Bollinger

Maintenant que la structure générale des données et des méthodes devient claire, nous allons créer un autre modèle de trading, basé sur les indicateurs de tendance des Bollinger Bands. Comme base de ce modèle de trading, nous avons utilisé un simple EA An Expert Advisor, basé sur les bandes de Bollinger Bandes de Bollinger - sont des niveaux, égaux à une certaine taille d'écarts types par rapport à la moyenne mobile simple. Plus de détails sur la façon dont cet indicateur est construit peuvent être trouvés dans la section Aide pour l'analyse technique, attachée au terminal MetaTrader 5.

L'essence de l'idée de trading est simple : le prix a la propriété de revenir, c'est-à-dire que si le prix atteint un certain niveau, alors très probablement, il tournera dans la direction opposée. Cette thèse est prouvée par un test sur une distribution normale de tout instrument de trading réel : la courbe de distribution normale sera légèrement allongée. Les bandes de Bollinger déterminent les points culminants les plus probables des niveaux de prix. Une fois qu'elles sont atteintes (bandes de Bollinger supérieures ou inférieures), le prix est susceptible de tourner en sens inverse.

Nous simplifions légèrement la tactique de trading et n'utiliserons pas l'indicateur auxiliaire - la moyenne mobile exponentielle double (Moyenne mobile double exponentielle, ouDEMA). Mais nous utiliserons des stops de protection stricts - des Stop Loss virtuels. Ils rendront le processus de trading plus stable et nous aideront en même temps à comprendre un exemple, dans lequel chaque modèle de trading utilise son propre niveau indépendant d'arrêts de sécurité.

Pour le niveau des stops de protection, nous utilisons le prix actuel plus ou moins la valeur de l'indicateur volatilité ATR. Par exemple, si la valeur actuelle de l' ATR est égale à 68 points et qu'il y a un signal de vente au prix de 1,25720, alors le Stop Loss virtuel pour ce deal sera égal à 1,25720 + 0,0068 = 1,26400. De même, mais en sens inverse, c'est fait pour acheter : 1.25720 - 0.0068 = 1.25040.

Le code source de ce modèle est fourni ci-dessous :

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model use Bollinger bands.
//| Buy when price is lower than lower band
//| Sell when price is higher than upper band
//+----------------------------------------------------------------------+  
struct cmodel_bollinger_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               period_bollinger;
   double            deviation;
   int               shift_bands;
   int               period_ATR;
   double            k_ATR;
   double            delta;
};
   
class cmodel_bollinger : public CModel
{
private:
   int               m_bollinger_period;
   double            m_deviation;
   int               m_bands_shift;
   int               m_ATR_period;
   double            m_k_ATR;
   //------------Indicators Data:-------------
   int               m_bollinger_handle;
   int               m_ATR_handle;
   double            m_bollinger_buff_main[];
   double            m_ATR_buff_main[];
   //-----------------------------------------
   MqlRates          m_raters[];
   double            m_current_price;
public:
                     cmodel_bollinger();
   bool              Init();
   bool              Init(cmodel_bollinger_param &m_param);
   bool              Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES TimeFrame, double delta,
                          uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_bollinger_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
   bool              CloseByStopSignal();
};

cmodel_bollinger::cmodel_bollinger()
{
   m_bollinger_handle   = INVALID_HANDLE;
   m_ATR_handle         = INVALID_HANDLE;
   ArraySetAsSeries(m_bollinger_buff_main,true);
   ArraySetAsSeries(m_ATR_buff_main,true);
   ArraySetAsSeries(m_raters, true);
   m_current_price=0.0;
}
//this default loader
bool cmodel_bollinger::Init()
{
   m_magic              = 322311;
   m_model_name         =  "Bollinger Bands Model";
   m_symbol             = _Symbol;
   m_timeframe          = _Period;
   m_bollinger_period   = 20;
   m_deviation          = 2.0;
   m_bands_shift        = 0;
   m_ATR_period         = 20;
   m_k_ATR              = 2.0;
   m_delta              = 0;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(cmodel_bollinger_param &m_param)
{
   m_magic              = 322311;
   m_model_name         = "Bollinger Model";
   m_symbol             = m_param.symbol;
   m_timeframe          = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_bollinger_period   = m_param.period_bollinger;
   m_deviation          = m_param.deviation;
   m_bands_shift        = m_param.shift_bands;
   m_ATR_period        = m_param.period_ATR;
   m_k_ATR              = m_param.k_ATR;
   m_delta              = m_param.delta;
   //if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES timeframe, double delta,
                           uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR)
{
   m_magic           = magic;
   m_model_name      = name;
   m_symbol          = symbol;
   m_timeframe       = timeframe;
   m_delta           = delta;
   m_bollinger_period= bollinger_period;
   m_deviation       = deviation;
   m_bands_shift     = bands_shift;
   m_ATR_period      = ATR_period;
   m_k_ATR           = k_ATR;
   if(!InitIndicators())return(false);
   return(true);
}


/*bool cmodel_bollinger::CheckParam(cmodel_bollinger_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT)){
      Print("Symbol ", m_symbol, " select failed. Check valid name symbol");
      return(false);
   }
   if(m_ma == 0){
      Print("Fast EMA must be bigest 0. Set MA = 12 (default)");
      m_ma=12;
   }
   return(true);
}*/

bool cmodel_bollinger::InitIndicators()
{
   m_bollinger_handle=iBands(m_symbol,m_timeframe,m_bollinger_period,m_bands_shift,m_deviation,PRICE_CLOSE);
   if(m_bollinger_handle==INVALID_HANDLE){
      Print("Error in creation of Bollinger indicator. Restart the Expert Advisor.");
      return(false);
   }
   m_ATR_handle=iATR(m_symbol,m_timeframe,m_ATR_period);
   if(m_ATR_handle==INVALID_HANDLE){
      Print("Error in creation of ATR indicator. Restart the Expert Advisor.");
      return(false);
   }
   return(true);
}

bool cmodel_bollinger::Processing()
{
   //if(timing(m_symbol,m_timeframe, m_timing)==false)return(false);
   
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   //m_symbol_info.Name(m_symbol);
   //m_symbol_info.RefreshRates();
   //Copy last data of moving average
 
   GetNumberOrders(m_orders);
   
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   if(m_orders.all_orders!=0)CloseByStopSignal();
   return(true);
}

bool cmodel_bollinger::LongOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   //Print("Model Bollinger: ", m_orders.buy_orders);
   bool rezult, time_buy=true;
   double lot=0.1;
   double sl=0.0;
   double tp=0.0;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   //lot=open_mm.optimal_f(m_symbol,OP_BUY,m_symbol_info.Ask(),sl,delta);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      sl=NormalizeDouble(m_symbol_info.Ask()-m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_BUY,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::ShortOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, time_sell=true;
   double lot=0.1;
   double sl=0.0;
   double tp;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {   
      sl=NormalizeDouble(m_symbol_info.Bid()+m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_SELL,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_BUY)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_SELL)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::CloseByStopSignal(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   bool rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL&&t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyRates(m_symbol,m_timeframe,0,3,m_raters);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.Type()==ORDER_TYPE_BUY)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by stop");
         continue;
      }
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.Type()==ORDER_TYPE_SELL)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by stop");
         continue;
      }
   }
   return(rez);
}

Comme on peut le constater, le code du modèle de trading est très similaire au code source des tactiques de trading précédentes. La principale variation ici est l'émergence de commandes stop virtuelles et de la fonction cmodel_bollinger : : CloseByStopSignal(), servant ces arrêts de sécurité.

En effet, en cas d'utilisation des arrêts de sécurité, leurs valeurs doivent simplement être transmises à la fonction SendOrder(). Et cette fonction entrera ces niveaux dans la table des commandes. Lorsque le prix franchit ou touche ces niveaux, la fonction CloseByStopSignal() fermera le deal avec une contre-commande et le retirera de la liste des commandes actives.


La combinaison de modèles de trading, d'instruments et de délais en une seule entité

Maintenant que nous avons deux modèles de trading, il est temps de les tester simultanément. Avant de faire une mise en page des modèles, il serait utile de déterminer leurs paramètres les plus efficaces. Pour le faire, nous devons faire l'optimisation de chaque modèle individuellement.

L'optimisation démontrera sur quel marché et sur quel intervalle de temps le modèle est le plus efficace. Pour chaque modèle, nous ne sélectionnerons seulement deux des meilleurs intervalles de temps et trois meilleurs instruments. En conséquence, nous obtenons douze solutions indépendantes de (2 modèles * 3 instruments * 2 intervalles de temps), qui seront testées toutes ensemble. Bien sûr, la méthode d'optimisation choisie souffre du soi-disant "ajustement" des résultats, mais pour nos besoins, cela n'a pas d'importance.

Les graphiques ci-dessous montrent les meilleurs résultats de l'échantillon :

MACD EURUSD M30

MACD EURUSD M30

1.2 . MACD EURUSD H3


MACD EURUSD H3

1.3 MACD AUDUSD H4

MACD AUDUSD H4

1.4 . MACD AUDUSD H1


MACD AUDUSD H1

1.5 MACD GBPUSD H12


MACD GBPUSD H12

1.6 MACD GBPUSD H6


MACD GBPUSD H6

2.1 Bollinger GBPUSD M15


Bollinger GBPUSD M15

2.2 Bollinger GBPUSD H1


Bollinger GBPUSD H1

2.3 Bollinger EURUSD M30

Bollinger EURUSD M30

 

2.4  Bollinger EURUSD H4


Bollinger EURUSD H4

 

2.5 Bollinger USDCAD M15


Bollinger USDCAD M15

 

2.6 Bollinger USDCAD H2

Bollinger USDCAD H2

Maintenant que les résultats optimaux sont connus, il ne nous reste qu'un peu plus à faire - rassembler les résultats en une seule entité.

Vous trouverez ci-dessous le code source du chargeur de fonctions, qui crée les douze modèles de trading, illustrés ci-dessus, après quoi l'EA commence systématiquement à trader  :

bool macd_default=true;
bool macd_best=false;
bool bollinger_default=false;
bool bollinger_best=false;

void InitModels()
{
   list_model = new CList;             // Initialized pointer of the model list
   cmodel_macd *model_macd;            // Create the pointer to a model MACD
   cmodel_bollinger *model_bollinger;  // Create the pointer to a model Bollinger
   
//----------------------------------------MACD DEFAULT----------------------------------------
   if(macd_default==true&&macd_best==false)
    {
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      // Loading of the parameters was completed successfully
      if(model_macd.Init(129475, "Model macd M15", _Symbol, _Period, 0.0, Fast_MA,Slow_MA,Signal_MA))
      { 
      
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Загружаем модель в список моделей
      }
      else
      {
                                 // The loading of parameters was completed successfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//-------------------------------------------------------------------------------------------
//----------------------------------------MACD BEST------------------------------------------
   if(macd_best==true&&macd_default==false)
   {
      // 1.1 EURUSD H30; FMA=20; SMA=24; 
      model_macd = new cmodel_macd; // Initialize the pointer to the model MACD
      if(model_macd.Init(129475, "Model macd H30", "EURUSD", PERIOD_M30, 0.0, 20,24,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " created successfully");
         list_model.Add(model_macd);// load the model into the list of models
      }
      else
      {// Loading parameters was completed unsuccessfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.2 EURUSD H3; FMA=8; SMA=12; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H3", "EURUSD", PERIOD_H3, 0.0, 8,12,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
       {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.3 AUDUSD H1; FMA=10; SMA=18; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd M15", "AUDUSD", PERIOD_H1, 0.0, 10,18,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// The loading of parameters was unsuccessful                       
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.4 AUDUSD H4; FMA=14; SMA=15; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H4", "AUDUSD", PERIOD_H4, 0.0, 14,15,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else{// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.5 GBPUSD H6; FMA=20; SMA=33; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H6, 0.0, 20,33,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was  unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.6 GBPUSD H12; FMA=12; SMA=30; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H12, 0.0, 12,30,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//----------------------------------------------------------------------------------------------
//-------------------------------------BOLLINGER DEFAULT----------------------------------------
   if(bollinger_default==true&&bollinger_best==false)
   {
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger",_Symbol,PERIOD_CURRENT,0,
                             period_bollinger,dev_bollinger,0,14,k_ATR))
      {
         Print("Model ", model_bollinger.Name(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
//--------------------------------------BOLLLINGER BEST-----------------------------------------
   if(bollinger_best==true&&bollinger_default==false)
   {
      //2.1 Symbol: EURUSD M30; period: 15; deviation: 2,75; k_ATR=2,75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_M30,0,15,2.75,0,14,2.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
              ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.2 Symbol: EURUSD H4; period: 30; deviation: 2.0; k_ATR=2.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_H4,0,30,2.00,0,14,2.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.3 Symbol: GBPUSD M15; period: 18; deviation: 2.25; k_ATR=3.0;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","GBPUSD",PERIOD_M15,0,18,2.25,0,14,3.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.4 Symbol: GBPUSD H1; period: 27; deviation: 2.25; k_ATR=3.75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","GBPUSD",PERIOD_H1,0,27,2.25,0,14,3.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.5 Symbol: USDCAD M15; period: 18; deviation: 2.5; k_ATR=2.00;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_M15,0,18,2.50,0,14,2.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.6 Symbol: USDCAD M15; period: 21; deviation: 2.5; k_ATR=3.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_H2,0,21,2.50,0,14,3.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
}

Testons maintenant les douze modèles simultanément :


Capitalisation des résultats

Le graphique obtenu est impressionnant. Cependant, ce qui est important n'est pas le résultat, mais le fait que tous les modèles tradent simultanément, utilisant tous leurs arrêts de sécurité individuelles, et tous indépendants les uns des autres. Essayons maintenant de capitaliser le graphique résultant. Pour cela nous utiliserons les fonctions classiques de capitalisation : méthode proportionnelle fixe et méthode proposée par Ryan Jones.

Le soi-disant-optimal f un cas particulier de la méthode proportionnelle fixe. L'essence de cette méthode est que chaque deal se voit attribuer une limite de pertes, égale à un pourcentage du compte. Les stratégies de capitalisation prudentes appliquent généralement une limite de perte de 2 %. C'est-à-dire à 10 000 $, la taille de la position est calculée pour que la perte, une fois le Stop Loss transmis, ne dépasse pas 200 $. Il y a cependant une certaine fonction de la croissance du solde final à partir de l'augmentation du risque. Cette fonction a une forme en cloche. C'est-à-dire dans un premier temps, avec l'augmentation du risque, il y a une augmentation du profit total. Cependant, il existe un seuil de risque pour chaque deal, après quoi le solde total des bénéfices commence à diminuer. Ce seuil est connu sous le nom de f optimal.

Cet article n'est pas consacré à la question de la gestion du capital, tout ce que nous devons savoir pour utiliser la méthode proportionnelle fixe - c'est le niveau des arrêts de sécurité, et le pourcentage du compte que nous pouvons risquer. La formule de Ryan Jones est construite différemment. Pour son travail, l'utilisation des arrêts de sécurité fixes n'est pas requise. Puisque le premier des modèles proposés (MACDmodel), est plutôt primitif et ne dispose pas des arrêts de sécurité, nous utiliserons cette méthode pour la capitalisation de ce modèle. Pour le modèle, basé sur les bandes de Bollinger, nous utiliserons la méthode des proportions fixes proportionnelles.

Afin de commence l’utilisation de la formule de capitalisation, nous devons remplir la variable m_delta, comprise dans le modèle de base.

Lors de l'utilisation de la formule des proportions fixes, il doit être égal au pourcentage de risque pour chaque deal Lorsque vous utilisez la méthode de Ryan Jones, il est égal à ce que l'on appelle l'incrément delta, c'est-à-dire au montant d'argent qui doit être gagné pour atteindre un niveau de volume de position plus élevé.

Ci-dessous le graphique de la capitalisation :


Comme on peut le constater, tous les modèles ont leurs propres formules de capitalisation (la méthode proportionnelle fixe ou la méthode de Ryan Jones).

Dans l'exemple fourni, nous avons utilisé les mêmes valeurs de risque maximum et de delta pour tous les modèles. Cependant, pour chacun d'eux, nous pouvons sélectionner des paramètres individuels pour la capitalisation. La mise au point de chacun des modèles n'est pas comprise dans l'éventail des questions couvertes par cet article.


Travailler avec les commandes en cours

Les modèles de trading présentés n'utilisent pas les commandes dites en cours. Ce sont des commandes qui ne sont exécutées que lorsque certaines conditions générales sont présentes.

En réalité, toute stratégie de trading utilisant des commandes en cours peut être adaptée à l'utilisation des commandes sur le marché. Au contraire, les commandes en cours sont nécessaires pour un fonctionnement plus fiable du système, car lorsque la panne du robot ou de l'équipement sur lequel il fonctionne, les commandes en cours exécuteraient toujours des arrêts de sécurité, ou vice versa, entreraient sur le marché en fonction sur les prix préalablement déterminés.

Le modèle de trading proposé vous permet de travailler avec des commandes de trading en cours, bien que le processus de contrôle de leur présentation, dans ce cas, soit beaucoup plus compliqué. Pour travailler avec ces commandes, nous utilisons la méthode surchargée Add (COrderInfo & order_info, double stop_loss, double take_profit) de la classe CTableOrders. Dans ce cas, la variable m_type de cette classe comportera le type approprié de la commande en cours, par exemple ORDER_TYPE_BUY_STOP ou ORDER_TYPE_SELL_LIMIT.

Plus tard, lorsque la commande en cours sera émise, vous devez "saisir" le moment où elle a été déclenchée pour fonctionner, sinon sa durée de validité expirera. Ce n'est pas si simple, puisqu'il ne suffit pas de simplement contrôler l'événement Trade, mais il faut aussi savoir ce qui l'a déclenché.

Le langage de MQL5 évolue, et en ce moment la question de l'inclusion d'une structure de service spéciale dans celui-ci, qui expliquerait l'événementTrade , est en cours de résolution. Mais pour l'instant, nous devons afficher la liste des commandes dans l'historique. Si une commande, avec le même ticket que la commande en cours dans le tableau des commandes, est trouvée dans l'historique, alors, il s'est produit des événements qui doivent être reflétés dans le tableau. 

Le code du modèle de base comprend une méthode spéciale CModel : : ReplaceDelayedOrders. Cette méthode fonctionne sur l'algorithme suivant. Tout d'abord, toutes les commandes actives dans le tableau des commandes sont vérifiées. Le ticket de ces commandes est comparé au ticket des commandes dans l'historique (HistoryOrderGetTicket()). Si le ticket de l'ordre dans l'historique coïncide avec l'ordre en cours dans le tableau des commandes, mais quele statut de la commande est exécuté (ORDER_STATE_PARTIAL ouORDER_STATE_FILLED), alors le statut des commandes en cours dans le tableau des commandes est également modifié pour exécuté.

De plus, si cet ordre n'est lié à aucun ordre en cours, imitant le travail du Stop Loss et du Take Profit, cet ordre est émis et leurs tickets sont entrés dans les valeurs de table appropriées (TicketSL, TicketTP). Et les commandes en cours, imitant les niveaux de stop et de profit de protection, sont émis au prix, spécifié à l'avance à l'aide des variables m_sl et m_tp. C'est-à-dire que ces prix doivent être connus au moment d’appeler de la méthode ReplaceDelayedOrders.

Il est à noter que la méthode est adaptée pour gérer la situation lors de la mise sur le marché d'un ordre, sur lequel les commandes en cours requis sont de type Stop Loss et Take Profit.

En général, le travail de la méthode proposée n'est pas anodin, et nécessite une certaine compréhension pour son utilisation :

bool CModel::ReplaceDelayedOrders(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int history_orders=HistoryOrdersTotal();
   ulong ticket;
   bool rez=false;
   long request;
   total_elements=ListTableOrders.Total();
   int try=0;
   if(total_elements==0)return(false);
   // View every order in the table
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      switch(t.Type())
      {
         case ORDER_TYPE_BUY:
         case ORDER_TYPE_SELL:
            for(int b=0;i<history_orders;b++)
            {
               ticket=HistoryOrderGetTicket(b);
               // If the ticket of the historical order is equal to one of the tickets 
               // Stop Loss or Take Profit, then the order was blocked, and it needs to be
               // deleted from the table of orders
               if(ticket==t.TicketSL()||ticket==t.TicketTP())
               {
                  ListTableOrders.DeleteCurrent();
               }
            }
            // If the orders, imitating the Stop Loss and Take Profit are not found in the history,
            // then perhaps they are not yet set. Therefore, they need to be inputted,
            // using the process for pending orders below:
            // the cycle  keeps going, the exit 'break' does not exist!!!
         case ORDER_TYPE_BUY_LIMIT:
         case ORDER_TYPE_BUY_STOP:
         case ORDER_TYPE_BUY_STOP_LIMIT:
         case ORDER_TYPE_SELL_LIMIT:
         case ORDER_TYPE_SELL_STOP:
         case ORDER_TYPE_SELL_STOP_LIMIT:
            for(int b=0;i<history_orders;b++)
            {
               ticket=HistoryOrderGetTicket(b);
               // If the ticket of the historical order is equal to the ticket of the pending order 
               // then the pending order has worked and needs to be put out
               // the pending orders, imitating the work of Stop Loss and Take Profit.
               // It is also necessary to change the pending status of the order in the table
               // of orders for the executed (ORDER_TYPE_BUY или ORDER_TYPE_SELL)
               m_order_info.InfoInteger(ORDER_STATE,request);
               if(t.Ticket()==ticket&&
                  (request==ORDER_STATE_PARTIAL||request==ORDER_STATE_FILLED))
                  {
                  // Change the status order in the table of orders:
                  m_order_info.InfoInteger(ORDER_TYPE,request);
                  if(t.Type()!=request)t.Type(request);
                  //------------------------------------------------------------------
                  // Put out the pending orders, imitating Stop Loss an Take Profit:
                  // The level of pending orders, imitating Stop Loss and Take Profit
                  // should be determined earlier. It is also necessary to make sure that
                  // the current order is not already linked with the pending order, imitating Stop Loss
                  // and Take Profit:
                  if(t.StopLoss()!=0.0&&t.TicketSL()==0)
                    {
                     // Try to put out the pending order:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put up a pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
                  if(t.TakeProfit()!=0.0&&t.TicketTP()==0){
                     // Attempt to put out the pending order, imitating Take Profit:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                           break;
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
               }
            }
            break;
         
      }
   }
   return(true);
}

En utilisant cette méthode, vous pouvez créer sans effort un modèle basé sur les commandes en cours.


Conclusion

Malheureusement, il est impossible de couvrir dans un seul article toutes les nuances, les défis et les avantages de l'approche proposée. Nous n'avons pas envisagé la sérialisation des données - une méthode vous permettant de stocker, d'enregistrer et de récupérer toutes les informations nécessaires sur l'état actuel des modèles à partir des fichiers de données. Au-delà de notre considération sont restés les modèles de trading, basés sur le commerce de spreads synthétiques. Ce sont des sujets très intéressants et, certainement, ils ont leurs propres solutions efficaces pour les concepts proposés.

La principale chose que nous avons réussi à faire est d’élaborer une structure de données entièrement dynamique et gérable. Le concept de listes chaînées les gère efficacement, rend les tactiques de trading indépendantes et individuellement personnalisables. Un autre avantage très important de cette approche est qu'elle est complètement universelle.

Par exemple, sur sa base, il suffit de créer deux EA de trading et de les placer sur le même instrument. Les deux ne fonctionneront automatiquement qu'avec leurs propres commandes, et uniquement avec leur propre système de gestion du capital. Ainsi, il existe un support d'une compatibilité descendante. Tout ce qui peut être fait simultanément dans un seul EA, peut être réparti entre plusieurs robots. Cette propriété est extrêmement importante lorsque vous travaillez dans des positions nettes.

Le modèle décrit n'est pas qu'une théorie. Il comprend un appareil élaboré de fonctions auxiliaires, les fonctions de gestion du capital et de contrôle des besoins marginaux. Le système d'envoi de commandes résiste aux soi-disant re-cotations et glissements - dont les effets sont souvent observés dans le commerce réel.

Le moteur de trading détermine la taille maximale de la position et le volume maximal de deals. Un algorithme spécial sépare les demandes de trading en plusieurs deals indépendants, chacun étant traité séparément. De plus, le modèle présenté a fait ses preuves dansAutomated Trading Championship of 2010 - tous les deals sont effectués avec précision et conformément au plan de trading, les modèles de trading, présentés sur le Championnat, sont gérés avec des degrés de risques variables, et sur différents systèmes de gestion d’argent.

L'approche présentée est une solution presque complète pour la participation aux Championnats, ainsi que pour le fonctionnement parallèle sur plusieurs instruments et intervalles de temps, sur plusieurs tactiques de trading. La seule difficulté pour se familiariser avec cette approche réside dans sa complexité. 

Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/217

Fichiers joints |
files-en.zip (18.6 KB)
Les indicateurs des tendances micro, moyenne et principale Les indicateurs des tendances micro, moyenne et principale
Le but de cet article est d'étudier les possibilités de l'automatisation du trade et de l'analyse, sur la base de quelques idées d'un livre de James Hyerczyk "Pattern, Price & Time: Utilisation de la théorie de Gann dans les systèmes de trading" sous forme d'indicateurs et d'Expert Advisor. Sans prétendre à l'exhaustivité, nous n'étudions ici que le Modèle - la première partie de la théorie de Gann.
Canaux de traçage - Schéma intérieure et extérieure Canaux de traçage - Schéma intérieure et extérieure
Je suppose que ce ne sera pas une exagération, si je dis que les canaux sont l'outil le plus populaire pour l'analyse du marché et la prise de décisions en trade après les moyennes mobiles. Sans plonger profondément dans la masse des stratégies de trade qui utilisent des canaux et leurs composants, nous allons discuter de la base mathématique et de l’implémentation pratique d'un indicateur, qui trace un canal déterminé par trois extremums sur l'écran du terminal client.
Approche Économétrique de l'Analyse des Graphiques Approche Économétrique de l'Analyse des Graphiques
Cet article décrit les méthodes d'analyse économétriques, l'analyse d'auto-corrélation et l'analyse de variance conditionnelle en particulier. Quel est l'avantage de l'approche décrite ici? L'utilisation des modèles GARCH non linéaires permet de représenter formellement la série analysée du point de vue mathématique et de créer une prévision pour un nombre spécifié d'étapes.
Calculs Parallèles dans MetaTrader 5 Calculs Parallèles dans MetaTrader 5
Le temps a été une grande valeur tout au long de l'histoire de l'humanité, et nous nous efforçons de ne pas le gaspiller inutilement. Cet article vous indiquera comment accélérer le travail de votre Expert Advisor si votre ordinateur dispose d'un processeur multi-noyau. De plus, l’implémentation de la méthode proposée ne nécessite la connaissance d'aucun autre langage que MQL5.