English Русский 中文 Español 日本語 Português
preview
Multilayer-Perzeptron und Backpropagation-Algorithmus (Teil II): Implementierung in Python und Integration mit MQL5

Multilayer-Perzeptron und Backpropagation-Algorithmus (Teil II): Implementierung in Python und Integration mit MQL5

MetaTrader 5Beispiele | 18 November 2021, 09:48
719 0
Jonathan Pereira
Jonathan Pereira

Einführung

Im vorangegangenen Artikel wurde die Erstellung eines einfachen Neurons (Perceptron) betrachtet. Wir lernten die Methode des Gradientenabstiegs, den Aufbau eines mehrschichtigen Perzeptron-Netzwerks (MLP), das aus miteinander verbundenen Perzeptrons besteht, und das Training solcher Netzwerke kennen.

In diesem Artikel möchte ich zeigen, wie einfach es ist, diesen Algorithmustyp mit der Sprache Python zu implementieren.

Für die Entwicklung von Integrationen mit MQL steht ein Python-Paket zur Verfügung, das eine Fülle von Möglichkeiten wie Datenexploration, Erstellung und Nutzung von maschinellen Lernmodellen ermöglicht.

Die eingebaute Python-Integration in MQL5 ermöglicht die Erstellung verschiedener Lösungen, von der einfachen linearen Regression bis hin zu Deep-Learning-Modellen. Da diese Sprache für den professionellen Einsatz konzipiert ist, gibt es viele Bibliotheken, die schwierige rechenbezogene Aufgaben ausführen können.

Wir werden ein Netzwerk manuell erstellen. Aber wie ich bereits im vorherigen Artikel erwähnt habe, ist dies nur ein Schritt, der uns hilft zu verstehen, was im Lern- und Vorhersageprozess tatsächlich passiert. Dann werde ich ein komplexeres Beispiel mit TensorFlow und Keras zeigen.


Was ist TensorFlow?

TensorFlow ist eine Open-Source-Bibliothek für schnelle numerische Verarbeitung.

Sie wurde von Google entwickelt, unterstützt und unter der Apache Open-Source-Lizenz veröffentlicht. Die API ist für die Sprache Python konzipiert, obwohl sie Zugang zur grundlegenden C++-API hat.

Im Gegensatz zu anderen numerischen Bibliotheken, die für Deep Learning entwickelt wurden, wie z.B. Theano, ist TensorFlow sowohl für den Einsatz in der Forschung als auch in der Produktion gedacht. Zum Beispiel die auf maschinellem Lernen basierende Suchmaschine RankBrain, die von Google verwendet wird, und ein sehr interessantes Computer-Vision-Projekt DeepDream.

Es kann sowohl in kleinen Systemen mit einer CPU und GPU oder mobilen Geräten laufen, als auch in großen verteilten Systemen, die Hunderte von Computern nutzen.

 

Was ist Keras?

Keras ist eine leistungsstarke und einfach zu bedienende Open-Source-Python-Bibliothek für die Entwicklung und Auswertung von Deep-Learning-Modellen.

Sie beinhaltet die leistungsstarken Berechnungsbibliotheken Theano und TensorFlow. Es ermöglicht die Definition und das Training von neuronalen Netzwerkmodellen in nur wenigen Zeilen Code.

Tutorial

Dieses Tutorial ist in 4 Abschnitte unterteilt:

  1. Installation und Vorbereitung der Python-Umgebung in MetaEditor.
  2. Erste Schritte und Modellrekonstruktion (Perceptron und MLP).
  3. Erstellung eines einfachen Modells mit Keras und TensorFlow.
  4. Wie man MQL5 und Python integriert.


1. Installieren und Vorbereiten der Python-Umgebung.

Zunächst sollten Sie Python von der offiziellen Website www.python.org/downloads/ herunterladen.

Um mit TensorFlow zu arbeiten, sollten Sie eine Version zwischen 3.3 und 3.8 installieren (ich persönlich benutze 3.7).

Nach dem Herunterladen und Starten des Installationsprozesses sollte man die Option "Add Python 3.7 to PATH" aktivieren. Dadurch wird sichergestellt, dass einige Dinge später ohne zusätzliche Konfiguration funktionieren.

Ein Python-Skript kann dann einfach direkt vom MetaTrader 5-Terminal aus gestartet werden.

  • Definieren Sie den Python-Ausführungspfad (Umgebung)
  • Installieren Sie die erforderlichen Projektabhängigkeiten 

Öffnen Sie MetaEditor und gehen Sie auf Extras \ Optionen.

Geben Sie hier den Pfad an, in dem sich die ausführbare Python-Datei befindet. Beachten Sie, dass nach der Installation der Standard-Python-Pfad verwendet werden sollte. Falls nicht, geben Sie den vollständigen Pfad zur ausführbaren Datei manuell ein. So können Sie Skripte direkt von Ihrem MetaTrader 5-Terminal aus starten.

1 - Konfiguration des Compilers

Ich persönlich verwende eine völlig separate Bibliotheksumgebung, eine virtuelle Umgebung. Dies ist eine Möglichkeit, eine "saubere" Installation zu erhalten und nur die Bibliotheken zu sammeln, die für das Produkt erforderlich sind.

Für weitere Informationen über das venv-Paket lesen Sie bitte hier.


Danach können Sie Python-Skripte direkt vom Terminal aus starten. Für dieses Projekt müssen wir die folgenden Bibliotheken installieren.

Wenn Sie sich nicht sicher sind, wie Sie die Bibliotheken installieren, lesen Sie bitte die entsprechende Anleitung zur Modulinstallation.

  • MetaTrader 5
  • TensorFlow
  • Matplotlib
  • Pandas
  • Sklearn

Nachdem wir nun die Umgebung installiert und konfiguriert haben, wollen wir einen kleinen Test durchführen, um zu verstehen, wie man ein kleines Skript im Terminal erstellt und ausführt. Um ein neues Skript direkt von MetaEditor aus zu starten, folgen Sie den folgenden Schritten:

New > Python-Skript

1 - Neues Skript

Geben Sie den Namen für Ihr Skript an. Der MQL-Assistent in MetaEditor wird Sie automatisch auffordern, einige Bibliotheken zu importieren. Das ist sehr interessant, und für unser Experiment wählen wir die Option numpy.

3 - Neues Skript II

Lassen Sie uns nun ein einfaches Skript erstellen, das eine Sinuskurve erzeugt.

# Copyright 2021, Lethan Corp.
# https://www.mql5.com/pt/users/14134597

import numpy as np
import matplotlib.pyplot as plt

data = np.linspace(-np.pi, np.pi, 201)
plt.plot(data, np.sin(data))
plt.xlabel('Angle [rad]')
plt.ylabel('sin(data)')
plt.axis('tight')
plt.show()

Um das Skript auszuführen, drücken Sie einfach F7 zum Kompilieren, öffnen Sie das MetaTrader 5-Terminal und führen Sie das Skript in einem Chart aus. Die Ergebnisse werden auf der Registerkarte "Experten" angezeigt, wenn es etwas zu drucken gibt. In unserem Fall öffnet das Skript ein Fenster mit dem Funktionsdiagramm, das wir erstellt haben.

3 - Sinuskurve


2. Erste Schritte und Modellrekonstruktion (Perceptron und MLP).

 

Der Einfachheit halber verwenden wir denselben Datensatz wie im MQL5-Beispiel.

Nachfolgend ist die Funktion predict() dargestellt, die den Ausgabewert für eine Zeile mit dem gegebenen Satz von Gewichten vorhersagt. Hier ist der erste Fall auch der Bias. Außerdem gibt es eine Aktivierungsfunktion.

# Transfer neuron activation
def activation(activation):
    return 1.0 if activation >= 0.0 else 0.0

# Make a prediction with weights
def predict(row, weights):
    z = weights[0]
    for i in range(len(row) - 1):
        z += weights[i + 1] * row[i]
    return activation(z)

Wie Sie bereits wissen, müssen wir, um ein Netz zu trainieren, den Prozess des Gradientenabstiegs implementieren, der im vorherigen Artikel ausführlich erläutert wurde. Als Fortsetzung werde ich die Trainingsfunktion "train_weights()" zeigen.

# Estimate Perceptron weights using stochastic gradient descent
def train_weights(train, l_rate, n_epoch):
    weights = [0.0 for i in range(len(train[0]))]  #random.random()
    for epoch in range(n_epoch):
        sum_error = 0.0
        for row in train:
            y = predict(row, weights)
            error = row[-1] - y
            sum_error += error**2
            weights[0] = weights[0] + l_rate * error

            for i in range(len(row) - 1):
                weights[i + 1] = weights[i + 1] + l_rate * error * row[i]
        print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
    return weights


Anwendung des MLP-Modells:

Dieses Tutorial ist in 5 Abschnitte unterteilt:

  • Start des Netzwerks
  • FeedForward
  • BackPropagation
  • Training
  • Prognose

Start des Netzwerks

Beginnen wir mit etwas Einfachem, indem wir ein neues, lernfähiges Netz erstellen.

Jedes Neuron hat eine Reihe von Gewichten, die gepflegt werden müssen, ein Gewicht für jede Eingangsverbindung und ein zusätzliches Gewicht für die Vorspannung. Da wir während des Trainings zusätzliche Eigenschaften des Neurons speichern müssen, werden wir ein Wörterbuch verwenden, um jedes Neuron zu repräsentieren und Eigenschaften mit Namen zu speichern, z. B. als "weights" für Gewichte.

from random import seed
from random import random

# Initialize a network
def initialize_network(n_inputs, n_hidden, n_outputs):
    network = list()
    hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
    network.append(hidden_layer)
    output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
    network.append(output_layer)
    return network

seed(1)
network = initialize_network(2, 1, 2)
for layer in network:
    print(layer)

Nachdem wir nun wissen, wie man ein Netz erstellt und startet, wollen wir sehen, wie man es zur Berechnung von Ausgabedaten verwenden kann.

    FeedForward

    from math import exp
    
    # Calculate neuron activation for an input
    def activate(weights, inputs):
        activation = weights[-1]
        for i in range(len(weights)-1):
            activation += weights[i] * inputs[i]
        return activation
    
    # Transfer neuron activation
    def transfer(activation):
        return 1.0 / (1.0 + exp(-activation))
    
    # Forward propagate input to a network output
    def forward_propagate(network, row):
        inputs = row
        for layer in network:
            new_inputs = []
            for neuron in layer:
                activation = activate(neuron['weights'], inputs)
                neuron['output'] = transfer(activation)
                new_inputs.append(neuron['output'])
            inputs = new_inputs
        return inputs
    
    # test forward propagation
    network = [[{'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}],
            [{'weights': [0.2550690257394217, 0.49543508709194095]}, {'weights': [0.4494910647887381, 0.651592972722763]}]]
    row = [1, 0, None]
    output = forward_propagate(network, row)
    print(output)
    

    Bei der Ausführung des obigen Skripts erhalten wir das folgende Ergebnis:

    [0.6629970129852887, 0.7253160725279748]

    Die tatsächlichen Ausgabewerte sind für den Moment absurd. Aber wir werden bald sehen, wie wir die Gewichte in den Neuronen sinnvoller gestalten können.

    Backpropagation

    # Calculate the derivative of an neuron output
    def transfer_derivative(output):
        return output * (1.0 - output)
    
    # Backpropagate error and store in neurons
    def backward_propagate_error(network, expected):
        for i in reversed(range(len(network))):
            layer = network[i]
            errors = list()
            if i != len(network)-1:
                for j in range(len(layer)):
                    error = 0.0
                    for neuron in network[i + 1]:
                        error += (neuron['weights'][j] * neuron['delta'])
                    errors.append(error)
            else:
                for j in range(len(layer)):
                    neuron = layer[j]
                    errors.append(expected[j] - neuron['output'])
            for j in range(len(layer)):
                neuron = layer[j]
                neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])
    
    # test backpropagation of error
    network = [[{'output': 0.7105668883115941, 'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614]}],
              [{'output': 0.6213859615555266, 'weights': [0.2550690257394217, 0.49543508709194095]}, {'output': 0.6573693455986976, 'weights': [0.4494910647887381, 0.651592972722763]}]]
    expected = [0, 1]
    backward_propagate_error(network, expected)
    for layer in network:
        print(layer)
    
    

    Bei der Ausführung gibt das Beispiel das Netz nach Abschluss der Fehlerprüfungen aus. Wie Sie sehen können, werden Fehlerwerte berechnet und in Neuronen für die Ausgabeschicht und die versteckte Schicht gespeichert.

    [{'output': 0.7105668883115941, 'weights': [0.13436424411240122, 0.8474337369372327, 0.763774618976614], 'delta': -0.0005348048046610517}]

    [{'output': 0.6213859615555266, 'weights': [0.2550690257394217, 0.49543508709194095], 'delta': -0.14619064683582808}, {'output': 0.6573693455986976, 'weights': [0.4494910647887381, 0.651592972722763], 'delta': 0.0771723774346327}]

    Trainieren des Netzwerks

    from math import exp
    from random import seed
    from random import random
    
    # Initialize a network
    def initialize_network(n_inputs, n_hidden, n_outputs):
        network = list()
        hidden_layer = [{'weights':[random() for i in range(n_inputs + 1)]} for i in range(n_hidden)]
        network.append(hidden_layer)
        output_layer = [{'weights':[random() for i in range(n_hidden + 1)]} for i in range(n_outputs)]
        network.append(output_layer)
        return network
    
    # Calculate neuron activation for an input
    def activate(weights, inputs):
        activation = weights[-1]
        for i in range(len(weights)-1):
            activation += weights[i] * inputs[i]
        return activation
    
    # Transfer neuron activation
    def transfer(activation):
        return 1.0 / (1.0 + exp(-activation))
    
    # Forward propagate input to a network output
    def forward_propagate(network, row):
        inputs = row
        for layer in network:
            new_inputs = []
            for neuron in layer:
                activation = activate(neuron['weights'], inputs)
                neuron['output'] = transfer(activation)
                new_inputs.append(neuron['output'])
            inputs = new_inputs
        return inputs
    
    # Calculate the derivative of an neuron output
    def transfer_derivative(output):
        return output * (1.0 - output)
    
    # Backpropagate error and store in neurons
    def backward_propagate_error(network, expected):
        for i in reversed(range(len(network))):
            layer = network[i]
            errors = list()
            if i != len(network)-1:
                for j in range(len(layer)):
                    error = 0.0
                    for neuron in network[i + 1]:
                        error += (neuron['weights'][j] * neuron['delta'])
                    errors.append(error)
            else:
                for j in range(len(layer)):
                    neuron = layer[j]
                    errors.append(expected[j] - neuron['output'])
            for j in range(len(layer)):
                neuron = layer[j]
                neuron['delta'] = errors[j] * transfer_derivative(neuron['output'])
    
    # Update network weights with error
    def update_weights(network, row, l_rate):
        for i in range(len(network)):
            inputs = row[:-1]
            if i != 0:
                inputs = [neuron['output'] for neuron in network[i - 1]]
            for neuron in network[i]:
                for j in range(len(inputs)):
                    neuron['weights'][j] += l_rate * neuron['delta'] * inputs[j]
                neuron['weights'][-1] += l_rate * neuron['delta']
    
    # Train a network for a fixed number of epochs
    def train_network(network, train, l_rate, n_epoch, n_outputs):
        for epoch in range(n_epoch):
            sum_error = 0
            for row in train:
                outputs = forward_propagate(network, row)
                expected = [0 for i in range(n_outputs)]
                expected[row[-1]] = 1
                sum_error += sum([(expected[i]-outputs[i])**2 for i in range(len(expected))])
                backward_propagate_error(network, expected)
                update_weights(network, row, l_rate)
            print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
    
    # Test training backprop algorithm
    seed(1)
    dataset = [[2.7810836,2.550537003,0],
        [1.465489372,2.362125076,0],
        [3.396561688,4.400293529,0],
        [1.38807019,1.850220317,0],
        [3.06407232,3.005305973,0],
        [7.627531214,2.759262235,1],
        [5.332441248,2.088626775,1],
        [6.922596716,1.77106367,1],
        [8.675418651,-0.242068655,1],
        [7.673756466,3.508563011,1]]
    
    n_inputs = len(dataset[0]) - 1
    n_outputs = len(set([row[-1] for row in dataset]))
    network = initialize_network(n_inputs, 2, n_outputs)
    train_network(network, dataset, 0.5, 20, n_outputs)
    for layer in network:
        print(layer)
    
    

    Nach dem Training wird das Netz ausgedruckt, wobei die gelernten Gewichte angezeigt werden. Außerdem hat das Netz noch Ausgangs- und Deltawerte, die ignoriert werden können. Falls erforderlich, könnten wir unsere Trainingsfunktion aktualisieren, um diese Daten zu löschen.

    >epoch=13, lrate=0.500, error=1.953

    >epoch=14, lrate=0.500, error=1.774

    >epoch=15, lrate=0.500, error=1.614

    >epoch=16, lrate=0.500, error=1.472

    >epoch=17, lrate=0.500, error=1.346

    >epoch=18, lrate=0.500, error=1.233

    >epoch=19, lrate=0.500, error=1.132

    [{'weights': [-1.4688375095432327, 1.850887325439514, 1.0858178629550297], 'output': 0.029980305604426185, 'delta': -0.0059546604162323625}, {'weights': [0.37711098142462157, -0.0625909894552989, 0.2765123702642716], 'output': 0.9456229000211323, 'delta': 0.0026279652850863837}]

    [{'weights': [2.515394649397849, -0.3391927502445985, -0.9671565426390275], 'output': 0.23648794202357587, 'delta': -0.04270059278364587}, {'weights': [-2.5584149848484263, 1.0036422106209202, 0.42383086467582715], 'output': 0.7790535202438367, 'delta': 0.03803132596437354}]

    Um eine Vorhersage zu treffen, können wir die bereits im vorherigen Beispiel konfigurierten Gewichte verwenden.

    Vorhersage

    from math import exp
    
    # Calculate neuron activation for an input
    def activate(weights, inputs):
        activation = weights[-1]
        for i in range(len(weights)-1):
            activation += weights[i] * inputs[i]
        return activation
    
    # Transfer neuron activation
    def transfer(activation):
        return 1.0 / (1.0 + exp(-activation))
    
    # Forward propagate input to a network output
    def forward_propagate(network, row):
        inputs = row
        for layer in network:
            new_inputs = []
            for neuron in layer:
                activation = activate(neuron['weights'], inputs)
                neuron['output'] = transfer(activation)
                new_inputs.append(neuron['output'])
            inputs = new_inputs
        return inputs
    
    # Make a prediction with a network
    def predict(network, row):
        outputs = forward_propagate(network, row)
        return outputs.index(max(outputs))
    
    # Test making predictions with the network
    dataset = [[2.7810836,2.550537003,0],
        [1.465489372,2.362125076,0],
        [3.396561688,4.400293529,0],
        [1.38807019,1.850220317,0],
        [3.06407232,3.005305973,0],
        [7.627531214,2.759262235,1],
        [5.332441248,2.088626775,1],
        [6.922596716,1.77106367,1],
        [8.675418651,-0.242068655,1],
        [7.673756466,3.508563011,1]]
    network = [[{'weights': [-1.482313569067226, 1.8308790073202204, 1.078381922048799]}, {'weights': [0.23244990332399884, 0.3621998343835864, 0.40289821191094327]}],
        [{'weights': [2.5001872433501404, 0.7887233511355132, -1.1026649757805829]}, {'weights': [-2.429350576245497, 0.8357651039198697, 1.0699217181280656]}]]
    for row in dataset:
        prediction = predict(network, row)
        print('Expected=%d, Got=%d' % (row[-1], prediction))
    

    Bei der Ausführung des Beispiels wird das erwartete Ergebnis für jeden Datensatz im Trainingsdatensatz ausgegeben, gefolgt von einer eindeutigen Vorhersage des Netzes.

    Den Ergebnissen zufolge erreicht das Netz bei diesem kleinen Datensatz eine Genauigkeit von 100 %.

    Expected=0, Got=0

    Expected=0, Got=0

    Expected=0, Got=0

    Expected=0, Got=0

    Expected=0, Got=0

    Expected=1, Got=1

    Expected=1, Got=1

    Expected=1, Got=1

    Expected=1, Got=1

    Expected=1, Got=1


    3. Erstellung eines einfachen Modells mit Keras und TensorFlow.

    Zum Sammeln der Daten wird das MetaTrader 5-Paket verwendet. Starten Sie das Skript, indem Sie die Bibliotheken importieren, die für die Extraktion, Konvertierung und Vorhersage der Kurse benötigt werden. Wir werden hier nicht im Detail auf die Datenaufbereitung eingehen, vergessen Sie aber bitte nicht, dass dies ein sehr wichtiger Schritt für das Modell ist.

    Beginnen wir mit einer kurzen Datenübersicht. Der Datensatz besteht aus den letzten 1000 EURUSD-Balken. Dieser Teil besteht aus mehreren Schritten:

    • Importieren der Bibliotheken
    • Verbinden mit dem MetaTrader
    • Datenerhebung
    • Konvertieren und Anpassen der Daten
    • Zeichnen der Daten


    import MetaTrader5 as mt5
    from pandas import to_datetime, DataFrame
    import matplotlib.pyplot as plt
    
    symbol = "EURUSD"
    
    if not mt5.initialize():
        print("initialize() failed")
        mt5.shutdown()
    
    rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_D1, 0, 1000)
    mt5.shutdown()
    
    rates = DataFrame(rates)
    rates['time'] = to_datetime(rates['time'], unit='s')
    rates = rates.set_index(['time'])
    
    plt.figure(figsize = (15,10))
    plt.plot(rates.close)
    plt.show()
    
    

    Nachdem Sie den Code ausgeführt haben, stellen Sie die Abschlussdaten als Linie in dem unten stehenden Diagramm dar.

    plot_1


    Verwenden wir einen einfachen Regressionsansatz, um den Schlusswert der nächsten Periode vorherzusagen.

    Für dieses Beispiel werden wir einen univariaten (eindimensionalen) Ansatz verwenden.

    Eine eindimensionale Zeitreihe ist ein Datensatz, der aus einer einzigen Reihe von Beobachtungen mit einer zeitlichen Anordnung besteht. Es wird ein Modell benötigt, das aus der Reihe der vergangenen Beobachtungen lernt, um den nächsten Wert in der Folge vorherzusagen.

    Der erste Schritt besteht darin, die geladene Reihe in einen Trainings- und einen Testsatz zu unterteilen. Erstellen wir eine Funktion, die eine solche Reihe in zwei Teile aufteilt. Die Aufteilung erfolgt nach dem angegebenen Prozentsatz, z. B. 70 % für das Training und 30 % für den Test. Für die Validierung (Backtest) gibt es andere Ansätze, z. B. die Aufteilung der Serie in Trainieren, Testen und Validieren. Da es sich um Finanzreihen handelt, sollten wir sehr vorsichtig sein, um eine Überanpassung zu vermeiden.

    Die Funktion erhält ein Numpy-Array und einen Clipping-Wert und gibt zwei geteilte Reihen zurück.

    Der erste Rückgabewert ist die gesamte Reihe von Position 0 bis zur Größe, die die Größe des Faktors darstellt, und die zweite Reihe ist die verbleibende Reihe.

    def train_test_split(values, fator):
        train_size = int(len(values) * fator)
        return values[0:train_size], values[train_size:len(values)]
    
    


    Keras-Modelle können als eine Folge von Schichten (layer) definiert werden.

    Wir erstellen ein sequenzielles Modell und fügen nach und nach Schichten hinzu, bis wir mit unserer Netzwerkarchitektur zufrieden sind.

    Zunächst müssen wir sicherstellen, dass die Eingabeschicht die richtige Anzahl von Eingaberessourcen hat. Dies geschieht, indem wir die erste Schicht mit dem Argument input_dim erstellen.

    Woher wissen wir, wie viele Schichten und Typen wir brauchen?

    Das ist eine sehr schwierige Frage. Es gibt Heuristiken, die wir verwenden können, und oft findet man die beste Netzstruktur durch Ausprobieren und Experimentieren. Im Allgemeinen braucht man ein Netz, das groß genug ist, um die Struktur des Problems zu erfassen.

    In diesem Beispiel verwenden wir eine vollständig verbundene, einschichtige Netzstruktur.

    Vollständig verknüpfte Schichten werden mit der Klasse Dense definiert. Wir können die Anzahl der Neuronen oder Knoten in einer Schicht als erstes Argument angeben und die Aktivierungsfunktion mit dem Aktivierungsargument spezifizieren.

    Wir werden die Aktivierungsfunktion rectified linear unit (ReLU) in der ersten Schicht verwenden.

    Bevor eine univariate Reihe prognostiziert werden kann, muss sie vorbereitet werden.

    Das MLP-Modell lernt mit Hilfe einer Funktion, die eine Folge vergangener Beobachtungen als Eingabe in Ausgabebeobachtungen umsetzt. Die Folge von Beobachtungen muss also in mehrere Beispiele umgewandelt werden, aus denen das Modell lernen kann.

    Betrachten wir eine univariate Sequenz:

    [10, 20, 30, 40, 50, 60, 70, 80, 90]

    Die Sequenz kann in mehrere E/A-Muster, so genannte Stichproben, aufgeteilt werden.

    In unserem Beispiel verwenden wir drei Zeitschritte, die als Eingabe verwendet werden, und einen Zeitschritt, der für die Ausgabe in der untersuchten Vorhersage verwendet wird.

    X,                                          y

    10, 20, 30                          40

    20, 30, 40                          50

    30, 40, 50                          60

    ...

    Im Folgenden erstellen wir die Funktion split_sequence(), die dieses Verhalten implementiert. Wir werden auch die univariate Menge in mehrere Stichproben aufteilen, wobei jede eine bestimmte Anzahl von Zeitschritten hat. Die Ausgabe ist ein einzelner Zeitschritt.

    Wir können unsere Funktion mit einem kleinen Datensatz testen, wie die Daten im obigen Beispiel.

    # univariate data preparation
    from numpy import array
     
    # split a univariate sequence into samples
    def split_sequence(sequence, n_steps):
        X, y = list(), list()
        for i in range(len(sequence)):
            # find the end of this pattern
            end_ix = i + n_steps
            # check if we are beyond the sequence
            if end_ix > len(sequence)-1:
                break
            # gather input and output parts of the pattern
            seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
            X.append(seq_x)
            y.append(seq_y)
        return array(X), array(y)
     
    # define input sequence
    raw_seq = [10, 20, 30, 40, 50, 60, 70, 80, 90]
    # choose a number of time steps
    n_steps = 3
    # split into samples
    X, y = split_sequence(raw_seq, n_steps)
    # summarize the data
    for i in range(len(X)):
        print(X[i], y[i])
    
    

    Der Code unterteilt einen univariaten Satz in sechs Stichproben mit jeweils drei Eingabezeitschritten und einem Ausgabezeitschritt.

    [10 20 30] 40

    [20 30 40] 50

    [30 40 50] 60

    [40 50 60] 70

    [50 60 70] 80

    [60 70 80] 90

    Um fortzufahren, müssen wir die Probe in X (Merkmal) und y (Ziel) aufteilen, damit wir das Netz trainieren können. Dazu verwenden wir die zuvor erstellte Funktion split_sequence().

    X_train, y_train = split_sequence(train, 3)
    X_test, y_test = split_sequence(test, 3)
    
    

    Nachdem wir nun die Datenproben vorbereitet haben, können wir das MLP-Netzwerk erstellen.

    Ein einfaches MLP-Modell hat nur eine versteckte Schicht von Knoten (Neuronen) und eine Ausgabeschicht für die Vorhersage.

    Wir können das MLP für die Vorhersage von univariaten Zeitreihen wie folgt definieren.

    # define model
    model = Sequential()
    model.add(Dense(100, activation='relu', input_dim=n_steps))
    model.add(Dense(1))
    model.compile(optimizer='adam', loss='mse')
    
    

    Um die Form der Eingabedaten zu definieren, müssen wir verstehen, was das Modell als Eingabedaten für jede Probe in Bezug auf die Anzahl der Zeitschritte erwartet.

    Die Anzahl der Zeitschritte als Eingabeparameter ist die Anzahl, die wir bei der Vorbereitung des Datensatzes als Argument für die Funktion split_sequence() wählen.

    Die Eingabedimension jeder Probe wird im Argument input_dim in der Definition der ersten versteckten Schicht angegeben. Technisch gesehen zeigt das Modell jeden Zeitschritt als eine separate Ressource an und nicht als separate Zeitschritte.

    Normalerweise haben wir mehrere Stichproben, so dass das Modell erwartet, dass die Eingabe-Trainingskomponente Dimensionen oder Form hat:

    [samples, features]

    Die Funktion split_sequence() erzeugt X in der Form [Stichproben, Merkmale], das sofort verwendet werden kann.

    Das Modell wird mit dem effizienten Algorithmus namens Adam für den stochastischen Gradientenabstieg trainiert. Es wird mit der Verlustfunktion MSE (mittlerer quadratischer Fehler) optimiert.

    Nachdem wir das Modell definiert haben, können wir es mit dem Datensatz trainieren.

    model.fit(X_train, y_train, epochs=100, verbose=2)

    Nach dem Training des Modells können wir den zukünftigen Wert vorhersagen. Das Modell geht davon aus, dass die Eingabeform zweidimensional ist [Stichproben, Merkmale], daher müssen wir die einzelne Eingabeprobe umformen, bevor wir die Vorhersage treffen. Ein Beispiel: Wir verwenden die Form [1, 3] für 1 Stichprobe und 3 Zeitschritte, die als Merkmale verwendet werden.

    Wir wählen den letzten Datensatz der Stichprobe X_test aus und vergleichen ihn nach der Vorhersage mit dem tatsächlichen Wert in der letzten Stichprobe y_test.

    # demonstrate prediction
    x_input = X_test[-1]
    x_input = x_input.reshape((1, n_steps))
    yhat = model.predict(x_input, verbose=0)
    
    print("Valor previsto: ", yhat)
    print("Valor real: ", y_test[-1])
    
    


    4. How to integrate MQL5 and Python.

    Wir haben mehrere Möglichkeiten, das Modell in einem Handelskonto zu verwenden. Eine davon ist die Verwendung nativer Python-Funktionen zum Öffnen und Schließen von Positionen. Aber in diesem Fall lassen wir uns die umfangreichen Möglichkeiten, die MQL bietet, entgehen. Aus diesem Grund habe ich mich für eine Integration zwischen Python und der MQL-Umgebung entschieden, die uns mehr Autonomie bei der Verwaltung von Positionen/Aufträgen gibt.

    Basierend auf dem Artikel Integration von MetaTrader 5 und Python: Daten senden und empfangen von Maxim Dmitrievsky habe ich diese Klasse unter Verwendung des Singleton-Musters implementiert, das für die Erstellung eines Socket-Clients für die Kommunikation verantwortlich sein wird. Dieses Muster stellt sicher, dass es nur eine Kopie eines bestimmten Objekttyps gibt, denn wenn das Programm zwei Zeiger verwendet, die beide auf dasselbe Objekt verweisen, zeigen die Zeiger auf dasselbe Objekt.

    class CClientSocket
      {
    private:
       static CClientSocket*  m_socket;
       int                    m_handler_socket;
       int                    m_port;
       string                 m_host;
       int                    m_time_out;
                         CClientSocket(void);
                        ~CClientSocket(void);
    public:
       static bool           DeleteSocket(void);
       bool                  SocketSend(string payload);
       string                SocketReceive(void);
       bool                  IsConnected(void);
       static CClientSocket *Socket(void);
       bool                  Config(string host, int port);
       bool                  Close(void);
      };
    

    Die Klasse CClienteSocke speichert den statischen Zeiger als privates Mitglied. Die Klasse hat nur einen 'private' Konstruktor, der nicht aufgerufen werden kann. Anstelle des Konstruktorcodes können wir die Socket-Methode verwenden, um sicherzustellen, dass nur ein Objekt verwendet wird.

    static CClientSocket *CClientSocket::Socket(void)
      {
       if(CheckPointer(m_socket)==POINTER_INVALID)
          m_socket=new CClientSocket();
       return m_socket;
      }
    

    Diese Methode prüft, ob der statische Zeiger auf den CClienteSocket-Socket zeigt. Wenn dies der Fall ist, wird der Verweis zurückgegeben; andernfalls wird ein neues Objekt erstellt und mit dem Zeiger verknüpft, wodurch sichergestellt wird, dass dieses Objekt ausschließlich in unserem System vorhanden ist.

    Um eine Verbindung mit dem Server herzustellen, ist es notwendig, die Verbindung zu initiieren. Hier ist die IsConnected-Methode zum Aufbau einer Verbindung, nach der wir mit dem Senden/Empfangen von Daten beginnen können.

    bool CClientSocket::IsConnected(void)
      {
       ResetLastError();
       bool res=true;
    
       m_handler_socket=SocketCreate();
       if(m_handler_socket==INVALID_HANDLE)
          res=false;
    
       if(!::SocketConnect(m_handler_socket,m_host,m_port,m_time_out))
          res=false;
    
       return res;
      }
    

    Nach erfolgreicher Verbindung und Übermittlung von Nachrichten müssen wir diese Verbindung schließen. Dazu verwenden wir die Methode Close, um die zuvor geöffnete Verbindung zu schließen.

    bool CClientSocket::Close(void)
      {
       bool res=false;
       if(SocketClose(m_handler_socket))
         {
          res=true;
          m_handler_socket=INVALID_HANDLE;
         }
       return res;
      }
    

    Nun müssen wir den Server registrieren, der neue MQL-Verbindungen empfängt und die Prognosen an unser Modell sendet.

    import socket
    
    class socketserver(object):
        def __init__(self, address, port):
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.address = address
            self.port = port
            self.sock.bind((self.address, self.port))
            
        def socket_receive(self):
            self.sock.listen(1)
            self.conn, self.addr = self.sock.accept()
            self.cummdata = ''
    
            while True:
                data = self.conn.recv(10000)
                self.cummdata+=data.decode("utf-8")
                if not data:
                    self.conn.close()
                    break
                return self.cummdata
        
        def socket_send(self, message):
            self.sock.listen(1)
            self.conn, self.addr = self.sock.accept()
            self.conn.send(bytes(message, "utf-8"))
        
                
        def __del__(self):
            self.conn.close()
    

    Unser Objekt ist einfach: In seinem Konstruktor erhalten wir die Adresse und den Port unseres Servers. Die Methode socket_received ist für die Annahme der neuen Verbindung und für die Überprüfung des Vorhandenseins gesendeter Nachrichten zuständig. Wenn es Nachrichten gibt, die empfangen werden sollen, führen wir eine Schleife aus, bis wir alle Teile der Nachricht erhalten haben. Dann wird die Verbindung mit dem Client geschlossen und die Schleife verlassen. Auf der anderen Seite ist die Methode socket_send für das Senden von Nachrichten an unseren Client zuständig. Dieses vorgeschlagene Modell erlaubt es uns also nicht nur, Vorhersagen an unser Modell zu senden, sondern eröffnet auch Möglichkeiten für verschiedene andere Dinge - alles hängt von Ihrer Kreativität und Ihren Bedürfnissen ab.

    Jetzt haben wir die fertige Kommunikation. Lassen Sie uns über zwei Dinge nachdenken:

    1. Wie trainiert man das Modell und wie speichert man es.
    2. Wie kann das trainierte Modell für Prognosen verwendet werden?

    Es wäre unpraktisch und falsch, jedes Mal, wenn wir eine Prognose machen wollen, Daten zu sammeln und zu trainieren. Aus diesem Grund führe ich immer ein Training durch, finde die besten Hyperparameter und speichere mein Modell zur späteren Verwendung.

    Ich werde eine Datei mit dem Namen model_train erstellen, die den Trainingscode für das Netzwerk enthält. Wir werden die prozentuale Differenz zwischen den Schlusskursen verwenden und versuchen, diese Differenz vorherzusagen. Bitte beachten Sie, dass ich nur zeigen möchte, wie das Modell durch Integration in die MQL-Umgebung verwendet werden kann, nicht aber das Modell selbst.

    import MetaTrader5 as mt5
    from numpy.lib.financial import rate
    from pandas import to_datetime, DataFrame
    from datetime import datetime, timezone
    from matplotlib import pyplot
    from sklearn.metrics import mean_squared_error
    from math import sqrt
    import numpy as np
    
    from tensorflow.keras import Sequential
    from tensorflow.keras.layers import Dense
    from tensorflow.keras.callbacks import *
    
    
    symbol = "EURUSD"
    date_ini = datetime(2020, 1, 1, tzinfo=timezone.utc)
    date_end = datetime(2021, 7, 1, tzinfo=timezone.utc)
    period   = mt5.TIMEFRAME_D1
    
    def train_test_split(values, fator):
        train_size = int(len(values) * fator)
        return np.array(values[0:train_size]), np.array(values[train_size:len(values)])
    
    # split a univariate sequence into samples
    def split_sequence(sequence, n_steps):
            X, y = list(), list()
            for i in range(len(sequence)):
                    end_ix = i + n_steps
                    if end_ix > len(sequence)-1:
                            break
                    seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
                    X.append(seq_x)
                    y.append(seq_y)
            return np.array(X), np.array(y)
    
    if not mt5.initialize():
        print("initialize() failed")
        mt5.shutdown()
        raise Exception("Error Getting Data")
    
    rates = mt5.copy_rates_range(symbol, period, date_ini, date_end)
    mt5.shutdown()
    rates = DataFrame(rates)
    
    if rates.empty:
        raise Exception("Error Getting Data")
    
    rates['time'] = to_datetime(rates['time'], unit='s')
    rates.set_index(['time'], inplace=True)
    
    rates = rates.close.pct_change(1)
    rates = rates.dropna()
    
    X, y = train_test_split(rates, 0.70)
    X = X.reshape(X.shape[0])
    y = y.reshape(y.shape[0])
    
    train, test = train_test_split(X, 0.7)
    
    n_steps = 60
    verbose = 1
    epochs  = 50
    
    X_train, y_train = split_sequence(train, n_steps)
    X_test, y_test   = split_sequence(test, n_steps)
    X_val, y_val     = split_sequence(y, n_steps)
    
    # define model
    model = Sequential()
    model.add(Dense(200, activation='relu', input_dim=n_steps))
    model.add(Dense(1))
    model.compile(optimizer='adam', loss='mse')
    
    history = model.fit(X_train
                       ,y_train  
                       ,epochs=epochs
                       ,verbose=verbose
                       ,validation_data=(X_test, y_test))
    
    model.save(r'C:\YOUR_PATH\MQL5\Experts\YOUR_PATH\model_train_'+symbol+'.h5')
    
    pyplot.title('Loss')
    pyplot.plot(history.history['loss'], label='train')
    pyplot.plot(history.history['val_loss'], label='test')
    pyplot.legend()
    pyplot.show()
    
    history = list()
    yhat    = list()
    
    for i in range(0, len(X_val)):
            pred = X_val[i]
            pred = pred.reshape((1, n_steps))
            history.append(y_val[i])
            yhat.append(model.predict(pred).flatten()[0])
    
    pyplot.figure(figsize=(10, 5))
    pyplot.plot(history,"*")
    pyplot.plot(yhat,"+")
    pyplot.plot(history, label='real')
    pyplot.plot(yhat, label='prediction')
    pyplot.ylabel('Price Close', size=10)
    pyplot.xlabel('time', size=10)
    pyplot.legend(fontsize=10)
    
    pyplot.show()
    rmse = sqrt(mean_squared_error(history, yhat))
    mse = mean_squared_error(history, yhat)
    
    print('Test RMSE: %.3f' % rmse)
    print('Test MSE: %.3f' % mse)
    

    Wir haben nun ein trainiertes Modell, das im Ordner mit der Erweiterung .h5 gespeichert ist. Wir können dieses Modell für Vorhersagen verwenden. Lassen Sie uns nun ein Objekt erstellen, das dieses Modell instanziiert, um es für die Verbindung zu verwenden.

    from tensorflow.keras.models import *
    
    class Model(object):
        def __init__(self, n_steps:int, symbol:str, period:int) -> None:
            super().__init__()
            self.n_steps = n_steps
            self.model = load_model(r'C:\YOUR_PATH\MQL5\Experts\YOUR_PATH\model_train_'+symbol+'.h5')
    
        def predict(self, data):
            return(self.model.predict(data.reshape((1, self.n_steps))).flatten()[0])
    

    Das Objekt ist einfach: Sein Konstruktor erzeugt eine Instanz des Attributs model, das das gespeicherte Modell enthält. Das Prognosemodell ist für die Erstellung der Vorhersage verantwortlich.

    Jetzt brauchen wir die Hauptmethode, die funktioniert und mit den Clients interagiert, Funktionen empfängt und Vorhersagen sendet

    import ast
    import pandas as pd
    from model import Model
    from server_socket import socketserver
    
    host = 'localhost'
    port = 9091 
    n_steps = 60
    TIMEFRAME = 24 | 0x4000
    model   = Model(n_steps, "EURUSD", TIMEFRAME)
    
    if __name__ == "__main__":
        serv = socketserver(host, port)
    
        while True:
            print("<<--Waiting Prices to Predict-->>")
            rates = pd.DataFrame(ast.literal_eval(serv.socket_receive()))
            rates = rates.rates.pct_change(1)
            rates.dropna(inplace=True)
            rates = rates.values.reshape((1, n_steps))
            serv.socket_send(str(model.predict(rates).flatten()[0]))
    

    Auf der MQL-Client-Seite müssen wir einen Roboter erstellen, der Daten sammelt und sie an unseren Server sendet, von dem er Vorhersagen erhält. Da unser Modell mit den Daten von geschlossenen Kerzen trainiert wurde, müssen wir eine Prüfung hinzufügen, dass die Daten nur gesammelt und gesendet werden, nachdem eine Kerze geschlossen wurde. Wir sollten die vollständigen Daten haben, um die Differenz für den Schluss des aktuellen Balkens, der gerade begonnen hat, vorherzusagen. Wir werden eine Funktion verwenden, die das Entstehen eines neuen Balkens überprüft.

    bool NewBar(void)
      {
       datetime time[];
       if(CopyTime(Symbol(), Period(), 0, 1, time) < 1)
          return false;
       if(time[0] == m_last_time)
          return false;
       return bool(m_last_time = time[0]);
      }
    

    Die Variable m_last_time wird im globalen Bereich deklariert. Sie speichert das Datum und die Uhrzeit des Taktbeginns. Es wird also geprüft, ob die Variable "time" von m_last_time abweicht: Wenn "true", dann hat ein neuer Takt begonnen. In diesem Fall sollte die Variable m_last_time durch time" ersetzt werden.

    Der EA sollte keine neue Position eröffnen, wenn es bereits eine offene Position gibt. Überprüfen Sie daher das Vorhandensein offener Positionen mit der CheckPosition-Methode, die für im globalen Bereich deklarierte Kauf- und Verkaufsvariablen true oder false setzt.

    void CheckPosition(void)
      {
       buy = false;
       sell  = false;
    
       if(PositionSelect(Symbol()))
         {
          if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY&&PositionGetInteger(POSITION_MAGIC) == InpMagicEA)
            {
             buy = true;
             sell  = false;
            }
          if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL&&PositionGetInteger(POSITION_MAGIC) == InpMagicEA)
            {
             sell = true;
             buy = false;
            }
         }
      }
    

    Wenn ein neuer Balken erscheint, prüfen Sie, ob es offene Positionen gibt. Wenn es eine offene Position gibt, warten Sie, bis sie geschlossen wird. Gibt es keine offene Position, leiten Sie den Verbindungsprozess ein, indem Sie die Methode IsConnected der Klasse CClienteSocket aufrufen.

    if(NewBar())
       {
          if(!Socket.IsConnected())
             Print("Error : ", GetLastError(), " Line: ", __LINE__);
        ...
       }
    

    Wenn true zurückgegeben wird, bedeutet dies, dass wir eine Verbindung mit dem Server herstellen, Daten sammeln und sie zurücksenden können.

    string payload = "{'rates':[";
    for(int i=InpSteps; i>=0; i--)
       {
          if(i>=1)
             payload += string(iClose(Symbol(), Period(), i))+",";
          else
             payload += string(iClose(Symbol(), Period(), i))+"]}";
       }
    

    Ich habe beschlossen, die Daten im Format {'rates':[1,2,3,4]} zu senden — auf diese Weise konvertieren wir sie in einen Pandas-Datenrahmen und müssen keine Zeit mit Datenkonvertierungen verschwenden.

    Sobald die Daten gesammelt wurden, senden Sie sie und warten auf eine Vorhersage, auf deren Grundlage eine Entscheidung getroffen werden kann. Ich werde einen gleitenden Durchschnitt verwenden, um die Kursrichtung zu überprüfen. Abhängig von der Kursrichtung und dem Wert der Vorhersage werden wir kaufen oder verkaufen.

    void OnTick(void)
      {
          ....
    
          bool send = Socket.SocketSend(payload);
          if(send)
            {
             if(!Socket.IsConnected())
                Print("Error : ", GetLastError(), " Line: ", __LINE__);
    
             double yhat = StringToDouble(Socket.SocketReceive());
    
             Print("Value of Prediction: ", yhat);
    
             if(CopyBuffer(handle, 0, 0, 4, m_fast_ma)==-1)
                Print("Error in CopyBuffer");
    
             if(m_fast_ma[1]>m_fast_ma[2]&&m_fast_ma[2]>m_fast_ma[3])
               {
                if((iClose(Symbol(), Period(), 2)>iOpen(Symbol(), Period(), 2)&&iClose(Symbol(), Period(), 1)>iOpen(Symbol(), Period(), 1))&&yhat<0)
                  {
                   m_trade.Sell(mim_vol);
                  }
               }
    
             if(m_fast_ma[1]<m_fast_ma[2]&&m_fast_ma[2]<m_fast_ma[3])
               {
                if((iClose(Symbol(), Period(), 2)<iOpen(Symbol(), Period(), 2)&&iClose(Symbol(), Period(), 1)<iOpen(Symbol(), Period(), 1))&&yhat>0)
                  {
                   m_trade.Buy(mim_vol);
                  }
               }
            }
            
          Socket.Close();
         }
      }
    

    Der letzte Schritt besteht darin, die zuvor aufgebaute Verbindung zu schließen und auf das Auftauchen eines neuen Balkens zu warten. Wenn sich ein neuer Balken öffnet, beginnt der Prozess des Sendens der Daten und des Empfangens der Vorhersage.

    Diese Architektur erweist sich als sehr nützlich und hat eine geringe Latenz. In einigen persönlichen Projekten verwende ich diese Architektur in Handelskonten, da sie es mir ermöglicht, die volle Leistung von MQL und die umfangreichen Ressourcen, die in Python für maschinelles Lernen verfügbar sind, zu nutzen.


    Was kommt als Nächstes?

    Im nächsten Artikel möchte ich eine flexiblere Architektur entwickeln, die die Verwendung von Modellen im Strategietester ermöglicht.

    Schlussfolgerung

    Ich hoffe, dies war ein nützliches kleines Tutorial über die Verwendung und Entwicklung verschiedener Python-Modelle und deren Integration in die MQL-Umgebung.

    In diesem Artikel werden wir:

    1. Einrichten der Python-Entwicklungsumgebung.
    2. Implementierung des Perceptron-Neurons und des MLP-Netzes in Python.
    3. Vorbereitung von univariaten Daten für das Lernen eines einfachen Netzwerks. 
    4. Wir haben die Architektur der Kommunikation zwischen Python und MQL eingerichtet.


    Weitere Ideen

    Dieser Abschnitt enthält einige zusätzliche Ideen, die Ihnen bei der Erweiterung dieses Tutorials helfen können.

    • Eingangsgröße. Untersuchen Sie die Anzahl der Tage, die für die Modelleingabe verwendet werden, zum Beispiel 21 Tage, 30 Tage.
    • Modellabstimmung. Untersuchen Sie verschiedene Strukturen und Hyperparameter, um die durchschnittliche Modellleistung zu erhalten.
    • Datenskalierung. Finden Sie heraus, ob Sie die Datengröße, wie Standardisierung und Normalisierung, nutzen können, um die Modellleistung zu verbessern.
    • Lerndiagnostik. Verwenden Sie Diagnosen wie Lernkurven für Lernverlust und Validierung; verwenden Sie den mittleren quadratischen Fehler, um die Struktur und die Hyperparameter des Modells abzustimmen.

    Wenn Sie diese Erweiterungsmöglichkeiten weiter erforschen, teilen Sie bitte Ihre Ideen mit.


    Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
    Originalartikel: https://www.mql5.com/pt/articles/9514

    Beigefügte Dateien |
    MQL5.zip (179.04 KB)
    Grafiken in der Bibliothek DoEasy (Teil 86): Grafische Objektkollektion - Verwaltung der Eigenschaftsänderungen Grafiken in der Bibliothek DoEasy (Teil 86): Grafische Objektkollektion - Verwaltung der Eigenschaftsänderungen
    In diesem Artikel geht es um die Kontrolle der Änderung von Eigenschaften sowie um das Entfernen und Umbenennen grafischer Objekte in der Bibliothek.
    Besser Programmieren (Teil 07): Tipps, um ein erfolgreicher freiberuflicher Entwickler zu werden Besser Programmieren (Teil 07): Tipps, um ein erfolgreicher freiberuflicher Entwickler zu werden
    Möchten Sie ein erfolgreicher Freelance-Entwickler auf MQL5 werden? Wenn die Antwort ja lautet, ist dieser Artikel genau das Richtige für Sie.
    Grafiken in der Bibliothek DoEasy (Teil 87): Grafische Kollektion - Verwaltung der Änderungen von Eigenschaften von Objekten auf allen offenen Charts Grafiken in der Bibliothek DoEasy (Teil 87): Grafische Kollektion - Verwaltung der Änderungen von Eigenschaften von Objekten auf allen offenen Charts
    In diesem Artikel werde ich meine Arbeit an der Kontrolle von Standardereignissen für grafische Objekte fortsetzen und eine Funktionalität schaffen, die es den Nutzern ermöglicht, Änderungen der Eigenschaften von grafischen Objekten zu kontrollieren, die auf beliebigen im Terminal geöffneten Charts platziert sind.
    Grafiken in der Bibliothek DoEasy (Teil 85): Grafische Objektkollektion - Hinzufügen neu erstellter Objekte Grafiken in der Bibliothek DoEasy (Teil 85): Grafische Objektkollektion - Hinzufügen neu erstellter Objekte
    In diesem Artikel werde ich die Entwicklung der abgeleiteten Klassen der abstrakten grafischen Objektklasse abschließen und mit der Implementierung der Möglichkeit beginnen, diese Objekte in der Klasse Kollektion zu speichern. Insbesondere werde ich die Funktionalität für das Hinzufügen von neu erstellten grafischen Standardobjekten in die Kollektionsklasse erstellen.