English Русский 中文 Español 日本語 Português
Tipps von einem professionellen Programmierer (Teil II): Speichern und Austauschen von Parametern zwischen einem Expert Advisor, Skripten und externen Programmen

Tipps von einem professionellen Programmierer (Teil II): Speichern und Austauschen von Parametern zwischen einem Expert Advisor, Skripten und externen Programmen

MetaTrader 5Beispiele | 11 Juni 2021, 08:45
761 0
Malik Arykov
Malik Arykov

Inhalt


Einführung

In diesem Artikel werden wir Parameter besprechen, die nach einem Terminal-Neustart (Shutdown) wiederhergestellt werden können. Alle Beispiele sind echte funktionierende Codesegmente aus meinem Cayman-Projekt.


Wo die Parameter gespeichert werden


Parameter-Beispiele

  • Zeit des Balkens Null. Zum Beispiel bei der Erkennung eines Kerzenmusters ist es logisch, dieses einmalig, nach dem Auftauchen eines neuen Balkens auf einem bestimmten Zeitrahmen, auszuwerten.
  • Parameter der Handelsebene. Sie können z.B. eine Handelsebene ("trading level") auswählen und ein Skript verwenden, um die Zeit und die Größe eines Deals festzulegen, der im Falle eines Level-Ausbruchs geöffnet werden soll. Das Skript übergibt Parameter an den Expert Advisor. Der Expert Advisor erstellt einen Analyzer für die Ebenen. Der Analyzer "schaltet" sich erst nach dem Auftauchen eines neuen Balkens auf dem angegebenen Zeitrahmen ein.
  • Nutzereinstellungen. Dazu gehören Farbe, Handelsregeln, Zeichenmethoden und andere Parameter. Offensichtlich sollten solche Parameter einmalig installiert werden, z.B. in einer Datei mit allen Einstellungen.
Wo sollen diese Parameter gespeichert werden?
  • Die globalen Variablen des Terminals
  • Grafische Objekte
  • Auftragskommentare
  • Textdateien
Speicher
Typ Geltungsbereich Laufzeit
Die globalen Variablen des Terminals double Alle Charts 4 Wochen nach dem letzten Zugriff
Grafische Objekte Jeder, Zeichenkette <= 63 Zeichen Aktueller Chart Laufzeit des Charts
Auftragskommentare Zeichenketten <= 23 Zeichen Alle Charts Laufzeit des Terminals
Textdateien Jeder, unbeschränkt Alle Charts Existenzdauer der Datei


Die globalen Variablen des Terminals

Die globalen Variablen des Terminals sind von jedem Chart aus verfügbar. Ihr Umfang kann eingeschränkt werden, indem zusätzliche Komponenten an den Variablennamen angehängt werden, z. B. ChartId, Symbol oder Zeitraum. Was nicht geändert werden kann, ist der Variablentyp. Sie können den Text nicht speichern.

Es gibt einen Lifehack: Integer-Werte packen/entpacken. Wie Sie wissen, nimmt double 8 Bytes (64 Bit) ein. Schauen Sie sich das folgende Beispiel an: Es zeigt, wie man mehrere Integer-Werte in einer Variablen speichern kann. Das Wichtigste dabei ist, die Bitgröße ihrer Maximalwerte zu bestimmen.

// -------------------------------------------------------------------------------------|
// Beispiel für das Packen/Entpacken von Integer-Werten in/aus einer globalen Variablen |
// unter Verwendung bitweiser Operationen                                               |
// -------------------------------------------------------------------------------------|
void OnStart() {
    
    int     value10 = 10; // max = 255 (8 bits)
    int     value20 = 300; // max = 65535 (16 bits)
    bool    value30 = true; // max = 1 (1 bit)
    
    // die Werte in 25 Bits packen (8+16+1)
    // 39 Bits (64-25) bleiben frei
    ulong packedValue = 
        (value10 << 17) + // Leerzeichen reservieren (16+1) für value20, value30
        (value20 << 1) + // Leerzeichen reservieren (1) für value30
        value30;
    
    // Sichern der globalen Variablen
    string nameGVar = "temp";
    GlobalVariableSet(nameGVar, packedValue);
    
    // Lesen der globalen Variablen
    packedValue = (ulong)GlobalVariableGet(nameGVar);
    
    // Entpacken der Werte
    // 0xFF, 0xFFFF, 0x1 - bit-Maske des jew. größten Wertes
    int value11 = (int)((packedValue >> 17) & 0xFF);
    int value21 = (int)((packedValue >> 1) & 0xFFFF);
    bool value31 = (bool)(packedValue & 0x1);
    
    // Vergleich der Werte
    if (value11 == value10 && value21 == value20 && value31 == value30) Print("OK");
    else PrintFormat("0x%X / 0x%X /0x%X / 0x%X", packedValue, value11, value21, value31);
}



Grafische Objekte

Können wir die Parameter von Skripts in grafischen Objekten speichern? Warum nicht. Setzen Sie die Objekteigenschaft OBJPROP_PRICE = 0 — in diesem Fall ist das Objekt visuell "ausgeblendet", ist aber von einem Programm zugänglich. Zur Sicherheit kann ein solches Objekt in einer Chart-Vorlage gespeichert werden. Die Logik des Parameterzugriffs ist wie folgt: wenn ein Objekt vorhanden ist, werden die Parameter extrahiert; wenn kein Objekt vorhanden ist, werden die Standardwerte gesetzt.


Auftragskommentare

Die maximale Länge der Kommentare für Handelsaufträge ist auf 23 Zeichen begrenzt. Was kann in einem Kommentar gespeichert werden? Zum Beispiel: SOP/H1/SS/C2/Br/Br/Br. Wobei (von links nach rechts)

  • SOP - Auftragsabsender (SOP - das Skript SendOrderByPlan)
  • H1  — Zeitrahmen der Ordererstellung (H1)
  • SS  — Ordertyp (SS - Sell Stop)
  • C2  — Algorithmus zum Schließen der Order
  • Br  — D1-Trend (Br - Bear, abwärts)
  • Br  — H4-Trend (Br - Bear, abwärts)
  • Br  — Trend im Zeitrahmen der Ordererstellung (Br - Bear, abwärts)

Warum brauchen wir das? Diese Daten können zum Beispiel für die Analyse von Geschäften verwendet werden. Ich verwende sie folgendermaßen: Wenn eine Pending Order ausgelöst wird, extrahiere ich den Wert des Schließalgorithmus und erstelle einen virtuellen Stop-Analyser AnalyserVirtSL, der dann unter bestimmten Bedingungen die Position schließt.


Textdateien

Dies ist vielleicht die zuverlässigste und universellste Art, Wiederherstellungsparameter zu speichern. Sie können einmalig Zugriffsklassen einrichten und diese dann immer und überall verwenden, wenn immer Sie sie benötigen.


Anwendungseinstellungen

Teil der Einstellungsdatei AppSettings.txt:

# ---------------------------------------------------------------------------
# Expert Advisor und Skript-Einstellungen
# Dateikodierung = UCS-2 LE mit BOM (erforderlich!!!) // es ist Unicode
# ---------------------------------------------------------------------------
TimeEurWinter = 10:00 # Beginnzeit des europäischen Handels in der Winterzeit (Server-Zeit)
TimeEurSummer = 09:00 # Beginnzeit des europäischen Handels in der Sommerzeit (Server-Zeit)
ColorSessionEur = 224,255,255 # Farbe der europäischen Handelszeit
ColorSessionUsd = 255,240,245 # Farbe der amerikanischen Handelszeit
NumberColorDays = 10 # die Anzahl der hervorzuhebenden Tage (Handelszeiten)



Die Klasse AppSettings.mqh

#property copyright "Copyright 2020, Malik Arykov"
#property link      "malik.arykov@gmail.com"
#property strict

#include <Cayman/Params.mqh>

// Parameternamen der Applikation
#define APP_TIME_EUR_SUMMER "TimeEurSummer"
#define APP_TIME_EUR_WINTER "TimeEurWinter"
#define APP_TIME_TRADE_ASIA "TimeTradeAsia"
#define APP_COLOR_SESSION_EUR "ColorSessionEur"
#define APP_COLOR_SESSION_USD "ColorSessionUsd"
#define APP_NUMBER_COLOR_DAYS "NumberColorDays"

// -------------------------------------------------------------------------------------|
// Allgemeine Einstellungen für den Expert Advisor und die Skripts                      |
// -------------------------------------------------------------------------------------|
class AppSettings {
private:
    Params  *m_params;
public:
    // Einzutragen in die Datei AppSettings.txt
    string  TimeEurSummer; // Beginnzeit des europäischen Handels in der Sommerzeit
    string  TimeEurWinter; // Beginnzeit des europäischen Handels in der Winterzeit
    string  TimeTradeAsia; // Endzeit für den asiatischen Korridorhandel
    color   ColorSessionEur; // Farbe der europäischen Handelszeit
    color   ColorSessionUsd; // Farbe der amerikanischen Handelszeit
    int     NumberColorDays; // Anzahl der hervorzuhebenden Tage

    // bestimmt vom Programm
    string  PeriodTrends; // Zeitrahmen (D1,H4) zur Trendberechnung
    string  TradePlan; // Handelsrichtung (kurzer Plan)
    bool    IsValid; // Parametergültigkeit
    
    // Methoden
    AppSettings();
    ~AppSettings() { delete m_params; };
    void Dump(string sender);
};

// -------------------------------------------------------------------------------------|
// Konstruktor                                                                          |
// -------------------------------------------------------------------------------------|
AppSettings::AppSettings() {

    IsValid = true;
    m_params = new Params();
    m_params.Load(PATH_APP_SETTINGS);
    if (m_params.Total() == 0) {
        PrintFormat("%s / ERROR: Invalid file / %s", __FUNCTION__, PATH_APP_SETTINGS);
        IsValid = false;
        return;
    }
        
    TimeEurWinter = m_params.GetValue(APP_TIME_EUR_WINTER);
    TimeEurSummer = m_params.GetValue(APP_TIME_EUR_SUMMER);
    TimeTradeAsia = m_params.GetValue(APP_TIME_TRADE_ASIA);
    ColorSessionEur = StringToColor(m_params.GetValue(APP_COLOR_SESSION_EUR));
    ColorSessionUsd = StringToColor(m_params.GetValue(APP_COLOR_SESSION_USD));
    NumberColorDays = (int)StringToInteger(m_params.GetValue(APP_NUMBER_COLOR_DAYS));
}

// -------------------------------------------------------------------------------------|
// Ausdruck der Parametereinstellungen                                                  | 
// -------------------------------------------------------------------------------------|
void AppSettings::Dump(string sender) {
    PrintFormat("sender=%s / %s", sender, PATH_APP_SETTINGS);
    PrintFormat("%s = %s", APP_TIME_EUR_WINTER, TimeEurWinter);
    PrintFormat("%s = %s", APP_TIME_EUR_SUMMER, TimeEurSummer);
    PrintFormat("%s = %s", APP_TIME_TRADE_ASIA, TimeTradeAsia);
    PrintFormat("%s = %s / %s", APP_COLOR_SESSION_EUR, ColorToString(ColorSessionEur), ColorToString(ColorSessionEur, true));
    PrintFormat("%s = %s / %s", APP_COLOR_SESSION_USD, ColorToString(ColorSessionEur), ColorToString(ColorSessionEur, true));
    PrintFormat("%s = %i", APP_NUMBER_COLOR_DAYS, NumberColorDays);
}




Charakteristika

Die Deklaration der Klasse AppSettings befindet sich in der Datei Uterminal.mqh, die über #include mit einem Expert Advisor und mit einem beliebigen Skript eingebunden wird.

extern AppSettings  *gAppSettings; // Anwendungseinstellungen

Mit dieser Lösung können Sie:

  • gAppSettings einmal an beliebiger Stelle initialisieren
  • gAppSettings in jeder Klasseninstanz verwenden (anstatt es als Parameter zu übergeben)


Parameter für die Analyse

Der Cayman Expert Advisor verwaltet verschiedene Analyzer wie z.B. AnalyzerTrend, AnalyserLevel, AnalyserVirtSL. Jeder Analyzer ist mit einem bestimmten Zeitrahmen verknüpft. Das bedeutet, dass der Analyzer nur gestartet wird, wenn ein neuer Balken auf dem angegebenen Zeitrahmen auftaucht. Analyzer-Beispiele werden in der Textdatei gespeichert, mit den Zeilen Key = Value. Zum Beispiel speichert der Analyzer für H4-Handelsebenen seine Parameter in der Datei Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt

  • Cayman — Projektname
  • Params — Unterverzeichnis mit den Parametern von Analyzer
  • 128968168864101576  — Chart ID // IntergerToString(ChartID())
  • exp_05_Lev607A160E_H4.txt  — der Name der Datei mit Analysatorparametern —
    • exp — Präfix
    • 05 — Typ von Analyzer
    • Lev607A160E  — der Name des Analyzers (Handelsebenen)
    • H4  — verfolgter Zeitrahmen.

Unten ist der Inhalt der Datei mit Kommentaren (die echte Datei hat keine Kommentare)

// Parameter der Handelsebene
nameObj=Lev607A160E // Name der Ebene
kindLevel=1 // Ebene Typ (1 - Widerstand)
riskValue=1.00 // Handelsvolumen bei Durchbruch der Ebene (1)
riskUnit=1 // Einheit für die Änderung des Geschäftsvolumens (1 - % der Mittel für die Marge)
algClose=2 // Algorithmus zum Schließen der Position (2 - zwei Korrekturbalken)
ticketNew=0 // Ticket einer Position, das bei Durchbruch der Ebene geöffnet wurde
ticketOld=0 // Ticket zum Schließen einer Position bei Ausbruch aus der Ebene
profits=0 // geplanter Gewinn in Punkten
losses=0 // geplanter Verlust in Punkten
// Parameter des Analyzer
symbol=EURUSD // Symbolname
period=16388 // Zeitrahmen (H4) für den Analyzer
time0Bar=1618603200 // Null-Bar-Zeit (sec)
typeAnalyser=5 // Analysator-Typ
colorAnalyser=16711935 // Farbe für Analyzer-Ergebnisse
resultAnalyser=Lev607A160E, H4, 20:00, RS // Analyzer-Ergebnisse


Es gibt eine Basisklasse Analyzer, die die Parameter eines beliebigen Analyzers speichern und wiederherstellen kann. Wenn ein Expert Advisor neu gestartet wird (z. B. nach einem Wechsel des Zeitrahmens), stellen die Parameter des Analyzers aus den entsprechenden Textdateien wieder her. Wenn der Zeitpunkt für einen neuen Balken noch nicht gekommen ist, wird die Analyse nicht neu gestartet. Die zum vorherigen Balken berechneten Analyzer-Ergebnisse (resultAnalyser, colorAnalyser) werden in den Kommentaren des Expert Advisors angezeigt.


Übergabe von Skriptparametern an einen Expert Advisor

Das Skript SetTradeLevel ermöglicht das Einstellen der Parameter einer Handelsebene. Ein Objekt (Gerade, Trendlinie oder Rechteck) wird im Chart ausgewählt. Das Skript SetTradeLevel findet das ausgewählte Objekt (Handelsebene) und setzt dessen Parameter.

Parameter des Skripts SetTradeLevel

Als Nächstes speichert das Skript die Parameter in der Datei Files\Cayman\Params\128968168864101576\exp_05_Lev607A160E_H4.txt und sendet den Befehl und den Pfad zur Datei über die Funktion SendCommand.

// -------------------------------------------------------------------------------------|
// Parameter der Handelsebenen an den Expert Advisor senden                             |
// -------------------------------------------------------------------------------------|
NCommand SendCommand() {

    // Laden der Parameter der Ebenen (wenn vorhanden)
    Params *params = new Params();
    string speriod = UConvert::PeriodToStr(_Period);
    params.Load(PREFIX_EXPERT, anaLevel, gNameLev, speriod);

    // Definieren der Befehle
    NCommand cmd = 
        (gKindLevel == levUnknown) ? cmdDelete :
        (params.Total() > 0) ? cmdUpdate :
        cmdCreate;

    // Sichern der Parameter
    params.Clear();
    params.Add(PARAM_NAME_OBJ, gNameLev);
    params.Add(PARAM_TYPE_ANALYSER, IntegerToString(anaLevel));
    params.Add(PARAM_PERIOD, IntegerToString(_Period));
    params.Add(PARAM_KIND_LEVEL, IntegerToString(gKindLevel));
    params.Add(PARAM_RISK_VALUE, DoubleToString(gRiskValue, 2));
    params.Add(PARAM_RISK_UNIT, IntegerToString(gRiskUnit));
    params.Add(PARAM_ALG_CLOSE, IntegerToString(gAlgClose));
    params.Add(PARAM_TICKET_OLD, IntegerToString(gTicketOld));
    params.Add(PARAM_PROFITS, IntegerToString(gProfits));
    params.Add(PARAM_LOSSES, IntegerToString(gLosses));
    params.Save();
    
    // Befehl an den Expert Advisor senden
    params.SendCommand(cmd);
    delete params;
    
    return cmd;
}


Die Funktion params.SendCommand(cmd) ist wie folgt:

// -------------------------------------------------------------------------------------|
// Befehl an den Expert Advisor senden                                                  |
// -------------------------------------------------------------------------------------|
void Params::SendCommand(NCommand cmd) {
    string nameObj = NAME_OBJECT_CMD;
    ObjectCreate(0, nameObj, OBJ_LABEL, 0, 0, 0);
    ObjectSetString(0, nameObj, OBJPROP_TEXT, m_path);
    ObjectSetInteger(0, nameObj, OBJPROP_ZORDER, cmd);
    ObjectSetInteger(0, nameObj, OBJPROP_TIMEFRAMES, 0);
}

Bei jedem Tick (OnTick) prüft der Expert Advisor über die Funktion CheckExpernalCommand() die Existenz des Objekts namens NAME_OBJECT_CMD. Wenn es existiert, werden der Befehl und der Pfad zur Datei mit den Analyzer-Parametern gelesen und das Objekt sofort gelöscht. Als Nächstes sucht der Expert Advisor anhand des Dateinamens nach einem laufenden Analyzer. Wenn cmd == cmdDelete, dann wird der Analyzer gelöscht. Wenn cmd == cmdUpdate, dann werden die Analyzerparameter aus der Datei aktualisiert. Wenn cmd == cmdNew, dann wird ein neuer Analyzer mit den Parametern aus der Datei erstellt.

Hier ist der vollständige Code der Klasse Params, die die Logik für die Arbeit mit Parameterdateien (Key=Value-Strings) kapselt.

#property copyright "Copyright 2020, Malik Arykov"
#property link      "malik.arykov@gmail.com"

#include <Arrays/ArrayString.mqh>
#include <Cayman/UConvert.mqh>
#include <Cayman/UFile.mqh>

// -------------------------------------------------------------------------------------|
// Parameterklasse (Schlüssel=Wert und Kommentaren nach #)                              |
// -------------------------------------------------------------------------------------|
class Params {
private:
    string  m_path; // Pfad zur Datei mit den Parametern
    NCommand m_cmd; // Befehl für den Expert Advisor
    CArrayString *m_items; // Array der Paare {Schlüssel=Wert}
    int Find(string key);
public:
    Params();
    ~Params() { delete m_items; };
    void Clear() { m_items.Clear(); };
    int Total() { return m_items.Total(); };
    string Path() { return m_path; };
    CArrayString *Items() { return m_items; };
    void Add(string line) { m_items.Add(line); };
    bool Add(string key, string value);
    string GetValue(string key);
    void Load(string prefix, int typeAnalyser, string nameObj, string speriod);
    void Load(string path);
    void Save();
    void SendCommand(NCommand cmd);
    NCommand TakeCommand();
    void Dump(string sender);
};

// -------------------------------------------------------------------------------------|
// Standard-Konstruktor                                                                 |
// -------------------------------------------------------------------------------------|
Params::Params() {
    m_items = new CArrayString();
}

// -------------------------------------------------------------------------------------|
// Hinzufügen eines Paares Schlüssel=Wert                                               | 
// -------------------------------------------------------------------------------------|
bool Params::Add(string key, string value) {

    int j = Find(key);
    string line = key + "=" + value;
    if (j >= 0) { // Aktualisieren
        m_items.Update(j, line);
        return false;
    }
    else { // Hinzufügen
        m_items.Add(line);
        return true;
    }
}

// -------------------------------------------------------------------------------------|
// Abrufen des Wertes gemäß dem Schlüssel                                               |
// -------------------------------------------------------------------------------------|
string Params::GetValue(string key) {

    // Finden des Schlüssels
    int j = Find(key);
    if (j < 0) return NULL; // no key
    
    // Prüfen des Trennzeichens
    string line = m_items.At(j);
    j = StringFind(line, "=");
    if (j < 0) { // nein =
        PrintFormat("%s / ERROR: Invalid string %s", __FUNCTION__, line);
        return NULL;
    }
    
    // Rückgabe des Wertes
    return UConvert::Trim(StringSubstr(line, j + 1));
}

// -------------------------------------------------------------------------------------|
// Finden des Parameterwerts nach dem Schlüssel                                         |
// -------------------------------------------------------------------------------------|
int Params::Find(string key) {

    int index = -1;
    for (int j = 0; j < m_items.Total(); j++) {
        if (StringFind(m_items.At(j), key) == 0) {
            index = j;
            break;
        }
    }
    return index;
}

// -------------------------------------------------------------------------------------|
// Laden der Parameter                                                                  |
// -------------------------------------------------------------------------------------|
void Params::Load(string prefix, int typeAnalyser, string nameObj, string speriod) {
    string nameFile = StringFormat("%s%02i_%s_%s.txt", prefix, typeAnalyser, nameObj, speriod);
    m_path = StringFormat("%s%s/%s", PATH_PARAMS, IntegerToString(ChartID()), nameFile);
    if (FileIsExist(m_path)) Load(m_path);
}

// -------------------------------------------------------------------------------------|
// Laden der Parameter                                                                  |
// -------------------------------------------------------------------------------------|
void Params::Load(string path) {
  
    m_path = path;
    if (!FileIsExist(m_path)) return;

    //PrintFormat("%s / %s", __FUNCTION__, m_path);
    string text = UFile::LoadText(m_path);
    if (text == NULL) return;
    
    // Aufspalten des Textes in Zeilen
    string line, lines[];
    int numLines = StringSplit(text, DLM_LINE, lines);
    for (int j = 0; j < numLines; j++) {
        line = lines[j];
        // Löschen des Kommentars
        int k = StringFind(line, "#");
        if (k == 0) continue; // die ganze Zeile ist ein Kommentar
        if (k > 0) line = StringSubstr(line, 0, k);
        // Hinzufügen einer nicht-leeren Zeichenkette
        if (line != "") m_items.Add(line);
    }
}

// -------------------------------------------------------------------------------------|
// Sichern der Parameter                                                                |
// -------------------------------------------------------------------------------------|
void Params::Save() {

    string text = "";
    for (int j = 0; j < m_items.Total(); j++) {
        text += m_items.At(j) + "\n";
    }
    // Neuschreiben der bestehenden Datei
    UFile::SaveText(text, m_path, true);
}

// -------------------------------------------------------------------------------------|
// Befehl an den Expert Advisor senden                                                  | 
// -------------------------------------------------------------------------------------|
void Params::SendCommand(NCommand cmd) {
    string nameObj = NAME_OBJECT_CMD;
    ObjectCreate(0, nameObj, OBJ_LABEL, 0, 0, 0);
    ObjectSetString(0, nameObj, OBJPROP_TEXT, m_path);
    ObjectSetInteger(0, nameObj, OBJPROP_ZORDER, cmd);
    ObjectSetInteger(0, nameObj, OBJPROP_TIMEFRAMES, 0);
}

// -------------------------------------------------------------------------------------|
// Erhalt eines Befehls von einem Skript                                                |
// -------------------------------------------------------------------------------------|
NCommand Params::TakeCommand() {
    string nameObj = NAME_OBJECT_CMD;
    if (ObjectFind(0, nameObj) < 0) return cmdUnknown;
    
    m_path = ObjectGetString(0, nameObj, OBJPROP_TEXT);
    m_cmd = (NCommand)ObjectGetInteger(0, nameObj, OBJPROP_ZORDER);
    ObjectDelete(0, nameObj);
    Load(m_path);
    
    return m_cmd;
}

// -------------------------------------------------------------------------------------|
// Ausgabe der Parameter                                                                |                                                             
// -------------------------------------------------------------------------------------|
void Params::Dump(string sender) {
    for (int j = 0; j < m_items.Total(); j++) {
        PrintFormat("%s / %s", sender, m_items.At(j));
    }
}

Für MQL5-Fans: Wenn Sie den Typ m_items auf CHashMap ändern, wird der Code der Funktionen Add, GetValue, Find deutlich reduziert. Aber die Klasse Params wird auch in MQL4 verwendet. Außerdem ist die Geschwindigkeit des Parameterzugriffs in diesem Fall nicht wichtig, da die Parameter einmal gelesen werden, um lokale Variablen zu initialisieren. Warum habe ich die Klasse für CHashMap nicht für MQL5 neu entwickelt? Wahrscheinlich, weil ich lange Zeit in einer Bank gearbeitet habe. Entwickler von Finanzsoftware haben einen sehr wichtigen Grundsatz: Wenn es funktioniert, rühre es nicht an! ;-)


Übergabe von Parametern an externe Programme

Die Datenaustauscheinheit zwischen verschiedenen Systemen ist de facto eine json-Datei. Zuvor war es eine xml-Datei. Die wesentlichen Vorteile von json-Dateien sind:

  • Einfache Erstellung (Generierung/Formatierung)
  • Hervorragende Unterstützung in allen Hochsprachen
  • Lesbarkeit

Zum Beispiel gibt es eine Klasse Bar mit folgenden Feldern: m_time, m_open, m_high, m_low, m_close, m_body. Dabei ist m_body die Farbe der Kerze: weiß, schwarz oder doji. Die Klasse Bar hat eine Methode ToJson(), die einen json-String erzeugt

string Bar::ToJson() {
        return "{" +
        "\n\t\"symbol\":\"" + _Symbol + "\"," +
        "\n\t\"period\":" + IntegerToString(_Period) + "," +
        "\n\t\"digits\":" + IntegerToString(_Digits) + "," +
        "\n\t\"timeBar\":\"" + TimeToStr(m_time) + "\"," +
        "\n\t\"open\":" + DoubleToString(m_open, _Digits) + "," +
        "\n\t\"high\":" + DoubleToString(m_high, _Digits) + "," +
        "\n\t\"low\":" + DoubleToString(m_low, _Digits) + "," +
        "\n\t\"close\":" + DoubleToString(m_close, _Digits) + "," +
        "\n\t\"body\":" + IntegerToString(m_body) + "," +
        "\n}";
}



Wir könnten stattdessen StringFormat verwenden, aber das würde Probleme beim Umordnen oder Löschen von Werten verursachen. Die Formatierung "\n\t" könnte gelöscht werden, da es eine ganze Reihe von Online-Json-Formatierungsdiensten gibt. Einer von ihnen ist JSON Parser. Sie stellen einmal den Erhalt eines gültigen json ein und verwenden die Funktion bar.ToJson(), wann immer Sie sie benötigen.

Ein externes Programm, zum Beispiel eine C#-Anwendung, kann eine json-Datei beliebiger Komplexität in ein Objekt umwandeln. Wie überträgt man eine json-Datei aus MQL? Es ist ganz einfach. Laden (speichern) Sie die json-Datei z. B. in das Terminalverzeichnis Files/Json. Ein externes Programm überwacht dieses Verzeichnis auf neue Dateien. Wenn es eine Datei gefunden hat, liest es diese ein, wandelt sie in ein Objekt um und löscht die Datei sofort oder verschiebt sie in das Archiv (für Statistiken).


Übernahme von Parametern von externen Programmen

Eine json-Bibliothek mit MQL-Programmen zu verbinden (oder das Rad neu erfinden), verursacht zusätzlichen Ärger. Eine bessere Lösung ist es, Textdateien mit Zeilen der Form Key=Value zu übergeben. Die Dateien können mit der Klasse Params (siehe oben) verarbeitet werden. Der Expert Advisor und der Indikator sind Kandidaten für den Empfang von Parametern aus externen Programmen oder Skripten. Sie müssen z. B. die Funktion CheckExternalCommand() in OnTick aufrufen, die das Vorhandensein von Dateien im Verzeichnis Files/ExtCmd überprüft. Wenn eine Datei gefunden wird, soll sie gelesen, verarbeitet (die Parameter akzeptiert) und die Datei gelöscht werden.

So, wir haben Methoden zum Empfangen und Übergeben von Parametern zwischen MQL und externen Programmen betrachtet. Denken Sie nun über Folgendes nach: Warum brauchen MQL-Programme DLLs? Solche Programme werden vom MQL-Markt nicht akzeptiert, und dafür gibt es nur einen Grund — die Sicherheit, da man von einer DLL aus auf alles zugreifen kann.


Übergabe von Parametern an ein Smartphone

Für weitere Operationen werde ich die Android-App WirePusher verwenden. Das ist ein wunderbarer Dienst (kostenlos und ohne Werbung). Ich weiß nicht, ob es so etwas auch für das iPhone gibt. Wenn es irgendwelche iPhone-Fans gibt, die diesen Artikel lesen, bitte in den Kommentaren teilen. 

Um den Dienst zu nutzen:

  • Installieren Sie WirePusher auf Ihrem Smartphone
  • Starten Sie die Anwendung. Auf dem Hauptbildschirm sehen Sie Ihre Kennung.
  • Fügen Sie https://wirepusher.com to Terminal/Service/Settings/Experts/Allow WebRequest hinzu.

Dann starten Sie das Skript (vergessen Sie nicht, Ihre id anstelle der Sternchen in id = "********" einzutragen.

void OnStart() {
    string id = "**********"; // Ihre Smartphone-Id in WirePusher
    WirePusher("Profit $1000", "Deal", "Closed", id);
}

// ------------------------------------------------------------------------------------------------
// Benachrichtigung über den Webservice WirePusher an das Smartphone senden
// Fügen Sie hinzu: https://wirepusher.com to Terminal/Service/Settings/Experts/Allow WebRequest
// message - Text der Benachrichtigung
// title - Titel der Benachrichtigung (z. B. Achtung / Warnung / Deal)
// type - Benachrichtigungstyp (z. B. Ausgelöste Pending-Order / Ausbruch aus der Ebene / Geschlossen)
// id - eindeutige Smartphone-ID aus der Android-App WirePusher
// ------------------------------------------------------------------------------------------------
bool WirePusher(string message, string title, string type, string id) {

    char data[]; // Datenarray für HTTP-Nachrichten im Hauptteil
    char result[]; // Datenarray der Antwort des Web-Anfrage
    string answer; // Header der Antwort der Web-Anfrage
    string url = "https://wirepusher.com/send?id={id}&title={title}&message={message}&type={type}";
    
    StringReplace(url, "{id}", id);
    StringReplace(url, "{type}", type);
    StringReplace(url, "{title}", title);
    StringReplace(url, "{message}", message);
    
    ResetLastError();
    int rcode = WebRequest("GET", url, NULL, 3000, data, result, answer);
    if (rcode != 200) {
        PrintFormat("%s / error=%i / url=%s / answer=%s / %s", 
            __FUNCTION__, GetLastError(), url, answer, CharArrayToString(result));
        return false;
    }
    PrintFormat("%s / %s / %s", __FUNCTION__, title, message);
    return true;
}

Im Cayman EA wird die Funktion WirePusher im AnalyserTrade aufgerufen, wenn:

  • Eine Pending-Order ausgelöst wird
  • Der Preis durchbricht eine Handelsebene
  • Eine Position geschlossen wird

Jedem Nachrichtentyp kann bei WirePusher ein eigener Sound zugewiesen werden. Früher hatte ich einen "Ta-da"-Sound für Position, die mit Gewinn geschlossen wurden und einen "Bomben"-Sound für solche, die mit Verlust geschlossen wurden. Aber dann hatte ich genug von "Bomben".


Schlussfolgerung

Die zuverlässigste und bequemste Methode zum Speichern von Parametern ist die Verwendung von Textdateien. Außerdem werden Dateioperationen in jedem Betriebssystem (Anwendung) vollständig unterstützt/gepuffert.


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

Andere Klassen in der Bibliothek DoEasy (Teil 72): Kontrolle und Aufzeichnung der Parameter von Chart-Objekten in der Kollektion Andere Klassen in der Bibliothek DoEasy (Teil 72): Kontrolle und Aufzeichnung der Parameter von Chart-Objekten in der Kollektion
In diesem Artikel werde ich die Arbeit mit den Klassen eines Chartobjekts und ihrer Kollektion vervollständigen. Ich werde auch die automatische Kontrolle von Änderungen Eigenschaften von Chartobjekten und ihren Fenstern implementieren, sowie das Speichern neuer Parameter in den Objekteigenschaften. Eine solche Überarbeitung ermöglicht die zukünftige Implementierung einer Ereignisfunktionalität für die gesamte Kollektion des Charts.
Swaps (Teil I): Locking und synthetische Positionen Swaps (Teil I): Locking und synthetische Positionen
In diesem Artikel werde ich versuchen, das klassische Konzept der Swap-Handelsmethoden zu erweitern. Ich werde erklären, warum ich zu dem Schluss gekommen bin, dass dieses Konzept besondere Aufmerksamkeit verdient und unbedingt zum Studium empfohlen wird.
Clusteranalyse (Teil I): Die Steigung von Indikatorlinien Clusteranalyse (Teil I): Die Steigung von Indikatorlinien
Die Clusteranalyse ist eines der wichtigsten Elemente der künstlichen Intelligenz. In diesem Artikel versuche ich, mit der Clusteranalyse die Steigung eines Indikators zu analysieren, um Schwellenwerte zu erhalten für die Bestimmung, ob ein Markt sich seitwärts bewegt (flat) oder ob er einem Trend folgt.
Andere Klassen in der Bibliothek DoEasy (Teil 71): Ereignisse der Kollektion von Chartobjekten Andere Klassen in der Bibliothek DoEasy (Teil 71): Ereignisse der Kollektion von Chartobjekten
In diesem Artikel werde ich die Funktionalität für die Verfolgung einiger Ereignisse von Chartobjekten erstellen — Hinzufügen/Entfernen von Symbolcharts und Chart-Unterfenstern, sowie Hinzufügen/Entfernen/Ändern von Indikatoren in Chart-Fenstern.