English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Présentation de MQL5 : Comment écrire un simple Expert Advisor et un indicateur personnalisé

Présentation de MQL5 : Comment écrire un simple Expert Advisor et un indicateur personnalisé

MetaTrader 5Exemples | 22 décembre 2021, 16:16
1 373 0
Denis Zyatkevich
Denis Zyatkevich

Introduction

Le langage de programmation MetaQuotes 5 (MQL5), inclus dans le terminal client MetaTrader 5, offre de nombreuses nouvelles possibilités et des performances supérieures par rapport au MQL4. Cet article vous aidera à vous familiariser avec ce nouveau langage de programmation. Les exemples simples de rédaction d'un Expert Advisor et d'un Indicateur personnalisé sont présentés dans cet article. Nous considérerons également quelques détails du langage MQL5, qui sont nécessaires pour comprendre ces exemples.

Les détails de l'article et la description complète de MQL5 peuvent être trouvés dans MQL5 Reference, inclus dans MetaTrader 5. Les informations contenues dans l'aide intégrée de MQL5 sont suffisantes pour étudier le langage. Cet article peut être utile pour ceux qui sont familiers avec MQL4, mais aussi pour les débutants, qui commencent tout juste à programmer des systèmes de trading et des indicateurs.


Premiers pas avec MQL5

La plateforme de trading MetaTrader 5 vous permet d'effectuer des analyses techniques d'instruments financiers et de trader, à la fois manuellement et en mode automatique. MetaTrader 5 diffère de son prédécesseur - MetaTrader 4. En particulier, les concepts de transaction, de position et d'ordre ont été affinés.

  • Position - est un engagement de marché, le nombre de contrats d'instruments financiers achetés ou vendus.
  • Order - est un ordre d'achat ou de vente d'un certain montant d'instruments financiers sous certaines conditions.
  • Deal - est un fait d'exécution d'un ordre par un courtier, qui conduit à l'ouverture, la modification ou la fermeture de la position.

Le terminal client a intégré le langage de programmation MQL5, qui vous permet d'écrire plusieurs types de programmes avec des objectifs différents :

  • Expert Advisor - est un programme qui se trade selon un algorithme spécifié. Un Expert Advisor vous permet de mettre en place le système des trades dans le trading automatisé (les opérations de trading peuvent être effectuées sans trader). Un Expert Advisor peut effectuer des opérations de trades, ouvrir et fermer des positions et gérer les ordres en attente.
  • Indicator - est un programme qui permet de présenter les données sous forme graphique, ce qui est pratique pour l'analyse.
  • Script - est un programme qui permet d'effectuer une séquence d'opérations à la fois.

Les Expert Advisors, les indicateurs et les scripts peuvent appeler des fonctions de la bibliothèque standard MQL5 et des fonctions DLL, y compris les bibliothèques du système d'exploitation. Les portions de code, situées dans les autres fichiers, peuvent être incluses dans le texte du programme, écrites en MQL5.

Pour écrire un programme (Expert Advisor, Indicateur ou Script), vous pouvez lancer le Terminal Client MetaTrader 5 et choisir MetaQuotes Language Editor dans le menu Tools, ou appuyez simplement sur la touche F4.

Figure 1. Lancement de MetaEditor.

Dans la fenêtre MetaEditor 5, choisissez New dans le menu File ou appuyez sur Ctrl+N.

Figure 2. Création d'un nouveau programme.

Dans la fenêtre de l'assistant MQL5, choisissez le type de programme que vous souhaitez créer : 

Figure 3. Assistant MQL5.

À l'étape suivante, vous pouvez spécifier le nom du programme, les informations sur l'auteur et les paramètres qui seront demandés à l'utilisateur après le lancement du programme.

Figure 4. Propriétés générales d'un Expert Advisor.

Après cela, le modèle de programme (Expert Advisor, Indicateur ou Script) sera créé, que vous pouvez modifier et remplir avec votre code :

Figure 5. Modèle d'un nouveau programme.

Lorsque le programme est prêt, il est nécessaire de le compiler. Pour compiler le programme, choisissez Compile dans le menu File ou appuyez sur la touche F7 :

Figure 6. Compilation du programme.

S'il n'y a pas d'erreurs dans le code du programme, le fichier avec l'extension .ex5 sera créé. Après cela, vous pouvez attacher ce nouvel Expert Advisor, Indicateur ou Script au graphique du Terminal Client MetaTrader 5 pour l'exécution.

Un programme MQL5 est une séquence d'opérateurs. Chaque opérateur se termine par un point-virgule « ; ». Pour votre aisance, vous pouvez ajouter des commentaires à votre code, qui sont placés à l'intérieur des symboles "/*" et "*/", ou après "//" à la fin de la ligne. MQL5 est un langage de programmation « orienté événement ». Cela signifie que lorsque certains événements (lancement ou fin de programme, arrivée d'une nouvelle cotation, etc.) se produisent, le Terminal Client lance la fonction correspondante (sous-programme), écrite par l'utilisateur, qui effectue les opérations spécifiées. Le Terminal Client a les événements prédéfinis suivants :

  • L'événement Start se produit lorsque le script est en cours d'exécution (utilisé dans les scripts uniquement). Il conduit à l'exécution de la fonction OnStart. Équivalent MQL4 - fonction start dans les scripts.
  • L'événement Init se produit lorsque l'Expert Advisor ou l'Indicateur est lancé. Il conduit à l'exécution de la fonction OnInit. Équivalent MQL4 - fonction init.
  • L'événement Deinit se produit lorsque l'Expert Advisor ou l'Indicateur est terminé (par exemple, après s'être détaché du graphique, tout en bouclant le Terminal Client, etc.). Il conduit à l'exécution de la fonction OnDeinit. Équivalent MQL4 - fonction deinit.
  • L'événement NewTick se produit lorsqu'une nouvelle cotation pour l'instrument financier actuel est arrivée (utilisé dans les Expert Advisors uniquement). Il conduit à l'exécution de la fonction OnTick. Équivalent MQL4 - fonction start dans Expert Advisors.
  • L'événement Calculate se produit lorsque l'indicateur est lancé (après l'exécution de la fonction OnInit) et lorsqu'une nouvelle cotation pour l'instrument financier actuel est arrivée (utilisé dans les indicateurs uniquement). Il conduit à l'exécution de la fonction OnCalculate. Équivalent MQL4 - fonction start dans les indicateurs.
  • L'événement Trade se produit lorsque l'ordre est exécuté, modifié ou supprimé, lorsque la position est ouverte, modifiée ou fermée (utilisé dans les Expert Advisors uniquement). Il conduit à l'exécution de la fonction OnTrade. Dans MQL4, il n'y a pas d'équivalent de cet événement et de cette fonction.
  • L'événement BookEvent se produit lorsque le Depth of Market est modifié (utilisé dans les Expert Advisors uniquement). Il conduit à l'exécution de la fonction OnBookEvent. Dans MQL4, il n'y a pas d'équivalent de cet événement et de cette fonction, ainsi que Depth of Market.
  • L'événement ChartEvent se produit lorsque l'utilisateur travaille avec un graphique : clique sur la souris et appuie sur les touches, lorsque la fenêtre du graphique est active. Il se produit également lors de la création, du déplacement ou de la suppression des objets graphiques, etc. (utilisés dans les Expert Advisors et les Indicateurs). Il conduit à l'exécution de la fonction OnChartEvent . Il n'y a pas d'équivalent de cet événement et de cette fonction dans MQL4.
  • L'événement Timer se produit périodiquement lorsque la minuterie est déclenchée, s'il a été activé à l'aide de la fonction EventSetTimer. Il conduit à l'exécution de la fonction OnTimer . Dans MQL4, il n'y a pas d'équivalent de cet événement et de cette fonction, ainsi qu'une minuterie.

Avant d'utiliser les variables, il est nécessaire de spécifier le type de données de chacune d'entre elles. MQL5 prend en charge plus de types de données que MQL4 :

  • bool est destiné à stocker des valeurs logiques (true ou false). Il occupe 1 octet de mémoire.
  • char est destiné à stocker des valeurs des nombres entiers de -128 à 127. Il occupe 1 octet de mémoire.
  • uchar est destiné à stocker des valeurs entières non signées de 0 à 255. Il occupe 1 octet de mémoire.
  • short est destiné à stocker des valeurs entières de -32 768 à 32 767. Il occupe 2 octets de mémoire.
  • ushort est destiné à stocker des valeurs entières non signées de 0 à 65 535. Il occupe 2 octets de mémoire.
  • int est destiné à stocker des valeurs entières de -2 147 483 648 à 2 147 483 647. Il occupe 4 octets de mémoire.
  • uint est destiné à stocker des valeurs entières non signées de 0 à 4 294 967 295. Il occupe 4 octets de mémoire.
  • long est destiné à stocker des valeurs entières de -9 223 372 036 854 775 808 à 9 223 372 036 854 775 807. Il occupe 8 octets de mémoire.
  • ulong est destiné à stocker des valeurs entières non signées de 0 à 18 446 744 073 709 551 615. Il occupe 8 octets de mémoire.
  • float est destiné à stocker des valeurs à virgule flottante. Il occupe 4 octets de mémoire.
  • double est destiné à stocker des valeurs à virgule flottante. Il est généralement utilisé pour stocker des données de prix. Il occupe 8 octets de mémoire.
  • datetime est destiné à stocker les valeurs de date et d'heure, c'est un nombre de secondes écoulées depuis le 01.01.1970 00:00:00. Il occupe 8 octets de mémoire.
  • color est destiné à stocker les informations sur la couleur, il contient les caractéristiques de trois composants de couleur - rouge, vert et bleu. Il occupe 4 octets de mémoire.
  • enum signifie énumération. Il permet de spécifier un type d'un certain ensemble limité de données. Il occupe 4 octets de mémoire.
  • string est destiné à stocker des chaînes de texte. Sa représentation interne est une structure de 8 octets, qui contient la taille du tampon avec la chaîne et le pointeur vers ce tampon.

Il est nécessaire de choisir le type de données approprié pour des performances optimales et une utilisation rationnelle de la mémoire. Dans MQL5, il existe un nouveau concept appelé structure. La structure combine les données logiquement liées.

Système de trading

Le système commercial, qui est utilisé dans cet article à titre d'exemple, est basé sur l'hypothèse que les institutions financières européennes sont ouvertes le matin, et plus tard, les événements économiques sont publiés aux États-Unis, ce qui conduit à la tendance de l'EURUSD. La période du graphique n'est pas importante, mais je recommande d'utiliser les barres des minutes, car toute la journée (ou une partie) est visible à la fois, donc c'est très pratique pour l'observation.

Figure 7. Système de trading.

À 7 heures du matin (heure du serveur), les ordres en attente Buy Stop et Sell Stop sont placés à une distance d'un point au-delà de la plage du taux du jour en cours. Pour les ordres Buy Stop en attente, le spread est pris en compte. Les niveaux StopLoss sont placés du côté opposé de la plage. Après exécution, l'ordre StopLoss est déplacé vers la moyenne mobile simple, mais seulement s'il est rentable.

L'avantage de ce type de trailing par rapport au Trailing Stop classique est le suivant : il permet d'éviter une fermeture anticipée de position en cas de pics de cours avec des correctifs. D'autre part, cela conduit à la fermeture de la position lorsque la tendance se termine et que le mouvement à plat commence. La moyenne mobile simple est calculée à l'aide des données du graphique en minutes et a une période de moyenne égale à 240.

Le niveau de profit dépend de la volatilité actuelle du marché. Pour déterminer la volatilité du marché, l'indicateur Average True Range (ATR) (avec une période égale à 5 est appliqué au graphique journalier) est utilisé. Ainsi, il montre la plage quotidienne moyenne de la semaine dernière. Pour déterminer la valeur du niveau de Take Profit pour la position longue, nous ajouterons la valeur de l'indicateur ATR au prix minimal du jour en cours. De même pour les positions courtes : on soustraira la valeur de l'indicateur ATR du taux maximal du jour courant. L’ordre n'est pas passé si la valeur du prix de l’ordre est au-delà des niveaux StopLoss et TakeProfit. Après 19 heures (heure du serveur), tous les ordres en attente sont supprimés et ne sont pas passés ce jour-là (les positions ouvertes sont toujours suivies jusqu'à la clôture).


Suppression d’un indicateur

Écrivons un indicateur qui montre les niveaux de profit du système de trade décrit ci-dessus.

Si le premier symbole d'une ligne est "#", cela signifie que cette chaîne est une directive de préprocesseur. Les directives sont utilisées pour spécifier des propriétés de programme supplémentaires, pour déclarer des constantes, pour inclure des fichiers d'en-tête et des fonctions importées. Notez qu'après les directives du préprocesseur, il n'y a pas de symboles point-virgule (;).

#property copyright   "2010, MetaQuotes Software Corp."
#property link        "http://www.mql5.com"
#property description "This indicator calculates TakeProfit levels"
#property description "using the average market volatility. It uses the values"
#property description "of Average True Range (ATR) indicator, calculated"
#property description "on daily price data. Indicator values are calculated"
#property description "using maximal and minimal price values per day."
#property version     "1.00"

Les informations sur l'auteur et sa page Web peuvent être spécifiées dans les propriétés de copyright et de lien , la propriété description vous permet d'ajouter la brève description, la propriété version vous permet de spécifier la version du programme. Lorsque l'indicateur est en cours d'exécution, ces informations se présentent comme suit :

Figure 8. Informations sur indicateur.

Il est nécessaire de spécifier la position des indicateurs : sur un graphique ou dans une fenêtre séparée. Cela peut être fait en spécifiant l'une des propriétés : indicator_chart_window ou indicator_separate_window :

#property indicator_chart_window

De plus, vous devez spécifier le nombre de tampons d'indicateurs qui seront utilisés et le nombre de séries graphiques. Dans notre cas, il y a deux lignes, chacune d'elles a son propre tampon - un tableau avec les données qui seront tracées.

#property indicator_buffers 2
#property indicator_plots   2

Pour chaque ligne d'indicateur, spécifions les propriétés suivantes : type (indicator_type property), couleur (indicator_color property), style de dessin (indicator_style property) et label de texte (indicator_label property) :

#property indicator_type1   DRAW_LINE
#property indicator_color1  C'127,191,127'
#property indicator_style1  STYLE_SOLID
#property indicator_label1  "Buy TP"
#property indicator_type2   DRAW_LINE
#property indicator_color2  C'191,127,127'
#property indicator_style2  STYLE_SOLID
#property indicator_label2  "Sell TP"

Les types de lignes de base sont : DRAW_LINE - pour les lignes, DRAW_SECTION - pour les sections, DRAW_HISTORAM pour les histogrammes. Il existe de nombreux autres styles de dessin. Vous pouvez définir la couleur en spécifiant la luminosité de ses trois composants RVB ou en utilisant les couleurs prédéfinies, par exemple, Rouge, Vert, Bleu, Blanc, etc. Les styles de lignes sont : STYLE_SOLID - ligne continue, STYLE_DASH - ligne pointillée, STYLE_DOT - ligne pointillée, STYLE_DASHDOT - ligne tiret-point, STYLE_DASHDOTDOT - tiret-deux points.

Figure 9. Description des  lignes d’indicateurs.

Au moyen du modificateur d'entrée, spécifions les variables externes (vous pouvez spécifier leurs valeurs après avoir lancé l'indicateur), leur type et leurs valeurs par défaut :

input int             ATRper       = 5;         //ATR Period
input ENUM_TIMEFRAMES ATRtimeframe = PERIOD_D1; //Indicator timeframe

Les noms des paramètres peuvent être spécifiés dans les commentaires - ils seront visibles à la place des noms des variables :

Figure 10. Paramètres d’entrée de l’indicateur.

Au niveau global (qui est visible pour toutes les fonctions), nous allons spécifier des variables (et leurs types), qui seront utilisées par différentes fonctions de notre indicateur.

double bu[],bd[];
int hATR;

Les tableaux bu[] et bd[] seront utilisés pour les lignes supérieure et inférieure de l'indicateur. Nous utiliserons des tableaux dynamiques (c'est-à-dire les tableaux sans nombre d'éléments spécifié), car nous ne connaissons pas exactement le nombre d'éléments qui seront utilisés (leur taille sera allouée automatiquement). Le descripteur de l'indicateur technique intégré sera stocké dans la variable hATR . Le descripteur de l'indicateur est nécessaire pour utiliser l'indicateur.

La fonction OnInit  est appelée après l'exécution de l'indicateur (après son rattachement au graphique).

void OnInit()
  {
   SetIndexBuffer(0,bu,INDICATOR_DATA);
   SetIndexBuffer(1,bd,INDICATOR_DATA);
   hATR=iATR(NULL,ATRtimeframe,ATRper);
  }

La fonction SetIndexBuffer est nécessaire pour spécifier le fait que les tableaux bu[] and bd[] sont les tampons de l'indicateur, qui seront utilisés pour stocker les valeurs des indicateurs, qui sont tracées sous forme de lignes d'indicateur. Le premier paramètre définit l'indice du tampon de l'indicateur, l'ordre commence à 0. Le deuxième paramètre spécifie un tableau, affecté au tampon de l'indicateur. Le troisième paramètre spécifie le type de données, stockées dans la mémoire tampon de l'indicateur : INDICATOR_DATA - données pour le traçage, INDICATOR_COLOR_INDEX - couleur de dessin, INDICATOR_CALCULS - tampons auxiliaires pour les calculs intermédiaires.

Le descripteur de l'indicateur, renvoyé par la fonction iATR, est stocké dans la variable hATR . Le premier paramètre de la fonction iATR est le symbole commercial, NULL - est le symbole du graphique actuel. Le deuxième paramètre spécifie la période du graphique, qui est utilisée pour le calcul de l'indicateur. Le troisième paramètre est la période de moyenne de l'indicateur ATR .

La fonction OnCalculate est appelée juste après la fin de l'exécution de la fonction OnInit et à chaque fois après l'arrivée de la nouvelle cotation pour le symbole actuel. Il existe deux manières d'appeler cette fonction. L'une d'elles, celle utilisée dans notre indicateur, se présente comme suit :

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])

Après l'appel de la fonction OnCalculate, le terminal client transmet les paramètres suivants : rates_total - nombre de barres sur le graphique courant, prev_calculated  - nombre de barres, déjà calculé par l'indicateur, time[], open[], high[], low[], close[], tick_volume[], volume[], spread[] - tableaux, contenant respectivement les valeurs time, open, high, low, close, tick_volume, volume et spread pour chaque barre. Pour réduire le temps de calcul, il n'est pas nécessaire de recalculer les valeurs des indicateurs, qui ont déjà été calculées et n'ont pas changé. Après avoir appelé la fonction OnCalculate, elle renvoie le nombre de barres déjà calculées.

Le code de la fonction OnCalculate est entre parenthèses. Il commence par les variables locales, qui sont utilisées dans la fonction - leurs types et noms.

  {
   int i,day_n,day_t;
   double atr[],h_day,l_day;

La variable i est utilisée comme compteur de cycle, les variables day_n and day_t sont utilisées pour mémoriser le nombre de jours et pour mémoriser temporairement le nombre de jours, lors du calcul des valeurs de prix maximales et minimales au cours de la journée. Le tableau atr[] est utilisé pour stocker les valeurs de l'indicateur ATR , et les variables h_day and l_day sont utilisées pour stocker les valeurs de prix maximales et minimales au cours de la journée.

Tout d'abord, nous devons copier les valeurs de l'indicateur ATR dans le tableau atr[] , en utilisant la fonction CopyBuffer. Nous utiliserons le descripteur de l'indicateur ATR  comme premier paramètre de cette fonction. Le deuxième paramètre est le nombre de tampons de l'indicateur (la numérotation commence à 0), l'indicateur ATR n'a qu'un seul tampon. Le troisième paramètre spécifie le nombre de premier élément à partir duquel l'indexation est effectuée du présent au passé, le zéroième élément correspond à la barre actuelle (non complétée). Le quatrième paramètre spécifie le nombre d'éléments à copier.

Copions deux éléments, car nous ne nous intéressons qu'à l'avant-dernier élément, qui correspond à la dernière barre (complétée). Le dernier paramètre est le tableau cible pour copier les données.

   CopyBuffer(hATR,0,0,2,atr);

La direction de l'indexation du tableau dépend du drapeau AS_SERIES . S'il est défini (c'est-à-dire égal à true), le tableau est considéré comme une série temporelle, l'indexation des éléments est effectuée des données les plus récentes aux plus anciennes. S'il n'est pas défini (c'est-à-dire égal à false), les éléments les plus anciens ont un indice inférieur, tandis que les éléments les plus récents ont un indice supérieur.

   ArraySetAsSeries(atr,true);

Pour le tableau atr[], nous définissons le drapeau AS_SERIES sur true en utilisant la fonction ArraySetAsSeries (le premier paramètre de cette fonction est le tableau, pour lequel le drapeau doit être modifié, le deuxième paramètre est la nouvelle valeur du drapeau). Maintenant, l'indice de la barre courante (non complétée) est égal à 0, l'indice de l'avant-dernière barre (complétée) est égal à 1.

L'opérateur for permet de créer une boucle.

   for(i=prev_calculated;i<rates_total;i++)
     {
      day_t=time[i]/PeriodSeconds(ATRtimeframe);
      if(day_n<day_t)
        {
         day_n=day_t;
         h_day=high[i];
         l_day=low[i];
        }
        else
        {
         if(high[i]>h_day) h_day=high[i];
         if(low[i]<l_day) l_day=low[i];
        }
      bu[i]=l_day+atr[1];
      bd[i]=h_day-atr[1];
     }

Après l'opérateur for, le premier opérateur entre parenthèses est une instruction : i=prev_calculated. Ensuite, il y a une expression, dans notre cas c'est : i<rates_total. C'est une condition de boucle - la boucle s'exécute tant qu'elle est true. La troisième est l'instruction qui s'exécute après chaque exécution de la boucle. Dans notre cas, c'est i++ (c'est égal à i=i+1 et signifie augmenter la variable i de 1).

Dans notre boucle, la variable i varie de la valeur prev_calculated à la valeur, égale à rates_total-1 à l'étape 1. Les tableaux de données historiques (time[], high[] and low[]) ne sont pas les séries temporelles par défaut, l'indice zéro correspond à la barre la plus ancienne de l'historique, la dernière correspond à la barre inachevée actuelle. En boucle, toutes les barres de la première barre non calculée (prev_calculated) à la dernière (rates_total-1) sont traitées. Pour chacune de ces barres, nous calculons les valeurs de notre indicateur.

Les valeurs de temps dans le tableau time[] sont stockées en nombre de secondes, écoulées depuis le 01.01.1970 00:00:00. Si nous le divisons par le nombre de secondes du jour (ou sur toute autre période), la partie entière du résultat sera le nombre du jour commençant à partir du 01.01.1970 (ou toute autre période). La fonction PeriodSeconds renvoie le nombre de secondes dans la période, qui est défini en tant que paramètre. La variable day_t est le nombre de jour, correspondant à la barre d'indice i . La variable day_n est le numéro du jour pour lequel les valeurs de prix les plus élevées et les plus basses sont calculées.

Considérons l'opérateur if. Si l'expression entre crochets de cet opérateur est true, alors l'opérateur, suivant le mot-clé if, est exécuté. S'il est false, l'opérateur, après le mot-clé else, est exécuté. Chaque opérateur peut être composé, c'est-à-dire composé de plusieurs opérateurs, dans notre cas ils sont mis entre parenthèses.

Les valeurs de prix les plus élevées et les plus basses du jour traité sont stockées dans les variables h_day and l_day , respectivement. Dans notre cas, nous vérifions la condition suivante : si la barre analysée correspond au nouveau jour, nous recommençons à calculer les valeurs de prix maximales et minimales, sinon nous continuons. Nous calculons les valeurs pour chaque ligne d’indicateur : pour la ligne supérieure - nous utilisons le cours du jour minimal, pour la ligne inférieure - nous utilisons les valeurs maximales du prix.

À la fin de la fonction OnCalculate, l'opérateur de return renvoie le nombre de barres calculées.

   return(rates_total);
  }

À l'intérieur de la fonction DeInit (cela fonctionne lorsque l'indicateur est supprimé du graphique ou lorsque le terminal client est clos), la mémoire, réservée par l'indicateur ATR, est libérée à l'aide de la fonction IndicatorRelease. Cette fonction n'a qu'un seul paramètre - le descripteur de l'indicateur.

void OnDeinit(const int reason)
  {
   IndicatorRelease(hATR);
  }

Maintenant, notre indicateur est complet. Pour le compiler, dans MetaEditor, choisissez Compiler dans le menu Fichier ou appuyez sur la touche F7. S'il n'y a pas d'erreurs dans le code, la compilation sera réussie. Les résultats de la compilation sont imprimés dans l'onglet Erreurs de la fenêtre Toolbox. Dans votre cas, le compilateur peut imprimer l'avertissement « Probable perte de conversion de données » pour la chaîne suivante :

      day_t=time[i]/PeriodSeconds(ATRtimeframe);

Dans cette ligne, nous éliminons intentionnellement la partie fractionnaire, donc cette perte de données n'est pas une erreur.

Lorsqu'un indicateur est complet et compilé, il peut être attaché aux graphiques dans le terminal client MetaTrader 5 ou peut être utilisé dans d'autres indicateurs, Expert Advisors ou scripts. Le code source de cet indicateur est disponible en pièce jointe dans cet article.


Rédaction d'un Expert Advisor

Il est maintenant temps d'écrire un Expert Advisor, qui applique le système de trading décrit ci-dessus. Nous supposerons qu'il n'échangera qu'un seul instrument financier. Pour que plusieurs Expert Advisors négocient sur un seul instrument, il est nécessaire d'analyser soigneusement la contribution de chacun d'entre eux dans toute leur globalité, et cela dépasse le cadre de cet article.

#property copyright   "2010, MetaQuotes Software Corp."
#property version     "1.00"
#property description "This Expert Advisor places the pending orders during the"
#property description "time from StartHour till EndHour on the price levels, that"
#property description "are 1 point below/lower the current trade range."
#property description "The StopLoss levels are placed at the opposite side"
#property description "of the price range. After order execution, the TakeProfit value"
#property description "is set at the 'indicator_TP' level. The StopLoss level is moved"
#property description "to the SMA values only for the profitable orders."

Le but de ces directives de préprocesseur a déjà été examiné dans la section Rédaction d’indicateur. Ils fonctionnent de la même manière pour un Expert Advisor.

Précisons les valeurs des paramètres d'entrée (qui peuvent être définis par l'utilisateur après le démarrage d'un Expert Advisor), leurs types et leurs valeurs par défaut.

input int    StartHour = 7;
input int    EndHour   = 19;
input int    MAper     = 240;
input double Lots      = 0.1;

Les paramètres StartHour et EndHour définissent la période (heures de début et de fin) des ordres en attente. Le paramètre MAper définit la période de calcul de la moyenne pour la moyenne mobile simple, qui est utilisée pour le niveau StopLoss de la position ouverte au cours de son trailing. Le paramètre Lots définit le volume d'instrument financier, utilisé dans le trading.

Précisons les variables globales, qui seront utilisées dans les différentes fonctions de trading :

int hMA,hCI;

La variable hMA sera utilisée pour stocker le descripteur de l'indicateur MA et la variable hCI sera utilisée pour stocker le descripteur de l'indicateur personnalisé (c'est un indicateur, qui a été écrit ci-dessus).

La fonction OnInit est exécutée, lors du lancement d'Expert Advisor.

void OnInit()
  {
   hMA=iMA(NULL,0,MAper,0,MODE_SMA,PRICE_CLOSE);
   hCI=iCustom(NULL,0,"indicator_TP");
  }

Dans cette fonction, nous obtenons les descripteurs de l'indicateur MA et de notre indicateur personnalisé. La fonction iMA et ses paramètres sont utilisés de la même manière que dans la fonction iATR, décrite ci-dessus.

Le premier paramètre de la fonction iCustom est le nom symbolique de l'instrument, NULL - signifie instrument pour le graphique courant. Le deuxième paramètre - est le délai de graphique, dont les données sont utilisées pour calculer l'indicateur, 0 - signifie la période pour le graphique courant. Le troisième paramètre est le nom de fichier de l'indicateur (sans extension). Le chemin du fichier est relatif au dossier MQL5\Indicators\.

Créons la fonction OnTick, qui s'exécute après chaque nouvelle nouvelle cotation :

void OnTick()
  {

Le code de la fonction est mis entre parenthèses.

Précisons les data structures prédéfinies, qui seront utilisées dans Expert Advisor :

   MqlTradeRequest request;
   MqlTradeResult result;
   MqlDateTime dt;
La structure MqlTradeRequest prédéfinie a des paramètres d'ordres et de positions, qui sont transmis à la fonction OrderSend dans les opérations de trades. Le but de la structure MqlTradeResult est de stocker les informations sur les résultats de l'opération de trade, renvoyées par la fonction OrderSend. Le but de la structure MqlDateTime prédéfinie est de stocker les informations de date et d'heure.

Précisons les variables locales (et leurs types) qui seront utilisées dans la fonction OnTick :

   bool bord=false, sord=false;
   int i;
   ulong ticket;
   datetime t[];
   double h[], l[], ma[], atr_h[], atr_l[],
          lev_h, lev_l, StopLoss,
          StopLevel=_Point*SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL),
          Spread   =NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_ASK) - SymbolInfoDouble(Symbol(),SYMBOL_BID),_Digits);
Les variables booléennes bord et sord sont utilisées comme des indicateurs, signalant la présence d'ordres Buy Stop et Sell Stop en attente. Si elle est présente, la variable correspondante a une valeur, égale à true, sinon elle est false. La variable i est utilisée comme compteur dans les opérateurs à boucle et pour stocker des données intermédiaires. Le ticket des ordres en attente est stocké dans la variable ticket .

Les tableaux t[], h[] et l[] sont utilisés pour stocker le temps, les valeurs de prix maximales et minimales pour chaque barre dans les données d'historique. Le tableau ma[] est utilisé pour stocker les valeurs de l'indicateur MA, les tableaux atr_h[] et atr_l[] sont utilisés pour stocker les valeurs des lignes supérieure et inférieure de l'indicateur personnalisé indicator_TP, que nous avons créé.

Les variables lev_h et lev_l sont utilisées pour stocker les valeurs du taux du jour maximales et minimales et les prix d'ouverture des ordres en attente. La variable StopLoss est utilisée pour stocker temporairement le prix Stop Loss de la position ouverte.

La variable StopLevel est utilisée pour stocker la valeur de STOP_LEVEL - la distance minimale entre le prix actuel et le prix de l’ordre en attente (en prix unitaires). Nous calculons cette valeur comme un produit du prix en points (la variable prédéfinie _Point) par la valeur de la variable STOP_LEVEL, définie en points. La valeur de STOP_LEVEL est renvoyée par la fonction SymbolInfoInterger. Le premier paramètre de cette fonction est le nom du symbole, le second - est l'identifiant de la propriété demandée. Le symbole (nom de l'instrument financier) peut être obtenu à l'aide de la fonction Symbole (il n'a pas de paramètres).

La valeur Spread est utilisée pour stocker la valeur du spread (en prix unitaires). Sa valeur est calculée comme une différence entre les valeurs Ask et Bid actuelles, normalisées à l'aide de la fonction NormalizeDouble. Le premier paramètre de cette fonction est la valeur de type double à normaliser, le second est le nombre de chiffres après le point, que nous avons obtenu à partir de la variable _Digits prédéfinie. Les valeurs Ask et Bid actuelles peuvent être obtenues à l'aide de la fonction SymbolInfoDouble. Le premier paramètre de cette fonction est le nom du symbole, le second - est l'identifiant de la propriété.

Remplissons la requête de structure avec des valeurs, qui seront communes à la plupart des appels de fonction OrderSend :

   request.symbol      =Symbol();
   request.volume      =Lots;
   request.tp          =0;
   request.deviation   =0;
   request.type_filling=ORDER_FILLING_FOK;
L'élément request.symbol contient le nom symbolique de l'instrument qui se trade, l'élément request.volume - le volume (taille du contrat) de l'instrument financier, request.tp - la valeur numérique de TakeProfit (dans certains cas, nous ne l'utiliserons pas et remplissez avec 0), le request.deviation - écart autorisé du prix pendant l'exécution de l'opération de trade, le request.type_filling - le type d'ordre, peut être l'un des suivants :
  • ORDER_FILLING_FOK - la transaction ne peut être exécutée que si le volume est égal ou supérieur à celui spécifié dans l'ordre. S'il n'y a pas de volume suffisant, l'ordre ne sera pas exécuté.
  • ORDER_FILLING_IOC - il n'y a pas de volume suffisant, l’ordre sera exécuté au volume de marché maximal disponible. 
  • ORDER_FILLING_RETURN - le même que le ORDER_FILING_IOC, mais dans ce cas un ordre supplémentaire pour volume manquant sera placé.

Obtenons l'heure actuelle du serveur (heure de la dernière cotation) à l'aide de la fonction TimeCurrent. Le seul paramètre de cette fonction est le pointeur vers la structure avec résultat.

   TimeCurrent(dt);

Pour tous nos calculs, nous avons besoin des données de prix historiques pour le jour en cours uniquement. Le nombre de barres, qui est nécessaire (et avec une certaine réserve) peut être calculé en utilisant cette formule : i = (dt.hour + 1)*60, où dt.hour - est l'élément de structure, contenant l'heure actuelle. Les valeurs de temps, prix maximal et minimal sont copiées dans les tableaux t[], h[] et l[] à l'aide des fonctions CopyTime, CopyHigh, CopyLow, respectivement :

   i=(dt.hour+1)*60;
   if(CopyTime(Symbol(),0,0,i,t)<i || CopyHigh(Symbol(),0,0,i,h)<i || CopyLow(Symbol(),0,0,i,l)<i)
     {
      Print("Can't copy timeseries!");
      return;
     }

Le premier paramètre dans les fonctions CopyTime, CopyHigh et CopyLow - est le nom du symbole, le second - est le délai de graphique, le troisième - est l'élément de départ à copier, le quatrième - est le nombre d'éléments à copier, le cinquième - est le tableau cible pour les données. Chacune de ces fonctions renvoie le nombre d'éléments copiés ou la valeur négative égale à -1 en cas d'erreur.

L'opérateur if est utilisé pour vérifier le nombre d'éléments copiés pour les trois tableaux. Si le nombre d'éléments copiés est plus petit que nécessaire pour le calcul (même pour l'un des tableaux), ou en cas d'erreur, il affiche le message « Impossible de copier la série temporelle ! » dans le journal des experts et termine l'exécution de la fonction OnTick à l'aide de l'opérateur de return. Le message est imprimé par la fonction Imprimer. Il peut imprimer n'importe quel type de données, qui sont séparées par des virgules.

Lorsque les données de prix ont été copiées dans les tableaux t[], h[] et l[], nous configurons l'indicateur AS_SERIES sur true en utilisant la fonction ArraySetAsSeries, qui a été considérée ci-dessus. Il est nécessaire de configurer l'indexation du tableau sous forme de séries temporelles (des prix actuels aux prix plus anciens) :

   ArraySetAsSeries(t,true);
   ArraySetAsSeries(h,true);
   ArraySetAsSeries(l,true);

Les valeurs du taux du jour maximales et minimales sont placées dans les variables lev_h et lev_l  :

   lev_h=h[0];
   lev_l=l[0];
   for(i=1;i<ArraySize(t) && MathFloor(t[i]/86400)==MathFloor(t[0]/86400);i++)
     {
      if(h[i]>lev_h) lev_h=h[i];
      if(l[i]<lev_l) lev_l=l[i];
     }

La boucle n'est exécutée que si la condition MathFloor(t[i]/86400) ==  MathFloor(t[0]/86400) est true afin de restreindre la recherche aux barres appartenant au jour en cours. Du côté gauche de cette expression, il y a un nombre de jours de barre en cours, à droite - le nombre du jour en cours (86400 est le nombre de secondes du jour). La fonction MathFloor arrondit la valeur numérique, c'est-à-dire qu'elle n'utilise qu'une partie entière pour les valeurs positives. Le seul paramètre de cette fonction est l'expression à arrondir. En MQL5, comme en MQL4, l'égalité est définie par des symboles "==" (voir Opérations de relations).

Les prix d’ordres sont calculés comme suit : pour l'ordre en attente de type Buy Stop nous ajoutons un point (la variable prédéfinie _Point est égale à la taille du point en prix unitaires) et Spread à la lev_h  variable (lev_h+=Spread+_Point ou lev_h=lev_h+Spread+_Point). Pour les ordres en attente de type Sell Stop, nous soustrayons un point de la valeur de la variable lev_l variable value (lev_l-=_Point ou lev_l=lev_l-_Point).

   lev_h+=Spread+_Point;
   lev_l-=_Point;

Ensuite, nous copions les valeurs des tampons de l'indicateur dans des tableaux à l'aide de la fonction CopyBuffer. Les valeurs MA sont copiées dans le tableau ma[] , les valeurs de la ligne supérieure de notre indicateur personnalisé sont copiées dans le tableau atr_h[] , les valeurs de la ligne inférieure de l'indicateur sont copiées dans le tableau atr_l[] . La fonction CopyBuffer a été décrite ci-dessus, lorsque nous avons examiné les détails de l'indicateur.

   if(CopyBuffer(hMA,0,0,2,ma)<2 || CopyBuffer(hCI,0,0,1,atr_h)<1 || CopyBuffer(hCI,1,0,1,atr_l)<1)
     {
      Print("Can't copy indicator buffer!");
      return;
     }

Nous avons besoin de la valeur de l'indicateur MA, qui correspond à l'avant-dernière barre (dernière terminée), et de la valeur de notre indicateur, qui correspond à la dernière barre, donc nous copions ces deux éléments dans le tableau ma[] et un élément dans le atr_h[] et atr_l[] . En cas d'erreur lors de la copie ou si le nombre de valeurs copiées est inférieur au besoin (pour l'un de ces tableaux), le message est imprimé dans le journal des Experts et la fonction OnTick est terminée à l'aide de l'opérateur return.

Pour le tableau ma[], nous configurons le drapeau AS_SERIES, qui indique l'indexation de la série temporelle du tableau.

   ArraySetAsSeries(ma,true);

Les tableaux atr_[] et atr_l[] n'ont qu'un seul élément, donc l'indexation des séries temporelles n'est pas importante. Comme la valeur atr_l[0] sera davantage utilisée pour déterminer le niveau de TakeProfit, les positions short sont fermées au prix Ask, mais nous ajoutons le spread à la valeur de atr_l[0], car les prix Bid sont utilisés dans les graphiques de prix .

   atr_l[0]+=Spread;

La fonction PositionsTotal renvoie le nombre de positions ouvertes (elle n'a pas de paramètres). Les indices des positions commencent à 0. Créons une boucle qui recherchera toutes les positions ouvertes :

// in this loop we're checking all opened positions
   for(i=0;i<PositionsTotal();i++)
     {
      // processing orders with "our" symbols only
      if(Symbol()==PositionGetSymbol(i))
        {
         // we will change the values of StopLoss and TakeProfit
         request.action=TRADE_ACTION_SLTP;
         // long positions processing
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
           {
            // let's determine StopLoss
            if(ma[1]>PositionGetDouble(POSITION_PRICE_OPEN)) StopLoss=ma[1]; else StopLoss=lev_l;
            // if StopLoss is not defined or lower than needed            
            if((PositionGetDouble(POSITION_SL)==0 || NormalizeDouble(StopLoss-PositionGetDouble(POSITION_SL),_Digits)>0
               // if TakeProfit is not defined or higer than needed
               || PositionGetDouble(POSITION_TP)==0 || NormalizeDouble(PositionGetDouble(POSITION_TP)-atr_h[0],_Digits)>0)
               // is new StopLoss close to the current price?
               && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-StopLoss-StopLevel,_Digits)>0
               // is new TakeProfit close to the current price?
               && NormalizeDouble(atr_h[0]-SymbolInfoDouble(Symbol(),SYMBOL_BID)-StopLevel,_Digits)>0)
              {
               // putting new value of StopLoss to the structure
               request.sl=NormalizeDouble(StopLoss,_Digits);
               // putting new value of TakeProfit to the structure
               request.tp=NormalizeDouble(atr_h[0],_Digits);
               // sending request to trade server
               OrderSend(request,result);
              }
           }
         // short positions processing
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            // let's determine the value of StopLoss
            if(ma[1]+Spread<PositionGetDouble(POSITION_PRICE_OPEN)) StopLoss=ma[1]+Spread; else StopLoss=lev_h;
            // if StopLoss is not defined or higher than needed
            if((PositionGetDouble(POSITION_SL)==0 || NormalizeDouble(PositionGetDouble(POSITION_SL)-StopLoss,_Digits)>0
               // if TakeProfit is not defined or lower than needed
               || PositionGetDouble(POSITION_TP)==0 || NormalizeDouble(atr_l[0]-PositionGetDouble(POSITION_TP),_Digits)>0)
               // is new StopLoss close to the current price?
               && NormalizeDouble(StopLoss-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>0
               // is new TakeProfit close to the current price?
               && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_ASK)-atr_l[0]-StopLevel,_Digits)>0)
              {
               // putting new value of StopLoss to the structure
               request.sl=NormalizeDouble(StopLoss,_Digits);
               // putting new value of TakeProfit to the structure
               request.tp=NormalizeDouble(atr_l[0],_Digits);
               // sending request to trade server
               OrderSend(request,result);
              }
           }
         // if there is an opened position, return from here...
         return;
        }
     }
En utilisant l'opérateur if à l'intérieur de la boucle, nous choisissons les positions qui ont été ouvertes pour le symbole du graphique actuel. La fonction PositionGetSymbol renvoie le symbole de l'instrument, en plus de cela, elle sélectionne automatiquement la position avec laquelle travailler. La fonction n'a qu'un seul paramètre - l'indice de position dans la liste des positions ouvertes. Il est fort possible qu'il soit nécessaire de modifier les valeurs StopLoss et TakeProfit des positions ouvertes, mettons donc la valeur TRADE_ACTION_SLTP dans l'élément request.action. Ensuite, selon leur direction, les positions sont divisées en positions long et short.

Pour les positions long, le niveau StopLoss est déterminé comme suit : Si la valeur de l'indicateur MA est supérieure au prix d'ouverture de la position, la valeur StopLoss est supposée être égale à la valeur de l'indicateur MA, sinon la valeur StopLoss est supposée être égale à la valeur de la variable lev_l . La valeur actuelle de StopLoss pour la position ouverte est déterminée à l'aide de la fonction PositionGetDouble, qui n'a qu'un seul paramètre - l'identifiant de la propriété de position. Si la valeur StopLoss n'est pas définie pour la position ouverte (égale à 0), ou si elle est supérieure à ce qu'elle devrait être, nous modifierons les valeurs de StopLoss et TakeProfit pour cette position. Si la valeur TakeProfit n'est pas définie (égale à 0), ou si elle est supérieure à ce qu'elle devrait être (supérieure à la ligne supérieure de notre indicateur) - nous modifierons les valeurs de StopLoss et TakeProfit pour cette position.

Nous devrons vérifier la possibilité de changer les valeurs StopLoss et TakeProfit. La nouvelle valeur de StopLoss doit être inférieure au prix Bid actuel (au moins de la valeur STOP_LEVEL), la nouvelle valeur de TakeProfit doit être supérieure au prix Bid actuel au moins de la valeur STOP_LEVEL. Nous avons utilisé la différence normalisée pour la comparaison, car les valeurs comparées peuvent différer dans les derniers chiffres en raison de l'imprécision causée par la conversion de nombres binaires à virgule flottante de type double en nombres décimaux à virgule flottante.

S'il est nécessaire de modifier les niveaux StopLoss ou TakeProfit pour la position ouverte et que les nouvelles valeurs sont valides selon les règles de trading, nous mettons les nouvelles valeurs de StopLoss et TakeProfit dans les éléments correspondants de la structure et appelons la fonction OrderSend pour envoyer des données au serveur de trading.

Le changement des valeurs StopLoss et TakeProfit pour les positions short est le même. Les positions short, comparées aux positions long, sont clôturées par les prix Ask, les valeurs Ask seront donc utilisées à des fins de comparaison. S'il y a une position ouverte pour le graphique actuel - nous terminons l'exécution de la fonction OnTick en utilisant l'opérateur de retour.

La fonction OrdersTotal (elle n'a pas de paramètres) renvoie le nombre des ordres en attente. Les indices commencent à 0. Créons une boucle qui sera utilisée pour traiter toutes les ordres en attente :

// in this loop we're checking all pending orders
   for(i=0;i<OrdersTotal();i++)
     {
      // choosing each order and getting its ticket
      ticket=OrderGetTicket(i);
      // processing orders with "our" symbols only
      if(OrderGetString(ORDER_SYMBOL)==Symbol())
        {
         // processing Buy Stop orders
         if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP)
           {
            // check if there is trading time and price movement is possible
            if(dt.hour>=StartHour && dt.hour<EndHour && lev_h<atr_h[0])
              {
               // if the opening price is lower than needed
               if((NormalizeDouble(lev_h-OrderGetDouble(ORDER_PRICE_OPEN),_Digits)>0
                  // if StopLoss is not defined or higher than needed
                  || OrderGetDouble(ORDER_SL)==0 || NormalizeDouble(OrderGetDouble(ORDER_SL)-lev_l,_Digits)!=0)
                  // is opening price close to the current price?
                  && NormalizeDouble(lev_h-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>0)
                 {
                  // pending order parameters will be changed
                  request.action=TRADE_ACTION_MODIFY;
                  // putting the ticket number to the structure
                  request.order=ticket;
                  // putting the new value of opening price to the structure
                  request.price=NormalizeDouble(lev_h,_Digits);
                  // putting new value of StopLoss to the structure
                  request.sl=NormalizeDouble(lev_l,_Digits);
                  // sending request to trade server
                  OrderSend(request,result);
                  // exiting from the OnTick() function
                  return;
                 }
              }
            // if there is no trading time or the average trade range has been passed
            else
              {
               // we will delete this pending order
               request.action=TRADE_ACTION_REMOVE;
               // putting the ticket number to the structure
               request.order=ticket;
               // sending request to trade server
               OrderSend(request,result);
               // exiting from the OnTick() function
               return;
              }
            // setting the flag, that indicates the presence of Buy Stop order
            bord=true;
           }
         // processing Sell Stop orders
         if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP)
           {
            // check if there is trading time and price movement is possible
            if(dt.hour>=StartHour && dt.hour<EndHour && lev_l>atr_l[0])
              {
               // if the opening price is higher than needed
               if((NormalizeDouble(OrderGetDouble(ORDER_PRICE_OPEN)-lev_l,_Digits)>0
                  // if StopLoss is not defined or lower than need
                  || OrderGetDouble(ORDER_SL)==0 || NormalizeDouble(lev_h-OrderGetDouble(ORDER_SL),_Digits)>0)
                  // is opening price close to the current price?
                  && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-lev_l-StopLevel,_Digits)>0)
                 {
                  // pending order parameters will be changed
                  request.action=TRADE_ACTION_MODIFY;
                  // putting ticket of modified order to the structure
                  request.order=ticket;
                  // putting new value of the opening price to the structure
                  request.price=NormalizeDouble(lev_l,_Digits);
                  // putting new value of StopLoss to the structure
                  request.sl=NormalizeDouble(lev_h,_Digits);
                  // sending request to trade server
                  OrderSend(request,result);
                  // exiting from the OnTick() function
                  return;
                 }
              }
            // if there is no trading time or the average trade range has been passedе
            else
              {
               // we will delete this pending order
               request.action=TRADE_ACTION_REMOVE;
               // putting the ticket number to the structure
               request.order=ticket;
               // sending request to trade server
               OrderSend(request,result);
               // exiting from the OnTick() function
               return;
              }
            // setting the flag, that indicates the presence of Sell Stop order
            sord=true;
           }
        }
     }
En utilisant la fonction OrderGetTicket, nous sélectionnons l’ordre pour un travail ultérieur, et nous enregistrons le ticket de l’ordre dans la variable ticket. Cette fonction n'a qu'un seul paramètre - indice d’ordres dans la liste des ordres ouverts. La fonction OrderGetString est utilisée pour obtenir le nom du symbole. Elle n'a qu'un seul paramètre - l'identifiant de la propriété d’ordres. Nous comparons le nom du symbole avec le nom du graphique actuel, il permet de sélectionner les ordres directement par instrument, sur lequel fonctionne l’Expert Advisor. Le type d’ordres est déterminé par la fonction OrderGetInteger avec l'identificateur de type d’ordres correspondant. Nous traiterons séparément les ordres Buy Stop et Sell Stop.

Si l'heure actuelle est comprise entre StartHour et EndHour et que le prix d'ouverture de l'ordre Buy Stop ne dépasse pas la ligne supérieure de l'indicateur, nous modifierons le prix d'ouverture et la valeur du niveau StopLoss (si nécessaire), sinon nous supprimons l’ordre.

Ensuite, nous déterminons s'il est nécessaire de modifier le prix d'ouverture ou le niveau StopLoss pour l'ordre en attente. Si le prix d'ouverture de l'ordre Buy Stop est inférieur à ce qu'il devrait être ou si StopLoss n'est pas défini ou supérieur, nous mettons la valeur TRADE_ACTION_MODIFY dans l'élément request.action - cela signifie que les paramètres de l'ordre en attente doivent être modifiés. Nous plaçons également le ticket d’ordres dans l'élément request.ticket et envoyons la demande de trade au serveur de trading à l'aide de la fonction OrderSend. En comparaison, nous déterminons le cas où le prix d'ouverture est inférieur à ce qu'il devrait être, car la valeur spread peut être modifiée, mais nous ne modifions pas l'ordre après chaque changement de spread, il sera fixé au niveau le plus élevé, qui correspond à la valeur spread maximale.

De plus, en comparaison, nous déterminons uniquement le cas où la valeur de StopLoss est plus élevée qu'elle ne devrait l'être, car la plage de prix peut s'étendre au cours de la journée et il est nécessaire de baisser la valeur de l'ordre StopLoss après les nouvelles baisses de prix. Après l'envoi de la demande au serveur de trading, l'exécution de la fonction OnTick est terminée à l'aide de l'opérateur de return. Si des ordres Buy Stop sont présents, la valeur de la variable bord est définie sur true.

Les ordres Sell Stop sont traités de la même manière que les ordres Buy Stop.

Plaçons maintenant les ordres Buy Stop et Sell Stop en attente en cas d'absence. Nous mettons la valeur TRADE_ACTION_PENDING dans l'élément request.action (cela signifie que l’ordre en attente est passé).

   request.action=TRADE_ACTION_PENDING;

Si la valeur de l'heure actuelle est comprise dans le temps de passation de l’ordre, nous passons les ordres :

   if(dt.hour>=StartHour && dt.hour<EndHour)
     {
      if(bord==false && lev_h<atr_h[0])
        {
         request.price=NormalizeDouble(lev_h,_Digits);
         request.sl=NormalizeDouble(lev_l,_Digits);
         request.type=ORDER_TYPE_BUY_STOP;
         OrderSend(request,result);
        }
      if(sord==false && lev_l>atr_l[0])
        {
         request.price=NormalizeDouble(lev_l,_Digits);
         request.sl=NormalizeDouble(lev_h,_Digits);
         request.type=ORDER_TYPE_SELL_STOP;
         OrderSend(request,result);
        }
     }
  }
Lorsque nous passons des ordres Buy Stop et Sell Stop, nous vérifions la présence des mêmes ordres en analysant les valeurs des variables bord et sord. Nous vérifions également la condition suivante : le prix de l’ordre devrait être compris dans les valeurs de notre indicateur. Le prix normalisé de l’ordre est placé dans l'élément request.price , la valeur normalisée de StopLoss est placée dans la variable request.sl , le type d’ordre (ORDER_BUY_STOP ou ORDER_SELL_STOP) est placé dans la variable request.type . Après cela, nous envoyons une demande au serveur de trading. Le code de la fonction OnTick se termine par un point-virgule.

Les ressources, allouées par des indicateurs, sont libérées à l'intérieur de la fonction OnDeinit à l'aide de la fonction IndicatorRelease, qui a été considérée ci-dessus.

void OnDeinit(const int reason)
  {
   IndicatorRelease(hCI);
   IndicatorRelease(hMA);
  }

L’Expert Advisor est terminé, la compilation devrait être réussie s'il n'y a pas d'erreurs. Nous pouvons maintenant l'exécuter en l'attachant au graphique. Le code source peut être téléchargé en pièces jointes à cet article.


Lancement et débogage

Maintenant que l’Expert Advisor et l’Indicateur sont prêts, voyons comment les lancer et comment les déboguer à l'aide du débogueur MetaEditor intégré.

Pour lancer un Expert Advisor, il faut le trouver dans le groupe Expert Advisors de la fenêtre du Navigateur. Après cela, choisissez Attacher au graphique dans le menu contextuel, qui apparaîtra lorsque vous cliquerez sur le bouton droit de la souris :

Figure 11. Lancement de l'Expert Advisor.

La fenêtre avec les paramètres d'entrée de l'Expert Advisor apparaîtra, vous pouvez modifier ces paramètres si nécessaire. Après avoir appuyé sur OK, l'icône apparaîtra dans le coin supérieur droit du graphique, cette icône indique que l'Expert Advisor fonctionne. Pour afficher ou fermer la fenêtre du Navigator, vous pouvez choisir Navigateur dans le menu Affichage ou appuyer sur Ctrl+N. La deuxième façon de lancer l'Expert Advisor est de le sélectionner dans le sous-menu Experts du menu Insertion

Pour qu'Expert Advisor puisse trader, l'AutoTrading doit être autorisé dans les options du Terminal Client : Menu Outils -> Fenêtre Options -> onglet Exprert Advisors -> L'option Autoriser le trading automatique doit être activée. Pour que l'Expert Advisor puisse appeler des fonctions à partir de DLL, l'option Autoriser les DLL importss doit également être activée.

Figure 12. Options du terminal - permettant l’AutoTrading.

De plus, vous pouvez définir l'autorisation ou l'interdiction de trading et d'importation des bibliothèques DLL externes pour chaque Expert Advisor individuellement en cochant les options correspondantes.

Malgré le fait que notre Expert Advisor utilise des indicateurs, les lignes des indicateurs ne sont pas tracées sur le graphique. Si nécessaire, vous pouvez attacher les indicateurs manuellement.

Le lancement des indicateurs s’effectue de la même manière que pour les Expert Advisors : si vous souhaitez lancer des indicateurs intégrés, développez l'arborescence des Indicateurs dans la fenêtre du Navigateur (pour les indicateurs personnalisés, il est nécessaire de développer l'arborescence des indicateurs personnalisés), cliquez avec le bouton droit pour que le menu contextuel s’affiche, puis choisissez Attacher au graphique dans le menu contextuel. La deuxième façon consiste à choisir Indicateurs dans le menu Insertion, choisissez le groupe (ou choisissez Custom pour les indicateurs personnalisés) et l'indicateur lui-même.

Les scripts sont lancés de la même manière que les Expert Advisors et les Indicateurs.

Les informations sur les événements du Terminal Client (connexion/déconnexion vers/du serveur de trading, mise à jour automatique, changements de positions et d'ordres, Expert Advisors et Scripts en cours d'exécution, messages d'erreur) peuvent être trouvées dans l'onglet Journal de la fenêtre Toolbox. Les messages imprimés par les Expert Advisors, les Indicateurs et les Scripts se trouvent dans l'onglet Experts.

Le MetaEditor a un débogueur intégré. Il vous permet de déboguer des programmes - exécution étape par étape d'Expert Advisors, d'Indicateurs et de Scripts. Le débogage permet de trouver des erreurs dans le code du programme et d'observer les processus lors de l'exécution de l'Expert Advisor, de l'Indicateur ou du Script. Pour exécuter un programme en mode débogage, il est nécessaire de choisir Démarrer dans le menu Déboguer ou d'appuyer sur la touche F5. Le programme sera compilé et s'exécutera en mode débogage dans le graphique séparé, sa période et son symbole peuvent être spécifiés dans l'onglet Débogage de la fenêtre Options de MetaEditor.

Figure 13. Options Editeur - Débogage.

Vous pouvez définir les points d'arrêt en appuyant sur la touche F9, ou en double-cliquant sur le côté gauche de la ligne, ou en choisissant la fenêtre Basculer le Point d’arrêt dans Débogage. En mode debug, l'exécution du programme s'arrêtera avant l'opérateur avec un point d'arrêt. Après l'arrêt, l'onglet Debug s’affichera dans la fenêtre Toolbox (Figure 14.). Sur le côté gauche, il y a un panneau de pile d'appels - le fichier, la fonction et le numéro d'une ligne y sont affichés. Du côté droit se trouve le panneau de contrôle - les valeurs des variables contrôlées y sont affichées. Pour ajouter la variable à la liste de surveillance, cliquez avec le bouton droit sur le panneau et sélectionnez Ajouter ou appuyez sur la touche Insertion.

Figure 14. Débogage du programme.

L'exécution du programme étape par étape peut être effectuée en appuyant sur les touches F11, F10 ou Shift+F11 . Après avoir appuyé sur la touche F11ou choisi Step Into dans le menu Debug, il progressera d’une étape de l'exécution du programme en entrant toutes les fonctions appelées. Après avoir appuyé sur la touche F10 ou choisi Step Over dans le menu Debug, il progressera d’une étape de l'exécution du programme sans entrer dans les fonctions appelées. Après avoir appuyé sur les touches Shift+F11 ou choisi Step Out dans le menu Debug, il entreprendra l'exécution d'un programme à un niveau supérieur. La flèche verte sur le côté gauche du code marque la ligne du code, qui sera exécutée.


Conclusion

L’article a décrit et présenté un exemple d'écriture d'un Expert Advisor et Indicateur en mode simplifié ainsi que les bases du langage de programmation MQL5. Le système de trading, fourni ici est choisi à titre d'exemple, donc l'auteur n'est pas responsable de son utilisation dans un trade réel.


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

Fichiers joints |
indicator_tp.mq5 (2.17 KB)
expert.mq5 (11.3 KB)
Utilisation des pointeurs d'objet dans MQL5 Utilisation des pointeurs d'objet dans MQL5
Par défaut, tous les objets de MQL5 sont transmis par référence, mais il est possible d'utiliser les pointeurs d'objet. Cependant, il est nécessaire d'effectuer la vérification du pointeur, car l'objet peut ne pas être initialisé. Dans ce cas, le programme MQL5 sera terminé avec une erreur critique et déchargé. Les objets, créés automatiquement, ne provoquent pas une telle erreur, donc dans ce sens, ils sont tout à fait sûrs. Dans cet article, nous allons essayer de comprendre la différence entre la référence d'objet et le pointeur d'objet, et d'examiner comment écrire du code sécurisé qui utilise les pointeurs.
MQL5 pour les débutants : Guide d'utilisation des indicateurs techniques dans Expert Advisors MQL5 pour les débutants : Guide d'utilisation des indicateurs techniques dans Expert Advisors
Afin d’obtenir les valeurs d’un indicateur intégré ou personnalisé dans un Expert Advisor, son descripteur doit d’abord être créé à l’aide de la fonction correspondante. Les exemples de l’article montrent comment utiliser tel ou tel indicateur technique lors de la création de vos propres programmes. L’article décrit les indicateurs générés dans le langage MQL5. Il est destiné à ceux qui n’ont pas beaucoup d’expérience dans le développement de stratégies de trading et offre des moyens simples et clairs de travailler avec des indicateurs en utilisant la bibliothèque de fonctions offerte.
Indicateurs personnalisés dans MQL5 pour débutants Indicateurs personnalisés dans MQL5 pour débutants
Tout nouveau sujet semble compliqué et difficile à apprendre pour un débutant. Les sujets que nous connaissons nous semblent très simples et clairs. Mais nous oublions simplement que nous avons tous dû étudier quelque chose à partir de zéro, et même notre langue maternelle. Il en va de même avec le langage de programmation MQL5 qui offre de larges possibilités de développer ses propres stratégies de trading - vous pouvez commencer à l'apprendre à partir de notions de base et d'exemples les plus simples. L'interaction d'un indicateur technique avec le terminal client MetaTrader 5 est envisagée dans cet article sur l'exemple de l'indicateur personnalisé simple SMA.
L'ordre de création et de destruction d'objets dans MQL5 L'ordre de création et de destruction d'objets dans MQL5
Chaque objet, qu'il s'agisse d'un objet personnalisé, d'un tableau dynamique ou d'un tableau d'objets, est créé et supprimé dans le programme MQL5 à sa manière. Souvent, certains objets font partie d'autres objets et l'ordre de suppression des objets lors de la désinitialisation devient particulièrement important. Cet article fournit quelques exemples qui couvrent les mécanismes de travail avec des objets.