Neuro-symbolische Systeme im algorithmischen Handel: Kombination von symbolischen Regeln und neuronalen Netzen
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:
- frequency (Häufigkeit) – wie oft das Muster in der Vergangenheit aufgetreten ist
- winrate (Gewinnrate) – wie oft sich der Kurs nach einem Muster in die vorhergesagte Richtung bewegt hat
- 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:
- Größe des Analysefensters (in unserem Fall sind es 10 Zeitschritte)
- Anzahl der Basismerkmale (Preis, Volumen, technische Indikatoren)
- 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
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Neuronale Netze im Handel: Modelle mit Wavelet-Transformation und Multitasking-Aufmerksamkeit (letzter Teil)
Neuronale Netze im Handel: Modelle mit Wavelet-Transformation und Multitasking-Aufmerksamkeit
Big Bang – Big Crunch (BBBC) Algorithmus
Black Hole Algorithmus (BHA)
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Veröffentlichter Artikel Neurosymbolische Systeme im Algorithmenhandel: Kombination von symbolischen Regeln und neuronalen Netzen:
Autor: Yevgeniy Koshtenko