English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Sehr einfach: Der Datenaustausch zwischen Indikatoren

Sehr einfach: Der Datenaustausch zwischen Indikatoren

MetaTrader 5Beispiele | 11 Februar 2016, 12:11
2 260 1
Alexey Subbotin
Alexey Subbotin

Einleitung

Ein Grund, aus dem wir Neueinsteiger schätzen, ist der, dass sie sich beharrlich weigern, die Suche zu nutzen, und es gibt viele Themen wie "FAQ", "Für Neueinsteiger" oder "Wer eine Frage von dieser Liste stellt, landet in der Hölle". Ihre wahre Mission ist es, Fragen zu stellen wie "Wie...", "Ist es möglich...", und oft erhalten sie Antworten wie "Auf gar keinen Fall", "Es ist unmöglich".

Die Phrase "sag niemals nie" hat Wissenschaftler, Ingenieure und Programmierer zum Nachdenken bewegt und zur Erschaffung von etwas Neuem aus etwas Vergessenem Altem.

1. Problemdefinition

Hier sehen Sie beispielsweise ein Zitat aus einem der Themen im MQL4-Community-Forum (übersetzt aus dem Russischen):

 ...es gibt zwei Indikatoren (nennen wir sie A und B). Indikator A nutzt wie gewohnt Daten direkt aus dem Preisdiagramm und B nutzt Daten des Indikators A. Hier die Frage: Was muss ich tun, um die Berechnung von B mithilfe der Daten von Indikator A (der bereits angehängt ist) dynamisch durchzuführen, anstatt iCustom("indicator A", ...) zu nutzen? Das heißt, wenn ich einige Einstellungen von Indikator A ändere, sollte dies in den Parameteränderungen des Indikators B wiedergegeben werden.

oder in anderen Worten:

...Angenommen, wir haben einen Moving Average an ein Diagramm angehängt. Wie erhalten wir nun einen direkten Zugriff auf seinen Datenpuffer?

und weiter:

... Wenn ich einen Indikator mithilfe von iCustom aufrufe, lädt er neu, auch wenn er vor dem Aufruf geladen wurde. Ist es möglich, dies zu verhindern?

Die Liste solcher Fragen kann beliebig erweitert werden. Sie werden wieder und wieder gestellt, und zwar nicht nur von Neueinsteigern. Das grundlegende Problem ist, dass MetaTrader über keine Technik verfügt, um auf die Daten des benutzerdefinierten Indikators zuzugreifen, ohne iCustom (MQL4) oder die Verbindung von iCustom und CopyBuffer (MQL5) zu verwenden. Aber für die Entwicklung des nächsten Kunstwerks wäre es äußerst verführerisch, ein paar Funktionen in MQL zu schreiben, die mithilfe von Daten von einem bestimmten Diagramm Daten abrufen oder etwas berechnen.

Es ist nicht sehr bequem, mit den oben aufgeführten Standardfunktionen zu arbeiten. Beispielsweise werden in MQL4 die Kopien des Indikators für jeden Caller erstellt, wenn iCustom aufgerufen wird. In MQL5 wurde dieses Problem durch Handles teilweise gelöst, sodass Berechnungen nur einmal für eine Master-Kopie ausgeführt werden. Doch das ist auch kein Allheilmittel: Wenn der Indikator, auf den wir uns beziehen, viele Berechnungsressourcen verbraucht, ist es so gut wie garantiert, dass sich das Terminal bei jeder erneuten Initialisierung für etliche Sekunden aufhängt.

Ich erinnere mich an mehrere Vorschläge, wie man auf den Ursprung zugreifen könnte, darunter die folgenden:

  • Mapping von Dateien auf einem physischen Laufwerk oder im Arbeitsspeicher;
  • gemeinsame Speichernutzung über DLL für den Datenaustausch;
  • Verwendung von globalen Variablen des Client Terminals für den Austausch und die Speicherung von Daten.

Es gab weitere Methoden, die Variationen der oben aufgeführten sind, sowie exotischere wie Sockets, Mailslots usw. (es gibt eine radikale Methode, die oft in Expert Advisors verwendet wird – die Übertragung von Indikatorberechnungen direkt in den Code des Expert Advisors –, doch dies würde den Rahmen dieses Beitrags sprengen).

Aus der Sicht des Verfassers haben alle diese Methoden ihre Vorteile, doch sie haben einen gemeinsamen Nachteil: Daten werden erst an einen bestimmten Ort kopiert und dann an andere verteilt. Dies benötigt erstens CPU-Ressourcen und stellt uns zweitens vor das Problem der Relevanz der übertragenen Daten (darauf gehen wir an dieser Stelle nicht ein).

Versuchen wir also, das Problem zu definieren:

Wir möchten eine Umgebung erschaffen, die den Zugriff auf Daten von Indikatoren ermöglicht, die an ein Diagramm angehängt sind, und die folgenden Eigenschaften aufweist:

  • kein Kopieren von Daten (und somit kein Relevanzproblem);
  • mindestmögliche Veränderung des Codes verfügbarer Methoden, wenn wir sie verwenden müssen;
  • MQL-Code ist vorzuziehen (natürlich müssen wir DLL nutzen, doch wir verwenden nur ein Dutzend Strings C++-Code).

Der Verfasser nutzte C++ Builder für die Erstellung von DLLs und die Client Terminals von MetaTrader 4 und MetaTrader 5. Die unten vorgestellten Quellcodes sind in MQL5 geschrieben, die MQL4-Codes sind an diesen Beitrag angehängt. Wir werden auf die wesentlichen Unterschiede zwischen ihnen eingehen.

2. Arrays

Zum Einstieg müssen wir uns der Theorie widmen, da wir mit Indikatorpuffern arbeiten werden und wissen müssen, wo sich ihre Daten im Speicher befinden. Diese Informationen sind nicht ordentlich dokumentiert.

Die dynamischen Arrays in MQL sind Strukturen mit variabler Größe, also ist es interessant, wie MQL das Problem mit der Neuzuweisung von Daten löst, wenn die Größe des Arrays gestiegen ist und hinter dem Array kein freier Speicher verfügbar ist. Dieses Problem kann auf zwei Arten gelöst werden:

  1. Neuzuweisung der neuen Daten in einem zusätzlichen Bereich des verfügbaren Speichers (und Speichern der Adressen aller Teile für dieses Array, beispielsweise mithilfe einer referenzierten Liste) oder
  2. Verschieben des gesamten Arrays in einen neuen Teil des Speichers, was ausreicht, um es zuzuweisen.

Die erste Methode führt zu weiteren Problemen, da wir in diesem Fall Datenstrukturen untersuchen müssen, die vom MQL-Compiler erstellt wurden. Die folgende Überlegung spricht für die zweite Variante (die langsamer ist): Wenn ein dynamisches Array an eine externe Funktion (an DLL) übergeben wird, erhält diese Funktion den Pointer zum ersten Datenelement. Andere Array-Elemente werden gleich hinter dem ersten Array-Element angeordnet.

Beim Übergeben als Referenz können die Daten des Arrays verändert werden. Das bedeutet, dass bei der Nutzung der ersten Methode das Problem entsteht, dass das gesamte Array in einen separaten Speicherbereich kopiert werden muss, um es an eine externe Funktion zu übertragen, um dann das Ergebnis zum Quell-Array hinzuzufügen, d. h. dieselben Aktionen durchzuführen, die in der zweiten Methode beinhaltet sind.

Obwohl diese Schlussfolgerung nicht 100 % logisch zwingend ist, kann sie dennoch als ziemlich zuverlässig betrachtet werden (dies wird auch durch die korrekte Arbeit unseres Produkts auf Basis dieser Idee bestätigt).

Deshalb nehmen wir an, dass die folgenden Aussagen wahr sind:

  • Daten dynamischer Arrays befinden sich zu jedem Zeitpunkt sequentiell, eines nach dem anderen, im Speicher und das Array kann in andere Teile des Computers verschoben werden, allerdings nur als Ganzes.
  • Die Adresse des ersten Array-Elements wird an die DLL-Bibliothek übergeben, wenn ein dynamisches Array als Parameter per Referenz an eine externe Funktion übergeben wird.

Weitere Annahmen: Mit Zeitpunkt meinen wir Zeitpunkte, zu denen die OnCalculate()-Funktion (in MQL4: start()) des entsprechenden Indikators aufgerufen wird. Zusätzlich nehmen wir der Einfachheit halber an, dass unsere Datenpuffer die gleichen Dimensionen und den Typ double[] haben.

3. Sine Qua Non

MQL unterstützt keine Pointer (mit Ausnahme der sogenannten Objekt-Pointer, die keine Pointer im herkömmlichen Sinne sind), wie es mehrfach von Repräsentanten von MetaQuotes Software betont wurde. Überprüfen wir also, ob das stimmt.

Was ist ein Pointer? Es ist nicht nur ein Identifikator mit einem Asterisk, sondern die Adresse einer Zelle im Computerspeicher. Und was ist die Zellenadresse? Es ist die sequentielle Nummer ab einem festgelegten Startpunkt. Und zum Schluss, was ist eine Nummer? Das ist eine ganze Zahl, die einer Zelle des Computerspeichers entspricht. Und warum können wir mit einem Pointer nicht wie mit einer ganzen Zahl arbeiten? Doch, das können wir, da MQL-Programme problemlos mit ganzen Zahlen arbeiten können!

Aber wie kann ein Pointer in eine ganze Zahl umgewandelt werden? Die Dynamic Link Library wird uns dabei helfen, genauer gesagt die Type-Casting-Möglichkeiten von C++. Da C++-Pointer Vier-Byte-Daten sind, ist es in unserem Fall praktisch, int als solchen Vier-Byte-Datentyp zu nutzen.

Das Zeichenbit ist unwichtig und wir werden nicht darauf eingehen (falls es gleich 1 ist, bedeutet dies nur, dass die ganze Zahl negativ ist). Wichtig ist, dass wir alle Pointer-Bits unverändert beibehalten. Wir können natürlich vorzeichenlose ganze Zahlen nutzen, doch es wäre besser, wenn die Codes für MQL5 und MQL4 ähnlich sind, da MQL4 keine vorzeichenlosen ganzen Zahlen unterstützt.

Also:

extern "C" __declspec(dllexport) int __stdcall GetPtr(double *a)
{
        return((int)a);
}

Nun haben wir eine Variable des Typen long mit dem Wert der Startadresse des Arrays! Nun müssen wir lernen, wie der Wert des i. Array-Elements gelesen wird:

extern "C" __declspec(dllexport) double __stdcall GetValue(int pointer,int i)
{
        return(((double*) pointer)[i]);
}

...und den Wert aufschreiben (das ist in unserem Fall nicht wirklich notwendig, aber dennoch...):

extern "C" __declspec(dllexport) void __stdcall SetValue(int pointer,int i,double value)
{
        ((double*) pointer)[i]=value;
}

Das war's. Nun können wir mit Pointern in MQL arbeiten.

4. Wrapping

Wir haben den Kern des Systems erschaffen. Nun müssen wir es auf die praktische Nutzung in MQL-Programmen vorbereiten. Freunde der Ästhetik müssen sich aber nicht übergangen fühlen, es wird noch weitere Themen geben.

Es gibt eine Milliarde Arten, dies zu tun. Wir wählen die hier dargelegte. Denken wir daran, dass das Client Terminal mit Global Variables über eine spezielle Funktion für den Datenaustausch zwischen separaten MQL-Programmen verfügt. Ihre natürlichste Nutzungsart ist die Speicherung von Pointern zu Indikatorpuffern, auf die wir zugreifen werden. Wir betrachten diese Variablen als Tabelle von Deskriptoren. Jeder Deskriptor hat einen durch einen String dargestellten Namen wie zum Beispiel:

String-Identifikator#Puffernummer#Symbol#Zeitraum#Pufferlänge#Indizierungsrichtung#zufällige_Zahl

Sein Wert entspricht der ganzzahligen Darstellung des entsprechenden Puffer-Pointers im Computerspeicher.

Einige Details über die Deskriptorfelder.

  • String-Identifikator– beliebiger String (beispielsweise Name des Indikators, häufig genutzte Variable short_name usw.); wird für die Suche des benötigten Pointers verwendet. Wir nehmen an, dass ein Indikator die Deskriptoren mit gleichem Identifikator registrieren wird und sie mithilfe des nachfolgenden Felds unterscheiden wird:
  • Puffernummer – wird zur Unterscheidung der Puffer verwendet;
  • Pufferlänge– wir müssen die Grenzen überwachen, ansonsten kann es zur Absturz des Terminals und zum Blue Screen of Death kommen;
  • Symbol, Zeitraum– Symbol und Zeitraum für die Festlegung des Diagrammfensters;
  • Indizierungsrichtung– legt die Richtung der Anordnung der Array-Elemente fest: 0: normal, 1: umgekehrt (AS_SERIES-Flag ist true);
  • zufällige_Zahl – wird verwendet, wenn es im Client Terminal mehrere Kopien eines Indikators mit verschiedenen Fenstern oder verschiedenen Parametersätzen gibt (sie können die gleichen Werte für das erste und zweite Feld festlegen, deshalb brauchen wir eine Möglichkeit, zwischen ihnen zu unterscheiden). Das ist vielleicht nicht die beste Lösung, doch sie funktioniert.

Als Erstes brauchen wir Funktionen, um den Deskriptor zu registrieren und zu löschen. Sehen Sie sich den ersten String der Funktion an: Der Aufruf der Funktion UnregisterBuffer() ist erforderlich, um alte Deskriptoren in der Liste globaler Variablen zu löschen.

Die Puffergröße wird bei jedem neuen Balken um 1 erhöht, also müssen wir RegisterBuffer() aufrufen. Hat sich die Puffergröße geändert, wird der neue Deskriptor in der Tabelle erstellt (die Information über seine Größe ist in seinem Namen enthalten), der alte bleibt in der Tabelle. Deshalb wird die Funktion verwendet.

void RegisterBuffer(double &Buffer[], string name, int mode) export
{
   UnregisterBuffer(Buffer);                    //first delete the variable just in case
   
   int direction=0;
   if(ArrayGetAsSeries(Buffer)) direction=1;    //set the right ordering_direction

   name=name+"#"+mode+"#"+Symbol()+"#"+Period()+"#"+ArraySize(Buffer)+"#"+direction;
   int ptr=GetPtr(Buffer);                      // get the buffer pointer

   if(ptr==0) return;
   
   MathSrand(ptr);                              //it's convenient to use the pointer value instead
                                                //of the current time for the random numbers base 
   while(true)
   {
      int rnd=MathRand();
      if(!GlobalVariableCheck(name+"#"+rnd))    //check for unique name - we assume that 
      {                                         //nobody will use more RAND_MAX buffers :)
         name=name+"#"+rnd;                     
         GlobalVariableSet(name,ptr);           //and write it to the global variable
         break;
      }
   }   
}
void UnregisterBuffer(double &Buffer[]) export
{
   int ptr=GetPtr(Buffer);                      //we will register by the real address of the buffer
   if(ptr==0) return;
   
   int gt=GlobalVariablesTotal();               
   int i;
   for(i=gt-1;i>=0;i--)                         //just look through all global variables
   {                                            //and delete our buffer from all places
      string name=GlobalVariableName(i);        
      if(GlobalVariableGet(name)==ptr)
         GlobalVariableDel(name);
   }      
}

Detaillierte Kommentare wären an dieser Stelle überflüssig. Wir halten nur fest, dass die erste Funktion einen neuen Deskriptor im oben aufgeführten Format in der Liste globaler Variablen erstellt. Die zweite durchsucht alle globalen Variablen und löscht alle Variablen und Deskriptoren, deren Wert dem des Pointers entspricht.

Betrachten wir nun die zweite Aufgabe: das Abrufen der Daten aus dem Indikator. Bevor wir den direkten Zugriff auf die Daten implementieren, müssen wir zuerst einen entsprechenden Deskriptor finden. Das geschieht mithilfe der folgenden Funktion. Ihr Algorithmus sieht so aus: Wir gehen durch alle globalen Variablen und prüfen sie auf das Vorhandensein von Feldwerten, die im Deskriptor angegeben sind.

Bei einem Treffer fügen wir den Namen zum Array hinzu, das als letzter Parameter übergeben wird. Als Ergebnis gibt die Funktion alle Speicheradressen aus, die die Suchkriterien erfüllen. Der ausgegebene Wert ist die Menge der gefundenen Deskriptoren.

int FindBuffers(string name, int mode, string symbol, int period, string &buffers[]) export
{
   int count=0;
   int i;
   bool found;
   string name_i;
   string descriptor[];
   int gt=GlobalVariablesTotal();

   StringTrimLeft(name);                                    //trim string from unnecessary spaces
   StringTrimRight(name);
   
   ArrayResize(buffers,count);                              //reset size to 0

   for(i=gt-1;i>=0;i--)
   {
      found=true;
      name_i=GlobalVariableName(i);
      
      StringExplode(name_i,"#",descriptor);                 //split string to fields
      
      if(StringFind(descriptor[0],name)<0&&name!=NULL) found=false; //check each field for the match
                                                                    //condition
      if(descriptor[1]!=mode&&mode>=0) found=false;
      if(descriptor[2]!=symbol&&symbol!=NULL) found=false;
      if(descriptor[3]!=period&&period>0) found=false;
      
      if(found)
      {
         count++;                                           //conditions met, add it to the list
         ArrayResize(buffers,count);
         buffers[count-1]=name_i;
      }
   }
   
   return(count);
}

Wie wir im Code der Funktion sehen, können einige der Suchbedingungen ausgelassen werden. Übergeben wir beispielsweise NULL als Namen, wird die Prüfung des String-Identifikators ausgelassen. Das Gleiche gilt für die anderen Felder:  mode<0, symbol:=NULL oder period<=0. Damit wird eine Ausweitung der Suchoptionen in der Tabelle der Deskriptoren ermöglicht.

Beispielsweise können Sie alle Moving-Average-Indikatoren in allen Diagrammfenstern suchen oder nur in EURUSD-Fenstern mit Zeitraum M15 usw. Zusätzliche Bemerkung: Die Prüfung des String-Identifikators wird durch die Funktion StringFind() anstatt der strikten Gleichheitsprüfung durchgeführt. Dies geschieht, um die Suche nach einem Teil des Deskriptors zu ermöglichen (sagen wir, wenn mehrere Indikatoren einen String des Typen "MA(xxx)" einstellen, kann die Suche mithilfe des Unterstrings "MA" durchgeführt werden; als Ergebnis erhalten wir alle registrierten Moving Averages).

Außerdem haben wir die Funktion StringExplode(string s, string separator, string &result[]) verwendet. Diese teilt den angegebenen String s mithilfe des Separators in Unterstrings auf und schreibt die Ergebnisse in das Array result.

void StringExplode(string s, string separator, string &result[])
{
   int i,pos;
   ArrayResize(result,1);
   
   pos=StringFind(s,separator); 
   if(pos<0) {result[0]=s;return;}
   
   for(i=0;;i++)
   {
      pos=StringFind(s,separator); 
      if(pos>=0)
      {
         result[i]=StringSubstr(s,0,pos);
         s=StringSubstr(s,pos+StringLen(separator));
      }
      else break;
      ArrayResize(result,ArraySize(result)+1);
   }
}

Nun, da wir über die Liste aller notwendigen Deskriptoren verfügen, können wir Daten aus dem Indikator abrufen:

double GetIndicatorValue(string descriptor, int shift) export
{
   int ptr;
   string fields[];
   int size,direction;
   if(GlobalVariableCheck(descriptor)>0)               //check that the descriptor is valid
   {                
      ptr = GlobalVariableGet(descriptor);             //get the pointer value
      if(ptr!=0)
      {
         StringExplode(descriptor,"#",fields);         //split its name to fields
         size = fields[4];                             //we need the current array size
         direction=fields[5];                                 
         if(direction==1) shift=size-1-shift;          //if the ordering_direction is reverse
         if(shift>=0&&shift<size)                      //check for its validity - to prevent crashes
            return(GetValue(MathAbs(ptr),shift));      //ok, return the value
      }   
   } 
   return(EMPTY_VALUE);                                //overwise return empty value 
}

Wie Sie sehen, wird damit die GetValue()-Funktion aus der DLL verpackt. Der Deskriptor muss auf Gültigkeit der Grenzen des Arrays geprüft werden. Dabei muss auch die Pufferreihenfolge des Indikators berücksichtigt werden. Falls die Funktion fehlschlägt, gibt sie EMPTY_VALUE aus.

Die Anpassung des Indikatorpuffers ist ähnlich:

bool SetIndicatorValue(string descriptor, int shift, double value) export
{
   int ptr;
   string fields[];
   int size,direction;
   if(GlobalVariableCheck(descriptor)>0)               //check for its validity
   {                
      ptr = GlobalVariableGet(descriptor);             //get descriptor value
      if(ptr!=0)
      {
         StringExplode(descriptor,"#",fields);         //split it to fields
         size = fields[4];                             //we need its size
         direction=fields[5];                                 
         if(direction==1) shift=size-1-shift;          //the case of the inverse ordering
         if(shift>=0&&shift<size)                      //check index to prevent the crash of the client terminal
         {
            SetValue(MathAbs(ptr),shift,value);
            return(true);
         }   
      }   
   }
   return(false);
}

Wenn alle Werte korrekt sind, wird die SetValue()-Funktion aus DLL aufgerufen. Der ausgegebene Wert entspricht dem Ergebnis der Anpassung: true bei Erfolg und false im Fall eines Fehlers.

5. Prüfung der Funktionsweise

Probieren wir nun alles zusammen aus. Als Ziel nutzen wir den Indikator Average True Range (ATR) aus dem Standardpaket und zeigen, was wir in seinem Code anpassen müssen, um das Kopieren seiner Werte in andere Indikatorfenster zu ermöglichen. Außerdem werden wir die Datenmodifizierung seiner Pufferdaten testen.

Zuerst müssen wir einige Codezeilen zur OnCalculate()-Funktion hinzufügen:

//+------------------------------------------------------------------+

//| Average True Range                                               |
//+------------------------------------------------------------------+
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 &TickVolume[],
                const long &Volume[],
                const int &Spread[])
  {
   if(prev_calculated!=rates_total)
      RegisterBuffer(ExtATRBuffer,"ATR",0);
…

Wie wir sehen können, muss die Registrierung des neuen Puffers jedes Mal durchgeführt werden, wenn neue Balken hinzukommen. Dies geschieht im (normalen) Verlauf der Zeit oder beim Herunterladen zusätzlicher historischer Daten.

Außerdem müssen wir Deskriptoren aus der Tabelle löschen, nachdem die Ausführung des Indikators vorbei ist. Deshalb müssen wir etwas Code zum Ereignis-Handler deinit hinzufügen (denken Sie allerdings daran, dass er void ausgeben und über einen Eingabeparameter const int reason verfügen muss):

void OnDeinit(const int reason)
{
   UnregisterBuffer(ExtATRBuffer);
}

Hier sehen Sie unseren angepassten Indikator im Diagrammfenster des Client Terminals (Abb. 1) und seinen Deskriptor in der Liste globaler Variablen (Abb. 2):

 

Abb. 1. Average True Range

Abb. 2 In der Liste globaler Variablen des Client Terminals erstellter Deskriptor

Abb. 2 In der Liste globaler Variablen des Client Terminals erstellter Deskriptor

Als Nächstes müssen wir auf die ATR-Daten zugreifen. Erstellen wir einen neuen Indikator (wir nennen ihn test) mit dem folgenden Code: 

//+------------------------------------------------------------------+
//|                                                         test.mq5 |
//|                                             Copyright 2009, alsu |
//|                                                 alsufx@gmail.com |
//+------------------------------------------------------------------+
#property copyright "2009, alsu"
#property link      "alsufx@gmail.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1

#include <exchng.mqh>

//---- plot ATRCopy
#property indicator_label1  "ATRCopy"
#property indicator_type1   DRAW_LINE
#property indicator_color1  Red
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- indicator buffers
double ATRCopyBuffer[];

string atr_buffer;
string buffers[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,ATRCopyBuffer,INDICATOR_DATA);
//---
   return(0);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//---
   int found=FindBuffers("ATR",0,NULL,0,buffers);   //search for descriptors in global variables
   if(found>0) atr_buffer=buffers[0];               //if we found it, save it to the atr_buffer
   else atr_buffer=NULL;
   int i;
   for(i=prev_calculated;i<rates_total;i++)
   {
      if(atr_buffer==NULL) break;
      ATRCopyBuffer[i]=GetIndicatorValue(atr_buffer,i);  //now it easy to get data
      SetIndicatorValue(atr_buffer,i,i);                 //and easy to record them
   }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Wie wir sehen können, ist es nicht schwer. Rufen Sie die Deskriptorliste durch einen Unterstring ab und lesen Sie die Werte des Indikatorpuffers mithilfe unserer Funktionen. Zuletzt schreiben wir Blödsinn hinein (in unserem Beispiel schreiben wir die Werte, die dem Index der Array-Elemente entsprechen).

Nun führen wir den Indikator test aus (unser Ziel ATR sollte immer noch an das Diagramm angehängt sein). Wie in Abb. 3 ersichtlich, befinden sich die Werte des ATR im unteren Unterfenster (in unserem Testindikator). Stattdessen sehen wir eine gerade Linie. Tatsächlich ist er mit Werten gefüllt, die den Array-Indizes entsprechen.

 

Abb. 3 Ergebnis der Ausführung von Test.mq5

Eine kleine Bewegung des Handgelenks – das ist alles :)

6. Abwärtskompatibilität

Der Verfasser hat versucht, eine in MQL5 geschriebene Bibliothek zu erstellen, die so nahe wie möglich an den MQL4-Standards ist. Es werden also nur einige wenige Anpassungen benötigt, damit sie in MQL4 nutzbar ist.

Insbesondere sollten Sie das Schlüsselwort export entfernen, das nur in MQL5 erscheint, und den Code an der Stelle anpassen, an der wir indirektes Type-Casting verwendet haben (MQL4 ist nicht so flexibel). Im Großen und Ganzen ist die Nutzung von Funktionen in Indikatoren absolut identisch. Die Unterschiede sind die Methode der Steuerung des Eingangs neuer Balken und die Historie.

Fazit

Ein paar Worte zu den Vorteilen.

Es scheint, als wäre die Verwendung der in diesem Beitrag beschriebenen Methode nicht ausschließlich auf ihren ursprünglichen Zweck beschränkt. Zusätzlich zur Erstellung schneller kaskadenförmiger Indikatoren kann die Bibliothek, einschließlich ihrer Historien-Testfälle, auch auf Expert Advisors angewendet werden. Der Verfasser kann sich weitere Anwendungsbereiche nur schwer vorstellen, doch ich glaube, dass die Benutzer gerne in dieser Richtung arbeiten werden.

Ich glaube, dass die folgenden technischen Details erforderlich sein können:

  • Verbesserung der Tabelle von Deskriptoren (insbesondere die Identifizierung von Fenstern);
  • Entwicklung einer zusätzlichen Tabelle für die Erstellungsreihenfolge der Deskriptoren. Dies kann für die Stabilität der Handelssysteme von Bedeutung sein.

Ebenso freue ich mich über weitere Vorschläge zur Verbesserung der Bibliothek.

Alle erforderlichen Dateien sind an den Artikel angehängt. Vorgestellt werden die Codes für MQL5 und MQL4 sowie der Quellcode der Bibliothek exchng.dll. Die Ablageorte der Dateien entsprechen der Ordnerstruktur des Client Terminals.

Haftungsausschluss

Die in diesem Beitrag beschriebenen Materialien beschreiben Programmiertechniken, die bei sorgloser Verwendung die auf Ihrem Computer ausgeführte Software beschädigen können. Die Verwendung der angehängten Dateien geschieht auf eigene Gefahr und kann zu bislang unbekannten Nebeneffekten führen.

Danksagungen

Der Autor hat die im Forum der MQL4.Community (http://forum.mql4.com) vorgeschlagenen Probleme und Ideen ihrer Benutzer verwendet: igor.senych, satop, bank, StSpirit, TheXpert, jartmailru, ForexTools, marketeer, IlyaA – sorry, falls die Liste unvollständig ist.

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/19

Beigefügte Dateien |
cpp.zip (0.36 KB)
mql4.zip (39.29 KB)
mql521x.zip (46.87 KB)

Andere Artikel von diesem Autor

Letzte Kommentare | Zur Diskussion im Händlerforum (1)
Marcel Fitzner
Marcel Fitzner | 20 Mai 2020 in 14:20

Very good article. Very promising speed increase, but I have a problem:

Not a 64 bit version

exchng.dll is not 64-bit version.

Looks like the C++-Wrapper-dll is outdated. Would it be possible for you to provide the source code or the MS Visual project files, so we can update the version?

Zeichnen von Indikatoremissionen in MQL5 Zeichnen von Indikatoremissionen in MQL5
In diesem Beitrag behandeln wir die Emission von Indikatoren, eine neuen Herangehensweise an die Marktforschung. Die Berechnung von Emissionen basiert auf den Schnittpunkten verschiedener Indikatoren: Nach jedem Tick erscheinen immer mehr Punkte mit unterschiedlichen Farben und Formen. Sie formen zahlreiche Cluster wie Nebel, Wolken, Bahnen, Linien, Bögen usw. Diese Formen helfen uns beim Finden der unsichtbaren Kräfte, die die Bewegung von Marktpreisen beeinflussen.
Datenaustausch: Erstellen einer DLL für MQL5 in 10 Minuten Datenaustausch: Erstellen einer DLL für MQL5 in 10 Minuten
Nicht allzu viele Entwickler erinnern sich noch daran, wie eine simple DLL geschrieben wird und was die Besonderheiten der unterschiedlichen Systemanbindungen sind. Anhand mehrerer Beispiele werde ich versuchen, Ihnen den gesamten Prozess zur Erstellung einer simplen DLL in 10 Minuten zu zeigen, sowie einige technische Einzelheiten der Umsetzung unserer Anbindung zu besprechen. Ich zeige Ihnen den Prozess der DLL-Erstellung in Visual Studio Schritt für Schritt mit Beispielen für den Austausch unterschiedlicher Typen von Variablen (Zahlen, Arrays, Strings usw.). Außerdem erkläre ich, wie Sie Ihr Client Terminal vor Abstürzen in benutzerdefinierten DLLs schützen können.
Exportieren von Angeboten aus MetaTrader 5 in .NET-Anwendungen mithilfe von WCF-Services Exportieren von Angeboten aus MetaTrader 5 in .NET-Anwendungen mithilfe von WCF-Services
Möchten Sie den Export von Angeboten aus MetaTrader 5 in Ihre eigene Anwendung aktualisieren? Mit der MQL5-DLL-Verbindung können Sie solche Lösungen erschaffen! Dieser Beitrag zeigt Ihnen eine der Möglichkeiten zum Exportieren von Angeboten aus MetaTrader 5 in .NET-Anwendungen. Für mich war es interessanter, sinnvoller und einfacher, den Export von Angeboten mithilfe dieser Plattform umzusetzen. Leider wird .NET von Version 5 weiterhin nicht unterstützt, sodass wir wie in den alten Tagen win32 dll mit .NET-Unterstützung als Zwischenlösung nutzen werden.
Das Preishistogramm (Marktprofil) und seine Umsetzung in MQL5 Das Preishistogramm (Marktprofil) und seine Umsetzung in MQL5
Das Marktprofil wurde von Peter Steidlmayer, einem wahrhaft brillanten Denker, entwickelt. Er schlug die alternative Darstellung von Informationen &uuml;ber &quot;horizontale&quot; und &quot;vertikale&quot; Marktbewegungen vor, die eine v&ouml;llig neue Reihe von Modellen erm&ouml;glicht. Er stellte die These auf, dass dem Markt ein Puls oder ein grundlegendes Muster namens Zyklus des Gleichgewichts und Ungleichgewichts zugrunde liegen muss. In diesem Beitrag werde ich auf das Preishistogramm eingehen, ein vereinfachtes Modell des Marktprofils, und beschreibe seine Umsetzung in MQL5.