English Русский 日本語
preview
Risikobalance beim gleichzeitigen Handel von mehreren Handelsinstrumenten

Risikobalance beim gleichzeitigen Handel von mehreren Handelsinstrumenten

MetaTrader 5Beispiele | 20 Juni 2024, 10:11
88 0
Aleksandr Seredin
Aleksandr Seredin

Dieser Artikel befasst sich mit dem Thema des Risikoausgleichs beim gleichzeitigen Handel mit mehreren Instrumenten im Tagesverlauf. Der Zweck dieses Artikels ist es, den Nutzer in die Lage zu versetzen, einen Code für Abwägungsinstrumente von Grund auf zu schreiben, und erfahrene Nutzer mit anderen, vielleicht bisher ungenutzten Implementierungen alter Ideen bekannt zu machen. Zu diesem Zweck werden wir uns mit der Definition des Risikokonzepts befassen, Kriterien für die Optimierung auswählen, uns auf die technischen Aspekte der Implementierung unserer Lösung konzentrieren, den Satz von Standard-Terminal-Funktionen für eine solche Implementierung analysieren und auch andere Möglichkeiten zur Integration dieses Algorithmus in Ihre Software-Infrastruktur ansprechen.


Kriterien für den Ausgleich von Handelsinstrumenten nach Risiko

Beim gleichzeitigen Handel mit mehreren Finanzinstrumenten werden wir zwei Hauptfaktoren als Risikoausgleichskriterien berücksichtigen.

  • Der Tick-Preis des Symbols
  • Die durchschnittliche tägliche Volatilität des Symbols

Der Tick-Preis ist ein Wert in der Währung der minimalen Preisänderung für ein Symbol mit einem Standard-Symbol-Lot. Wir berücksichtigen dieses Kriterium, weil der Tick-Wert bei verschiedenen Instrumenten stark variieren kann. Zum Beispiel von 1,27042 für EURGBP auf 0,61374 für AUDNZD.

Die durchschnittliche tägliche Volatilität ist die charakteristische Veränderung des Symbolpreises an einem Tag. Dieser Wert ist bei verschiedenen Symbolen weniger konstant als das zuvor gewählte Kriterium und kann sich im Laufe der Zeit je nach Marktphase ändern. So bewegt sich beispielsweise der EURGBP im Durchschnitt um 336 Punkte, während der CHFJPY am selben Tag um 1271 Punkte steigen kann, was fast das Vierfache ist. Die hier präsentierten Daten charakterisieren die „üblichen“ und „wahrscheinlichsten“ Werte der Preisvolatilität, ohne eine ungewöhnlich hohe Volatilität des Symbols in bestimmten Momenten zu berücksichtigen, wenn der Preis beginnt, sich sehr stark in eine Richtung zu bewegen, ohne dass es zu einem Rollback kommt. Nachfolgend finden Sie ein Beispiel für eine solche Bewegung beim USDJPY.

Abbildung 1. Erhöhte Symbolvolatilität auf dem D1-Chart

Abbildung 1. Erhöhte Symbolvolatilität im D1-Chart

Dieses Verhalten kann zu sehr ernsten Risiken für die Einlage führen, die in dem Artikel „Wie man die Risiken des Händlers reduziert“ ausführlich beschrieben werden. In diesem Artikel stellen wir die These auf, dass es unmöglich ist, sich gegen ein solches Risiko durch Ausgleichsinstrumente zu schützen. Dies ist eine völlig andere Risikokategorie. Durch den Ausgleich können wir die Einlage vor dem Risiko schützen, dass der Markt im Rahmen der durchschnittlichen Volatilität gegen unsere Position läuft. Wenn Sie Ihre Fonds vor abnormalen Bewegungen oder „schwarzen Schwänen“ schützen wollen, sollten Sie die folgenden Grundsätze beachten: 

  • Gehen Sie kein hohes prozentuales Risiko für ein einzelnes Symbol ein, 
  • streben Sie nicht danach, ständig in einer offenen Position zu sein, 
  • halten Sie nicht alle verwalteten Gelder auf einem einzigen Konto bei einem einzigen Broker, 
  • handeln Sie nicht dieselben Eröffnungen auf verschiedenen Konten und bei verschiedenen Brokern gleichzeitig. 

Wenn Sie diese Grundsätze befolgen, können Sie Verluste minimieren, wenn der Preis des Symbols gegen Sie läuft, während Sie eine offene Position halten. Kommen wir nun auf die mit der Standardvolatilität verbundenen Risiken zurück.

Die gleichzeitige Berücksichtigung dieser beiden Faktoren ermöglicht es Ihnen, die Risiken auszugleichen und die erwarteten Gewinne für jedes Währungspaar zu normalisieren, während Sie gleichzeitig handeln, ohne die Risiken eines einzelnen Instruments zu übergewichten. Dieser Ansatz wird in der Folge homogenere Handelsstatistiken für die weitere Analyse der Transaktionshistorie liefern und den Fehler reduzieren, wenn der Strategieoptimierer mit diesen Daten arbeitet, und dementsprechend die Standardabweichung der Stichprobe von den Durchschnittsdaten verringern. Um genauer zu verstehen, wie sich der Wert der Standardabweichung auf die Qualität der Analyse einer Menge auswirkt, können Sie den Artikel „Mathematik im Handel: Wie schätzt man Handelsergebnisse ein“ lesen. Kommen wir nun zur Auswahl eines Containers für die Speicherung von Daten.


Auswahl von Containern für die Datenspeicherung 

Bei der Auswahl von Containern für die Speicherung von Daten in unserem Projekt werden wir die folgenden Faktoren berücksichtigen:

  • Container-Leistung,
  • Speicherbedarf für seine Initialisierung,
  • Verfügbarkeit integrierter Funktionen für die Datenanalyse,
  • einfache Initialisierung über die Nutzeroberfläche.

Die häufigsten Kriterien bei der Auswahl von Containern für die Datenspeicherung sind die Leistung des Containers und der Bedarf an Computerspeicher für die Speicherung der Daten. Unterschiedliche Speichertypen können in der Regel eine bessere Leistung bei der Datenverarbeitung oder einen Zugewinn an Speicherplatz bieten.

Zur Überprüfung deklarieren wir ein einfaches Array und einen speziellen Container vom Typ Vektor und initialisieren sie vorläufig mit dem Datentyp double und identischen Werten.

   double arr[] = {1.5, 2.3};
   vector<double> vect = {1.5, 2.3};
Verwenden Sie die Operation sizeof und bestimmen Sie die Speichergröße, die den oben genannten Typen entspricht, in der Kompilierungsphase.
Print(sizeof(arr));
Print(sizeof(vect));

Daraus ergeben sich 16 und 128 Bytes. Dieser Unterschied im Speicherbedarf für den Vektordatentyp wird durch das Vorhandensein integrierter Funktionen bestimmt, einschließlich zusätzlicher redundanter Speicherzuweisung, um eine bessere Leistung zu gewährleisten.

Auf dieser Grundlage werden wir einen einfachen Speichertyp wie ein Array verwenden, wenn wir unsere Aufgaben für Container betrachten, die nur die Speicherung zuvor ausgewählter Daten erfordern. Für homogene Datentypen, die wir später in unserem Algorithmus verarbeiten werden, wäre es ratsam, den speziellen Datentyp Vektor zu verwenden. Die Verwendung dieses Typs spart auch Entwicklungszeit, da keine nutzerdefinierten Funktionen für Standardoperationen geschrieben werden müssen, die bereits in für Vektoren vorliegen.

Die für die Berechnung erforderliche Datenspeicherung sieht dann wie folgt aus.
   string symbols[];       // symbols used for balancing

   double tick_val[],      // symbol tick price
          atr[],           // symbol volatility
          volume[],        // calculated position volume for symbols taking into account balancing 
          point[];         // value of one price change point 

   vector<double> risk_contract; // risk amount for a standard contract

Lassen Sie uns nun zu den Optionen für die Implementierung von Lösungen für die Eingabe von Daten für ausgeglichene Symbole übergehen.


Auswahl einer Methode zur Symboleingabe

Bei der Auswahl der Dateneingabemethoden im MetaTrader 5 gibt es viele Lösungen. Im Allgemeinen werden sie in Lösungen unterteilt, die darauf abzielen, direkt mit dem Nutzer über ein Standard-Terminal-Dialogfeld zu interagieren, oder mit anderen Anwendungen über auf der Festplatte gespeicherte Dateien oder Ferninteraktionsschnittstellen zu interagieren.

Angesichts der Notwendigkeit, viele im Terminal gespeicherte Symbole im String-Format manuell einzugeben, können wir auch die folgenden möglichen Optionen für die Implementierung der Dateneingabe in unserem Skript berücksichtigen:

  1. Lesen von Daten aus einer Tabellendatei *.csv.
  2. Lesen von Daten aus einer Binärdatei *.binary.
  3. Lesen von Daten aus einer Datenbankdatei *.sqlite.
  4. Verwendung von Methoden Dritter für die Interaktion des Terminals mit entfernten Datenbanken.
  5. Verwendung von Web-Api-Lösungen.
  6. Standard für die terminale Verwendung einer oder mehrerer Variablen des entsprechenden Typs mit einem Speicherklassenmodifikator des Eingabetyps.

Bevor wir uns in unserem Projekt für eine Dateneingabemethode entscheiden, werden wir kurz die Vor- und Nachteile der einzelnen Optionen für die Durchführung dieser Aufgabe betrachten. Bei der unter Punkt 1 beschriebenen Methode kann das Lesen von Daten aus einer Tabellendatei des Typs *.csv über die Standardterminalfunktionen für die Handhabung von Dateien erfolgen. Im Allgemeinen könnte der Code wie folgt aussehen:

   string file_name = "Inputs.csv";                         // file name

   int handle=FileOpen(file_name,FILE_CSV|FILE_READ,";");   // attempt to find and open the file

   if(handle!=INVALID_HANDLE)                               // if the file is found, then
     {
      while(FileIsEnding(handle)==false)                    // start reading the file
        {
         string str_follow = FileReadString(handle);        // reading
        
        // here we implement filling the container depending on its type
        }
     }

Weitere Möglichkeiten, die Funktionalität des Arbeitens mit Dateien zu implementieren, sind in der Terminaldokumentation hinreichend ausführlich beschrieben. Bei dieser Option muss der Nutzer lediglich eine Eingabeparameterdatei mit einer Tabellenkalkulationsanwendung eines Drittanbieters, wie MS Excel oder OpenOffice, erstellen. Sogar Windows Standard-Notepad ist dafür geeignet.

Im zweiten Punkt ist die Standardterminalfunktion FileLoad() für die Verwendung von *.bin-Dateien geeignet. Um sie zu verwenden, müssen Sie im Voraus wissen, welche Datenstruktur die Anwendung beim Speichern dieser Datei verwendet hat, damit Sie Daten aus einer Datei mit dieser Erweiterung lesen können. Die Umsetzung einer solchen Idee könnte folgendermaßen aussehen.

   struct InputsData                   // take the structure, according to which the binary file was created 
     {
      int                  symbol_id;  // id of a balanced symbol
      ENUM_POSITION_TYPE   type;       // position type
     };

   InputsData inputsData[];            // storage of inputs

   string  filename="Inputs.bin";      // file name

   ArrayFree(inputsData);              // array released

   long count=FileLoad(filename,inputsData,FILE_COMMON); // load file

Der größte Nachteil dieses Ansatzes ist, dass die Funktion FileLoad() nicht mit Datenstrukturen arbeitet, die Objektdatentypen enthalten. Dementsprechend funktioniert es nicht mit einer Struktur, wenn diese den Datentyp string enthält. In diesem Fall müssen Sie zusätzlich nutzerdefinierte „Container-Dictionaries“ verwenden, um die ID der Zeichen im Integer-Wert in den entsprechenden String Datentyp zu konvertieren oder eine zusätzliche Anfrage an die entsprechende Datenbank zu stellen. Im Allgemeinen wird diese Methode für unsere Implementierung nicht die erfolgreichste sein, da sie zu kompliziert ist, um relativ einfache Operationen durchzuführen.

Der dritte Punkt empfiehlt insbesondere die Verwendung der in das Terminal integrierten Funktionen für die Arbeit mit einer Datenbank mit .sqlite-Dateien. Dies ist eine integrierte Terminaloption für die Arbeit mit einer relationalen Datenbank, die auf der Interaktion mit einer auf der Festplatte gespeicherten Datei beruht.

   string filename="Inputs.sqlite"; // file name with inputs prepared in advance

   int db=DatabaseOpen(filename, DATABASE_OPEN_READWRITE |
                       DATABASE_OPEN_CREATE | DATABASE_OPEN_COMMON); // open the database

   if(db!=INVALID_HANDLE)                                            // if opened
     {
      // implement queries to the database using the DatabaseExecute() function
      // the query structure will depend on the structure of the database tables
     }

Bei der Umsetzung dieses Ansatzes ist es wichtig, zunächst die Struktur der Datenbanktabellen aufzubauen. Die Struktur der Abfragen zur Ermittlung der erforderlichen Daten hängt davon ab. Der Hauptvorteil dieses Konzepts ist die Möglichkeit der relationalen Datenspeicherung, bei der die Kennungen der Werkzeuge in Tabellen im Ganzzahlformat und nicht im Stringformat gespeichert werden, was einen erheblichen Gewinn bei der Optimierung des Speicherplatzes auf dem Computer darstellt. Es ist auch erwähnenswert, dass diese Version der Datenbank unter bestimmten Bedingungen sehr produktiv sein kann. Weitere Einzelheiten finden Sie in dem Artikel „SQLite: Native Handhabung von SQL-Datenbanken in MQL5“.

Im vierten Absatz wird die Möglichkeit beschrieben, entfernte Datenbanken zu verwenden, was die Verwendung zusätzlicher Bibliotheken von Drittanbietern erfordert. Diese Option ist arbeitsintensiver als die im vorigen Absatz beschriebene Methode, da sie nicht vollständig über die Standardfunktionalität des Terminals umgesetzt werden kann. Es gibt viele Veröffentlichungen zu diesem Thema, darunter auch eine gute Möglichkeit zur Implementierung der Interaktion des Terminals mit der MySQL-Datenbank, die im Artikel „Wie man auf die MySQL-Datenbank von MQL5 (MQL4) aus zugreift“ beschrieben wird.

Die im fünften Absatz beschriebene Verwendung von Web-Api-Anfragen zum Erhalt von Eingaben kann wahrscheinlich als die universellste und plattformübergreifende Lösung für unsere Aufgabe angesehen werden. Die Funktionalität ist über die vordefinierte Funktion WebRequest() in das Terminal integriert und eignet sich aufgrund ihrer Vielseitigkeit perfekt, wenn Sie bereits über eine Infrastruktur für Back-End- und Front-End-Anwendungen verfügen. Andernfalls kann es sehr zeit- und ressourcenaufwändig sein, diese Anwendungen von Grund auf neu zu entwickeln, auch wenn diese Lösungen in vielen modernen Programmiersprachen und Interpretern geschrieben werden können.

In der aktuellen Implementierung werden wir Variablen mit dem Eingabetyp Speicherklassenmodifikator des Datentyps string ab Punkt sechs verwenden, einfach weil diese Option alle erforderlichen Funktionen bereitstellen kann, ohne dass eine zusätzliche Entwicklung von nutzerdefinierten Programmen erforderlich ist. Natürlich werden wir nicht viele Variablen für jedes Symbol deklarieren, da wir nicht im Voraus wissen können, wie viele Symbole wir ausgleichen werden, und dafür gibt es eine elegantere und flexiblere Lösung. Wir arbeiten mit einer Zeile, die alle Werte enthält und in der die Daten weiter getrennt werden. Dazu deklarieren wir eine Variable auf globaler Ebene in der folgenden Form:

input string input_symbols = "EURCHFz USDJPYz";

Stellen Sie sicher, dass Sie ihn mit dem Standardwert initialisieren, damit ein Nutzer, der kein Entwickler ist, genau weiß, wie er die Symbole eingeben muss, damit die Anwendung korrekt funktioniert. In diesem Fall wird aus Gründen der Nutzerfreundlichkeit ein normales Leerzeichen als Zeilentrennzeichen verwendet.

Wir werden die Daten aus dieser Variablen abrufen und unser Array symbols[] mit einer vordefinierten Terminalfunktion StringSplit() in folgender Form füllen:

   string symbols[];                         // storage of user-entered symbols
   
   StringSplit(input_symbols,' ',symbols);   // split the string into the symbols we need

   int size = ArraySize(symbols);            // remember the size of the resulting array right away

Bei der Ausführung der Funktion StringSplit() wird das ihr per Referenz übergebene Array symbols[] mit Daten gefüllt, die aus der Zeichenkette mit dem Trennzeichen („ “) in Form eines Leerzeichens extrahiert wurden.

Nachdem wir nun ein gefülltes Array mit den Werten der Symbolnamen für den Risikoausgleich haben, können wir nun die notwendigen Daten zu den ausgewählten Terminalsymbolen für weitere Berechnungen abfragen.


Abrufen der erforderlichen Symboldaten durch vordefinierte Terminalfunktionen

Für die Berechnungen müssen wir den Wert der minimalen Preisänderung jedes Instruments kennen und wissen, wie viel uns diese Änderung in der Einzahlungswährung kosten wird. Wir können dies über die vordefinierte Terminalfunktion SymbolInfoDouble() mit der erforderlichen Enumeration ENUM_SYMBOL_INFO_DOUBLE als einem der Funktionsparameter implementieren. Die Implementierung der Daten-Enumeration wird über die bekannte „for“-Schleife wie folgt gestaltet:

for(int i=0; i<size; i++)  // loop through previously entered symbols
     {
      point[i] = SymbolInfoDouble(symbols[i],SYMBOL_POINT);                	// requested the minimum price change size (tick)
      tick_val[i] = SymbolInfoDouble(symbols[i],SYMBOL_TRADE_TICK_VALUE_LOSS);  // request tick price in currency
     }

Beachten Sie, dass die Enumeration ENUM_SYMBOL_INFO_DOUBLE nicht nur den Wert SYMBOL_TRADE_TICK_VALUE_LOSS für die Abfrage des Tick-Preises in Währung enthält, sondern auch den Wert SYMBOL_TRADE_TICK_VALUE und den ihm entsprechenden Wert SYMBOL_TRADE_TICK_VALUE_PROFIT. Eigentlich kann eine Anfrage für jeden der angegebenen Werte in unserer Berechnung verwendet werden, da der Unterschied zwischen diesen Werten nicht sehr groß ist. Die Werte der angegebenen Argumente für das Kreuz AUDNZD sind beispielsweise in der folgenden Tabelle aufgeführt:

Funktionsparameter Von der Funktion zurückgegebener Tick-Preis
SYMBOL_TRADE_TICK_VALUE 0.6062700000000001
SYMBOL_TRADE_TICK_VALUE_LOSS 0.6066200000000002
SYMBOL_TRADE_TICK_VALUE_PROFIT 0.6062700000000001

Tabelle 1. Unterschied zwischen den Tick-Preisen, die für verschiedene Parameter der Funktion SymbolInfoDouble() für AUDNZD zurückgegeben werden

Obwohl es in unserem Fall am besten wäre, den Parameter SYMBOL_TRADE_TICK_VALUE_LOSS zu verwenden, glaube ich, dass wir jede der hier vorgeschlagenen Optionen nutzen können. Wie Richard Hamming bereits 1962 feststellte: 

„Der Zweck der Datenverarbeitung ist Erkenntnis, nicht Zahlen“.

Gehen wir nun dazu über, die erforderlichen Daten zur Volatilität anzufordern.


Abruf der erforderlichen Volatilitätsdaten über die standardmäßige nutzerdefinierte Klasse CiATR

In den vorangegangenen Kapiteln haben wir bereits das Konzept der Volatilität als Indikator für eine typische Kursänderung eines Symbols über einen bestimmten Zeitraum, in unserem Fall während eines Handelstages, erwähnt. Trotz aller Offensichtlichkeit dieses Indikators können die Methoden zu seiner Berechnung sehr unterschiedlich sein, vor allem aufgrund der folgenden Aspekte:

  • Berücksichtigung nicht geschlossener Lücken auf Tages-Charts.
  • Verwendung einer Mittelwertbildung und eines Zeitraums, auf den sie angewendet wird.
  • Ignorieren von Balke.
  • Berechnung auf der Grundlage des Höchst-/Tiefstwerts des Tagesbalkens oder nur auf der Grundlage von Eröffnungs-/Schließ-Kurse.
  • Oder wir handeln in der Regel mit Renko-Balken und die Mittelungszeit ist für uns überhaupt nicht von Bedeutung.

Die Berücksichtigung von Lücken bei der Berechnung der Volatilität wird bei Arbeiten an den Börsen sehr häufig verwendet, im Gegensatz zum Devisenmarkt, weil Marktlücken, die während des Tages nicht geschlossen werden, auf dem Devisenmarkt sehr selten sind und sich die endgültige Berechnung der durchschnittlichen täglichen Volatilität nicht ändert. Die Berechnung unterscheidet sich hier nur dadurch, dass wir den Maximalwert der beiden Werte nehmen. Die erste ist die Differenz zwischen dem Höchst- und Tiefstwert jedes Balkens, die zweite ist die Differenz zwischen dem Höchst- und Tiefstwert des aktuellen Balkens und dem Schlusskurs des vorherigen Balkens. Wir können dazu die integrierte Funktion MathMax() verwenden.

Bei der Mittelwertbildung der ermittelten Werte ist zu berücksichtigen, dass dieser Indikator umso langsamer auf Veränderungen der Marktvolatilität reagiert, je länger der Mittelwertbildungszeitraum ist. In der Regel wird ein Zeitraum von 3 bis 5 Tagen für die durchschnittliche tägliche Volatilität auf dem Devisenmarkt verwendet. Außerdem ist es ratsam, abnormale Marktbewegungen bei der Berechnung des Volatilitätsindikators auszuschließen. Dies kann automatisch anhand des Medianwerts der Stichprobe erfolgen. Dazu können wir die integrierte Methode Median() verwenden, die für eine Instanz der Vektorklasse aufgerufen wird.

Viele Händler ziehen es vor, die Mittelwertbildung vorzunehmen, indem sie die Volatilität nur über die Eröffnungs- und Schlusskurse berücksichtigen, ohne die Dochte der Kerzen mit einzubeziehen. Diese Methode wird nicht empfohlen, da sie einen Wert ergeben kann, der weit unter der tatsächlichen Marktvolatilität liegt. Bei der Verwendung von Renko-Balken ändert sich die Logik sehr stark, und hier kommt die Aggregation der Volatilität nicht aus dem Handelszeitraum, sondern der Handelszeitraum wird durch die Volatilität bestimmt. Daher wird dieser Ansatz in unserem Artikel nicht verwendet.

In unserer Implementierung verwenden wir die Volatilitätsabfrage durch den ATR-Terminalindikator unter Verwendung der standardmäßigen nutzerdefinierten CiATR-Klasse, die in der Terminalbibliothek im Open-Source-Format gespeichert ist. Wir werden den schnellen Wert 3 verwenden, um den Indikator zu mitteln. Im Allgemeinen sieht der Code der Volatilitätsanforderung wie folgt aus. Auf der globalen Ebene deklarieren wir den Namen einer Klassenvariablen mit einem Aufruf des Standardkonstruktors.

CiATR indAtr[];

Hier verwenden wir das Array von Indikatoren, um Daten gleichzeitig zu speichern und nicht, um einen bestehenden Indikator zu überladen, hauptsächlich aus Gründen der Bequemlichkeit und der Möglichkeit, die Funktionalität des Codes weiter zu erweitern. Als Nächstes fügen wir den folgenden Code zu unserer Symbol-Iterationsschleife hinzu, in der wir bereits Daten zu Symbolen abfragen, um Indikatorwerte abzufragen.

indAtr[i].Create(symbols[i],PERIOD_D1, atr_period);   // create symbol and period
   
indAtr[i].Refresh();          // be sure to update data

atr[i] = indAtr[i].Main(1);   // request data on closed bars on D1

Nachdem Sie nun alle notwendigen Ausgangsdaten für die Berechnung erhalten haben, können Sie direkt mit den Berechnungen fortfahren.


Zwei Optionen für die Logik der Risikoberechnung für verschiedene Methoden zum Beenden einer Position

Bei der Ausarbeitung der Logik für den Risikoausgleich beim gleichzeitigen Handel mit mehreren Instrumenten müssen die folgenden wichtigen Punkte berücksichtigt werden. Wie Ausstiege aus ausgeglichenen Positionen in unserem Handelssystem vorgesehen sind und wie wir die Korrelation der Symbole, die wir ausgleichen werden, berücksichtigen. Selbst auf den ersten Blick liefert ein klares Kriterium für die Korrelation von Kreuzen auf den Devisenmärkten nicht immer ein garantiertes Korrelationsniveau im betrachteten Zeitraum und kann oft als separates Symbol betrachtet werden. Wenn wir uns zum Beispiel zu Beginn des Handelstages für eine Reihe von Symbolen entschieden haben, mit denen wir heute handeln werden, und für die Richtung, in die wir einsteigen wollen, sollten wir Folgendes im Voraus wissen. Werden wir alle Positionen am Ende des Handelstages schließen oder nicht? Werden wir den Ausstieg aus Positionen im Laufe des Tages für jede einzelne Position in Betracht ziehen, oder werden wir einfach alle Positionen im Laufe der Zeit schließen? Es kann kein allgemeingültiges Rezept für alle geben, denn jeder Händler trifft seine Ein- und Ausstiegsentscheidungen anhand einer Vielzahl von Kriterien auf der Grundlage seiner eigenen Kenntnisse und Erfahrungen.

In dieser Implementierung werden wir eine universelle Berechnung vornehmen, die es uns ermöglicht, das Volumen des Einstiegs in eine Position auf der Grundlage der Tatsache, dass Sie handeln, flexibel zu bestimmen, indem wir einfach die Eingabe-Risikoparameter für das Instrument ändern und Instrumente einschließen/ausschließen, die nach Meinung des Händlers im Moment korreliert sind. Zu diesem Zweck deklarieren wir den Input-Risikoparameter für ein Instrument wie folgt.

input double risk_per_instr = 50;

Um eine universelle Nutzung zu ermöglichen, werden wir auch die gleichzeitige Ausgabe von Bilanzierungsergebnissen bei der Regression des vom Nutzer eingegebenen Risikos auf das jeweilige Handelssymbol und unter Berücksichtigung der Korrelation dieser Symbole ermöglichen. Dies ermöglicht es dem Händler, eine Reihe von unterschiedlichen Positionsvolumina und vor allem die Anteile dieser Instrumente für den gleichzeitigen Handel zu erhalten. Zu diesem Zweck müssen wir zunächst den folgenden Eintrag in unseren Hauptberechnungszyklus aufnehmen.

risk_contract[i] = tick_val[i]*atr[i]/point[i]; // calculate the risk for a standard contract taking into account volatility and point price

Als Nächstes suchen wir außerhalb der festgelegten Schleife das Instrument mit dem höchsten Risikowert in unserem Set, um daraus einen Anteil zu bilden, der das Handelsvolumen im gesamten Set ausgleicht. Die integrierten Funktionen unseres Containers sind genau für diesen Zweck gedacht.

double max_risk = risk_contract.Max();          // call the built-in container method to find the maximum value

Da wir nun das maximale Risiko in unserem Set kennen, arrangieren wir eine weitere Schleife, um das ausgeglichene Volumen für jedes Instrument in unserer Stichprobe zu berechnen, vorausgesetzt, es besteht keine Korrelation zwischen ihnen, und zeigen die Ergebnisse sofort im Journal an.

for(int i=0; i<size; i++)	// loop through the size of our symbol array
     {
      volume[i] = NormalizeDouble((max_risk / risk_contract[i]) * (risk_per_instr / max_risk),calc_digits); // calculate the balanced volume
     }

Print("Separate");		// display the header in the journal preliminarily

for(int i=0; i<size; i++)	// loop through the array again
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i],calc_digits));	// display the resulting volume values
     }

Dies stellte sich als maximales Volumen für den Intraday-Handel mit unseren Positionen heraus, ausgeglichen durch die durchschnittliche tägliche wahrscheinlichste und erwartete Volatilität, unter Berücksichtigung des Risikos, das wir dem Symbol zugewiesen haben.

Als Nächstes berechnen wir das Volumen unserer Positionen unter der Annahme, dass die Symbole im Laufe des Handelstages zu korrelieren beginnen, was das erwartete Risiko für das Symbol im Vergleich zu den von uns eingegebenen Parametern erheblich erhöhen kann. Dazu fügen wir den folgenden Code ein, bei dem wir den resultierenden Wert einfach durch die Anzahl der gehandelten Instrumente dividieren.

Print("Complex");		// display the header in the journal preliminarily
   
for(int i=0; i<size; i++)	// loop through the array
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i]/size,calc_digits));	// calculate the minimum volume for entry
     }

Jetzt haben wir die Ober- und Untergrenzen für die Volumina der risikobasierten Positionen erhalten. Der Händler ist in der Lage, das Volumen der Einträge innerhalb eines bestimmten Bereichs selbständig zu bestimmen. Die Hauptsache ist, dass die in der Berechnung angegebenen Proportionen eingehalten werden. Die Protokollierung der Ergebnisse ist die einfachste, aber nicht die einzige Methode. Sie können auch andere Standardterminalfunktionen verwenden, um dem Nutzer Informationen anzuzeigen. In diesem Fall bietet das Terminal eine sehr umfangreiche Funktionalität, einschließlich der Verwendung von Funktionen wie MessageBox(), Alert(), SendNotification(), SendMail() und vielen anderen. Wir gehen nun zum vollständigen Code des EA in der kompilierten Datei über.


Endgültige Implementierung der Lösung in das Skript 

Infolgedessen wird unser Implementierungscode wie folgt aussehen.

#property strict		

#include <Indicators\Oscilators.mqh>

//---
input string input_symbols = "EURCHFz USDJPYz";
input double risk_per_instr = 50;
input int atr_period = 3;
input int calc_digits = 3;
CiATR indAtr[];


//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  { 
   string symbols[];
   StringSplit(input_symbols,' ',symbols);   

   int size = ArraySize(symbols);
   double tick_val[], atr[], volume[], point[];
   vector<double> risk_contract;

   ArrayResize(tick_val,size);
   ArrayResize(atr,size);
   ArrayResize(volume,size);
   ArrayResize(point,size);
   ArrayResize(indAtr,size);
   risk_contract.Resize(size);

   for(int i=0; i<size; i++)
     {
      indAtr[i].Create(symbols[i],PERIOD_D1, atr_period);
      indAtr[i].Refresh();

      point[i] = SymbolInfoDouble(symbols[i],SYMBOL_POINT);
      tick_val[i] = SymbolInfoDouble(symbols[i],SYMBOL_TRADE_TICK_VALUE);

      atr[i] = indAtr[i].Main(1);
      risk_contract[i] = tick_val[i]*atr[i]/point[i];
     }

   double max_risk = risk_contract.Max();
   Print("Max risk in set\t"+symbols[risk_contract.ArgMax()]+"\t"+DoubleToString(max_risk));

   for(int i=0; i<size; i++)
     {
      volume[i] = NormalizeDouble((max_risk / risk_contract[i]) * (risk_per_instr / max_risk),calc_digits);
     }

   Print("Separate");
   for(int i=0; i<size; i++)
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i],calc_digits));
     }

   Print("Complex");
   for(int i=0; i<size; i++)
     {
      Print(symbols[i]+"\t"+DoubleToString(volume[i]/size,calc_digits));
     }
  }
//+------------------------------------------------------------------+

Nach der Kompilierung erscheint das Eingabefenster. Wir wollen zum Beispiel drei Symbole ausgleichen und das maximale Risiko beträgt 500 USD.


Abbildung 2. Eingaben des Händlers

Abbildung 2. Eingaben des Händlers

Als Ergebnis der Ausführung des Skripts erhalten Sie die folgenden Daten für das Volumen jedes Symbols unter Berücksichtigung der Risikobilanz.

Abbildung 3. Ausgabedaten

Abbildung 3. Ausgabedaten

Hier finden Sie einen voll funktionsfähigen Code zur Berechnung des gleichzeitigen Handelsvolumens mehrerer risikobehafteter Symbole unter Verwendung der einfachsten, aber minimal notwendigen Funktionen des Terminals. Wenn Sie es wünschen, können wir diesen Algorithmus mit den im Artikel genannten zusätzlichen Funktionen sowie mit unseren eigenen Entwicklungen skalieren.


Schlussfolgerung

Wir haben ein voll funktionsfähiges Skript erhalten, das es Händlern, die nicht über den algorithmischen Handel handeln, ermöglicht, das Positionsvolumen beim Intraday-Handel schnell und präzise anzupassen. Ich hoffe, dass auch diejenigen, die algorithmisch handeln, hier neue Ideen finden, um ihre Software-Infrastruktur zu verbessern und möglicherweise ihre aktuellen Handelsergebnisse zu steigern. Danke fürs Lesen und Feedback!

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

Beigefügte Dateien |
RiskBallance.mq5 (2.27 KB)
Statistische Arbitrage mit Vorhersagen Statistische Arbitrage mit Vorhersagen
Wir werden uns mit statistischer Arbitrage beschäftigen, wir werden mit Python nach Korrelations- und Kointegrationssymbolen suchen, wir werden einen Indikator für den Pearson-Koeffizienten erstellen und wir werden einen EA für den Handel mit statistischer Arbitrage mit Vorhersagen erstellen, die mit Python und ONNX-Modellen gemacht werden.
Verwendung von Optimierungsalgorithmen zur Konfiguration von EA-Parametern im laufenden Betrieb Verwendung von Optimierungsalgorithmen zur Konfiguration von EA-Parametern im laufenden Betrieb
Der Artikel behandelt die praktischen Aspekte der Verwendung von Optimierungsalgorithmen, um die besten EA-Parameter im laufenden Betrieb zu finden, sowie die Virtualisierung von Handelsoperationen und EA-Logik. Der Artikel kann als Anleitung für die Implementierung von Optimierungsalgorithmen in einen EA verwendet werden.
Neuronale Netze leicht gemacht (Teil 75): Verbesserung der Leistung von Modellen zur Vorhersage einer Trajektorie Neuronale Netze leicht gemacht (Teil 75): Verbesserung der Leistung von Modellen zur Vorhersage einer Trajektorie
Die Modelle, die wir erstellen, werden immer größer und komplexer. Dies erhöht nicht nur die Kosten für ihr Training, sondern auch für ihren Betrieb. Die Zeit, die für eine Entscheidung benötigt wird, ist jedoch oft entscheidend. In diesem Zusammenhang sollten wir Methoden zur Optimierung der Modellleistung ohne Qualitätseinbußen in Betracht ziehen.
Entwicklung eines Expertenberaters für mehrere Währungen (Teil 3): Überarbeitung der Architektur Entwicklung eines Expertenberaters für mehrere Währungen (Teil 3): Überarbeitung der Architektur
Wir haben bereits einige Fortschritte bei der Entwicklung eines Mehrwährungs-EAs mit mehreren parallel arbeitenden Strategien gemacht. In Anbetracht der gesammelten Erfahrungen sollten wir die Architektur unserer Lösung überprüfen und versuchen, sie zu verbessern, bevor wir zu weit vorpreschen.