Risikomanagement (Teil 4): Vervollständigung der Methoden der Schlüsselklasse
- Einführung
- Zusammenfassung des Artikels
- Neue Enums und Strukturen
- Verbesserungen an Konstruktor und Hauptfunktion
- Neue Klassenvariablen
- Verwaltung offener Positionen
- Methoden zur Behandlung von Grenzwertüberschreitungen
- Die Ereignisbehandlung
- Funktionen für das dynamische Risiko
- Dynamisches Risiko aufbauen
- Schlussfolgerung
Einführung
In Fortsetzung des vorangegangenen Artikels über das Risikomanagement werden wir die wichtigsten Variablen und Ausgangsmethoden für unsere spezialisierte Klasse erläutern. Dieses Mal werden wir uns auf die Vervollständigung der Methoden konzentrieren, die erforderlich sind, um zu überprüfen, ob festgelegte maximale Verlust- oder Gewinngrenzen überschritten worden sind. Ferner werden wir zwei dynamische Ansätze für das Risikomanagement pro Trade vorstellen.
Zusammenfassung des Artikels
Wir beginnen mit der Optimierung einiger Funktionen sowie des Konstruktors und Destruktors der Klasse. Dann werden wir neue Strukturen, Enums und Kernkonstanten definieren. Als Nächstes werden wir Funktionen für die Verwaltung der vom Expert Advisor eröffneten Positionen implementieren, einschließlich Mechanismen zur Erkennung der Überschreitung von festgelegten maximalen Gewinn- oder Verlustwerten. Schließlich werden wir Methoden zur Verwaltung des dynamischen Risikos pro Trade hinzufügen.
Neue Enums und Strukturen
Beginnen wir mit der Definition der neuen Strukturen, Enums und Konstanten, die wir später in diesem Artikel verwenden werden.
1. Dynamisches Risiko pro Trade
Wie bereits erwähnt, habe ich ein neues Konzept eingeführt: das dynamische Risiko pro Handel.
Was ist das?
Das dynamische Risiko pro Trade ist kein fester Wert, sondern eine Variable, die sich auf der Grundlage von Gewinn oder Verlust im Verhältnis zu einem bestimmten Anfangssaldo anpasst. Dies kann insbesondere zum Schutz eines Handelskontos nützlich sein. Sinkt der Saldo beispielsweise auf einen bestimmten Prozentsatz des Anfangssaldos, kann das Risiko automatisch angepasst werden, um weitere Verluste zu minimieren. Das bedeutet, dass sich das Risiko pro Trade jedes Mal anpasst, wenn ein bestimmtes Niveau erreicht wird.
Betrachten wir ein praktisches Beispiel aus der Tabelle. Angenommen, der anfängliche Kontostand beträgt 10.000 $.
Wir wählen folgende Schwellenwerte für die Risikoanpassung: 3 %, 5 % und 7 %.
| Bedingung | Neues Risiko % |
|---|---|
| Wenn der Saldo um 3 % (300 $, d. h. unter 9.700 $) sinkt | wird das Risiko auf 0,7 % angepasst. |
| Wenn der Saldo um 5 % (500 $, d. h. unter 9.500 $) sinkt | wird das Risiko auf 0,5 % angepasst. |
| Wenn der Saldo um 7 % (700 $, d. h. unter 9.300 $) sinkt | wird das Risiko auf 0,25 % angepasst. |
Wichtige Hinweise zu dynamischen Risikoparametern und zur Funktionsweise:
- Alle in diesem Artikel genannten Werte sind vollständig anpassbare Parameter, die je nach den Bedürfnissen und Vorlieben des Nutzers angepasst werden können. Dazu gehören sowohl die Schwellenwerte, die Risikoanpassungen auslösen, als auch der Anfangssaldo, für den diese Prozentsätze gelten.
Das Anfangssaldo kann auf zwei Arten ermittelt werden:
- PropFirm-Konto (z. B. FTMO): Wenn diese Option über den Risikomanagement-Parameter (propfirm_ftmo) ausgewählt wird, muss der Anfangssaldo manuell über einen Eingabeparameter in den Einstellungen des Expert Advisor eingegeben werden. Dieser Saldo bleibt während des gesamten Handels fest, d. h., er ändert sich im Laufe der Zeit nicht und ist ein statischer Wert, der vom Nutzer zu Beginn festgelegt wird.
- Persönliches Konto: Wenn der Kontotyp personal_account ausgewählt ist, wird der Anfangssaldo automatisch vom Expert Advisor beim Aufruf von AccountInfoDouble() ermittelt, der bei der Systeminitialisierung den aktuellen Kontostand zurückgibt. In diesem Fall ist der Saldo dynamisch und spiegelt die aktuellen Mittel auf dem Kontos genau wider.
- Verringert das Risiko, das Konto zu sprengen oder das Handelskapital vollständig zu verlieren.
- Erhöht die allgemeine Sicherheit beim Handel, indem es das Risiko längerer Verlustperioden reduziert.
Nachteile des dynamischen Risikos von einzelnen Trades:
-
Die Erholung nach einer Pechsträhne oder einem erheblichen Rückgang kann aufgrund der präventiven Reduzierung des Engagements langsamer erfolgen. Die Erholung verläuft zwar langsamer, ist aber sicherer und kontrollierter.
Struktur für die Umsetzung des dynamischen Risikos pro Trade
Um das dynamische Risiko pro Handel zu verwalten und zu konfigurieren, müssen wir auf die Eigenschaft assigned_percentage innerhalb der Variablen gmlpo zugreifen und diese ändern. Um diese automatischen, auf dem Saldo basierenden Anpassungen durchzuführen, müssen wir eine Struktur mit zwei Hauptelementen schaffen:
- Spezifischer Saldo, bei dem die Risikoanpassung ausgelöst wird (z. B. wenn der Saldo von ursprünglich 10.000 $ auf 9.700 $ fällt, wie in der Tabelle angegeben).
- Neuer Risikoprozentsatz, der auf den Saldo angewandt wird, wenn das angegebene Niveau erreicht ist (z. B. Anpassung des Risikos auf 0,7 % in diesem Fall).
Wir könnten zunächst eine einfache Struktur mit zwei getrennten reellen Variablen erstellen, aber dieser Ansatz ist nicht ausreichend. Stattdessen verwenden wir zwei separate Arrays innerhalb der Struktur.
Warum Arrays statt einzelner Variablen verwenden?
Wir verwenden Arrays anstelle von einzelnen Variablen, weil wir mehrere zusammenhängende Werte effizient sortieren müssen. Die Sortierung ist aufgrund der spezifischen Methode erforderlich, die wir für ein effizientes dynamisches Risikomanagement verwenden. Konkret verwenden wir die Funktion ArraySort(), um die Salden zu sortieren, bei denen Risikoanpassungen ausgelöst werden (balance_to_activate_the_risk[]).
Warum die Sortierung mit ArraySort() erforderlich ist
Der Hauptgrund dafür ist, dass wir eine Methode ohne eine Schleife anwenden, die kontinuierlich überprüft, ob der aktuelle Saldo bestimmte Schwellenwerte überschreitet, die eine Risikoanpassung auslösen. Dieser schleifenfreie Ansatz wurde gewählt, um die Systemleistung und -geschwindigkeit zu optimieren, was bei häufigen Überprüfungen (z. B. bei jedem Tick oder beim Schließen jedes Trades) von entscheidender Bedeutung ist.
Wenn die Werte nicht korrekt sortiert sind (d. h. in aufsteigender Reihenfolge), können schwerwiegende Probleme auftreten. Um dies zu verdeutlichen, betrachten wir ein praktisches Beispiel.
Gehen wir davon aus, dass wir zunächst die folgenden Schwellenwerte festlegen:
- Erste Schwelle: 3 % (Aktivierungssaldo: 9.700 $)
- Zweite Schwelle: 7 % (Aktivierungssaldo: 9.300 $)
- Dritte Schwelle: 5 % (Aktivierungssaldo: 9.500 $)
Wie Sie sehen können, sind diese Werte falsch sortiert (der zweite Schwellenwert ist größer als der dritte). Das Problem mit der falschen Reihenfolge ist darauf zurückzuführen, dass unsere schleifenfreie Methode eine ganzzahlige Variable (Zähler) verwendet, um den aktuellen Risikostatus zu verfolgen.
Stellen wir uns vor, was passieren wird:
- Wenn der Anfangssaldo um 3 % (auf 9.700 $) sinkt, erhöht sich der Zähler um eins, wodurch das Risiko auf 0,7 % angepasst wird.
- Wenn das Saldo dann auf die nächste Stufe (7 %) fällt, erreicht das Konto 9.300 $ und der Zähler steigt wieder an, wobei der Zwischenwert (5 % bei einem Saldo von 9.500 $) übersprungen wird.
- Der Zwischenwert (9.500 $) bleibt ungenutzt, was zu Verwirrung und ernsthaften Berechnungsproblemen führt, da das dynamische Risiko nicht ordnungsgemäß angepasst wird.
Wenn sich das Konto vom niedrigsten Stand aus erholt (in diesem Fall fälschlicherweise von 5 %), muss es 9.300 $ überschreiten, um zum vorherigen Wert zurückzukehren, was nicht korrekt ist. Da aber die ursprüngliche Methode die Reihenfolge nicht korrekt berücksichtigte, wird auch die Wiederherstellung nicht richtig funktionieren und zusätzliche Fehler verursachen.
Aus diesen Gründen ist eine ordnungsgemäße Sortierung entscheidend für das optimale Funktionieren der schleifenfreien Methode. Die einfachste Struktur, die wir vorschlagen, sieht wie folgt aus:
struct Dynamic_gmlpo { double balance_to_activate_the_risk[]; double risk_to_be_adjusted[]; };
Obwohl diese Struktur einfach ist, bleibt jedoch eine wichtige Einschränkung bestehen. Da diese beiden Arrays Schlüssel-Wert-Paare darstellen (Saldo – angepasster Risikoprozentsatz), ist die Beibehaltung dieser Zuordnung während der Sortierung entscheidend. An dieser Stelle kommt die Struktur CHashMap ins Spiel.
Die Implementierung mit CHashMap ermöglicht die direkte Verknüpfung jedes spezifischen Saldos mit dem entsprechenden Risikoprozentsatz. Beim Sortieren des Hauptfeldes (Salden) wird die Zuordnung zum jeweiligen Risiko automatisch beibehalten. Dies garantiert absolute Genauigkeit bei allen Operationen und Berechnungen.
Was wir also bisher implementiert haben, ist eine einfache Anfangslösung mit zwei getrennten Arrays, die es uns ermöglicht, die Anwendung der Methode ArraySort() vorübergehend zu vereinfachen. Für eine zuverlässigere und genauere Implementierung, insbesondere wenn Sie mehrere Schlüssel-Wert-Paare effizient verarbeiten möchten, empfehlen wir jedoch die Verwendung von CHashMap. Diese Struktur gewährleistet eine korrekte Zuordnung jedes Saldos zu seinem jeweiligen bereinigten Risiko, was dazu beiträgt, potenzielle Fehler beim Sortieren und Abfragen dynamischer Werte zu vermeiden.
Im folgenden Artikel wird untersucht, wie diese Lösung mit CHashMap umgesetzt werden kann, und es werden praktische Beispiele und schrittweise Erläuterungen gegeben.
Saldo-Kontrolle
Zu den Enums, die für eine ordnungsgemäße Implementierung des dynamischen Risikos erforderlich sind, müssen wir zwei zusätzliche Optionen hinzufügen, die eindeutig festlegen, wann die Gleichgewichtsprüfung durchgeführt wird. Diese Prüfung ist von entscheidender Bedeutung, da sie den genauen Zeitpunkt festlegt, zu dem zu beurteilen ist, ob der aktuelle Saldo unter die vordefinierten prozentualen Schwellenwerte gesunken ist, wodurch eine Änderung des dynamischen Risikos ausgelöst wird.
Es gibt zwei Hauptarten von Kontrollen:
-
Prüfen bei jedem Markttick: Bei dieser Methode wird die Prüfung kontinuierlich bei jeder Kursbewegung (Tick) durchgeführt. Diese Option bietet eine hohe Genauigkeit, da sie kontinuierlich prüft, ob das Kapital (aktueller Saldo) unter einen bestimmten Schwellenwert gefallen ist. Allerdings gibt es einen erheblichen Nachteil: Der ständige Vergleich mit dem aktuellen Kapital kann zu unangenehmen oder ineffizienten Situationen führen.
Nehmen wir zum Beispiel an, der Anfangssaldo beträgt 10.000 $ und die erste Risikoaktivierungsstufe tritt ein, wenn der Saldo 9.700 $ erreicht. Schwankt das Kapital zwischen 9.701 $ und 9.699 $, wird die dynamische Risikoneudefinition mehrfach ausgelöst und deaktiviert, was nicht nur unpraktisch ist, sondern aufgrund der hohen Prüfungsfrequenz auch die Systemressourcen übermäßig belastet.
-
Prüfen beim Schließen von Trades: Diese zweite Methode führt die Prüfung nur beim Schließen eines Trades durch, nicht bei jedem Tick. Diese Option ist effizienter im Hinblick auf die Ressourcennutzung, da der Saldo nur zu bestimmten Zeitpunkten überprüft wird. Sie kann jedoch weniger genau sein, da die Prüfung nur bei Tradesabschluss erfolgt und möglicherweise erhebliche zwischenzeitliche Schwankungen, die eine rechtzeitige Änderung des dynamischen Risikos auslösen könnten, nicht berücksichtigt werden.
Um die Wahl zwischen diesen beiden Methoden zu erleichtern, werden wir sie im Code mithilfe eines Enums klar definieren:
//--- enum ENUM_REVISION_TYPE { REVISION_ON_CLOSE_POSITION, //Check GMLPO only when closing positions REVISION_ON_TICK //Check GMLPO on all ticks };
Damit haben die Nutzer die freie Wahl, wie und wann sie die Saldenprüfungen durchführen wollen, und können das dynamische Risikoverhalten auf der Grundlage ihrer individuellen Bedürfnisse und Präferenzen hinsichtlich der Systemleistung und -genauigkeit anpassen.
Modus des dynamischen Risikos pro Trade (GMLPO)
Um die Art und Weise zu verwalten, in der das dynamische Risiko pro Trade (GMLPO) angewendet wird, werden wir eine spezielle Enumeration verwenden, die drei klar unterscheidbare Optionen bietet. Als Nächstes werde ich den Zweck der einzelnen Optionen erläutern und erklären, warum ich mich entschieden habe, sie mithilfe einer Enumeration zu implementieren.
Ursprünglich wurden vom Nutzer eingegebene Textzeichenfolgen verwendet, um bestimmte Prozentsätze festzulegen, bei denen sich das Risiko ändern sollte, und um neue Risikowerte anzugeben. Bei dieser ursprünglichen Methode musste der Nutzer in einer Zeile (Zeichenkette) manuell negative Prozentsätze für das Saldo eingeben, die eine Risikoänderung auslösten, und in einer anderen Zeile die neuen Risikowerte, die angewendet werden sollten. Dieser Ansatz war zwar durchaus praktikabel, hatte aber einen entscheidenden Nachteil: Zeichenketten konnten nicht automatisch mit der Optimierungsfunktion des Expert Advisors optimiert werden. Dies machte das Einrichten und Testen verschiedener Szenarien langsam, unpraktisch und ziemlich mühsam.
Um diese Einschränkungen zu überwinden, habe ich beschlossen, eine Enumeration zu implementieren, die eine einfache Auswahl zwischen drei klar definierten Modi ermöglicht und damit Flexibilität und Komfort bei der Optimierung bietet:
-
DYNAMIC_GMLPO_FULL_CUSTOM: Dieser Modus ist vollständig anpassbar und ermöglicht es dem Nutzer, mehrere Risikoaktivierungsstufen und die damit verbundenen neuen Prozentwerte manuell einzustellen. Obwohl in diesem Modus die Verwendung von Zeichenketten beibehalten wird, kann der Nutzer die gewünschte Anzahl von Änderungen angeben und so maximale Flexibilität auf Kosten der automatischen Optimierungsfunktionen erreichen.
-
DYNAMIC_GMLPO_FIXED_PARAMETERS: Dieser Modus vereinfacht die dynamische Risikokonfiguration erheblich, da die maximale Anzahl der zulässigen Änderungen auf vier begrenzt ist. Hier gibt der Nutzer negative Saldoanteile und die entsprechenden Risikoprozentsätze direkt über numerische Parameter ein, was die Optimierung erheblich vereinfacht. Diese Option bietet ein Gleichgewicht zwischen Anpassungsflexibilität und automatisierter Test-/Optimierungsleistung.
-
NO_DYNAMIC_GMLPO: In diesem Modus wird die dynamische Risikofunktion vollständig deaktiviert. Es ist ideal für Nutzer, die es vorziehen, während des gesamten Handels ein festes Risiko beizubehalten, ohne dynamische Änderungen aufgrund von Saldoschwankungen.
//--- enum ENUM_OF_DYNAMIC_MODES_OF_GMLPO { DYNAMIC_GMLPO_FULL_CUSTOM, //Customisable dynamic risk per operation DYNAMIC_GMLPO_FIXED_PARAMETERS,//Risk per operation with fixed parameters NO_DYNAMIC_GMLPO //No dynamic risk for risk per operation };
Eine solche enum-basierte Implementierung bietet Klarheit, Bequemlichkeit und die Möglichkeit, verschiedene Konfigurationen leicht zu optimieren, was eine schnelle Identifizierung der besten Lösung entsprechend den spezifischen Nutzerpräferenzen und Strategien ermöglicht.
Verbuchen der Trades
Um unser Risikomanagementsystem zu verbessern, werden wir eine spezielle Funktion hinzufügen, die eine genaue Verfolgung aller offenen Positionen auf dem Konto ermöglicht, unabhängig davon, ob sie manuell von einem Nutzer oder von einem Expert Advisor eröffnet wurden.
Bei Positionen, die von einem Expert Advisor eröffnet wurden, können wir auch eindeutig festPositionen, ob ein bestimmtes Ticket mit der von diesem Expert Advisor zugewiesenen magischen Zahl übereinstimmt. Dies ist besonders nützlich, wenn Sie genau wissen müssen, wie viele Positionen von diesem Expert Advisor eröffnet wurden, und diese leicht von manuell eröffneten Positionen unterscheiden können.
Um dieses Ziel zu erreichen, werden wir eine einfache, aber effektive Struktur definieren, die die wesentlichen Informationen über jede Position enthält:
struct Positions { ulong ticket; //position ticket ENUM_POSITION_TYPE type; //position type };
Überschreitung von Gewinn- oder Verlustobergrenzen
Jetzt fügen wir eine separate Enumeration hinzu, die angibt, welche Kriterien berücksichtigt werden, um festzustellen, ob die zuvor festgelegte Gewinn- oder Verlustobergrenze überschritten wurde.
Die Funktion, die für die Durchführung dieser Prüfung zuständig ist, gibt „true“ zurück, wenn die angegebenen Bedingungen gemäß dem gewählten Kriterium erfüllt sind.
Die Enumeration besteht aus drei klar unterscheidbaren Optionen:
//--- Mode to check if a maximum loss or gain has been exceeded enum MODE_SUPERATE { EQUITY, //Only Equity CLOSE_POSITION, //Only for closed positions CLOSE_POSITION_AND_EQUITY//Closed positions and equity };
-
EQUITY:
Bei dieser Option wird ausschließlich das Kapital des Kontos (d. h. der Echtzeitsaldo unter Berücksichtigung offener und geschlossener Positionen) ausgewertet. Gewinne oder Verluste aus bereits geschlossenen Trades des laufenden Tages werden nicht berücksichtigt. Die Funktion zeigt nur dann an, dass die maximale Gewinn- oder Verlustgrenze überschritten wurde, wenn das Echtzeit-Kapital die festgelegte Grenze direkt überschreitet. -
CLOSED_POSITIONS:
Bei dieser Methode werden nur Gewinne oder Verluste aus Positionen berücksichtigt, die während des laufenden Tages geschlossen wurden. Dabei werden die offenen Positionen und das aktuelle Kapital vollständig ignoriert. Eine Limitüberschreitung wird also allein durch das kumulierte Ergebnis der abgeschlossenen Trades bestimmt. -
CLOSED_POSITION_AND_EQUITY:
Dies ist die umfassendste und genaueste Methode, da sie beide oben genannten Ansätze kombiniert. Die Funktion bewertet gleichzeitig den Gewinn oder Verlust der während des Tages geschlossenen Positionen und das aktuelle Echtzeit-Kapital. Das bedeutet, dass das gesamte Tagesergebnis analysiert wird, was eine genauere und strengere Bewertung der Überschreitung der festgelegten Grenzwerte ermöglicht.
Durch die Verwendung dieser Enumeration im Risikomanagementsystem ermöglichen wir den Nutzern, die Methode der Grenzwertprüfung flexibel zu wählen, was eine einfache Anpassung an die verschiedenen Strategien und Genauigkeitsstufen ermöglicht, die im Risikomanagement erforderlich sind.
Definiert
Um mit neuen Variablen fortzufahren, ist es wichtig, einige Konstanten mit der #define-Direktive zu definieren.
Zunächst legen wir das Präfix define fest, um die von unserem Expert Advisor generierten Operationen und Meldungen leicht identifizieren zu können. Ein solches Präfix kann den Namen des Expert Advisors enthalten, sodass er in vom System generierten Protokollen oder Kommentaren leicht zu erkennen ist. In diesem Fall werden wir zum Beispiel verwenden:
#define EA_NAME "CRiksManagement | " //Prefix
Ferner müssen wir mehrere Konstanten (Flags) definieren, die für die genaue Kontrolle der offenen Positionen und der schwebenden Aufträge verwendet werden. Diese Flags ermöglichen eine schnelle Identifizierung des Handelstyps (Kauf, Verkauf, Limit-Order, Stop-Order usw.), was ein effektives Risikomanagement, die Schließung von Positionen und die Ausführung spezifischer Anfragen bezüglich der aktuellen Marktlage und unserer Positionen erleichtert.
Im Folgenden sind die jeweiligen Definitionskonstanten aufgeführt:
//--- positions #define FLAG_POSITION_BUY 2 #define FLAG_POSITION_SELL 4 //--- orders #define FLAG_ORDER_TYPE_BUY 1 #define FLAG_ORDER_TYPE_SELL 2 #define FLAG_ORDER_TYPE_BUY_LIMIT 4 #define FLAG_ORDER_TYPE_SELL_LIMIT 8 #define FLAG_ORDER_TYPE_BUY_STOP 16 #define FLAG_ORDER_TYPE_SELL_STOP 32 #define FLAG_ORDER_TYPE_BUY_STOP_LIMIT 64 #define FLAG_ORDER_TYPE_SELL_STOP_LIMIT 128 #define FLAG_ORDER_TYPE_CLOSE_BY 256
Diese Konstanten sorgen für eine klarere und effizientere Implementierung in zukünftigen Funktionen, was die Lesbarkeit, Wartung und Skalierbarkeit des Expert Advisor-Codes erheblich vereinfacht.
Verbesserungen an Konstruktor und Hauptfunktion
Wir werden mit der Optimierung des Risikomanagements mit dem Konstruktor der Klasse CRiskManagement beginnen. Mit der Hinzufügung des dynamischen Risikos pro Handel wurden mehrere wichtige Verbesserungen am Code vorgenommen:
Zunächst haben wir den verwendeten Typ der Losgröße (type_get_lot) und den Parameter für den Anfangssaldo (account_propfirm_balance) explizit definiert, was bei der Verwendung eines PropFirm-Kontos nützlich ist. Beachten Sie außerdem, dass die Definition von EA_NAME ständig in Kommentaren angezeigt wird, die von den Hauptklassenfunktionen erzeugt werden. So lassen sie sich in den Terminalprotokollen schnell identifizieren.
Die verbesserte Implementierung des Konstruktors sieht wie folgt aus:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CRiskManagemet::CRiskManagemet(bool mdp_strict_, ENUM_GET_LOT type_get_lot_, ulong magic_number_ = NOT_MAGIC_NUMBER, ENUM_MODE_RISK_MANAGEMENT mode_risk_management_ = personal_account, double account_propfirm_balance = 0) { if(magic_number_ == NOT_MAGIC_NUMBER) { Print(EA_NAME, " (Warning) No magic number has been chosen, taking into account all the magic numbers and the user's trades"); } //--- this.mdp_is_strict = mdp_strict_; this.type_get_lot = type_get_lot_; //--- this.account_balance_propfirm = account_propfirm_balance ; trade = new CTrade(); trade.SetExpertMagicNumber(this.magic_number); this.account_profit = GetNetProfitSince(true, this.magic_number, D'1972.01.01 00:00'); this.magic_number = magic_number_; this.mode_risk_managemet = mode_risk_management_; this.ActivateDynamicRiskPerOperation = false; //--- this.last_day_time = iTime(_Symbol, PERIOD_D1, 0); this.last_weekly_time = iTime(_Symbol, PERIOD_W1, 0); this.init_time = magic_number_ != NOT_MAGIC_NUMBER ? TimeCurrent() : D'1972.01.01 00:00'; //--- this.positions_open = false; this.curr_profit = 0; UpdateProfit(); //--- for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong position_ticket = PositionGetTicket(i); if(!PositionSelectByTicket(position_ticket)) continue; ulong position_magic = PositionGetInteger(POSITION_MAGIC); ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); if(position_magic == magic_number_ || magic_number_ == NOT_MAGIC_NUMBER) { this.positions_open = true; Positions new_pos; new_pos.type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); new_pos.ticket = position_ticket; ExtraFunctions::AddArrayNoVerification(open_positions, new_pos); } } }
Es wurden neue wichtige Variablen hinzugefügt:
- curr_profit: Speichert den aktuellen Gewinn und ermöglicht so eine ständige Ergebniskontrolle.
- ActivateDynamicRiskPerOperation: Boolesche Variable, die bestimmt, ob das dynamische Risiko während des Expert Advisor-Betriebs verwendet wird.
- mdp_is_strict: Variable, die bestimmt, ob das Risikomanagement mdp streng überwachen wird.
- type_get_lot: Variable, die den Typ des Loses speichert.
Verbessern des Destruktors
Es wurden mehrere wesentliche Verbesserungen am Destruktor vorgenommen, insbesondere im Hinblick auf eine angemessene dynamische Speicherverwaltung. Jetzt wird der Zeiger der Klasse CTrade mit der Funktion CheckPointer() überprüft, wodurch sichergestellt wird, dass er nur dann gelöscht wird, wenn er dynamisch ist, wodurch potenzielle Fehler bei der Speicherfreigabe vermieden werden.
Die optimierte Implementierung des Destruktors sieht wie folgt aus:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CRiskManagemet::~CRiskManagemet() { if(CheckPointer(trade) == POINTER_DYNAMIC) delete trade; ArrayFree(this.open_positions); ArrayFree(this.dynamic_gmlpos.balance_to_activate_the_risk); ArrayFree(this.dynamic_gmlpos.risk_to_be_adjusted); }
Die ordnungsgemäße Verwendung von CheckPointer() gewährleistet, dass der Pointer auf trade korrekt und nur bei Bedarf freigegeben wird. Ferner wird durch die Verwendung der Funktion ArrayFree() der von den Klassenarrays belegte Speicher effektiv freigegeben, was eine ordnungsgemäße Speicherverwaltung gewährleistet und den Expert Advisor, der dieses Risikomanagement umsetzt, stabiler und effektiver macht.
Allgemeine Verbesserungen
Ich habe mehrere wichtige Änderungen vorgenommen, die darauf abzielen, das Risikomanagementsystem insgesamt zu stärken und mögliche Fehler bei der Arbeit mit Expert Advisors zu vermeiden. Diese werden im Folgenden ausführlicher beschrieben.
1. Prüfungen beim Abrufen des Losgrößenvolumens und Stop Loss (SL)
Es wurden Schlüsselprüfungen hinzugefügt, um sicherzustellen, dass der Risikowert pro Trade (gmlpo.assigned_percentage) nicht ungültig oder Null ist. Diese Prüfungen ermöglichen die rechtzeitige Erkennung kritischer Fehler und geben den Nutzern Meldungen, sodass falsche Einstellungen schnell korrigiert werden können.
Das Hauptkriterium ist die direkte Validierung von gmlpo.assigned_percentage. Wenn dieser Wert kleiner oder gleich null ist, wird eine kritische Meldung auf der Konsole ausgegeben und die Funktion beendet, wobei sichere Werte zurückgegeben werden, um eine Fehlfunktion des Expert Advisors zu verhindern.
if(gmlpo.assigned_percentage <= 0) { PrintFormat("%s Critical error, the value of gmlpo.value(%.2f) is invalid", EA_NAME, this.gmlpo.value); return 0; }
Beispiel in der Funktion GetSL():
//+----------------------------------------------------------------------------------+ //| Get the ideal stop loss based on a specified lot and the maximum loss per trade | //+----------------------------------------------------------------------------------+ long CRiskManagemet::GetSL(const ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50) { if(gmlpo.assigned_percentage <= 0) { PrintFormat("%s Critical error, the value of gmlpo.value(%.2f) is invalid", EA_NAME, this.gmlpo.value); return 0; } double lot; return CalculateSL(type, this.gmlpo.value, lot, DEVIATION, STOP_LIMIT); }
Beispiel in der Funktion GetLote():
//+-----------------------------------------------------------------------------------------------+ //| Function to obtain the ideal lot based on the maximum loss per operation and the stop loss | //+-----------------------------------------------------------------------------------------------+ double CRiskManagemet::GetLote(const ENUM_ORDER_TYPE order_type) { if(gmlpo.assigned_percentage <= 0) { PrintFormat("%s Critical error, the value of gmlpo.value(%.2f) is invalid", EA_NAME, this.gmlpo.value); this.lote = 0.00; return this.lote; } //--- if(this.type_get_lot == GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION) { double MaxLote = GetMaxLote(order_type); SetNMPLO(this.lote, MaxLote); PrintFormat("%s Maximum loss in case the next operation fails %.2f ", EA_NAME, this.nmlpo); } else { this.lote = GetLotByRiskPerOperation(this.gmlpo.value, order_type); } //--- return this.lote; }
2. Verbesserungen der Parameterzuweisungsfunktion (SetEnums())
Diese Funktion ist von entscheidender Bedeutung und muss im Ereignis OnInit() ausgeführt werden. Jetzt enthält es eine zusätzliche Prüfung, die die korrekte Zuordnung von Geld- oder Prozentwerten auf der Grundlage der Nutzerauswahl sicherstellt. Diese Validierung verhindert die Zuweisung falscher oder negativer Werte, insbesondere wenn ein fester Geldbetrag (Geld) als Kriterium verwendet wird.
Verbesserte Implementierung von SetEnums():
//+----------------------------------------------------------------------------------------+ //| Function to set how losses or gains are calculated, | //| by percentage applied to (balance, equity, free margin or net profit) or simply money. | //+----------------------------------------------------------------------------------------+ //Note: This method is mandatory, it must be executed in the OnInit event. void CRiskManagemet::SetEnums(ENUM_RISK_CALCULATION_MODE mode_mdl_, ENUM_RISK_CALCULATION_MODE mode_mwl_, ENUM_RISK_CALCULATION_MODE mode_gmlpo_, ENUM_RISK_CALCULATION_MODE mode_ml_, ENUM_RISK_CALCULATION_MODE mode_mdp_) { this.gmlpo.mode_calculation_risk = mode_gmlpo_; this.mdl.mode_calculation_risk = mode_mdl_; this.mdp.mode_calculation_risk = mode_mdp_; this.ml.mode_calculation_risk = mode_ml_; this.mwl.mode_calculation_risk = mode_mwl_; //-- En caso se haya escojido el modo dinero, asignamos la variable que guarda el dinero o porcentage alas varialbes correspondientes if(this.gmlpo.mode_calculation_risk == money) { this.gmlpo.value = this.gmlpo.percentage_applied_to; this.ActivateDynamicRiskPerOperation = false; } else this.gmlpo.value = 0; this.mdp.value = this.mdp.mode_calculation_risk == money ? (this.mdp.percentage_applied_to > 0 ? this.mdp.percentage_applied_to : 0) : 0; this.mdl.value = this.mdl.mode_calculation_risk == money ? (this.mdl.percentage_applied_to > 0 ? this.mdl.percentage_applied_to : 0) : 0; this.ml.value = this.ml.mode_calculation_risk == money ? (this.ml.percentage_applied_to > 0 ? this.ml.percentage_applied_to : 0) : 0; this.mwl.value = this.mwl.mode_calculation_risk == money ? (this.mwl.percentage_applied_to > 0 ? this.mwl.percentage_applied_to : 0) : 0; }
Neue Klassenvariablen
Im Zuge der weiteren Verbesserungen werden wir Variablen hinzufügen, die eine präzisere und effektivere Kontrolle der offenen Trades ermöglichen.
Zunächst definieren wir ein spezielles Array, um Informationen über alle offenen Positionen im Konto zu speichern, sowohl manuell vom Nutzer als auch automatisch vom Expert Advisor eröffnet:
//--- Positions open_positions[];
Ferner werden wir zwei Schlüsselvariablen hinzufügen:
- Die boolesche Variable positions_open, die angibt, ob es offene Positionen auf dem Markt gibt. Diese Variable spielt eine wichtige Rolle bei der Optimierung der Leistung und hilft, redundante Prüfungen zu vermeiden, wenn es keine aktiven Positionen gibt.
//--- Boolean variable to check if there are any open operations by the EA or user bool positions_open;
-
Die reelle Variable namens curr_profit, die den aktuellen kumulierten Gewinn oder Verlust in Echtzeit speichert. Diese Variable ermöglicht eine schnelle Berechnung des aktuellen Handelsstatus:
//--- Variable to store the current profit of the EA or user double curr_profit;
Dynamisches Risiko pro Trade
Um das dynamische Risiko pro Trade (dynamisches GMLPO) effektiv zu verwalten, müssen wir mehrere spezifische Variablen in unserer Klasse hinzufügen und klar definieren. Diese Variablen bieten eine wirksame Kontrolle und ermöglichen eine automatische Risikoanpassung nach nutzerdefinierten Parametern. Nachfolgend finden Sie eine ausführliche Erläuterung der einzelnen Punkte:
1. Struktur für das Speichern der Salden und der dynamischen Risiken
Wir werden eine nutzerdefinierte Struktur namens Dynamic_gmlpo verwenden, die zwei dynamische Arrays enthält:
Dynamic_gmlpo dynamic_gmlpos;
Diese Struktur ermöglicht die Speicherung mehrerer spezifischer Saldostufen zusammen mit den jeweiligen Risikoprozentsätzen und erleichtert auf diese Weise ein dynamisches Risikomanagement.
2. Variable zur Bestimmung der Art der dynamischen Risikoprüfung
Wir werden eine Enum-Variable mit dem Namen revision_type definieren, mit der die Nutzer wählen können, wie die dynamischen Risikoprüfungen durchgeführt werden sollen (bei jedem Tick oder beim Schließen von Positionen):
ENUM_REVISION_TYPE revision_type;
3. Boolesche Variable zur Aktivierung oder Deaktivierung des dynamischen Risikos
Diese boolesche Variable speichert die Entscheidung, ob das dynamische Risiko pro Trade verwendet werden soll:
bool ActivateDynamicRiskPerOperation; 4. Variable zur Speicherung des aktuellen dynamischen Risikoindexes
Um eine genaue Kontrolle über die Änderungen des dynamischen Risikoniveaus zu erhalten, wird eine Indexvariable verwendet, die angibt, bei welchem dynamischen Array-Element wir uns gerade befinden:
int index_gmlpo; 5. Variable zur Speicherung des Basissaldos (Anfangssaldo)
Diese Variable speichert den vom Nutzer gewählten Ausgangs- oder Referenzsaldo, auf den später bestimmte Prozentsätze angewendet werden, um das dynamische Risiko zu aktivieren.
double chosen_balance; 6. Variable, die den nächsten Zielsaldo für die Veränderung des Risikos pro Trade angibt (in positiver Richtung)
Diese Variable speichert die nächste Saldostufe, die überschritten werden muss, um den Risikoprozentsatz dynamisch anzupassen:
double NewBalanceToOvercome; 7. Die boolesche Variable zur Vermeidung von Fehlern, wenn der Index den zulässigen Bereich überschreitet
Diese boolesche Variable gibt an, ob der vom Nutzer festgelegte mögliche Mindestsaldo (maximal zulässiger negativer Prozentsatz) erreicht wurde. Wenn der Wert der Variablen wahr wird, wird die Indexerhöhung angehalten, um ein Überschreiten des maximal zulässigen Bereichs zu verhindern.
bool TheMinimumValueIsExceeded; 8. Variable zur Speicherung des anfänglichen Risikoprozentsatzes pro Handel
Diese Variable speichert den ursprünglichen Prozentsatz, der für das Risiko pro Trade festgelegt wurde. Sie wird hauptsächlich verwendet, um den ursprünglichen Wert wiederherzustellen, wenn sich der Saldo nach einem Rückgang wieder erholt.
double gmlpo_percentage; Diese Variablen ermöglichen die effektive Implementierung eines zuverlässigen, sicheren und einfach zu verwaltenden Mechanismus, der Klarheit und eine präzise Kontrolle des dynamischen Risikos pro Trade in verschiedenen operativen Szenarien gewährleistet.
Die Variable zur Kontrolle des maximalen Tagesgewinns
Für eine strengere Kontrolle der täglichen Gewinne wird eine boolesche Variable mdp_is_strict eingeführt. Anhand dieser Variable kann das Risikomanagementsystem feststellen, ob die Berechnung des maximalen Gewinns angepasst werden muss, um die täglichen Verluste zu berücksichtigen.
- Wenn mdp_is_strict auf true gesetzt ist, wird der maximale Tagesgewinn nur dann als überschritten angesehen, wenn alle vorherigen Verluste aufgeholt wurden und dann das ursprüngliche Zielniveau erreicht wurde. Wenn der maximale Tagesgewinn beispielsweise 50 $ beträgt und im Laufe des Tages 20 $ verloren wurden, müssen insgesamt 70 $ verdient werden (um den Verlust von 20 $ auszugleichen und einen Nettogewinn von 50 $ zu erzielen), damit das Zielniveau als erreicht gilt.
- Wenn mdp_is_strict falsch ist, haben tägliche Verluste keinen Einfluss auf die Berechnung des maximalen Gewinns. Wenn in diesem Fall das Gewinnziel bei 50 $ liegt und bereits 40 $ Verlust entstanden sind, reicht es aus, weitere 10 $ zu erzielen (um den Verlust von 40 $ auszugleichen und einen Nettogewinn von 10 $ zu erzielen), um den maximalen Tagesgewinn zu erreichen.
bool mdp_is_strict; Variabler Typ der Losgröße
Um die Funktion zur Losgrößenzuweisung zu vereinfachen, werden wir die Funktion GetBatch() so anpassen, dass keine manuelle Angabe des Typs der Losgröße mehr erforderlich ist. Stattdessen wird der Losgrößentyp im Konstruktor initialisiert oder kann durch zusätzliche Funktionen, die wir entwickeln werden, geändert werden. Dieser Ansatz ermöglicht eine direktere Konfiguration und verringert das Risiko von Fehlern bei der manuellen Eingabe von Parametern.
ENUM_GET_LOT type_get_lot;
Dank dieser Verbesserungen erwarten wir eine höhere Effizienz und Genauigkeit sowohl beim Risikomanagement als auch bei der Losgrößenzuweisung auf unserer Handelsplattform.
Verwaltung offener Positionen
In diesem Abschnitt werden wir uns auf die ordnungsgemäße Verwaltung aller Positionen konzentrieren, die durch eine magische Zahl oder durch den Nutzer eröffnet wurden. Dazu werden wir mehrere nützliche und übersichtliche Funktionen erstellen, die es ermöglichen, jederzeit den Überblick über offene Trades zu behalten.
1. Funktion zur Überprüfung des Vorhandenseins von Tickets im internen Array (open_positions)
Die Funktion TheTicketExists() dient dazu, schnell zu überprüfen, ob ein bestimmtes Ticket in unserem internen Array open_positions enthalten ist. Dieser Vorgang ist besonders wichtig, um festzustellen, ob eine bestimmte Position bereits verwaltet wird oder ob weitere Maßnahmen erforderlich sind.
Die Logik ist einfach: Wir durchlaufen das Array und vergleichen jedes Element mit dem angegebenen Ticket. Wenn eine Übereinstimmung gefunden wird, wird true zurückgegeben, andernfalls false.
Deklaration:
bool TheTicketExists(const ulong ticket); //Check if a ticket is in the operations array
Umsetzung:
//+------------------------------------------------------------------+ //| Function to check if the ticket is in the array | //+------------------------------------------------------------------+ bool CRiskManagemet::TheTicketExists(const ulong ticket) { for(int i = 0; i < this.GetPositionsTotal() ; i++) if(this.open_positions[i].ticket == ticket) return true; return false; }
2. Funktion zum Abrufen der Gesamtzahl der offenen Positionen
Die Funktion GetPositionsTotal() gibt einfach die Anzahl der Elemente zurück, die im internen Array open_positions vorhanden sind. Es ermöglicht eine einfache und schnelle Bestimmung der Anzahl der verwalteten Positionen in Echtzeit.
Erklärung und Umsetzung:
inline int GetPositionsTotal() const { return (int)this.open_positions.Size(); } //Get the total number of open positions
3. Funktion zum Abrufen der Gesamtzahl von Positionen mit Flags
Die Funktion GetPositions() verwendet ein System von Flags, um eine größere Flexibilität bei der Zählung offener Positionen auf der Grundlage bestimmter Kriterien, wie z. B. der Handelsart (Kauf oder Verkauf), zu ermöglichen. Dazu wird der Positionstyp (ENUM_POSITION_TYPE) in Werte umgewandelt, die mit binären Flags kompatibel sind (normalerweise Potenzen von 2, z. B. 2, 4, 8 usw.).
Die Logik besteht darin, alle offenen Positionen zu durchlaufen und jede mit den angegebenen Flags zu vergleichen, um dann den Zähler für jede gefundene Übereinstimmung zu erhöhen.
Umsetzung:
//+------------------------------------------------------------------+ //| Function to obtain the number of open positions | //+------------------------------------------------------------------+ int CRiskManagemet::GetPositions(int flags) const { int count = 0; for(int i = 0; i < ArraySize(this.open_positions) ; i++) { if(this.open_positions[i].type == POSITION_TYPE_BUY && (flags & FLAG_POSITION_BUY) != 0 ) { count++; } else if(this.open_positions[i].type == POSITION_TYPE_SELL && (flags & FLAG_POSITION_SELL) != 0 ) { count++; } } return count; }
4. Funktion zum Prüfen von derzeit offenen Positionen
Schließlich bietet die Funktion ThereAreOpenOperations() eine schnelle und effiziente Möglichkeit, um herauszufinden, ob unser Expert Advisor gerade offene Positionen bearbeitet. Sie gibt einfach den Wert der internen booleschen Variablen (positions_open) zurück.
Erklärung und Umsetzung:
inline bool ThereAreOpenOperations() const { return this.positions_open; } //Check if there are any open operations
5. Zusätzliche Hilfsfunktionen
Zusätzlich zu den Funktionen, die integraler Bestandteil der Klasse CRiskManagement sind, werden externe Hilfsfunktionen erstellt, um die Ausführung bestimmter Aufgaben zu erleichtern, wie z. B. das Schließen von Aufträgen auf der Grundlage von Flags.
5.1 Funktion zum Schließen schwebender Aufträge durch Flags
Bevor wir Aufträge nach bestimmten Kriterien abschließen können, müssen wir die Auftragsarten (ENUM_ORDER_TYPE) in kompatible Binärflags umwandeln. Dieser Schritt ist entscheidend für die Vermeidung von Fehlern bei der Durchführung von bitweisen Operationen (&).
Nachfolgend finden Sie eine einfache Funktion, die eine bestimmte Auftragsart in das entsprechende Kennzeichen umwandelt.
Wenn ein ungültiger oder verallgemeinerter Wert (WRONG_VALUE) übergeben wird, gibt die Funktion eine Kombination aller Flags zurück:
// Converts an order type to its corresponding flag int OrderTypeToFlag(ENUM_ORDER_TYPE type) { if(type == ORDER_TYPE_BUY) return FLAG_ORDER_TYPE_BUY; else if(type == ORDER_TYPE_SELL) return FLAG_ORDER_TYPE_SELL; else if(type == ORDER_TYPE_BUY_LIMIT) return FLAG_ORDER_TYPE_BUY_LIMIT; else if(type == ORDER_TYPE_SELL_LIMIT) return FLAG_ORDER_TYPE_SELL_LIMIT; else if(type == ORDER_TYPE_BUY_STOP) return FLAG_ORDER_TYPE_BUY_STOP; else if(type == ORDER_TYPE_SELL_STOP) return FLAG_ORDER_TYPE_SELL_STOP; else if(type == ORDER_TYPE_BUY_STOP_LIMIT) return FLAG_ORDER_TYPE_BUY_STOP_LIMIT; else if(type == ORDER_TYPE_SELL_STOP_LIMIT) return FLAG_ORDER_TYPE_SELL_STOP_LIMIT; else if(type == ORDER_TYPE_CLOSE_BY) return FLAG_ORDER_TYPE_CLOSE_BY; return (FLAG_ORDER_TYPE_BUY | FLAG_ORDER_TYPE_SELL | FLAG_ORDER_TYPE_BUY_LIMIT | FLAG_ORDER_TYPE_SELL_LIMIT | FLAG_ORDER_TYPE_BUY_STOP | FLAG_ORDER_TYPE_SELL_STOP | FLAG_ORDER_TYPE_BUY_STOP_LIMIT | FLAG_ORDER_TYPE_SELL_STOP_LIMIT | FLAG_ORDER_TYPE_CLOSE_BY); }
Die Hauptfunktion durchläuft alle bestehenden Aufträge und schließt diejenigen ab, die den angegebenen Flags entsprechen:
// Close all orders that match the flags in `flags` void CloseAllOrders(int flags, CTrade &obj_trade, ulong magic_number_ = NOT_MAGIC_NUMBER) { ResetLastError(); for(int i = OrdersTotal() - 1; i >= 0; i--) { ulong ticket = OrderGetTicket(i); if(OrderSelect(ticket)) { ENUM_ORDER_TYPE type_order = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); ulong magic = OrderGetInteger(ORDER_MAGIC); int bandera = OrderTypeToFlag(type_order); if((bandera & flags) != 0 && (magic == magic_number_ || magic_number_ == NOT_MAGIC_NUMBER)) { if(type_order == ORDER_TYPE_BUY || type_order == ORDER_TYPE_SELL) obj_trade.PositionClose(ticket); else obj_trade.OrderDelete(ticket); } } else { PrintFormat("Error selecting order %d, last error %d", ticket, GetLastError()); } } }
5.2 Funktionen zum Abrufen der Gesamtzahl der offenen Positionen
Außerdem wird eine einfache externe Funktion hinzugefügt, um die Gesamtzahl der offenen Positionen zu zählen, ohne sich ausschließlich auf die Klasse CRiskManagement verlassen zu müssen. Zunächst müssen wir die Positionsarten (ENUM_POSITION_TYPE) in kompatible Flags umwandeln.
5.2.1 Funktion zur Umwandlung von ENUM_POSITION_TYPE in gültige Flags
int PositionTypeToFlag(ENUM_POSITION_TYPE type) { if(type == POSITION_TYPE_BUY) return FLAG_POSITION_BUY; else if(type == POSITION_TYPE_SELL) return FLAG_POSITION_SELL; return FLAG_POSITION_BUY | FLAG_POSITION_SELL; }
5.2.2 Funktion zum Abrufen der Gesamtzahl der offenen Positionen nach Flags
Diese Funktion durchläuft alle vorhandenen Positionen und zählt nur diejenigen, die mit den angegebenen Flags übereinstimmen:
//--- int GetPositions(int flags = FLAG_POSITION_BUY | FLAG_POSITION_SELL, ulong magic_number_ = NOT_MAGIC_NUMBER) { int counter = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong position_ticket = PositionGetTicket(i); if(!PositionSelectByTicket(position_ticket)) continue; // Si la selección falla, pasa a la siguiente posición ulong position_magic = PositionGetInteger(POSITION_MAGIC); ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // Check if the position type matches the flags if((flags & PositionTypeToFlag(type)) != 0 && (position_magic == magic_number_ || magic_number_ == NOT_MAGIC_NUMBER)) { counter++; } } return counter; }
Umgang mit Grenzwertüberschreitungen
Nun werden wir mehrere Funktionen definieren, mit denen wir feststellen können, ob wir den maximalen Verlust oder den maximalen Tagesgewinn überschritten haben.
Beginnen wir mit der Entwicklung von zwei Hauptfunktionen: eine für die Kontrolle der Verlustüberschreitung und die andere für die Bestätigung des Erreichens des maximalen erwarteten Gewinns.
Jeder dieser Modi ist in einer Funktion gekapselt, die uns je nach gewählter Option mitteilt, ob wir das gesetzte Ziel erreicht haben (Rückgabe von true) oder ob wir noch einen langen Weg vor uns haben (Rückgabe von false). Im Modus CLOSED_POSITIONS ist es zum Beispiel äußerst wichtig zu wissen, welcher Gewinn bereits erzielt wurde, da er mit den festgelegten maximalen Verlust- oder Gewinngrenzen (täglich oder wöchentlich) verglichen werden muss.
Schauen wir uns an, wie dies in Code umgesetzt werden kann:
//+------------------------------------------------------------------+ //| Boolean function to check if a loss was overcome | //+------------------------------------------------------------------+ bool CRiskManagemet::IsSuperated(double profit_, double loss_, const MODE_SUPERATE mode) const { if(loss_ <= 0 || !this.positions_open) return false; //if loss is zero return false (the loss is not being used) //--- if(mode == EQUITY) //--- { if(this.curr_profit * -1 > loss_) return true; } else if(mode == CLOSE_POSITION) { if(profit_ * -1 > loss_) return true; } else if(mode == CLOSE_POSITION_AND_EQUITY) { double new_loss = profit_ < 0 ? loss_ - MathAbs(profit_) : loss_; if(this.curr_profit * -1 > new_loss) return true; } return false; }
Aufschlüsselung der Modi:
-
EQUITY: In diesem Modus wird der aktuelle Gewinn (Kapital minus Kontosaldo) direkt verglichen. Übersteigt dieser negative Wert die angegebene Verlustschwelle, haben wir die festgelegte Grenze überschritten.
-
CLOSE_POSITION: In diesem Modus analysieren wir den Gewinn aus bereits geschlossenen Positionen und multiplizieren ihn für einen angemessenen Vergleich mit -1.
-
CLOSE_POSITION_AND_EQUITY: Dieser Modus ist etwas komplexer. Hier wird der maximale Verlust an den aktuellen Gewinn angepasst. Wenn der Tag unrentabel ist und der Gewinn negativ ist, ziehen wir diesen Wert von der zulässigen Verlustgrenze ab. Wenn der negative aktuelle Gewinn den eingestellten Wert übersteigt, ist auch der Schwellenwert überschritten.
Spezialisierte Funktionen zur Gewinnmaximierung
Wie bei den Verlusten benötigen wir auch hier eine Möglichkeit, zu überprüfen, ob wir den maximal erwarteten Gewinn überschritten haben. Zu diesem Zweck werden wir eine spezielle Funktion einrichten, die sicherstellt, dass unsere Trades nicht unbeabsichtigt den Wert des maximalen Tagesgewinns (mdp) verändern.
Nachfolgend finden Sie den Code für diese Funktion:
//+------------------------------------------------------------------+ //| Function to check if the maximum profit per day was exceeded | //+------------------------------------------------------------------+ bool CRiskManagemet::MDP_IsSuperated(const MODE_SUPERATE mode) const { if(this.mdp.value <= 0 || !this.positions_open) return false; //if loss is zero return false (the loss is not being used) //--- if(mode == EQUITY) //--- { if(this.curr_profit > this.mdp.value) return true; } else if(mode == CLOSE_POSITION) { if(this.daily_profit > this.mdp.value) return true; } else if(mode == CLOSE_POSITION_AND_EQUITY) { double new_mdp = this.daily_profit > 0 ? this.mdp.value - this.daily_profit : (this.mdp_is_strict == false ? this.mdp.value : this.mdp.value + (this.daily_profit * -1)); if(this.curr_profit > new_mdp) return true; } //--- return false; }
Wie die Funktion funktioniert:
-
EQUITY: Dabei wird der aktuelle Gewinn (curr_profit) direkt mit dem maximalen Tagesgewinn (mdp) verglichen. Wenn der aktuelle Gewinn höher ist, gilt das Ziel als überschritten.
-
CLOSE_POSITION: Es wird geprüft, ob der Gewinn aus nur während des Tages geschlossenen Positionen (daily_profit) den Wert von mdp übersteigt.
-
CLOSE_POSITION_AND_EQUITY: Dieser Fall ist der komplexeste und umfasst zwei Situationen:
- Wenn der Tagesgewinn positiv ist, ziehen wir diesen Wert von mdp ab, um ein neues angepasstes Ziel festzulegen.
- Wenn der Tagesgewinn negativ ist und die Gewinnverwaltungspolitik strikt ist (mdpisstrict), addieren wir den absoluten Wert dieses Verlustes zu mdp, um ihn auszugleichen, bevor die Zielkennzahl als überschritten angesehen werden kann. Wenn die Politik nicht streng ist, verwenden wir den Anfangswert von mdp und ignorieren die täglichen Verluste.
Diese Funktion bietet eine präzise Kontrolle über die Gewinnziele und stellt sicher, dass wir auch an volatilen Handelstagen genau einschätzen können, ob wir unser Gewinnziel erreicht oder überschritten haben.
Maximaler Verlust in PropFirm
Um beim Thema Verlustmanagement zu bleiben, ist es wichtig, ein wesentliches Merkmal von PropFirm-Konten, insbesondere von FTMO-Konten, zu beachten. Bei diesen Konten ist die Verlustobergrenze festgelegt. Sie ändert sich nicht und bleibt von Beginn des Tests an konstant. Wenn wir zum Beispiel mit einem Kontostand von 10.000 USD beginnen, liegt die Verlustobergrenze bei 9.000 USD. Das heißt, wenn das Kapital des Kontos jemals unter diesen Schwellenwert fällt, geht die Förderungswürdigkeit automatisch verloren.
Um die Überwachung dieses Schwellenwerts zu vereinfachen und zusätzliche Komplikationen zu vermeiden, werden wir eine spezielle Methode einführen, die prüft, ob die maximale Verlustgrenze erreicht oder überschritten wurde:
//--- Function to check if the maximum loss has been exceeded in a PropFirm account of the FTMO type inline bool IsSuperatedMLPropFirm() const { return (this.ml.value == 0 || !this.positions_open) ? false : AccountInfoDouble(ACCOUNT_EQUITY) < (account_balance_propfirm - (this.ml.value)); }
Die Logik ist einfach: Wenn es keine offenen Positionen gibt oder die Variable, die den maximalen Verlust (ml) steuert, gleich Null ist, wird die Prüfung übersprungen und false zurückgegeben. Wenn es offene Positionen gibt und ml einen Wert hat, vergleicht die Funktion das aktuelle Kapital mit dem anfänglichen Testsaldo abzüglich des maximal zulässigen Verlusts.
Überprüfungsfunktionen
Zusätzlich zu der oben beschriebenen Methode werden wir praktische Funktionen entwickeln, mit denen sich schnell feststellen lässt, ob vorgegebene Gewinn- oder Verlustgrenzen erreicht oder überschritten wurden. Diese Funktionen sind vereinfachte Wrapper-Aufrufe für die allgemeine IsSuperated-Methode:
//--- functions to verify if the established losses were exceeded inline bool ML_IsSuperated(const MODE_SUPERATE mode) const {return this.mode_risk_managemet == personal_account ? IsSuperated(this.gross_profit, this.ml.value, mode) : IsSuperatedMLPropFirm(); } inline bool MWL_IsSuperated(const MODE_SUPERATE mode) const {return IsSuperated(this.weekly_profit, this.mwl.value, mode); } inline bool MDL_IsSuperated(const MODE_SUPERATE mode) const {return IsSuperated(this.daily_profit, this.mdl.value, mode); } inline bool GMLPO_IsSuperated() const {return IsSuperated(0, this.gmlpo.value, EQUITY); } inline bool NMLPO_IsSuperated() const {return IsSuperated(0, this.nmlpo, EQUITY); } bool MDP_IsSuperated(const MODE_SUPERATE mode) const;
Diese Funktionen bieten eine effiziente, unkomplizierte Möglichkeit, die Bedingungen für tägliche, wöchentliche und andere Verlustgrenzen zu bewerten.
FTMO maximaler dynamischer Tagesverlust
Ein wichtiger Aspekt des Risikomanagements bei FTMO ist die Erkenntnis, dass der maximale Tagesverlust nicht feststeht, sondern dynamisch ist. Dieser Wert ändert sich je nach dem im Laufe des Tages angefallenen Gewinn. Je mehr Gewinne im Laufe des Tages erzielt werden, desto größer ist der Spielraum für potenzielle Verluste; daher schwankt dieses Limit, anstatt konstant zu bleiben.
Nachfolgend finden Sie eine Funktion, die den maximalen Tagesverlust auf der Grundlage des realisierten Gewinns aktualisiert:
//--- Update Loss (only if ftmo propfirm FTMO is selected) inline void UpdateDailyLossFTMO() { this.mdl.value += this.daily_profit > 0 ? this.daily_profit : 0; PrintFormat("%s The maximum loss per operation has been modified, its new value: %.2f",EA_NAME,this.mdl.value); }
Diese Funktion sollte von der Methode aufgerufen werden, die die Transaktionen des Expert Advisors verarbeitet, d. h. OnTradeTransaction.
//+------------------------------------------------------------------+ //| TradeTransaction function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result)
Die Ereignisbehandlung
Kommen wir nun zu den neuen Ereignissen, die innerhalb der Ereignisverwaltungsmethoden des Expert Advisors ablaufen werden.
OnTradeTransaktion
Die OnTradeTransaction-Methode ist für das dynamische Risikomanagement von zentraler Bedeutung, da sie bei jedem Ereignis im Zusammenhang mit Kontohandelsoperationen ausgelöst wird, z. B. bei der Eröffnung oder Schließung von Positionen, der Änderung bestehender Aufträge oder der Finanzierung des Kontos.
Die Funktion hat drei Schlüsselparameter:
- trans: Kommen wir nun zu den neuen Ereignissen, die innerhalb der Ereignisverwaltungsmethoden des Expert Advisors ablaufen werden.
- request: Informationen über anhängige oder kürzlich abgeschlossene Handelsanfragen.
- result: Ergebnisse, die nach der Bearbeitung dieser Anfragen zurückgegeben werden.
Für das Risikomanagement sind wir in erster Linie an den Informationen in trans interessiert, da sie genaue Angaben über den Typ der Transaktion und den damit verbundenen Vorgang liefern.
In der Enumeration sind mehrere Transaktionstypen der Trades definiert:
ENUM_TRADE_TRANSACTION_TYPE Arten von Trades:
| ID | Beschreibung |
|---|---|
| TRADE_TRANSACTION_ORDER_ADD | Hinzufügen eines neuen Auftrags. |
| TRADE_TRANSACTION_ORDER_UPDATE | Aktualisieren eines offenen Auftrags. Zu solchen Änderungen gehören explizite Bearbeitungen im Kundenterminal oder auf dem Handelsserver sowie Änderungen des Auftragsstatus während der Auftragserteilung (z. B. Übergang von ORDER_STATE_STARTED zu ORDER_STATE_PLACED oder von ORDER_STATE_PLACED zu ORDER_STATE_PARTIAL usw.). |
| TRADE_TRANSACTION_ORDER_DELETE | Entfernen eines Auftrags aus der Liste der offenen Aufträge. Ein Auftrag kann entweder aus der Liste der offenen Aufträge entfernt werden, nachdem der entsprechende Antrag gestellt wurde, oder nachdem er ausgeführt (erfüllt) und in die Historie aufgenommen wurde. |
| TRADE_TRANSACTION_DEAL_ADD | Hinzufügen eines Deals zur Historie. Tritt auf, nachdem ein Auftrag ausgeführt oder ein Kontostand durchgeführt wurde. |
| TRADE_TRANSACTION_DEAL_UPDATE | Aktualisieren eines Deals in der Historie. Ein bereits abgeschlossener Deal kann auf dem Server geändert werden. Zum Beispiel, wenn sie in einem externen Handelssystem (Börse) eingestellt wurde, an das der Makler sie geschickt hat. |
| TRADE_TRANSACTION_DEAL_DELETE | Entfernen eines Deals aus der Historie. Ein bereits abgeschlossener Deal kann auf dem Server gelöscht werden. Zum Beispiel, wenn sie in einem externen Handelssystem (Börse), an das der Makler sie geschickt hat, gelöscht wurde. |
| TRADE_TRANSACTION_HISTORY_ADD | Hinzufügen eines Auftrags zur Historie, nachdem er ausgeführt oder storniert wurde. |
| TRADE_TRANSACTION_HISTORY_UPDATE | Aktualisieren eines Auftrags in der Auftragshistorie. Dieser Typ ist auch für die Erweiterung der Serverfunktionalität gedacht. |
| TRADE_TRANSACTION_HISTORY_DELETE | Entfernen eines Auftrags aus der Auftragshistorie. Dieser Typ ist auch für die Erweiterung der Serverfunktionalität gedacht. |
| TRADE_TRANSACTION_POSITION | Positionsveränderungen, die nicht mit der Durchführung des Deals zusammenhängen. Dieser Transaktionstyp bedeutet, dass die Position auf dem Handelsserver geändert wurde. Eine Position kann sich hinsichtlich des Volumens, des Eröffnungskurses sowie des Stop-Loss- und Take-Profit-Niveaus ändern. Informationen über diese Änderungen werden in der Struktur MqlTradeTransaction über die Ereignisbehandlung durch OnTradeTransaction übergeben. Eine Bestandsänderung (Hinzufügung, Aktualisierung oder Löschung), die durch einen Trade verursacht wird, löst später keine TRADE_TRANSACTION_POSITION-Transaktion aus. |
| TRADE_TRANSACTION_REQUEST | Benachrichtigung, dass der Server die Handelsanfrage bearbeitet und das Ergebnis zurückgesendet hat. Für diesen Transaktionstyp muss nur ein Feld in MqlTradeTransaction analysiert werden: type (Transaktionstyp). Für weitere Informationen analysieren Sie den zweiten und dritten Parameter von OnTradeTransaction (Anfrage und Ergebnis). |
Der Schwerpunkt liegt jedoch auf dem folgenden Transaktionstyp:
- TRADE_TRANSACTION_DEAL_ADD zeigt an, dass eine neue Transaktion zur Historie hinzugefügt wurde (ein abgeschlossenes Trade oder eine bestätigte Positionseröffnung).
Erstellen der Funktion OnTradeTransactionEvent
Als Nächstes werden wir eine Funktion für eine klare und effiziente Handhabung dieses Ereignisses definieren:
void OnTradeTransactionEvent(const MqlTradeTransaction& trans);
Die Funktion benötigt nur den Parameter trans, der alle Informationen über die aktuelle Transaktion enthält.
Die Grundstruktur unserer Funktion beginnt damit, dass wir prüfen, ob der Transaktionstyp TRADE_TRANSACTION_DEAL_ADD ist, und dann eine Vorauswahl des Trades aus der Historie treffen:
//+------------------------------------------------------------------+ //| OnTradeTransaction Event | //+------------------------------------------------------------------+ void CRiskManagemet::OnTradeTransactionEvent(const MqlTradeTransaction &trans) { HistoryDealSelect(trans.deal); if(trans.type == TRADE_TRANSACTION_DEAL_ADD) { ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY); ulong position_magic = (ulong)HistoryDealGetInteger(trans.deal, DEAL_MAGIC); bool is_select = PositionSelectByTicket(trans.position); if(entry == DEAL_ENTRY_IN && is_select && (this.magic_number == position_magic || this.magic_number == NOT_MAGIC_NUMBER)) { Print(EA_NAME, " New position opened with ticket: ", trans.position); this.positions_open = true; Positions new_pos; new_pos.type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); new_pos.ticket = trans.position; AddArrayNoVerification(open_positions, new_pos); return; } if(entry == DEAL_ENTRY_OUT && TheTicketExists(trans.position) == true && !is_select) { Print(EA_NAME, " Position with ticket ", trans.position, " has been closed"); DeleteTicket(trans.position); //--- if(this.revision_type == REVISION_ON_CLOSE_POSITION) CheckAndModifyThePercentageOfGmlpo(); //--- if(GetPositionsTotal() == 0) this.positions_open = false; //--- UpdateProfit(); //--- if(this.mode_risk_managemet == propfirm_ftmo) UpdateDailyLossFTMO(); SetGMLPO(); Print(StringFormat("%-6s| %.2f", "GMLPO", this.gmlpo.value)); } } }
Innerhalb dieser Funktion behandeln wir zwei Schlüsselszenarien:
-
Offene Positionen: Wir bestätigen, dass die Position offen ist (is_select) und ihre magische Zahl mit dem angegebenen Wert übereinstimmt, bevor wir sie zu unserer internen Liste hinzufügen.
-
Geschlossene Positionen: Wir prüfen, ob die Position geschlossen wurde (nicht auswählbar) und ob sie in unserer Liste der verwalteten Positionen vorhanden ist. In diesem Fall entfernen wir das Ticket aus dem Register, aktualisieren Schlüsselvariablen wie den kumulierten Gewinn und den Gesamtstatus der offenen Positionen und passen den maximalen Tagesverlust dynamisch an, wenn der FTMO-Modus aktiviert ist.
Auf diese Weise kann der Rahmen proaktiv auf jede registrierte Transaktion reagieren und ein klares, effizientes und dynamisches Risikomanagement bieten.
OnTickEvent
Das Ereignis OnTick ist eines der Schlüsselereignisse im Expert Advisor (EA) Betrieb, da es bei jedem neuen Markttick ausgelöst wird.
In unserem Fall wird er verwendet, um den kumulierten Gesamtgewinn für alle offenen Positionen zu aktualisieren, gefiltert nach der magischen Zahl, falls diese angegeben ist. Wenn die magische Zahl nicht festgelegt ist, wird die Aktualisierung für alle offenen Positionen durchgeführt, unabhängig vom magischen Wert.
Die grundlegende Methodendefinition lautet wie folgt:
void OnTickEvent(); Diese Funktion wird nur ausgeführt, wenn offene Positionen vorhanden sind, um unnötige Berechnungen zu vermeiden.
Die Funktion ist wie folgt aufgebaut:
//+------------------------------------------------------------------+ //| Function to execute in OnTick | //+------------------------------------------------------------------+ void CRiskManagemet::OnTickEvent(void) { if(!positions_open) return; //--- GetPositionsProfit(); //--- if(this.revision_type == REVISION_ON_TICK) CheckAndModifyThePercentageOfGmlpo(); }Ausführliche Erläuterung des Verfahrens:
-
Aktualisierung des Gesamtgewinns:
-
Die Methode GetPositionsProfit() ruft Informationen über den aktuellen Gewinn oder Verlust von verwalteten Positionen ab und aktualisiert diese. Dadurch wird sichergestellt, dass wir immer über aktuelle und genaue Daten über die Gesamtleistung der offenen Trades verfügen.
-
-
GMLPO validieren und dynamisch aktualisieren:
-
Wenn wir die Option REVIEW_ON_TICK gewählt haben, prüft der Expert Advisor bei jedem neuen Tick, ob die zuvor definierte Gewinn- oder Verlustschwelle überschritten wurde, und passt so das zulässige Risiko pro Handel dynamisch an. Dadurch können wir das Volumen der offenen Positionen (Höhe des Marktexpositionsniveaus) in Echtzeit regulieren und die Risikomanagementleistung verbessern.
-
Funktionen für das dynamische Risiko und die Arbeit mit Positionsarrays
In diesem Abschnitt werden wir Schlüsselfunktionen zur effektiven Verwaltung dynamischer Risiken und zur Bewältigung praktischer Aufgaben im Zusammenhang mit Positionsarrays implementieren. Diese Instrumente werden in verschiedenen Phasen unserer Risikomanagementstrategie von Nutzen sein.
Funktion zur Umwandlung einer Zeichenkette in einen Datentyp
Diese Funktion verwendet Schablonen, um sich leicht an verschiedene einfache Datentypen anzupassen (ohne komplexe Klassen oder Strukturen). Sein Hauptziel ist die Umwandlung einer Zeichenkette in den gewünschten Datentyp und die Vereinfachung der dynamischen Informationsverarbeitung in Echtzeit.
Umsetzung:
template <typename S> void StringToType(string token, S &value, ENUM_DATATYPE type) { if(StringLen(token) == 0) { Print("Error: String is empty."); return; } switch(type) { case TYPE_BOOL: value = (S)(StringToInteger(token) != 0); // Convertir a bool break; case TYPE_CHAR: value = (S)((char)StringToInteger(token)); // Convertir a char break; case TYPE_UCHAR: value = (S)((uchar)StringToInteger(token)); // Convertir a uchar break; case TYPE_SHORT: value = (S)((short)StringToInteger(token)); // Convertir a short break; case TYPE_USHORT: value = (S)((ushort)StringToInteger(token)); // Convertir a ushort break; case TYPE_COLOR: value = (S)((color)StringToInteger(token)); // Convertir a color break; case TYPE_INT: value = (S)(StringToColor(token)); // Convertir a int break; case TYPE_UINT: value = (S)((uint)StringToInteger(token)); // Convertir a uint break; case TYPE_DATETIME: value = (S)(StringToTime(token)); // Convertir a datetime break; case TYPE_LONG: value = (S)((long)StringToInteger(token)); // Convertir a long break; case TYPE_ULONG: value = (S)((ulong)StringToInteger(token)); // Convertir a ulong break; case TYPE_FLOAT: value = (S)((float)StringToDouble(token)); // Convertir a float break; case TYPE_DOUBLE: value = (S)(StringToDouble(token)); // Convertir a double break; case TYPE_STRING: value = (S)(token); // Mantener como string break; default: Print("Error: Unsupported data type in ConvertToType."); break; } }
Diese Funktion ist besonders wichtig bei der Arbeit mit dynamischen Risiken, da die Parameter als Textstrings vorliegen und für die weitere Verwendung in numerische Variablen umgewandelt werden müssen.
Funktion zur Umwandlung einer Zeichenkette in ein Array primitiver Typen
In ähnlicher Weise verwendet diese Funktion Schablonen, um die Umwandlung einer Textzeichenfolge in ein Array des gewünschten Typs zu vereinfachen. Die Funktion führt Folgendes aus:
- Teilt die Eingabezeichenkette mit einem angegebenen Trennzeichen auf (Standard ist ein Komma ',').
- Speichert jedes resultierende Element in einem temporären Array.
- Konvertiert jedes Element in den angegebenen Datentyp und weist es dem Zielarray zu.
//--- template <typename S> void StringToArray(S &array_receptor[], string cadena, ENUM_DATATYPE type_data, ushort separator = ',') { string result[]; int num = StringSplit(cadena, separator, result); ArrayResize(array_receptor, ArraySize(result)); for(int i = 0; i < ArraySize(array_receptor) ; i++) { S value; StringToType(result[i], value, type_data); array_receptor[i] = value; } }
In praktischen dynamischen Risikoszenarien können wir zum Beispiel Daten wie „5.0, 4.5, 3.0“ erhalten, die automatisch in ein Double-Array umgewandelt werden. Dies vereinfacht die Verwaltung dynamischer Parameter innerhalb unseres Systems erheblich.
Erweiterte Funktionen für die Arbeit mit Arrays
Im Folgenden finden Sie einige nützliche Funktionen, die ein effektives Array-Management erleichtern, insbesondere bei der Arbeit mit dynamischen Strategien und komplexen Strukturen im Risikomanagement.
Funktion zum Entfernen mehrerer Elemente aus einem Array nach Index
Mit dieser Funktion können Sie mehrere Elemente auf einmal aus einem Array entfernen, was die Leistung und Lesbarkeit verbessert. Die Hauptlogik besteht darin, die zu entfernenden Indizes zu sortieren und dann nur die zu behaltenden Elemente zurück in das ursprüngliche Array zu kopieren.
Umsetzung:
//--- template <typename T> void RemoveMultipleIndexes(T &arr[], int &indexes_to_remove[]) { int oldSize = ArraySize(arr); int removeSize = ArraySize(indexes_to_remove); if(removeSize == 0 || oldSize == 0) return; // Ordenamos los índices para garantizar eficiencia al recorrerlos ArraySort(indexes_to_remove); int writeIndex = 0, readIndex = 0, removeIndex = 0; while(readIndex < oldSize) { if(removeIndex < removeSize && readIndex == indexes_to_remove[removeIndex]) { removeIndex++; } else { arr[writeIndex] = arr[readIndex]; writeIndex++; } readIndex++; } ArrayResize(arr, writeIndex); }
Funktion zum Hinzufügen von Elementen zu einem Array
Die folgende Funktion erleichtert das Hinzufügen neuer Elemente zu einem Array beliebigen Typs und passt sich automatisch an den Datentyp an.
Umsetzung:
//--- template <typename X> void AddArrayNoVerification(X &array[], const X &value) { ArrayResize(array, array.Size() + 1); array[array.Size() - 1] = value; }
Es ist ganz einfach: Wir erhöhen die Größe des Arrays um eins und weisen den neuen Wert dem letzten Element zu.
Spezialisierte Funktion zum Entfernen eines einzelnen Eintrags per Ticket
Diese Funktion ist für Arrays von Strukturen gedacht, die ein Ticketfeld enthalten. Sie entfernt ein bestimmtes Element auf der Grundlage dieses eindeutigen Identifikators.
Umsetzung:
//--- template<typename T> bool RemoveIndexFromAnArrayOfPositions(T &array[], const ulong ticket) { int size = ArraySize(array); int index = -1; // Search index and move elements in a single loop for(int i = 0; i < size; i++) { if(array[i].ticket == ticket) { index = i; } if(index != -1 && i < size - 1) { array[i] = array[i + 1]; // Move the elements } } if(index == -1) return false; // Reducir el tamaño del array if(size > 1) ArrayResize(array, size - 1); else if(size <= 1) ArrayFree(array); return true; }
Es ist wichtig zu beachten, dass diese Funktion erfordert, dass die Struktur ein Element namens Ticket enthält. Wenn wir versuchen, es mit Strukturen zu verwenden, die dieses Element nicht haben, tritt ein Fehler auf:
'ticket' - undeclared identifier
in template 'bool RemoveIndexFromAnArrayOfPositions(T&[],const ulong)' specified with [T=Message]
see template instantiation 'ExtraFunctions::RemoveIndexFromAnArrayOfPositions<Message>'
1 errors, 0 warnings
In diesem Fall enthält die Struktur „Message“ nicht das Element „Ticket“.
Zusatzfunktion zur Wiederholung einer Textzeichenfolge
Schließlich gibt es noch eine nützliche Funktion, die den Text so oft wie nötig wiederholt. Dies ist besonders nützlich für den Druck von Tabellen oder die visuelle Trennung von Informationen.
Umsetzung:
string StringRepeat(string str, int count) { string result = ""; for(int i = 0; i < count; i++) result += str; return result; }
Zum Beispiel beim Aufruf von:
StringRepeat("-", 10) мы получим "----------".
Diese fortschrittlichen Funktionen vereinfachen die Arbeit mit Arrays erheblich und verbessern die Lesbarkeit des Codes. Sie bieten effiziente und vielseitige Werkzeuge für ein dynamisches und präzises Risikomanagement in unseren Handelsframeworks.
Dynamisches Risiko aufbauen
Damit sind wir beim wichtigsten Teil angelangt: der Umsetzung des dynamischen Risikos. Dazu werden wir zwei Schlüsselfunktionen nutzen, die die Bearbeitung erheblich vereinfachen und die Anpassungsfähigkeit verbessern.
Bevor wir beginnen, müssen wir folgende Bibliothek einbinden:
#include <Generic\HashMap.mqh> Diese Bibliothek wird uns dabei helfen, Daten im Zusammenhang mit dem dynamischen Risiko richtig zu organisieren und zu verarbeiten.
Dynamische Risiko-Initialisierungsfunktion
In Anlehnung an das ursprüngliche Konzept, das in den vorangegangenen Artikeln vorgestellt wurde, wird unser dynamisches Risikosystem auf einer Struktur beruhen, die zwei Double-Arrays enthält: eines, um die neuen anzuwendenden Risikowerte zu speichern, und das andere, um die Saldenniveaus oder Prozentsätze anzugeben, die diese Änderungen auslösen.
Aufgrund der Einschränkungen der MQL5-Sprache ist es nicht möglich, Double-Arrays direkt als Parameter an den Expert Advisor zu übergeben. Um diese Einschränkung zu umgehen, verwenden wir kommagetrennte Zeichenketten und konvertieren sie dann mithilfe der zuvor implementierten Funktionen in numerische Arrays.
Die Hauptinitialisierungsfunktion für das dynamische Risiko konvertiert diese Zeichenketten in numerische Arrays, prüft auf Duplikate und stellt sicher, dass die Werte korrekt sortiert sind.
Die Funktionsdeklaration lautet wie folgt:
void SetDynamicGMLPO(string percentages_to_activate, string risks_to_be_applied, ENUM_REVISION_TYPE revision_type_);
Das interne Verfahren läuft wie folgt ab:
1. Validierung der dynamischen Risikonutzung
Bevor wir eine Operation durchführen, überprüfen wir, ob der GMLPO-Prozentsatz korrekt definiert ist:
if(this.gmlpo.assigned_percentage == 0) return; if(this.gmlpo.mode_calculation_risk == money) { this.ActivateDynamicRiskPerOperation = false; Print(EA_NAME, __FUNCTION__, "::'Money' mode is not valid for dynamic risk, change it to 'Percentage %' or change the group mode to 'No dynamic risk for risk per operation' "); return; }
2. Zweck der gewählten Prüfungsart
this.revision_type = revision_type_;
3. Konvertierung von Zeichenketten in numerische Arrays
Mit den zuvor erstellten Funktionen wandeln wir die Strings in Double-Arrays um:
//--- ExtraFunctions::StringToArray(this.dynamic_gmlpos.balance_to_activate_the_risk, percentages_to_activate, TYPE_DOUBLE, ','); ExtraFunctions::StringToArray(this.dynamic_gmlpos.risk_to_be_adjusted, risks_to_be_applied, TYPE_DOUBLE, ',');
4. Validierung der resultierenden Arrays
//--- if(this.dynamic_gmlpos.risk_to_be_adjusted.Size() < 1 && this.dynamic_gmlpos.balance_to_activate_the_risk.Size() < 1) { Print(EA_NAME, __FUNCTION__, "::Critical error: the size of the array is less than 1"); this.ActivateDynamicRiskPerOperation = false; return; } if(this.dynamic_gmlpos.risk_to_be_adjusted.Size() != this.dynamic_gmlpos.balance_to_activate_the_risk.Size()) { Print(EA_NAME, __FUNCTION__, "::Critical error the double arrays for the risk due to dynamic operation are not equal"); this.ActivateDynamicRiskPerOperation = false; return; } Print(EA_NAME, " Arrays before revision"); PrintArrayAsTable(dynamic_gmlpos.balance_to_activate_the_risk, "Negative percentages to modify the risk", "balance"); PrintArrayAsTable(dynamic_gmlpos.risk_to_be_adjusted, "Risk to be adjusted", "new risk");
Als Nächstes müssen wir sicherstellen, dass beide Arrays die gleiche Länge haben und nicht leer sind. Andernfalls werden wir das dynamische Risiko deaktivieren, um Fehler zu vermeiden.
5. Reinigung und Vorbereitung der endgültigen Struktur
Schließlich löschen wir die HashMap, wählen den entsprechenden Referenzsaldo auf der Grundlage des Kontotyps (FTMO oder persönlich) und bereiten ein Hilfsarray für mögliche doppelte oder ungültige Indizes vor:
balanceRiskMap.Clear(); this.chosen_balance = this.mode_risk_managemet == propfirm_ftmo ? this.account_balance_propfirm : AccountInfoDouble(ACCOUNT_BALANCE); int indexes_to_remove[];
Diese Funktion bietet ein zuverlässiges, robustes dynamisches Risiko-Setup, das auf die Bedürfnisse jedes Kontos oder jeder Handelsstrategie zugeschnitten ist und ein effektives und präzises Risikomanagement gewährleistet.
6. Schleife zum Hinzufügen gültiger Elemente zur HashMap
Als Nächstes implementieren wir eine Schlüsselschleife, die der HashMap nur gültige Elemente hinzufügt und so ein organisiertes und effizientes dynamisches Risikomanagement gewährleistet. Elemente werden unter den folgenden Bedingungen als ungültig betrachtet:
- Wenn der Prozentsatz, der das Risiko auslöst, kleiner oder gleich Null ist.
- Wenn der neue Risikowert kleiner als oder gleich null ist.
- Wenn das Element bereits in der HashMap vorhanden ist (Duplikat).
Wenn wir ein ungültiges Element entdecken, fügen wir es vorübergehend dem Array indexes_to_remove hinzu, um es später zu entfernen. Bei der Auswertung beider Arrays (balance_to_activate_the_risk und risk_to_be_adjusted) reicht es aus, wenn einer der beiden Werte in einem Paar ungültig ist, um das gesamte Paar auszuschließen und die Datenintegrität und -konsistenz zu wahren.
Implementierung der Schleife:
//--- for(int i = 0 ; i < ArraySize(dynamic_gmlpos.balance_to_activate_the_risk) ; i++) { if(dynamic_gmlpos.balance_to_activate_the_risk[i] <= 0) { Print(EA_NAME, " (Warning) The percentage value that will be exceeded to modify the risk is 0 or less than this (it will not be taken into account)"); ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i); continue; } if(dynamic_gmlpos.risk_to_be_adjusted[i] <= 0) { Print(EA_NAME, " (Warning) The new percentage to which the field is modified is 0 or less than this (it will not be taken into account)"); ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i); continue; } if(balanceRiskMap.ContainsKey(dynamic_gmlpos.balance_to_activate_the_risk[i]) == false) balanceRiskMap.Add(dynamic_gmlpos.balance_to_activate_the_risk[i], dynamic_gmlpos.risk_to_be_adjusted[i]); else ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i); }
Nehmen wir zum Beispiel an, wir haben diese Arrays:
- [1, 3, 5]
- [0.1, 0.3, 0.0]
In diesem Fall ist das letzte Paar (5 – 0,0) ungültig, da der bereinigte Risikowert Null ist, sodass 5 aus dem entsprechenden Array entfernt wird.
7. Entfernen von Duplikaten und ungültigen Elementen
Nachdem wir ungültige oder doppelte Einträge identifiziert haben, gehen wir dazu über, sie zu entfernen. Außerdem reorganisieren wir das Hauptfeld balance_to_activate_the_risk, um die richtige Reihenfolge und Datenkonsistenz zu gewährleisten:
//--- ExtraFunctions::RemoveMultipleIndexes(dynamic_gmlpos.balance_to_activate_the_risk, indexes_to_remove); ArraySort(dynamic_gmlpos.balance_to_activate_the_risk); ArrayResize(dynamic_gmlpos.risk_to_be_adjusted, ArraySize(dynamic_gmlpos.balance_to_activate_the_risk));
Eine Größenänderung des Arrays risk_to_be_adjusted ist erforderlich, damit beide Arrays nach dem Löschen konsistent bleiben.
8. Anpassung und Umwandlung von Risikowerten
Als Nächstes werden die in balance_to_activate_the_risk gespeicherten Prozentwerte auf der Grundlage des ausgewählten Kontosaldos angepasst und in Geldbeträge umgerechnet. Das Array risk_to_be_adjusted wird mit den entsprechenden Werten aktualisiert:
//--- for(int i = 0 ; i < ArraySize(dynamic_gmlpos.balance_to_activate_the_risk) ; i++) { double value; balanceRiskMap.TryGetValue(this.dynamic_gmlpos.balance_to_activate_the_risk[i], value); dynamic_gmlpos.risk_to_be_adjusted[i] = value; dynamic_gmlpos.balance_to_activate_the_risk[i] = this.chosen_balance - (this.chosen_balance * (dynamic_gmlpos.balance_to_activate_the_risk[i] / 100.0)); }
9. Endgültige Initialisierung des dynamischen Risikos
Schließlich initialisieren wir die erforderlichen Variablen, um sicherzustellen, dass das dynamische Risiko korrekt funktioniert:
//--- this.index_gmlpo = 0; this.ActivateDynamicRiskPerOperation = true; this.TheMinimumValueIsExceeded = false; this.NewBalanceToOvercome = 0.00; Print(EA_NAME, " Arrays ready: "); PrintArrayAsTable(dynamic_gmlpos.balance_to_activate_the_risk, "Negative percentages to modify the risk", "balance"); PrintArrayAsTable(dynamic_gmlpos.risk_to_be_adjusted, "Risk to be adjusted", "new risk");
Auf diese Weise ist die dynamische Risikoanpassung genau, effizient und passt sich kontinuierlich an die aktuellen Indikatoren unserer Handelsstrategie an.
Die ganze Funktion:
//+------------------------------------------------------------------+ //| Function to set dynamic risks per operation | //+------------------------------------------------------------------+ void CRiskManagemet::SetDynamicGMLPO(string percentages_to_activate, string risks_to_be_applied, ENUM_REVISION_TYPE revision_type_) { if(this.gmlpo.assigned_percentage <= 0) return; if(this.gmlpo.mode_calculation_risk == money) { this.ActivateDynamicRiskPerOperation = false; Print(EA_NAME, __FUNCTION__, "::'Money' mode is not valid for dynamic risk, change it to 'Percentage %' or change the group mode to 'No dynamic risk for risk per operation' "); return; } //--- this.revision_type = revision_type_; //--- ExtraFunctions::StringToArray(this.dynamic_gmlpos.balance_to_activate_the_risk, percentages_to_activate, TYPE_DOUBLE, ','); ExtraFunctions::StringToArray(this.dynamic_gmlpos.risk_to_be_adjusted, risks_to_be_applied, TYPE_DOUBLE, ','); //--- if(this.dynamic_gmlpos.risk_to_be_adjusted.Size() < 1 || this.dynamic_gmlpos.balance_to_activate_the_risk.Size() < 1) { Print(EA_NAME, __FUNCTION__, "::Critical error: the size of the array is less than 1"); this.ActivateDynamicRiskPerOperation = false; return; } if(this.dynamic_gmlpos.risk_to_be_adjusted.Size() != this.dynamic_gmlpos.balance_to_activate_the_risk.Size()) { Print(EA_NAME, __FUNCTION__, "::Critical error the double arrays for the risk due to dynamic operation are not equal"); this.ActivateDynamicRiskPerOperation = false; return; } Print(EA_NAME, " Arrays before revision"); PrintArrayAsTable(dynamic_gmlpos.balance_to_activate_the_risk, "Negative percentages to modify the risk", "balance"); PrintArrayAsTable(dynamic_gmlpos.risk_to_be_adjusted, "Risk to be adjusted", "new risk"); //--- balanceRiskMap.Clear(); this.chosen_balance = this.mode_risk_managemet == propfirm_ftmo ? this.account_balance_propfirm : AccountInfoDouble(ACCOUNT_BALANCE); int indexes_to_remove[]; //--- for(int i = 0 ; i < ArraySize(dynamic_gmlpos.balance_to_activate_the_risk) ; i++) { if(dynamic_gmlpos.balance_to_activate_the_risk[i] <= 0) { Print(EA_NAME, " (Warning) The percentage value that will be exceeded to modify the risk is 0 or less than this (it will not be taken into account)"); ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i); continue; } if(dynamic_gmlpos.risk_to_be_adjusted[i] <= 0) { Print(EA_NAME, " (Warning) The new percentage to which the field is modified is 0 or less than this (it will not be taken into account)"); ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i); continue; } if(balanceRiskMap.ContainsKey(dynamic_gmlpos.balance_to_activate_the_risk[i]) == false) balanceRiskMap.Add(dynamic_gmlpos.balance_to_activate_the_risk[i], dynamic_gmlpos.risk_to_be_adjusted[i]); else ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i); } //--- ExtraFunctions::RemoveMultipleIndexes(dynamic_gmlpos.balance_to_activate_the_risk, indexes_to_remove); ArraySort(dynamic_gmlpos.balance_to_activate_the_risk); ArrayResize(dynamic_gmlpos.risk_to_be_adjusted, ArraySize(dynamic_gmlpos.balance_to_activate_the_risk)); //--- for(int i = 0 ; i < ArraySize(dynamic_gmlpos.balance_to_activate_the_risk) ; i++) { double value; balanceRiskMap.TryGetValue(this.dynamic_gmlpos.balance_to_activate_the_risk[i], value); dynamic_gmlpos.risk_to_be_adjusted[i] = value; dynamic_gmlpos.balance_to_activate_the_risk[i] = this.chosen_balance - (this.chosen_balance * (dynamic_gmlpos.balance_to_activate_the_risk[i] / 100.0)); } //--- this.index_gmlpo = 0; this.ActivateDynamicRiskPerOperation = true; this.TheMinimumValueIsExceeded = false; this.NewBalanceToOvercome = 0.00; Print(EA_NAME, " Arrays ready: "); PrintArrayAsTable(dynamic_gmlpos.balance_to_activate_the_risk, "Negative percentages to modify the risk", "balance"); PrintArrayAsTable(dynamic_gmlpos.risk_to_be_adjusted, "Risk to be adjusted", "new risk"); }
Klare und detaillierte Erläuterung der Funktion zur Änderung des dynamischen Risikoprozentsatzes (GMLPO)
Im Folgenden wird die Funktion CheckAndModifyThePercentageOfGmlpo Schritt für Schritt erklärt. Es ermöglicht ein dynamisches Risikomanagement pro Handel und passt sich automatisch an das erreichte Kapitalniveau des Handelskontos an.
1. Eine erste Validierung
Die Funktion prüft zunächst, ob das dynamische Risikomanagement aktiviert ist. Wenn sie deaktiviert ist, kehrt die Funktion sofort zurück.
if(!this.ActivateDynamicRiskPerOperation) return;
2. Validierung des aktuellen Saldos
Als Nächstes ruft die Funktion das Kontokorrentkapital ab (den tatsächlichen Kontowert, einschließlich gleitender Gewinne oder Verluste). Dieser Wert wird mit dem zuvor ausgewählten Saldo (chosen_balance) verglichen.
Wenn das aktuelle Kapital den ausgewählten Saldo übersteigt und die nächste Zielsaldenebene noch nicht erreicht ist, nimmt die Funktion keine Änderungen vor und kehrt zurück.
double account_equity = AccountInfoDouble(ACCOUNT_EQUITY); if(account_equity > this.chosen_balance && this.NewBalanceToOvercome != this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]) return;
3. Risikoveränderung bei abnehmendem Kapital
Fällt das aktuelle Kapital unter die festgelegten Werte, wird der nächst niedrigere Saldo ermittelt.
-
Wenn der Saldo unter ein bestimmtes Niveau gefallen ist:
- Es wird ein neuer Risikoprozentsatz für diese Stufe festgelegt.
- Die internen Variablen werden aktualisiert, um das neue Risikoniveau widerzuspiegeln.
if(this.TheMinimumValueIsExceeded == false) { if(account_equity < this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]) { PrintFormat("%s The risk percentage per operation has been modified because the value of %.2f was exceeded", EA_NAME, this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]); while(IsStopped() == false && index_gmlpo < (int)this.dynamic_gmlpos.balance_to_activate_the_risk.Size()) { if(index_gmlpo < (int)this.dynamic_gmlpos.balance_to_activate_the_risk.Size() - 1) { if(this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo] > account_equity && account_equity > this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo + 1]) { this.NewBalanceToOvercome = this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]; this.gmlpo.assigned_percentage = this.dynamic_gmlpos.risk_to_be_adjusted[index_gmlpo]; index_gmlpo ++; PrintFormat("%s The new balance to overcome: %.2f", EA_NAME, NewBalanceToOvercome); break; } } else if(index_gmlpo == this.dynamic_gmlpos.balance_to_activate_the_risk.Size() - 1) { if(this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo] > account_equity) { this.NewBalanceToOvercome = this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]; this.gmlpo.assigned_percentage = this.dynamic_gmlpos.risk_to_be_adjusted[index_gmlpo]; this.TheMinimumValueIsExceeded = true; PrintFormat("%s The new balance to overcome: %.2f", EA_NAME, NewBalanceToOvercome); PrintFormat("%s The minimum value %.2f was exceeded", EA_NAME, this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]); break; } } index_gmlpo++; } PrintFormat("%s The new risk per operation %.2f", EA_NAME, this.gmlpo.assigned_percentage); SetGMLPO(); } }
4. Wiederherstellung des Risikos bei wieder steigendem Kapital
Wenn das Kapital später wieder ansteigt und das zuvor erreichte Zielniveau überschreitet:
-
Unser Rahmenwerk passt das Risiko pro Trade erneut an und stellt die vorherigen Werte auf der Grundlage der in den Arrays definierten Werte wieder her.
if(this.NewBalanceToOvercome > 0.00) { if(account_equity > this.NewBalanceToOvercome) { PrintFormat("%s Equity %.2f exceeded balance to shift risk to %.2f", EA_NAME, account_equity, NewBalanceToOvercome); while(!IsStopped() && index_gmlpo > 0) { if(index_gmlpo > 0) { if(this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo] < account_equity && account_equity < this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo - 1]) { break; } this.index_gmlpo--; } } this.TheMinimumValueIsExceeded = false; if(this.index_gmlpo == 0) { Print(EA_NAME, " Excellent, the balance has been positively exceeded"); PrintFormat("%s The risk percentage per operation has been modified because the value of %.2f was exceeded", EA_NAME, this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]); this.gmlpo.assigned_percentage = this.gmlpo_percentage; this.NewBalanceToOvercome = 0.00; PrintFormat("%s The new risk per operation %.2f", EA_NAME, this.gmlpo.assigned_percentage); SetGMLPO(); } else if(index_gmlpo > 0) { Print(EA_NAME, " Excellent, the balance has been positively exceeded"); PrintFormat("%s The risk percentage per operation has been modified because the value of %.2f was exceeded", EA_NAME, this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]); this.NewBalanceToOvercome = this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo - 1]; this.gmlpo.assigned_percentage = this.dynamic_gmlpos.risk_to_be_adjusted[index_gmlpo - 1]; PrintFormat("%s The new risk per operation %.2f", EA_NAME, this.gmlpo.assigned_percentage); PrintFormat("%s The new balance to overcome: %.2f", EA_NAME, NewBalanceToOvercome); SetGMLPO(); } } }
Schlussfolgerung
In dieser Artikelserie haben wir Schritt für Schritt untersucht, wie man ein umfassendes, zuverlässiges Risikomanagementsystem entwickelt. Im abschließenden Teil haben wir die Details ausgearbeitet und ein fortgeschrittenes, äußerst nützliches Konzept des dynamischen Risikos pro Trade eingeführt. Dieses Tool passt das Risikoniveau automatisch auf der Grundlage der tatsächlichen Ergebnisse des Kontos an, was für die Erhaltung des Kapitals und die Verbesserung der Handelsleistung von entscheidender Bedeutung ist.
Ich hoffe, dass dieses Material hilfreich ist, vor allem, wenn Sie gerade Ihre Reise in die Welt der MQL5-Programmierung beginnen.
Im nächsten Abschnitt über das Risikomanagement werden wir diesen Ansatz weiter ausbauen und lernen, wie wir alles, was wir gelernt haben, in der Praxis anwenden können, indem wir dieses fortschrittliche Managementsystem in einen Handelsroboter integrieren.
Zu diesem Zweck wird der zuvor entwickelte Indikator Order Blocks verwendet:
Ferner werden wir die spezifischen Vorteile eines wirksamen Risikomanagements im Vergleich zu einer Arbeit ohne jegliche Kontrolle deutlich machen. Schließlich werden wir spezifische Einstellungen konfigurieren, die den täglichen Einsatz dieser fortschrittlichen Tools in jeder automatisierten Strategie erheblich vereinfachen.
In diesem Artikel verwendete oder aktualisierte Dateien:
| Dateiname | Typ | Beschreibung |
|---|---|---|
| Risk_Management.mqh | .mqh (Include-Datei) | Die Hauptdatei enthält allgemeine Funktionen und die Implementierung der Klasse CRiskManagement, die für das Risikomanagement im System zuständig ist. Diese Datei definiert und erweitert alle Funktionen im Zusammenhang mit der Gewinn- und Verlustverwaltung. |
Übersetzt aus dem Spanischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/es/articles/17508
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
Neuronale Netze im Handel: Integration der Chaostheorie in die Zeitreihenprognose (Attraos)
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Marktsimulation (Teil 13): Sockets (VII)
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.