English Русский 中文 Español 日本語 Português
preview
Neuro-symbolische Systeme im algorithmischen Handel: Kombination von symbolischen Regeln und neuronalen Netzen

Neuro-symbolische Systeme im algorithmischen Handel: Kombination von symbolischen Regeln und neuronalen Netzen

MetaTrader 5Handelssysteme |
42 2
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Einführung in neurosymbolische Systeme: Grundsätze der Kombination von Regeln und neuronalen Netzen

Stellen Sie sich vor, Sie versuchen, einem Computer zu erklären, wie man an der Börse handelt. Einerseits gibt es klassische Regeln und Muster – „Kopf und Schultern“, „Doppelboden“ und Hunderte anderer Muster, die jeder Händler kennt. Viele von uns haben EAs in MQL5 geschrieben und versucht, diese Muster zu kodieren. Aber der Markt ist ein lebendiger Organismus, er verändert sich ständig, und strenge Regeln versagen oft.

Auf der anderen Seite gibt es neuronale Netze – modern, leistungsstark, aber manchmal völlig undurchsichtig in ihren Entscheidungen. Füttern Sie ein LSTM-Netzwerk mit historischen Daten, und es wird Vorhersagen mit angemessener Genauigkeit machen. Die Gründe für diese Entscheidungen bleiben jedoch oft ein Geheimnis. Beim Handel kann jeder falsche Schritt bares Geld kosten.

Ich erinnere mich, dass ich vor einigen Jahren mit diesem Dilemma in meinem Handelsalgorithmus zu kämpfen hatte. Klassische Muster führten zu falsch-positiven Ergebnissen, und das neuronale Netz erstellte manchmal unglaubliche Vorhersagen ohne jegliche Logik. Und dann dämmerte es mir: Was wäre, wenn wir beide Ansätze kombinieren? Wie wäre es, wenn wir klare Regeln als Systemrahmen verwenden und das neuronale Netz als adaptiven Mechanismus, der die aktuelle Marktlage berücksichtigt?

So wurde die Idee eines neurosymbolischen Systems für den algorithmischen Handel geboren. Stellen Sie sich einen erfahrenen Händler vor, der alle klassischen Muster und Regeln kennt, aber auch weiß, wie er sich dem Markt anpassen kann, indem er subtile Nuancen und Beziehungen berücksichtigt. Ein solches System hat ein „Skelett“ aus klaren Regeln und „Muskeln“ in Form eines neuronalen Netzes, das für Flexibilität und Anpassungsfähigkeit sorgt.

In diesem Artikel erkläre ich, wie mein Team und ich ein solches System in Python entwickelt haben und zeige, wie man die klassische Musteranalyse mit modernen Methoden des maschinellen Lernens kombiniert. Wir werden die Architektur von den Basiskomponenten bis hin zu komplexen Entscheidungsmechanismen durchgehen, und natürlich werde ich auch echten Code und Testergebnisse vorstellen.

Sind Sie bereit, in die Welt einzutauchen, in der klassische Handelsregeln auf neuronale Netze treffen? Na dann, auf geht's!


Symbolische Regeln im Handel: Muster und ihre Statistik

Beginnen wir mit einer einfachen Frage: Was ist ein Marktmuster? In der klassischen technischen Analyse ist dies eine bestimmte Figur auf dem Chart, z. B. ein „Doppelboden“ oder eine „Fahne“. Aber wenn wir über die Programmierung von Handelssystemen sprechen, müssen wir abstrakter denken. In unserem Code ist ein Muster eine Folge von Kursbewegungen, die in binärer Form kodiert sind: 1 für Wachstum, 0 für Rückgang.

Das wirkt primitiv, könnte man sagen? Ganz und gar nicht. Diese Darstellung gibt uns ein leistungsfähiges Instrument für die Analyse. Nehmen wir die Sequenz [1, 1, 0, 1, 0] – dies ist nicht nur eine Reihe von Zahlen, sondern ein kodierter Mini-Trend. In Python können wir mit einfachem, aber effektivem Code nach solchen Mustern suchen:

pattern = tuple(np.where(data['close'].diff() > 0, 1, 0))

Aber der eigentliche Zauber beginnt, wenn wir die Statistiken analysieren. Für jedes Muster können wir drei Schlüsselparameter berechnen:

  1. frequency (Häufigkeit) – wie oft das Muster in der Vergangenheit aufgetreten ist
  2. winrate (Gewinnrate) – wie oft sich der Kurs nach einem Muster in die vorhergesagte Richtung bewegt hat
  3. reliability (Zuverlässigkeit) – ein komplexer Indikator, der sowohl die Häufigkeit als auch die Gewinnrate berücksichtigt

Hier ein reales Beispiel aus meiner Praxis: Das Muster [1, 1, 1, 0, 0] auf EURUSD H4 zeigt eine Gewinnrate von 68% mit einer Häufigkeit von mehr als 200 Mal pro Jahr. Klingt verlockend, oder? Dabei ist es jedoch wichtig, nicht in die Falle der Überoptimierung zu tappen.

Aus diesem Grund haben wir einen dynamischen Zuverlässigkeitsfilter hinzugefügt:

reliability = frequency * winrate * (1 - abs(0.5 - winrate))

Diese Gleichung ist in ihrer Einfachheit verblüffend. Es berücksichtigt nicht nur die Häufigkeit und Gewinnrate, sondern bestraft auch Muster mit verdächtig hoher Effizienz, die sich oft als statistische Anomalie herausstellen.

Die Länge der Muster ist eine andere Geschichte. Kurze Muster (3-4 Balken) sind üblich, aber begleitet von viel Rauschen. Lange (20-25 Balken) sind zuverlässiger, aber selten. Die goldene Mitte liegt normalerweise im Bereich von 5-8 Balken. Obwohl ich zugeben muss, dass ich bei einigen Instrumenten hervorragende Ergebnisse mit Mustern aus 12 Balken erzielt habe.

Ein wichtiger Punkt ist der Prognosehorizont. In unserem System verwenden wir den Parameter forecast_horizon, der bestimmt, wie viele Balken im Voraus wir versuchen, die Bewegung vorherzusagen. Empirisch haben wir einen Wert von 6 ermittelt, der ein optimales Gleichgewicht zwischen Prognosegenauigkeit und Handelsmöglichkeiten bietet.

Am interessantesten wird es jedoch, wenn wir anfangen, die Muster unter verschiedenen Marktbedingungen zu analysieren. Ein und dasselbe Muster kann sich bei unterschiedlicher Volatilität oder zu verschiedenen Tageszeiten völlig unterschiedlich verhalten. Aus diesem Grund sind einfache Statistiken nur der erste Schritt. An dieser Stelle kommen neuronale Netze ins Spiel, aber darüber sprechen wir im nächsten Abschnitt.


Neuronale Netzarchitektur für die Marktdatenanalyse

Werfen wir nun einen Blick auf das „Gehirn“ unseres Systems – das neuronale Netz. Nach umfangreichen Experimenten haben wir uns für eine hybride Architektur entschieden, die LSTM-Schichten für die Verarbeitung von Zeitreihen und voll verknüpfte Schichten für die Verarbeitung statistischer Merkmale von Mustern kombiniert.

Warum LSTM? Der Punkt ist, dass die Marktdaten nicht nur eine Reihe von Zahlen sind, sondern eine Sequenz, in der jeder Wert mit den vorherigen in Beziehung steht. LSTM-Netze eignen sich hervorragend zur Erfassung solcher langfristigen Abhängigkeiten. So sieht die Grundstruktur unseres Netzes aus:

model = tf.keras.Sequential([
    tf.keras.layers.LSTM(256, input_shape=input_shape, return_sequences=True),
    tf.keras.layers.Dropout(0.4),
    tf.keras.layers.LSTM(128),
    tf.keras.layers.Dropout(0.3),
    tf.keras.layers.Dense(64, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

Beachten Sie die Dropout-Schichten – dies ist unser Schutz vor Überanpassung. In den ersten Versionen des Systems haben wir sie nicht verwendet, und das Netz funktionierte perfekt mit historischen Daten, versagte aber auf dem realen Markt. Dropout schaltet nach dem Zufallsprinzip einige Neuronen während des Trainings aus und zwingt das Netz, nach robusteren Mustern zu suchen.

Ein wichtiger Punkt ist die Dimension der Eingabedaten. Der Parameter input_shape wird durch drei Schlüsselfaktoren bestimmt:

  1. Größe des Analysefensters (in unserem Fall sind es 10 Zeitschritte)
  2. Anzahl der Basismerkmale (Preis, Volumen, technische Indikatoren)
  3. Anzahl der aus den Mustern extrahierten Merkmale

Das Ergebnis ist ein Tensor der Dimension (batch_size, 10, features), wobei „features“ die Gesamtzahl aller Merkmale ist. Dies ist genau das Datenformat, das die erste LSTM-Schicht erwartet.

Beachten Sie den Parameter return_sequences=True in der ersten LSTM-Schicht. Das bedeutet, dass die Schicht für jeden Zeitschritt eine Folge von Ausgaben liefert, nicht nur den letzten. Dadurch kann die zweite LSTM-Schicht detailliertere Informationen über die zeitliche Dynamik erhalten. Das zweite LSTM erzeugt jedoch nur den Endzustand – seine Ausgabe geht an vollständig verbundene Schichten.

Vollständig verknüpfte Schichten (Dense) fungieren als „Interpreter“ – sie wandeln die von LSTM gefundenen komplexen Muster in eine konkrete Lösung um. Die erste Dense-Schicht mit ReLU-Aktivierung verarbeitet nichtlineare Abhängigkeiten, und die letzte Schicht mit Sigmoid-Aktivierung erzeugt die Wahrscheinlichkeit einer Aufwärtsbewegung des Preises.

Der Prozess der Modellerstellung verdient besondere Aufmerksamkeit:

model.compile(
    optimizer='adam',
    loss='binary_crossentropy',
    metrics=['accuracy', tf.keras.metrics.Precision(), tf.keras.metrics.Recall()]
)

Wir verwenden den Adam-Optimierer, der sich bei nicht-stationären Daten, wie z. B. Marktpreisen, als effektiv erwiesen hat. Die binäre Kreuzentropie als Verlustfunktion ist ideal für unser binäres Klassifikationsproblem (Vorhersage der Richtung der Kursbewegung). Eine Reihe von Metriken hilft, nicht nur die Genauigkeit, sondern auch die Qualität der Vorhersagen in Bezug auf falsch positive und falsch negative Ergebnisse zu verfolgen.

Während der Entwicklung haben wir mit verschiedenen Netzwerkkonfigurationen experimentiert. Wir haben versucht, Faltungsschichten (CNN) hinzuzufügen, um lokale Muster zu erkennen, und haben mit dem Aufmerksamkeitsmechanismus experimentiert, sind aber letztlich zu dem Schluss gekommen, dass Einfachheit und Transparenz der Architektur wichtiger sind. Je komplexer das Netz ist, desto schwieriger ist es, seine Entscheidungen zu interpretieren, und im Handel ist es von entscheidender Bedeutung, die Logik hinter dem Systembetrieb zu verstehen.


Integration von Mustern in neuronale Netze: Anreicherung der Eingabedaten

Jetzt kommt der interessanteste Teil: wie wir klassische Muster mit einem neuronalen Netz „kreuzen“. Dabei handelt es sich nicht nur um eine Aneinanderreihung von Merkmalen, sondern um ein ganzes System der vorläufigen Datenverarbeitung und -analyse.

Beginnen wir mit einem einfachen Satz von Eingabedaten. Für jeden Zeitpunkt bilden wir einen mehrdimensionalen Merkmalsvektor, der Folgendes enthält:

base_features = [
    'close',  # Close price 
    'volume',  # Volume
    'rsi',    # Relative Strength Index
    'macd',   # MACD
    'bb_upper', 'bb_lower'  # Bollinger Bands borders
]

Dies ist jedoch erst der Anfang. Die wichtigste Neuerung ist die Hinzufügung von Musterstatistiken. Für jedes Muster berechnen wir drei Schlüsselindikatoren:

pattern_stats = {
    'winrate': np.mean(outcomes),  # Percentage of successful triggers
    'frequency': len(outcomes),     # Occurrence frequency
    'reliability': len(outcomes) * np.mean(outcomes) * (1 - abs(0.5 - np.mean(outcomes)))  # Reliability
}

Besonderes Augenmerk sollte auf die letzte Kennzahl – Zuverlässigkeit (reliability) – gelegt werden. Dabei handelt es sich um eine Eigenentwicklung, die nicht nur die Häufigkeit und Gewinnquote, sondern auch die „Verdächtigkeit“ von Statistiken berücksichtigt. Liegt die Gewinnquote zu nahe an 100 % oder ist sie zu unbeständig, sinkt der Zuverlässigkeitsindikator.

Die Integration dieser Daten in ein neuronales Netz erfordert besondere Sorgfalt. 

def prepare_data(df):
    # We normalize the basic features using MinMaxScaler
    X_base = self.scaler.fit_transform(df[base_features].values)
    
    # For pattern statistics we use special normalization
    pattern_features = self.pattern_analyzer.extract_pattern_features(
        df, lookback=len(df)
    )
    
    return np.column_stack((X_base, pattern_features))

Lösung des Problems der unterschiedlichen Mustergrößen:

def extract_pattern_features(self, data, lookback=100):
    features_per_length = 5  # fixed number of features per pattern
    total_features = len(self.pattern_lengths) * features_per_length
    
    features = np.zeros((len(data) - lookback, total_features))
    # ... filling the feature array

Jedes Muster, unabhängig von seiner Länge, wird in einen Vektor fester Größe umgewandelt. Dies löst das Problem der wechselnden Anzahl aktiver Muster und ermöglicht es dem neuronalen Netz, mit einer Eingabe konstanter Dimension zu arbeiten.

Die Berücksichtigung des Marktumfelds ist eine andere Geschichte. Wir fügen besondere Merkmale hinzu, die den aktuellen Stand des Marktes charakterisieren:

market_features = {
    'volatility': calculate_atr(data),  # Volatility via ATR
    'trend_strength': calculate_adx(data),  # Trend strength via ADX
    'market_phase': identify_market_phase(data)  # Market phase
}

Dadurch kann sich das System an unterschiedliche Marktbedingungen anpassen. In Zeiten hoher Volatilität erhöhen wir zum Beispiel automatisch die Anforderungen an die Zuverlässigkeit der Muster.

Ein wichtiger Punkt ist der Umgang mit fehlenden Daten. Im realen Handel ist dies ein häufiges Problem, insbesondere wenn man mit mehreren Zeitrahmen arbeitet. Wir lösen sie durch eine Kombination von Methoden:

# Fill in the blanks, taking into account the specifics of each feature
df['close'] = df['close'].fillna(method='ffill')  # for prices
df['volume'] = df['volume'].fillna(df['volume'].rolling(24).mean())  # for volumes
pattern_features = np.nan_to_num(pattern_features, nan=-1)  # for pattern features

Als Ergebnis erhält das neuronale Netz einen vollständigen und konsistenten Datensatz, in dem klassische technische Muster die grundlegenden Marktindikatoren organisch ergänzen. Dies verschafft dem System einen einzigartigen Vorteil: Es kann sich sowohl auf bewährte Muster als auch auf komplexe Beziehungen stützen, die beim Training entdeckt werden.


System der Entscheidungsfindung: Von der Analyse zu den Signalen

Lassen Sie uns darüber sprechen, wie das System tatsächlich Entscheidungen trifft. Vergessen Sie die neuronalen Netze und Muster für eine Minute – am Ende des Tages müssen wir eine klare Entscheidung treffen: in den Markt einsteigen oder nicht. Und wenn wir einsteigen, dann müssen wir das Volumen kennen.

Unsere Grundlogik ist einfach: Wir nehmen zwei Datenströme – eine Vorhersage aus einem neuronalen Netz und eine Musterstatistik. Das neuronale Netz gibt uns die Wahrscheinlichkeit einer Auf-/Abwärtsbewegung an, und die Muster bestätigen oder widerlegen diese Prognose. Aber der Teufel steckt wie immer im Detail.

Hier sehen Sie, was unter der Haube vor sich geht:

def get_trading_decision(self, market_data):
    # Get a forecast from the neural network
    prediction = self.model.predict(market_data)
    
    # Extract active patterns
    patterns = self.pattern_analyzer.get_active_patterns(market_data)
    
    # Basic check of market conditions
    if not self._market_conditions_ok():
        return None  # Do not trade if something is wrong
        
    # Check the consistency of signals
    if not self._signals_aligned(prediction, patterns):
        return None  # No consensus - no deal
        
    # Calculate the signal confidence
    confidence = self._calculate_confidence(prediction, patterns)
    
    # Determine the position size
    size = self._get_position_size(confidence)
    
    return TradingSignal(
        direction='BUY' if prediction > 0.5 else 'SELL',
        size=size,
        confidence=confidence,
        patterns=patterns
    )

Als erstes prüfen wir die grundlegenden Marktbedingungen. Keine Raketenwissenschaft, nur gesunder Menschenverstand:

def _market_conditions_ok(self):
    # Check the time
    if not self.is_trading_session():
        return False
        
    # Look at the spread
    if self.current_spread > self.MAX_ALLOWED_SPREAD:
        return False
        
    # Check volatility
    if self.current_atr > self.volatility_threshold:
        return False
    
    return True

Als Nächstes folgt die Prüfung der Signalkonsistenz. Wichtig ist dabei, dass nicht alle Signale perfekt aufeinander abgestimmt sein müssen. Es reicht aus, dass sich die Hauptindikatoren nicht widersprechen:

def _signals_aligned(self, ml_prediction, pattern_signals):
    # Define the basic direction
    ml_direction = ml_prediction > 0.5
    
    # Count how many patterns confirm it
    confirming_patterns = sum(1 for p in pattern_signals 
                            if p.predicted_direction == ml_direction)
    
    # At least 60% of patterns need to be confirmed
    return confirming_patterns / len(pattern_signals) >= 0.6

Der schwierigste Teil ist die Berechnung der Signalkonfidenz. Nach zahlreichen Experimenten und Analysen verschiedener Ansätze haben wir uns für eine kombinierte Metrik entschieden, die sowohl die statistische Zuverlässigkeit der Vorhersage des neuronalen Netzes als auch die historische Leistung der erkannten Muster berücksichtigt:

def _calculate_confidence(self, prediction, patterns):
    # Baseline confidence from ML model
    base_confidence = abs(prediction - 0.5) * 2
    
    # Consider confirming patterns
    pattern_confidence = self._get_pattern_confidence(patterns)
    
    # Weighted average with empirically selected ratios
    return (base_confidence * 0.7 + pattern_confidence * 0.3)

Diese Entscheidungsarchitektur demonstriert die Effizienz eines hybriden Ansatzes, bei dem klassische technische Analysemethoden die Fähigkeiten des maschinellen Lernens organisch ergänzen. Jede Komponente des Systems trägt zur endgültigen Entscheidung bei, während ein mehrstufiges Kontrollsystem das erforderliche Maß an Zuverlässigkeit und Widerstandsfähigkeit gegenüber unterschiedlichen Marktbedingungen gewährleistet.


Schlussfolgerung

Die Kombination klassischer Muster mit der Analyse neuronaler Netzwerke führt zu einem qualitativ neuen Ergebnis: Das neuronale Netzwerk erfasst subtile Marktbeziehungen, während bewährte Muster die Grundstruktur von Handelsentscheidungen liefern. In unseren Tests hat dieser Ansatz durchweg bessere Ergebnisse erzielt als die rein technische Analyse und der isolierte Einsatz von maschinellem Lernen.

Eine wichtige Entdeckung war die Erkenntnis, dass Einfachheit und Interpretierbarkeit entscheidend sind. Wir haben bewusst auf komplexere Architekturen verzichtet, um ein transparentes und verständliches System zu schaffen. Dies ermöglicht nicht nur eine bessere Kontrolle der Handelsentscheidungen, sondern auch die Möglichkeit, bei veränderten Marktbedingungen schnell Anpassungen vorzunehmen. In einer Welt, in der viele die Komplexität suchen, hat sich die Einfachheit als unser Wettbewerbsvorteil erwiesen.

Ich hoffe, dass unsere Erfahrungen denjenigen nützlich sein werden, die ebenfalls die Grenzen dessen ausloten, was an der Schnittstelle zwischen klassischem Handel und künstlicher Intelligenz möglich ist. Schließlich entstehen in solchen interdisziplinären Bereichen oft die interessantesten und praktischsten Lösungen. Experimentieren Sie weiter, aber denken Sie daran, dass es keinen Königsweg im Handel gibt. Es gibt nur einen Weg der ständigen Weiterentwicklung und Verbesserung Ihrer Werkzeuge.

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

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (2)
Evgeniy Chernish
Evgeniy Chernish | 22 Jan. 2025 in 09:25
Das Hauptproblem ist die Stabilität der berechneten Häufigkeit des Auftretens einer weißen oder schwarzen Kerze nach dem Auftreten eines Musters. Bei kleinen Stichproben ist sie unzuverlässig, und bei großen Stichproben ist sie 50/50.

Und ich verstehe die Logik nicht, zuerst die Musterfrequenz als eines der Merkmale in Neuronka einzuspeisen und dann die gleiche Frequenz zu verwenden, um darauf aufbauende Neuronka-Signale zu filtern.


Stanislav Korotky
Stanislav Korotky | 22 Jan. 2025 in 11:53
Ohne den Ansatz an sich zu berühren, werden durch die Reduzierung der realen Bewegungsbereiche auf zwei Klassen die nützlichen Informationen, die das neuronale Netz extrahieren könnte (um derentwillen wir es einschrauben), zunichte gemacht - ähnlich wie wenn wir das Farbbilderkennungssystem mit Schwarz-Weiß-Bildern füttern würden. IMHO ist es notwendig, das Netz nicht an die alten Methoden der binären Muster anzupassen, sondern echte, unscharfe Muster auf vollständigen Daten hervorzuheben.
Neuronale Netze im Handel: Modelle mit Wavelet-Transformation und Multitasking-Aufmerksamkeit (letzter Teil) Neuronale Netze im Handel: Modelle mit Wavelet-Transformation und Multitasking-Aufmerksamkeit (letzter Teil)
Im vorangegangenen Artikel haben wir die theoretischen Grundlagen erforscht und mit der Umsetzung der Ansätze des Systems Multitask-Stockformer begonnen, das die Wavelet-Transformation und das Self-Attention-Multitask-Modell kombiniert. Wir fahren fort, die Algorithmen dieses Rahmens zu implementieren und ihre Effektivität anhand realer historischer Daten zu bewerten.
Neuronale Netze im Handel: Modelle mit Wavelet-Transformation und Multitasking-Aufmerksamkeit Neuronale Netze im Handel: Modelle mit Wavelet-Transformation und Multitasking-Aufmerksamkeit
Wir laden Sie ein, einen Rahmen zu erkunden, der Wavelet-Transformationen und ein Multitasking-Selbstaufmerksamkeitsmodell kombiniert, um die Reaktionsfähigkeit und Genauigkeit von Prognosen unter volatilen Marktbedingungen zu verbessern. Die Wavelet-Transformation ermöglicht die Zerlegung der Renditen von Vermögenswerten in hohe und niedrige Frequenzen, wodurch langfristige Markttrends und kurzfristige Schwankungen sorgfältig erfasst werden.
Big Bang – Big Crunch (BBBC) Algorithmus Big Bang – Big Crunch (BBBC) Algorithmus
Der Artikel stellt die Methode Big Bang – Big Crunch vor, die aus zwei Schlüsselphasen besteht: zyklische Erzeugung von Zufallspunkten und deren Komprimierung zur optimalen Lösung. Dieser Ansatz kombiniert Erkundung und Verfeinerung und ermöglicht es uns, schrittweise bessere Lösungen zu finden und neue Optimierungsmöglichkeiten zu erschließen.
Black Hole Algorithmus (BHA) Black Hole Algorithmus (BHA)
Der Black Hole Algorithm (BHA) nutzt die Prinzipien der Schwerkraft von Schwarzen Löchern, um Lösungen zu optimieren. In diesem Artikel werden wir uns ansehen, wie BHA die besten Lösungen findet und dabei lokale Extreme vermeidet, und warum dieser Algorithmus zu einem leistungsstarken Werkzeug für die Lösung komplexer Probleme geworden ist. Erfahren Sie, wie einfache Ideen zu beeindruckenden Ergebnissen in der Welt der Optimierung führen können.