
Guide d'écriture d'une DLL pour MQL5 en Delphi
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
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
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





- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation