English Русский 中文 Español 日本語 Português
preview
Erstellung eines Indikators für die Volatilitätsprognose mit Python

Erstellung eines Indikators für die Volatilitätsprognose mit Python

MetaTrader 5Statistik und Analyse |
17 4
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Einführung

Oh, Mist! Meine Stopps sind mal wieder weggeblasen worden...

Mit diesem Satz habe ich im Jahr 2021 jeden zweiten Handelstag begonnen. Ich erinnere mich, wie ich in Charts und Zahlen vertieft saß, stolz auf mein neues Handelssystem, und dann – bumm – war die Hälfte meiner Einlage weg. Weil ein kluger Kopf eine Aussage über Krypto gemacht hat und der Markt verrückt geworden ist.

Klingt vertraut, nicht wahr? Ich bin sicher, dass jeder Algo-Händler diese Erfahrung gemacht hat. Es scheint, dass ich alles berechnet und getestet habe, und das System funktioniert perfekt mit historischen Daten... Aber wie sieht es auf dem realen Markt aus? „Hallo, Volatilität, lange nicht mehr gesehen!“

Nach einem weiteren solchen „Abenteuer“ wurde ich wütend und beschloss, der Sache auf den Grund zu gehen. Es kann doch nicht sein, dass es unmöglich ist, diese Markthysterie irgendwie vorherzusehen! Ich glaube, ich habe alle vorhandenen Studien zur Volatilität durchforstet. Wissen Sie, was am lustigsten ist? Es stellte sich heraus, dass die Lösung an der Schnittstelle zwischen alten Methoden und neuen Technologien lag.

In diesem Artikel werde ich meinen Weg von der Verzweiflung zu einem funktionierenden Volatilitätsprognosesystem beschreiben. Kein langweiliges Zeug oder akademisches Fachchinesisch – nur echte Erfahrung und funktionierende Lösungen. Ich zeige Ihnen, wie ich MetaTrader 5 mit Python kombiniert habe (Spoiler: sie haben sich nicht auf Anhieb verstanden), wie ich maschinelles Lernen für mich nutzbar gemacht habe und auf welche Fallstricke ich dabei gestoßen bin.

Die wichtigste Erkenntnis, die ich aus dieser ganzen Geschichte gewonnen habe, ist, dass man weder klassischen Indikatoren noch trendigen neuronalen Netzen blind vertrauen darf. Ich erinnere mich, wie ich eine Woche damit verbracht habe, ein sehr komplexes neuronales Netz einzurichten, und dann zeigte ein einfaches XGBoost bessere Ergebnisse. Oder wie einmal ein einfacher Bollinger ein Depot rettete, wo alle intelligenten Algorithmen versagten.

Mir wurde auch klar, dass es beim Handel, wie beim Boxen, nicht auf die Wucht des Schlags ankommt, sondern auf die Fähigkeit, ihn zu antizipieren. Mein System macht keine übernatürlichen Vorhersagen. Es hilft Ihnen einfach, auf Marktüberraschungen vorbereitet zu sein und die Sicherheitsmarge Ihrer Handelsstrategie rechtzeitig zu erhöhen.

Kurz gesagt, wenn Sie es leid sind, dass Ihre Algorithmen über jedes kleine bisschen Volatilität stolpern, willkommen in meiner Welt. Ich werde Ihnen alles so erzählen, wie es ist, mit Codebeispielen, Charts und Analysen. Fangen wir an.


Projektkonzept

Nach monatelangen Experimenten und einer eingehenden Analyse der Marktdaten wurde ein Konzept für ein System entwickelt, das die Volatilität mit erstaunlicher Genauigkeit vorhersagen kann. Die wichtigste Entdeckung war, dass die Volatilität im Gegensatz zum Preis die Eigenschaft der Stationarität hat – sie neigt dazu, zu ihrem Mittelwert zurückzukehren und bildet stabile Muster. Diese Eigenschaft macht die Vorhersage nicht nur möglich, sondern auch praktisch anwendbar im realen Handel.

Das System basiert auf der leistungsstarken Kombination von MetaTrader 5 und Python, wobei jedes Tool seine Stärken ausspielt. MetaTrader 5 dient als zuverlässige Quelle für Marktdaten. Es liefert uns historische Kurse und einen Echtzeit-Datenstrom mit minimalen Verzögerungen. Und Python wird zu unserem Analyselabor, in dem ein umfangreicher Satz von Bibliotheken für maschinelles Lernen (Sklearn, XGBoost, PyTorch) dabei hilft, wertvolle Muster aus diesen Daten zu extrahieren und Hypothesen über die Stationarität der Volatilität zu bestätigen.

Die Systemarchitektur besteht aus drei Schlüsselebenen:

  1. Die Datenpipeline ist die Grundlage des Systems. Hier findet die primäre Verarbeitung der Daten aus MetaTrader 5 statt: Rauschunterdrückung, Berechnung von Dutzenden von Volatilitätsmetriken und die Bildung von Merkmalen für Modelle. Besonderes Augenmerk wurde auf die Optimierung gelegt – das System arbeitet ohne Verzögerungen und Speicherlecks. Auf dieser Ebene wird auch die Stationarität der Zeitreihen getestet und es werden signifikante Volatilitätsmuster ermittelt.
  2. Kern der Analyse. Der Kern basiert auf einem Ensemble spezialisierter maschineller Lernmodelle. Jeder ist auf seinen eigenen Zeithorizont zugeschnitten: von Intraday-Schwankungen bis zu wöchentlichen Trends. Bei den Tests stellte sich heraus, dass selbst ein einfaches XGBoost komplexe neuronale Netze bei der Vorhersagegenauigkeit oft übertrifft, insbesondere bei der Erkennung von Volatilitätsclustern.
  3. Risk Advisor ist ein Empfehlungssystem für das Risikomanagement. Auf der Grundlage von Volatilitätsprognosen schlägt es optimale Stop-Loss- und Take-Profit-Niveaus vor. In Zeiten erhöhter zukünftiger Volatilität wird empfohlen, die Schutzaufträge zu erweitern und in ruhigeren zukünftigen Stunden zu verengen, um einen präziseren Markteinstieg zu ermöglichen. Hier spielt die Stationarität der Volatilität eine wichtige Rolle, die es dem System ermöglicht, die Handelsparameter effektiv anzupassen.

Die Modelle werden anhand eines einzigartigen Datensatzes trainiert, der Kurse in verschiedenen Zeitrahmen enthält – von Tick bis Daily. Auf diese Weise kann das System drei wichtige Marktbedingungen erkennen: geringe Volatilität, Trend und Explosivität. Auf der Grundlage dieser Informationen werden Empfehlungen für optimale Einstiegsniveaus und Schutzaufträge ausgesprochen. Aufgrund der Stationarität der Volatilität ist das System nicht nur in der Lage, den aktuellen Zustand zu erkennen, sondern auch Übergänge zwischen diesen Zuständen vorherzusagen.

Das Hauptmerkmal des Systems ist seine Anpassungsfähigkeit. Sie gibt nicht nur feste Empfehlungen ab, sondern passt sie an die aktuelle Marktsituation an. Für jede Handelssituation bietet das System einen individuellen Satz von Parametern an, der auf der Prognose der zukünftigen Volatilität basiert. Diese Anpassungsfähigkeit ist besonders effektiv, da sich die Volatilität immer wieder ändert.

In den folgenden Abschnitten werden wir jede Systemkomponente im Detail untersuchen, den eigentlichen Code zeigen und die Ergebnisse des Backtestings mitteilen. Sie werden sehen, wie theoretische Konzepte über die Stationarität der Volatilität in ein praktisches Werkzeug für die Marktanalyse umgewandelt werden.


Installieren der erforderlichen Software

Bevor wir uns in die Systementwicklung stürzen, sollten wir einen Blick auf die Installation der gesamten erforderlichen Software werfen. Aus eigener Erfahrung weiß ich, dass die Einrichtung der MetaTrader 5-Python-Verbindung viele Leute ins Straucheln bringt. Daher werde ich versuchen, Ihnen nicht nur zu erklären, wie Sie alles installieren, sondern auch, wie Sie die wichtigsten Fallstricke vermeiden können.

Beginnen wir mit Python. Wir benötigen Version 3.8 oder höher, die von der offiziellen Website python.org heruntergeladen werden kann. Achten Sie bei der Installation darauf, dass Sie „Python zu PATH hinzufügen“ ankreuzen, sonst müssen Sie die Pfade später manuell hinzufügen. Nach der Installation von Python erstellen wir als erstes eine virtuelle Umgebung für das Projekt. Dieser Schritt ist nicht zwingend erforderlich, aber sehr nützlich – er schützt uns vor Versionskonflikten in der Bibliothek.

python -m venv venv_volatility
venv_volatility\Scripts\activate  # for Windows
source venv_volatility/bin/activate  # for Linux/MacOS

Installieren wir nun die erforderlichen Bibliotheken. Wir benötigen einige grundlegende Tools: numpy und pandas für die Arbeit mit Daten, scikit-learn und xgboost für maschinelles Lernen, pytorch für neuronale Netze und natürlich eine Bibliothek für die Arbeit mit MetaTrader 5. Hier ist der Befehl zur Installation des gesamten Pakets:

pip install numpy pandas scikit-learn xgboost pytorch MetaTrader5 pylint jupyter

Werfen wir einen genaueren Blick auf die Installation von MetaTrader 5. Sie müssen es von der Website Ihres Brokers herunterladen – dies ist wichtig, da es unterschiedliche Versionen geben kann. Wählen Sie bei der Installation einen Ordner mit einem einfachen Pfad, ohne kyrillische Zeichen oder Leerzeichen – das erspart Ihnen eine Menge Ärger bei der Einrichtung der Kommunikation mit Python.

Vergessen Sie nach der Installation des Terminals nicht, in den Einstellungen den automatischen Handel und den DLL-Import zu aktivieren sowie AlgoTrading zu aktivieren. Es klingt offensichtlich, aber ich habe selbst einige Stunden mit der Fehlersuche verbracht, bis ich mich an diese Einstellungen erinnerte.

Jetzt kommt der lustige Teil – die Überprüfung der Verbindung zwischen Python und MetaTrader 5. Ich habe ein kleines Skript entwickelt, um sicherzustellen, dass alles so funktioniert, wie es sollte:

import MetaTrader5 as mt5

def test_mt5_connection():
    if not mt5.initialize():
        print("MT5 initialization error:", mt5.last_error())
        return False
    
    print("MetaTrader5 package author:", mt5.__author__)
    print("MetaTrader5 package version:", mt5.__version__)
    
    terminal_info = mt5.terminal_info()
    if terminal_info is None:
        print("Error getting terminal data:", mt5.last_error())
        return False
    
    print(f"Connected to terminal '{terminal_info.name}' ({terminal_info.path})")
    print("Trade server:", terminal_info.connected)
    
    mt5.shutdown()
    return True

if __name__ == "__main__":
    test_mt5_connection()

Worauf ist zu achten, wenn Probleme auftreten? Der häufigste Stolperstein ist die Initialisierung von MetaTrader 5. Wenn das Skript keine Verbindung zum Terminal herstellen kann, überprüfen Sie zunächst, ob MetaTrader 5 selbst läuft. Es scheint offensichtlich zu sein, aber glauben Sie mir, selbst erfahrene Entwickler vergessen das manchmal.

Wenn das Terminal läuft, aber immer noch keine Verbindung zustande kommt, überprüfen Sie Ihre Administratorrechte und Firewall-Einstellungen. Windows geht manchmal lieber auf Nummer sicher und blockiert die Verbindung.

Für die Entwicklung empfehle ich VS Code oder PyCharm – beide Editoren sind hervorragend für die Python-Entwicklung geeignet. Installieren Sie die Erweiterung für Python und Jupyter – dies wird das Debuggen und Testen von Code erheblich vereinfachen.

Die letzte Prüfung besteht darin, einige historische Daten zu erhalten:

import MetaTrader5 as mt5
mt5.initialize()
data = mt5.copy_rates_from_pos("EURUSD", mt5.TIMEFRAME_M1, 0, 1000)
print(data is not None)
mt5.shutdown()

Wenn der Code ohne Fehler läuft, ist Ihre Entwicklungsumgebung einsatzbereit! Im nächsten Abschnitt werden wir uns mit dem Empfang und der Verarbeitung von Daten aus dem MetaTrader 5 befassen.


Abrufen von Daten aus MetaTrader 5

Bevor wir uns in komplexe Berechnungen stürzen, sollten wir sicherstellen, dass wir die Daten vom Handelsterminal korrekt empfangen. Ich habe ein einfaches Skript geschrieben, mit dem Sie MetaTrader 5 testen und die Datenstruktur untersuchen können:

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

pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1500)
pd.set_option('display.float_format', lambda x: '%.5f' % x)

def check_mt5_data(symbol="EURUSD"):
    if not mt5.initialize():
        print(f"MT5 initialization error: {mt5.last_error()}")
        return
    
    print("\n=== Symbol Information ===")
    symbol_info = mt5.symbol_info(symbol)
    if symbol_info is None:
        print(f"Failed to get {symbol} data")
        mt5.shutdown()
        return
    
    print(f"Current spread: {symbol_info.spread} points")
    print(f"Tick size: {symbol_info.trade_tick_size}")
    print(f"Contract size: {symbol_info.trade_contract_size}")
    
    # Last 100 ticks
    print("\n=== Latest Ticks ===")
    ticks = mt5.copy_ticks_from(symbol, datetime.now() - timedelta(minutes=5), 
                               100, mt5.COPY_TICKS_ALL)
    ticks_frame = pd.DataFrame(ticks)
    ticks_frame['time'] = pd.to_datetime(ticks_frame['time'], unit='s')
    print(ticks_frame.head())
    
    # 5-minute bars (last 100 bars)
    print("\n=== 5-Minute Bars (Last 100) ===")
    rates_5m = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_M5, 0, 100)
    rates_5m_frame = pd.DataFrame(rates_5m)
    rates_5m_frame['time'] = pd.to_datetime(rates_5m_frame['time'], unit='s')
    print(rates_5m_frame.head())
    
    # Hourly bars (last 24 hours)
    print("\n=== Hourly Bars (Last 24) ===")
    rates_1h = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_H1, 0, 24)
    rates_1h_frame = pd.DataFrame(rates_1h)
    rates_1h_frame['time'] = pd.to_datetime(rates_1h_frame['time'], unit='s')
    print(rates_1h_frame.head())
    
    # Daily bars (last 30 days)
    print("\n=== Daily Bars (Last 30) ===")
    rates_d1 = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_D1, 0, 30)
    rates_d1_frame = pd.DataFrame(rates_d1)
    rates_d1_frame['time'] = pd.to_datetime(rates_d1_frame['time'], unit='s')
    print(rates_d1_frame.head())
    
    # Statistics for different timeframes
    print("\n=== 5-Minute Bars Statistics ===")
    print(f"Average volume: {rates_5m_frame['tick_volume'].mean():.2f}")
    print(f"Average spread: {rates_5m_frame['spread'].mean():.2f}")
    print(f"Average range: {(rates_5m_frame['high'] - rates_5m_frame['low']).mean():.5f}")
    
    print("\n=== Hourly Bars Statistics ===")
    print(f"Average volume: {rates_1h_frame['tick_volume'].mean():.2f}")
    print(f"Average spread: {rates_1h_frame['spread'].mean():.2f}")
    print(f"Average range: {(rates_1h_frame['high'] - rates_1h_frame['low']).mean():.5f}")
    
    print("\n=== Daily Bars Statistics ===")
    print(f"Average volume: {rates_d1_frame['tick_volume'].mean():.2f}")
    print(f"Average spread: {rates_d1_frame['spread'].mean():.2f}")
    print(f"Average range: {(rates_d1_frame['high'] - rates_d1_frame['low']).mean():.5f}")
    
    # Current quotes
    print("\n=== Current Market Depth ===")
    depth = mt5.market_book_get(symbol)
    if depth is not None:
        bid = depth[0].price if depth[0].type == 1 else depth[1].price
        ask = depth[0].price if depth[0].type == 2 else depth[1].price
        print(f"Bid: {bid}")
        print(f"Ask: {ask}")
        print(f"Spread: {(ask - bid):.5f}")
    
    mt5.shutdown()

if __name__ == "__main__":
    check_mt5_data()

Dieser Code zeigt alle Informationen an, die wir benötigen, um die Gültigkeit der Verbindung und die Qualität der empfangenen Daten zu überprüfen. Sobald Sie es starten, werden Sie es sehen:

  • Grundlegende Informationen über das Handelsinstrument
  • Tabelle der letzten Ticks
  • Tabelle der stündlichen Balken
  • Statistiken über Volumen und Spreads
  • Aktuelle Notierungen aus der Markttiefe

Nach dem Start sehen wir sofort, dass alles so funktioniert, wie es soll. Wenn irgendwo ein Problem auftritt, zeigt das Skript, in welchem Stadium genau etwas schief gelaufen ist.

In den folgenden Abschnitten werden wir diese Daten zur Berechnung der Volatilität verwenden, aber zunächst muss sichergestellt werden, dass der zugrunde liegende Datenabruf korrekt funktioniert.


Vorverarbeitung der Daten

Als ich anfing, mich mit Volatilitätsprognosen zu beschäftigen, dachte ich, das Wichtigste sei ein cooles maschinelles Lernmodell. In der Praxis hat sich schnell gezeigt, dass die Qualität der Datenaufbereitung das Entscheidende ist. Ich möchte Ihnen zeigen, wie ich die Daten für unser Prognosesystem aufbereite.

Hier ist der vollständige Vorverarbeitungscode, den ich verwende:

import MetaTrader5 as mt5
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from datetime import datetime, timedelta

pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1500)
pd.set_option('display.float_format', lambda x: '%.5f' % x)

class VolatilityProcessor:
    def __init__(self, lookback_periods=(5, 10, 20)):
        self.lookback_periods = lookback_periods
        self.scaler = StandardScaler()
        
    def calculate_volatility_features(self, df):
        df = df.copy()
        
        # Basic calculations
        df['returns'] = df['close'].pct_change().fillna(0)
        df['log_returns'] = (np.log(df['close']) - np.log(df['close'].shift(1))).fillna(0)
        
        # True Range
        df['true_range'] = np.maximum(
            df['high'] - df['low'],
            np.maximum(
                abs(df['high'] - df['close'].shift(1).fillna(df['high'])),
                abs(df['low'] - df['close'].shift(1).fillna(df['low']))
            )
        )
        
        # ATR and volatility
        for period in self.lookback_periods:
            df[f'atr_{period}'] = df['true_range'].rolling(window=period, min_periods=1).mean()
            df[f'volatility_{period}'] = df['returns'].rolling(window=period, min_periods=1).std()
        
        # Parkinson volatility
        df['parkinson_vol'] = np.sqrt(
            1/(4 * np.log(2)) * 
            np.power(np.log(df['high'].div(df['low'])), 2)
        )
        
        # Garman-Klass volatility
        df['garman_klass_vol'] = np.sqrt(
            0.5 * np.power(np.log(df['high'].div(df['low'])), 2) -
            (2*np.log(2)-1) * np.power(np.log(df['close'].div(df['open'])), 2)
        )
        
        # Relative volatility changes
        for period in self.lookback_periods:
            df[f'vol_change_{period}'] = (
                df[f'volatility_{period}'].div(df[f'volatility_{period}'].shift(1))
            )
            
        # Replace all infinities and NaN
        for col in df.columns:
            if df[col].dtype == float:
                df[col] = df[col].replace([np.inf, -np.inf], np.nan).fillna(0)
        
        return df
    
    def prepare_features(self, df):
        feature_cols = []
        
        # Time-based features - correct time conversion
        time = pd.to_datetime(df['time'], unit='s')
        
        # Hours (0-23) -> radians (0-2π)
        hours = time.dt.hour.values
        df['hour_sin'] = np.sin(2 * np.pi * hours / 24.0)
        df['hour_cos'] = np.cos(2 * np.pi * hours / 24.0)
        
        # Week days (0-6) -> radians (0-2π)
        days = time.dt.dayofweek.values
        df['day_sin'] = np.sin(2 * np.pi * days / 7.0)
        df['day_cos'] = np.cos(2 * np.pi * days / 7.0)
        
        # Select features
        for period in self.lookback_periods:
            feature_cols.extend([
                f'atr_{period}',
                f'volatility_{period}',
                f'vol_change_{period}'
            ])
        
        feature_cols.extend([
            'parkinson_vol', 
            'garman_klass_vol',
            'hour_sin',
            'hour_cos',
            'day_sin',
            'day_cos'
        ])
        
        # Create features DataFrame
        features = df[feature_cols].copy()
        
        # Final cleanup and scaling
        features = features.replace([np.inf, -np.inf], 0).fillna(0)
        scaled_features = self.scaler.fit_transform(features)
        
        return pd.DataFrame(
            scaled_features,
            columns=features.columns,
            index=features.index
        )
    
    def create_target(self, df, forward_window=12):
        future_vol = df['returns'].rolling(
            window=forward_window, min_periods=1, center=False
        ).std().shift(-forward_window).fillna(0)
        
        return future_vol

    def prepare_dataset(self, df, forward_window=12):
        print("\n=== Preparing Dataset ===")
        print("Initial shape:", df.shape)
        
        df = self.calculate_volatility_features(df)
        print("After calculating features:", df.shape)
        
        features = self.prepare_features(df)
        target = self.create_target(df, forward_window)
        
        print("Final shape:", features.shape)
        return features, target

def check_mt5_data(symbol="EURUSD"):
    if not mt5.initialize():
        print(f"MT5 initialization error: {mt5.last_error()}")
        return None
    
    rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_H1, 0, 10000)
    mt5.shutdown()
    
    if rates is None:
        return None
        
    return pd.DataFrame(rates)

def main():
    symbol = "EURUSD"
    rates_frame = check_mt5_data(symbol)
    
    if rates_frame is not None:
        print("\n=== Processing Hourly Data for Volatility Analysis ===")
        processor = VolatilityProcessor(lookback_periods=(5, 10, 20))
        features, target = processor.prepare_dataset(rates_frame)
        
        print("\nFeature statistics:")
        print(features.describe())
        print("\nFeature columns:", features.columns.tolist())
        print("\nTarget statistics:")
        print(target.describe())
    else:
        print("Failed to process data: MT5 data retrieval error")

if __name__ == "__main__":
    main()

Wie es funktioniert.


Zunächst laden wir die letzten 10.000 H1-Balken aus dem MetaTrader 5 herunter. Warum genau so viele? Durch Versuch und Irrtum habe ich herausgefunden, dass dies die optimale Menge ist – genug, um zu lernen, aber nicht so viel, dass sich der Markt wesentlich verändert.

Jetzt beginnt der interessanteste Teil. Die Klasse VolatilityProcessor erledigt die ganze Drecksarbeit der Datenaufbereitung. Hier sehen Sie, was unter der Haube vor sich geht:

  1. Berechnung der grundlegenden Volatilitätsindikatoren. Wir betrachten hier drei Arten von Volatilität:
    • Klassische Standardabweichung der Renditen
    • True Range und ATR sind zwar altmodisch, funktionieren aber immer noch.
    • Die Parkinson- und die Harman-Klass-Methode sind gut geeignet, um Bewegungen innerhalb eines Tages zu erfassen.
  2. Bearbeitungszeit. Anstelle der üblichen One-Hot-Codierung von Stunden und Wochentage verwende ich Sinus und Kosinus. Dies ist nicht nur eine Angeberei – wir sagen dem Modell, dass 11:00 Uhr und 12:00 Uhr nahe beieinander liegen und nicht an entgegengesetzten Enden des Spektrums.
  3. Normalisierung und Bereinigung von Daten. Dies ist der entscheidende Teil:
    • Entfernen von Ausreißern und unendlichen Werten
    • Auffüllen von Lücken mit Nullen (nur nach sorgfältiger Prüfung, dass die Daten dadurch nicht verfälscht werden)
    • Skalierung aller Merkmale auf denselben Bereich

Als Ergebnis erhalten wir 15 Merkmale – die optimale Anzahl für unsere Aufgabe. Ich habe versucht, weitere Indikatoren hinzuzufügen (alle Arten von exotischen Indikatoren), aber das hat die Ergebnisse nur verschlechtert.

Die Zielvariable ist die zukünftige Volatilität über die nächsten 12 Perioden. Warum 12? Bei stündlichen Daten erhalten wir so eine Prognose für den nächsten halben Tag – genug, um Handelsentscheidungen zu treffen, aber nicht so viel, dass die Prognose bedeutungslos wird.

Worauf ist zu achten?

  1. min_periods=1 wird überall bei rollierenden Operationen verwendet – dadurch gehen keine Daten am Anfang der Zeitreihe verloren.
  2. Die Verwendung von .div() anstelle des üblichen / ist nicht nur eine Laune; Pandas kann auf diese Weise Randfälle besser behandeln.
  3. Die Ersetzung von Unendlichkeiten erfolgt ganz am Ende jeder Phase, sodass wir keine Problembereiche übersehen.

Im nächsten Abschnitt werden wir ein maschinelles Lernmodell erstellen, das mit diesen aufbereiteten Daten arbeitet. Aber denken Sie daran, dass ein noch so gutes Modell schlecht aufbereitete Daten nicht speichern kann.


Erstellen eines Modells für maschinelles Lernen

Damit sind wir beim interessantesten Teil angelangt – der Erstellung eines Prognosemodells. Zunächst wählte ich den offensichtlichen Weg der Regression, um den genauen Wert der zukünftigen Volatilität vorherzusagen. Die Logik war einfach: Wir ermitteln eine bestimmte Zahl, multiplizieren sie mit einem bestimmten Verhältnis, und schon haben Sie Ihr Stop-Loss-Niveau.

Erster Versuch: Regressionsmodell


Ich habe mit dem einfachsten Code begonnen – einem einfachen XGBRegressor mit minimalen Einstellungen. Die Parameter sind gering: einhundert Bäume, Lernrate 0,1 und Tiefe 5. Es war naiv zu glauben, dass dies ausreichen würde, aber wer hat nicht schon solche Fehler am Anfang seiner Reise gemacht?

Die Ergebnisse sind, gelinde gesagt, wenig beeindruckend. Das R-Quadrat schwankte zwischen 0,05 und 0,06, was bedeutet, dass das Modell nur 5-6 % der Variation in den Daten erklärte. Die Standardabweichung der Vorhersagen erwies sich als fast dreimal kleiner als die tatsächliche Abweichung. Der mittlere absolute Fehler schien ziemlich gut zu sein, aber es war eine Falle.

import MetaTrader5 as mt5
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import TimeSeriesSplit
import xgboost as xgb
from xgboost import XGBRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
import matplotlib.pyplot as plt
from datetime import datetime, timedelta

# VolatilityProcessor class remains unchanged
class VolatilityProcessor:
    def __init__(self, lookback_periods=(5, 10, 20)):
        self.lookback_periods = lookback_periods
        self.scaler = StandardScaler()
        
    def calculate_volatility_features(self, df):
        df = df.copy()
        
        # Basic calculations
        df['returns'] = df['close'].pct_change().fillna(0)
        df['log_returns'] = (np.log(df['close']) - np.log(df['close'].shift(1))).fillna(0)
        df['abs_returns'] = abs(df['returns'])
        
        # Price changes
        df['price_range'] = (df['high'] - df['low']) / df['close']
        df['price_change'] = (df['close'] - df['open']) / df['open']
        
        # True Range
        df['true_range'] = np.maximum(
            df['high'] - df['low'],
            np.maximum(
                abs(df['high'] - df['close'].shift(1).fillna(df['high'])),
                abs(df['low'] - df['close'].shift(1).fillna(df['low']))
            )
        )
        
        # ATR and volatility for different periods
        for period in self.lookback_periods:
            # Standard features
            df[f'atr_{period}'] = df['true_range'].rolling(window=period, min_periods=1).mean()
            df[f'volatility_{period}'] = df['returns'].rolling(window=period, min_periods=1).std()
            
            # Additional rolling statistics
            df[f'mean_range_{period}'] = df['price_range'].rolling(window=period, min_periods=1).mean()
            df[f'mean_price_change_{period}'] = df['price_change'].rolling(window=period, min_periods=1).mean()
            df[f'max_price_change_{period}'] = df['price_change'].rolling(window=period, min_periods=1).max()
            df[f'min_price_change_{period}'] = df['price_change'].rolling(window=period, min_periods=1).min()
        
        # Advanced volatility measures
        # Parkinson volatility
        df['parkinson_vol'] = np.sqrt(
            1/(4 * np.log(2)) * 
            np.power(np.log(df['high'].div(df['low'])), 2)
        )
        
        # Garman-Klass volatility
        df['garman_klass_vol'] = np.sqrt(
            0.5 * np.power(np.log(df['high'].div(df['low'])), 2) -
            (2*np.log(2)-1) * np.power(np.log(df['close'].div(df['open'])), 2)
        )
        
        # Rogers-Satchell volatility
        df['rogers_satchell_vol'] = np.sqrt(
            np.log(df['high'].div(df['close'])) * np.log(df['high'].div(df['open'])) +
            np.log(df['low'].div(df['close'])) * np.log(df['low'].div(df['open']))
        )
        
        # Relative changes
        for period in self.lookback_periods:
            df[f'vol_change_{period}'] = (
                df[f'volatility_{period}'].div(df[f'volatility_{period}'].shift(1))
            )
            df[f'atr_change_{period}'] = (
                df[f'atr_{period}'].div(df[f'atr_{period}'].shift(1))
            )
            
        # Replace all infinities and NaN
        for col in df.columns:
            if df[col].dtype == float:
                df[col] = df[col].replace([np.inf, -np.inf], np.nan).fillna(0)
        
        return df
    
    def prepare_features(self, df):
        feature_cols = []
        
        # Time-based features
        time = pd.to_datetime(df['time'], unit='s')
        
        # Hours (0-23) -> radians (0-2π)
        hours = time.dt.hour.values
        df['hour_sin'] = np.sin(2 * np.pi * hours / 24.0)
        df['hour_cos'] = np.cos(2 * np.pi * hours / 24.0)
        
        # Days of week (0-6) -> radians (0-2π)
        days = time.dt.dayofweek.values
        df['day_sin'] = np.sin(2 * np.pi * days / 7.0)
        df['day_cos'] = np.cos(2 * np.pi * days / 7.0)
        
        # Select features
        for period in self.lookback_periods:
            feature_cols.extend([
                f'atr_{period}',
                f'volatility_{period}',
                f'vol_change_{period}'
            ])
        
        feature_cols.extend([
            'parkinson_vol', 
            'garman_klass_vol',
            'hour_sin',
            'hour_cos',
            'day_sin',
            'day_cos'
        ])
        
        # Create features DataFrame
        features = df[feature_cols].copy()
        
        # Final cleanup and scaling
        features = features.replace([np.inf, -np.inf], 0).fillna(0)
        scaled_features = self.scaler.fit_transform(features)
        
        return pd.DataFrame(
            scaled_features,
            columns=feature_cols,
            index=features.index
        )
    
    def create_target(self, df, forward_window=12):
        """Create target with log transformation"""
        future_vol = df['returns'].rolling(
            window=forward_window, min_periods=1, center=False
        ).std().shift(-forward_window)
        
        # Add small constant to avoid log(0)
        log_vol = np.log(future_vol + 1e-10)
        
        return log_vol.fillna(log_vol.mean())

    def prepare_dataset(self, df, forward_window=12):
        print("\n=== Preparing Dataset ===")
        print("Initial shape:", df.shape)
        
        df = self.calculate_volatility_features(df)
        print("After calculating features:", df.shape)
        
        features = self.prepare_features(df)
        target = self.create_target(df, forward_window)
        
        print("Final shape:", features.shape)
        return features, target

class VolatilityModel:
    def __init__(self, lookback_periods=(5, 10, 20), forward_window=12):
        self.processor = VolatilityProcessor(lookback_periods)
        self.forward_window = forward_window
        self.model = XGBRegressor(
            n_estimators=500,
            learning_rate=0.05,
            max_depth=10,
            min_child_weight=1,
            subsample=0.8,
            colsample_bytree=0.8,
            gamma=0.1,
            reg_alpha=0.1,
            reg_lambda=1,
            random_state=42,
            n_jobs=-1,
            objective='reg:squarederror'  # Better for log-transformed targets
        )
        self.feature_importance = None
        
    def prepare_data(self, rates_frame):
        """Prepare data using our processor"""
        features, target = self.processor.prepare_dataset(rates_frame)
        return features, target
    
    def create_train_test_split(self, features, target, test_size=0.2):
        """Split data preserving time order"""
        split_idx = int(len(features) * (1 - test_size))
        
        X_train = features.iloc[:split_idx]
        X_test = features.iloc[split_idx:]
        y_train = target.iloc[:split_idx]
        y_test = target.iloc[split_idx:]
        
        return X_train, X_test, y_train, y_test
    
    def train(self, X_train, y_train, X_test, y_test):
        """Train model with validation"""
        print("\n=== Training Model ===")
        print("Training set shape:", X_train.shape)
        print("Test set shape:", X_test.shape)
        
        # Train the model
        eval_set = [(X_train, y_train), (X_test, y_test)]
        self.model.fit(
            X_train, 
            y_train,
            eval_set=eval_set,
            verbose=True
        )
        
        # Save feature importance
        importance = self.model.feature_importances_
        self.feature_importance = pd.DataFrame({
            'feature': X_train.columns,
            'importance': importance
        }).sort_values('importance', ascending=False)
        
        # Make predictions and evaluate
        predictions = self.predict(X_test)
        metrics = self.calculate_metrics(y_test, predictions)
        
        return metrics
    
    def calculate_metrics(self, y_true, y_pred):
        """Calculate model performance metrics with detailed R2"""
        # Basic metrics
        rmse = np.sqrt(mean_squared_error(y_true, y_pred))
        mae = mean_absolute_error(y_true, y_pred)
        
        # Manual R2 calculation for verification
        y_true_mean = np.mean(y_true)
        total_sum_squares = np.sum((y_true - y_true_mean) ** 2)
        residual_sum_squares = np.sum((y_true - y_pred) ** 2)
        r2_manual = 1 - (residual_sum_squares / total_sum_squares)
        
        # Stats for debugging
        metrics = {
            'RMSE': rmse,
            'MAE': mae,
            'R2 (sklearn)': r2_score(y_true, y_pred),
            'R2 (manual)': r2_manual,
            'RSS': residual_sum_squares,
            'TSS': total_sum_squares,
            'Mean Prediction': np.mean(y_pred),
            'Mean Actual': np.mean(y_true),
            'Std Prediction': np.std(y_pred),
            'Std Actual': np.std(y_true),
            'Min Prediction': np.min(y_pred),
            'Max Prediction': np.max(y_pred),
            'Min Actual': np.min(y_true),
            'Max Actual': np.max(y_true)
        }
        
        print("\nDetailed Metrics Analysis:")
        print(f"Total Sum of Squares (TSS): {total_sum_squares:.8f}")
        print(f"Residual Sum of Squares (RSS): {residual_sum_squares:.8f}")
        print(f"R2 components: 1 - ({residual_sum_squares:.8f} / {total_sum_squares:.8f})")
        
        return metrics
    
    def predict(self, features):
        """Get volatility predictions with inverse log transform"""
        log_predictions = self.model.predict(features)
        return np.exp(log_predictions) - 1e-10
    
    def plot_feature_importance(self):
        """Visualize feature importance"""
        plt.figure(figsize=(12, 6))
        plt.bar(
            self.feature_importance['feature'],
            self.feature_importance['importance']
        )
        plt.xticks(rotation=45)
        plt.title('Feature Importance')
        plt.tight_layout()
        plt.show()
    
    def plot_predictions(self, y_true, y_pred, title="Model Predictions"):
        """Visualize predictions vs actual values"""
        plt.figure(figsize=(15, 7))
        plt.plot(y_true.values, label='Actual', alpha=0.7)
        plt.plot(y_pred, label='Predicted', alpha=0.7)
        plt.title(title)
        plt.legend()
        plt.tight_layout()
        plt.show()

def check_mt5_data(symbol="EURUSD"):
    if not mt5.initialize():
        print(f"MT5 initialization error: {mt5.last_error()}")
        return None
    
    rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_H1, 0, 10000)
    mt5.shutdown()
    
    if rates is None:
        return None
        
    return pd.DataFrame(rates)

def main():
    # Get data
    symbol = "EURUSD"
    rates_frame = check_mt5_data(symbol)
    
    if rates_frame is not None:
        # Create and train model
        model = VolatilityModel(lookback_periods=(5, 10, 20), forward_window=12)
        features, target = model.prepare_data(rates_frame)
        
        # Split data
        X_train, X_test, y_train, y_test = model.create_train_test_split(
            features, target, test_size=0.2
        )
        
        # Train and evaluate
        metrics = model.train(X_train, y_train, X_test, y_test)
        print("\n=== Model Performance ===")
        for metric, value in metrics.items():
            print(f"{metric}: {value:.6f}")
        
        # Make predictions
        predictions = model.predict(X_test)
        
        # Visualize results
        model.plot_feature_importance()
        model.plot_predictions(y_test, predictions, "Volatility Predictions")
    else:
        print("Failed to process data: MT5 data retrieval error")

if __name__ == "__main__":
    main()

Warum Fallen? Das liegt daran, dass das Modell einfach gelernt hat, Werte in der Nähe des Durchschnitts vorherzusagen. In ruhigen Zeiten sah alles gut aus, aber sobald es richtig losging, verpasste es das Modell zum Glück.

Experimente zur Verbesserung der Regression


Wochenlang wurde versucht, das Regressionsmodell zu verbessern. Ich probierte verschiedene Architekturen neuronaler Netze aus, fügte immer mehr neue Funktionen hinzu, experimentierte mit verschiedenen Verlustfunktionen und veränderte die Hyperparameter, bis ich völlig erschöpft war.

Alles erwies sich als nutzlos. Manchmal ist es mir gelungen, das R-Quadrat auf 0,15-0,20 zu erhöhen, aber zu welchem Preis? Das Modell wurde instabil, überangepasst und, was am wichtigsten ist, es verfehlte immer noch die wichtigsten Momente hoher Volatilität.

Überdenken des Ansatzes


Und dann dämmerte es mir: Wozu brauchen wir überhaupt einen genauen Volatilitätswert? Einem Händler ist es egal, ob die Volatilität 0,00234 oder 0,00256 beträgt. Entscheidend ist, ob sie deutlich höher als üblich ausfallen wird.

So wurde die Idee geboren, das Problem als Klassifizierung neu zu formulieren. Anstatt bestimmte Werte vorherzusagen, begannen wir, zwei Zustände zu definieren: normale/niedrige Volatilität (Kennzeichen 0) und hohe Volatilität über dem 75. Perzentil (Kennzeichen 1).

Warum hat das besser funktioniert?


Erstens haben wir klarere Signale erhalten. Anstelle von vagen Vorhersagen gab es nun eine sichere Antwort: ob ein Anstieg zu erwarten ist oder nicht. Dieser Ansatz erwies sich als wesentlich einfacher zu interpretieren und in ein Handelssystem zu integrieren.

Zweitens ist das Modell besser im Umgang mit Extremwerten geworden. Bei der Regression wurden die Ausreißer „verwischt“, aber bei der Klassifizierung bildeten sie ein klares Muster der Klasse mit hoher Volatilität.

Drittens hat die praktische Anwendbarkeit zugenommen. Ein Händler braucht ein klares Signal, um zu handeln. Es stellte sich heraus, dass es viel einfacher war, die Höhe der Schutzanordnungen für zwei Staaten anzupassen, als zu versuchen, sie auf ein Kontinuum von Werten zu übertragen.

import MetaTrader5 as mt5
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
import xgboost as xgb
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns

class VolatilityProcessor:
    def __init__(self, lookback_periods=(5, 10, 20), volatility_threshold=75):
        """
        Args:
            lookback_periods: periods for feature calculation
            volatility_threshold: percentile for defining high volatility
        """
        self.lookback_periods = lookback_periods
        self.volatility_threshold = volatility_threshold
        self.scaler = StandardScaler()
        
    def calculate_features(self, df):
        df = df.copy()
        
        # Basic calculations
        df['returns'] = df['close'].pct_change()
        df['abs_returns'] = abs(df['returns'])
        
        # True Range
        df['true_range'] = np.maximum(
            df['high'] - df['low'],
            np.maximum(
                abs(df['high'] - df['close'].shift(1)),
                abs(df['low'] - df['close'].shift(1))
            )
        )
        
        # Signs of volatility
        for period in self.lookback_periods:
            # ATR
            df[f'atr_{period}'] = df['true_range'].rolling(window=period).mean()
            
            # Volatility
            df[f'volatility_{period}'] = df['returns'].rolling(period).std()
            
            # Extremes
            df[f'high_low_range_{period}'] = (
                (df['high'].rolling(period).max() - df['low'].rolling(period).min()) 
                / df['close']
            )
            
            # Volatility acceleration
            df[f'volatility_change_{period}'] = (
                df[f'volatility_{period}'] / df[f'volatility_{period}'].shift(1)
            )
        
        # Add sentiment indicators
        df['body_ratio'] = abs(df['close'] - df['open']) / (df['high'] - df['low'])
        df['upper_shadow'] = (df['high'] - df[['open', 'close']].max(axis=1)) / (df['high'] - df['low'])
        df['lower_shadow'] = (df[['open', 'close']].min(axis=1) - df['low']) / (df['high'] - df['low'])
        
        # Data clearing
        for col in df.columns:
            if df[col].dtype == float:
                df[col] = df[col].replace([np.inf, -np.inf], np.nan)
                df[col] = df[col].fillna(method='ffill').fillna(0)
                
        return df
    
    def prepare_features(self, df):
        # Select features for the model
        feature_cols = []
        
        # Add time features
        time = pd.to_datetime(df['time'], unit='s')
        df['hour_sin'] = np.sin(2 * np.pi * time.dt.hour / 24)
        df['hour_cos'] = np.cos(2 * np.pi * time.dt.hour / 24)
        df['day_sin'] = np.sin(2 * np.pi * time.dt.dayofweek / 7)
        df['day_cos'] = np.cos(2 * np.pi * time.dt.dayofweek / 7)
        
        # Collect all features
        for period in self.lookback_periods:
            feature_cols.extend([
                f'atr_{period}',
                f'volatility_{period}',
                f'high_low_range_{period}',
                f'volatility_change_{period}'
            ])
            
        feature_cols.extend([
            'body_ratio',
            'upper_shadow',
            'lower_shadow',
            'hour_sin',
            'hour_cos',
            'day_sin',
            'day_cos'
        ])
        
        # Create DataFrame with features
        features = df[feature_cols].copy()
        features = features.replace([np.inf, -np.inf], 0).fillna(0)
        
        # Scale features
        scaled_features = self.scaler.fit_transform(features)
        
        return pd.DataFrame(
            scaled_features,
            columns=feature_cols,
            index=features.index
        )
    
    def create_target(self, df, forward_window=12):
        """Creates a binary label: 1 for high volatility, 0 for low volatility"""
        # Calculate future volatility
        future_vol = df['returns'].rolling(
            window=forward_window, min_periods=1, center=False
        ).std().shift(-forward_window)
        
        # Determine the threshold for high volatility
        vol_threshold = np.nanpercentile(future_vol, self.volatility_threshold)
        
        # Create binary labels
        target = (future_vol > vol_threshold).astype(int)
        target = target.fillna(0)
        
        return target
    
    def prepare_dataset(self, df, forward_window=12):
        print("\n=== Preparing Dataset ===")
        print("Initial shape:", df.shape)
        
        df = self.calculate_features(df)
        print("After calculating features:", df.shape)
        
        features = self.prepare_features(df)
        target = self.create_target(df, forward_window)
        
        print("Final shape:", features.shape)
        print(f"Positive class ratio: {target.mean():.2%}")
        
        return features, target

class VolatilityClassifier:
    def __init__(self, lookback_periods=(5, 10, 20), forward_window=12, volatility_threshold=75):
        self.processor = VolatilityProcessor(lookback_periods, volatility_threshold)
        self.forward_window = forward_window
        
        self.model = XGBClassifier(
            n_estimators=200,
            max_depth=6,
            learning_rate=0.1,
            subsample=0.8,
            colsample_bytree=0.8,
            min_child_weight=1,
            gamma=0.1,
            reg_alpha=0.1,
            reg_lambda=1,
            scale_pos_weight=1,
            random_state=42,
            n_jobs=-1,
            eval_metric=['auc', 'error']
        )
        
        self.feature_importance = None
    
    def prepare_data(self, rates_frame):
        features, target = self.processor.prepare_dataset(rates_frame)
        return features, target
    
    def create_train_test_split(self, features, target, test_size=0.2):
        split_idx = int(len(features) * (1 - test_size))
        
        X_train = features.iloc[:split_idx]
        X_test = features.iloc[split_idx:]
        y_train = target.iloc[:split_idx]
        y_test = target.iloc[split_idx:]
        
        return X_train, X_test, y_train, y_test
    
    def train(self, X_train, y_train, X_test, y_test):
        print("\n=== Training Model ===")
        print("Training set shape:", X_train.shape)
        print("Test set shape:", X_test.shape)
        
        # Train the model
        eval_set = [(X_train, y_train), (X_test, y_test)]
        self.model.fit(
            X_train, 
            y_train,
            eval_set=eval_set,
            verbose=True
        )
        
        # Maintain the importance of features
        importance = self.model.feature_importances_
        self.feature_importance = pd.DataFrame({
            'feature': X_train.columns,
            'importance': importance
        }).sort_values('importance', ascending=False)
        
        # Evaluate the model
        predictions = self.predict(X_test)
        metrics = self.calculate_metrics(y_test, predictions)
        
        return metrics
    
    def calculate_metrics(self, y_true, y_pred):
        metrics = {
            'Accuracy': accuracy_score(y_true, y_pred),
            'Precision': precision_score(y_true, y_pred),
            'Recall': recall_score(y_true, y_pred),
            'F1 Score': f1_score(y_true, y_pred)
        }
        
        # Error matrix
        cm = confusion_matrix(y_true, y_pred)
        print("\nConfusion Matrix:")
        print(cm)
        
        return metrics
    
    def predict(self, features):
        return self.model.predict(features)
    
    def predict_proba(self, features):
        return self.model.predict_proba(features)
    
    def plot_feature_importance(self):
        plt.figure(figsize=(12, 6))
        plt.bar(
            self.feature_importance['feature'],
            self.feature_importance['importance']
        )
        plt.xticks(rotation=45, ha='right')
        plt.title('Feature Importance')
        plt.tight_layout()
        plt.show()
    
    def plot_confusion_matrix(self, y_true, y_pred):
        cm = confusion_matrix(y_true, y_pred)
        plt.figure(figsize=(8, 6))
        sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
        plt.title('Confusion Matrix')
        plt.ylabel('True Label')
        plt.xlabel('Predicted Label')
        plt.tight_layout()
        plt.show()

def check_mt5_data(symbol="EURUSD"):
    if not mt5.initialize():
        print(f"MT5 initialization error: {mt5.last_error()}")
        return None
    
    rates = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_H1, 0, 10000)
    mt5.shutdown()
    
    if rates is None:
        return None
        
    return pd.DataFrame(rates)

def main():
    symbol = "EURUSD"
    rates_frame = check_mt5_data(symbol)
    
    if rates_frame is not None:
        # Create and train the model
        model = VolatilityClassifier(
            lookback_periods=(5, 10, 20), 
            forward_window=12, 
            volatility_threshold=75
        )
        features, target = model.prepare_data(rates_frame)
        
        # Separate data
        X_train, X_test, y_train, y_test = model.create_train_test_split(
            features, target, test_size=0.2
        )
        
        # Train and evaluate
        metrics = model.train(X_train, y_train, X_test, y_test)
        print("\n=== Model Performance ===")
        for metric, value in metrics.items():
            print(f"{metric}: {value:.4f}")
        
        # Make predictions
        predictions = model.predict(X_test)
        
        # Visualize results
        model.plot_feature_importance()
        model.plot_confusion_matrix(y_test, predictions)
    else:
        print("Failed to process data: MT5 data retrieval error")

if __name__ == "__main__":
    main()

Neue Modellergebnisse


Nach der Umstellung auf die Klassifizierung verbesserten sich die Ergebnisse drastisch. Die „Präzision“ erreichte etwa 70 %, was bedeutet, dass von 10 Signalen mit hoher Volatilität 7 tatsächlich ausgelöst wurden. Ein ‚Recall‘ von etwa 65 % bedeutet, dass wir etwa zwei Drittel aller gefährlichen Momente erfasst haben. Aber die Hauptsache ist, dass das Modell im Handel wirklich anwendbar geworden ist.

Nachdem nun die Grundstruktur des Modells definiert wurde, wollen wir im nächsten Abschnitt darüber sprechen, wie es in ein reales Handelssystem integriert werden kann und welche spezifischen Handelsentscheidungen auf der Grundlage seiner Signale getroffen werden können. Ich bin sicher, dass dies der interessanteste Teil unserer Reise in die Welt der Volatilitätsprognosen sein wird.

Wie gefällt Ihnen dieser Ansatz? Es wäre interessant zu wissen, ob Sie etwas Ähnliches in Ihrer Praxis verwendet haben. Und wenn ja, welche anderen Volatilitätsmetriken halten Sie für nützlich?


Indikator für zukünftige extreme Volatilität

Der von mir entwickelte Indikator ist ein umfassendes Instrument zur Vorhersage von Volatilitätsspitzen auf dem Devisenmarkt. Im Gegensatz zu herkömmlichen Volatilitätsindikatoren, die lediglich den aktuellen Stand anzeigen, sagt unser Indikator die Wahrscheinlichkeit starker Bewegungen in den nächsten 12 Stunden voraus.

import tkinter as tk
from tkinter import ttk
import matplotlib
matplotlib.use('Agg')  # Important to install before importing pyplot
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import time
import MetaTrader5 as mt5

class VolatilityPredictor(tk.Tk):
   def __init__(self):
       super().__init__()
       self.title("Volatility Predictor")
       self.geometry("600x600")
       
       # Initialize the model
       self.model = VolatilityClassifier(
           lookback_periods=(5, 10, 20),
           forward_window=12,
           volatility_threshold=75
       )
       
       # Load and train the model at startup
       self.initialize_model()
       
       # Create the interface
       self.create_gui()
       
       # Launch the update
       self.update_data()
   
   def initialize_model(self):
       rates_frame = check_mt5_data("EURUSD")
       if rates_frame is not None:
           features, target = self.model.prepare_data(rates_frame)
           X_train, X_test, y_train, y_test = self.model.create_train_test_split(
               features, target, test_size=0.2
           )
           self.model.train(X_train, y_train, X_test, y_test)
   
   def create_gui(self):
       # Top panel with settings
       control_frame = ttk.Frame(self)
       control_frame.pack(fill='x', padx=2, pady=2)
       
       ttk.Label(control_frame, text="Symbol:").pack(side='left', padx=2)
       self.symbol_var = tk.StringVar(value="EURUSD")
       symbol_list = ["EURUSD", "GBPUSD", "USDJPY"]  # Simplified list
       ttk.Combobox(control_frame, textvariable=self.symbol_var, 
                   values=symbol_list, width=8).pack(side='left', padx=2)
       
       ttk.Label(control_frame, text="Alert:").pack(side='left', padx=2)
       self.threshold_var = tk.StringVar(value="0.7")
       ttk.Entry(control_frame, textvariable=self.threshold_var, 
                width=4).pack(side='left')
       
       # Chart
       self.fig = Figure(figsize=(6, 4), dpi=100)
       self.canvas = FigureCanvasTkAgg(self.fig, self)
       self.canvas.draw()
       self.canvas.get_tk_widget().pack(fill='both', expand=True, padx=2, pady=2)
       
       # Probability indicator
       gauge_frame = ttk.Frame(self)
       gauge_frame.pack(fill='x', padx=2, pady=2)
       
       self.probability_var = tk.StringVar(value="0%")
       self.probability_label = ttk.Label(
           gauge_frame, 
           textvariable=self.probability_var,
           font=('Arial', 20, 'bold')
       )
       self.probability_label.pack()
       
       self.progress = ttk.Progressbar(
           gauge_frame, 
           length=150,
           mode='determinate',
           maximum=100
       )
       self.progress.pack(pady=2)

   def update_data(self):
       try:
           rates = check_mt5_data(self.symbol_var.get())
           if rates is not None:
               features, _ = self.model.prepare_data(rates)
               probability = self.model.predict_proba(features)[-1][1]
               
               self.update_indicators(rates, probability)
               
               threshold = float(self.threshold_var.get())
               if probability > threshold:
                   self.alert(probability)
           
       except Exception as e:
           print(f"Error updating data: {e}")
       
       finally:
           self.after(1000, self.update_data)

   def update_indicators(self, rates, probability):
       self.fig.clear()
       ax = self.fig.add_subplot(111)
       
       df = rates.tail(50)  # Show fewer bars for compactness
       width = 0.6
       width2 = 0.1
       
       up = df[df.close >= df.open]
       down = df[df.close < df.open]
       
       ax.bar(up.index, up.close-up.open, width, bottom=up.open, color='g')
       ax.bar(up.index, up.high-up.close, width2, bottom=up.close, color='g')
       ax.bar(up.index, up.low-up.open, width2, bottom=up.open, color='g')
       
       ax.bar(down.index, down.close-down.open, width, bottom=down.open, color='r')
       ax.bar(down.index, down.high-down.open, width2, bottom=down.open, color='r')
       ax.bar(down.index, down.low-down.close, width2, bottom=down.close, color='r')
       
       ax.grid(False)  # Remove the grid for compactness
       ax.set_xticks([])  # Remove X axis labels
       self.canvas.draw()
       
       prob_pct = int(probability * 100)
       self.probability_var.set(f"{prob_pct}%")
       self.progress['value'] = prob_pct
       
       if prob_pct > 70:
           self.probability_label.configure(foreground='red')
       elif prob_pct > 50:
           self.probability_label.configure(foreground='orange')
       else:
           self.probability_label.configure(foreground='green')

   def alert(self, probability):
       window = tk.Toplevel(self)
       window.title("Alert!")
       window.geometry("200x80")  # Reduced alert window
       
       msg = f"High volatility: {probability:.1%}"
       ttk.Label(window, text=msg, font=('Arial', 10)).pack(pady=10)
       ttk.Button(window, text="OK", command=window.destroy).pack()

def main():
   app = VolatilityPredictor()
   app.mainloop()

if __name__ == "__main__":
   main()

Das Hauptfenster des Indikators ist in drei Hauptbereiche unterteilt. Im oberen Bereich wird ein japanisches Kerzen-Chart der letzten 100 Balken angezeigt, das die aktuelle Kursdynamik übersichtlich darstellt. Grüne und rote Kerzen zeigen traditionell steigende und fallende Marktbewegungen an.

Der zentrale Teil enthält das Hauptelement des Indikators – eine halbkreisförmige Wahrscheinlichkeitsskala. Sie zeigt die aktuelle Wahrscheinlichkeit einer Volatilitätsspitze in Prozent an, von 0 bis 100. Der Indikatorpfeil ist je nach Risikostufe unterschiedlich gefärbt: grün für eine geringe Wahrscheinlichkeit von bis zu 50 %, orange für eine mittlere Wahrscheinlichkeit von 50 % bis 70 % und rot für eine hohe Wahrscheinlichkeit von über 70 %.

Die Prognose basiert auf einer Analyse der aktuellen Marktlage und der historischen Volatilitätsmuster. Das Modell berücksichtigt die Daten der letzten 20 Balken, um eine Prognose zu erstellen, und sagt die Wahrscheinlichkeit einer erhöhten Volatilität in den nächsten 12 Stunden voraus. Die höchste Vorhersagegenauigkeit wird in den ersten 4-6 Stunden nach dem Signal erreicht.

Bei geringer Wahrscheinlichkeit (grüner Bereich) wird der Markt wahrscheinlich seine ruhige Bewegung fortsetzen. Dies ist ein guter Zeitpunkt, um mit den Standardeinstellungen des Handelssystems zu arbeiten. Während dieser Zeiträume können reguläre Stop-Loss-Niveaus verwendet werden.

Wenn der Indikator eine Durchschnittswahrscheinlichkeit anzeigt und der Pfeil orangefarben ist, sollten Sie besonders vorsichtig sein. In solchen Fällen empfiehlt es sich, die Schutzanordnungen um etwa ein Viertel der Standardgröße zu erhöhen.

Wenn die Wahrscheinlichkeit eines Spikes hoch ist und der Pfeil rot wird, sollte das Risikomanagement ernsthaft überprüft werden. In solchen Phasen empfiehlt es sich, den Stop-Loss um mindestens das Anderthalbfache zu erhöhen und eventuell keine neuen Positionen zu eröffnen, bis sich die Situation stabilisiert hat.

Die Bedienelemente befinden sich auf der unteren Teil des Indikators. Hier können Sie ein Handelsinstrument und ein Zeitintervall auswählen und den Schwellenwert für den Erhalt von Benachrichtigungen festlegen. Standardmäßig ist der Schwellenwert auf 70 % eingestellt – dies ist der optimale Wert, der ein Gleichgewicht zwischen der Anzahl der Signale und ihrer Zuverlässigkeit herstellt.

Die Prognosegenauigkeit des Indikators erreicht bei Signalen mit hoher Volatilität 70 %. Das bedeutet, dass von zehn Warnungen vor einem möglichen Anstieg sieben tatsächlich eintreten. Gleichzeitig erfasst der Indikator etwa zwei Drittel aller bedeutenden Marktbewegungen.

Es ist wichtig zu verstehen, dass der Indikator nicht die Richtung der Kursbewegung vorhersagt, sondern nur die Wahrscheinlichkeit einer erhöhten Volatilität. Ihr Hauptzweck besteht darin, den Händler vor einer möglichen starken Marktbewegung zu warnen, damit er seine Handelsstrategie und die Höhe der Schutzaufträge im Voraus anpassen kann.

Für künftige Versionen des Indikators ist geplant, die Möglichkeit der automatischen Anpassung der Stop-Loss auf der Grundlage der prognostizierten Volatilität hinzuzufügen. Dies wird den Risikomanagementprozess weiter automatisieren und den Handel sicherer machen.

Der Indikator ist eine perfekte Ergänzung zu bestehenden Handelssystemen und dient als zusätzlicher Filter für das Risikomanagement. Sein Hauptvorteil ist die Fähigkeit, vor möglichen starken Bewegungen im Voraus zu warnen, was dem Händler Zeit gibt, sich vorzubereiten und seine Strategie anzupassen.


Schlussfolgerung

Im modernen Handel ist die Vorhersage der Volatilität nach wie vor eine der wichtigsten Aufgaben für den erfolgreichen Handel. Der in diesem Artikel beschriebene Weg von einem einfachen Regressionsmodell zu einem High-Volatility-Klassifikator zeigt, dass manchmal eine einfache Lösung effektiver ist als eine komplexe. Das entwickelte System erreicht eine Vorhersagegenauigkeit von etwa 70 % und erfasst etwa zwei Drittel aller bedeutenden Marktbewegungen.

Die wichtigste Schlussfolgerung ist, dass für die praktische Anwendung nicht der exakte Wert der künftigen Volatilität, sondern eine rechtzeitige Warnung vor ihrem potenziellen Anstieg wichtiger ist. Der erstellte Indikator löst dieses Problem erfolgreich und ermöglicht es den Händlern, ihre Handelsstrategien und die Höhe der Schutzaufträge im Voraus anzupassen. Die Kombination von klassischen Analysemethoden mit modernen Machine-Learning-Technologien eröffnet neue Möglichkeiten für das Marktrisikomanagement.

Es stellte sich heraus, dass der Schlüssel zum Erfolg nicht die Komplexität der Algorithmen, sondern die richtige Formulierung des Problems und eine qualitativ hochwertige Datenaufbereitung waren. Dieser Ansatz kann an verschiedene Handelsinstrumente und Zeitrahmen angepasst werden und macht den Handel sicherer und berechenbarer.

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

Beigefügte Dateien |
VolPredictor.py (20.19 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (4)
Aleksei Morozov
Aleksei Morozov | 9 Feb. 2025 in 09:21
Toller Artikel, vielen Dank! Mir ist klar, dass der Artikel noch recht frisch ist, aber ich frage trotzdem: Haben Sie Erfahrung mit Volatilitätsprognosen? Als ich selbst mit Regressionen "herumprobierte", bestätigte ich die Beobachtungen Dritter, dass Vorhersagen mit dem Wort "absolut" unmöglich sind. Kurz gesagt - Training des Modells über einen Zeitraum von mehreren Monaten mit Validierung anhand der Werte des nächsten Monats und Testen des Modells im nächsten Monat. Die Testregressionslinie liegt perfekt auf den Kursen. Aber es lohnt sich, das "Ziel" für das Modell um 1 Bar in die Zukunft zu verschieben und der Test ist ein komplettes Arschloch. Es ist kein Geheimnis, dass alle Indikatoren, einschließlich der Volatilität, vom Preis abgeleitet werden. Man ist skeptisch, dass das Ergebnis ähnlich sein sollte. Andererseits ist mir klar, dass der Grad der Vielfalt der Daten im Datensatz die Leistung des Modells stark beeinflussen kann. Warum ich mich für Ihren Artikel interessierte - ich dachte, dass Ihr Ansatz viel besser ist als die "Anpassung" von Finanznachrichtenkalendern innerhalb der Strategie, um Handelsoperationen in der Nähe (vor) der Nachrichten zu vermeiden.
Yevgeniy Koshtenko
Yevgeniy Koshtenko | 12 Feb. 2025 in 12:13
Aleksei Morozov Handel in der Nähe (vor) der Nachrichten zu vermeiden.

Hallo! Vielen Dank. Ich verlasse mich nicht nur auf eine Methode. Ich habe einen umfassenden Python EA, der naive Musteranalyse, maschinelles Lernen auf Binärcode, maschinelles Lernen auf 3D-Balken, neuronales Netzwerk auf Volumenanalyse, Volatilitätsanalyse, Wirtschaftsmodell basierend auf Weltbank- und IWF-Daten, riesige Datensätze mit Hunderttausenden von Zeilen über alle Länder der Welt, alle Statistiken, die auf .... möglich sind, beinhaltet.Und ein statistisches Modul, das alle möglichen statistischen Merkmale erstellt, und ein genetischer Algorithmus, der Hyperparameter optimiert, und ein Arbitragemodul, das faire Währungspreise erstellt, und das Herunterladen der Schlagzeilen und des Inhalts der weltweiten Medien zu einer bestimmten Währung, mit einer Analyse der emotionalen Färbung aller Nachrichtenartikel und -notizen (in 80 % der Fälle, wenn die Medien Sie ermutigen, etwas zu kaufen, kommt dann der Einbruch, wenn die Nachrichten negativ sind - steigt höchstwahrscheinlich mit einer Verzögerung von 3-4 Tagen).

Haben Sie irgendwelche Ideen, was man noch hinzufügen könnte? Ich bin nur zu dem Schluss gekommen, dass ich noch einen Upload von Positionen von einer bekannten Kontoüberwachungsseite (ich weiß nicht, ob ich den Namen hier sagen darf) machen muss, ich habe den Code gemacht, ich werde auch einen Artikel darüber schreiben, der Kurs geht meistens gegen die Masse.

Ich arbeite auch daran, Daten über Futures-Volumina, Volumencluster und die Analyse von COT-Berichten hochzuladen - ebenfalls in Python.

Yevgeniy Koshtenko
Yevgeniy Koshtenko | 12 Feb. 2025 in 12:14
Aleksei Morozov Handel in der Nähe (vor) der Nachrichten zu vermeiden.

Ich verwende sowohl Regressions- als auch Klassifizierungsmodelle, und bald möchte ich ein Supersystem erstellen, das alle Vorzeichen, alle Signale aller Modelle sowie gleitende Gewinne/Verluste und Gewinne/Verluste der Kontohistorie erhält und alles in das DQN-Modell einspeist=).

Михалыч Трейдинг
Михалыч Трейдинг | 22 Feb. 2025 in 09:10
venv_volatility\Scripts\activate

Die Antwort auf den ersten Befehl ist "Python", aber für diese Zeile erhalte ich "The system cannot find the specified path"

(ich habe Python frisch installiert und bin Ihren Anweisungen gefolgt)

Neuronale Netze im Handel: Ein multimodaler, werkzeuggestützter Agent für Finanzmärkte (FinAgent) Neuronale Netze im Handel: Ein multimodaler, werkzeuggestützter Agent für Finanzmärkte (FinAgent)
Wir laden Sie ein, FinAgent kennenzulernen, ein multimodales Finanzhandelsagenten-Framework zur Analyse verschiedener Datentypen, die die Marktdynamik und historische Handelsmuster widerspiegeln.
Neuronale Netze im Handel: Ein Agent mit geschichtetem Gedächtnis (letzter Teil) Neuronale Netze im Handel: Ein Agent mit geschichtetem Gedächtnis (letzter Teil)
Wir setzen unsere Arbeit an der Entwicklung des Systems von FinMem fort, das mehrschichtige Speicheransätze verwendet, die menschliche kognitive Prozesse nachahmen. Dadurch kann das Modell nicht nur komplexe Finanzdaten effektiv verarbeiten, sondern sich auch an neue Signale anpassen, was die Genauigkeit und Effektivität von Anlageentscheidungen auf sich dynamisch verändernden Märkten erheblich verbessert.
Neuronale Netze im Handel: Ein multimodaler, werkzeuggestützter Agent für Finanzmärkte (letzter Teil) Neuronale Netze im Handel: Ein multimodaler, werkzeuggestützter Agent für Finanzmärkte (letzter Teil)
Wir entwickeln weiterhin die Algorithmen für FinAgent, einen multimodalen Finanzhandelsagenten, der multimodale Marktdynamikdaten und historische Handelsmuster analysiert.
Einführung in MQL5 (Teil 25): Aufbau eines EAs, der mit Chart-Objekten handelt (II) Einführung in MQL5 (Teil 25): Aufbau eines EAs, der mit Chart-Objekten handelt (II)
In diesem Artikel wird erklärt, wie man einen Expert Advisor (EA) erstellt, der mit Chart-Objekten, insbesondere Trendlinien, interagiert, um Ausbruchs- und Umkehrmöglichkeiten zu erkennen und zu handeln. Sie werden lernen, wie der EA gültige Signale bestätigt, die Handelsfrequenz verwaltet und die Konsistenz mit den vom Nutzer ausgewählten Strategien aufrechterhält.