English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Guide d'écriture d'une DLL pour MQL5 en Delphi

Guide d'écriture d'une DLL pour MQL5 en Delphi

MetaTrader 5Exemples | 17 novembre 2021, 15:23
175 0
Andriy Voitenko
Andriy Voitenko

Introduction

Le mécanisme d'écriture d'une DLL sera considéré à l'aide d'un exemple de l'environnement d’élaboration de Delphi 2009. Cette version a été choisie en raison du fait que dans MQL5, toutes les lignes sont stockées au format Unicode. Dans les anciennes versions de Delphi, le module SysUtils manque la fonction permettant de travailler avec des lignes au format Unicode.

Si, pour une quelconque raison, vous utilisez une ancienne version antérieure (Delphi 2007 et plus ancien), vous devez alors travailler avec des lignes au format ANSI, et pour échanger des données avec MetaTrader 5, vous devez produire des conversions directes et inverses en Unicode . Pour éviter de telles complications, je recommande d’élaborer le module DLL pour MQL5 dans un environnement pas plus ancien que Delphi 2009. Une version d’adaptation de 30 jours pour Delphi peut être téléchargée sur le site officiel http://embarcadero.com.

1. Création du projet

Pour créer le projet, nous devons exécuter l'assistant DLL en choisissant l'élément de menu : 'Fichier -> Nouveau -> Autre ... -> Assistant DLL' Comme illustré en Figure 1.

Figure 1. Création d'un projet à l'aide de l'assistant DLL

En conséquence, nous allons créer un projet DLL vide, comme illustré en figure 2.


Figure 2. Un projet DLL vide

L'essence d'un long commentaire dans le titre du projet est de vous rappeler une connexion correcte et l'utilisation d'un gestionnaire de mémoire lorsque vous travaillez avec de la mémoire dynamiquement allouée. Ceci sera discuté plus en détail dans la section traitant des chaînes.

Avant de commencer le remplissage de la nouvelle DLL avec des fonctions, il est important de configurer le projet.

Ouvrez la fenêtre des propriétés du projet à partir du menu : 'Projet -> Options...' ou via le clavier 'Shift + Ctrl + F11' .

Afin de simplifier le processus de débogage, il est nécessaire que le fichier DLL soit directement créé dans le dossier '.. \\MQL5\\Libraries' Trade Terminal MetaTrtader5. Pour le faire, dans l'onglet DelphiCompiler, définissez la valeur de propriété correspondante Output directory , comme illustré en Figure 3. Cela éliminera le besoin de copier constamment le fichier, généré par DLL, du dossier du projet dans le dossier du terminal.

 Figure 3. Indiquez le dossier pour stocker le fichier DLL résultant

Afin d'éviter l'accrochage de modules BPL lors de l'assemblage, sans la présence desquels dans le dossier système Windows, la DLL créée ne fonctionnera plus à l'avenir, il est important de vérifier que sur l'onglet Packages, le flag Build avec les packages d'exécution n'est pas coché, comme illustré en figure 4.

Figure 4. Exclusion des modules BPL de l’assemblage

Après avoir terminé la configuration du projet, enregistrez-la dans votre dossier de travail, le nom indiqué du projet est le futur nom du fichier DLL compilé.

2. Ajout des procédures et des fonctions 

Examinons la situation générale lors de l'écriture des procédures et fonctions exportées dans le module DLL, sur un exemple de procédure sans paramètres. L'annonce et le transfert des paramètres seront discutés dans la section suivante.

Une petite digression. Lors de l'écriture des procédures et des fonctions dans le langage Object Pascal, le programmeur a la possibilité d'utiliser les fonctions intégrées de la bibliothèque Delphi, sans parler des innombrables composants, élaborés pour cet environnement. Par exemple, pour effectuer la même action, telle que faire apparaître un affichage d'une fenêtre modale avec un message texte, vous pouvez utiliser comme fonction API - MessageBox ainsi qu'une procédure de la bibliothèque VCL - ShowMessage..

La deuxième option conduit à inclure le module Dialogues et offre l'avantage d'un travail facile avec les dialogues standard de Windows. Cependant, la taille du fichier DLL résultant augmentera d'environ 500 KB Par conséquent, si vous préférez créer de petits fichiers DLL, qui n’occupent pas beaucoup d'espace disque, je ne vous conseillerais pas d'utiliser les composants VCL.

Un exemple de projet de test avec des explications est ci-dessous :

library dll_mql5;

uses
  Windows, // necessary for the work of the MessageBox function
  Dialogs; // necessary for the work of the ShowMessage procedure from the Dialogs module

var   Buffer: PWideChar;
//------------------------------------------------------+
procedure MsgBox(); stdcall; // 
//to avoid errors, use the stdcall (or cdecl) for the exported functions
//------------------------------------------------------+
begin
    {1} MessageBox(0,'Hello World!','terminal', MB_OK);
    {2} ShowMessage('Hello World!');// alternative to the MessageBox function 
end;

//----------------------------------------------------------+
exports
//----------------------------------------------------------+
  {A} MsgBox,
  {B} MsgBox name 'MessageBox';// renaming of the exported function


//----------------------------------------------------------+
procedure DLLEntryPoint(dwReason: DWord); // event handler
//----------------------------------------------------------+
begin
    case dwReason of

      DLL_PROCESS_ATTACH: // DLL attached to the process;
          // allocate memory
          Buffer:=AllocMem(BUFFER_SIZE);

      DLL_PROCESS_DETACH: // DLL detached from the process;
          // release memory
          FreeMem(Buffer);

    end;
end;

//----------------------------------------------------------+
begin
    DllProc := @DLLEntryPoint; //Assign event handler
    DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
//----------------------------------------------------------+

Toutes les fonctions exportées doivent être annoncées avec le modificateur stdcall ou cdecl. Si aucun de ces modificateurs n'est indiqué, Delphi utilise l'accord fastcall par défaut, qui utilise principalement les registres CPU pour transmettre les paramètres, plutôt que la pile. Cela conduira sans doute à une erreur de travail avec les paramètres transmis, au stade de l'appel de la DLL des fonctions externes.

La section "begin end" comporte un code d'initialisation standard d'un gestionnaire d'événements DLL. La procédure de rappel DLLEntryPoint sera appelée lors de la connexion et de la déconnexion du processus qui l'a appelée. Ces événements peuvent être utilisés pour la bonne gestion dynamique de la mémoire, allouée pour nos propres besoins, comme indiqué dans l'exemple.

Appel à MQL5 :

#import "dll_mql5.dll"
    void MsgBox(void);
    void MessageBox(void);
#import

// Call of procedure
   MsgBox();
// If the names of the function coincide with the names of MQL5 standard library function
// use the DLL name when calling the function
   dll_mql5::MessageBox();

3. Transmission des paramètres à la fonction et aux valeurs renvoyées

Avant d'envisager la transmission des paramètres, analysons la table de correspondance des données pour MQL5 et Object Pascal.

Type de données pour MQL5
Type de données pour Object Pascal (Delphi)
Note
char ShortInt
uchar
Byte
short
SmallInt
ushort
Word

int
Entier
 
uint Cardinal
long Int64
ulong
UInt64

flotter Seul
double double

ushort (символ) WideChar
string PWideChar  
bool booléen  
datetime TDateTime une conversion est requise (voir ci-dessous dans cette section)
couleur TColor  

Tableau 1. Le tableau de correspondance des données pour MQL5 et Object Pascal

Comme vous pouvez le voir dans le tableau, pour tous les types de données autres que datetime, Delphi dispose d’ un analogue complet.

Examinons maintenant deux manières de transmission des paramètres : par valeur et par référence. Le format de déclaration des paramètres pour les deux versions est donné dans le Tableau 2.

Méthode de transfert des paramètres
Annonce pour MQL5
Annonce pour Delphi
Note 
par valeur
int func (int a); func (a:Integer): Entier;  correct

int func (int a);
func (var a : Entier): Entier;
 Erreur : violation d'accès écrire à <adresse mémoire>
par lien
int func (int &a);
func (var a : Entier): Entier;
 correct, cependant les lignes sont transmises sans modificateur var!
  int func (int &a);  func(a: Entier): Entier;  erreur : au lieu de la valeur de la variable, comporte l'adresse de la cellule mémoire

Tableau 2. Méthodes de passage des paramètres

Examinons maintenant les exemples de travail avec les paramètres passés et les valeurs renvoyées.

3.1 Conversion de la date et de l'heure

Tout d'abord, traitons du type de date et d'heure que vous souhaitez convertir, car le type datetime correspond à TDateTime uniquement dans sa taille mais pas dans son format. Pour une transformation facile, utilisez Int64 comme type de données reçu, au lieu de TDateTime. Voici les fonctions de transformation directe et inverse :

uses 
    SysUtils,  // used for the constant UnixDateDelta 
    DateUtils; // used for the function IncSecon, DateTimeToUnix

//----------------------------------------------------------+
Function MQL5_Time_To_TDateTime(dt: Int64): TDateTime;
//----------------------------------------------------------+
begin
      Result:= IncSecond(UnixDateDelta, dt);
end;

//----------------------------------------------------------+
Function TDateTime_To_MQL5_Time(dt: TDateTime):Int64;
//----------------------------------------------------------+
begin
      Result:= DateTimeToUnix(dt);
end;

3.2 Travailler avec des types de données simples

Examinons comment transférer des types de données simples, sur l'exemple des plus couramment utilisés, int, double, bool et datetime.

Appel à Pascal Objet :

//----------------------------------------------------------+
function SetParam(var i: Integer; d: Double; const b: Boolean; var dt: Int64): PWideChar; stdcall;
//----------------------------------------------------------+
begin
  if (b) then d:=0;                   // the value of the variable d is not changed in the calling program
  i:= 10;                             // assign a new value for i
  dt:= TDateTime_To_MQL5_Time(Now()); // assign the current time for dt
  Result:= 'value of variables i and dt are changed';
end;

Appel à MQL5 :

#import "dll_mql5.dll"
    string SetParam(int &i, double d, bool b, datetime &dt);
#import

// initialization of variables
   int i = 5;
   double d = 2.8;
   bool b = true;
   datetime dt= D'05.05.2010 08:31:27';
// calling the function
   s=SetParam(i,d,b,dt);
// output of results
   printf("%s i=%s d=%s b=%s dt=%s",s,IntegerToString(i),DoubleToString(d),b?"true":"false",TimeToString(dt));
Résultat : 
The values of variables i and dt are changed i =  10  d =  2.80000000  b = true dt =  2009.05 .  05  08 :  42 

La valeur de d n'a pas changé puisqu'elle a été transférée par valeur. Pour empêcher l'apparition de modifications dans la valeur d'une variable, à l'intérieur d'une fonction DLL, un modificateur const. a été utilisé sur la variable b.

3.3 Travailler avec des structures et des tableaux

A plusieurs reprises, il est utile de regrouper les paramètres de différents types en structures, et les paramètres d'un même type en tableaux. Pensez à travailler avec tous les paramètres transférés de la fonction SetParam, de l'exemple précédent, en les intégrant dans une structure.

Appel à Pascal Objet : 

type
StructData = packed record
    i: Integer;
    d: Double;
    b: Boolean;
    dt: Int64;
  end;

//----------------------------------------------------------+
function SetStruct(var data: StructData): PWideChar; stdcall;
//----------------------------------------------------------+
begin
  if (data.b) then data.d:=0;
  data.i:= 10;                                 // assign a new value for i
  data.dt:= TDateTime_To_MQL5_Time(Now()); // assign the current time for dt
  Result:= 'The values of variables i, d and dt are changed';
end

Appel à MQL5 :  

struct STRUCT_DATA
  {
   int i;
   double d;
   bool b;
   datetime dt;
  };

#import "dll_mql5.dll"
    string SetStruct(STRUCT_DATA &data);
#import

   STRUCT_DATA data;
   
   data.i = 5;
   data.d = 2.8;
   data.b = true;
   data.dt = D'05.05.2010 08:31:27';
   s = SetStruct(data);
   printf("%s i=%s d=%s b=%s dt=%s", s, IntegerToString(data.i),DoubleToString(data.d), 
             data.b?"true":"false",TimeToString(data.dt));
Résultat :
The values of variables i,  d  and dt are changed i =  10  d =  0.00000000  b = true dt =  2009.05 .  05  12 :  19 

Il est nécessaire de noter une différence considérable par rapport au résultat de l'exemple précédent. La structure étant transférée via une référence, il est impossible de protéger les champs sélectionnés de l'édition dans la fonction appelée. La tâche de surveiller l'intégrité des données, dans ce cas, incombe entièrement au programmeur.

Pensez à travailler avec des tableaux, sur un exemple de remplissage du tableau avec la séquence de nombres de Fibonacci :

Appel à Pascal Objet :

//----------------------------------------------------------+
function SetArray(var arr: IntegerArray; const len: Cardinal): PWideChar; stdcall;
//----------------------------------------------------------+
var i:Integer;
begin
  Result:='Fibonacci numbers:';
  if (len < 3) then exit;
  arr[0]:= 0;
  arr[1]:= 1;
  for i := 2 to len-1 do
    arr[i]:= arr[i-1] + arr[i-2];
end;

Appel à MQL5 : 

#import "dll_mql5.dll"
    string SetArray(int &arr[],int len);
#import
   
   int arr[12];
   int len = ArraySize(arr);
// passing the array by reference to be filled by data in DLL
   s = SetArray(arr,len);
//output of result
   for(int i=0; i<len; i++) s = s + " " + IntegerToString(arr[i]);
   printf(s);
Résultat :  
Fibonacci numbers 0 1 1 2 3 5 8 13 21 34 55 89

3.4 Travailler avec des chaînes

Revenons à la gestion de la mémoire. Dans DLL, il est possible d'utiliser votre propre gestionnaire de mémoire. Cependant, étant donné que la DLL et le programme qui l'appelle sont souvent écrits dans différents langages de programmation et que leurs propres gestionnaires de mémoire, plutôt que la mémoire système générale, sont utilisés dans le travail, toute la responsabilité de l'exactitude du fonctionnement de la mémoire à la jonction DLL et l'application, repose sur le programmeur.

Pour travailler avec la mémoire, il est important de respecter la règle d'or, qui ressemble à quelque chose comme : "Ceux qui ont alloué de la mémoire doivent être ceux qui la libèrent." C'est-à-dire qu'il ne faut pas essayer de libérer la mémoire dans le code mql5- du programme, allouée dans la DLL, et vice versa.

Examinons un exemple de gestion de la mémoire dans un style d'appels de fonction API Windows. Dans notre cas, le programme mql5 attribue de la mémoire pour le tampon, un pointeur vers le tampon transmis à la DLL en tant que PWideChar et DLL ne remplit ce tampon qu'avec la valeur souhaitée, comme indiqué dans le exemple suivant :

Appel à Pascal Objet :

//----------------------------------------------------------+
procedure SetString(const str:PWideChar) stdcall;
//----------------------------------------------------------+
begin
  StrCat(str,'Current time:');
  strCat(str, PWideChar(TimeToStr(Now)));
end;

Appel à MQL5 : 

#import "dll_mql5.dll"
    void SetString(string &a);
#import

// the string must be initialized before the use
// the size of the buffer must be initially larger or equal to the string length
   StringInit(s,255,0); 
//passing the buffer reference to DLL 
   SetString(s);
// output of result 
   printf(s);

Résultat :

Current Time: 11: 48:51 

La mémoire pour le tampon de ligne peut être sélectionnée dans la DLL de plusieurs manières, comme le montre l'exemple suivant :

Appel à Pascal Objet : 

//----------------------------------------------------------+
function GetStringBuffer():PWideChar; stdcall;
//----------------------------------------------------------+
var StrLocal: WideString;
begin
     // working through the dynamically allocated memory buffer
     StrPCopy(Buffer, WideFormat('Current date and time: %s', [DateTimeToStr(Now)]));
     // working through the global varialble of WideString type
     StrGlobal:=WideFormat('Current time: %s', [TimeToStr(Time)]);
     // working through the local varialble of WideString type
     StrLocal:= WideFormat('Current data: %s', [DateToStr(Date)]);

{A}  Result := Buffer;

{B}  Result := PWideChar(StrGlobal);
     // it's equal to the following
     Result := @StrGlobal[1];

{С}  Result := 'Return of the line stored in the code section';

     // pointer to the memory, that can be released when exit from the function
{D}  Result := @StrLocal[1];
end;
Appel à MQL5 : 
#import "dll_mql5.dll"
    string GetStringBuffer(void);
#import

   printf(GetStringBuffer());

 Résultat : 

Current Date: 19.05.2010

Ce qui est important, c'est que les quatre options fonctionnent. Dans les deux premières options, le travail avec la ligne se fait via une mémoire globalement allouée

Dans l'option A, la mémoire est allouée indépendamment, et dans l'option B, le travail avec la gestion de la mémoire est assuré par le gestionnaire de mémoire.

Dans l'option C, la constante de ligne n'est pas stockée dans la mémoire, mais dans le segment de code, de sorte que le gestionnaire de mémoire n'attribue pas de mémoire dynamique pour son stockage. L'option D est une erreur de programmation en gras, car la mémoire, allouée à la variable locale, peut être immédiatement libérée après avoir quitté la fonction.

Et bien que le gestionnaire de mémoire ne libère pas cette mémoire instantanément et qu'il n'ait pas le temps de remplir de corbeille, je recommande d'exclure cette dernière option de l'utilisation.

3.5 Utilisation des paramètres par défaut

Parlons de l'utilisation des paramètres facultatifs. Ils sont intéressants car leurs valeurs n'ont pas besoin d'être précisées lors de l'appel de procédures et de fonctions. En attendant, ils doivent être décrits, strictement après tous les paramètres obligatoires dans la déclaration des procédures et des fonctions, comme le montre l'exemple suivant :

Appel à Pascal Objet : 

//----------------------------------------------------------+
function SetOptional(var a:Integer; b:Integer=0):PWideChar; stdcall;
//----------------------------------------------------------+
begin
    if (b=0) then Result:='Call with default parameters'
    else          Result:='Call without default parameters';
end;
Appel à MQL5 :
#import "dll_mql5.dll"
    string SetOptional(int &a, int b=0);
#import

  i = 1;
  s = SetOptional(i); // second parameter is optional
  printf(s);

Résultat : 

Call with default parameters

Pour faciliter le débogage, le code des exemples ci-dessus est organisé sous forme de script, il se trouve dans le fichier Testing__DLL.mq5.

4. Éventuelles erreurs au stade de la conception

Erreur Le chargement de DLL n'est pas autorisé.

Solution: Accédez aux paramètres de MetaTrader 5 via le menu 'Outils-Options' et autorisez l'importation de la fonction DLL, comme indiqué dans figure 5.

  Figure 5. Autorisation d'importer des fonctions DLL

 Figure 5. Autorisation d'importer des fonctions DLL

Erreur Impossible de trouver 'nom de fonction' dans 'nom DLL'.
Solution: Assurez-vous la fonction de rappel est indiquée dans la section Exportations du projet DLL. Si c'est le cas, vous devez vérifier la correspondance complète du nom de la fonction dans la DLL et dans le programme mql5 - étant donné qu'il est sensible aux caractères !

Erreur Violation d'accès écrire à [adresse mémoire]
Solution: Vous devez vérifier l'exactitude de la description des paramètres transmis (voir tableau 2). Étant donné que cette erreur est généralement associée au traitement des lignes, il est important de suivre les recommandations pour travailler avec les lignes, énoncées au paragraphe 3.4 de cet article.

5. Exemple de code DLL

Comme exemple visuel de l'utilisation de DLL, examinons les calculs de paramètres du canal de régression, constitués de trois lignes. Pour vérifier l'exactitude de la construction du canal, nous utiliserons l'objet intégré "Régression du canal". Le calcul de la droite d'approximation pour LS (méthode des moindres carrés) est tiré du site http://alglib.sources.ru/, où se trouve une collection d'algorithmes pour le traitement des données. Le code des algorithmes est présenté dans plusieurs langages de programmation, dont Delphi.

Pour calculer les coefficients de a et b, par la droite d'approximation y = a + b * x, utilisez la procédure, décrite dans le fichier LRLine linreg.pas.

 procedure  LRLine (  const  XY: TReal2DArray;  / / Two-dimensional array of real numbers for X and Y coordinates 
                           N : AlglibInteger;  // number of points
                     var Info : AlglibInteger; // conversion status
                       var  A: Double;  / / Coefficients of the approximating line 
                        var  B: Double);
  

Pour calculer les paramètres du canal, utilisez la fonction CalcLRChannel.

Appel à Pascal Objet :  

//----------------------------------------------------------+
function CalcLRChannel(var rates: DoubleArray; const len: Integer;
                          var A, B, max: Double):Integer; stdcall;
//----------------------------------------------------------+
var arr: TReal2DArray;
    info: Integer;
    value: Double;
begin

    SetLength(arr,len,2);
    // copy the data to a two-dimensional array
    for info:= 0 to len - 1 do
    begin
      arr[info,0]:= rates[info,0];
      arr[info,1]:= rates[info,1];
    end;

    // calculation of linear regression coefficients
    LRLine(arr, len, info, A,  B);

    // find the maximal deviation from the approximation line found
    // and determine the width of the channel 
    max:= rates[0,1] - A;
    for info := 1 to len - 1 do
    begin
      value:= Abs(rates[info,1]- (A + B*info));
      if (value > max) then max := value;
    end;

    Result:=0;
end;

Appel à MQL5 :

#import "dll_mql5.dll"
    int CalcLRChannel(double &rates[][2],int len,double &A,double &B,double &max);
#import

   double arr[][2], //data array for processing in the ALGLIB format
              a, b,  // Coefficients of the approximating line  
              max; // maximum deviation from the approximating line is equal to half the width of the channel
   
   int len = period; //number of points for calculation
   ArrayResize(arr,len);

// copying the history to a two-dimensional array
   int j=0;
   for(int i=rates_total-1; i>=rates_total-len; i--)
     {
      arr[j][0] = j;
      arr[j][1] = close[i];
      j++;
     }

// calculation of channel parameters
   CalcLRChannel(arr,len,a,b,max);

Le code indicateur, qui utilise la fonction CalcLRChannel pour les calculs, se trouve dans le fichier LR_Channel.mq5 et ci-dessous : 

//+------------------------------------------------------------------+
//|                                                   LR_Channel.mq5 |
//|                        Copyright 2009, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2009, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window

#include <Charts\Chart.mqh>
#include <ChartObjects\ChartObjectsChannels.mqh>

#import "dll_mql5.dll"
int CalcLRChannel(double &rates[][2],int len,double &A,double &B,double &max);
#import

input int period=75;

CChart               *chart;
CChartObjectChannel  *line_up,*line_dn,*line_md;
double                arr[][2];
//+------------------------------------------------------------------+
int OnInit()
//+------------------------------------------------------------------+
  {

   if((chart=new CChart)==NULL)
     {printf("Chart not created"); return(false);}

   chart.Attach();
   if(chart.ChartId()==0)
     {printf("Chart not opened");return(false);}

   if((line_up=new CChartObjectChannel)==NULL)
     {printf("Channel not created"); return(false);}

   if((line_dn=new CChartObjectChannel)==NULL)
     {printf("Channel not created"); return(false);}

   if((line_md=new CChartObjectChannel)==NULL)
     {printf("Channel not created"); return(false);}

   return(0);
  }
//+------------------------------------------------------------------+
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[])
//+------------------------------------------------------------------+
  {

   double a,b,max;
   static double save_max;
   int len=period;

   ArrayResize(arr,len);

// copying of history to a two-dimensional array
   int j=0;
   for(int i=rates_total-1; i>=rates_total-len; i--)
     {
      arr[j][0] = j;
      arr[j][1] = close[i];
      j++;
     }

// procedure of calculating the channel parameters
   CalcLRChannel(arr,len,a,b,max);

// if the width of the channel has changed
   if(max!=save_max)
     {
      save_max=max;

      // Delete the channel
      line_md.Delete();
      line_up.Delete();
      line_dn.Delete();

      // Creating a channel with new coordinates
      line_md.Create(chart.ChartId(),"LR_Md_Line",0, time[rates_total-1],     a, time[rates_total-len], a+b*(len-1)    );
      line_up.Create(chart.ChartId(),"LR_Up_Line",0, time[rates_total-1], a+max, time[rates_total-len], a+b*(len-1)+max);
      line_dn.Create(chart.ChartId(),"LR_Dn_Line",0, time[rates_total-1], a-max, time[rates_total-len], a+b*(len-1)-max);

      // assigning the color of channel lines     
      line_up.Color(RoyalBlue);
      line_dn.Color(RoyalBlue);
      line_md.Color(RoyalBlue);

      // assigning the line width
      line_up.Width(2);
      line_dn.Width(2);
      line_md.Width(2);
     }

   return(len);
  }
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
//+------------------------------------------------------------------+
  {
// Deleting the created objects
   chart.Detach();

   delete line_dn;
   delete line_up;
   delete line_md;
   delete chart;
  }

Le résultat du travail de l'indicateur est la création d'un canal de régression bleu, tel que indiqué en figure 6. Pour vérifier l'exactitude de la construction du canal, le graphique montre un « canal de régression », issu de l'arsenal d'instruments d'analyse technique de MetaTrader 5, marqué en rouge.

Comme on peut le voir sur la figure, les lignes centrales du canal se confondent. Pendant ce temps, il y a une légère différence dans la largeur du canal (quelques points), qui sont dues aux différentes approches dans son calcul. 

Figure 6. Comparaison des canaux de régression

Figure 6. Comparaison des canaux de régression

Conclusion

Cet article décrit les fonctionnalités de l'écriture d'une DLL à l'aide d'une plate-forme d’élaboration d'applications Delphi.

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

Fichiers joints |
mql5_sources.zip (276.43 KB)
dll_mql5_sources.zip (453.47 KB)
Analyser  les modèles de chandeliers Analyser les modèles de chandeliers
La réalisation d'un graphique en chandeliers japonais et l'analyse des modèles de chandeliers constituent un domaine passionnant d'analyse technique. L'avantage des chandeliers est qu'ils représentent les données de sorte que vous puissiez suivre la dynamique à l'intérieur des données. Dans cet article, nous analysons les types de chandeliers, la classification des modèles de chandeliers et présentons un indicateur pouvant déterminer les modèles de chandeliers.
Un exemple de système de trading axé sur un indicateur Heiken-Ashi Un exemple de système de trading axé sur un indicateur Heiken-Ashi
Dans cet article, nous examinons la question de l'utilisation d'un indicateur Heiken-Ashi dans le trading. Sur la base de cet indicateur, un système de trading simple est considéré et un conseiller expert MQL5 est écrit. Les opérations de trading sont implémentées sur les bases de classes de la bibliothèque de classes Standard. Les résultats des tests de la stratégie de trading examinée, sont basés sur l'historique et obtenus à l'aide du testeur de stratégie MetaTrader 5 intégré, sont fournis dans l'article.
Création d’un Expert Advisor, qui trade sur un certain nombre d’instruments Création d’un Expert Advisor, qui trade sur un certain nombre d’instruments
Le concept de diversification des actifs sur les marchés financiers est vieux et a toujours attiré les traders débutants. Dans cet article, l’auteur propose une approche extrêmement simple d’une élaboration d’un Expert Advisor multi-devises, pour une première introduction à cette direction des stratégies de trading.
Un Gestionnaire de Commande  Virtuelle pour suivre les commandes  dans l'environnement MetaTrader 5 axé sur la position Un Gestionnaire de Commande Virtuelle pour suivre les commandes dans l'environnement MetaTrader 5 axé sur la position
Cette bibliothèque de classes peut être ajoutée à un Expert Advisor MetaTrader 5 pour lui permettre d'être écrite avec une approche axée sur les commandes largement comparable à MetaTrader 4, par rapport à l'approche axée sur la position de MetaTrader 5. Il le fait en gardant une trace des commandes virtuelles sur le terminal client MetaTrader 5, tout en maintenant un arrêt de protection du courtier pour chaque position pour la protection contre les catastrophes.