English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Leitfaden zum Schreiben einer DLL für MQL5 in Delphi

Leitfaden zum Schreiben einer DLL für MQL5 in Delphi

MetaTrader 5Beispiele | 9 März 2016, 08:30
988 0
Andriy Voitenko
Andriy Voitenko

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

 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

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

Beigefügte Dateien |
mql5_sources.zip (276.43 KB)
dll_mql5_sources.zip (453.47 KB)
Kontrolle der Saldo-Gefällekurve während der Arbeit eines Expert Advisors Kontrolle der Saldo-Gefällekurve während der Arbeit eines Expert Advisors
Regeln für ein Handelssystem zu finden und sie in einen Expert Advisor zu programmieren, ist nur die Hälfte der Arbeit. Irgendwie muss man ja auch die Abläufe des Expert Adivsors kontrollieren, während er die Ergebnisse des Handels anhäuft. Dieser Beitrag beschreibt einen Ansatz, der die Leistung eines Expert Advisors durch Erzeugung eines Feedbacks steigert, das die Saldo-Gefällekurve misst.
Wie man rasch einen Expert Advisor für den Automatisierten Trading-Wettbewerb 2010 erzeugt Wie man rasch einen Expert Advisor für den Automatisierten Trading-Wettbewerb 2010 erzeugt
Zur Entwicklung eines Expert Advisors zur Teilnahme am Automatisierten Trading-Wettbewerb 2010, nehmen wir ein Template eines fertigen Expert Advisors her. Selbst noch unerfahrene MQL5 Programmierer können diese Aufgabe bewältigen, da ja für die Strategien die grundlegenden Klassen, Funktionen und Templates schon entwickelt sind. Daher genügt es, nur ein bisschen Code zur Implementierung Ihres Trading-Konzepts zu schreiben.
Analyse von Kerzenmustern Analyse von Kerzenmustern
Die Konstruktion japanischer Kerzendiagramme und die Analyse von Kerzenmustern sind ein erstaunlicher Bereich der technischen Analyse. Der Vorteil von Kerzen ist, dass sie Daten auf eine Art darstellen, dank der Sie die Dynamiken innerhalb der Daten verfolgen können. In diesem Beitrag analysieren wir Arten von Kerzen, klassifizieren Kerzenmuster und stellen einen Indikator vor, der Kerzenmuster bestimmen kann.
Fehler finden und Protokollierung Fehler finden und Protokollierung
Der MetaEditor 5 verfügt über ein Feature zur Fehlersuche. Doch Wenn Sie Ihre MQL5 Programme schreiben, möchten Sie oft nicht nur einzelne Werte anzeigen, sondern alle Meldungen sehen können, die während des Tests und der Online-Arbeit auftauchen. Wenn die Inhalte der Protokolldatei groß sind, dann liegt es nahe, die rasche und schnelle Abfrage der benötigten Meldung zu automatisieren In diesem Beitrag geht es um das Finden von Fehlern in den MQL5 Programmen sowie um Methoden der Protokollierung. Darüber hinaus werden wir die Protokollierung in Dateien vereinfachen und LogMon kennen lernen, ein einfaches Programm zur bequemen Ansicht von Protokollen.