
Integrieren Sie Ihr eigenes LLM in einen EA (Teil 5): Handelsstrategie mit LLMs(IV) entwickeln und testen - Test der Handelsstrategie
Inhaltsverzeichnis
- Inhaltsverzeichnis
- Einführung
- Entwicklungsumgebung für das Beispiel in diesem Artikel
- Methoden zum Laden von LLMs in MQL5
- Konvertierung des GPT-2-Modells in das ONNX-Modell
- Formulierung der EA-Strategie und Server-Funktionalität
- Erstellen des Inferenzdienstes
- Der Client-EA
- Backtests
- Schlussfolgerung
Einführung
In früheren Artikeln haben wir vorgestellt, wie man vortrainierte GPT-2-Modelle mit verschiedenen Methoden feinabstimmen kann, damit GPT-2 Aufgaben nach unseren Wünschen ausführt, und wir haben diese Methoden über mehrere Dimensionen hinweg verglichen. Natürlich haben wir nur einige häufig verwendete Methoden vorgestellt, was nicht bedeutet, dass nur diese Methoden zur Feinabstimmung von GPT-2-Modellen verwendet werden können. Sie können versuchen, GPT-2 mit anderen Methoden, die auf unserem Beispielimplementierungsprozess basieren, fein abzustimmen, sie zu vergleichen und ein besseres Modell zu wählen. Wenn Sie während dieses Prozesses auf Probleme stoßen, können Sie am Ende des Artikels einen Kommentar hinterlassen.
Unser fein abgestimmtes GPT-2-Modell verfügt nun über die anfängliche Fähigkeit, einfache, quantitative Handelsstrategien auszuführen. Daher wird in diesem Artikel erläutert, wie wir unser fein abgestimmtes Modell in unsere quantitative Handelsstrategie integrieren können. Das im Beispiel verwendete Modell ist das GPT-2-Modell, das mit Adapter-Tuning feinabgestimmt wurde (spezifischer Artikellink: „Integrieren Sie Ihr eigenes LLM in einen EA (Teil 5): Handelsstrategie mit LLMs entwickeln und testen (III) – Adapter-Tuning“. Sofern nicht anders angegeben, beziehen sich daher alle Verweise auf GPT-2 in diesem Artikel auf dieses Modell.
Es ist jedoch zu beachten, dass das von uns feinabgestimmte Modell auf begrenzten Daten zu Demonstrationszwecken basiert und nicht für reale Handelsumgebungen geeignet ist. Ohne Tests und Optimierungen dürfen sie nicht direkt im realen Handel eingesetzt werden, was von größter Bedeutung ist. Unser früherer Vorhersagecode wurde in der Python-Umgebung erstellt, aber MQL5, als hochintegrierte Programmiersprache für die MetaTrader 5-Plattform, bietet leistungsstarke Werkzeuge zur Entwicklung von Expert Advisors (EAs). Um automatisierte quantitative Handelsstrategien zu implementieren, müssen wir daher zur MQL5-Umgebung zurückkehren. In diesem Artikel wird dieser Prozess Schritt für Schritt erläutert.
Schauen wir uns an, wie man dieses trainierte Modell aus der Python-Umgebung in den MQL5 EA migriert, sodass es direkt auf der MetaTrader 5-Plattform läuft und Echtzeit-Handelsentscheidungen unterstützt.
Entwicklungsumgebung für das Beispiel in diesem Artikel
Lassen Sie uns die Betriebsumgebung für die Code-Beispiele in diesem Artikel vorstellen. Das bedeutet natürlich nicht, dass Ihre Code-Umgebung die gleiche sein muss wie meine, aber wenn Sie beim Ausführen des Codes auf Probleme stoßen, können Sie sich an meiner Umgebungskonfiguration orientieren.
- Betriebssystem: Ubuntu 22.04.5 LTS (oder die entsprechende Version von WSL)
- Python-Version: 3.10.14
- Erforderliche Python-Bibliotheken:
- torch-2.4.1
- numpy-1.26.3
- pandas-2.2.3
- transformers-4.45.1
- petf-0.13.0
- matplotlib-3.9.2
- onnx-1.17.0
- onnxconverter-common-1.14.0
- onnxruntime-1.20.1
- onnxruntime-tools-1.7.0
Bevor Sie mit dem nächsten Schritt fortfahren beachten Sie bitte, dass Sie ein Modell mit Adapter-Tuning trainiert haben, wie im vorherigen Artikel beschrieben (da die Größe des Modells die Plattformgrenze überschreitet, können die bereits trainierten Gewichte nicht hochgeladen werden).
Methoden zum Laden von LLMs in MQL5
Um das trainierte GPT-2-Modell in den MQL5 EA zu integrieren, ist das erste Problem, das wir lösen müssen, wie wir dieses Modell, das im Wesentlichen ein in Python trainiertes Modell ist, in die MQL5-Umgebung laden und ausführen können. Hier sind mehrere Methoden denkbar:
1. Konvertieren Sie das Modell nach ONNX und fügen Sie es dem EA hinzu
ONNX (Open Neural Network Exchange) ist ein offenes Format zur Darstellung neuronaler Netze, das die Interoperabilität zwischen verschiedenen Deep-Learning-Frameworks ermöglicht. In meinem vorigen Artikel (Datenkennzeichnung für Zeitreihenanalyse (Teil 6): Anwendung und Test des EAs, der ONNX verwendet) habe ich die Integration einfacher Modelle in EAs mit Hilfe von ONNX vorgestellt. Wir können das GPT-2-Modell auch in das ONNX-Format konvertieren, es in den EA importieren und die integrierte ONNX-Laufzeitbibliothek in MQL5 verwenden, um die Modellinferenz durchzuführen. Für die MQL5-Unterstützung von ONNX siehe die Hilfedatei „MQL5 Reference / ONNX models“ oder die offizielle MQL5-Dokumentation (https://www.mql5.com/de/docs/onnx).
- Vorteile:
- Hohe Leistung: Die ONNX-Laufzeit ist in der Regel auf Leistung optimiert, was eine relativ effiziente Inferenz im EA ermöglicht.
- Hohe Integration: MQL5 verfügt über integrierte Unterstützung für ONNX, sodass keine externen Programme oder Bibliotheken erforderlich sind.
- Unabhängigkeit: Das konvertierte ONNX-Modell kann unabhängig von der Python-Umgebung ausgeführt werden.
- Nachteile:
- Komplexität der Umwandlung: Die Konvertierung komplexer Sprachmodelle in das ONNX-Format kann eine Herausforderung darstellen und erfordert die Behandlung von Kompatibilitätsproblemen der Operatoren.
- Schwierigkeiten bei der Fehlersuche: Das Debuggen von ONNX-Modellen ist weniger komfortabel als das Debuggen von Python-Modellen.
2. Direkte Ausführung von Python-Inferenzskripten mit Winapi
MQL5 bietet Zugang zu Winapi, sodass wir die Funktion „WinExec()“ von „kernel32.dll“ aufrufen können, um externe Programme auszuführen. Auf diese Weise können wir vorhandene Python-Skripte verwenden, um das GPT-2-Modell zu laden und Inferenzen durchzuführen, und dann das Skript im EA mit „WinExec()“ aufrufen und seine Ausgabeergebnisse analysieren (alternativ kann diese Funktionalität auch mit der Funktion „ShellExecuteW()“ aus „shell32.dll“ erreicht werden). Diese Methode erfordert eine gewisse Entwicklungserfahrung und Vertrautheit mit der Windows-Entwicklung, um sie zu implementieren.
- Vorteile:
- Einfach und direkt: Das Modell muss nicht konvertiert werden, sondern nutzt direkt den vorhandenen Python-Code.
- Flexibilität: Sie können die umfangreichen Bibliotheken und Werkzeuge des Python-Ökosystems problemlos nutzen.
- Nachteile:
- Mehrkosten für die Leistung: Für jede Schlussfolgerung muss ein neuer Python-Prozess gestartet werden, was zu erheblichem Leistungsmehraufwand und Ineffizienz führt.
- Abhängigkeit: Der EA hängt von der externen Python-Umgebung und den Skripten ab.
- Datenaustausch: Es erfordert den Datenaustausch zwischen MQL5 und Python, was die Komplexität erhöht.
- Sicherheit: Es können verschiedene unerwartete Situationen auftreten, die zu unkontrollierbaren Abstürzen führen können.
Anmerkung: Von dieser Methode ist dringend abzuraten! Ich stelle diese Lösung nur vor, um zu zeigen, dass sie machbar ist und in Tests oder unter kontrollierten Bedingungen verwendet werden kann. Verwenden Sie es nicht ohne ausreichendes Vertrauen.
3. Abrufen von Python-Inferenzergebnissen durch Socket-Kommunikation
Das ist ähnlich wie die zweite Methode, aber unter Verwendung von Socket-Kommunikation anstelle von Winapi (tatsächlich kann auch das HTTP-Protokoll verwendet werden, ähnlich wie die HTTP-Dienste, die von gängigen Inferenz-Frameworks bereitgestellt werden, die im Wesentlichen dasselbe wie Socket sind, und dieser Artikel wird nicht weiter darauf eingehen). Die spezifische Implementierungsmethode besteht darin, einen Socket-Server in Python laufen zu lassen, um das Modell zu laden und Inferenzen durchzuführen, wobei der EA als Client fungiert, der sich mit dem Server verbindet, Eingabedaten sendet und Inferenzergebnisse empfängt.
- Vorteile:
- Bessere Leistung: Die Socket-Kommunikation kann den Overhead des Prozessstarts reduzieren und ist viel sicherer.
- Flexibilität: Dennoch können Sie die Vorteile von Python nutzen.
- Nachteile:
- Komplexität: Sie müssen die Kommunikationslogik zwischen dem Socket-Server und dem Client implementieren.
- Abhängigkeit: Der EA hängt von der externen Python-Umgebung und dem Socket-Server ab, was einige Kenntnisse zur Einrichtung des Dienstes erfordert.
- Stabilität: Die Stabilität der Sockelverbindungen kann den Betrieb des EA beeinträchtigen.
Hinweis:Für diese Methode gibt es eine spezielle Implementierung in meinem früheren Artikel. Wenn Sie daran interessiert sind, können Sie den Artikel „Datenkennzeichnung für Zeitreihenanalyse (Teil 5): Anwendung und Test in einem EA, der Socket verwendet“.
Wir haben verschiedene Umrechnungsmethoden diskutiert. Derzeit tendiere ich weiterhin dazu, das GPT-2-Modell in das ONNX-Format zu konvertieren und in den EA zu integrieren, da dies auch plattformübergreifende Probleme lösen kann und der EA eine höhere Integration und Stabilität aufweist. Wenn die Parameter des ONNX-Modells jedoch zu groß sind, können sie nicht in MQL5 ausgeführt werden (zum Beispiel überschreitet unser aktuelles GPT-2-Modell die MQL5-Dateiladegrenze).
Eine weitere Herausforderung ist die Lösung des Tokenizer-Problems im Transformermodell, da Modelle wie GPT-2 mit einem Tokenizer ausgestattet sind, um Eingabeinformationen zu verarbeiten, und um das GPT-2-Modell in MQL5 auszuführen, müssen wir den GPT-2-Tokenizer in MQL5 erstellen, was ein bedeutendes Projekt ist. Das ist schwierig, aber nicht unmöglich. Die MQL5-Dateigrößenbeschränkung ist jedoch ein schwer zu lösendes Problem.
Obwohl ich versucht habe, sie auf das INT8-Format zu quantisieren, überschritt sie immer noch die Grenze und konnte nicht geladen werden. Wenn im INT4-Format quantisiert wird, unterstützt MQL5 keine quantisierten Modelle im INT4-Format, obwohl die Modellgröße den Anforderungen entspricht! Daher können wir diese Methode nur bedauernd aufgeben. Dennoch werde ich in diesem Artikel ein Beispiel für die Konvertierung unseres auf den Adapter abgestimmten GPT-2-Modells in das ONNX-Format geben, in der Hoffnung, dieses Problem bald lösen zu können!
In diesem Artikel habe ich mich entschieden, die Socket-Kommunikation mit dem Python-Inferenzdienst zu diskutieren. Der Vorteil dieser Methode besteht darin, dass sie die Datensicherheit gewährleisten und unsere EA-Implementierung vereinfachen kann. Im EA brauchen wir uns nur auf unsere Strategie und Handelslogik zu konzentrieren und müssen uns nicht um zusätzliche Fragen der Modulintegration kümmern. Ein weiterer Vorteil dieser Methode besteht darin, dass diese Methode auch dann für die Entwicklung des EA verwendet werden kann, wenn es vor Ort keine entsprechende Modellentwicklungsumgebung gibt, z. B. wenn das Modell auf einem entfernten Gerät entwickelt und trainiert wird, selbst wenn die Entwicklungsumgebung nicht mit der lokalen Umgebung kompatibel ist.
Auch wenn diese Methode technisch kompliziert erscheint und mehr Wissen erfordert, kann sie eine hohe operative Effizienz erreichen und die Unabhängigkeit des EA gewährleisten, was für ein effizientes Echtzeit-Handelsumfeld entscheidend ist. Da wir dies bereits in einem früheren Artikel ausführlich erörtert haben, wird in diesem Artikel nicht weiter auf die Einzelheiten eingegangen. Wenn Sie Fragen zu den Codebeispielen haben, können Sie die ausführliche Einführung im vorherigen Artikel nachlesen.
Konvertierung des GPT-2-Modells in das ONNX-Modell
Im vorherigen Abschnitt habe ich die verschiedenen Herausforderungen beschrieben, die bei der Konvertierung des fein abgestimmten GPT-2-Modells in das ONNX-Format und dessen Verwendung in MQL5 aufgetreten sind. Ich glaube aber immer noch, dass dies eine Richtung ist, die es wert ist, ausprobiert zu werden. Deshalb werde ich in diesem Artikel einen zusätzlichen Abschnitt verwenden, um vorzustellen, wie man dieses personalisierte, fein abgestimmte Modell in das ONNX-Format konvertiert, in der Hoffnung, dass jeder eine Lösung für die aktuelle Misere finden kann. Wenn Sie an diesem Teil nicht interessiert sind, können Sie diesen Abschnitt auslassen.
1. Modellumwandlungsmethoden
Ⅰ. Direkte Umwandlung (https://github.com/rayhern/convert-gpt2-xl-to-onnx)
Dieses GitHub-Repository bietet ein Skript zur direkten Konvertierung von GPT-2-Modellen, das auf der Bibliothek „Transformers“ von Hugging Face und dem Exporter „torch.onnx“ basiert. Aufgrund der langjährigen mangelnden Wartung durch den Autor kann es jedoch einige Einschränkungen aufweisen und ist möglicherweise nicht mit den neuesten Versionen der „Transformers“-Bibliothek kompatibel.
- Vorteile: Bietet ein relativ einfaches Skript, das direkt verwendet werden kann; speziell für die Konvertierung von GPT-2-Modellen optimiert.
- Nachteile: Der Wartungsstatus dieses Repositorys ist möglicherweise unklar, und es ist möglicherweise nicht mit den neuesten „Transformers“-Versionen kompatibel und nur auf bestimmte Versionen von GPT-2-Modellen anwendbar.
Ⅱ. Microsofts ONNX-API (https://github.com/microsoft/onnxruntime-genai)
Microsofts Bibliothek „onnxruntime-genai“ bietet eine Reihe von ONNX-Konvertierungs- und Optimierungs-APIs für generative KI-Modelle.
- Vorteile: Optimiert für die ONNX-Laufzeit, verbessert die Inferenzleistung und wird von Microsoft unterstützt und gepflegt.
- Nachteile: Sie müssen die API der Bibliothek „onnxruntime-genai“ erlernen, die im Vergleich zu anderen Methoden komplexer sein kann.
Ⅲ. Verwendung von „torch.onnx“ zum Exportieren des Modells
PyTorch bietet eine eingebaute ONNX-Exportfunktion „torch.onnx“, die PyTorch-Modelle in das ONNX-Format exportieren kann.
- Vorteile: Eng mit dem PyTorch-Framework integriert und einfach zu nutzen, ist „torch.onnx“ ein weit verbreitetes ONNX-Exportwerkzeug.
- Nachteile: Möglicherweise müssen Sie einige Kompatibilitätsprobleme mit Operatoren lösen, insbesondere bei neueren oder nutzerdefinierten Operatoren, und einige Exportparameter manuell anpassen, um die Korrektheit und Leistung des Modells sicherzustellen.
Ⅳ. Konvertierung des Modells mit Hilfe von „transformers.onnx“
Hugging Face Bibliothek „transformers“ bietet ein eigenes ONNX-Konvertierungswerkzeug „transformers.onnx“, das Modelle aus der „transformers“-Bibliothek einfach in das ONNX-Format konvertieren kann.
- Vorteile: Es ist einfach und leicht zu bedienen, bietet eine einfache Befehlszeilenschnittstelle zur einfachen Konvertierung von Modellen, ist eng mit der „transformers“-Bibliothek integriert, unterstützt mehrere vortrainierte Modelle und wird vom Hugging Face-Team aktiv gewartet und aktualisiert.
- Nachteile: Im Vergleich zu „torch.onnx“ ist „transformers.onnx“ ein relativ neues Werkzeug und kann Kompatibilitätsprobleme haben.
Ⅴ. Optimum verwenden
Optimum ist eine von Hugging Face eingeführte Werkzeugbibliothek zur Modelloptimierung und -beschleunigung, die auch ONNX-Konvertierungsfunktionen bietet.
- Vorteile: Optimierte Integration, kann ONNX-Konvertierung mit anderen Optimierungstechniken (wie Quantisierung, Pruning) kombinieren und wird vom Team Hugging Face unterstützt und gepflegt.
- Nachteile: Sie müssen die Verwendung der Optimum-Bibliothek erlernen, was einige technische Grundlagen erfordert.
Diese Umrechnungsmethoden haben ihre eigenen Vor- und Nachteile. Sie müssen sich nicht auf die in diesem Artikel verwendete Methode beschränken, sondern können je nach Ihren Bedürfnissen eine geeignete Methode wählen. In unserem Beispiel wird die Bibliothek „transformers.onnx“ verwendet, um das GPT-2-Modell zu konvertieren.
2. Konvertierung des GPT-2-Modells in das ONNX-Modell
Nachdem wir uns für die Verwendung von „transformers.onnx für die Modellkonvertierung entschieden haben, werden wir nun einen detaillierten Konvertierungsprozess beschreiben.
Ⅰ. Abhängigkeiten installieren
Stellen Sie zunächst sicher, dass die Bibliotheken „transformers“ und „onnx“ installiert sind. Wenn sie nicht installiert sind, können Sie den folgenden Befehl verwenden, um sie zu installieren:
pip install transformers onnx
Wenn Sie für bestimmte Hardware optimieren müssen, wie z.B. die Verwendung von GPU-Beschleunigung, müssen Sie auch „onnxruntime-gpu“ installieren:
pip install onnxruntime-gpu
Ⅱ. Konvertierung der Befehl
„transformers.onnx“ bietet ein einfaches Kommandozeilenwerkzeug. Ohne besondere Anforderungen ist die Verwendung dieses Tools für die Modellkonvertierung einfach: Führen Sie einfach den folgenden Befehl aus:
python -m transformers.onnx --model=path/to/your/tuned_model --feature=causal-lm-with-past path/to/save/onnx_model
Die Parameter dieses Befehls:
- „python -m transformers.onnx“: Rufen Sie das Werkzeug „Transformers.onnx“ auf.
- „--model=pfad/zu/ihrem/tuned_model“: Geben Sie den Pfad des feinabgestimmten GPT-2-Modells an. In unserem Beispiel lautet dieser Pfad „gpt2_Adapter-tuning“.
- „--feature=causal-lm-with-past“: Geben Sie die Art der Modellfunktionalität an. Da wir ein kausales Sprachmodell verwenden und „past_key_values unterstützen müssen, um die Generierungseffizienz zu verbessern, wählen wir „causal-lm-with-past“.
- „path/to/save/onnx_model“: Geben Sie den Pfad zum Speichern des ONNX-Modells an. Wir können ihn zum Beispiel auf „gpt2_onnx“ setzen.
Vollständiges Befehlsbeispiel:
python -m transformers.onnx --model=gpt2_Adapter-tuning --feature=causal-lm-with-past gpt2_onnx
Führen Sie den obigen Befehl in der Befehlszeile aus. „transformers.onnx“ wird automatisch die erforderlichen Konfigurationsdateien herunterladen und das Modell in das ONNX-Format konvertieren. Nach Abschluss der Konvertierung sehen Sie eine Datei namens „model.onnx“ im angegebenen Ausgabeverzeichnis (in diesem Fall „gpt2_onnx“), zusammen mit einigen möglichen JSON-Dateien wie „config.json“.
Wenn Sie jedoch während der Modellkonvertierung einige Einstellungen anpassen müssen, um dem aktuellen Anwendungsfall besser gerecht zu werden, kann dieses Tool unsere Anforderungen eindeutig nicht erfüllen. Für komplexe Anwendungsszenarien ist es daher nach wie vor notwendig, geeignete Skripte für die Konvertierung zu schreiben, um eine genauere Kontrolle über die exportierte Modellform zu haben.
Ⅲ. Konvertierungs-Skript
Um das mit Adapter-Tuning feinabgestimmte GPT-2-Modell zu konvertieren, muss der Konvertierungsprozess das Adapter-Modul und die Einstellungen laden und auch die ONNX OP-Version einstellen, um Kompatibilitätsprobleme zu vermeiden. Anschließend werden wir die entsprechenden Funktionen Schritt für Schritt nach unseren Bedürfnissen implementieren.
Zunächst importieren wir die erforderlichen Python-Bibliotheken sowie die Klassen Adapter() und GPT2LMHeadModelWithAdapters(). Diese Klassen wurden im vorherigen Artikel (Integrieren Sie Ihr eigenes LLM in einen EA (Teil 5): Handelsstrategie mit LLMs entwickeln und testen (III) – Adapter-Tuning) ausführlich vorgestellt. Sie können direkt aus dem vorhandenen Skript importieren, und hier kopieren wir diese Klassen zum besseren Verständnis in das Konvertierungsskript:
import os import logging from pathlib import Path from transformers.onnx import export, FeaturesManager from transformers import AutoConfig, AutoTokenizer, GPT2LMHeadModel, modeling_outputs from torch import nn import torch.nn.functional as F import onnx # Set up basic configuration for logging logging.basicConfig(level=logging.INFO) tokenizer = AutoTokenizer.from_pretrained('gpt2') # Define the Adapter class, which is a simple feed-forward network with dropout class Adapter(nn.Module): def __init__(self, in_features, bottleneck_features=64): super(Adapter, self).__init__() # Down projection layer self.down_project = nn.Linear(in_features, bottleneck_features) # Up projection layer self.up_project = nn.Linear(bottleneck_features, in_features) # Dropout layer for regularization self.dropout = nn.Dropout(0.1) # Initialize weights of the layers self.init_weights() def init_weights(self): # Initialize weights for down projection layer nn.init.normal_(self.down_project.weight, mean=0.0, std=0.02) nn.init.constant_(self.down_project.bias, 0) # Initialize weights for up projection layer nn.init.normal_(self.up_project.weight, mean=0.0, std=0.02) nn.init.constant_(self.up_project.bias, 0) def forward(self, hidden_states): # Apply down projection and ReLU activation hidden_states = self.down_project(hidden_states) hidden_states = F.relu(hidden_states) # Apply dropout hidden_states = self.dropout(hidden_states) # Apply up projection hidden_states = self.up_project(hidden_states) # Apply dropout again hidden_states = self.dropout(hidden_states) return hidden_states # Define the GPT2LMHeadModelWithAdapters class, which inherits from GPT2LMHeadModel # and adds adapter layers to each transformer layer class GPT2LMHeadModelWithAdapters(GPT2LMHeadModel): def __init__(self, config): super().__init__(config) # Create a list of adapter modules, one for each transformer layer self.adapters = nn.ModuleList([Adapter(config.n_embd) for _ in range(config.n_layer)]) def forward( self, input_ids=None, past_key_values=None, attention_mask=None, token_type_ids=None, position_ids=None, head_mask=None, inputs_embeds=None, encoder_hidden_states=None, encoder_attention_mask=None, labels=None, use_cache=None, output_attentions=None, output_hidden_states=None, return_dict=None, ): # Get the outputs from the transformer transformer_outputs = self.transformer( input_ids, past_key_values=past_key_values, attention_mask=attention_mask, token_type_ids=token_type_ids, position_ids=position_ids, head_mask=head_mask, inputs_embeds=inputs_embeds, encoder_hidden_states=encoder_hidden_states, encoder_attention_mask=encoder_attention_mask, use_cache=use_cache, output_attentions=output_attentions, output_hidden_states=output_hidden_states, return_dict=return_dict, ) hidden_states = transformer_outputs[0] # Apply each adapter to the hidden states for i, adapter in enumerate(self.adapters): hidden_states = hidden_states + adapter(hidden_states) # Get the logits for the language modeling head lm_logits = self.lm_head(hidden_states) # Compute loss if labels are provided loss = None if labels is not None: # Shift logits and labels for loss computation shift_logits = lm_logits[..., :-1, :].contiguous() shift_labels = labels[..., 1:].contiguous() # Flatten the logits and labels for cross-entropy loss loss_fct = nn.CrossEntropyLoss() loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) # Return the outputs in the appropriate format if not return_dict: output = (lm_logits,) + transformer_outputs[1:] return ((loss,) + output) if loss is not None else output return modeling_outputs.CausalLMOutputWithCrossAttentions( loss=loss, logits=lm_logits, past_key_values=transformer_outputs.past_key_values, hidden_states=transformer_outputs.hidden_states, attentions=transformer_outputs.attentions, cross_attentions=transformer_outputs.cross_attentions, )
Als Nächstes müssen wir das feinabgestimmte GPT-2-Modell laden und den Modellumwandlungsprozess steuern. Wir verwenden die Funktion „load_model_and_tokenizer()“, um das fein abgestimmte GPT-2-Modell zu laden, die Funktion „export_model_to_onnx()“, um das Modell in das ONNX-Format zu konvertieren, und die Funktion „main()“, um den gesamten Prozess und die Eingabe-/Ausgabepfade zu steuern. Schließlich definieren wir eine Funktion „check_onnx()“ zur Überprüfung der Exportergebnisse und eine Funktion „quantization()“ zur Quantisierung. Hier ist ein Beispiel:
# Function to load the model and tokenizer def load_model_and_tokenizer(model_id): try: # Load the model configuration config = AutoConfig.from_pretrained(model_id) # Load the model model = GPT2LMHeadModelWithAdapters.from_pretrained(model_id) # Load the tokenizer # tokenizer = AutoTokenizer.from_pretrained('gpt2') return config, model,tokenizer except Exception as e: # Log any errors that occur during loading logging.error(f"Error loading model and tokenizer: {e}") raise # Function to export the model to ONNX format def export_model_to_onnx(model, config, tokenizer, output_path, opset): try: # Get the appropriate feature for the model model_type = config.model_type.replace("-", "_") feature = "causal-lm-with-past" # Get the ONNX configuration onnx_config_constructor = FeaturesManager.get_config(model_type, feature=feature) onnx_config = onnx_config_constructor(config) # Create the output directory if it doesn't exist if not os.path.exists(output_path.parent): os.makedirs(output_path.parent) # Export the model to ONNX export( model=model, config=onnx_config, opset=opset, output=output_path, preprocessor=tokenizer, ) # Log success message logging.info(f"Model successfully converted to ONNX and saved in {output_path}") except Exception as e: # Log any errors that occur during export logging.error(f"Error exporting model to ONNX: {e}") raise # Main function to orchestrate the process def main(): # Define the model ID, output path, and ONNX opset version model_id = "gpt2_Adapter-tuning" onnx_path = "./gpt2_onnx" out_path = Path(os.path.join(onnx_path, "gpt2_adapter_tuning.onnx")) opset = 14 # Load the model and tokenizer config, model, tokenizer = load_model_and_tokenizer(model_id) # Export the model to ONNX export_model_to_onnx(model, config, tokenizer, out_path, opset) def check_onnx(): # Check the ONNX model onnx_model = onnx.load("gpt2_onnx/gpt2_adapter_tuning.onnx") onnx.checker.check_model(onnx_model) print("ONNX model check passed!") def quantization(): from onnxruntime.quantization import quantize_dynamic, QuantType # load model model_path = "gpt2_onnx/gpt2_adapter_tuning.onnx" onnx_model = onnx.load(model_path) #dynamic quantize INT4 quantized_model_path = "gpt2_onnx/quantized_gpt2.onnx" quantize_dynamic(model_path, quantized_model_path, weight_type=QuantType.QUInt4) print(f"Save the quantized model to: {quantized_model_path}")
Die Implementierung dieses Teils des Codes bereitet keine Schwierigkeiten, und es gibt ausführliche Kommentare im Code, sodass wir nicht im Detail darauf eingehen werden. Wir werden nur die wichtigsten Teile des Codes besprechen:
- Es muss die Modellklasse mit dem Adaptermodul verwendet werden, um das feinabgestimmte Modell zu laden:
model = GPT2LMHeadModelWithAdapters.from_pretrained(model_id)
- Dateipfade müssen in das Pfadformat konvertiert werden, das von „transformers.onnx.export()“ unterstützt wird, und können nicht direkt als Stringpfade verwendet werden. Wir verwenden die Klasse „Path“ aus der Bibliothek „pathlib“ zur Konvertierung:
out_path = Path(os.path.join(onnx_path, "gpt2_adapter_tuning.onnx"))
- Die Parameter „tokenizer“ und „preprocessor“ der Funktion „export()“ können nur einen setzen. Andernfalls wird ein Fehler gemeldet. Es wird empfohlen, „preprocessor“ zu verwenden:
export(model=model, config=onnx_config, opset=opset, output=output_path, preprocessor=tokenizer)
- Wir müssen die Opset-Version bestimmen, die mit der von MQL5 unterstützten Opset-Version übereinstimmen muss, um korrekt geladen zu werden. Wir wählen opset=14:
opset = 14
- Der Eingabepfad des Modells (d.h. der Ordner, der das mit Adapter-Tuning feinabgestimmte GPT-2-Modell enthält) wird auf den Ordner „gpt2_Adapter-tuning“ unter dem aktuellen Projektpfad gesetzt, und der Ausgabepfad wird auf den Ordner „gpt2_onnx“ unter dem aktuellen Projektpfad gesetzt:
model_id = "gpt2_Adapter-tuning" onnx_path = "./gpt2_onnx"
- Die Funktionen „check_onnx()“ und „quantization()“ sind nicht obligatorisch und werden nur als Referenz angegeben.
Natürlich ist dies nur ein einfaches Beispiel für ein Konvertierungsskript. Wir haben keine weiteren Details festgelegt, wie z. B. die Unterstützung dynamischer Eingaben für Sequenzen. Wenn Sie die entsprechende Funktionsweise benötigen, fügen Sie bitte die entsprechenden Funktionen auf der Grundlage des Beispielskripts hinzu.
Das vollständige Konvertierungsskript ist ebenfalls im Anhang enthalten und trägt den Namen „torch2onnx.py“.
Formulierung der EA-Strategie und Server-Funktionalität
Wir haben die Arbeitsweise des EA bestimmt. Als Nächstes müssen wir einen Plan festlegen, um zu bestimmen, welche Dienste der Server bereitstellt und welche Funktionen der Client integriert: Der Client-EA ist hauptsächlich für die Datenerfassung und die Durchführung von Transaktionen zuständig; der Python-Server empfängt die vom Client gesendeten Daten, berechnet die Inferenzergebnisse und sendet die Ergebnisse zurück an den Client; der Client-EA und der Python-Server kommunizieren über Socket.
1. EA-Strategie
Als Nächstes werden wir eine Handelsstrategie entwickeln, die auf den Ergebnissen der GPT-2-Prognose basiert. Da der Schwerpunkt dieses Artikels auf der Demonstration der Integration des GPT-2-Modells in den MQL5 EA liegt, werden wir als Beispiel eine einfache Handelsstrategie erstellen. Es sollte betont werden, dass es sich hierbei um eine einfache Beispielstrategie handelt, die nur zu Demonstrationszwecken dient und keine tatsächliche Handelsberatung darstellt. In der Praxis müssen umfassendere und robustere Handelsstrategien entwickelt und gründliche Backtests und Risikobewertungen durchgeführt werden.
EA-Strategie-Logik:
- Abrufen der Schlusskurse der letzten 20 Zeitpunkte für jede 1-Minute.
- Übertragen der Daten an den Server und warten darauf, dass der Server die Berechnungsergebnisse zurücksendet.
- Senden der Handelsaufträge auf der Grundlage der vom Server zurückgesendeten Handelssignale, ohne Stop Loss oder Take Profit zu setzen, und es soll immer nur eine offene Position geben.
2. Server Funktionsdesign
Auf der Serverseite müssen wir die Hauptfunktionen implementieren, die darin bestehen, Daten vom Client-EA zu empfangen, Modellinferenzen durchzuführen, um Ergebnisse zu erhalten, und die Handelssignale zu berechnen, die auf der Grundlage der Inferenzergebnisse an den Client zurückgesendet werden.
Server-seitige Funktionen:
- Empfangen von Daten vom Client.
- Laden des GPT-2-Modell und des Tokenizers, und jederzeit das Modell bereithalten.
- Ausführen der Inferenz und Berechnung der Differenz zwischen dem aktuellen, tatsächlichen Preis und dem Mittelwert des vorhergesagten Preises auf der Grundlage der Inferenzergebnisse. Wenn die Differenz größer als 0 ist, wird ein Kaufsignal gesendet; wenn sie kleiner als 0 ist, wird ein Verkaufssignal gesendet; wenn sie gleich 0 ist, wird kein Signal gesendet.
- Prüfen und entscheiden, ob CPU oder GPU für die Modellinferenz verwendet werden soll (je nach dem vom aktuellen Gerät unterstützten Modus).
Als Nächstes werden wir die entsprechende Funktionsweisen implementieren.
Erstellen des Inferenzdienstes
Wie man den Inferenzdienst erstellt, habe ich in einem früheren Artikel („Datenkennzeichnung für Zeitreihenanalyse (Teil 5):Anwendung und Test in einem EA, der Socket verwendet“ ausführlich beschrieben, wo das hier verwendete Skript „server.py“ zur Verfügung gestellt wird). Der Code-Teil folgt immer noch der Hauptlogik des Skripts „server.py“aus dem vorigen Artikel, nur dass wir es an unser fein abgestimmtes GPT-2-Modell anpassen und einige andere Optimierungen und Verbesserungen vornehmen.
Der geänderte Code enthält im Wesentlichen die folgenden Änderungen:
- Anpassung der Modellinferenz an das GPT-2-Modell, mit wesentlichen Änderungen in der Funktion „eva()“.
- Optimierung der Socket-Handshake-Logik durch Hinzufügen der Möglichkeit, den Client wieder zu verbinden, ohne den Server nach der Trennung neu zu starten, was für Backtests bequemer ist und keinen Neustart des Servers nach den Backtests erfordert.
- Hinzufügen der Erkennung des Client-Verbindungsstatus, um unnötige Ressourcenverschwendung zu vermeiden.
- Vermeiden Sie redundantes Ausdrucken von Ergebnissen, drucken Sie nur Ergebnisse, wenn sich die Vorhersageergebnisse ändern.
- Hinzufügen einer Fehlerbehandlungslogik zur Vermeidung von Serverabstürzen.
- Optimieren Sie die gesamte Codelogik.
Was den Code betrifft, so wird in diesem Artikel nicht weiter auf die Einzelheiten eingegangen, sondern nur auf die Teile, die geändert werden müssen.
1. Erforderliche Bibliotheken importieren
Zusätzlich zum Import der normalen benötigten Bibliotheken müssen wir auch die Klassen Adapter und GPT2LMHeadModelWithAdapters importieren, die wir in das Skript eingebaut haben. Sie können diese Klassen aus meinem früheren Artikel über die Feinabstimmung von GPT-2 beziehen oder sie direkt aus der in diesem Artikel bereitgestellten „torch2onnx.py“ importieren. Der Beispielcode importiert die beiden Klassen direkt aus „torch2onnx.py“.
import socket from time import sleep import pandas as pd import numpy as np import warnings import base64 import hashlib import struct from torch2onnx import GPT2LMHeadModelWithAdapters,Adapter from transformers import AutoTokenizer import logging import torch from statistics import mean # Set logging and warning logging.basicConfig(level=logging.INFO) warnings.filterwarnings("ignore") # Set device dvc='cuda' if torch.cuda.is_available() else 'cpu' # Global model_id = "gpt2_Adapter-tuning" encoder_length=20 prediction_length=10 info_file="results.json" host="0.0.0.0" port=10055
2. Hinzufügen einer Logik zum Laden von GPT-2-Modellen in der Funktion „load_model()“.
Im Originalskript „server.py“ wird die Funktion „load_model()“ zum Laden des Modells verwendet. Beachten Sie, dass wir hier die Ladelogik für das GPT-2-Modell sowie die Ladelogik für den GPT-2-Tokenizer hinzufügen müssen.
# Function to loda model def load_model(): try: # Load the model model = GPT2LMHeadModelWithAdapters.from_pretrained(model_id).to(dvc) # Load the tokenizer tokenizer = AutoTokenizer.from_pretrained('gpt2') print("Model loaded!") return model,tokenizer except Exception as e: # Log any errors that occur during loading logging.error(f"Error loading model and tokenizer: {e}") raise
3. Hinzufügung der GPT-2-Modell-Inferenzlogik in der Funktion „eva()“.
def eva(msg,model,tokenizer): # Get the data msg=np.fromstring(msg, dtype=float, sep= ',').tolist() # Parse the data input_data=msg[-encoder_length:] # Create the prompt prompt = ' '.join(map(str, input_data)) # Generate the predication token=tokenizer.encode(prompt, return_tensors='pt').to(dvc) attention_mask = torch.ones_like(token).to(dvc) model.eval() generated = tokenizer.decode( model.generate( token, attention_mask=attention_mask, pad_token_id=tokenizer.eos_token_id, do_sample=True, max_length=200)[0], skip_special_tokens=True) generated_prices=generated.split('\n')[0] # Remove non-numeric formats def try_float(s): try: return float(s) except ValueError: return None generated_prices=generated_prices.split() generated_prices=list(map(try_float,generated_prices)) generated_prices = [f for f in generated_prices if f is not None] generated_prices=generated_prices[0:prediction_length] # Calculate and send the results last_price=input_data[-1] prediction_mean=mean(generated_prices) if (last_price-prediction_mean) >= 0: # print('Send sell.') return "sell" else: # print("Send buy.") return "buy"
Beachten Sie, dass die Eingabelänge mit dem Datenformat übereinstimmen muss, das beim Training des GPT-2-Modells mit Adapter-Tuning verwendet wird:
- input_data = msg[-encoder_length:]: Verwenden der letzten 20 vom Client gesendeten Datenpunkte als Modelleingabe.
- prompt = ' '.join(map(str, input_data)): Konvertierung der Daten in das String-Format und Umwandlung in eine Eingabeaufforderung.
- token = tokenizer.encode(prompt, return_tensors='pt').to(dvc): Verwenden des Tokenizer vom vorab trainierten GPT-2-Modell, um die Eingabeaufforderung zu kodieren und auf das aktuell unterstützte Gerät zu übertragen (das dem für die Modellinferenz verwendeten Gerät entspricht).
- attention_mask = torch.ones_like(token).to(dvc): Definieren der Aufmerksamkeitsmaske für die Modellinferenz.
- model.generate(token, attention_mask=attention_mask, pad_token_id=tokenizer.eos_token_id, do_sample=True, max_length=200)[0]: Durchführen der Modellinferenz.
- generated = tokenizer.decode(model.generate(...), skip_special_tokens=True): Dekodieren der Vorhersageergebnisse und Überspringen spezieller Token.
- generated_prices = generated.split('\n')[0]: Aufteilung der dekodierten Inferenzergebnisse.
- try_float(s) : Diese Funktion wird verwendet, um festzustellen, ob es Elemente in den Schlussfolgerungsergebnissen gibt, die nicht in das Float-Format umgewandelt werden können.
- generated_prices = generated_prices.split(): Trennen der Vorhersageergebnisse durch Leerzeichen und entfernen der Trennzeichen, die nicht in Zahlen umgewandelt werden können.
- generated_prices = list(map(try_float, generated_prices)): Alle Elemente in generated_prices in Zahlen im Float-Format umwandeln; wenn es Elemente gibt, die nicht in Zahlen umgewandelt werden können, die Funktion try_float(s) verwenden, um sie auf None zu setzen.
- generated_prices = [f for f in generated_prices if f is not None]: Durchlaufen aller Elemente in generated_prices und Entfernen von Elementen, die keine sind.
- generated_prices = generated_prices[0:prediction_length]: Nur die ersten 10 vorhergesagten Werte als Referenz erhalten.
- if (last_price - prediction_mean) >= 0: Berechnen der Differenz zwischen den letzten vom Client gesendeten Daten und dem Mittelwert der vorhergesagten Werte. Wenn größer oder gleich 0, wird ein Verkaufssignal gesendet; wenn kleiner als 0, wird ein Kaufsignal gesendet.
Wir haben uns für die Verwendung der Bibliothek „transformers“ für die Inferenz entschieden. Sie können auch das bereits erwähnte Skript „torch2onnx.py“ verwenden, um das Modell in das ONNX-Format zu konvertieren und die Bibliothek „onnxruntime zur Inferenz zu verwenden. Auf diese Methode wird in diesem Artikel nicht eingegangen.
4. Server
Die gesamte Funktionsweise des Servers ist in die Klasse „server_()“ integriert, und die Änderungen am Gesamtcode sind nicht signifikant. Wir werden ihn hier nicht im Detail interpretieren und nur auf die geänderten Teile eingehen.
class server_: def __init__(self, host = host, port = port): self.sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM,) self.host = host self.port = port self.sk.bind((self.host, self.port)) self.re = '' self.model,self.tokenizer=load_model() self.stop=None self.sk.listen(1) self.sk_, self.ad_ = self.sk.accept() self.last_action=None print('server running:',self.sk_, self.ad_) def msg(self): self.re = '' wsk=False while True: sleep(0.5) if self.is_connected(): try: data = self.sk_.recv(2500) except Exception as e: break if not data: break if (data[1] & 0x80) >> 7: fin = (data[0] & 0x80) >> 7 # FIN bit opcode = data[0] & 0x0f # opcode masked = (data[1] & 0x80) >> 7 # mask bit mask = data[4:8] # masking key payload = data[8:] # payload data # print('fin is:{},opcode is:{},mask:{}'.format(fin,opcode,masked)) message = "" for i in range(len(payload)): message += chr(payload[i] ^ mask[i % 4]) data=message wsk=True else: data=data.decode("utf-8") if '\r\n\r\n' in data: self.handshake(data) data=data.split('\r\n\r\n',1)[1] if "stop" in data: self.stop=True break if len(data)<50: break self.re+=data bt=eva(self.re, self.model,self.tokenizer) bt=bytes(bt, "utf-8") # If the signal changes,then print the information if bt != self.last_action: if bt == b'buy': print('Send buy.') elif bt == b'sell': print('Send sell.') self.last_action = bt if wsk: tk=b'\x81' lgt=len(bt) tk+=struct.pack('B',lgt) bt=tk+bt self.sk_.sendall(bt) else: print("Disconnected!Try to connect the client...") try: # reconnect self.sk_.close() self.sk.listen(1) self.sk_, self.ad_ = self.sk.accept() print('Reconnected:', self.sk_, self.ad_) # handshake while True: sleep(0.5) data = self.sk_.recv(2500) data=data.decode("utf-8") if '\r\n\r\n' in data: self.handshake(data) break print("Reconnection succeed!") # # clean the socket # while True: # if not self.sk_.recv(2500): # break except Exception as e: print(f"Reconnection failed: {e}") return self.re def __del__(self): print("server closed!") self.sk.close() if self.sk_ is not None: self.sk_.close() self.ad_.close() def handshake(self,data): try: # Handshake key = data.split("\r\n")[4].split(": ")[1] GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ac = base64.b64encode(hashlib.sha1((key+GUID).encode('utf-8')).digest()) response_tpl="HTTP/1.1 101 Switching Protocols\r\n" \ "Upgrade:websocket\r\n" \ "Connection: Upgrade\r\n" \ "Sec-WebSocket-Accept: %s\r\n" \ "WebSocket-Location: ws://%s/\r\n\r\n" response_str = response_tpl % (ac.decode('utf-8'), "127.0.0.1:10055") self.sk_.send(bytes(response_str, encoding='utf-8')) print('Handshake succeed!') except Exception as e: print(f"Connection failed: {e}") return None def is_connected(self): try: # Check remote # remote_addr = self.sk_.getpeername() data = self.sk_.recv(1, socket.MSG_PEEK) return True except socket.error: self.last_action=None return False
- Hinzufügen der Klassenfunktion „is_connected(self)“, um festzustellen, ob der Client online ist.
- Hinzufügen der Klassenfunktion „handshake(self, data)“, um die Handshake-Logik zu integrieren und zu vermeiden, dass die Haupt-Parsing-Logik überladen wird.
- Hinzufügen des Klassenmitglieds „self.last_action“, um zu erkennen, ob sich das Handelssignal geändert hat. Drucken Sie die Ergebnisse nur, wenn sich das Handelssignal ändert, um häufiges Drucken zu vermeiden. Wenn der Client die Verbindung trennt, setzen Sie sie auf „None“ zurück, um zu vermeiden, dass falsche Signale gesendet werden, wenn der Client die Verbindung wieder aufnimmt.
Anmerkung: Unsere Host-Adresse ist auf „0.0.0.0“ eingestellt, denn wenn sie auf „127.0.0.1“ eingestellt ist, können entfernte Clients, die auf einem anderen Host laufen, keine Verbindung herstellen. Das bedeutet, dass durch die Einstellung auf „0.0.0.0“ auch dann eine Verbindung hergestellt werden kann, wenn sich Server und Client nicht auf demselben Host befinden (der Client-EA muss die richtige Host-IP-Adresse einstellen).
Der gesamte Code befindet sich in der beigefügten Datei „server.py“. Wenn der Server läuft, zeigt das Terminal die entsprechenden Laufinformationen an.
Der Client-EA
Der Client folgt im Wesentlichen der Logik aus einem früheren Artikel (der in einem früheren Artikel ausdrücklich erwähnt wurde), mit entsprechenden Änderungen in der Logik. Es gibt weiterhin zwei Socket-Kompatibilitätsmethoden (eine mit Winapi zur Implementierung von WebSocket, die andere mit dem eingebauten Socket-Modul in MQL5), um Signalunterbrechungen zu vermeiden, die dadurch entstehen, dass das in MQL5 eingebaute Socket unter bestimmten Umständen keine Verbindung herstellen kann. Die Hauptbetriebslogik besteht darin, den Socket in der Funktion „OnInit()“ zu initialisieren, die Handelslogik in der Funktion „OnTick()“zu behandeln und das Senden von Daten an den Server und das Empfangen von Schlussfolgerungsergebnissen zu jeder festgelegten Zeit in der Funktion „OnTimer()“ zu behandeln.
1. Definition der Konstanten
#include <WinAPI\winhttp.mqh> int sk=-1; string host="127.0.0.1"; int port= 10055; int data_len=100; string pre=NULL; HINTERNET ses_h,cnt_h,re_h,ws_h;
- „sk“: Socket-Handle.
- „host“ und „port“: Adresse und Port des Servers, mit dem eine Verbindung hergestellt werden soll.
- „data_len“: Anzahl der zu sendenden Preisdatenpunkte.
- „pre“: Zeichenkette zum Speichern der Vorhersageergebnisse.
- „ses_h“, „cnt_h“, „re_h“, „ws_h“: Sitzungshandle, Verbindungshandle, Anfragehandle und WebSocket-Handle für WinHttp.
2. Initialisierung des Socket
int OnInit() { //--- create timer EventSetTimer(60); ses_h=cnt_h=re_h=ws_h=NULL; //handshake ses_h=WinHttpOpen("MT5", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0); //Print(ses_h); if (ses_h==NULL){ Print("Http open failed!",string(kernel32::GetLastError())); return INIT_FAILED; } cnt_h=WinHttpConnect(ses_h, host, port, 0); //Print(cnt_h); if (cnt_h==NULL){ Print("Http connect failed!",string(kernel32::GetLastError())); return INIT_FAILED; } re_h=WinHttpOpenRequest(cnt_h, "GET", NULL, NULL, NULL, NULL, 0); if(re_h==NULL){ Print("Request open failed!",string(kernel32::GetLastError())); return INIT_FAILED; } uchar nullpointer[]= {}; if(!WinHttpSetOption(re_h,WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET,nullpointer,0)) { Print("Set web socket failed!",string(kernel32::GetLastError())); return INIT_FAILED; } bool br; br = WinHttpSendRequest( re_h, NULL, 0, nullpointer, 0, 0, 0); if (!br) { Print("send request failed!",string(kernel32::GetLastError())); return INIT_FAILED; } br=WinHttpReceiveResponse(re_h,nullpointer); if (!br) { Print("receive response failed!",string(kernel32::GetLastError())); return INIT_FAILED; } ulong nv=0; ws_h=WinHttpWebSocketCompleteUpgrade(re_h,nv); if (!ws_h) { Print("Web socket upgrade failed!",string(kernel32::GetLastError())); return INIT_FAILED; } else{ Print("Web socket connected!"); } WinHttpCloseHandle(re_h); re_h=NULL; sk=SocketCreate(); Print(sk); Print(GetLastError()); if (sk==INVALID_HANDLE) { Print("Failed to create socket"); //return INIT_FAILED; } if (!SocketConnect(sk,host, port,1000)) { Print("Failed to connect to built-in socket"); //return INIT_FAILED; } //--- return(INIT_SUCCEEDED); }
Im Initialisierungsteil implementieren wir hauptsächlich die Initialisierung von Winapi WebSocket und dem eingebauten Socket in MQL5. Dieser Teil hat sich im Vergleich zum vorherigen Artikel nicht wesentlich verändert und wird daher in diesem Artikel nicht weiter behandelt.
3. Handelsstrategie
void OnTick() { //--- MqlTradeRequest request; MqlTradeResult result; //int x=SymbolInfoInteger(_Symbol,SYMBOL_FILLING_MODE); if (pre!=NULL) { //Print("The predicted value is:",pre); ulong numt=0; ulong tik=0; bool sod=false; ulong tpt=-1; ZeroMemory(request); numt=PositionsTotal(); //Print("All tickets: ",numt); if (numt>0) { tik=PositionGetTicket(numt-1); sod=PositionSelectByTicket(tik); tpt=PositionGetInteger(POSITION_TYPE);//ORDER_TYPE_BUY or ORDER_TYPE_SELL if (tik==0 || sod==false || tpt==0) return; } if (pre=="buy") { if (tpt==POSITION_TYPE_BUY) return; request.action=TRADE_ACTION_DEAL; request.symbol=Symbol(); request.volume=0.1; request.deviation=5; request.type_filling=ORDER_FILLING_IOC; request.type = ORDER_TYPE_BUY; request.price = SymbolInfoDouble(Symbol(), SYMBOL_ASK); if(tpt==POSITION_TYPE_SELL) { request.position=tik; Print("Close sell order."); } else{ Print("Open buy order."); } OrderSend(request, result); } else{ if (tpt==POSITION_TYPE_SELL) return; request.action = TRADE_ACTION_DEAL; request.symbol = Symbol(); request.volume = 0.1; request.type = ORDER_TYPE_SELL; request.price = SymbolInfoDouble(Symbol(), SYMBOL_BID); request.deviation = 5; //request.type_filling=SymbolInfoInteger(_Symbol,SYMBOL_FILLING_MODE); request.type_filling=ORDER_FILLING_IOC; if(tpt==POSITION_TYPE_BUY) { request.position=tik; Print("Close buy order."); } else{ Print("OPen sell order."); } OrderSend(request, result); } //is_pre=false; } pre=NULL; }
Wir integrieren die gesamte Handelsstrategie in die Funktion „OnTick()“, um die Logik zu verdeutlichen. Wenn die Funktion OnTick() ausgeführt wird, ist zu prüfen, ob die globale Variable „pre“ leer ist. Wenn sie nicht leer ist, bedeutet dies, dass vom Client Vorhersageergebnisse gesendet wurden.
Dann senden wir auf der Grundlage der Vorhersageergebnisse Handelsaufträge („buy“ oder „sell“):
- Bei der Auswahl von „buy“, eröffnen wir eine Position, wenn noch keine Position vorhanden ist, oder schließen einen bestehenden Verkaufsauftrag.
- Bei einem „sell“, eröffnen wir eine Position, wenn keine Position vorhanden ist, oder schließen wir einen bestehenden Kaufauftrag.
Wir haben immer nur einen Auftrag offen, ohne Take Profit oder Stop Loss zu setzen, und kontrollieren die Position nur durch Handelssignale.
4. Interaktion mit dem Server
void OnTimer() { //--- MqlTradeRequest request; MqlTradeResult result; char recv_data[5]; double priceData[100]; string dataToSend; char ds[]; int nc=CopyClose(Symbol(),0,0,data_len,priceData); for(int i=0;i<ArraySize(priceData);i++) dataToSend+=(string)priceData[i]+","; int dsl=StringToCharArray(dataToSend,ds); if (sk!=-1) { if (SocketIsWritable(sk)) { Print("Send data:",dsl); int ssl=SocketSend(sk,ds,dsl); } uint len=SocketIsReadable(sk); if (len) { int rsp_len=SocketRead(sk,recv_data,len,500); if(rsp_len>0) { string result=NULL; result+=CharArrayToString(recv_data,0,rsp_len); Print("The predicted value is:",result); if (StringFind(result,"buy")) { pre="buy"; } if (StringFind(result,"sell")){ pre="sell"; } } } } else { ulong send=0; if (ws_h) { send=WinHttpWebSocketSend(ws_h, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, ds, dsl); //Print("Send data failed!",string(kernel32::GetLastError())); if(!send) { ZeroMemory(recv_data); ulong rb=0; WINHTTP_WEB_SOCKET_BUFFER_TYPE st=-1; ulong get=WinHttpWebSocketReceive(ws_h,recv_data,ArraySize(recv_data),rb,st); if (!get) { pre=NULL; pre+=CharArrayToString(recv_data,0); Print("The predicted value is:",pre); } } } } }
Die Hauptfunktion des Servers besteht darin, 100 Datenpunkte aus dem aktuellen Chart zu sammeln, sie an den Server zu senden, die Ergebnisse vom Server zu erhalten und dann die globale Variable auf der Grundlage der Ergebnisse zu ändern, um sicherzustellen, dass die Handelsstrategie entsprechend den vom Server gelieferten Ergebnissen ausgeführt wird. Hier verwenden wir zwei Socket-Verbindungsmethoden, um die Dateninteraktionslogik zu implementieren, und wählen automatisch die geeignete Methode auf der Grundlage des aktuell verbundenen Socket-Typs aus.
5. Freigabe von Ressourcen
void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); uchar stop[]; int ls=StringToCharArray("stop",stop); SocketSend(sk,stop,ls); SocketClose(sk); // close the websocket WinHttpSendRequest(re_h,NULL,0,stop,0,0,0); BYTE closearray[]= {}; ulong close=WinHttpWebSocketClose(ws_h, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, closearray, 0); if(close) { Print("websocket close error "+string(kernel32::GetLastError())); if(re_h!=NULL) WinHttpCloseHandle(re_h); if(ws_h!=NULL) WinHttpCloseHandle(ws_h); if(cnt_h!=NULL) WinHttpCloseHandle(cnt_h); if(ses_h!=NULL) WinHttpCloseHandle(ses_h); } }
In der Funktion „OnDeinit()“ werden die entsprechenden Systemressourcen freigegeben und die Wiederherstellung der Ressourcen durchgeführt.
Da der GPT-2-Modellinferenzprozess nicht im EA implementiert ist, macht dies unsere EA-Logik viel einfacher und prägnanter. Beachten Sie, dass wir dem EA keine Logik zur Risikokontrolle hinzugefügt haben und dass es sehr riskant ist, sich ausschließlich auf die GPT-2-Inferenzergebnisse zu verlassen, um zu entscheiden, ob eine Position gehalten oder eröffnet werden soll. Bitte beachten Sie nochmals, dass dieses EA-Beispiel nicht für den realen Handel verwendet werden sollte!
Der vollständige Code ist im Anhang des Artikels unter dem Namen „gpt2_EA.mql5“ zu finden.
Backtests
Um die Leistung des EAs zu bewerten, können wir ein Backtest im Strategietester des MetaTrader 5-Clients durchführen. Wir wählen den entsprechenden Bereich historischer Daten aus, legen die Backtest-Parameter fest und führen dann den Backtest durch (da unser gpt2-Modell auf dem Währungspaar NZDUSD trainiert wurde, können wir für den Backtest nur das Währungspaar NZDUSD auswählen).
Der Backtest läuft:
Nach Abschluss des Backtests sind die Ergebnisse wie folgt:
Sie können die Rentabilität des EA, den maximalen Drawdown, die Gewinnrate und andere Metriken analysieren, indem Sie den Backtest-Bericht überprüfen. Bedenken Sie, dass unsere Handelsstrategie einfach ist und die Backtest-Ergebnisse daher nicht ideal sind. Dies liegt vor allem daran, dass unsere Strategie keiner Parameteroptimierung oder Risikokontrolle unterzogen wurde und der Trainingsprozess und die Datenaufbereitung für das Modell ein erhebliches Optimierungspotenzial aufweisen. Alles in allem erfordert dies eine Menge Geduld. Es ist wichtig zu beachten, dass aufgrund von Änderungen der Marktbedingungen und der Einschränkungen des Modells die Backtest-Ergebnisse keine Garantie für die Leistung des EA im zukünftigen Live-Handel sind und die Einschränkungen des Modells auch zu instabilen Vorhersageergebnissen führen können.
Schlussfolgerung
In diesem Artikel haben wir gezeigt, wie man ein mit spezifischen Finanzdaten (des Währungspaares NZDUSD) feinabgestimmtes GPT-2-Modell in ein EA-Programm integriert. Dabei haben wir den gesamten Prozess von der Feinabstimmung des Modells und der Implementierung der Inferenzlogik über die Einrichtung von Server und Client bis hin zur Integration von Handelsstrategien systematisch erläutert.
Es ist wichtig zu betonen, dass die von uns entwickelte Handelsstrategie relativ einfach ist und nur zu Demonstrationszwecken dient. In der Praxis müssen umfassendere und robustere Strategien entwickelt werden, z. B. die Kombination mehrerer technischer Indikatoren, die Berücksichtigung der Marktstimmung, die Festlegung von Stop Loss und Take Profit usw.
Darüber hinaus bergen der Trainingsprozess und die Datenaufbereitung für das Modell ein erhebliches Optimierungspotenzial, und Änderungen der Marktbedingungen und Modellbeschränkungen können zu instabilen Vorhersageergebnissen führen. Dennoch liegt die Bedeutung dieser Arbeit darin, dass sie das Potenzial großer Sprachmodelle für den quantitativen Handel demonstriert. Modelle wie GPT-2 können herkömmliche Marktdaten analysieren und mit Nachrichtendaten, Daten aus sozialen Medien und anderen textreichen Daten umgehen, was eine umfassendere Analyse der Marktstimmung ermöglicht und Händlern hilft, klügere Entscheidungen zu treffen. Diese verkehrsträgerübergreifende Fähigkeit ist etwas, was die traditionellen Finanzmodelle nicht haben, und ist ein Bereich, den wir weiter erforschen müssen.
Im nächsten Artikel werden wir anhand eines Beispiels zeigen, wie sich die Anwendung großer Sprachmodelle im quantitativen Handel optimieren lässt.
Anhang:
Dateien | Beschreibung |
---|---|
torch2onnx.py | Das Python-Skript zur Konvertierung des GPT-2-Modells in das ONNX-Format |
server.py | Das Python-Skript zur Bereitstellung von GPT2-Modellinferenzdiensten und Ergebnissen |
gpt2_EA.mq5 | EA-Programm zum Testen der Inferenzergebnisse von GPT2-Modellen |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/13506





- 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.
Sehen Sie sich den neuen Artikel an: Integrieren Sie Ihr eigenes LLM in EA (Teil 5): Handelsstrategie mit LLMs entwickeln und testen(IV) - Handelsstrategie testen.
Autor: Yuqiang Pan