Contrôler la Pente de la Courbe d' Équilibre Pendant le Travail d'un Expert Advisor
Introduction
Cet article décrit l'une des approches qui permet d'améliorer les performances des Expert Advisors un feedback. Dans ce cas, le feedback sera basé sur la mesure de la pente de la courbe d'équilibre. Le contrôle de la pente s'effectue automatiquement en réglant le volume de travail. Un Expert Advisor peut trader dans les modes suivants : avec un volume de coupe, avec une quantité de travail de lots (selon celle initialement ajustée) et avec un volume intermédiaire. Le mode de fonctionnement est échangé automatiquement.
Différentes caractéristiques de régulation sont utilisées dans la chaîne de feedback : échelonnée, échelonnée avec hystérésis, linéaire. Il permet d'ajuster le système de contrôle de la pente de la courbe d'équilibre aux caractéristiques d'un certain système.
L'idée principale est d'automatiser le processus de prise de décision pour un trader tout en surveillant son propre système de trading Il est raisonnable de réduire les risques pendant les périodes défavorables de son fonctionnement. Au retour au mode de fonctionnement normal, les risques peuvent être rétablis à leur niveau initial.
Bien sûr, ce système n'est pas une panacée, et il ne transformera pas un Expert Advisor perdant en un conseiller rentable. D'une certaine manière, il s'agit d'un ajout au MM (gestion de l'argent) d'Expert Advisor qui l'empêche de subir des pertes considérables sur un compte.
L'article comprend une bibliothèque, qui permet d'intégrer cette fonction au code de n'importe quel Expert Advisor.
Principe de Fonctionnement
Examinons le principe de fonctionnement du système, qui contrôle la pente de la courbe d'équilibre. Supposons que nous ayons un Expert Advisor en trading. Sa courbe d'équilibre hypothétique se présente comme suit :
Figure 1. Principe de fonctionnement du système qui contrôle la pente de la courbe d'équilibre
La courbe d'équilibre initiale pour l'Expert Advisor qui utilise un volume constant d'opérations de trade est illustrée ci-dessus. Les trades clos sont indiqués avec les points rouges. Connectons ces points avec une ligne courbe, qui représente le changement d'équilibre de l'Expert Advisor pendant le trading (ligne noire épaisse).
Nous allons maintenant surveiller en continu l'angle de pente de cette ligne par rapport à l'axe du temps (indiqué par de fines lignes bleues). Ou pour être plus précis, avant d'ouvrir chaque trade par un signal, nous calculerons l'angle de pente par deux trades précédemment clos (ou par deux trades, pour que la description soit plus simple). Si l'angle de pente devient inférieur à la valeur spécifiée, notre système de contrôle commence à fonctionner ; il diminue le volume en fonction de la valeur calculée de l'angle et de la fonction de régulation indiquée.
De cette manière, si le commerce entre dans une période infructueuse, le volume diminue à partir de Vmax. à Vmindurant la Т3...Т5 période de trading. Après le T5 points, le trading est effectué avec un volume minimal indiqué - dans le mode de rejet du volume de trade. Une fois que la rentabilité de l'Expert Advisor est rétablie et que l'angle de pente de la courbe d'équilibre s'élève au-dessus de la valeur indiquée, le volume commence à augmenter. Cela se produit dans l'intervalle Т8...Т10. Après le point Т10, le volume des opérations de trade revient à l'état initial Vmax.
La courbe d'équilibre formée à la suite d'une telle régulation est représentée dans la partie inférieure de la fig. 1. Vous pouvez constater que le prélèvement initial de B1 à B2 a diminué et est devenu de B1 à B2*. Vous pouvez également observer que le bénéfice a légèrement diminué pendant la période de rétablissement du volume maximum Т8...Т10 - c'est le revers de la médaille.
La couleur verte met en évidence la partie de la courbe d'équilibre lorsque le trading a été effectué avec un volume minimal indiqué. La couleur jaune représente les parties de transition du volume maximum au minimum et inversement. Plusieurs variantes de transition sont possibles ici :
- échelonné - changements de volume par étapes discrètes du volume maximum au minimum et inversement ;
- linéaire - le volume est modifié linéairement en fonction de l'angle de pente de la courbe d'équilibre dans l'intervalle régulé ;
- échelonné avec hystérésis - la transition du volume maximum au minimum et inversement est effectuée à des valeurs de différence de l'angle de pente;
Illustrons-le en images :
Figure 2. Types de caractéristiques de régulation
Les caractéristiques de régulation affectent les taux du système de contrôle - le retard d'activation/désactivation, le processus de transition du volume maximum au volume minimum et inversement. Il est recommandé de choisir une caractéristique sur une base expérimentale pour obtenir les meilleurs résultats de test.
Ainsi, nous améliorons le système de trading avec le feedback basé sur l'angle de pente de la courbe d'équilibre. Notez qu'une telle régulation du volume ne convient qu'aux systèmes, qui n'ont pas le volume dans le cadre du système de trading lui-même. Par exemple, si le principe de la Martingale est utilisé, vous ne pouvez pas utiliser ce système directement sans modifier l'Expert Advisor initial.
De plus, nous devons attirer notre attention sur les points importants suivants :
- l'efficacité de la gestion de la pente de la ligne d'équilibre dépend directement du rapport du volume de travail en mode de fonctionnement normal au volume en mode de rejet de volume. Plus grand ce ratio est, plus efficace la gestion est C'est la raison pour laquelle le volume de travail initial doit être largement supérieur au minimum possible.
- la durée moyenne d'altération des hausses et baisses de la balance de l'Expert Advisor doit être largement plus grande que le temps de réaction du système de contrôle. Sinon, le système ne parviendra pas à régler la pente de la courbe d'équilibre. Plus le rapport entre la période moyenne et le temps de réaction est élevé, plus efficace le système est. Cette exigence touche presque chaque système de régulation automatique.
Implémentation en MQL5 à l'aide de la programmation orientée-objet
Rédigeons une bibliothèque qui assure l'approche décrite ci-dessus. Pour ce faire, utilisons la nouvelle fonctionnalité de MQL5 - l'approche orientée-objet. Cette approche permet d’élaborer et d'étendre facilement notre bibliothèque à l'avenir sans réécrire de grandes parties du code à partir de zéro.
Classe du TradeSymbol
Étant donné que le test en monnaies multiples est implémenté dans la nouvelle plate-forme MetaTrader 5, nous avons besoin d'une classe qui renferme en elle-même l'ensemble du travail avec n'importe quel symbole de travail. Il permet d'utiliser cette bibliothèque dans des Expert Advisors en monnaies multiples. Cette classe ne couvre pas directement le système de contrôle, elle est auxiliaire. Ainsi, cette classe sera utilisée pour les opérations avec le symbole de travail.
//--------------------------------------------------------------------- // Operations with work symbol: //--------------------------------------------------------------------- class TradeSymbol { private: string trade_symbol; // work symbol private: double min_trade_volume; // minimum allowed volume for trade operations double max_trade_volume; // maximum allowed volume for trade operations double min_trade_volume_step; // minimum change of volume double max_total_volume; // maximum change of volume double symbol_point; // size of one point double symbol_tick_size; // minimum change of price int symbol_digits; // number of digits after decimal point protected: public: void RefreshSymbolInfo( ); // refresh market information about the work symbol void SetTradeSymbol( string _symbol ); // set/change work symbol string GetTradeSymbol( ); // get work symbol double GetMaxTotalLots( ); // get maximum cumulative volume double GetPoints( double _delta ); // get change of price in points public: double NormalizeLots( double _requied_lot ); // get normalized trade volume double NormalizePrice( double _org_price ); // get normalized price with consideration of step of change of quote public: void TradeSymbol( ); // constructor void ~TradeSymbol( ); // destructor };
La structure de la classe est très simple. Le but est d'obtenir, de stocker et de traiter les informations actuelles du marché par un symbole indiqué. Les principales méthodes sont TradeSymbol::RefreshSymbolInfo, TradeSymbol::NormalizeLots, TradeSymbol::NormalizePrice.. Considérons-les un par un.
La méthode TradeSymbol::RefreshSymbolInfo est destinée à l’actualisation des informations du marché par le symbole de travail.
//--------------------------------------------------------------------- // Refresh market information by work symbol: //--------------------------------------------------------------------- void TradeSymbol::RefreshSymbolInfo( ) { // If a work symbol is not set, don't do anything: if( GetTradeSymbol( ) == NULL ) { return; } // Calculate parameters necessary for normalization of volume: min_trade_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_MIN ); max_trade_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_MAX ); min_trade_volume_step = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_STEP ); max_total_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_LIMIT ); symbol_point = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_POINT ); symbol_tick_size = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_TRADE_TICK_SIZE ); symbol_digits = ( int )SymbolInfoInteger( GetTradeSymbol( ), SYMBOL_DIGITS ); }
Faites attention à un point important qui est utilisé dans plusieurs méthodes. Étant donné que la réalisation actuelle de MQL5 ne permet pas d'utiliser un constructeur avec des paramètres, vous devez faire appel à la méthode suivante pour le réglage principal des symboles de travail :
void SetTradeSymbol( string _symbol ); // set/change work symbol
La méthode TradeSymbol::NormalizeLots est utilisée pour obtenir un volume correct et normalisé. Nous savons que la taille d'une position ne peut pas être inférieure à la valeur minimale possible autorisée par le courtier. L'étape minimale de changement d'une position est également déterminée par le courtier, et elle peut différer. Cette méthode renvoie la valeur de volume la plus proche à partir du bas.
EIle vérifie également si le volume de la position supposée dépasse la valeur maximale autorisée par le courtier.
//--------------------------------------------------------------------- // Get normalized trade volume: //--------------------------------------------------------------------- // - input necessary volume; // - output is normalized volume; //--------------------------------------------------------------------- double TradeSymbol::NormalizeLots( double _requied_lots ) { double lots, koeff; int nmbr; // If a work symbol is not set, don't do anything: if( GetTradeSymbol( ) == NULL ) { return( 0.0 ); } if( this.min_trade_volume_step > 0.0 ) { koeff = 1.0 / min_trade_volume_step; nmbr = ( int )MathLog10( koeff ); } else { koeff = 1.0 / min_trade_volume; nmbr = 2; } lots = MathFloor( _requied_lots * koeff ) / koeff; // Lower limit of volume: if( lots < min_trade_volume ) { lots = min_trade_volume; } // Upper limit of volume: if( lots > max_trade_volume ) { lots = max_trade_volume; } lots = NormalizeDouble( lots, nmbr ); return( lots ); }
La méthode TradeSymbol::NormalizePrice est utilisée pour obtenir un prix correct et normalisé. Étant donné que le nombre de chiffres significatifs après la virgule (précision du prix) doit être déterminé pour un symbole donné, nous devons tronquer le prix. En plus de cela, certains symboles (par exemple, les contrats à terme) ont une phase minimum de modification de prix supérieur à un point. C'est pourquoi nous devons faire en sorte que les valeurs de prix soient des multiples du minimum de discrétion.
//--------------------------------------------------------------------- // Normalization of price with consideration of step of price change: //--------------------------------------------------------------------- double TradeSymbol::NormalizePrice( double _org_price ) { // Minimal step of quote change in points: double min_price_step = NormalizeDouble( symbol_tick_size / symbol_point, 0 ); double norm_price = NormalizeDouble( NormalizeDouble(( NormalizeDouble( _org_price / symbol_point, 0 )) / min_price_step, 0 ) * min_price_step * symbol_point, symbol_digits ); return( norm_price ); }
Le prix non-normalisé nécessaire est introduit dans la fonction. Et il renvoie le prix normalisé, qui est le plus proche du nécessaire.
Le but des autres méthodes est clairement décrit dans les commentaires ; il ne nécessite aucune description supplémentaire.
Classe TBalanceHistory
Cette classe, est destinée à fonctionner avec l'historique du solde d'un compte, qui est clair pour son nom. C'est aussi une classe de base pour plusieurs classes décrites ci-dessous. L'objectif principal de cette classe est d’accéder à l'historique du trade d'un Expert Advisor. De plus, vous pouvez filtrer l'historique par symbole de travail, par « nombre magique », par date de début de suivi de l'Expert Advisor ou par les trois éléments simultanément.
//--------------------------------------------------------------------- // Operations with balance history: //--------------------------------------------------------------------- class TBalanceHistory { private: long current_magic; // value of "magic number" when accessing the history of deals ( 0 - any number ) long current_type; // type of deals ( -1 - all ) int current_limit_history; // limit of depth of history ( 0 - all history ) datetime monitoring_begin_date; // date of start of monitoring history of deals int real_trades; // number of actual trades already performed protected: TradeSymbol trade_symbol; // operations with work symbol protected: // "Raw" arrays: double org_datetime_array[ ]; // date/time of trade double org_result_array[ ]; // result of trade // Arrays with data grouped by time: double group_datetime_array[ ]; // date/time of trade double group_result_array[ ]; // result of trade double last_result_array[ ]; // array for storing results of last trades ( points on the Y axis ) double last_datetime_array[ ]; // array for storing time of last trades ( points on the X axis ) private: void SortMasterSlaveArray( double& _m[ ], double& _s[ ] ); // synchronous ascending sorting of two arrays public: void SetTradeSymbol( string _symbol ); // set/change work symbol string GetTradeSymbol( ); // get work symbol void RefreshSymbolInfo( ); // refresh market information by work symbol void SetMonitoringBeginDate( datetime _dt ); // set date of start of monitoring datetime GetMonitoringBeginDate( ); // get date of start of monitoring void SetFiltrParams( long _magic, long _type = -1, int _limit = 0 );// set parameters of filtration of deals public: // Get results of last trades: int GetTradeResultsArray( int _max_trades ); public: void TBalanceHistory( ); // constructor void ~TBalanceHistory( ); // destructor };
Les paramètres de filtrage lors de l’interprétation des résultats des derniers trades et de l'historique sont définis à l'aide de la méthode TBalanceHistory::SetFiltrParams. Elle dispose des paramètres d'entrée suivants :
- __magic - "nombre magique" de trade qui doit être lu depuis l’historique Si la valeur zéro est indiquée, donc les trades seront lus avec n’importe quel «nombre magique».
- _type -type de deals qui doivent être lus Il peut avoir les valeurs suivantes - DEAL_TYPE_BUY (pour la lecture des trades longs uniquement), DEAL_TYPE_SELL (pour la lecture des trades courts uniquement) et -1 (pour la lecture des trades longs et courts).
- __limit - limite la profondeur de l’historique analysé des trades. S'il est égal à zéro, tout l'historique disponible est analysé.
Par défaut, les valeurs suivantes sont définies lors de la création de l'objet de la classe TBalanceHistory_magic = 0, _type = -1, __limit = 0.
La méthode principale de cette classe est TBalanceHistory::GetTradeResultsArray. EIle est destinée à remplir les tableaux de membres de classe last_result_array et last_datetime_array avec les résultats des derniers trades. La méthode dispose des paramètres d'entrée suivants :
- __max_trades - nombre maximum de trades qui doivent être lus à partir de l'historique et écrits dans les tableaux de sortie. Vu que nous avons besoin d'au moins deux points pour calculer l'angle de pente, cette valeur ne doit pas être inférieure à deux. Si cette valeur est égale à zéro, tout l'historique disponible des trades est analysé. Le nombre de points nécessaires au calcul de la pente de la courbe d'équilibre est quasiment indiqué ici.
//--------------------------------------------------------------------- // Reads the results of last (by time) trades to arrays: //--------------------------------------------------------------------- // - returns the number of actually read trades but not more than specified; //--------------------------------------------------------------------- int TBalanceHistory::GetTradeResultsArray( int _max_trades ) { int index, limit, count; long deal_type, deal_magic, deal_entry; datetime deal_close_time, current_time; ulong deal_ticket; // ticket of deal double trade_result; string symbol, deal_symbol; real_trades = 0; // Number of trades should be no less than two: if( _max_trades < 2 ) { return( 0 ); } // If a work symbol is not specified, don't do anything: symbol = trade_symbol.GetTradeSymbol( ); if( symbol == NULL ) { return( 0 ); } // Request the history of deals and orders from the specified time to the current moment: if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true ) { return( 0 ); } // Calculate number of trades: count = HistoryDealsTotal( ); // If there are less trades in the history than it is necessary, then exit: if( count < _max_trades ) { return( 0 ); } // If there are more trades in the history than it is necessary, then limit them: if( current_limit_history > 0 && count > current_limit_history ) { limit = count - current_limit_history; } else { limit = 0; } // If needed, adjust dimension of "raw" arrays by the specified number of trades: if(( ArraySize( org_datetime_array )) != ( count - limit )) { ArrayResize( org_datetime_array, count - limit ); ArrayResize( org_result_array, count - limit ); } // Fill the "raw" array with trades from history base: real_trades = 0; for( index = count - 1; index >= limit; index-- ) { deal_ticket = HistoryDealGetTicket( index ); // If those are not closed deals, don't go further: deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY ); if( deal_entry != DEAL_ENTRY_OUT ) { continue; } // Check "magic number" of deal if necessary: deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC ); if( current_magic != 0 && deal_magic != current_magic ) { continue; } // Check symbol of deal: deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL ); if( symbol != deal_symbol ) { continue; } // Check type of deal if necessary: deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE ); if( current_type != -1 && deal_type != current_type ) { continue; } else if( current_type == -1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL )) { continue; } // Check time of closing of deal: deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME ); if( deal_close_time < monitoring_begin_date ) { continue; } // So, we can read another trade: org_datetime_array[ real_trades ] = deal_close_time / 60; org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME ); real_trades++; } // if there are less trades than necessary, return: if( real_trades < _max_trades ) { return( 0 ); } count = real_trades; // Sort the "raw" array by date/time of closing the order: SortMasterSlaveArray( org_datetime_array, org_result_array ); // If necessary, adjust dimension of group arrays for the specified number of points: if(( ArraySize( group_datetime_array )) != count ) { ArrayResize( group_datetime_array, count ); ArrayResize( group_result_array, count ); } ArrayInitialize( group_datetime_array, 0.0 ); ArrayInitialize( group_result_array, 0.0 ); // Fill the output array with grouped data ( group by the identity of date/time of position closing ): for( index = 0; index < count; index++ ) { // Get another trade: deal_close_time = ( datetime )org_datetime_array[ index ]; trade_result = org_result_array[ index ]; // Now check if the same time already exists in the output array: current_time = ( datetime )group_datetime_array[ real_trades ]; if( current_time > 0 && MathAbs( current_time - deal_close_time ) > 0.0 ) { real_trades++; // move the pointer to the next element group_result_array[ real_trades ] = trade_result; group_datetime_array[ real_trades ] = deal_close_time; } else { group_result_array[ real_trades ] += trade_result; group_datetime_array[ real_trades ] = deal_close_time; } } real_trades++; // now this is the number of unique elements // If there are less trades than necessary, exit: if( real_trades < _max_trades ) { return( 0 ); } if( ArraySize( last_result_array ) != _max_trades ) { ArrayResize( last_result_array, _max_trades ); ArrayResize( last_datetime_array, _max_trades ); } // Write the accumulated data to the output arrays with reversed indexation: for( index = 0; index < _max_trades; index++ ) { last_result_array[ _max_trades - 1 - index ] = group_result_array[ index ]; last_datetime_array[ _max_trades - 1 - index ] = group_datetime_array[ index ]; } // In the output array replace the results of single trades with the accumulating total: for( index = 1; index < _max_trades; index++ ) { last_result_array[ index ] += last_result_array[ index - 1 ]; } return( _max_trades ); }
Des contrôles obligatoires sont effectués au début - si un symbole de travail est indiqué et si les paramètres d'entrée sont corrects.
Ensuite, nous lisons l'historique des trades et des commandes de la date indiquée à l’instant courant. Elle est réalisée dans la partie suivante du code :
// Request the history of deals and orders from the specified time to the current moment: if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true ) { return( 0 ); } // Calculate number of trades: count = HistoryDealsTotal( ); // If there are less trades in the history than it is necessary, then exit: if( count < _max_trades ) { return( 0 ); }
De plus, le nombre total de deals dans l'historique est vérifié. S'il est inférieur à ce qui est indiqué, d'autres actions n'ont aucun sens. Dès que les tableaux "bruts" sont préparés, le cycle de remplissage avec les informations de l'historique des trades est exécuté. Cela se fait de la manière suivante :
// Fill the "raw" array from the base of history of trades: real_trades = 0; for( index = count - 1; index >= limit; index-- ) { deal_ticket = HistoryDealGetTicket( index ); // If the trades are not closed, don't go further: deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY ); if( deal_entry != DEAL_ENTRY_OUT ) { continue; } // Check "magic number" of deal if necessary: deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC ); if( _magic != 0 && deal_magic != _magic ) { continue; } // Check symbols of deal: deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL ); if( symbol != deal_symbol ) { continue; } // Check type of deal if necessary: deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE ); if( _type != -1 && deal_type != _type ) { continue; } else if( _type == -1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL )) { continue; } // Check time of closing of deal: deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME ); if( deal_close_time < monitoring_begin_date ) { continue; } // So, we can rad another trade: org_datetime_array[ real_trades ] = deal_close_time / 60; org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME ); real_trades++; } // If there are less trades than necessary, exit: if( real_trades < _max_trades ) { return( 0 ); }
Au début, le ticket du deal de l'historique est lu à l'aide de la fonction HistoryDealGetTicket ; une lecture supplémentaire des détails du deal est effectuée à l'aide du ticket obtenu. Vu que nous ne nous intéressons qu'aux trades clos (nous allons analyser le solde), le type de deal est vérifié dans un premier temps. Cela se fait en appelant la fonction HistoryDealGetInteger avec le paramètre DEAL_ENTRY. Si la fonction renvoie DEAL_ENTRY_OUT, alors c'est la clôture d'une position.
Après ce "numéro magique" du deal, le type de deal (le paramètre d'entrée de la méthode est indiqué) et le symbole du deal sont vérifiés. Si tous les paramètres du deal répondent aux exigences, le dernier paramètre est vérifié - l'heure de clôture du deal. Cela se fait de la manière suivante :
// Check the time of closing of deal: deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME ); if( deal_close_time < monitoring_begin_date ) { continue; }
La date/heure du deal est comparée à la date/heure donnée de début de surveillance de l'historique. Si la date/heure du deal est supérieure à celle donnée, alors nous allons lire notre transaction dans le tableau - lisez le résultat du trade en points et l'heure du trade en minutes (dans ce cas, l'heure de clôture). Après cela, le compteur des deals lus real_trades est augmenté ; et le cycle continue.
Une fois que les tableaux "bruts" sont remplis avec la quantité d'informations nécessaire, nous devons trier le tableau où l'heure de clôture des deals est stockée. Dans le même temps, nous devons conserver la correspondance de l'heure de clôture dans le tableau org_datetime_array et les résultats des deals dans le tableau org_result_array. Cela se fait en utilisant la méthode spécialement écrite:
TBalanceHistory::SortMasterSlaveArray( double& _master[ ], double& _slave[ ] ). Le premier paramètre est _master - le tableau qui est trié par ordre croissant. Le deuxième paramètre est _slave - le tableau dont les éléments doivent être déplacés de manière synchronisée avec les éléments du premier tableau. Le tri est effectué via la méthode "bubble".
Après toutes les opérations décrites ci-dessus, nous avons deux tableaux avec le temps et les résultats des deals triés par temps. Puisqu'un seul point sur la courbe d'équilibre (point sur l'axe Y) peut correspondre à chaque instant du temps (point sur l'axe X), nous devons regrouper les éléments du tableau avec le même temps de clôture (s'il y en a) . La partie suivante du code assure cette opération :
// Fill the output array with grouped data ( group by identity of date/time of closing of position ): real_trades = 0; for( index = 0; index < count; index++ ) { // Get another trade: deal_close_time = ( datetime )org_datetime_array[ index ]; trade_result = org_result_array[ index ]; // Now check, if the same time already exists in the output array: current_time = ( datetime )group_datetime_array[ real_trades ]; if( current_time > 0 && MathAbs( current_time - deal_close_time ) > 0.0 ) { real_trades++; // move the pointer to the next element group_result_array[ real_trades ] = trade_result; group_datetime_array[ real_trades ] = deal_close_time; } else { group_result_array[ real_trades ] += trade_result; group_datetime_array[ real_trades ] = deal_close_time; } } real_trades++; // now this is the number of unique elements
Quasiment , tous les trades avec la "même" heure de clôture sont ajoutés ici. Les résultats sont écrits dans les tableaux TBalanceHistory::group_datetime_array (heure de clôture) et TBalanceHistory::group_result_array (résultats des trades). Ensuite , nous obtenons deux tableaux triés avec des éléments uniques. L'identité du temps dans ce cas est jugée en une minute. Cette transformation peut être graphiquement illustrée :
Figure 3. Groupement de deals en même temps
Tous les deals en moins d'une minute (partie gauche de la figure) sont regroupés en un ensemble avec arrondi du temps et ajoutant les résultats (partie droite de la figure). Il permet de lisser le « bavardage » du temps de clôture des deals et d'améliorer la stabilité de la régulation.
Ensuite, vous devez effectuer deux autres transformations des tableaux obtenus. Inversez l'ordre des éléments pour que le premier deal corresponde à l'élément zéro ; et remplacer les résultats des trades individuels par le total cumulé, c'est-à-dire par le solde. Cela se fait dans le fragment suivant du code:
// Write the accumulated data into output arrays with reversed indexation: for( index = 0; index < _max_trades; index++ ) { last_result_array[ _max_trades - 1 - index ] = group_result_array[ index ]; last_datetime_array[ _max_trades - 1 - index ] = group_datetime_array[ index ]; } // Replace the results of single trades with the cumulative total in the output array: for( index = 1; index < _max_trades; index++ ) { last_result_array[ index ] += last_result_array[ index - 1 ]; }
Classe TBalanceSlope
Cette classe est destinée à réaliser des opérations avec la courbe de solde d'un compte. Il est généré à partir de la classe TBalanceHistory ; et il hérite de toutes ses données et méthodes protégées et publiques. Examinons en détail sa structure :
//--------------------------------------------------------------------- // Operations with the balance curve: //--------------------------------------------------------------------- class TBalanceSlope : public TBalanceHistory { private: double current_slope; // current angle of slope of the balance curve int slope_count_points; // number of points ( trades ) for calculation of slope angle private: double LR_koeff_A, LR_koeff_B; // rates for the equation of the straight-line regression double LR_points_array[ ]; // array of point of the straight-line regression private: void CalcLR( double& X[ ], double& Y[ ] ); // calculate the equation of the straight-line regression public: void SetSlopePoints( int _number ); // set the number of points for calculation of angle of slope double CalcSlope( ); // calculate the slope angle public: void TBalanceSlope( ); // constructor void ~TBalanceSlope( ); // destructor };
Nous déterminerons l'angle de pente de la courbe d'équilibre par l'angle de pente de la ligne de régression linéaire tracée pour le nombre indiqué de points (trades) sur la courbe d'équilibre. Ainsi, tout d'abord, nous devons calculer l'équation de la régression linéaire de la forme suivante : A*x + B. La méthode suivante assure ce travail :
//--------------------------------------------------------------------- // Calculate the equation of the straight-line regression: //--------------------------------------------------------------------- // input parameters: // X[ ] - arras of values of number series on the X axis; // Y[ ] - arras of values of number series on the Y axis; //--------------------------------------------------------------------- void TBalanceSlope::CalcLR( double& X[ ], double& Y[ ] ) { double mo_X = 0, mo_Y = 0, var_0 = 0, var_1 = 0; int i; int size = ArraySize( X ); double nmb = ( double )size; // If the number of points is less than two, the curve cannot be calculated: if( size < 2 ) { return; } for( i = 0; i < size; i++ ) { mo_X += X[ i ]; mo_Y += Y[ i ]; } mo_X /= nmb; mo_Y /= nmb; for( i = 0; i < size; i++ ) { var_0 += ( X[ i ] - mo_X ) * ( Y[ i ] - mo_Y ); var_1 += ( X[ i ] - mo_X ) * ( X[ i ] - mo_X ); } // Value of the A coefficient: if( var_1 != 0.0 ) { LR_koeff_A = var_0 / var_1; } else { LR_koeff_A = 0.0; } // Value of the B coefficient: LR_koeff_B = mo_Y - LR_koeff_A * mo_X; // Fill the array of points that lie on the regression line: ArrayResize( LR_points_array, size ); for( i = 0; i < size; i++ ) { LR_points_array[ i ] = LR_koeff_A * X[ i ] + LR_koeff_B; } }
Nous utilisons ici la méthode des moindres carrés pour calculer l'erreur minimale de position de la droite de régression par rapport aux données initiales. Le tableau qui stocke les coordonnées Y, qui se trouvent sur la ligne calculée, est également rempli. Ce tableau n'est pas utilisé pour le moment et est destiné à être élaboré ultérieurement
La méthode principale utilisée dans la classe donnée est TBalanceSlope::CalcSlope. Il renvoie l'angle de pente de la courbe d'équilibre, qui est calculé par le montant indiqué des derniers trades. Voici sa réalisation :
//--------------------------------------------------------------------- // Calculate slope angle: //--------------------------------------------------------------------- double TBalanceSlope::CalcSlope( ) { // Get result of trading from the history of trades: int nmb = GetTradeResultsArray( slope_count_points ); if( nmb < slope_count_points ) { return( 0.0 ); } // Calculate the regression line by the results of last trades: CalcLR( last_datetime_array, last_result_array ); current_slope = LR_koeff_A; return( current_slope ); }
Tout d'abord, la quantité indiquée de derniers points de la courbe d'équilibre est analysée. Cela se fait en appelant la méthode de la classe de base TBalanceSlope::GetTradeResultsArray. Si le nombre de points de lecture n'est pas inférieur à celui indiqué, la ligne de régression est calculée. Cela se fait à l'aide de la méthode TBalanceSlope::CalcLR. Rempli à l'étape précédente, les tableaux last_result_array et last_datetime_array, qui appartiennent à la classe de base, sont utilisés comme arguments.
Les méthodes restantes sont simples et ne nécessitent pas de description détaillée.
Class TBalanceSlopeControl
C'est la classe de base, qui gère la pente de la courbe d'équilibre en modifiant le volume de travail. Il est généré à partir de la classe TBalanceSlope et hérite de toutes ses méthodes et données publiques et protégées. Le seul but de cette classe est de calculer le volume de travail actuel en fonction de l'angle de pente actuel de la courbe d'équilibre. Examinons cela en détail :
//--------------------------------------------------------------------- // Managing slope of the balance curve: //--------------------------------------------------------------------- enum LotsState { LOTS_NORMAL = 1, // mode of trading with normal volume LOTS_REJECTED = -1, // mode of trading with lowered volume LOTS_INTERMEDIATE = 0, // mode of trading with intermediate volume }; //--------------------------------------------------------------------- class TBalanceSlopeControl : public TBalanceSlope { private: double min_slope; // slope angle that corresponds to the mode of volume rejection double max_slope; // slope angle that corresponds to the mode of normal volume double centr_slope; // slope angle that corresponds to the mode of volume switching without hysteresis private: ControlType control_type; // type of the regulation function private: double rejected_lots; // volume in the rejection mode double normal_lots; // volume in the normal mode double intermed_lots; // volume in the intermediate mode private: LotsState current_lots_state; // current mode of volume public: void SetControlType( ControlType _control ); // set type of the regulation characteristic void SetControlParams( double _min_slope, double _max_slope, double _centr_slope ); public: double CalcTradeLots( double _min_lots, double _max_lots ); // get trade volume protected: double CalcIntermediateLots( double _min_lots, double _max_lots, double _slope ); public: void TBalanceSlopeControl( ); // constructor void ~TBalanceSlopeControl( ); // destructor };
Avant de calculer le volume actuel, nous devons définir les paramètres initiaux. Cela se fait en appelant les méthodes suivantes :
void SetControlType( ControlType _control ); // set type of the regulation characteristic
_Input parameter_control - c'est le type de la caractéristique de régulation. EIle peut avoir la valeur suivante :
- STEP_WITH_HYSTERESISH - échelonnée avec caractéristique de régulation d'hystérésis ;
- STEP_WITHOUT_HYSTERESIS - échelonnée sans caractéristique de régulation d'hystérésis ;
- LINEAR - caractéristique de régulation linéaire ;
- NON_LINEAR - caractéristique de régulation non linéaire (non implémentée dans cette version) ;
void SetControlParams( double _min_slope, double _max_slope, double _centr_slope );
Les paramètres d'entrée sont les suivants :
- _min_slope - angle de pente de la courbe d'équilibre qui correspond au trading avec un volume minimal ;
- _max_slope - angle de pente de la courbe d'équilibre qui correspond au trading avec un volume maximal ;
- _centr_slope - angle de pente de la courbe d'équilibre qui correspond à la caractéristique de régulation échelonnée sans hystérésis ;
Le volume est calculé selon la méthode suivante :
//--------------------------------------------------------------------- // Get trade volume: //--------------------------------------------------------------------- double TBalanceSlopeControl::CalcTradeLots( double _min_lots, double _max_lots ) { // Try to calculate slope of the balance curve: double current_slope = CalcSlope( ); // If the specified amount of trades is not accumulated yet, trade with minimal volume: if( GetRealTrades( ) < GetSlopePoints( )) { current_lots_state = LOTS_REJECTED; rejected_lots = trade_symbol.NormalizeLots( _min_lots ); return( rejected_lots ); } // If the regulation function is stepped without hysteresis: if( control_type == STEP_WITHOUT_HYSTERESIS ) { if( current_slope < centr_slope ) { current_lots_state = LOTS_REJECTED; rejected_lots = trade_symbol.NormalizeLots( _min_lots ); return( rejected_lots ); } else { current_lots_state = LOTS_NORMAL; normal_lots = trade_symbol.NormalizeLots( _max_lots ); return( normal_lots ); } } // If the slope of linear regression for the balance curve is less than the allowed one: if( current_slope < min_slope ) { current_lots_state = LOTS_REJECTED; rejected_lots = trade_symbol.NormalizeLots( _min_lots ); return( rejected_lots ); } // If the slope of linear regression for the balance curve is greater than specified: if( current_slope > max_slope ) { current_lots_state = LOTS_NORMAL; normal_lots = trade_symbol.NormalizeLots( _max_lots ); return( normal_lots ); } // The slope of linear regression for the balance curve is within specified borders (intermediate state): current_lots_state = LOTS_INTERMEDIATE; // Calculate the value of intermediate volume: intermed_lots = CalcIntermediateLots( _min_lots, _max_lots, current_slope ); intermed_lots = trade_symbol.NormalizeLots( intermed_lots ); return( intermed_lots ); }
Les principaux points importants de l’implémentation de la méthode TBalanceSlopeControl::CalcTradeLots sont les suivants :
- Jusqu'à ce que le montant minimal indiqué de trades soit accumulé, tradez avec un volume minimal. C'est logique, car on ne sait pas dans quelle période (rentable ou non) se trouve actuellement l'Expert Advisor, juste après l'avoir défini pour le trading.
- Si la fonction de régulation est celle échelonnée sans hystérésis, alors pour définir l'angle de commutation entre les modes de trading via la méthode TBalanceSlopeControl::SetControlParams, vous devez utiliser uniquement le paramètre_ _centr_slope. Les paramètres_ _min_slope et __max_slopesont ignorés. Il est fait pour effectuer l'optimisation correcte par ce paramètre dans le testeur de stratégie MetaTrader 5.
Selon l'angle de pente calculé, le trading est effectué avec un volume minimal, maximal ou intermédiaire. Le volume intermédiaire est calculé via la méthode simple - TBalanceSlopeControl::CalcIntermediateLots. Cette méthode est protégée et elle est utilisée dans la classe. Son code est indiqué ci-dessous :
//--------------------------------------------------------------------- // Calculation of intermediate volume: //--------------------------------------------------------------------- double TBalanceSlopeControl::CalcIntermediateLots( double _min_lots, double _max_lots, double _slope ) { double lots; // If the regulation function is stepped with hysteresis: if( control_type == STEP_WITH_HYSTERESISH ) { if( current_lots_state == LOTS_REJECTED && _slope > min_slope && _slope < max_slope ) { lots = _min_lots; } else if( current_lots_state == LOTS_NORMAL && _slope > min_slope && _slope < max_slope ) { lots = _max_lots; } } // If the regulation function is linear: else if( control_type == LINEAR ) { double a = ( _max_lots - _min_lots ) / ( max_slope - min_slope ); double b = normal_lots - a * .max_slope; lots = a * _slope + b; } // If the regulation function is non-linear ( not implemented yet ): else if( control_type == NON_LINEAR ) { lots = _min_lots; } // If the regulation function is unknown: else { lots = _min_lots; } return( lots ); }
Autres méthodes de cette classe ne nécessitent aucune description.
Exemple d'Intégration du Système dans un Expert Advisor
Examinons le processus d’implémentation du système de contrôle de la pente de la courbe d'équilibre dans un Expert Advisor étape par étape.
Étape 1 - ajout de l'instruction pour connecter la bibliothèque élaborée à l'Expert Advisor :
#include <BalanceSlopeControl.mqh>
Étape 2 - ajout des variables externes pour le réglage des paramètres du système de contrôle de la pente de la ligne d'équilibre à l'Expert Advisor :
//--------------------------------------------------------------------- // Parameters of the system of controlling the slope of the balance curve; //--------------------------------------------------------------------- enum SetLogic { No = 0, Yes = 1, }; //--------------------------------------------------------------------- input SetLogic UseAutoBalanceControl = No; //--------------------------------------------------------------------- input ControlType BalanceControlType = STEP_WITHOUT_HYSTERESIS; //--------------------------------------------------------------------- // Amount of last trades for calculation of LR of the balance curve: input int TradesNumberToCalcLR = 3; //--------------------------------------------------------------------- // Slope of LR to decrease the volume to minimum: input double LRKoeffForRejectLots = -0.030; //--------------------------------------------------------------------- // Slope of LR to restore the normal mode of trading: input double LRKoeffForRestoreLots = 0.050; //--------------------------------------------------------------------- // Slope of LR to work in the intermediate mode: input double LRKoeffForIntermedLots = -0.020; //--------------------------------------------------------------------- // Decrease the initial volume to the specified value when the LR is inclined down input double RejectedLots = 0.10; //--------------------------------------------------------------------- // Normal work volume in the mode of MM with fixed volume: input double NormalLots = 1.0;
Étape 3 - ajout de l'objet de type TBalanceSlopeControl à l'Expert Advisor :
TBalanceSlopeControl BalanceControl;
Cette déclaration peut être ajoutée au début de l'Expert Advisor, avant les définitions des fonctions.
Etape 4 - ajout du code d'initialisation du système de pilotage de la courbe d'équilibre à la fonction OnInit de l'Expert Advisor :
// Adjust our system of controlling the slope of the balance curve: BalanceControl.SetTradeSymbol( Symbol( )); BalanceControl.SetControlType( BalanceControlType ); BalanceControl.SetControlParams( LRKoeffForRejectLots, LRKoeffForRestoreLots, LRKoeffForIntermedLots ); BalanceControl.SetSlopePoints( TradesNumberToCalcLR ); BalanceControl.SetFiltrParams( 0, -1, 0 ); BalanceControl.SetMonitoringBeginDate( 0 );
Étape 5 - ajout de l'appel de méthode pour actualiser les informations courantes de marché à la fonction OnTick de l'Expert Advisor :
// Refresh market information:
BalanceControl.RefreshSymbolInfo( );
L'appel de cette méthode peut être ajouté au tout début de la fonction OnTick ou après la vérification de la nouvelle prochaine barre (pour les Expert Advisors avec une telle vérification).
Étape 6 - ajout du code pour le calcul du volume actuel avant le code où les positions sont ouvertes :
if( UseAutoBalanceControl == Yes ) { current_lots = BalanceControl.CalcTradeLots( RejectedLots, NormalLots ); } else { current_lots = NormalLots; }
Si un système de gestion de l'argent est utilisé dans l'Expert Advisor, alors à la place des NormalLots, vous devez écrire la méthode TBalanceSlopeControl::CalcTradeLots - le volume actuel calculé par le système MM de l'Expert Advisor.
Test Expert Advisor BSCS-TestExpert.mq5 avec le système intégré décrit ci-dessus est joint à cet article. Le principe de son fonctionnement est basé sur l'intersection des niveaux de l'indicateur CCI Cet Expert Advisor est élaboré pour les tests et n'est pas adapté pour travailler sur des comptes réels. Nous allons le tester à l'horizon H4 (2008.07.01 - 2010.09.01) de l'EURUSD.
Analysons le résultat du travail de cette EA. Le graphique de changement d'équilibre avec le système de contrôle de la pente désactivé est présenté ci-dessous. Pour le voir, définissez la valeur No pour le paramètre externe UseAutoBalanceControl.
Figure 4. Graphique initial du changement d'équilibre
Définissez maintenant le paramètre externe UseAutoBalanceControl sur Oui et testez l'Expert Advisor. Vous obtiendrez le graphique avec le système activé de contrôle de la pente d'équilibre.
Figure 5. Graphique de changement d'équilibre avec le système de contrôle activé
Vous pouvez constater que la plupart des périodes du graphique supérieur (fig.4) semblent coupées et qu'elles ont une forme plate dans le graphique inférieur (fig.5). C'est le résultat du fonctionnement de notre système. Vous pouvez comparer les principaux paramètres de fonctionnement de l'Expert Advisor :
Paramètre | UseAutoBalanceControl = No | UseAutoBalanceControl = Oui |
---|---|---|
Total net de bénéfice | 18 378.00 | 17 261.73 |
Facteur de bénéfice | 1.47 | 1.81 |
Facteur de récupération | 2.66 | 3.74 |
Gain prévu: | 117.81 | 110.65 |
Prélèvement absolu du solde: | 1 310.50 | 131.05 |
Prélèvement absolu des capitaux propres : | 1 390.50 | 514.85 |
Prélèvement maximal du solde : | 5 569.50 (5.04%) | 3 762.15 (3.35%) |
Prélèvement maximal des capitaux propres : | 6 899.50 (6.19%) | 4 609.60 (4.08%) |
Les meilleurs paramètres parmi ceux comparés sont mis en évidence avec la couleur verte. Les bénéfices et les gains prévus ont légèrement diminué ; c'est l'envers de la régulation, qui apparaît à la suite de décalages de commutation entre les états du volume de travail. En somme, il y a une amélioration des taux de travail de l'Expert Advisor. En particulier, amélioration du prélèvement et du facteur de bénéfice
Conclusion
Je vois plusieurs façons d'améliorer ce système :- Utilisation du trading virtuel lorsque l'Expert Advisor entre dans une période de travail défavorable. Ensuite, le volume de travail normal n'aura plus d'importance. Cela permettra de diminuer le prélèvement.
- Utiliser des algorithmes plus complexes pour déterminer l'état actuel de fonctionnement de l'Expert Advisor (rentable ou non). Par exemple, nous pouvons essayer d'appliquer un réseau neuronal pour une telle analyse. Une enquête supplémentaire est nécessaire dans ce cas, bien sûr.
Ainsi, nous avons pris en compte le principe et le résultat de fonctionnement du système, qui permet d'améliorer les caractéristiques de qualité d'un Expert Advisor. Le fonctionnement conjoint avec le système de gestion de l'argent, dans certains cas, permet d'accroitre la rentabilité sans augmenter le risque.
Je tiens à vous rappeler encore une fois : aucun système auxiliaire ne peut rendre un Expert Advisor rentable à partir d’un perdant.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/145
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation