English Русский Español 日本語 Português
preview
Entwicklung eines Expert Advisors für mehrere Währungen (Teil 5): Variable Positionsgrößen

Entwicklung eines Expert Advisors für mehrere Währungen (Teil 5): Variable Positionsgrößen

MetaTrader 5Tester | 12 August 2024, 12:01
95 0
Yuriy Bykov
Yuriy Bykov

Einführung

Im vorherigen Teil haben wir die Möglichkeit beschrieben, den Zustand des EA nach einem Neustart wiederherzustellen. Es spielt keine Rolle, was der Grund dafür war - ein Neustart des Terminals, eine Änderung des Zeitrahmens auf dem Chart mit dem EA, das Starten einer neueren Version des EA - in allen Fällen erlaubte die Wiederherstellung des Status dem EA, nicht bei Null anzufangen zu arbeiten und bereits offene Positionen nicht zu verlieren, sondern sie weiter zu bearbeiten.

Die Größe der eröffneten Positionen blieb jedoch für jede Instanz der Strategie während des gesamten Testzeitraums gleich. Ihre Größe wurde bei der EA-Einführung festgelegt. Wenn sich der Kontostand des Handelskontos durch den Einsatz des EA erhöht, kann die Positionsgröße erhöht werden, ohne das Risiko zu erhöhen. Es wäre sinnvoll, sich dies zunutze zu machen. Beginnen wir also damit, die Verwendung variabler Positionsgrößen zu implementieren.


Konzepte

Zunächst einmal müssen wir uns auf die Konzepte einigen, die auf einem gemeinsamen Ziel beruhen - der optimalen Zusammenarbeit zwischen mehreren Instanzen von Handelsstrategien.

Feste Strategiegröße (Fixed Lot) — eine bestimmte Größe, die zur Berechnung der Größe aller offenen Positionen in einer Handelsstrategie verwendet wird. Im einfachsten Fall können alle offenen Positionen eine Größe haben, die diesem Wert entspricht. Wenn Sie irgendwelche Tricks anwenden, um die Größe der zweiten und der nachfolgenden geöffneten Positionen in einer Serie zu erhöhen oder zu verringern, kann eine feste Größe die Größe der ersten Position in der Serie festlegen, und die nachfolgenden werden auf der Grundlage dieser Größe und der Anzahl der bereits geöffneten Positionen berechnet. Dies gilt als keine sehr gute Technik.

Normalisierter Strategiesaldo (Fitted Balance) — Anfangssaldo, bei dem der Drawdown während des gesamten Testzeitraums 10% des Anfangssaldos für die gewählte feste Strategiegröße erreicht, aber nicht überschreitet. Warum genau 10 %? Diese Zahl ist noch kein sehr großer Drawdown, was psychologisch akzeptabel erscheint und sich für schnelle, grobe mentale Berechnungen als bequemer erweist. Im Allgemeinen können wir jeden Wert nehmen - 1 %, 5 % oder sogar 50 %. Dies ist nichts anderes als ein Normalisierungsparameter.

Normalisierte Handelsstrategie — eine Handelsstrategie, für die eine feste Strategiegröße und normalisierter Strategiesaldo gewählt wurden. Wenn wir also eine solche Strategie für den ausgewählten Testzeitraum starten, sollten wir einen maximalen Drawdown-Wert von etwa 10 % des normalisierten Strategiesaldos erhalten.

Wenn wir eine Handelsstrategie haben, können wir sie in eine normalisierte Handelsstrategie umwandeln, indem wir die folgenden Aktionen durchführen:

  • Wählen Sie eine feste Strategiegröße, z. B. 0,01.
  • Wählen Sie den Testzeitraum (Start- und Enddatum)
  • Starten Sie die Strategie im ausgewählten Testzeitraum mit einem hohen Anfangssaldo und betrachten Sie den Wert des maximalen absoluten Drawdowns nach Kapital (equity).
  • Ermitteln Sie den Wert des normalisierten Strategiesaldos, indem Sie den maximalen absoluten Drawdown mit dem Kapital mit 10 multiplizieren.

Betrachten wir das folgende Beispiel. Nehmen wir an, dass wir einen maximalen absoluten Drawdown von 440 USD für die feste Strategiegröße von 0,01 erhalten. Wenn dieser Wert genau 10 % des Anfangssaldos betragen soll, können wir 440 USD durch 0,10 teilen oder mit 10 multiplizieren (was dasselbe ist):

440 USD / 0,10 = 440 USD * 10 = 4400 USD

Wir setzen diese beiden Werte (0,01 und 4400) in den Parametern für die Erstellung einer Handelsstrategie-Instanz und erhalten eine normalisierte Handelsstrategie.

Für eine normalisierte Handelsstrategie können wir nun die Größe der offenen Positionen für jeden beliebigen Saldo berechnen, wobei der maximale relative Drawdown durch das Kapital gleich 10 % bleibt. Dazu genügt es, die Größe der eröffneten Positionen proportional zum Verhältnis zwischen dem aktuellen Gesamtsaldo (Total Balance) und dem normalisierten Saldo (Fitted Balance) zu verändern.

CurrentLot = FixedLot * (TotalBalance / FittedBalance)

Bei den im obigen Beispiel verwendeten Werten 0,01 und 4400 und einem Saldowert von 10.000 USD sollte der Umfang der offenen Positionen auf der Grundlage des Basiswertes berechnet werden:

AktuellerWert = 0,01 * (10.000 / 4400) = 0,0227

Es wird nicht möglich sein, genau diese Größe zu öffnen. Wir müssen auf 0,02 aufrunden, sodass der Drawdown in diesem Fall etwas weniger als 10 % betragen kann. Wenn wir aufrunden (auf 0,03), dann kann der Drawdown etwas mehr als 10 % betragen. Je höher der Saldo ist, desto weniger fällt ein Rundungsfehler ins Gewicht.

Wenn wir das Konzept einer festen Positionsgröße für eine Strategie eingeführt haben, können wir alle Optionen zur Verwaltung der Größe der Strategiepositionen der Strategie selbst überlassen. Daher müssen wir nur drei mögliche Optionen für eine Geldmanagementstrategie auf der Ebene des EA implementieren, der verschiedene Instanzen von Handelsstrategien kombiniert:

  • Feste Größe oder das Fehlen einer Geldverwaltungsstrategie. Die in der Strategie festgelegte Größe wird unabhängig vom Stand des Handelskontos angewendet. Die Strategie wird beim Testen einer separaten Instanz der Strategie verwendet, um das normalisierte Strategiesaldo zu bestimmen.

  • Konstante Größe für einen bestimmten festen Saldo. Zu Beginn wird die Größe, die proportional zum festen Saldo der Strategie ist, auf der Grundlage des normalisierten Saldos der Strategie und der festen Größe berechnet. Diese Größe wird während des gesamten Testzeitraums verwendet. Mit dieser Strategie wird die Gleichmäßigkeit (Linearität) der Wachstumskurve des Geldes während des gesamten Testzeitraums überprüft, wobei der angegebene maximale absolute Drawdown eingehalten wird.

  • Variable Größe für den aktuellen Saldo. Für jede Positionseröffnung wird die Größe im Verhältnis zum aktuellen Kontostand auf der Grundlage des strategienormalisierten Saldos und der festen Positionsgröße bestimmt. Die Strategie wird in der Praxis eingesetzt, um den Erwartungswert des maximalen relativen Drawdowns zu ermitteln.

Im Folgenden finden Sie Beispiele für die Verwendung dieser drei Optionen. Nehmen wir den EA mit einer Kopie der Strategie, legen wir ein großes Startguthaben von 100.000 USD fest und starten wir den Test für den Zeitraum 2018-2022 mit der festen Größe der offenen Positionen von 0,01 Lots. Wir erhalten die folgenden Ergebnisse:

Abb. 1. Ergebnisse mit der festgelegten Größe und dem Restbetrag von 100.000 USD


Wie wir sehen können, gab es während dieses Testzeitraums einen maximalen absoluten Drawdown von etwa 153 USD, was ungefähr 0,15 % des Kontostands entspricht. Genauer gesagt, ist es für uns richtiger, den relativen Drawdown in Bezug auf den anfänglichen Kontostand zu bewerten. Da die Differenz zwischen Anfangs- und Endsaldo jedoch gering ist (etwa 1 % des Anfangssaldos), entspricht ein Drawdown von 0,15 % mit guter Genauigkeit dem absoluten Wert von 150 USD, egal zu welchem Zeitpunkt im Testzeitraum er auftritt.

Berechnen wir, wie hoch das Anfangssaldo sein kann, damit der maximale absolute Drawdown 10 % des Anfangssaldos beträgt:

FittedBalance = MaxDrawdown / 10% = 153 / 0.10 = 153 * 10 = USD 1530

Lassen Sie uns unsere Berechnungen überprüfen:

Abb. 2. Ergebnisse mit der festen Größe und dem Saldo von 1530 USD


Es zeigt sich, dass der absolute Drawdown des Kapitals den gleichen Wert von 153 USD erreichte, der relative Drawdown jedoch nicht 10 %, sondern nur 7,2 % betrug. Das ist normal, denn es bedeutet nur, dass der größte Drawdown stattfand, als der Kontostand bereits etwas höher als zu Beginn war und der Wert von 153 USD bereits weniger als 10 % des aktuellen Saldos betrug.

Prüfen wir nun die zweite Option - eine konstante Größe für einen bestimmten festen Saldo. Legen Sie ein hohes Startguthaben von 100.000 USD fest und gestatten Sie, dass nur ein Zehntel davon, d.h. 10.000 USD, verwendet wird. Dies ist der Wert des CurrentBalance (aktueller Saldo), der während des gesamten Testzeitraums gleich bleibt. Unter diesen Bedingungen sollte die Größe der eröffneten Positionen sein:

CurrentBalance = TotalBalance * 0.1 = 10,000

CurrentLot = FixedLot *  (CurrentBalance / FittedBalance) = 0.01 * (10,000 / 1530) = 0.0653

Während des Betriebs wird dieser Wert auf ein Vielfaches des Losänderungsschritts gerundet. Wir erhalten die folgenden Ergebnisse:

Abb. 3. Ergebnisse mit der festen Größe für den festen Saldo von 10.000 USD von den verfügbaren 100.000 USD


Wie Sie sehen können, betrug der absolute Drawdown 1016 USD, was mit ausreichender Genauigkeit 10 % der 10.000 USD entspricht, die dieser Strategie zugewiesen wurden. Im Verhältnis zum Gesamtsaldo macht dies jedoch nur 1 % aus.

Betrachten wir schließlich die dritte Option - variable Größe für den aktuellen Saldo. Legen Sie das Anfangssaldo auf 10.000 USD fest und lassen Sie es in vollem Umfang nutzen. Das geschieht folgendermaßen:


Abb. 4. Ergebnisse mit der variablen Größe für den aktuellen Saldo

Hier sehen wir, dass der maximale absolute Drawdown bereits 10 % des Anfangssaldos übersteigt, der relative Drawdown jedoch weiterhin innerhalb der akzeptablen 10 % bleibt. Wir haben eine normalisierte Handelsstrategie erhalten, für die Fitted Balance = 1530 ist, und können nun leicht die Größe der offenen Positionen berechnen, um einen bestimmten Drawdown von 10 % zu gewährleisten.


Berechnung der Positionsgröße

Bei der Betrachtung der folgenden Geldverwaltungsoptionen lassen sich folgende Überlegungen anstellen:

  • Wenn es sich um eine Kopie der Strategie handelt, könnten dann Optionen mit einem variablen Los sinnvoll sein? Sieht aus, als könnten sie das nicht. Wir brauchen nur die erste Option zu verwenden. Die zweite und dritte können wir ein paar Mal verwenden, um die Leistung zu demonstrieren, aber dann brauchen wir sie nicht mehr.

  • Wenn wir mit einem EA arbeiten, der bereits mehrere Instanzen von Handelsstrategien kombiniert, kann dann der Handel mit einem festen Los sinnvoll sein? Sieht so aus, als ob es nicht geht. In diesem Fall kann die zweite Option in der Testphase nützlich sein, und die dritte Option wird die Hauptoption sein.

Daraus lässt sich folgende Schlussfolgerung ziehen: Virtuelle Aufträge haben immer eine feste Größe, die sich aus dem Parameter feste Strategiegröße ergibt. Bei den hier besprochenen Strategien reicht es aus, die minimale Losgröße als feste Größe zu verwenden, das bei den meisten Instrumenten gleich 0,01 ist.

Basierend auf dem Workflow stellt sich heraus, dass das Empfängerobjekt oder die symbolischen Empfänger in die reale Position offene Größe neu berechnet werden müssen. Dazu müssen sie den Wert des normalisierten Saldos, der einen Drawdown von 10 % dieses Saldos gewährleistet, von der Strategie oder genauer gesagt von einer virtuellen Order erhalten.

Was aber, wenn wir einen kleineren oder größeren Drawdown verwenden wollen? Dazu reicht es aus, die Größe der eröffneten Positionen auf die eine oder andere Weise zu ändern, je nachdem, wie oft wir den erwarteten maximalen Drawdown gegenüber dem Wert von 10 % ändern wollen.

Eine dieser Methoden ist die ausdrückliche Einführung eines Gewichtungsmultiplikators, der angibt, welcher Teil des aktuellen Kontostands von der EA verwendet werden kann.

Der dedizierte Saldo (Current Balance) ist ein Teil des gesamten Kontosaldos, der diesem EA für den Handel zugewiesen wurde.

Saldomultiplikator (Depo-Teil) ist das Verhältnis zwischen dem zugewiesenen Strategiesaldo und dem Gesamtkontosaldo.

DepoPart =  CurrentBalance / TotalBalance

Dann kann die Größe der Ausgangsposition wie folgt berechnet werden:

CurrentLot = FixedLot * (CurrentBalance / FittedBalance)

CurrentLot =  FixedLot * (DepoPart * TotalBalance / FittedBalance)

Hier können wir eine wichtige Bemerkung machen, die für unsere Umsetzung sehr nützlich sein wird. Wenn wir den normalisierten Saldo nach der Erstellung einer Strategieinstanz neu berechnen, wird der Gesamtsaldo in der Gleichung zur Berechnung der Positionsgröße anstelle des aktuellen Strategiesaldos verwendet:

FittedBalance = FittedBalance / DepoPart

CurrentLot =  FixedLot * (TotalBalance / FittedBalance)

Wir werden die Neuberechnung des normalisierten Saldos einmal bei der Initialisierung des EA durchführen. Danach brauchen wir den Multiplikator Depo Part nicht mehr. 


Kombinieren mehrerer Strategien

Die vorangegangenen Erörterungen bezogen sich auf den Fall, dass eine Instanz einer Strategie in einem EA verwendet wird. Denken wir nun darüber nach, was zu tun ist, wenn wir mehrere standardisierte Strategien in einem EA kombinieren wollen, der während des gesamten Testzeitraums einen Drawdown von höchstens 10 % (oder einem anderen im Voraus festgelegten Wert) zulässt. Für den Moment betrachten wir den angegebenen Drawdown-Wert von genau 10 %.

Wenn wir uns auf den schlimmsten Fall konzentrieren, der bei der Kombination von Strategien eintreten kann, dann ist dies das gleichzeitige Erreichen des maximalen Drawdowns von 10% durch alle Strategieinstanzen. In diesem Fall müssen wir die Positionsgröße jeder Strategie im Verhältnis zur Anzahl der Instanzen reduzieren. Wenn wir beispielsweise drei Kopien einer Strategie kombinieren, müssen wir die Größe der Positionen um das Dreifache reduzieren. 

Dies kann dadurch erreicht werden, dass der der Strategie zugewiesene Saldo um einen bestimmten Faktor verringert oder der normalisierte Saldo der Strategien um einen bestimmten Faktor erhöht wird. Wir werden die zweite Option wählen. 

Wenn wir die Anzahl der Strategien in einer Gruppe mit StrategiesCount bezeichnen, dann sieht die Gleichung für die Neuberechnung des normalisierten Saldos wie folgt aus:

FittedBalance = StrategiesCount * FittedBalance

Die Wahrscheinlichkeit eines solchen Worst-Case wird jedoch mit zunehmender Anzahl der Strategieinstanzen sehr stark reduziert, wenn diese so ausgewählt werden, dass sie sich möglichst wenig unterscheiden. In diesem Fall treten die Drawdowns zu unterschiedlichen Zeitpunkten und nicht gleichzeitig auf. Dies kann während des Tests festgestellt werden. Dann können wir einen weiteren Skalierungsfaktor (Scale) einführen, der standardmäßig gleich eins ist, aber wenn gewünscht, kann er größer gemacht werden, um die Größe der Positionen zu erhöhen, indem das normalisierte Gleichgewicht der Strategien reduziert wird:

FittedBalance = StrategiesCount * FittedBalance

FittedBalance = FittedBalance / Skala

Durch die Wahl des Multiplikators Scale können wir wiederum sicherstellen, dass eine Gruppe von Strategien während des gesamten Testzeitraums einen bestimmten Drawdown liefert. In diesem Fall erhalten wir eine normalisierte Gruppe von Strategien.

Normalisierte Strategiegruppe — eine Gruppe von normalisierten Handelsstrategien, für die ein Skalierungsfaktor gewählt wurde, der einen maximalen Drawdown von nicht mehr als 10% gewährleistet, wenn die Gruppe zusammenarbeitet.

Wenn wir dann mehrere normalisierte Gruppen von Strategien gebildet haben, können sie alle wieder zu einer neuen normalisierten Gruppe kombiniert werden, und zwar nach demselben Prinzip, das bei der Kombination normalisierter Strategien verwendet wird. Mit anderen Worten, wir sollten einen Multiplikator für die Gruppe von Gruppen wählen, sodass der maximale Drawdown nicht mehr als 10 % beträgt, wenn alle Strategien aus allen Gruppen gleichzeitig arbeiten. Dieser Vereinheitlichungsprozess kann auf einer beliebigen Anzahl von Ebenen fortgesetzt werden. In diesem Fall wird das anfängliche normalisierte Gleichgewicht jeder Strategie einfach mit der Anzahl der Strategien oder Gruppen in der Gruppe auf jeder Assoziationsebene multipliziert und durch die Skalierungsfaktoren der einzelnen Ebenen geteilt:

FittedBalance = StrategiesCount1 * FittedBalance

FittedBalance = StrategiesCount2 * FittedBalance

...

FittedBalance = FittedBalance / Scale1

FittedBalance = FittedBalance / Scale2

...

Dann sieht die endgültige Gleichung für die Neuberechnung des normalisierten Saldos jeder Strategie wie folgt aus:

FittedBalance = (StrategiesCount1 *  StrategiesCount1  * ... ) * FittedBalance / (Scale1 * Scale2 * ... )

Wenden Sie schließlich den letzten Skalierungsmultiplikator Depo Part in der Gleichung zur Berechnung der Positionsgröße an, um die normalisierte Gruppe von Strategien, die sich auf der höchsten Ebene der Kombination befindet, in eine Gruppe mit einem anderen spezifizierten Drawdown anstelle von 10 % zu verwandeln:

CurrentLot =  FixedLot * (DepoPart * TotalBalance / FittedBalance)

Es gibt zwei neue Klassen für die Implementierung. Die erste Klasse CVirtualStrategyGroup ist für die Neuberechnung der normalisierten Salden von Strategien zuständig, wenn diese zu Gruppen zusammengefasst werden. Die zweite Klasse CMoney ist für die Berechnung der tatsächlichen Eröffnungsvolumina auf der Grundlage der festen Größe der Strategie, des normalisierten Saldos und des zugewiesenen Saldos verantwortlich. 


Die Klasse der Handelsstrategiegruppe

Diese Klasse wird verwendet, um Objekte zu erstellen, die entweder eine Gruppe von Strategien oder eine Gruppe von Strategiegruppen darstellen. In beiden Fällen wird bei der Erstellung einer Gruppe ein Skalierungsfaktor durch den Aufruf der einzelnen Methode Scale() angewendet.

//+------------------------------------------------------------------+
//| Class of trading strategies group(s)                             |
//+------------------------------------------------------------------+
class CVirtualStrategyGroup {
protected:
   void              Scale(double p_scale); // Scale normalized balance 
public:
   CVirtualStrategyGroup(CVirtualStrategy *&p_strategies[],
                         double p_scale = 1);   // Constructor for a group of strategies
   CVirtualStrategyGroup(CVirtualStrategyGroup *&p_groups[],
                         double p_scale = 1);   // Constructor for a group of strategy groups

   CVirtualStrategy      *m_strategies[];       // Array of strategies
   CVirtualStrategyGroup *m_groups[];           // Array of strategy groups
};

Die Konstruktoren nehmen einen Skalierungsfaktor als Parameter und entweder ein Array von Zeigern auf Strategien oder ein Array von Zeigern auf Strategiegruppen. Das resultierende Array wird in die entsprechende Eigenschaft des erstellten Objekts kopiert, und die Methode Scale() wird auf jedes Arrayelement angewendet. Wir müssen diese Methode in die Strategieklasse für Strategieobjekte aufnehmen.

//+------------------------------------------------------------------+
//| Constructor for strategy groups                                  |
//+------------------------------------------------------------------+
CVirtualStrategyGroup::CVirtualStrategyGroup(
   CVirtualStrategy *&p_strategies[],
   double p_scale
) {
   ArrayCopy(m_strategies, p_strategies);
   Scale(p_scale / ArraySize(m_strategies));
}

//+------------------------------------------------------------------+
//| Constructor for a group of strategy groups                       |
//+------------------------------------------------------------------+
CVirtualStrategyGroup::CVirtualStrategyGroup(
   CVirtualStrategyGroup *&p_groups[],
   double p_scale
) {
   ArrayCopy(m_groups, p_groups);
   Scale(p_scale / ArraySize(m_groups));
}

//+------------------------------------------------------------------+
//| Scale normalized balance                                         |
//+------------------------------------------------------------------+
void CVirtualStrategyGroup::Scale(double p_scale) {
   FOREACH(m_groups,     m_groups[i].Scale(p_scale));
   FOREACH(m_strategies, m_strategies[i].Scale(p_scale));
}

Speichern Sie den Code in der Datei VirtualStrategyGroup.mqh im aktuellen Ordner.

Nehmen wir die notwendigen Ergänzungen an der virtuellen Strategieklasse vor. Wir müssen zwei neue Klasseneigenschaften hinzufügen, um das normalisierte Gleichgewicht und die feste Größe der Strategie zu speichern. Da sie installiert werden sollten, wird nun ein zuvor unnötiger Konstruktor benötigt. Die öffentliche Methode FittedBalance() gibt einfach den Wert des normierten Gleichgewichts der Strategie zurück, während die Methode Scale() den Wert mit einem bestimmten Multiplikator skaliert.

//+------------------------------------------------------------------+
//| Class of a trading strategy with virtual positions               |
//+------------------------------------------------------------------+
class CVirtualStrategy : public CStrategy {
protected:
   ...
   double            m_fittedBalance;  // Strategy normalized balance
   double            m_fixedLot;       // Strategy fixed size
   ...
public:
   CVirtualStrategy(double p_fittedBalance = 0, double p_fixedLot = 0.01); // Constructor
   ...
   double            FittedBalance() {    // Strategy normalized balance
      return m_fittedBalance;
   }

   void              Scale(double p_scale) { // Scale normalized balance
      m_fittedBalance /= p_scale;
   }
};

Speichern Sie diesen Code in der Datei VirtualStrategy.mqh im aktuellen Ordner.

Außerdem müssen wir kleinere Änderungen in der Klasse CSimpleVolumesStrategy vornehmen. Wir sollten einen zusätzlichen Parameter für den Konstruktor für die Strategie normalisierte Balance implementieren und den Parameter für die Einstellung der Größen der virtuellen Positionen entfernen. Sie wird nun immer gleich sein und dem Mindestbetrag von 0,01 entsprechen.

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSimpleVolumesStrategy::CSimpleVolumesStrategy(
   string           p_symbol,
   ENUM_TIMEFRAMES  p_timeframe,
   int              p_signalPeriod,
   double           p_signalDeviation,
   double           p_signaAddlDeviation,
   int              p_openDistance,
   double           p_stopLevel,
   double           p_takeLevel,
   int              p_ordersExpiration,
   int              p_maxCountOfOrders,
   double           p_fittedBalance = 0) :
// Initialization list
   CVirtualStrategy(p_fittedBalance, 0.01),
   m_symbol(p_symbol),
   m_timeframe(p_timeframe),
   m_signalPeriod(p_signalPeriod),
   m_signalDeviation(p_signalDeviation),
   m_signaAddlDeviation(p_signaAddlDeviation),
   m_openDistance(p_openDistance),
   m_stopLevel(p_stopLevel),
   m_takeLevel(p_takeLevel),
   m_ordersExpiration(p_ordersExpiration),
   m_maxCountOfOrders(p_maxCountOfOrders) {
   ...
}

Speichern Sie die Änderungen in der Datei SimpleVolumesStrategy.mqh des aktuellen Ordners.

Wir brauchen die Möglichkeit, eine Gruppe von Strategien, d. h. die Instanzen unserer neuen Klasse CVirtualStrategyGroup, zum EA-Objekt hinzuzufügen. Implementieren wir also die überladene Methode Add() in die EA-Klasse, die genau das tun wird:

//+------------------------------------------------------------------+
//| Class of the EA handling virtual positions (orders)              |
//+------------------------------------------------------------------+
class CVirtualAdvisor : public CAdvisor {
   ...
public:
   ...
   virtual void      Add(CVirtualStrategyGroup &p_group);  // Method for adding a group of strategies
   ...
};

//+------------------------------------------------------------------+
//| Method for adding a group of strategies                          |
//+------------------------------------------------------------------+
void CVirtualAdvisor::Add(CVirtualStrategyGroup &p_group) {
   FOREACH(p_group.m_groups, {
      CVirtualAdvisor::Add(p_group.m_groups[i]);
      delete p_group.m_groups[i];
   });
   FOREACH(p_group.m_strategies, CAdvisor::Add(p_group.m_strategies[i]));
}

Da die Strategiegruppen nach dem Hinzufügen zum EA nicht mehr benötigt werden, entfernen wir sie bei dieser Methode sofort aus dem dynamischen Speicherbereich. Speichern Sie die an der Datei VirtualAdvisor.mqh vorgenommenen Änderungen im aktuellen Ordner.


Die Klasse für das Geldmanagement

Diese Klasse ist dafür verantwortlich, die tatsächliche Größe der virtuellen Positionen entsprechend den drei möglichen Money-Management-Strategien zu bestimmen.

Das Klassenobjekt sollte eindeutig sein. Wir können also entweder das Singleton-Entwurfsmuster dafür verwenden, oder, wie es schließlich implementiert wurde, die Klasse kann nur statische Felder und Methoden enthalten, die für beliebige Objekte zugänglich sind.

Die wichtigste Methode in dieser Klasse ist die Methode zur Bestimmung der realen Größe für die virtuelle Position (Order) von Volume(). Zwei weitere Methoden ermöglichen es, die Werte zweier Parameter festzulegen, die bestimmen, welcher Teil des Kontostandes für den Handel verwendet wird.

//+------------------------------------------------------------------+
//| Basic money management class                                     |
//+------------------------------------------------------------------+
class CMoney {
   static double     s_depoPart;       // Used part of the total balance
   static double     s_fixedBalance;   // Total balance used
public:
   CMoney() = delete;                  // Disable the constructor
   static double     Volume(CVirtualOrder *p_order); // Determine the real size of the virtual position

   static void       DepoPart(double p_depoPart) {
      s_depoPart = p_depoPart;
   }
   static void       FixedBalance(double p_fixedBalance) {
      s_fixedBalance = p_fixedBalance;
   }
};

double CMoney::s_depoPart = 1.0;
double CMoney::s_fixedBalance = 0;

//+------------------------------------------------------------------+
//| Determine the real size of the virtual position                  |
//+------------------------------------------------------------------+
double CMoney::Volume(CVirtualOrder *p_order) {
   // Request the normalized strategy balance for the virtual position  
   double fittedBalance = p_order.FittedBalance();
   
   // If it is 0, then the real volume is equal to the virtual one
   if(fittedBalance == 0.0) {
      return p_order.Volume();
   }
   
   // Otherwise, find the value of the total balance for trading
   double totalBalance = s_fixedBalance > 0 ? s_fixedBalance : AccountInfoDouble(ACCOUNT_BALANCE);
   
   // Return the calculated real volume based on the virtual one
   return p_order.Volume() * totalBalance * s_depoPart / fittedBalance ;
}
//+------------------------------------------------------------------+

Speichern Sie diesen Code in der Datei Money.mqh im aktuellen Ordner.


EAs testen

Nehmen wir Änderungen an den EA-Dateien vor, um sie zu testen. In der Datei SimpleVolumesExpertSingle.mq5 müssen wir lediglich den Parameter Positionsgröße aus der Liste der Parameter des Strategiekonstruktors in der EA-Initialisierungsfunktion entfernen:

int OnInit() {
// Create an EA handling virtual positions
   expert = new CVirtualAdvisor(magic_, "SimpleVolumesSingle");

   expert.Add(new CSimpleVolumesStrategy(
                 symbol_, timeframe_,
                 fixedLot_,
                 signalPeriod_, signalDeviation_, signaAddlDeviation_,
                 openDistance_, stopLevel_, takeLevel_, ordersExpiration_,
                 maxCountOfOrders_)
             );       // Add one strategy instance

   return(INIT_SUCCEEDED);
}

Wir werden den EA jetzt nicht verwenden, um nach neuen guten Kombinationen von Parametern für einzelne Strategieinstanzen zu suchen, da wir die zuvor gefundenen Kombinationen verwenden werden. Aber wenn nötig, ist der EA bereit für eine Optimierung.

Nehmen wir weitere wichtige Ergänzungen an der Datei SimpleVolumesExpert.mq5 vor. Wir brauchen sie hauptsächlich, um die Fähigkeiten der hinzugefügten Klassen zu demonstrieren, daher sollten wir dies nicht als endgültigen Code betrachten.

Zunächst werden wir eine Enumeration erstellen, um verschiedene Arten der Gruppierung von Instanzen von Handelsstrategien darzustellen:

enum ENUM_VA_GROUP {
   VAG_EURGBP,          // Only EURGBP (3 items)
   VAG_EURUSD,          // Only EURUSD (3 items)
   VAG_GBPUSD,          // Only GBPUSD (3 items)
   VAG_EURGBPUSD_9,     // EUR-GBP-USD (9 items)
   VAG_EURGBPUSD_3_3_3  // EUR-GBP-USD (3+3+3 items)
};

Die ersten drei Werte entsprechen der Verwendung von drei Kopien von Handelsstrategien für eines der Symbole (EURGBP, EURUSD oder GBPUSD). Der vierte Wert entspricht der Verwendung einer Gruppe von allen neun Strategieinstanzen. Der fünfte Wert entspricht der Verwendung einer Gruppe von drei normalisierten Gruppen, die drei Kopien von Handelsstrategien für ein bestimmtes Symbol umfassen.

Erweitern wir die Liste der Eingabeparameter ein wenig:

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input group "::: Strategy groups"
input ENUM_VA_GROUP group_ = VAG_EURGBP;  // - Strategy group

input group "::: Money management"
input double expectedDrawdown_ = 10;      // - Maximum risk (%)
input double fixedBalance_ = 0;           // - Used deposit (0 - use all) in the account currency
input double scale_ = 1.0;                // - Group scaling multiplier

input group "::: Other parameters"
input ulong  magic_        = 27183;       // - Magic

Stellen Sie in der EA-Initialisierungsfunktion die Parameter für die Geldverwaltung unter Berücksichtigung der Normalisierung des maximal zulässigen Drawdowns von 10 % ein, erstellen Sie neun Kopien von Strategien, ordnen Sie sie entsprechend der gewählten Gruppierung an und fügen Sie sie dem EA hinzu:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   // Set parameters in the money management class
   CMoney::DepoPart(expectedDrawdown_ / 10.0);
   CMoney::FixedBalance(fixedBalance_);

   // Create an EA handling virtual positions
   expert = new CVirtualAdvisor(magic_, "SimpleVolumes_" + EnumToString(group_));

   // Create and fill the array of all strategy instances
   CVirtualStrategy *strategies[] = {
      new CSimpleVolumesStrategy("EURGBP", PERIOD_H1,  13, 0.3, 1.0, 0, 10500,  465,  1000, 3, 1600),
      new CSimpleVolumesStrategy("EURGBP", PERIOD_H1,  17, 1.7, 0.5, 0, 16500,  220,  1000, 3,  900),
      new CSimpleVolumesStrategy("EURGBP", PERIOD_H1,  51, 0.5, 1.1, 0, 19500,  370, 22000, 3, 1600),

      new CSimpleVolumesStrategy("EURUSD", PERIOD_H1,  24, 0.1, 0.3, 0,  7500, 2400, 24000, 3, 2300),
      new CSimpleVolumesStrategy("EURUSD", PERIOD_H1,  18, 0.2, 0.4, 0, 19500, 1480,  6000, 3, 2000),
      new CSimpleVolumesStrategy("EURUSD", PERIOD_H1, 128, 0.7, 0.3, 0,  3000,  170, 42000, 3, 2200),

      new CSimpleVolumesStrategy("GBPUSD", PERIOD_H1,  80, 1.1, 0.2, 0,  6000, 1190,  1000, 3, 2500),
      new CSimpleVolumesStrategy("GBPUSD", PERIOD_H1, 128, 2.0, 0.9, 0,  2000, 1170,  1000, 3,  900),
      new CSimpleVolumesStrategy("GBPUSD", PERIOD_H1,  13, 1.5, 0.8, 0,  2500, 1375,  1000, 3, 1400),
   };

   // Create arrays of pointers to strategies, one symbol at a time, from the available strategies
   CVirtualStrategy *strategiesEG[] = {strategies[0], strategies[1], strategies[2]};
   CVirtualStrategy *strategiesEU[] = {strategies[3], strategies[4], strategies[5]};
   CVirtualStrategy *strategiesGU[] = {strategies[6], strategies[7], strategies[8]};

   // Create and add selected groups of strategies to the EA
   switch(group_) {
   case VAG_EURGBP: {
      expert.Add(CVirtualStrategyGroup(strategiesEG, scale_));
      FOREACH(strategiesEU, delete strategiesEU[i]);
      FOREACH(strategiesGU, delete strategiesGU[i]);
      break;
   }
   case VAG_EURUSD: {
      expert.Add(CVirtualStrategyGroup(strategiesEU, scale_));
      FOREACH(strategiesEG, delete strategiesEG[i]);
      FOREACH(strategiesGU, delete strategiesGU[i]);
      break;
   }
   case VAG_GBPUSD: {
      expert.Add(CVirtualStrategyGroup(strategiesGU, scale_));
      FOREACH(strategiesEU, delete strategiesEU[i]);
      FOREACH(strategiesEG, delete strategiesEG[i]);
      break;
   }
   case VAG_EURGBPUSD_9: {
      expert.Add(CVirtualStrategyGroup(strategies, scale_));
      break;
   }
   case VAG_EURGBPUSD_3_3_3: {
      // Create a group of three strategy groups
      CVirtualStrategyGroup *groups[] = {
         new CVirtualStrategyGroup(strategiesEG, 1.25),
         new CVirtualStrategyGroup(strategiesEU, 2.24),
         new CVirtualStrategyGroup(strategiesGU, 2.64)
      };

      expert.Add(CVirtualStrategyGroup(groups, scale_));
      break;
   }
   default:
      return(INIT_FAILED);
   }

// Load the previous state if available
   expert.Load();

   return(INIT_SUCCEEDED);
}

Speichern Sie die an der Datei SimpleVolumesExpert.mq5 vorgenommenen Änderungen im aktuellen Ordner.


Test

Testen wir die erste Gruppe - drei Exemplare der Strategie, die mit dem Symbol EURGBP arbeiten. Wir erhalten die folgenden Ergebnisse:


Abb. 5. Ergebnisse für EURGBP mit drei Strategien, Skala=1


Wie wir sehen können, betrug der maximale relative Drawdown in der Kombination 8% statt 10% für jede einzelne Instanz der normalisierten Strategie. Das bedeutet, dass wir unsere Position ein wenig aufstocken können. Um den Drawdown von 10% zu erreichen, setzen wir Scale = 10% / 8% = 1,25.


Abb. 6. Ergebnisse für EURGBP mit drei Strategien, Skala=1,25


Jetzt beträgt der Drawdown etwa 10 %. Führen wir einen ähnlichen Vorgang durch, um einen Skalierungsmultiplikator für die zweite und dritte Gruppe auszuwählen. Wir erhalten die folgenden Ergebnisse:

Abb. 7. Ergebnisse für EURUSD mit drei Strategien, Skala=2,24


Abb. 8. Ergebnisse für GBPUSD mit drei Strategien, Skala=2.64


Wir verwenden die ausgewählten Werte der Skalierungsmultiplikatoren im Code, um eine normalisierte Gruppe von drei normalisierten Gruppen von Strategien zu erstellen:

// Create a group of three strategy groups
CVirtualStrategyGroup *groups[] = {
     new CVirtualStrategyGroup(strategiesEG, 1.25),
     new CVirtualStrategyGroup(strategiesEU, 2.24),
     new CVirtualStrategyGroup(strategiesGU, 2.64)
 };

Lassen Sie uns nun einen Skalierungsmultiplikator für die vierte Gruppe auswählen. Wenn wir alle 9 Instanzen zu einer Gruppe zusammenfassen, erhalten wir die folgenden Ergebnisse:


Abb. 9. Ergebnisse für EURGBP, EURUSD, GBPUSD (insgesamt 9 Strategien), Skala=1


So können wir den Skalierungsfaktor auf 3,3 erhöhen und innerhalb von 10 % des relativen Drawdowns bleiben:


Abb. 10. Ergebnisse für EURGBP, EURUSD und GBPUSD (insgesamt 9 Strategien), Skala=3,3


Zum Schluss noch das Interessanteste. Kombinieren wir dieselben 9 normalisierten Strategien, aber auf eine andere Art und Weise: Wir normalisieren Gruppen von drei Strategien für einzelne Symbole getrennt und kombinieren dann die resultierenden drei normalisierten Gruppen zu einer Gruppe. Wir erhalten Folgendes:


Fig. 11. Ergebnisse für EURGBP, EURUSD und GBPUSD (3 + 3 + 3 Strategien), Skala=1


Der Endsaldo ist höher als bei der vierten Gruppe mit der gleichen Skala=1, aber auch der Drawdown ist höher: 4,57% statt 3%. Bringen wir die fünfte Gruppe auf einen Drawdown von 10 % und vergleichen wir dann das Endergebnis:


Abb. 12. Ergebnisse für EURGBP, EURUSD, GBPUSD (3 + 3 + 3 Strategien), Skala=2,18


Es ist nun klar, dass die fünfte Option für die Gruppierung der Strategien viel bessere Ergebnisse liefert, wobei der maximale relative Drawdown innerhalb von 10 % bleibt. Während des gewählten Testzeitraums hat sich der Gewinn im Vergleich zur vierten Gruppierungsoption mehr als verdoppelt.

Betrachten wir abschließend die Linearität des Saldenwachstums für die fünfte Gruppierungsoption. Auf diese Weise können wir beurteilen, ob es interne Zeiträume gibt, in denen der EA während des gesamten Testzeitraums deutlich schlechter abschneidet als in anderen internen Zeiträumen. Setzen Sie dazu den Wert des Parameters FixedBalance = 10.000, sodass der EA immer nur diesen Betrag des Kontostandes zur Berechnung der Positionsgrößen verwendet.



Abb. 13. Ergebnisse für EURGBP, EURUSD, GBPUSD (3 + 3 + 3 Strategien), FixedBalance = 10000, Scale=2.18


In der Testgrafik habe ich die internen Perioden, in denen der Saldozuwachs nahe Null war, mit grünen Rechtecken markiert. Ihre Dauer beträgt zwischen einem und sechs Monaten. Das bedeutet, dass es etwas gibt, das man anstreben kann. Der einfachste Weg, mit solchen Perioden umzugehen, ist eine stärkere Diversifizierung: Verwenden Sie mehr Instanzen von Handelsstrategien, die auf verschiedenen Symbolen und Zeitrahmen funktionieren.

Der maximale Drawdown in Bezug auf das Kapital betrug 995 USD in absoluten Zahlen, d. h. nur etwa 10 % des für den Handel verwendeten Saldos von 10.000 USD. Dies bestätigt, dass das implementierte Geldmanagementsystem korrekt funktioniert.


Schlussfolgerung

Jetzt können wir unseren EA auf Handelskonten mit unterschiedlichen Anfangsbilanzwerten laufen lassen und steuern, wie die Bilanz für verschiedene Instanzen von Handelsstrategien verteilt wird. Einige werden mehr bekommen, und sie werden größere Positionen eröffnen. Andere werden weniger bekommen, und ihre Positionen werden kleiner sein. Im Allgemeinen können wir mit Hilfe von Tests Parameter auswählen, die den vorher festgelegten maximalen zulässigen Drawdown einhalten.

Es ist anzumerken, dass wir nur durch Tests feststellen können, ob der Drawdown eingehalten wird. Wir können nicht garantieren, dass die Einhaltung der Vorgaben gewährleistet ist, wenn der EA in einem Zeitraum gestartet wird, der nicht zur Optimierung genutzt wird. Sie kann sich sowohl nach oben als auch (merkwürdigerweise) nach unten verändern. Daher sollte hier jeder selbst entscheiden, inwieweit er den Testergebnissen vertrauen kann und wie er sie verwendet.

Ich werde das Projekt weiter entwickeln. Vielen Dank fürs Lesen!


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

Risikomanager für den manuellen Handel Risikomanager für den manuellen Handel
In diesem Artikel wird detailliert beschrieben, wie man eine Risikomanager-Klasse für den manuellen Handel von Grund auf schreibt. Diese Klasse kann auch als Basisklasse für die Vererbung durch algorithmische Händler verwendet werden, die automatisierte Programme einsetzen.
GIT: Was ist das? GIT: Was ist das?
In diesem Artikel werde ich ein sehr wichtiges Werkzeug für Entwickler vorstellen. Wenn Sie mit GIT nicht vertraut sind, lesen Sie diesen Artikel, um eine Vorstellung davon zu bekommen, was es ist und wie man es mit MQL5 verwendet.
Die Rolle der Qualität von Zufallszahlengeneratoren für die Effizienz von Optimierungsalgorithmen Die Rolle der Qualität von Zufallszahlengeneratoren für die Effizienz von Optimierungsalgorithmen
In diesem Artikel werden wir uns den Mersenne-Twister-Zufallszahlengenerator ansehen und ihn mit dem Standardgenerator in MQL5 vergleichen. Wir werden auch herausfinden, welchen Einfluss die Qualität des Zufallszahlengenerators auf die Ergebnisse der Optimierungsalgorithmen hat.
Entwicklung eines Replay Systems (Teil 43): Chart Trader Projekt (II) Entwicklung eines Replay Systems (Teil 43): Chart Trader Projekt (II)
Die meisten Menschen, die programmieren lernen wollen oder davon träumen, haben eigentlich keine Ahnung, was sie da tun. Ihre Tätigkeit besteht darin, dass sie versuchen, Dinge auf eine bestimmte Art und Weise zu schaffen. Bei der Programmierung geht es jedoch nicht darum, geeignete Lösungen zu finden. Auf diese Weise können mehr Probleme als Lösungen entstehen. Hier werden wir etwas Fortgeschritteneres und daher etwas anderes machen.