English 日本語
preview
Klassische Strategien neu interpretieren: Rohöl

Klassische Strategien neu interpretieren: Rohöl

MetaTrader 5Beispiele | 26 Juni 2024, 14:03
87 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Einführung

Öl ist der wichtigste Rohstoff auf der Erde. Rohöl ist in seiner ursprünglichen Form nutzlos; nach seiner Raffinierung wird es jedoch in verschiedenen Industriezweigen verwendet, von der einfachen Landwirtschaft bis hin zu komplexen Arzneimitteln. Öl ist einer der wenigen Rohstoffe, die wirklich in allen Branchen gefragt sind. Der Ölpreis ist ein wichtiger ökonometrischer Indikator für das weltweite Produktionsniveau und das Wirtschaftswachstum.

Der weltweite Rohölhandel wird von zwei Benchmarks beherrscht: West Texas Intermediate (WTI), der nordamerikanischen Benchmark, und Brent, die für die Notierung des größten Teils des weltweiten Rohöls verwendet wird.

In dieser Diskussion werden wir eine klassische Rohöl-Spread-Handelsstrategie wieder aufgreifen, in der Hoffnung, eine optimale maschinelle Lernstrategie zu finden, die diese klassische Strategie in einem modernen, von Algorithmen dominierten Ölmarkt schmackhafter macht.

Zu Beginn unserer Diskussion werden wir zunächst die Unterschiede zwischen den beiden oben genannten Öl-Benchmarks herausstellen. Von dort aus beginnen wir mit der Visualisierung des Brent-WTI-Spreads in MQL5 und besprechen die klassische Spread-Handelsstrategie. Auf dieser Grundlage werden wir demonstrieren, wie man überwachtes maschinelles Lernen auf die Spanne zwischen West Texas Intermediate- und Brent-Ölpreisen anwenden kann, um potenziell Frühindikatoren für Preisveränderungen aufzudecken. Nach der Lektüre dieses Artikels werden Sie die folgenden Punkte sicher verstehen:

  • Der Unterschied zwischen den Brent- und WTI-Benchmarks und warum sie so wichtig sind.
  • Wie man MQL5-Matrix- und -Vektor-Funktionen verwendet, um kompakte maschinelle Lernmodelle zu erstellen, die einfach zu pflegen und von Grund auf zu implementieren sind.
  • Wie man die Pseudo-Inverse-Technik anwendet, um eine Kleinst-Quadrat-Lösung für die Prognose des zukünftigen Brent-Preises unter Verwendung des WTI-Brent-Spreads zu finden.

Globaler Rohöl-Benchmark: Brent

Wenn Rohöl aus dem Boden gewonnen wird, ist es ein Gemisch aus Sauerstoff, Kohlenstoff, Wasserstoff und Schwefelverunreinigungen. Brent ist eine Klassifizierung für Rohölmischungen, die als „leicht und süß“ (light & sweet) gelten. Um als süß zu gelten, muss die Mischung geringe Konzentrationen von Schwefelverunreinigungen aufweisen. Außerdem wird es als leicht bezeichnet, weil es eine geringe Dichte hat. Diese Eigenschaften sind wünschenswert, da sie darauf hinweisen, dass die Mischung leicht zu verfeinern ist. Die letzte Eigenschaft von Brent, die wir hervorheben wollen, ist, dass Brent von geringerer Qualität ist als WTI. Brent wird hauptsächlich in der Nordsee gefördert. Nach der Förderung kann es leicht in Fässern an Bord großer Öltanker gelagert werden, was Brent einen deutlichen Vorteil gegenüber WTI verschafft: Es ist sehr leicht zugänglich. Brent wird derzeit mit einem Aufschlag gegenüber WTI gehandelt.

Preis für Brent

Abb. 1: Historischer Preis von Brent in MQL5

Nordamerikanische Rohöl-Benchmark: West Texas Intermediate

West Texas Intermediate (WTI) ist eine Klassifizierung für eine bestimmte Rohölmischung, bei der es sich um ein „leichtes, süßes“ Öl handeln muss. WTI wird in den gesamten USA gefördert, hauptsächlich aber in Texas. Es ist süßer und leichter als Brent, was bedeutet, dass es sich leichter zu Fertigprodukten verarbeiten lässt. In der Vergangenheit wurde es in den Binnenregionen der USA abgebaut und war daher weit weniger zugänglich als Brent. Dank massiver Investitionen an der Golfküste und der Aufhebung des Ölexportverbots im Jahr 2015 ist WTI heute jedoch leichter zugänglich als je zuvor.

West Texas Intermediate

Abb. 2: Historischer Preis von WTI in MQL5

Erste Schritte: Visualisierung der Differenz

Für den Anfang können wir ein praktisches Skript erstellen, um den Spread (Differenz) zwischen den beiden Rohstoffen zu visualisieren. Wir können die MQL5-Grafikbibliothek verwenden, um jede gewünschte Funktion einfach darzustellen. Die Grafikbibliothek verwaltet die Skalierung für Sie, was immer hilfreich ist. Nachdem Sie die Grafikbibliothek eingebunden haben, werden Sie eine Variable mit der Bezeichnung „consumption“ finden. Mit dieser Variable können wir leicht die Hälfte, ein Viertel oder einen beliebigen Bruchteil der verfügbaren Gesamtdaten auswählen.

Da wir historische Daten zu zwei verschiedenen Vermögenswerten abfragen, müssen wir die Gesamtzahl der für jeden Markt verfügbaren Balken kennen. Von dort aus nehmen wir an, dass die kleinste, verfügbare Anzahl von Balken die Gesamtzahl der verfügbaren Balken ist. Wir verwenden einen ternären Operator, um die richtige Anzahl von Balken auszuwählen.

Nachdem wir die richtige Anzahl von Balken ermittelt haben, können wir den Spread (Differenz) darstellen.

//+------------------------------------------------------------------+
//|                                             Brent-WTI Spread.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Graphics\Graphic.mqh>
//Set this value between 0 and 1 to control how much data is used
double consumption = 1.0;
int brent_bars = (int) NormalizeDouble((iBars("UK Brent Oil",PERIOD_CURRENT) * consumption),0);
int wti_bars = (int) NormalizeDouble((iBars("WTI_OIL",PERIOD_CURRENT) * consumption),0);
//We want to know which symbol has the least number of bars.
int max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars;

//+------------------------------------------------------------------+
//|This event handler is only triggered when the script launches     |
//+------------------------------------------------------------------+
void OnStart()
  {
   CGraphic graphic;
   double from = 0;
   double to  = max_bars;
   double step = 1;
   graphic.Create(0,"G",0,0,0,600,200);
   CColorGenerator generator;
   uint spread = generator.Next();
   CCurve *curve = graphic.CurveAdd(SpreadFunction,from,to,step,spread,CURVE_LINES,"Blue");
   curve.Name("Spread");
   graphic.XAxis().Name("Time");
   graphic.XAxis().NameSize(12);
   graphic.YAxis().Name("Brent-WTI Spread");
   graphic.YAxis().NameSize(12);
   graphic.CurvePlotAll();
   graphic.Update();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|This function returns the Brent-WTI spread                        |
//+------------------------------------------------------------------+
double SpreadFunction(double x)
  {
   return(iClose("UK Brent Oil",PERIOD_CURRENT,(max_bars - x)) - iClose("WTI_OIL",PERIOD_CURRENT,(max_bars - x)));
  }
//+------------------------------------------------------------------+

Brent-WTI-Spread

Abb. 3: Visualisierung der Brent-WTI-Spread in MQL5.


Überblick über unsere Handelsstrategie: Einsatz von überwachtem maschinellem Lernen

Die Prämisse der klassischen Rohölstrategie war, dass sich langfristig immer ein Preisgleichgewicht einstellen wird. Die klassische Strategie für den Handel mit Öl-Spreads sieht vor, dass wir zunächst den aktuellen Spread zwischen Brent und WTI beobachten. Liegt der Spread über seiner Basislinie, die zum Beispiel der gleitende 20er-Durchschnitt sein könnte, dann würden wir daraus schließen, dass der Spread in naher Zukunft zu seinem Mittelwert zurückkehren wird. Wenn also die Brent-Preise steigen, würden wir verkaufen. Umgekehrt würden wir kaufen, wenn die Brent-Preise fallen würden. 

Seitdem diese Strategie entwickelt wurde, hat sich der Ölmarkt jedoch erheblich verändert. Wir brauchen ein objektives Verfahren, um abzuleiten, welche Beziehungen zwischen dem Spread und dem künftigen Preis von Brent bestehen. Das maschinelle Lernen ermöglicht es dem Computer, seine eigenen Handelsregeln aus allen Beziehungen zu lernen, die er analytisch beobachten kann.

Damit unser Computer seine eigene Handelsstrategie erstellen kann, beginnen wir mit einer Datenmatrix A.

A symbolisiert die historischen Preisdaten, die uns für Brent vorliegen. Wir verwenden den Schlusskurs, den Spread und einen Intercept, der einen konstanten Wert von eins hat. Anschließend erstellen wir einen separaten Spaltenvektor x, der für jede Spalte in A einen Koeffizienten enthält. Dieser Wert wird direkt aus den Marktdaten berechnet und von unserem Modell zur Vorhersage künftiger Preise verwendet.

Definition von A und x

Abb. 4: Rahmen für das Problem der kleinsten Quadrate.

Nachdem wir unsere Eingabematrix A erstellt haben, müssen wir wissen, welche Schlusskurse von Brent mit jeder der Eingaben in A gepaart wurden. Wir speichern den Ausgabepreis in einem Vektor, y. Unser Ziel ist es, einen Weg zu finden, die Eingabedatenmatrix A auf den Ausgabedatenvektor y abzubilden und dabei den geringstmöglichen Fehler über alle uns vorliegenden Trainingsbeobachtungen zu approximieren. Die Antwort auf dieses Problem wird als die Lösung des kleinsten Quadrats bezeichnet.

Einführung in die Lösung der kleinsten Quadrate

Abb. 5: Unser Ausgangsvektor y.

Es gibt viele gültige Problemlösungen für die Szenarien der kleinsten Quadrate. Im Folgenden wird eine Technik vorgestellt, die als Pseudo-Inversions-Technik bekannt ist: Die Pseudo-Inversions-Technik ist ein charakteristisches Konzept der linearen Algebra, mit dem sich nichtquadratische Matrizen invertieren lassen. Wir werden die Pseudo-Inversionsmethode anwenden, um Koeffizientenwerte für die Spalte x zu finden, die dem geringstmöglichen Fehler A auf abbilden. 

Moore-Penrose-Pseudo-Inverse Lösung

Abb. 6: Einführung der pseudo-inversen Lösung.

Die beiden obigen Gleichungen besagen zunächst, dass wir nach einem Wert von x suchen, der den Fehler zwischen unserer Vorhersage A * x und dem tatsächlichen Brent-Schlusskurs y minimiert. Beachten Sie die doppelten, vertikalen Linien um Ax-y. Diese doppelten vertikalen Linien sind ein Symbol für die L2-Norm. Wenn wir es mit physischen Objekten in der realen Welt zu tun haben, können wir fragen: „Wie groß ist es?“. Wenn wir jedoch wissen wollen, wie groß ein Vektor oder eine Matrix ist, fragen wir nach ihrer Norm, die auf verschiedene Weise berechnet werden kann, am häufigsten trifft man auf die L1- oder L2-Norm. Für unsere Diskussion werden wir nur die L2-Norm betrachten.

Die L2-Norm wird berechnet, indem jede Einheit des Vektors quadriert wird, alle quadrierten Werte summiert werden und dann die Quadratwurzel der Summe berechnet wird. Sie wird auch als euklidische Norm bezeichnet. In einer einfacheren Sprache würden wir sagen: „Wir suchen nach Werten für x, die die Größe aller Fehler unseres Modells reduzieren“, und in einer technischeren Sprache würden wir sagen: „Finden Sie optimale Werte für x, die die L2-Norm der Residuen minimieren“.

Der Wert von x, der unsere Bedingungen erfüllt, wird mit x* bezeichnet. Um x* zu finden, berechnen wir das Punktprodukt der Pseudo-Inverse von A und y. Es ist höchst unwahrscheinlich, dass Sie die Pseudo-Inversionsfunktion jemals selbst implementieren müssen, es sei denn als Übung in linearer Algebra. Andernfalls werden wir uns auf die integrierte Funktion in MQL5 verlassen.

//+------------------------------------------------------------------+
//|Demonstrating the pseudo-inverse solution in action.              |                                                                |
//+------------------------------------------------------------------+
void OnStart()
  {
//Training and test data
   matrix A; //A is the input data. look at the figure above if you need a reminder.
   matrix y,x; //y is the output data, x is the coefficients.
   A.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_OHLC,20,1000);
   y.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,1,1000);
   A.Reshape(1000,4);
   y.Reshape(1000,1);
   Print("Attempting Psuedoinverse Decomposition");
   Print("Attempting to calculate the Pseudoinverse Coefficients: ");
   x = A.PInv().MatMul(y);
   Print("Coefficients: ");
   Print("Open: ",x[0][0],"\nHigh: ",x[1][0],"\nLow: ",x[3][0],"\nClose: ",x[3][0]);
  }
//+------------------------------------------------------------------+

Pseudo-Inversions-Skript

Abb. 7: Ein Beispiel für die Anwendung der Pseudo-Inversen Technik.

Der obige Code ist eine einfache Demonstration der Verwendung der Pseudo-Inversen-Technik. In diesem Beispiel wollen wir den Schlusskurs eines Symbols anhand seines aktuellen Eröffnungs-, Höchst-, Tiefst- und Schlusskurses vorhersagen. Dieses einfache Beispiel verdeutlicht die wichtigsten Grundsätze, die wir verstehen müssen. Wir beginnen mit der Definition unserer Eingabedaten, die in der Matrix A gespeichert sind. Um die Daten abzurufen, verwenden wir die Funktion CopyRates, die die folgenden Parameter in der angegebenen Reihenfolge benötigt:

  • Symbol name: Der Name des Symbols, mit dem wir handeln wollen.
  • Timeframe: Der Zeitrahmen, der mit unserem Risikoniveau übereinstimmt.
  • Rates mask: Hier wird angegeben, welche Preise kopiert werden sollen, sodass wir z. B. nur die Eröffnungspreise auswählen können, falls gewünscht.
  • From: Das Startdatum für das Kopieren der Daten, wobei sichergestellt wird, dass eine Lücke zwischen den Eingabe- und den Ausgabedaten besteht und dass die Eingabedaten von einem früheren Datum ausgehen.
  • Count: Die Anzahl der zu kopierenden Kerzen.

Nachdem wir die Eingangsdatenmatrix A erstellt haben, wiederholen wir den Vorgang für die Ausgangsdatenmatrix y. Anschließend werden beide Matrizen neu geformt, um sicherzustellen, dass sie die richtige Größe haben und für die geplanten Operationen geeignet sind.

Als Nächstes wird der x-Spaltenvektor mit den aus A und y abgeleiteten Werten aufgefüllt. Glücklicherweise unterstützt die MQL5-API die Verkettung von Matrixoperationen, sodass wir die Pseudo-Inverse mit einer einzigen Zeile Code berechnen können. Anschließend können wir die Koeffizienten in unserem x-Spaltenvektor ausdrucken.

Wir werden die gleichen Schritte anwenden, um unsere Handelsstrategie zu entwickeln. Der einzige zusätzliche Schritt, der hier nicht gezeigt wird, ist die Verwendung unseres Modells, um Vorhersagen zu treffen, was später in unserer Diskussion erläutert wird. Mit dieser Grundlage sind wir bereit, mit dem Aufbau unserer Handelsstrategie zu beginnen.

Alles zusammenfügen

Wir sind nun bereit, das Herzstück unseres Algorithmus zu definieren. Wir beginnen mit der Handelsbibliothek, die wir für die Eröffnung und Verwaltung von Positionen benötigen.

//+------------------------------------------------------------------+
//|                                                     Brent EA.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//Libraries
#include  <Trade\Trade.mqh>
CTrade ExtTrade;
#include <TrailingStop\ATRTrailingStop3.mqh>
ATRTrailingStop ExtATRTrailingStop;

Anschließend legen wir die Größe unserer Handelspositionen und die Risikoparameter fest. Die erste Eingabe bestimmt, um wie viel größer als das Mindestlos jede Position sein wird. Mit der zweiten Eingabe wird das Gewinnniveau festgelegt, bei dem alle offenen Positionen geschlossen werden. Danach folgt der Eingabeparameter, der den Gesamtbetrag des Drawdown begrenzt, den wir für dieses Konto zulassen. Und schließlich legen wir fest, wie viele Positionen wir jedes Mal eröffnen möchten, wenn wir einen Handel platzieren.

//Inputs
input double lot_multiple = 1.0;
input double profit_target = 10;
input double max_loss = 20;
input int position_size = 2;

Nun müssen wir wissen, wie viele Balken in jedem Markt verfügbar sind, damit wir sicherstellen können, dass wir immer versuchen, die richtige Anzahl von Balken zu kopieren, die in beiden Märkten verfügbar sind. Die „richtige Zahl“ ist in unserem Fall die kleinste, verfügbare Anzahl von Balken. Wir haben auch eine Variable mit der Bezeichnung „consumption“ definiert, weil wir damit steuern können, wie viele Daten wir verwenden wollen. Im folgenden Codebeispiel verwenden wir 1 % aller verfügbaren historischen Daten.

//Set this value between 0 and 1 to control how much data is used
double consumption = 0.01;
//We want to know which symbol has the least number of bars.
double brent_bars = (double) NormalizeDouble((iBars("UK Brent Oil",PERIOD_CURRENT) * consumption),0);
double wti_bars = (double) NormalizeDouble((iBars("WTI_OIL",PERIOD_CURRENT) * consumption),0);

Hier bestimmen wir, welcher Markt weniger Balken zur Verfügung hat, und verwenden diese Anzahl von Balken als unsere Grenze. Wenn wir diesen Schritt auslassen, werden die Daten zwischen den beiden Märkten möglicherweise nicht übereinstimmen, es sei denn, Ihr Makler garantiert gleichmäßig übereinstimmende Datensätze zu historischen Kursen für beide Vermögenswerte. „Look Ahead“ ist unser Prognosehorizont, d. h. wie viele Schritte in die Zukunft wir prognostizieren.

//Select the lowest
double max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars;
//How far into the future are we forecasting
double look_ahead = NormalizeDouble((max_bars / 4),0);
//How many bars should we fetch? 
int fetch = (int) (max_bars - look_ahead) - 1;

Nun müssen wir die Variablen definieren, die wir in unserer Notation festgelegt haben; ich füge eine Kopie des Bildes bei, damit Sie nicht nach oben scrollen müssen. Denken Sie daran, dass A die Matrix ist, die unsere Eingabedaten speichert. Wir können so viele oder so wenige Eingaben wählen, wie wir wollen, in diesem Beispiel werde ich 3 Eingaben verwenden. x* stellt den Wert von x dar, der die L2-Norm unserer Residuen minimiert.

Moore-Penrose-Pseudo-Inverse Lösung

Abb. 6: Eine Erinnerung an die von uns definierte Notation.

//Matrix A stores our inputs. y is the output. x is the coefficients.
matrix A = matrix::Zeros(fetch,6);
matrix y = matrix::Zeros(fetch,1);
vector wti_price = vector::Zeros(fetch);
vector brent_price = vector::Zeros(fetch);
vector spread;
vector intercept = vector::Ones(fetch);
matrix x = matrix::Zeros(6,1);
double forecast = 0;
double ask = 0;
double bid = 0;
double min_volume = 0;

Wir definieren zwei String-Variablen, um die Namen der Symbole zu speichern, mit denen wir handeln wollen, und sind damit bei unserer OnInit-Funktion angelangt. Diese Funktion ist in unserem Fall einfach, wir müssen nur das Mindesthandelsvolumen kennen, das für Brent erlaubt ist.

string brent = "UK Brent Oil";
string wti = "WTI_OIL";
bool model_initialized = false;
int OnInit()
  {
//Initialise trailing stops
   if(atr_multiple > 0)
      ExtATRTrailingStop.Init(atr_multiple);
   min_volume = SymbolInfoDouble(brent,SYMBOL_VOLUME_MIN);
   return(INIT_SUCCEEDED);
//---
  }

Wir arbeiten jetzt an unserer OnTick-Funktion. Innerhalb des Hauptteils aktualisieren wir zunächst die Preise für das Angebot und die Nachfrage, die wir im Auge behalten. Dann prüfen wir, ob unser Modell initialisiert wurde. Wenn nicht, wird es trainiert und angepasst, andernfalls prüfen wir, ob wir offene Positionen haben. Falls wir keine offenen Positionen haben, erhalten wir eine Prognose von unserem Modell und handeln dann in die Richtung, die unser Modell vorhersagt. Andernfalls, wenn wir offene Positionen haben, prüfen wir, ob unsere Positionen das Gewinnziel oder den maximalen Drawdown-Level nicht überschritten haben.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   ask = SymbolInfoDouble(brent,SYMBOL_ASK);
   bid = SymbolInfoDouble(brent,SYMBOL_BID);
   if(model_initialized)
     {
      if(PositionsTotal() == 0)
        {
         forecast = 0;
         forecast = ModelForecast();
         InterpretForecast();
        }

      else
        {
         ManageTrades();
        }
     }

   else
     {
      model_initialized = InitializeModel();
     }

  }
//+------------------------------------------------------------------+

Dies ist die Funktion, die dafür verantwortlich ist, zu überprüfen, ob wir das Risikoniveau überschritten oder unser Gewinnziel erreicht haben. Es wird nur im OnTick Event Handler aufgerufen, wenn wir offene Trades haben.

void ManageTrades()
  {
   if(AccountInfoDouble(ACCOUNT_PROFIT) > profit_target)
      CloseAll();
   if(AccountInfoDouble(ACCOUNT_PROFIT) < (-1 * max_loss))
      CloseAll();
  }

Immer wenn unser Modell eine Vorhersage gemacht hat, rufen wir InterpretForecast auf, um die Vorhersagen unseres Modells zu interpretieren und die entsprechenden Positionen zu eröffnen.

void InterpretForecast()
  {
   if(forecast != 0)
     {
      if(forecast > iClose(_Symbol,PERIOD_CURRENT,0))
        {
         check_buy();
        }

      else
         if(forecast < iClose(_Symbol,PERIOD_CURRENT,0))
           {
            check_sell();
           }
     }
  }


Wir haben ein spezielles Verfahren für das Eingehen von Kaufpositionen. Beachten Sie, dass das zuvor ermittelte Mindestvolumen mit dem eingegebenen Lot-Multiplikator multipliziert wird, sodass der Nutzer die Kontrolle über die Losgröße hat, die für die Eingabe von Geschäften verwendet wird.

void check_buy()
  {
   if(PositionsTotal() == 0)
     {
      for(int i = 0; i < position_size; i++)
        {
         ExtTrade.Buy(lot_multiple * min_volume,brent,ask,0,0,"BUY");
        }
     }
  }

Ich habe auch spezielle Verfahren für das Eingehen von Verkaufspositionen eingefügt, falls wir spezifische Regeln erkennen, die ausschließlich für eine der beiden Positionsseiten gelten.

void check_sell()
  {
   if(PositionsTotal() == 0)
     {
      for(int i = 0; i < position_size; i++)
        {
         ExtTrade.Sell(lot_multiple * min_volume,brent,bid,0,0,"SELL");
        }
     }
  }

Jetzt definieren wir eine Funktion, die alle offenen Positionen schließt. Er durchläuft die offenen Positionen, die wir haben, und schließt nur die Positionen, die unter Brent eröffnet wurden. Wenn Sie mit diesem EA sowohl mit Brent als auch mit WTI handeln möchten, entfernen Sie einfach die Sicherheitsprüfungen, die ich eingebaut habe, um sicherzustellen, dass das Symbol Brent ist. Denken Sie daran, dass ich Brent nur zu Demonstrationszwecken ausgewählt habe. Es steht Ihnen frei, den EA individuell anzupassen.

void CloseAll(void)
  {
   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetSymbol(i) == brent)
           {
            ulong ticket;
            ticket = PositionGetTicket(i);
            ExtTrade.PositionClose(ticket);
           }
        }
     }
  }

Wir werden nun 2 Methoden zum Schließen von Kauf- bzw. Verkaufspositionen definieren. Auch hier wird wieder eine Iteration über alle Positionen durchgeführt und das entsprechende Ticket für jede Position ermittelt. Dann überprüfen wir, ob die Positionsart mit der gesuchten Art übereinstimmt. Wenn alles erfolgreich ist, werden wir die Position schließen.

void close_buy()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal(); i++)
        {
         ticket = PositionGetTicket(i);
         type = (int)PositionGetInteger(POSITION_TYPE);
         if(type == POSITION_TYPE_BUY)
           {
            ExtTrade.PositionClose(ticket);
           }
        }
     }
  }

void close_sell()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal(); i++)
        {
         ticket = PositionGetTicket(i);
         type = (int)PositionGetInteger(POSITION_TYPE);
         if(type == POSITION_TYPE_SELL)
           {
            ExtTrade.PositionClose(ticket);
           }
        }
     }
  }

Wir werden nun festlegen, wie unser Modell initialisiert werden soll:

  1. Wir stellen sicher, dass beide Symbole verfügbar sind und dem Marktfenster hinzugefügt wurden.
  2. Wir kopieren die Ausgabedaten in die Matrix y (der Schlusskurs von Brent, beginnend mit Kerze 1).
  3. Wir kopieren die Eingabedaten in die Matrix A (Schlusskurs der Sorte Brent, beginnend bei 1 plus unserem Prognosehorizont).
  4. Es folgt die Umformung der Datenmatrix A.
  5. Wir berechnen den Spread zwischen Brent und WTI und addieren ihn zu A.
  6. Für den Intercept fügen wir eine Reihe von 1en in A ein.
  7. Wir transponieren sowohl A als auch y.

Sobald diese Schritte abgeschlossen sind, wird geprüft, ob die Eingabedaten gültig sind. Wenn nicht, wird eine Fehlermeldung ausgegeben. Ist sie gültig, fahren wir mit der Berechnung der x-Koeffizientenmatrix fort.

bool InitializeModel()
  {
//Try select the symbols
   if(SymbolSelect(brent,true) && SymbolSelect(wti,true))
     {
      Print("Symbols Available. Bars: ",max_bars," Fetch: ",fetch," Look ahead: ",look_ahead);
      //Get historical data on Brent , our model output
      y.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,1,fetch);
      //model input
      A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1 + look_ahead),fetch);
      brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch);
      wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch);
      //Calculate the spread
      spread = brent_price - wti_price;
      Print("The Current Spread: ",spread);
      A.Reshape(3,fetch);
      //Add the spread to the input matrix
      A.Row(spread,1);
      //Add a column for the intercept
      A.Row(intercept,2);
      //Reshape the matrices
      A = A.Transpose();
      y = y.Transpose();
      //Inspect the matrices
      if((A.Cols() == 0 || y.Cols() == 0))
        {
         Print("Error occured when copying historical data");
         Print("A rows: ",A.Rows()," y rows: ",y.Rows()," A columns: ",A.Cols()," y cols: ",y.Cols());
         Print("A");
         Print(A);
         Print("y");
         Print(y);
         return(false);
        }

      else
        {
         Print("No errors occured when copying historical data");
         x = A.PInv().MatMul(y);
         Print("Finished Fitting The Model");
         Print(x);
         return(true);
        }
     }

   Print("Faield to select symbols");
   return(false);
  }

Schließlich müssen wir eine Funktion zur Vorhersage zukünftiger Werte des Brent-Schlusskurses definieren.

double ModelForecast()
  {
   if(model_initialized)
     {
      //model input
      A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1);
      brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1);
      wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1);
      //Calculate the spread
      spread = brent_price - wti_price;
      Print("The Spread: ",spread);
      A.Reshape(3,fetch);
      //Add the spread to the input matrix
      A.Row(spread,1);
      //Add a column for the intercept
      A.Row(intercept,2);
      //Reshape the matrices
      A = A.Transpose();
      double _forecast = (A[0][0]*x[0][0]) + (A[1][0]*x[1][0]) + (A[2][0]*x[2][0]);
      return(_forecast);
     }
   return(0);
  }

Alles zusammengenommen ergibt unsere Bewerbung folgendes Ergebnis.

//+------------------------------------------------------------------+
//|                                                     Brent EA.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//Libraries
#include  <Trade\Trade.mqh>
CTrade ExtTrade;
#include <TrailingStop\ATRTrailingStop3.mqh>
ATRTrailingStop ExtATRTrailingStop;

//Inputs
input double atr_multiple = 5.0;
input double lot_multiple = 1.0;
input double profit_target = 10;
input double max_loss = 20;
input int position_size = 2;

//Set this value between 0 and 1 to control how much data is used
double consumption = 0.01;
//We want to know which symbol has the least number of bars.
double brent_bars = (double) NormalizeDouble((iBars("UK Brent Oil",PERIOD_CURRENT) * consumption),0);
double wti_bars = (double) NormalizeDouble((iBars("WTI_OIL",PERIOD_CURRENT) * consumption),0);
//Select the lowest
double max_bars = (brent_bars < wti_bars) ? brent_bars : wti_bars;
//How far into the future are we forecasting
double look_ahead = NormalizeDouble((max_bars / 4),0);
//How many bars should we fetch?
int fetch = (int)(max_bars - look_ahead) - 1;
//Matrix A stores our inputs. y is the output. x is the coefficients.
matrix A = matrix::Zeros(fetch,6);
matrix y = matrix::Zeros(fetch,1);
vector wti_price = vector::Zeros(fetch);
vector brent_price = vector::Zeros(fetch);
vector spread;
vector intercept = vector::Ones(fetch);
matrix x = matrix::Zeros(6,1);
double forecast = 0;
double ask = 0;
double bid = 0;
double min_volume = 0;

string brent = "UK Brent Oil";
string wti = "WTI_OIL";
bool model_initialized = false;
int OnInit()
  {
//Initialise trailing stops
   if(atr_multiple > 0)
      ExtATRTrailingStop.Init(atr_multiple);
   min_volume = SymbolInfoDouble(brent,SYMBOL_VOLUME_MIN);
   return(INIT_SUCCEEDED);
//---
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   ask = SymbolInfoDouble(brent,SYMBOL_ASK);
   bid = SymbolInfoDouble(brent,SYMBOL_BID);
   if(model_initialized)
     {
      if(PositionsTotal() == 0)
        {
         forecast = 0;
         forecast = ModelForecast();
         InterpretForecast();
        }

      else
        {
         ManageTrades();
        }
     }

   else
     {
      model_initialized = InitializeModel();
     }

  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|This function closes trades if we reach our profit or loss limit  |                                                              |
//+------------------------------------------------------------------+
void ManageTrades()
  {
   if(AccountInfoDouble(ACCOUNT_PROFIT) > profit_target)
      CloseAll();
   if(AccountInfoDouble(ACCOUNT_PROFIT) < (-1 * max_loss))
      CloseAll();
  }

//+------------------------------------------------------------------+
//|This function judges if our model is giving a long or short signal|                                                                |
//+------------------------------------------------------------------+
void InterpretForecast()
  {
   if(forecast != 0)
     {
      if(forecast > iClose(_Symbol,PERIOD_CURRENT,0))
        {
         check_buy();
        }

      else
         if(forecast < iClose(_Symbol,PERIOD_CURRENT,0))
           {
            check_sell();
           }
     }
  }

//+------------------------------------------------------------------+
//|This function checks if we can open buy positions                  |
//+------------------------------------------------------------------+
void check_buy()
  {
   if(PositionsTotal() == 0)
     {
      for(int i = 0; i < position_size; i++)
        {
         ExtTrade.Buy(lot_multiple * min_volume,brent,ask,0,0,"BUY");
        }
     }
  }

//+------------------------------------------------------------------+
//|This function checks if we can open sell positions                |
//+------------------------------------------------------------------+
void check_sell()
  {
   if(PositionsTotal() == 0)
     {
      for(int i = 0; i < position_size; i++)
        {
         ExtTrade.Sell(lot_multiple * min_volume,brent,bid,0,0,"SELL");
        }
     }
  }

//+------------------------------------------------------------------+
//|This function will close all open trades                          |
//+------------------------------------------------------------------+
void CloseAll(void)
  {
   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetSymbol(i) == brent)
           {
            ulong ticket;
            ticket = PositionGetTicket(i);
            ExtTrade.PositionClose(ticket);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//|This function closes any open buy trades                          |
//+------------------------------------------------------------------+
void close_buy()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal(); i++)
        {
         ticket = PositionGetTicket(i);
         type = (int)PositionGetInteger(POSITION_TYPE);
         if(type == POSITION_TYPE_BUY)
           {
            ExtTrade.PositionClose(ticket);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//|This function closes any open sell trades                         |
//+------------------------------------------------------------------+
void close_sell()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal(); i++)
        {
         ticket = PositionGetTicket(i);
         type = (int)PositionGetInteger(POSITION_TYPE);
         if(type == POSITION_TYPE_SELL)
           {
            ExtTrade.PositionClose(ticket);
           }
        }
     }
  }


//+------------------------------------------------------------------+
//|This function initializes our model and fits it onto the data     |
//+------------------------------------------------------------------+
bool InitializeModel()
  {
//Try select the symbols
   if(SymbolSelect(brent,true) && SymbolSelect(wti,true))
     {
      Print("Symbols Available. Bars: ",max_bars," Fetch: ",fetch," Look ahead: ",look_ahead);
      //Get historical data on Brent , our model output
      y.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,1,fetch);
      //model input
      A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1 + look_ahead),fetch);
      brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch);
      wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,(1+look_ahead),fetch);
      //Calculate the spread
      spread = brent_price - wti_price;
      Print("The Current Spread: ",spread);
      A.Reshape(3,fetch);
      //Add the spread to the input matrix
      A.Row(spread,1);
      //Add a column for the intercept
      A.Row(intercept,2);
      //Reshape the matrices
      A = A.Transpose();
      y = y.Transpose();
      //Inspect the matrices
      if((A.Cols() == 0 || y.Cols() == 0))
        {
         Print("Error occured when copying historical data");
         Print("A rows: ",A.Rows()," y rows: ",y.Rows()," A columns: ",A.Cols()," y cols: ",y.Cols());
         Print("A");
         Print(A);
         Print("y");
         Print(y);
         return(false);
        }

      else
        {
         Print("No errors occured when copying historical data");
         x = A.PInv().MatMul(y);
         Print("Finished Fitting The Model");
         Print(x);
         return(true);
        }
     }

   Print("Faield to select symbols");
   return(false);
  }

//+------------------------------------------------------------------+
//|This function makes a prediction once our model has been trained  |
//+------------------------------------------------------------------+
double ModelForecast()
  {
   if(model_initialized)
     {
      //model input
      A.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1);
      brent_price.CopyRates(brent,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1);
      wti_price.CopyRates(wti,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1);
      //Calculate the spread
      spread = brent_price - wti_price;
      Print("The Spread: ",spread);
      A.Reshape(3,fetch);
      //Add the spread to the input matrix
      A.Row(spread,1);
      //Add a column for the intercept
      A.Row(intercept,2);
      //Reshape the matrices
      A = A.Transpose();
      double _forecast = (A[0][0]*x[0][0]) + (A[1][0]*x[1][0]) + (A[2][0]*x[2][0]);
      return(_forecast);
     }
   return(0);
  }
//+------------------------------------------------------------------+

Wir sind nun bereit, unseren Handelsalgorithmus mit dem integrierten MetaTrader 5 Strategy Tester zu testen.

Testen unseres EA

Abb. 7: Backtesting unseres quantitativen Handelsalgorithmus.

Backtesting unseres EA

Abb. 8: Historische Renditen aus unserem Backtest.

Schlussfolgerung

Die Strategie, die wir heute in Betracht gezogen haben, ist verbesserungsfähig. So befinden sich beispielsweise 67 % aller bekannten Ölreserven der Welt im Nahen Osten, aber wir haben keinen der Öl-Benchmarks am Persischen Golf berücksichtigt. Darüber hinaus gibt es andere aufschlussreiche Spreads, die möglicherweise Vorhersagequalitäten haben, die weitere Untersuchungen rechtfertigen, wie z. B. der Crack-Spread. Der Crack-Spread ist ein Maß für die Rentabilität der Raffinerien. In der Vergangenheit hat sich gezeigt, dass bei hohen Crack-Spreads das Angebot tendenziell zunimmt und bei niedrigen Crack-Spreads das Angebot tendenziell zurückgeht. Wenn Sie den Artikel bis hierher gelesen haben, sollten Sie sofort erkennen, welche Auswirkungen der Crack-Spread auf den Rohölpreis haben kann.

Unsere Strategie ist rentabel, aber sie ist anfällig für unregelmäßige Ausfälle: Die Ölmärkte sind notorisch volatil, und weitere Verbesserungen werden schrittweise durch die Anwendung robusterer Risikomanagementprinzipien erreicht, die dennoch rentabel sind.

Ich wünsche Ihnen Frieden, Wohlstand und profitable Geschäfte.


Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/14855

Beigefügte Dateien |
Brent_EA.mq5 (8.37 KB)
Ein Algorithmus zur Auswahl von Merkmalen, der energiebasiertes Lernen in reinem MQL5 verwendet Ein Algorithmus zur Auswahl von Merkmalen, der energiebasiertes Lernen in reinem MQL5 verwendet
In diesem Artikel stellen wir die Implementierung eines Algorithmus zur Auswahl von Merkmalen vor, der in einer wissenschaftlichen Arbeit mit dem Titel „FREL: A stable feature selection algorithm“ vorgestellt wurde und auch als Merkmalsgewichtung als reguliertes energiebasiertes Lernen bezeichnet werden kann.
Aufbau eines Modells aus Kerzen, Trend und Nebenbedingungen (Teil 3): Erkennung von Trendänderungen bei der Verwendung dieses Systems Aufbau eines Modells aus Kerzen, Trend und Nebenbedingungen (Teil 3): Erkennung von Trendänderungen bei der Verwendung dieses Systems
In diesem Artikel wird untersucht, wie Wirtschaftsnachrichten, das Anlegerverhalten und verschiedene Faktoren die Trendumkehr an den Märkten beeinflussen können. Es enthält eine Videoerklärung und fährt fort mit der Integration von MQL5-Code in unser Programm, um Trendumkehrungen zu erkennen, uns zu warnen und geeignete Maßnahmen auf der Grundlage der Marktbedingungen zu ergreifen. Dieser Artikel knüpft an frühere Artikel der Reihe an.
Trianguläre Arbitrage mit Vorhersagen Trianguläre Arbitrage mit Vorhersagen
Dieser Artikel vereinfacht die Dreiecksarbitrage und zeigt Ihnen, wie Sie mit Hilfe von Prognosen und spezieller Software intelligenter mit Währungen handeln können, selbst wenn Sie neu auf dem Markt sind. Sind Sie bereit, mit Expertise zu handeln?
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 18): Neuronale Architektursuche mit Eigenvektoren MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 18): Neuronale Architektursuche mit Eigenvektoren
Die Suche nach neuronaler Architektur, ein automatischer Ansatz zur Bestimmung der idealen Einstellungen für neuronale Netze, kann bei vielen Optionen und großen Testdatensätzen von Vorteil sein. Wir untersuchen, wie dieser Prozess bei gepaarten Eigenvektoren noch effizienter gestaltet werden kann.