
Leitfaden zum Schreiben einer DLL für MQL5 in Delphi
Einleitung
Der Mechanismus zum Schreiben einer DLL wird anhand eines Beispiels in der Entwicklungsumgebung von Delphi 2009 vorgeführt. Diese Version wurde aufgrund der Tatsache ausgewählt, dass in MQL5 alle Zeilen im Unicode-Format gespeichert werden. In älteren Versionen von Delphi fehlt dem SysUtils-Modul die Funktion zum Arbeiten mit Unicode-Zeilen.
Wenn Sie aus irgendeinem Grund mit einer älteren Version arbeiten (Delphi 2007 und älter), müssen Sie Zeilen im ANSI-Format nutzen. Für den Datenaustausch mit MetaTrader 5 müssen sie direkte und umgekehrte Konvertierungen aus/in Unicode vornehmen. Um solche Komplikationen zu vermeiden, empfehle ich für die Entwicklung des DLL-Moduls für MQL5 die Nutzung einer Umgebung, die nicht älter ist als Delphi 2009. Eine 30-tägige Testversion kann von der offiziellen Webseite http://embarcadero.com heruntergeladen werden.
1. Erstellen des Projekts
Um das Projekt zu erstellen, müssen wir den DLL Wizard ausführen, indem wir den entsprechenden Menüeintrag auswählen: 'File -> New -> Other... -> DLL Wizard' (Datei -> Neu -> Sonstige... -> DLL Wizard), wie in Abbildung 1 illustriert.
Abbildung 1. Erstellen eines Projekts mit DLL Wizard
Als Ergebnis wird ein leeres DLL-Projekt erstellt, wie in Abbildung 2 illustriert.
Abbildung 2. Leeres DLL-Projekt
Die lange Kommentarzeile im Titel des Projekts soll den Anwender daran erinnern, eine korrekte Verbindung zu nutzen und beim Arbeiten mit dynamisch zugewiesenem Speicher einen Speichermanager zu verwenden. Dies wird in dem Abschnitt, der Strings behandelt, näher beschrieben.
Bevor Sie beginnen, die neue DLL mit Funktionen auszustatten, muss das Projekt konfiguriert werden.
Öffnen Sie die Projekteigenschaften aus dem Menü: 'Project -> Options...' (Projekt -> Optionen...) oder über das Tastenkürzel 'Strg + Umschalt + F11'.
Um den Debugging-Prozess zu vereinfachen, sollte die DLL-Datei direkt im Ordner '.. \MQL5\Libraries' des Handelsterminals von MetaTrader 5 erstellt werden. Legen Sie hierzu in der Registerkarte DelphiCompiler den entsprechenden Eigenschaftswert Output directory (Ausgabeverzeichnis) wie in Abbildung 3 illustriert fest. Dadurch wird verhindert, dass die erstellte DLL-Datei ständig aus dem Projektordner in den Ordner des Terminals kopiert werden muss.
Abbildung 3. Festlegen des Ordners zum Speichern der erstellten DLL-Datei
Damit während der Erstellung keine BPL-Module angehängt werden, ohne deren Vorhandensein im Windows-Systemordner die erstellte DLL nicht funktionieren wird, muss unbedingt sichergestellt werden, dass in der Registerkarte Packages (Pakete) die Option Build with runtime packages (Erstellung mit Laufzeitpaketen) deaktiviert ist, wie in Abbildung 4 illustriert.
Abbildung 4. Abwählen von BPL-Modulen aus der Erstellung
Nachdem die Projektkonfiguration abgeschlossen ist, speichern Sie das Projekt in Ihrem Arbeitsordner. Der festgelegte Name des Projekts ist der zukünftige Name der kompilierten DLL-Datei.
2. Einfügen von Verfahren und Funktionen
Sehen wir uns die allgemeine Situation beim Schreiben der exportierten Verfahren und Funktionen im DLL-Modul anhand eines Beispiels für ein Verfahren ohne Parameter an. Die Erklärung und Übertragung von Parametern wird im nächsten Abschnitt besprochen.
Ein kurzer Ausflug: Beim Schreiben von Verfahren und Funktionen in der Sprache Object Pascal hat der Programmierer die Möglichkeit, eingebaute Delphi-Bibliotheksfunktionen zu nutzen, ganz zu schweigen von den unzähligen für diese Umgebung entwickelten Komponenten. Zum Beispiel können Sie für die Ausführung einer bestimmten Funktion wie den Aufruf eines modalen Fensters mit einer Textbenachrichtigung eine API-Funktion nutzen – MessageBox – sowie ein Verfahren aus der VCL-Bibliothek – ShowMessage.
Durch die zweite Option wird das Modul Dialogs einbezogen und es kann einfach mit Windows-Standarddialogen gearbeitet werden. Allerdings steigt die Größe der somit erstellten DLL-Datei um etwa 500 KB. Deshalb empfehle ich, dass Sie keine VCL-Komponenten verwenden, wenn sie kleine DLL-Dateien erstellen möchten, die wenig Speicherplatz verbrauchen.
Hier sehen Sie ein Musterprojekt mit Erklärungen:
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. //----------------------------------------------------------+
Alle exportierten Funktionen müssen mit dem Modifikator stdcall oder cdecl erklärt werden. Falls keiner dieser Modifikatoren festgelegt ist, nutzt Delphi standardmäßig die fastcall-Vereinbarung, die hauptsächlich CPU-Register anstatt Stacks zum Übergeben von Parametern nutzt. Dies führt beim Aufrufen der externen DLL-Funktionen höchstwahrscheinlich zu Fehlern beim Arbeiten mit den übergebenen Parametern.
Der Abschnitt "begin end" beinhaltet einen Standard-Initialisierungscode eines DLL-Ereignis-Handlers. Das Callback-Verfahren DLLEntryPoint wird beim Verbinden und Trennen des Prozesses, der es aufgerufen hat, aufgerufen. Diese Ereignisse können für korrektes dynamisches Speichermanagement genutzt werden, das an unsere Bedürfnisse angepasst ist, wie es im Beispiel verdeutlicht wird.
Aufruf für 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. Übergeben von Parametern an die Funktion und ausgegebene Werte
Bevor wir auf das Übergeben von Parametern eingehen, analysieren wir die Tabelle mit den Datenpaaren für MQL5 und Object Pascal.
Datentyp für MQL5 |
Datentyp für Object Pascal (Delphi) |
Hinweise |
---|---|---|
char | ShortInt | |
uchar |
Byte | |
short |
SmallInt | |
ushort |
Word |
|
int |
Integer |
|
uint | Cardinal | |
long | Int64 | |
ulong |
UInt64 |
|
float | Single | |
double | Double |
|
ushort (Symbol) | WideChar | |
string | PWideChar | |
bool | Boolean | |
datetime | TDateTime | Konvertierung erforderlich (siehe weiter unten in diesem Abschnitt) |
color | TColor |
Tabelle 1. Datenpaartabelle für MQL5 und Object Pascal
Wie Sie in der Tabelle sehen können, gibt es für alle Datentypen außer datetime ein genaues Gegenstück in Delphi.
Sehen Sie sich nun zwei Arten zum Übergeben von Parametern an: nach Wert und nach Verweis. Das Format für die Deklarierung von Parametern für beide Versionen finden Sie in Tabelle 2.
Übertragungsmethode der Parameter |
Erklärung für MQL5 |
Erklärung für Delphi |
Hinweise |
---|---|---|---|
nach Wert |
int func (int a); | func (a:Integer): Integer; | richtig |
int func (int a); |
func (var a: Integer): Integer; |
Fehler: access violation write to <memory address> | |
nach Verweis |
int func (int &a); |
func (var a: Integer): Integer; |
richtig, allerdings werden Zeilen ohne Modifikator var übertragen! |
int func (int &a); | func (a: Integer): Integer; | Fehler: Enthält anstatt des Werts der Variable die Adresse der Speicherzelle |
Tabelle 2. Methoden zur Übertragung von Parametern
Sehen wir uns nun Beispiele für die Arbeit mit übergebenen Parametern und ausgegebenen Werten an.
3.1 Konvertieren von Datum und Zeit
Behandeln wir zunächst den Typ von Datum und Zeit, den Sie konvertieren möchten, da der datetime-Typ nur in seiner Größe, aber nicht in seinem Format TDateTime entspricht. Um die Umwandlung zu vereinfachen, nutzen Sie Int64 anstatt TDateTime als erhaltenen Datentyp. Im folgenden sehen Sie die Funktionen für die direkte und umgekehrte Umwandlung:
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 Arbeit mit einfachen Datentypen
Sehen wir uns anhand der häufig verwendeten Typen int, double, bool und datetime an, wie einfache Datentypen übertragen werden.
Aufruf für Object Pascal:
//----------------------------------------------------------+ 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;
Aufruf für 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));Ergebnis:
The values of variables i and dt are changed i = 10 d = 2.80000000 b = true dt = 2009.05 . 05 08 : 42
Der Wert von d hat sich nicht verändert, da er nach Wert übertragen wurde. Um Veränderungen der Werte einer Variable innerhalb einer DLL-Funktion zu vermeiden, wurde der Modifikator const auf die Variable b angewandt.
3.3 Arbeit mit Strukturen und Arrays
Es ist oft hilfreich, Parameter verschiedener Typen in Strukturen und Parameter desselben Typen in Arrays zu gruppieren. Sehen wir uns die Arbeit mit allen übertragenen Parametern der Funktion SetParam aus dem vorherigen Beispiel an und integrieren sie in eine Struktur.
Aufruf für Object Pascal:
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;
Aufruf für 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));Ergebnis:
The values of variables i, d and dt are changed i = 10 d = 0.00000000 b = true dt = 2009.05 . 05 12 : 19
Ein wesentlicher Unterschied zum Ergebnis des vorherigen Beispiels muss beachtet werden. Da die Struktur über einen Verweis übertragen wird, ist es unmöglich, die ausgewählten Felder vor einer Bearbeitung in der aufgerufenen Funktion zu schützen. Die Pflicht, die Integrität der Daten zu überwachen, liegt in diesem Fall vollständig beim Programmierer.
Sehen wir uns die Arbeit mit Arrays anhand eines Beispiels an, in dem das Array mit den Zahlen der Fibonacci-Folge gefüllt wird:
Aufruf für Object Pascal:
//----------------------------------------------------------+ 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;
Aufruf für 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);Ergebnis:
Fibonacci numbers 0 1 1 2 3 5 8 13 21 34 55 89
3.4 Arbeit mit Strings
Zurück zum Speichermanagement. In einer DLL können Sie Ihren eigenen Speichermanager verwenden. Doch da DLLs und die Programme, die sie aufrufen, oft in unterschiedlichen Programmiersprachen geschrieben sind und für die Arbeit ihre eigenen Speichermanager anstatt des allgemeinen Systemspeichers verwendet werden, liegt die Last der Verantwortung für die Korrektheit der Arbeit des Speichers an der Verbindungsstelle zwischen DLL und Anwendung auf den Schultern des Programmierers.
Bei der Arbeit mit dem Speicher sollte die goldene Regel befolgt werden, die in etwa so klingt: "Wer Speicher zuweist, muss ihn auch freigeben." Das bedeutet, Sie sollten nicht versuchen, in der DLL zugewiesenen Speicher im Code eines MQL5-Programms freizugeben und umgekehrt.
Sehen wir uns ein Beispiel eines Speichermanagements im Stil von Windows-API-Funktionsaufrufen an. In unserem Fall weist das MQL5-Programm Speicher für den Puffer zu, ein Pointer zu dem Puffer wird als PWideChar an die DLL übergeben und die DLL füllt diesen Puffer nur mit dem gewünschten Wert aus, wie es im folgenden Beispiel verdeutlicht wird:
Aufruf für Object Pascal:
//----------------------------------------------------------+ procedure SetString(const str:PWideChar) stdcall; //----------------------------------------------------------+ begin StrCat(str,'Current time:'); strCat(str, PWideChar(TimeToStr(Now))); end;
Aufruf für 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);
Ergebnis:
Current Time: 11: 48:51
Der Speicher für den Zeilenpuffer kann auf mehrere Arten in der DLL ausgewählt werden, wie es im folgenden Beispiel illustriert wird:
Aufruf für Object Pascal:
//----------------------------------------------------------+ 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;Aufruf für MQL5:
#import "dll_mql5.dll" string GetStringBuffer(void); #import printf(GetStringBuffer());
Ergebnis:
Current Date: 19.05.2010
Charakteristisch ist, dass alle vier Optionen funktionieren. In den ersten beiden Optionen findet die Arbeit mit der Zeile über global zugewiesenen Speicher statt.
In Option A wird der Speicher unabhängig zugewiesen, in Option B übernimmt der Speichermanager die Aufgabe des Speichermanagements.
In Option C wird die Zeilenkonstante nicht im Speicher, sondern im Code-Segment gespeichert, sodass der Speichermanager keinen dynamischen Speicher für ihre Speicherung zuweist. Option D ist ein grober Programmierfehler, da der der lokalen Variable zugewiesene Speicher sofort nach der Beendigung der Funktion freigegeben werden kann.
Und obwohl der Speichermanager den Speicher nicht sofort freigibt und die Zeit nicht ausreicht, damit er vermüllt wird, empfehle ich, die letztgenannte Option nicht zu nutzen.
3.5 Nutzung der Standardparameter
Sprechen wir über die Nutzung optionaler Parameter. Diese sind interessant, weil ihre Werte beim Aufrufen von Verfahren und Funktionen nicht zwangsläufig festgelegt werden müssen. Allerdings müssen sie strikt nach allen Pflichtparametern in der Deklarierung der Verfahren und Funktionen beschrieben werden, wie im folgenden Beispiel illustriert:
Aufruf für Object Pascal:
//----------------------------------------------------------+ 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;Aufruf für MQL5:
#import "dll_mql5.dll" string SetOptional(int &a, int b=0); #import i = 1; s = SetOptional(i); // second parameter is optional printf(s);
Ergebnis:
Call with default parameters
Um den Debugging-Prozess zu vereinfachen, wurde der Code der oben aufgeführten Beispiele als Script zusammengestellt und befindet sich in der Datei Testing_DLL.mq5.
4. Mögliche Fehler in der Konzeptphase
Fehler: DLL Loading is not allowed.
Lösung: Öffnen Sie die MetaTrader-5-Einstellungen im Menü 'Tools -> Options' und erlauben Sie den Import von DLL-Funktionen, wie in Abbildung 5 dargestellt.
Abbildung 5. Erlaubnis, DLL-Funktionen zu importieren
Fehler: Cannot find 'function name' in 'DLL name'.
Lösung: Überprüfen Sie, ob die Aufruffunktion im Abschnitt Exports des DLL-Projekts festgelegt ist. Ist sie festgelegt, überprüfen Sie, ob der Funktionsname in der DLL und im MQL5-Programm sich vollständig entsprechen. Achten Sie unbedingt auf die Groß- und Kleinschreibung!
Fehler: Access violation write to [memory address]
Lösung: Überprüfen Sie die Korrektheit der Beschreibung der übertragenen Parameter (siehe Tabelle 2). Da dieser Fehler oft mit der Verarbeitung von Zeilen zusammenhängt, ist es wichtig, die Empfehlungen für die Arbeit mit Zeilen gemäß Abschnitt 3.4 dieses Beitrags zu befolgen.
5. Beispiel eines DLL-Codes
Betrachten Sie als visuelles Beispiel für die Verwendung von DLLs die Berechnungen der Parameter des Regressionskanals, der aus drei Zeilen besteht. Um die Korrektheit des Aufbaus des Kanals zu überprüfen, nutzen wir das eingebaute Objekt "Regressionskanal". Die Berechnung der sich annähernden Geraden für LN (Methode mit den wenigsten Quadraten, "least squares method) stammt von der Seite http://alglib.sources.ru, auf der Sie eine Kollektion von Algorithmen für die Datenverarbeitung finden. Der Code der Algorithmen wird in mehreren Programmiersprachen zur Verfügung gestellt, darunter auch Delphi.
Um die Koeffizienten a und b durch die sich annähernde Gerade y=a+b*x zu berechnen, nutzen Sie das in der Datei linreg.pas beschriebene Verfahren LRLine.
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);
Zur Berechnung der Parameter des Kanals nutzen Sie die Funktion CalcLRChannel.
Aufruf für Object Pascal:
//----------------------------------------------------------+ 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;
Aufruf für 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);
Der Indikatorcode, der die Funktion CalcLRChannel für Berechnungen nutzt, befindet sich in der Datei LR_Channel.mq5 und ist im Folgenden illustriert:
//+------------------------------------------------------------------+ //| 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; }
Das Ergebnis der Arbeit des Indikators ist die Erschaffung eines blauen Regressionskanals, wie in Abbildung 6 dargestellt. Um die Richtigkeit des Aufbaus des Kanals zu überprüfen, zeigt das Diagramm einen "Regressionskanal" aus dem Hauptarsenal von MetaTrader 5, hier mit Rot gekennzeichnet.
Wie Sie in der Abbildung sehen können, verschmelzen die zentralen Linien des Kanals. Dabei gibt es einen kleinen Unterschied in der Breite des Kanals (wenige Punkte), der an den unterschiedlichen Herangehensweisen an die Berechnung liegt.
Abbildung 6. Vergleich von Regressionskanälen
Fazit
Dieser Beitrag beschreibt die Funktionen des Schreibens einer DLL mithilfe einer Entwicklungsplattform für Delphi-Anwendungen.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/96





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.