English Русский Español Português
preview
Finden von nutzerdefinierten Währungspaar-Mustern in Python mit MetaTrader 5

Finden von nutzerdefinierten Währungspaar-Mustern in Python mit MetaTrader 5

MetaTrader 5Handel | 16 Mai 2025, 07:56
36 0
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Einführung in die Forex-Musteranalyse

Was sehen Anfänger, wenn sie sich zum ersten Mal die Charts von Währungspaaren ansehen? Viele Intraday-Schwankungen, Volatilitätsanstiege und -rückgänge, Trendwechsel und vieles mehr. Höhen, Tiefen, Zickzack - wie findet man das alles heraus? Ich begann auch meine Bekanntschaft mit dem Forex, indem ich mich in das Studium der Kursmusteranalyse vertiefte.

Viele Dinge in unserer Welt erscheinen auf den ersten Blick chaotisch. Aber jeder erfahrene Spezialist sieht in seinem persönlichen Umfeld Muster und Möglichkeiten, die für andere verwirrend erscheinen. Das Gleiche gilt für die Charts von Währungspaaren. Wenn wir versuchen, dieses Chaos zu systematisieren, können wir verborgene Muster entdecken, die auf künftige Kursbewegungen hindeuten können.

Aber wie findet man sie? Wie kann man ein echtes Muster von zufälligem Rauschen unterscheiden? Hier beginnt der Spaß. Ich beschloss, mein eigenes System zur Musteranalyse mit Python und MetaTrader 5 zu entwickeln. Eine Art Symbiose aus Mathematik und Programmierung zur Eroberung des Forex.

Die Idee war, eine große Menge historischer Daten mit Hilfe eines Algorithmus zu untersuchen, der wiederkehrende Muster findet und ihre Leistung bewertet. Klingt interessant? In der Realität erwies sich die Umsetzung als nicht so einfach.


Einrichten der Umgebung: Installation der erforderlichen Bibliotheken und Verbindung zum MetaTrader 5

Unsere erste Aufgabe besteht also darin, Python zu installieren. Es kann von der offiziellen Website python.org heruntergeladen werden. Stellen Sie sicher, dass das Kästchen „Python zu PATH hinzufügen“ markiert ist. 

Der nächste wichtige Schritt sind Bibliotheken. Wir werden ein paar von ihnen brauchen. Die wichtigste davon ist MetaTrader 5. Außerdem gibt es „Pandas“ für die Arbeit mit Daten. Und vielleicht „numpy“. Öffnen Sie die Befehlszeile und geben Sie ein:

pip install MetaTrader5 pandas numpy matplotlib pytz

Als erstes müssen Sie MetaTrader 5 selbst installieren. Laden Sie es von der offiziellen Website Ihres Brokers herunter und installieren Sie es. Nichts Kompliziertes.

Jetzt müssen wir den Pfad zum Terminal finden. In der Regel handelt es sich um eine Datei wie „C:\Programme\MetaTrader 5\terminal64.exe“.

Öffnen Sie nun Python und geben Sie ein:

import MetaTrader5 as mt5

if not mt5.initialize(path="C:/Program Files/MetaTrader 5/terminal64.exe"):
    print("MetaTrader 5 initialization failed.")
    mt5.shutdown()
else:
    print("MetaTrader 5 initialized successfully.")

Starten Sie es. Wenn Sie die Meldung über die erfolgreiche Terminalinitialisierung sehen, ist alles richtig gemacht worden.

Sie möchten sicherstellen, dass alles funktioniert? Versuchen wir, einige Daten zu erhalten:

import MetaTrader5 as mt5
import pandas as pd
from datetime import datetime

if not mt5.initialize():
    print("Oops! Something went wrong.")
    mt5.shutdown()

eurusd_ticks = mt5.copy_ticks_from("EURUSD", datetime.now(), 10, mt5.COPY_TICKS_ALL)
ticks_frame = pd.DataFrame(eurusd_ticks)
print("Look at this beauty:")
print(ticks_frame)

mt5.shutdown()

Wenn Sie eine Datentabelle sehen, dann herzlichen Glückwunsch! Sie haben gerade Ihren ersten Schritt in die Welt des algorithmischen Devisenhandels mit Python gemacht. Es ist nicht so schwierig, wie es scheint.


Code-Struktur: Grundlegende Funktionen und ihr Zweck

Beginnen wir also mit der Analyse der Codestruktur. Dies ist ein komplettes System zur Analyse von Mustern auf dem Devisenmarkt. 

Wir beginnen mit der wichtigsten Funktion des Systems - der Funktion find_patterns. Diese Funktion durchsucht historische Daten, um Muster einer bestimmten Länge zu erkennen. Nachdem wir die ersten Muster gefunden haben, müssen wir ihre Effizienz bewerten. Diese Funktion speichert auch das letzte Muster zur späteren Verwendung.

Die nächste Funktion ist calculate_winrate_and_frequency. Diese Funktion analysiert die gefundenen Muster - hier ist die Häufigkeit des Auftretens und die Gewinnrate, sowie die Sortierung der Muster.

Die Funktion process_currency_pair spielt ebenfalls eine wichtige Rolle. Dies ist ein ziemlich wichtiger Prozess. Es werden die Daten geladen, durchlaufen, nach Mustern unterschiedlicher Länge gesucht und es werden die 300 wichtigsten Muster für Verkäufe und Käufe angegeben. Was den Anfang des Codes betrifft, so finden Sie hier die Initialisierung, die Parametereinstellungen, das Chart-Intervall (TF) und den Zeitraum (in meinem Fall von 1990 bis 2024).

Gehen wir nun zur Hauptcode-Ausführungsschleife über. Zu den Merkmalen des Mustersuchalgorithmus gehören unterschiedliche Musterlängen, da kurze Muster häufig vorkommen, aber keine Zuverlässigkeit bieten, während lange Muster zu selten sind, obwohl sie effektiver sind. Wir sollten alle Dimensionen berücksichtigen.


Abrufen von Daten aus MetaTrader 5: Funktion copy_rates_range

Unsere erste Funktion soll Daten vom Terminal empfangen. Schauen wir uns den Code an:

import MetaTrader5 as mt5
import pandas as pd
import time
from datetime import datetime, timedelta
import pytz

# List of major currency pairs
major_pairs = ['EURUSD']

# Setting up data request parameters
timeframe = mt5.TIMEFRAME_H4
start_date = pd.Timestamp('1990-01-01')
end_date = pd.Timestamp('2024-05-31')

def process_currency_pair(symbol):
    max_retries = 5
    retries = 0
    while retries < max_retries:
        try:
            # Loading OHLC data
            rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date)
            if rates is None:
                raise ValueError("No data received")
            ohlc_data = pd.DataFrame(rates)
            ohlc_data['time'] = pd.to_datetime(ohlc_data['time'], unit='s')
            break
        except Exception as e:
            print(f"Error loading data for {symbol}: {e}")
            retries += 1
            time.sleep(2)  # Wait before retrying

    if retries == max_retries:
        print(f"Failed to load data for {symbol} after {max_retries} attempts")
        return

    # Further data processing...

Was geht in diesem Code vor sich? Zunächst definieren wir unsere Währungspaare. Im Moment haben wir nur EURUSD, aber Sie können andere hinzufügen. Dann legen wir das Zeitintervall fest. H4 ist 4 Stunden. Dies ist der optimale Zeitpunkt. 

Als Nächstes folgen die Termine. Von 1990 bis 2024. Wir werden eine Menge historischer Kurse benötigen. Je mehr Daten wir haben, desto genauer ist unsere Analyse. Nun zur Hauptsache - der Funktion process_currency_pair. Es lädt Daten mit copy_rates_range.

Was bekommen wir als Ergebnis? DataFrame mit historischen Daten. Time, Open, High, Low, Close (OHLC) - alles, was für die Arbeit notwendig ist.

Wenn etwas schief geht, werden Fehler erkannt, auf dem Bildschirm angezeigt und wir versuchen es erneut.


Verarbeitung von Zeitreihen: Umwandlung von OHLC-Daten in Kursbewegungsrichtungen

Kehren wir zu unserer Hauptaufgabe zurück. Wir wollen die chaotischen Schwankungen des Forex-Marktes in etwas Ordentlicheres verwandeln - in Trends und Umkehrungen. Wie können wir das tun? Wir werden die Preise in Richtungen umwandeln.

Hier ist unser Code:

# Fill missing values with the mean
ohlc_data.fillna(ohlc_data.mean(), inplace=True)

# Convert price movements to directions
ohlc_data['direction'] = np.where(ohlc_data['close'].diff() > 0, 'up', 'down')

Was ist hier eigentlich los? Zunächst füllen wir die Lücken. Lücken können unser Endergebnis erheblich verschlechtern. Wir füllen sie mit Durchschnittswerten. 

Und jetzt kommt der interessanteste Teil. Wir erstellen eine neue Spalte mit dem Namen „direction“ (Richtung). Dort übersetzen wir Preisdaten in Daten, die das Trendverhalten simulieren. Es funktioniert auf elementare Art und Weise:

  • Wenn der aktuelle Schlusskurs höher ist als der vorherige, schreiben wir „up“.
  • Wenn sie darunter liegt, schreiben wir „down“.

Eine recht einfache Formulierung, aber sehr wirksam. Statt komplexer Zahlen haben wir nun eine einfache Abfolge von „Ups“ und „Downs“. Diese Reihenfolge ist für die menschliche Wahrnehmung viel einfacher. Aber warum brauchen wir ihn? Diese „Hochs“ und „Tiefs“ sind die Bausteine für unsere Muster. Von ihnen werden wir ein vollständiges Bild des Marktgeschehens erhalten.


Mustersuchalgorithmus: Funktion find_patterns

Wir haben also eine Abfolge von „Ups“ und „Downs“. Als Nächstes werden wir nach sich wiederholenden Mustern in dieser Sequenz suchen.

Hier ist die Funktion find_patterns:

def find_patterns(data, pattern_length, direction):
    patterns = defaultdict(list)
    last_pattern = None
    last_winrate = None
    last_frequency = None

    for i in range(len(data) - pattern_length - 6):
        pattern = tuple(data['direction'][i:i+pattern_length])
        if data['direction'][i+pattern_length+6] == direction:
            patterns[pattern].append(True)
        else:
            patterns[pattern].append(False)

    # Check last prices for pattern match
    last_pattern_tuple = tuple(data['direction'][-pattern_length:])
    if last_pattern_tuple in patterns:
        last_winrate = np.mean(patterns[last_pattern_tuple]) * 100
        last_frequency = len(patterns[last_pattern_tuple])
        last_pattern = last_pattern_tuple

    return patterns, last_pattern, last_winrate, last_frequency

Wie funktioniert das alles?

  • Wir erstellen das Wörterbuch mit den Mustern, „patterns“. Dies wird als eine Art Bibliothek dienen, in der wir alle gefundenen Muster speichern werden.
  • Dann beginnen wir, die Daten zu iterieren. Wir nehmen ein Datenmuster der Länge pattern_length (dies kann 3, 4, 5 usw. bis zu 25 sein) und schauen, was 6 Balken danach passiert.
  • Wenn sich der Kurs nach 6 Balken in die gewünschte Richtung bewegt (nach oben bei Kaufmustern oder nach unten bei Verkaufsmustern), setzen wir True. Wenn nicht - False.
  • Wir tun dies für alle möglichen Datenproben. Wir sollten ähnliche Muster erhalten: „up-up-down“ - True, „down-up-up“ - False und so weiter.
  • Als Nächstes prüfen wir, ob eines der Muster, auf die wir zuvor gestoßen sind, gerade gebildet wird. Ist dies der Fall, berechnen wir die Gewinnrate (Prozentsatz der erfolgreichen Treffer) und die Häufigkeit des Auftretens.

Auf diese Weise verwandeln wir eine einfache Abfolge von „Ups“ und „Downs“ in ein ziemlich leistungsfähiges Prognoseinstrument. Aber das ist nicht alles. Als Nächstes werden wir diese Muster sortieren, die effizientesten auswählen und sie analysieren.


Berechnung von Musterstatistiken: WinRate und Häufigkeit des Auftretens

Da wir nun eine Reihe von Mustern haben, müssen wir die besten auswählen.

Werfen wir einen Blick auf unseren Code:

def calculate_winrate_and_frequency(patterns):
    results = []
    for pattern, outcomes in patterns.items():
        winrate = np.mean(outcomes) * 100
        frequency = len(outcomes)
        results.append((pattern, winrate, frequency))
    results.sort(key=lambda x: x[1], reverse=True)
    return results

Hier nehmen wir jedes Muster und seine Ergebnisse (wir haben sie zuvor als Wahr und Falsch bezeichnet) und berechnen dann die Gewinnrate „winrate“ - das ist unser Produktivitätsprozentsatz. Wenn ein Muster in 7 von 10 Fällen funktioniert, beträgt seine Gewinnrate 70 %. Wir zählen auch die Häufigkeit, d. h. wie oft das Muster auftritt. Je öfter, desto zuverlässiger sind unsere Statistiken. All dies wird in die Ergebnisliste aufgenommen. Und schließlich die Sortierung. Wir setzen die besten Muster an die Spitze der Liste.


Ergebnisse sortieren: Auswählen von signifikanten Mustern

Jetzt haben wir genug Daten. Aber wir werden nicht alle von ihnen brauchen. Wir müssen sie aussortieren.

filtered_buy_results = [result for result in all_buy_results if result[2] > 20]
filtered_sell_results = [result for result in all_sell_results if result[2] > 20]

filtered_buy_results.sort(key=lambda x: x[1], reverse=True)
top_300_buy_patterns = filtered_buy_results[:300]

filtered_sell_results.sort(key=lambda x: x[1], reverse=True)
top_300_sell_patterns = filtered_sell_results[:300]

Wir haben die Sortierung auf ähnliche Weise eingerichtet. Zunächst sortieren wir alle Muster aus, die weniger als 20 Mal vorkommen. Wie die Statistik zeigt, sind seltene Muster weniger zuverlässig. 

Anschließend sortieren wir die verbleibenden Muster nach der Gewinnrate. Die effizientesten werden an den Anfang der Liste gesetzt. Als Ergebnis wählen wir die besten 300 aus. Das ist alles, was von einer Vielzahl von Mustern übrig bleiben sollte, deren Anzahl tausend übersteigt.  


Arbeiten mit verschiedenen Längen der Muster: von 3 bis 25

Jetzt müssen wir die Mustervariationen auswählen, die statistisch gesehen und beim Handel durchgängig Gewinne bringen werden. Die Optionen sind unterschiedlich lang. Sie können entweder aus 3 oder 25 Kursbewegungen bestehen. Lassen Sie uns alle möglichen überprüfen:

pattern_lengths = range(3, 25)  # Pattern lengths from 3 to 25
all_buy_patterns = {}
all_sell_patterns = {}

for pattern_length in pattern_lengths:
    buy_patterns, last_buy_pattern, last_buy_winrate, last_buy_frequency = find_patterns(ohlc_data, pattern_length, 'up')
    sell_patterns, last_sell_pattern, last_sell_winrate, last_sell_frequency = find_patterns(ohlc_data, pattern_length, 'down')
    all_buy_patterns[pattern_length] = buy_patterns
    all_sell_patterns[pattern_length] = sell_patterns

Wir starten unseren Mustersuchfilter für jede Länge von 3 bis 25. Warum verwenden wir diese Implementierung? Muster, die weniger als drei Bewegungen umfassen, sind zu unzuverlässig - wir haben dies bereits erwähnt. Muster, die länger als 25 sind, sind zu selten. Für jede Länge suchen wir sowohl nach Kauf- als auch nach Verkaufsmustern. 

Aber warum brauchen wir so viele verschiedene Längen? Kurze Muster können schnelle Marktumschwünge erfassen, während lange Muster langfristige Trends aufzeigen können. Wir wissen nicht im Voraus, was wirksamer sein wird, also testen wir alles.


Analyse von Kauf- und Verkaufsmustern

Nun, da wir eine Auswahl von Mustern unterschiedlicher Länge haben, ist es an der Zeit zu bestimmen, welche tatsächlich funktionieren. 

Hier ist unser Code in Aktion:

all_buy_results = []
for pattern_length, patterns in all_buy_patterns.items():
    results = calculate_winrate_and_frequency(patterns)
    all_buy_results.extend(results)

all_sell_results = []
for pattern_length, patterns in all_sell_patterns.items():
    results = calculate_winrate_and_frequency(patterns)
    all_sell_results.extend(results)

Wir nehmen jedes Muster - sowohl Kauf als auch Verkauf - und sortieren es durch unseren Gewinnraten- und Häufigkeitsrechner. 

Aber wir zählen nicht nur Statistiken. Wir suchen nach dem Unterschied zwischen Kauf- und Verkaufsmustern. Warum ist das wichtig? Denn der Markt kann sich bei steigenden und fallenden Kursen unterschiedlich verhalten. Manchmal funktionieren Kaufmuster besser, während manchmal Verkaufsmuster zuverlässiger sind.

Als Nächstes werden wir zum nächsten Schritt übergehen und Muster unterschiedlicher Länge miteinander vergleichen. Es kann sich herausstellen, dass kurze Muster besser geeignet sind, um den Einstiegspunkt in den Markt zu bestimmen, und lange Muster besser geeignet sind, um den langfristigen Trend zu bestimmen. Dasselbe kann auch andersherum passieren. Aus diesem Grund analysieren wir alles und verwerfen nichts im Voraus.

Am Ende dieser Analyse stehen die ersten Ergebnisse: welche Muster funktionieren besser beim Kauf, welche beim Verkauf, welche Länge der Muster ist unter verschiedenen Marktbedingungen am effektivsten. Mit diesen Daten können wir bereits eine Analyse der Preise auf dem Devisenmarkt durchführen.

Aber denken Sie daran, dass auch das beste Muster keine Garantie für den Erfolg ist. Der Markt ist voller Überraschungen. Unsere Aufgabe ist es, die Erfolgschancen zu erhöhen, und das tun wir, indem wir die Muster von allen Seiten analysieren.


Blick in die Zukunft: Vorhersage auf der Grundlage aktueller Muster

Nun ist es an der Zeit für einige Vorhersagen. Werfen wir einen Blick auf unseren Code für die Vorhersagen:

if last_buy_pattern:
    print(f"\nLast buy pattern for {symbol}: {last_buy_pattern}, Winrate: {last_buy_winrate:.2f}%, Frequency: {last_buy_frequency}")
    print(f"Forecast: Price will likely go up.")
if last_sell_pattern:
    print(f"\nLast sell pattern for {symbol}: {last_sell_pattern}, Winrate: {last_sell_winrate:.2f}%, Frequency: {last_sell_frequency}")
    print(f"Forecast: Price will likely go down.")

Wir sehen uns das letzte Muster an, das sich gebildet hat, und versuchen, die Zukunft vorherzusagen und unsere Handelsanalyse durchzuführen.

Bitte beachten Sie, dass wir zwei Szenarien in Betracht ziehen: ein Kauf- und ein Verkaufsmuster. Warum? Denn der Markt ist eine ewige Konfrontation zwischen Bullen und Bären, Käufern und Verkäufern. Wir sollten auf jede Wendung der Ereignisse vorbereitet sein.

Für jedes Muster geben wir drei Schlüsselparameter aus: das Muster selbst, seine Gewinnrate und die Häufigkeit des Auftretens. Die Gewinnrate ist besonders wichtig. Wenn ein Kaufmuster eine Gewinnrate von 70 % aufweist, bedeutet dies, dass der Kurs in 70 % der Fälle, in denen dieses Muster auftrat, tatsächlich gestiegen ist. Das sind ziemlich gute Ergebnisse. Aber denken Sie daran, auch 90 % sind keine Garantie. In der Welt der Devisen ist immer Platz für Überraschungen.

Auch die Frequenz spielt eine wichtige Rolle. Ein Muster, das häufig auftritt, ist zuverlässiger als ein seltenes.

Der interessanteste Teil ist unsere Prognose. „Der Preis wird wahrscheinlich steigen“ oder „Der Preis wird wahrscheinlich fallen“. Diese Prognosen bringen eine gewisse Befriedigung über die geleistete Arbeit. Aber denken Sie daran, dass auch die genaueste Vorhersage nur eine Wahrscheinlichkeit ist, keine Garantie. Der Devisenmarkt ist ziemlich schwer vorherzusagen. Nachrichten, wirtschaftliche Ereignisse und sogar Tweets von einflussreichen Personen können die Richtung der Kursbewegung innerhalb von Sekunden ändern.

Unser Code ist also kein Allheilmittel, sondern ein sehr intelligenter EA. Sie kann wie folgt interpretiert werden: „Auf der Grundlage historischer Daten haben wir Grund zu der Annahme, dass der Preis steigen (oder fallen) wird". Es liegt an Ihnen zu entscheiden, ob Sie in den Markt eintreten wollen oder nicht. Die Anwendung dieser Prognosen ist ein durchdachter Prozess. Sie haben Informationen über mögliche Bewegungen, aber jeder Schritt muss dennoch mit Bedacht und unter Berücksichtigung der allgemeinen Marktlage unternommen werden.


Zeichnen der Zukunft: Visualisierung der besten Muster und Prognosen

Fügen wir unserem Code ein wenig Visualisierungsmagie hinzu:

import matplotlib.pyplot as plt

def visualize_patterns(patterns, title, filename):
    patterns = patterns[:20]  # Take top 20 for clarity
    patterns.reverse()  # Reverse the list to display it correctly on the chart

    fig, ax = plt.subplots(figsize=(12, 8))
    
    winrates = [p[1] for p in patterns]
    frequencies = [p[2] for p in patterns]
    labels = [' '.join(p[0]) for p in patterns]

    ax.barh(range(len(patterns)), winrates, align='center', color='skyblue', zorder=10)
    ax.set_yticks(range(len(patterns)))
    ax.set_yticklabels(labels)
    ax.invert_yaxis()  # Invert the Y axis to display the best patterns on top

    ax.set_xlabel('Winrate (%)')
    ax.set_title(title)

    # Add occurrence frequency
    for i, v in enumerate(winrates):
        ax.text(v + 1, i, f'Freq: {frequencies[i]}', va='center')

    plt.tight_layout()
    plt.savefig(filename)
    plt.close()

# Visualize top buy and sell patterns
visualize_patterns(top_300_buy_patterns, f'Top 20 Buy Patterns for {symbol}', 'top_buy_patterns.png')
visualize_patterns(top_300_sell_patterns, f'Top 20 Sell Patterns for {symbol}', 'top_sell_patterns.png')

# Visualize the latest pattern and forecast
def visualize_forecast(pattern, winrate, frequency, direction, symbol, filename):
    fig, ax = plt.subplots(figsize=(8, 6))
    
    ax.bar(['Winrate'], [winrate], color='green' if direction == 'up' else 'red')
    ax.set_ylim(0, 100)
    ax.set_ylabel('Winrate (%)')
    ax.set_title(f'Forecast for {symbol}: Price will likely go {direction}')

    ax.text(0, winrate + 5, f'Pattern: {" ".join(pattern)}', ha='center')
    ax.text(0, winrate - 5, f'Frequency: {frequency}', ha='center')

    plt.tight_layout()
    plt.savefig(filename)
    plt.close()

if last_buy_pattern:
    visualize_forecast(last_buy_pattern, last_buy_winrate, last_buy_frequency, 'up', symbol, 'buy_forecast.png')
if last_sell_pattern:
    visualize_forecast(last_sell_pattern, last_sell_winrate, last_sell_frequency, 'down', symbol, 'sell_forecast.png')

Wir haben zwei Funktionen erstellt: visualize_patterns und visualize_forecast. Der erste zeichnet ein informatives horizontales Balkendiagramm mit den 20 besten Mustern, ihren Gewinnraten und der Häufigkeit ihres Auftretens. Die zweite erstellt eine visuelle Darstellung unserer Vorhersage auf der Grundlage des aktuellen Musters.

Bei Mustern verwenden wir horizontale Spalten, da die Muster lang sein können und dadurch leichter zu lesen sind. Unsere Farbe ist für das menschliche Auge angenehm wahrnehmbar - himmelblau.

Wir speichern unsere Meisterwerke in PNG-Dateien.


Testen und Backtesting des Musteranalysesystems

Wir haben unser Musteranalysesystem entwickelt, aber woher wissen wir, ob es tatsächlich funktioniert? Zu diesem Zweck müssen wir sie mit historischen Daten testen. 

Hier ist unser Code, der für diese Aufgabe benötigt wird:

def simulate_trade(data, direction, entry_price, take_profit, stop_loss):
    for i, row in data.iterrows():
        current_price = row['close']
        
        if direction == "BUY":
            if current_price >= entry_price + take_profit:
                return {'profit': take_profit, 'duration': i}
            elif current_price <= entry_price - stop_loss:
                return {'profit': -stop_loss, 'duration': i}
        else:  # SELL
            if current_price <= entry_price - take_profit:
                return {'profit': take_profit, 'duration': i}
            elif current_price >= entry_price + stop_loss:
                return {'profit': -stop_loss, 'duration': i}
    
    # If the loop ends without reaching TP or SL, close at the current price
    last_price = data['close'].iloc[-1]
    profit = (last_price - entry_price) if direction == "BUY" else (entry_price - last_price)
    return {'profit': profit, 'duration': len(data)}

def backtest_pattern_system(data, buy_patterns, sell_patterns):
    equity_curve = [10000]  # Initial capital $10,000
    trades = []
    
    for i in range(len(data) - max(len(p[0]) for p in buy_patterns + sell_patterns)):
        current_data = data.iloc[:i+1]
        last_pattern = tuple(current_data['direction'].iloc[-len(buy_patterns[0][0]):])
        
        matching_buy = [p for p in buy_patterns if p[0] == last_pattern]
        matching_sell = [p for p in sell_patterns if p[0] == last_pattern]
        
        if matching_buy and not matching_sell:
            entry_price = current_data['close'].iloc[-1]
            take_profit = 0.001  # 10 pips
            stop_loss = 0.0005  # 5 pips
            trade_result = simulate_trade(data.iloc[i+1:], "BUY", entry_price, take_profit, stop_loss)
            trades.append(trade_result)
            equity_curve.append(equity_curve[-1] + trade_result['profit'] * 10000)  # Multiply by 10000 to convert to USD
        elif matching_sell and not matching_buy:
            entry_price = current_data['close'].iloc[-1]
            take_profit = 0.001  # 10 pips
            stop_loss = 0.0005  # 5 pips
            trade_result = simulate_trade(data.iloc[i+1:], "SELL", entry_price, take_profit, stop_loss)
            trades.append(trade_result)
            equity_curve.append(equity_curve[-1] + trade_result['profit'] * 10000)  # Multiply by 10000 to convert to USD
        else:
            equity_curve.append(equity_curve[-1])
    
    return equity_curve, trades

# Conduct a backtest
equity_curve, trades = backtest_pattern_system(ohlc_data, top_300_buy_patterns, top_300_sell_patterns)

# Visualizing backtest results
plt.figure(figsize=(12, 6))
plt.plot(equity_curve)
plt.title('Equity Curve')
plt.xlabel('Trades')
plt.ylabel('Equity ($)')
plt.savefig('equity_curve.png')
plt.close()

# Calculating backtest statistics
total_profit = equity_curve[-1] - equity_curve[0]
win_rate = sum(1 for trade in trades if trade['profit'] > 0) / len(trades) if trades else 0
average_profit = sum(trade['profit'] for trade in trades) / len(trades) if trades else 0

print(f"\nBacktest Results:")
print(f"Total Profit: ${total_profit:.2f}")
print(f"Win Rate: {win_rate:.2%}")
print(f"Average Profit per Trade: ${average_profit*10000:.2f}")
print(f"Total Trades: {len(trades)}")

Was ist hier eigentlich los? Die Funktion simulate_trade ist unser Simulator für einen einzelnen Handel. Er überwacht den Kurs und schließt das Handelsgeschäft, wenn Take Profit oder Stop Loss erreicht ist. 

backtest_pattern_system ist eine wichtigere Funktion. Es geht die historischen Daten Schritt für Schritt und Tag für Tag durch und prüft, ob sich eines unserer Muster gebildet hat. Haben Sie ein Kaufmuster gefunden? Dann kaufen wir. Haben Sie eine Verkaufsmuster gefunden? Wir verkaufen.

Wir verwenden einen festen Take Profit von 100 Punkten und einen Stop Loss von 50 Punkten. Wir müssen die Grenzen für einen zufriedenstellenden Gewinn festlegen - nicht zu viel, damit wir nicht mehr als das Limit riskieren, aber auch nicht zu wenig, damit der Gewinn wachsen kann.

Nach jedem Handel aktualisieren wir unsere Aktienkurve. Am Ende unserer Arbeit erhalten wir das folgende Ergebnis: wie viel wir insgesamt verdient haben, wie viel Prozent der Transaktionen profitabel waren und wie hoch der durchschnittliche Gewinn pro Handel ist. Und natürlich visualisieren wir die Ergebnisse.

Wir wollen die Mustersuche mit der Sprache MQL5 implementieren. Hier ist unser Code:

//+------------------------------------------------------------------+
//|                                       PatternProbabilityIndicator|
//|                                                 Copyright 2024   |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Your Name Here"
#property link      "https://www.mql5.com"
#property version   "1.06"
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   2

//--- plot BuyProbability
#property indicator_label1  "BuyProbability"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrGreen
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

//--- plot SellProbability
#property indicator_label2  "SellProbability"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

//--- input parameters
input int      InpPatternLength = 5;    // Pattern Length (3-10)
input int      InpLookback     = 1000;  // Lookback Period (100-5000)
input int      InpForecastHorizon = 6;  // Forecast Horizon (1-20)

//--- indicator buffers
double         BuyProbabilityBuffer[];
double         SellProbabilityBuffer[];

//--- global variables
int            g_pattern_length;
int            g_lookback;
int            g_forecast_horizon;
string         g_patterns[];
int            g_pattern_count;
int            g_pattern_occurrences[];
int            g_pattern_successes[];
int            g_total_bars;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- validate inputs
   if(InpPatternLength < 3 || InpPatternLength > 10)
   {
      Print("Invalid Pattern Length. Must be between 3 and 10.");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   if(InpLookback < 100 || InpLookback > 5000)
   {
      Print("Invalid Lookback Period. Must be between 100 and 5000.");
      return INIT_PARAMETERS_INCORRECT;
   }

   if(InpForecastHorizon < 1 || InpForecastHorizon > 20)
   {
      Print("Invalid Forecast Horizon. Must be between 1 and 20.");
      return INIT_PARAMETERS_INCORRECT;
   }

   //--- indicator buffers mapping
   SetIndexBuffer(0, BuyProbabilityBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, SellProbabilityBuffer, INDICATOR_DATA);
   
   //--- set accuracy
   IndicatorSetInteger(INDICATOR_DIGITS, 2);
   
   //--- set global variables
   g_pattern_length = InpPatternLength;
   g_lookback = InpLookback;
   g_forecast_horizon = InpForecastHorizon;
   
   //--- generate all possible patterns
   if(!GeneratePatterns())
   {
      Print("Failed to generate patterns.");
      return INIT_FAILED;
   }
   
   g_total_bars = iBars(_Symbol, PERIOD_CURRENT);
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 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[])
{
   //--- check for rates total
   if(rates_total <= g_lookback + g_pattern_length + g_forecast_horizon)
   {
      Print("Not enough data for calculation.");
      return 0;
   }

   int start = (prev_calculated > g_lookback + g_pattern_length + g_forecast_horizon) ? 
               prev_calculated - 1 : g_lookback + g_pattern_length + g_forecast_horizon;
   
   if(ArraySize(g_pattern_occurrences) != g_pattern_count)
   {
      ArrayResize(g_pattern_occurrences, g_pattern_count);
      ArrayResize(g_pattern_successes, g_pattern_count);
   }
   
   ArrayInitialize(g_pattern_occurrences, 0);
   ArrayInitialize(g_pattern_successes, 0);
   
   // Pre-calculate patterns for efficiency
   string patterns[];
   ArrayResize(patterns, rates_total);
   for(int i = g_pattern_length; i < rates_total; i++)
   {
      patterns[i] = "";
      for(int j = 0; j < g_pattern_length; j++)
      {
         patterns[i] += (close[i-j] > close[i-j-1]) ? "U" : "D";
      }
   }
   
   // Main calculation loop
   for(int i = start; i < rates_total; i++)
   {
      string current_pattern = patterns[i];
      
      if(StringLen(current_pattern) != g_pattern_length) continue;
      
      double buy_probability = CalculateProbability(current_pattern, true, close, patterns, i);
      double sell_probability = CalculateProbability(current_pattern, false, close, patterns, i);
      
      BuyProbabilityBuffer[i] = buy_probability;
      SellProbabilityBuffer[i] = sell_probability;
   }
   
   // Update Comment with pattern statistics if total bars changed
   if(g_total_bars != iBars(_Symbol, PERIOD_CURRENT))
   {
      g_total_bars = iBars(_Symbol, PERIOD_CURRENT);
      UpdatePatternStatistics();
   }
   
   return(rates_total);
}

//+------------------------------------------------------------------+
//| Generate all possible patterns                                   |
//+------------------------------------------------------------------+
bool GeneratePatterns()
{
   g_pattern_count = (int)MathPow(2, g_pattern_length);
   if(!ArrayResize(g_patterns, g_pattern_count))
   {
      Print("Failed to resize g_patterns array.");
      return false;
   }
   
   for(int i = 0; i < g_pattern_count; i++)
   {
      string pattern = "";
      for(int j = 0; j < g_pattern_length; j++)
      {
         pattern += ((i >> j) & 1) ? "U" : "D";
      }
      g_patterns[i] = pattern;
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| Calculate probability for a given pattern                        |
//+------------------------------------------------------------------+
double CalculateProbability(const string &pattern, bool is_buy, const double &close[], const string &patterns[], int current_index)
{
   if(StringLen(pattern) != g_pattern_length || current_index < g_lookback)
   {
      return 50.0; // Return neutral probability on error
   }

   int pattern_index = ArraySearch(g_patterns, pattern);
   if(pattern_index == -1)
   {
      return 50.0;
   }

   int total_occurrences = 0;
   int successful_predictions = 0;
   
   for(int i = g_lookback; i > g_pattern_length + g_forecast_horizon; i--)
   {
      int historical_index = current_index - i;
      if(historical_index < 0 || historical_index + g_pattern_length + g_forecast_horizon >= ArraySize(close))
      {
         continue;
      }

      if(patterns[historical_index] == pattern)
      {
         total_occurrences++;
         g_pattern_occurrences[pattern_index]++;
         if(is_buy && close[historical_index + g_pattern_length + g_forecast_horizon] > close[historical_index + g_pattern_length])
         {
            successful_predictions++;
            g_pattern_successes[pattern_index]++;
         }
         else if(!is_buy && close[historical_index + g_pattern_length + g_forecast_horizon] < close[historical_index + g_pattern_length])
         {
            successful_predictions++;
            g_pattern_successes[pattern_index]++;
         }
      }
   }
   
   return (total_occurrences > 0) ? (double)successful_predictions / total_occurrences * 100 : 50;
}

//+------------------------------------------------------------------+
//| Update pattern statistics and display in Comment                 |
//+------------------------------------------------------------------+
void UpdatePatternStatistics()
{
   string comment = "Pattern Statistics:\n";
   comment += "Pattern Length: " + IntegerToString(g_pattern_length) + "\n";
   comment += "Lookback Period: " + IntegerToString(g_lookback) + "\n";
   comment += "Forecast Horizon: " + IntegerToString(g_forecast_horizon) + "\n\n";
   comment += "Top 5 Patterns:\n";
   
   int sorted_indices[];
   ArrayResize(sorted_indices, g_pattern_count);
   for(int i = 0; i < g_pattern_count; i++) sorted_indices[i] = i;
   
   // Use quick sort for better performance
   ArraySort(sorted_indices);
   
   for(int i = 0; i < 5 && i < g_pattern_count; i++)
   {
      int idx = sorted_indices[g_pattern_count - 1 - i];  // Reverse order for descending sort
      double win_rate = g_pattern_occurrences[idx] > 0 ? 
                        (double)g_pattern_successes[idx] / g_pattern_occurrences[idx] * 100 : 0;
      
      comment += g_patterns[idx] + ": " +
                 "Occurrences: " + IntegerToString(g_pattern_occurrences[idx]) + ", " +
                 "Win Rate: " + DoubleToString(win_rate, 2) + "%\n";
   }
   
   Comment(comment);
}

//+------------------------------------------------------------------+
//| Custom function to search for a string in an array               |
//+------------------------------------------------------------------+
int ArraySearch(const string &arr[], string value)
{
   for(int i = 0; i < ArraySize(arr); i++)
   {
      if(arr[i] == value) return i;
   }
   return -1;
}

So sieht es in der Tabelle aus:


Erstellen eines EA für Mustererkennung und Handel

Als Nächstes habe ich die Entwicklungen im MetaTrader 5-Tester überprüft, da die Tests in Python erfolgreich waren. Der nachstehende Code ist dem Artikel ebenfalls beigefügt. Der Code ist eine praktische Umsetzung des Konzepts der Musteranalyse auf dem Devisenmarkt. Dahinter steht die Idee, dass historische Preismuster statistisch signifikante Informationen über zukünftige Marktbewegungen liefern können.

EA-Schlüsselkomponenten:

  • Erzeugung von Mustern: Der EA verwendet eine binäre Darstellung von Kursbewegungen (aufwärts oder abwärts) und erstellt alle möglichen Kombinationen für eine bestimmte Musterlänge.
  • Statistische Analyse: Der EA führt eine retrospektive Analyse durch, bei der die Häufigkeit des Auftretens der einzelnen Muster und ihre Vorhersagekraft bewertet werden.
  • Dynamische Anpassung: Der EA aktualisiert kontinuierlich die Musterstatistiken, um sich an die veränderten Marktbedingungen anzupassen.
  • Handelsentscheidungen treffen: Basierend auf den identifizierten effektivsten Kauf- und Verkaufsmustern eröffnet, schließt oder hält der EA die Positionen.
  • Parametrisierung: Der EA bietet die Möglichkeit, wichtige Parameter wie die Musterlänge, den Analysezeitraum, den Prognosehorizont und die Mindestanzahl der zu berücksichtigenden Musterereignisse anzupassen.

Insgesamt habe ich 4 Versionen des EA gemacht: die erste basiert auf dem Konzept des Artikels, es öffnet Trades basierend auf Mustern, und schließt sie, wenn ein neues besseres Muster in die entgegengesetzte Richtung erkannt wird. Der zweite ist derselbe, aber für mehrere Währungen: Er arbeitet mit den 10 liquidesten Devisenpaaren laut Weltbankstatistik. Die dritte Option ist dieselbe, aber sie schließt den Handel, wenn der Preis eine Anzahl von Balken passiert, die größer ist als der Prognosehorizont. Der letzte wird durch Take Profit und Stop Loss geschlossen.

Hier ist der Code für den ersten EA, der Rest findet sich in den angehängten Dateien:

//+------------------------------------------------------------------+
//|                                  PatternProbabilityExpertAdvisor |
//|                                Copyright 2024, Evgeniy Koshtenko |
//|                          https://www.mql5.com/en/users/koshtenko |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, Evgeniy Koshtenko"
#property link      "https://www.mql5.com/en/users/koshtenko"
#property version   "1.00"

#include <Trade\Trade.mqh>            // Include the CTrade trading class

//--- input parameters
input int      InpPatternLength = 5;    // Pattern Length (3-10)
input int      InpLookback     = 1000;  // Lookback Period (100-5000)
input int      InpForecastHorizon = 6;  // Forecast Horizon (1-20)
input double   InpLotSize = 0.1;        // Lot Size
input int      InpMinOccurrences = 30;  // Minimum Pattern Occurrences

//--- global variables
int            g_pattern_length;
int            g_lookback;
int            g_forecast_horizon;
string         g_patterns[];
int            g_pattern_count;
int            g_pattern_occurrences[];
int            g_pattern_successes[];
int            g_total_bars;
string         g_best_buy_pattern = "";
string         g_best_sell_pattern = "";

CTrade trade;                         // Use the CTrade trading class
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- validate inputs
   if(InpPatternLength < 3 || InpPatternLength > 10)
   {
      Print("Invalid Pattern Length. Must be between 3 and 10.");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   if(InpLookback < 100 || InpLookback > 5000)
   {
      Print("Invalid Lookback Period. Must be between 100 and 5000.");
      return INIT_PARAMETERS_INCORRECT;
   }

   if(InpForecastHorizon < 1 || InpForecastHorizon > 20)
   {
      Print("Invalid Forecast Horizon. Must be between 1 and 20.");
      return INIT_PARAMETERS_INCORRECT;
   }

   //--- set global variables
   g_pattern_length = InpPatternLength;
   g_lookback = InpLookback;
   g_forecast_horizon = InpForecastHorizon;
   
   //--- generate all possible patterns
   if(!GeneratePatterns())
   {
      Print("Failed to generate patterns.");
      return INIT_FAILED;
   }
   
   g_total_bars = iBars(_Symbol, PERIOD_CURRENT);
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   if(!IsNewBar()) return;
   
   UpdatePatternStatistics();
   
   string current_pattern = GetCurrentPattern();
   
   if(current_pattern == g_best_buy_pattern)
   {
      if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
      {
         trade.PositionClose(_Symbol);
      }
      if(!PositionSelect(_Symbol))
      {
         trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "Buy Pattern: " + current_pattern);
      }
   }
   else if(current_pattern == g_best_sell_pattern)
   {
      if(PositionSelect(_Symbol) && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
      {
         trade.PositionClose(_Symbol);
      }
      if(!PositionSelect(_Symbol))
      {
         trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "Sell Pattern: " + current_pattern);
      }
   }
}

//+------------------------------------------------------------------+
//| Generate all possible patterns                                   |
//+------------------------------------------------------------------+
bool GeneratePatterns()
{
   g_pattern_count = (int)MathPow(2, g_pattern_length);
   if(!ArrayResize(g_patterns, g_pattern_count))
   {
      Print("Failed to resize g_patterns array.");
      return false;
   }
   
   for(int i = 0; i < g_pattern_count; i++)
   {
      string pattern = "";
      for(int j = 0; j < g_pattern_length; j++)
      {
         pattern += ((i >> j) & 1) ? "U" : "D";
      }
      g_patterns[i] = pattern;
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| Update pattern statistics and find best patterns                 |
//+------------------------------------------------------------------+
void UpdatePatternStatistics()
{
   if(ArraySize(g_pattern_occurrences) != g_pattern_count)
   {
      ArrayResize(g_pattern_occurrences, g_pattern_count);
      ArrayResize(g_pattern_successes, g_pattern_count);
   }
   
   ArrayInitialize(g_pattern_occurrences, 0);
   ArrayInitialize(g_pattern_successes, 0);
   
   int total_bars = iBars(_Symbol, PERIOD_CURRENT);
   int start = total_bars - g_lookback;
   if(start < g_pattern_length + g_forecast_horizon) start = g_pattern_length + g_forecast_horizon;
   
   double close[];
   ArraySetAsSeries(close, true);
   CopyClose(_Symbol, PERIOD_CURRENT, 0, total_bars, close);
   
   string patterns[];
   ArrayResize(patterns, total_bars);
   ArraySetAsSeries(patterns, true);
   
   for(int i = 0; i < total_bars - g_pattern_length; i++)
   {
      patterns[i] = "";
      for(int j = 0; j < g_pattern_length; j++)
      {
         patterns[i] += (close[i+j] > close[i+j+1]) ? "U" : "D";
      }
   }
   
   for(int i = start; i >= g_pattern_length + g_forecast_horizon; i--)
   {
      string current_pattern = patterns[i];
      int pattern_index = ArraySearch(g_patterns, current_pattern);
      
      if(pattern_index != -1)
      {
         g_pattern_occurrences[pattern_index]++;
         if(close[i-g_forecast_horizon] > close[i])
         {
            g_pattern_successes[pattern_index]++;
         }
      }
   }
   
   double best_buy_win_rate = 0;
   double best_sell_win_rate = 0;
   
   for(int i = 0; i < g_pattern_count; i++)
   {
      if(g_pattern_occurrences[i] >= InpMinOccurrences)
      {
         double win_rate = (double)g_pattern_successes[i] / g_pattern_occurrences[i];
         if(win_rate > best_buy_win_rate)
         {
            best_buy_win_rate = win_rate;
            g_best_buy_pattern = g_patterns[i];
         }
         if((1 - win_rate) > best_sell_win_rate)
         {
            best_sell_win_rate = 1 - win_rate;
            g_best_sell_pattern = g_patterns[i];
         }
      }
   }
   
   Print("Best Buy Pattern: ", g_best_buy_pattern, " (Win Rate: ", DoubleToString(best_buy_win_rate * 100, 2), "%)");
   Print("Best Sell Pattern: ", g_best_sell_pattern, " (Win Rate: ", DoubleToString(best_sell_win_rate * 100, 2), "%)");
}

//+------------------------------------------------------------------+
//| Get current price pattern                                        |
//+------------------------------------------------------------------+
string GetCurrentPattern()
{
   double close[];
   ArraySetAsSeries(close, true);
   CopyClose(_Symbol, PERIOD_CURRENT, 0, g_pattern_length + 1, close);
   
   string pattern = "";
   for(int i = 0; i < g_pattern_length; i++)
   {
      pattern += (close[i] > close[i+1]) ? "U" : "D";
   }
   
   return pattern;
}

//+------------------------------------------------------------------+
//| Custom function to search for a string in an array               |
//+------------------------------------------------------------------+
int ArraySearch(const string &arr[], string value)
{
   for(int i = 0; i < ArraySize(arr); i++)
   {
      if(arr[i] == value) return i;
   }
   return -1;
}

//+------------------------------------------------------------------+
//| Check if it's a new bar                                          |
//+------------------------------------------------------------------+
bool IsNewBar()
{
   static datetime last_time = 0;
   datetime current_time = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(current_time != last_time)
   {
      last_time = current_time;
      return true;
   }
   return false;
}

Die Testergebnisse für den EURUSD lauten wie folgt:

Und zwar im Detail:

Nicht schlecht, und die Grafik ist wunderschön. Andere EA-Versionen verharren entweder bei Null oder gehen in lange Drawdowns über. Auch die beste Option entspricht nicht ganz meinen Kriterien. Ich bevorzuge EAs mit einem Gewinnfaktor über 2 und einer Sharpe Ratio über 1. Mir ist aufgefallen, dass im Python-Tester sowohl die Handelskommission als auch der Spread und der Swap berücksichtigt werden müssen.


Mögliche Verbesserungen: Erweiterung des Zeitrahmens und Hinzufügen von Indikatoren

Lassen Sie uns unsere Überlegungen fortsetzen. Das System zeigt sicherlich positive Ergebnisse, aber wie können diese verbessert werden, und ist es realistisch?

Jetzt betrachten wir den 4-Stunden-Zeitrahmen. Lassen Sie uns versuchen, weiter zu schauen. Wir sollten ein Tages-, Wochen- und vielleicht sogar ein Monatschart hinzufügen. Mit diesem Ansatz werden wir in der Lage sein, mehr globale Trends und großräumigere Muster zu erkennen. Erweitern wir den Code, um alle diese Zeitskalen abzudecken:

timeframes = [mt5.TIMEFRAME_H4, mt5.TIMEFRAME_D1, mt5.TIMEFRAME_W1, mt5.TIMEFRAME_MN1]
for tf in timeframes:
    ohlc_data = get_ohlc_data(symbol, tf, start_date, end_date)
    patterns = find_patterns(ohlc_data)

Mehr Daten, mehr Rauschen. Wir müssen lernen, dieses Rauschen auszusortieren, um klarere Daten zu erhalten.

Erweitern wir die Palette der analysierten Merkmale. In der Welt des Handels ist dies der Zusatz von technischen Indikatoren. RSI, MACD und Bollinger Bands sind die am häufigsten verwendeten Instrumente.

def add_indicators(data):
    data['RSI'] = ta.RSI(data['close'])
    data['MACD'] = ta.MACD(data['close']).macd()
    data['BB_upper'], data['BB_middle'], data['BB_lower'] = ta.BBANDS(data['close'])
    return data

ohlc_data = add_indicators(ohlc_data)

Indikatoren können uns helfen, unsere Mustersignale zu bestätigen. Oder wir können zusätzlich nach Mustern auf Indikatoren suchen.


Schlussfolgerung

Wir haben also unsere Arbeit zum Finden und Analysieren von Mustern abgeschlossen. Wir haben ein System entwickelt, das nach Mustern im Marktchaos sucht. Wir haben gelernt, unsere Ergebnisse zu visualisieren, Backtests durchzuführen und zukünftige Verbesserungen zu planen. Aber am wichtigsten ist, dass wir gelernt haben, wie analytische Händler zu denken. Wir folgen nicht einfach der Masse, wir suchen unseren eigenen Weg, unsere eigenen Muster, unsere eigenen Möglichkeiten.

Vergessen Sie nicht, dass der Markt ein Produkt der Handlungen lebender Menschen ist. Er wächst und verändert sich. Und unsere Aufgabe ist es, uns mit ihr zu verändern. Die Muster von heute funktionieren vielleicht morgen nicht mehr, aber das ist kein Grund zur Verzweiflung. Dies ist eine Gelegenheit zu lernen, sich anzupassen und zu wachsen. Verwenden Sie dieses System als Ausgangspunkt. Experimentieren Sie, verbessern Sie, schaffen Sie Ihr eigenes. Vielleicht finden Sie genau das Muster, das Ihnen die Türen zum erfolgreichen Handel öffnet!

Ich wünsche Ihnen viel Glück auf dieser spannenden Reise! Lassen Sie Ihre Muster immer gewinnbringend sein, und lassen Sie Verluste nur Lektionen auf Ihrem Weg zum Erfolg sein. Bis bald in der Welt des Forex!

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

Beigefügte Dateien |
PredictPattern.py (9.23 KB)
AutoPattern.mq5 (18.98 KB)
PatternEA.mq5 (16.12 KB)
PatternEAMult.mq5 (9.59 KB)
Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Artificial Showering Algorithm (ASHA) Artificial Showering Algorithm (ASHA)
Der Artikel stellt den Künstlichen Duschalgorithmus (ASHA) vor, eine neue metaheuristische Methode, die für die Lösung allgemeiner Optimierungsprobleme entwickelt wurde. Auf der Grundlage der Simulation von Wasserfluss- und Akkumulationsprozessen konstruiert dieser Algorithmus das Konzept eines idealen Feldes, in dem jede Einheit der Ressource (Wasser) aufgerufen ist, eine optimale Lösung zu finden. Wir werden herausfinden, wie ASHA Fließ- und Akkumulationsprinzipien anpasst, um Ressourcen in einem Suchraum effizient zuzuweisen, und seine Implementierung und Testergebnisse sehen.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Von der Grundstufe bis zur Mittelstufe: Prioritätsfolge der Operatoren Von der Grundstufe bis zur Mittelstufe: Prioritätsfolge der Operatoren
Dies ist sicherlich die schwierigste Frage, die sich rein theoretisch erklären lässt. Deshalb müssen Sie alles üben, was wir hier besprechen werden. Dies mag auf den ersten Blick einfach erscheinen, aber das Thema Operatoren kann nur in der Praxis in Verbindung mit ständiger Weiterbildung verstanden werden.