Implémentation d'Indicateurs en tant que Classes par des Exemples de Zigzag et d' ATR
Pourquoi en avons-nous besoin ?
MetaQuotes Software Corp. a révisé le concept de travail avec des indicateurs personnalisés dans la nouvelle 5ème version du terminal client MetaTrader. Maintenant, ils sont exécutés beaucoup plus rapidement ; il n'y a qu'un seul exemple de chaque indicateur avec des paramètres d'entrée uniques, il n'est donc calculé qu'une seule fois, indépendamment de l'utilisation de ses copies sur même dix graphiques d'un symbole, etc.
Mais le fonctionnement d'un algorithme reste inchangé. En cas de perte de connexion à un serveur ou de synchronisation importante de l'historique, la valeur prev_calculated (ou IndicatorCounted() pour MetaTrader 4) est mise à zéro, ce qui conduit à un recalcul entier de l'indicateur pour l'ensemble de l'historique (les développeurs l'ont intentionnellement fait pour garantir l'exactitude de valeurs des indicateurs en toutes circonstances). Il y a plusieurs choses qui peuvent affecter la vitesse de calcul des indicateurs :
- Grande période : rates_total ;
- Calculs complexes et consommateurs en ressources ;
- Utiliser plusieurs symboles et points ;
- Ordinateur personnel faible;
Plus il y a d'éléments applicables à votre situation, plus le problème de recalcul de l'indicateur pour l'ensemble de l'historique est réel pour vous. De plus, la situation s'aggrave avec un mauvais canal de transmission des informations.
Bien sûr, vous pouvez limiter la profondeur du calcul des indicateurs à l'aide d'unparamètre d’entrée supplémentaire, mais il y a une nuance lors de l'utilisation des indicateurs iCustom. Le nombre maximal de barres utilisées par n'importe quel graphique ou indicateur personnalisé est défini sur la portée globale pour l'ensemble determinal La mémoire est allouée pour chaque tampon d'un indicateur personnalisé, et elle n'est limitée que parTERMINAL_MAXBARS.
Cependant, il y a un ajout important - si vous limitez le nombre maximum de barres directement calculées dans l'algorithme d'indicateur (par exemple, à l'aide d'un paramètre d'entrée ou directement dans le code), alors la mémoire sera dynamiquement allouée à l'arrivée de chaque nouvelle barre (augmenter progressivement jusqu'à la limite TERMINAL_MAXBARS spécifiée (ou un peu plus - cet algorithme dépend entièrement des développeurs, ils peuvent le modifier dans les prochaines versions)).
Façons d'éviter le recalcul de l'indicateur pour l'ensemble de l'historique
Pour le moment, je vois les manières suivantes de résoudre ce problème :- Demandez à MetaQuotes de réviser ce problème au niveau de la plate-forme
- Créez une classe distincte pour l’implémentation d'un analogue de prev_calculated
Il y avait une autre variante comme hypothèse selon laquelle vous pouvez construire directement dans l'indicateur un algorithme de calcul de prev_calculated, mais il est apparu que MetaTrader 5, à la différence de MetaTrader 4, "efface" tous les tampons de l'indicateur lors de la mise à zéro de prev_calculated (c'est-à-dire qu'il exécute de force la mise à zéro de tous les tableaux d'indicateurs ; vous ne pouvez pas le contrôler, car ce comportement est implémenté au niveau de la plate-forme).
Analysons chaque variante séparément.
- La première variante ne dépend que des développeurs. Peut-être qu'ils l'examineront après la publication de l'article. Et peut-être que l’implémentation d'un mécanisme à part entière affectera fortement les performances du bloc de calcul des indicateurs personnalisés (ce mécanisme peut toutefois être implémenté en option) et ils laisseront tout tel quel maintenant.
- La deuxième variante. Création d'une classe spéciale qui sera en charge de l'implémentation d'un analogue de prev_calculated. Nous pouvons l'utiliser à la fois dans un indicateur personnalisé (uniquement pour obtenir les valeurs prev_calculated) et dans un fournisseur de données à utiliser dans des Expert Advisors (ou des scripts) avec une classe élaborée séparément pour le calcul de l'indicateur personnalisé nécessaire.
Avantages et inconvénients de la deuxième variante de résolution du problème
Avantages :- volume fixe de mémoire requis par allocation unique de mémoire pour untableau dynamique avec organisation d'un accès en anneau aux éléments du tableau ;
- synchronisation et calcul de l'indicateur lors de l'utilisation d'uneclasse distincte pour son calcul à la demande (sans utiliser de sémaphores, balises, événements, etc.);
- lors de l'utilisation d'un appel séparé pour le calcul de l'indicateur, le résultat du recalcul est renvoyé sous une forme étendue (par exemple : il n'y a eu aucun changement, seul le dernier rayon a été modifié, un nouveau rayon a été ajouté, etc.).
- nécessité de stocker sa propre copie de l'historique des prix, qui est utilisée pour le calcul des valeurs d'un indicateur ;
- nécessité d'une synchronisationmanuelle de l'historique avec l'historique du terminal à l'aide d'opérations logiques de comparaison de données.
Création de la classe CCustPrevCalculated pour l’implémentation d'un analogue de prev_calculated
L'implémentation de la classe elle-même ne comporte rien d'intéressant à décrire. L'algorithme considère à la fois l'expansion de l'historique des deux côtés et son possible "coupure" du côté gauche. Cet algorithme peut également traiter l'insertion de l'historique dans les données calculées (il est réel pour MetaTrader 4, dans MetaTrader 5, je ne l'ai pas encore rencontré). Le code source de la classe se trouve dans le fichier CustPrevCalculated.mqh.
Permettez-moi de vous parler des éléments clés.
Création d'un Accès en Anneau aux Éléments du Tableau
Pour créer cette classe, nous allons utiliser une méthode non conventionnelle - l'accès en anneau aux éléments du tableau pour l'allocation unique de mémoire pour le tableau et pour éviter les procédures excessives de copie de tableaux. Examinons-le par l'exemple de 5 éléments :
Initialement, nous travaillons avec le tableau, dont la numération commence par 0. Mais que devons-nous faire si nous devons ajouter la valeur suivante en conservant la taille du tableau (ajouter une nouvelle barre) ? Il y a deux formes :
- copier les cellules mémoire 2 à 5 dans les cellules 1 à 4 respectivement ; on a donc la cellule mémoire vide 5 ;
- modifier l'indexation du tableau sans modifier les informations qui y sont stockées (adressage en boucle).
Pour implémenter la deuxième variante, nous avons besoin d'une variable, appelons-la DataStartInd ; elle stockera la position de l'index zéro du tableau. Pour la commodité des calculs ultérieurs, sa numérotation correspondra à l'indexation habituelle d'un tableau (c'est-à-dire qu'il partira de zéro). Dans la variable BarsLimit nous allons stocker le nombre d'éléments du tableau. Ainsi, l'adresse réelle de l'élément du tableau pour l'index virtuel « I » sera calculée à l'aide de la formule simple suivante :
- (DataStartInd+I) % BarsLimit – pour la numération habituelle
- (DataStartInd+DataBarsCount-1-I) % BarsLimit - pour l'adressage comme dans les séries chronologiques
Algorithmes de synchronisation de l'historique
Pour ma part, j'ai sélectionné et implémenté trois modes de fonctionnement de l'algorithme de synchronisation d'une copie d'historique (historique local) avec l'historique dans le terminal client :- CPCHSM_NotSynch – la synchronisation de l'historique local n'est pas effectuée pour les barres déjà formées (à vos risques et périls). En fait, ce mode peut être librement utilisé pour un indicateur, où un écart insignifiant des valeurs de prix ne peut pas fortement affecter la précision des calculs (MA, ADX, etc.). Ce mode peut être fatal pour le ZigZag, par exemple, où un excès d'un pic sur un autre est considérable.
- CPCHSM_Normal – l'historique local est synchronisé à chaque nouvelle barre par l'algorithme décrit ci-dessous.
- CPCHSM_Paranoid – l'historique local est synchronisé à chaque appel de la fonction de synchronisation des données décrite ci-dessous.
Le mécanisme de synchronisation lui-même est basé sur un autre paramètre défini par un programmeur - HSMinute (stocké sous HistorySynchSecond). Nous supposons qu'un Dealer Center ne peut corriger que les dernières minutes HSMinute de l'historique. Si aucune différence n'est trouvée lors de la synchronisation de cette période, l'historique est considéré comme identique et la comparaison est arrêtée. Si une différence est trouvée, tout l'historique est vérifié et corrigé.
De plus, l'algorithme permet de vérifier uniquement les prix/spreads/volumes à partir de la structure MqlRates indiquée à l'initialisation. Par exemple, pour dessiner ZigZag, nous n'avons besoin que des prix High et Low.
Utilisation pratique de la classe CCustPrevCalculated
Pour initialiser la classe CCustPrevCalculated, nous devons appeler la fonction InitData(), qui renvoie 'true' en cas de succès :CCustPrevCalculated CustPrevCalculated; CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15);Pour synchroniser l'historique, nous devons appeler la fonction PrepareData() :
CPCPrepareDataResultCode resData; resData = CustPrevCalculated.PrepareData();
Variantes de valeurs pouvant être renvoyées par la fonction PrepareData() :
enum CPCPrepareDataResultCode { CPCPDRC_NoData, // Returned when there is no data for calculation (not prepared by the server) CPCPDRC_FullInitialization, // Full initialization of the array has been performed CPCPDRC_Synch, // Synchronization with adding new bars has been performed CPCPDRC_SynchOnlyLastBar, // Synchronization of only the last bar has been performed (possible cutting of the history) CPCPDRC_NoRecountNotRequired // Recalculation has not been performed, since the data was not changed };
Fonctions de la classe CCustPrevCalculated pour l'Accès aux Données
Remarque : pour accélérer les calculs, les contrôles de débordement de tableau sont exclus. Pour être plus précis, des valeurs erronées seront renvoyées si l'index est incorrect.
Nom | Objectif |
---|---|
uint GetDataBarsCount() | Renvoie le nombre de barres disponibles |
uint GetDataBarsCalculated() | Renvoie le nombre de barres inchangées |
uint GetDataStartInd() | Renvoie l'index pour l'accès en boucle (pour les indicateurs personnalisés) |
bool GetDataBarsCuttingLeft() | Renvoie le résultat de la coupe des barres depuis la gauche |
double GetDataOpen(int shift, bool AsSeries) | Renvoie 'Open' pour la barre de décalage |
double GetDataHigh(int shift, bool AsSeries) | Renvoie « High » pour la barre de changement de vitesse |
double GetDataLow(int shift, bool AsSeries) | Renvoie Low pour la barre de décalage |
double GetDataClose(int shift, bool AsSeries) | Renvoie 'Close' pour la barre de décalage |
datetime GetDataTime(int shift, bool AsSeries) | Renvoie 'Time' pour la barre de décalage |
long GetDataTick_volume(int shift, bool AsSeries) | Renvoie 'Tick_volume' pour la barre de décalage |
long GetDataReal_volume(int shift, bool AsSeries) | Renvoie 'Real_volume' pour la barre de décalage |
int GetDataSpread(int shift, bool AsSeries) | Renvoie 'Spread' pour la barre de décalage |
Exemples d'optimisation supplémentaire de la classe CCustPrevCalculated
- Refuser deMqlRates en passant à plusieurs tableaux (déterminés par un certain but) (diminue les besoins en mémoire, mais augmente la charge sur le nombre d'appels de tableaux de copie).
- Division de chaque fonction d'accès en deux fonctions indépendantes pour une utilisation définitive avec certain type d'indexation de tableau (déclinant du paramètre « bool AsSeries »). L'avantage n'est que dans la condition logique «if (AsSeries)».
Création du CCustZigZagPPC pour le calcul de l'indicateur personnalisé ZigZag sur la base des données de la classe CCustPrevCalculated
Cet algorithme est basé sur l'indicateur personnalisé ZigZag professionnel Le code source de la classe se trouve dans le fichier ZigZags.mqh ; de plus, la bibliothèque OutsideBar.mqh est utilisée pour travailler avec des barres externes.
Créons une structure distincte pour la description d'une barre de notre indicateur :
struct ZZBar { double UP, DN; // Buffers of the ZigZag indicator OrderFormationBarHighLow OB; // Buffer for caching of an external bar };
Déterminons également le résultat du retour des calculs de la classe :
enum CPCZZResultCode { CPCZZRC_NotInitialized, // Class is no initialized CPCZZRC_NoData, // Faield to receive data (including the external bar) CPCZZRC_NotChanged, // No changes of ZZ rays CPCZZRC_Changed // ZZ rays changed };
Pour initialiser la classe CCustZigZagPPC, nous devons appeler la fonction Init() une fois ; il renvoie 'true' en cas de succès :
CCustZigZagPPC ZZ1; ZZ1.Init(CustPrevCalculated, _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10);
Pour les calculs de l'indicateur, nous devons commencer la mise à jour des données sur la base des données précédemment calculées de la classe CCustPrevCalculated :
CPCPrepareDataResultCode resZZ1; resZZ1 = ZZ1.PrepareData(resData);
Et puis appelez la procédure Calculate() :
if ( (resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired) )
ZZ1.Calculate();
L'exemple complet d'utilisation d'une classe CCustPrevCalculated avec plusieurs classes CCustZigZagPPC est donné dans le fichier ScriptSample_CustZigZagPPC.mq5.
Fonction d'accès aux données de la classe CCustZigZagPPC
Nom | Objectif |
---|---|
uint GetBarsCount() | Renvoie le nombre de barres disponibles |
uint GetBarsCalculated() | Renvoie le nombre de barres calculées |
double GetUP(uint shift, bool AsSeries) | Renvoie la valeur du pic ZigZag pour une barre |
double GetDN(uint shift, bool AsSeries) | Renvoie la valeur du ZigZag bas pour une barre |
OrderFormationBarHighLow GetOB(uint shift, bool AsSeries) | Renvoie la valeur 'Outside' pour une barre |
Contrôle visuel et de programme
Pour la vérification visuelle, attachons l'indicateur d'origine à un graphique, et attachons dessus l'indicateur de test spécialement écrit Indicator_CustZigZag.mq5 avec des paramètres d'entrée identiques (mais vous devez sélectionner d'autres couleurs, pour voir les deux indicateurs); voici le résultat de son fonctionnement :
Rouge - original, bleu - le nôtre, calculé sur les 100 dernières barres.
De la même manière nous pouvons les comparer dans un Expert Advisor ; y aura-t-il une différence ? Les résultats obtenus à partir d'iCustom("AlexSTAL_ZigZagProf") et de la classe CCustZigZagPPC sont comparés à chaque tick du test Expert Advisor Expert_CustZigZagPPC_test.mq5. Les informations sur le calcul sont affichées dans le journal (il peut n'y avoir aucun calcul aux premières barres, en raison du manque d'historique de l'algorithme) :
(EURUSD,M1) 1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116; 1.35971; // it is normal
(EURUSD,M1) Tick processed: 1.35797; 1.35644; 1.35844; 1.35761; 1.35901; 1.35760; 1.35959; 1.35791; 1.36038; 1.35806; 1.36042; 1.35976; 1.36116;
(EURUSD,M1) Divergence on the bar: 7
Examinons cet Expert Advisor plus en détail. Déterminer les variables globales pour travailler :
#include <ZigZags.mqh> CCustPrevCalculated CustPrevCalculated; CCustZigZagPPC ZZ1; int HandleZZ;
initialisez les variables
int OnInit() { // Creating new class and initializing it CustPrevCalculated.InitData(_Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15); // Initializing the class ZZ ZZ1.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, 150, CPCHSM_Normal, CPCH_high|CPCH_low, 15, 0, true, 12, 10); // Receiving handle for the custom indicator HandleZZ = iCustom(_Symbol, _Period, "AlexSTAL_ZigZagProf", 12, 10, 0 , true); Print("ZZ_handle = ", HandleZZ, " error = ", GetLastError()); return(0); }Traitement des ticks dans l'Expert Advisor :
void OnTick() { // Calculation of data CPCPrepareDataResultCode resData, resZZ1; resData = CustPrevCalculated.PrepareData(); // Start recalculation for each indicator! PrepareData obligatory! resZZ1 = ZZ1.PrepareData(resData); // Расчет данных ZZ1 if ( !((resZZ1 != CPCPDRC_NoData) && (resZZ1 != CPCPDRC_NoRecountNotRequired)) ) return; // Получим результаты расчета ZZ1.Calculate();
Nous avons maintenant les barres ZZ1.GetBarsCalculated() calculées par le CCustZigZagPPC. Ajoutons le code de comparaison des données d'iCustom("AlexSTAL_ZigZagProf") et de la classe CCustZigZagPPC :
int tmpBars = (int)ZZ1.GetBarsCalculated(); double zzUP[], zzDN[]; CopyBuffer(HandleZZ, 0, 0, tmpBars, zzUP); CopyBuffer(HandleZZ, 1, 0, tmpBars, zzDN); // Perform comparison string tmpSt1 = "", tmpSt2 = ""; for (int i = (tmpBars-1); i >= 0; i--) { double tmpUP = ZZ1.GetUP(i, false); double tmpDN = ZZ1.GetDN(i, false); if (tmpUP != zzUP[i]) Print("Divergence on the bar: ", i); if (tmpDN != zzDN[i]) Print("Divergence on the bar: ", i); if (tmpUP != EMPTY_VALUE) tmpSt1 = tmpSt1 + DoubleToString(tmpUP, _Digits) + "; "; if (tmpDN != EMPTY_VALUE) tmpSt1 = tmpSt1 + DoubleToString(tmpDN, _Digits) + "; "; if (zzUP[i] != EMPTY_VALUE) tmpSt2 = tmpSt2 + DoubleToString(zzUP[i], _Digits) + "; "; if (zzDN[i] != EMPTY_VALUE) tmpSt2 = tmpSt2 + DoubleToString(zzDN[i], _Digits) + "; "; } Print("Tick processed: ", tmpSt1); Print(" ", tmpSt2); }
Voici l'utilisation pratique simple de la classe CCustZigZagPPC dans un Expert Advisor ou un script. Les fonctions d'accès direct GetUP(), GetDN(), GetOB() au lieu deCopyBuffer() .
Déplacement de notre indicateur vers une classe distincte (par l'exemple de l' iATR)
Plan général:
1. Étape Préparatoire.
- Copiez MyIndicator.mqh en tant que fichier avec un autre nom (il s'agit de ATRsample.mqh dans mon exemple) et ouvrez ce dernier dans MetaEditor 5.
- Remplacez le texte "MyInd" par le nom de votre indicateur (il s'agit de "ATR" dans mon exemple).
2. Choisissez des paramètres externes qui seront pris de l'indicateur initial (d'origine) à la classe, déclarez-les et initialisez-les.
Dans mon exemple, l'indicateur ATR dispose d’ un paramètre externe :input int InpAtrPeriod=14; // ATR period
- ajoutez ce paramètre à notre classe et à la fonction d'initialisation de la classe :
class CCustATR { protected: ... uchar iAtrPeriod; ... public: ... bool Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod);
- modifiez l'en-tête du corps de la fonction Init et initialisez le paramètre variable avec la valeur d'entrée :
bool CCustATR::Init(CCustPrevCalculated *CPC, string Instr, ENUM_TIMEFRAMES TF, int Limit, CPCHistorySynchMode HSM, uchar HS, uint HSMinute, uchar AtrPeriod) { ... BarsLimit = Limit; iAtrPeriod = AtrPeriod; ...
3. Déterminez le nombre de tampons requis dans l'indicateur initial, déclarez-les dans notre classe. Déclarez également les fonctions de retour des tampons INDICATOR_DATA.
- Modifiez la structure
struct ATRBar { double Val; // Indicator buffers };
à notre propre structure :
struct ATRBar { double ATR; double TR; };
- Déterminer les valeurs zéro :
CPCPrepareDataResultCode CCustATR::PrepareData(CPCPrepareDataResultCode resData) { ... for (uint i = (DataBarsCalculated == 0)?0:(DataBarsCalculated+1); i < DataBarsCount; i++) { Buf[PInd(i, false)].ATR = EMPTY_VALUE; Buf[PInd(i, false)].TR = EMPTY_VALUE; } ...
- Modifiez et ajoutez la fonction de retour des valeurs des tampons INDICATOR_DATA :
modifiez (s'il n'y a qu'un seul tampon, vous pouvez ignorer le changement)
class CCustATR { ... double GetVal(uint shift, bool AsSeries); // returns the Val value of the buffer for a bar ...
à
class CCustATR { ... double GetATR(uint shift, bool AsSeries); // Возвращает значение буфера ATR для бара ...
et changez le code de la fonction correspondante :
double CCustATR::GetATR(uint shift, bool AsSeries) { if ( shift > (DataBarsCount-1) ) return(EMPTY_VALUE); return(Buf[PInd(shift, AsSeries)].ATR); }Remarque : au lieu de plusieurs fonctions de renvoi des valeurs de tampon, vous ne pouvez en utiliser qu'une seule, qui dispose d’ un paramètre supplémentaire - le numéro ou le nom du tampon.
4. Copiez la logique de la fonction OnCalculate() de l'indicateur initial dans la fonction correspondante de la classe
- Contrôles primaires
CPCATRResultCode CCustATR::Calculate() { ... // Check if there are enough bars for the calculation if (DataBarsCount <= iAtrPeriod) return(CPCATRRC_NoData); ...
- Calculs : au premier tick et le nombre de barres pour les calculs aux prochains ticks :
if ( DataBarsCalculated != 0 ) BarsForRecalculation = DataBarsCount - ATRDataBarsCalculated - 1; else { Buf[PInd(0, false)].TR = 0.0; Buf[PInd(0, false)].ATR = 0.0; //--- filling out the array of True Range values for each period for (uint i = 1; i < DataBarsCount; i++) Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false)); //--- first AtrPeriod values of the indicator are not calculated double firstValue = 0.0; for (uint i = 1; i <= iAtrPeriod; i++) { Buf[PInd(i, false)].ATR = 0; firstValue += Buf[PInd(i, false)].TR; } //--- calculating the first value of the indicator firstValue /= iAtrPeriod; Buf[PInd(iAtrPeriod, false)].ATR = firstValue; BarsForRecalculation = DataBarsCount - iAtrPeriod - 2; }
- Le calcul à chaque tick lui-même :
for (uint i = (DataBarsCount - BarsForRecalculation - 1); i < DataBarsCount; i++) { Buf[PInd(i, false)].TR = MathMax(CustPrevCalculated.GetDataHigh(i, false), CustPrevCalculated.GetDataClose(i-1, false)) - MathMin(CustPrevCalculated.GetDataLow(i, false), CustPrevCalculated.GetDataClose(i-1, false)); Buf[PInd(i, false)].ATR = Buf[PInd(i-1, false)].ATR + (Buf[PInd(i, false)].TR-Buf[PInd(i-iAtrPeriod, false)].TR) / iAtrPeriod; ...
C'est tout. Notre classe a été créée. Pour la vérification visuelle, vous pouvez créer un indicateur de test (dans mon exemple, il s'agit de Indicator_ATRsample.mq5) :
J'ai eu l'idée en corrigeant l'article, que si vous utilisez la classe ensemble CCustPrevCalculated avec un seul indicateur personnalisé, vous pouvez intégrer la création, l'initialisation et la synchronisation de cette classe dans l'indicateur personnalisé (dans mes exemples ce sont CCustZigZagPPC et CCustATR ). Lors de l'appel de la fonction d'initialisation des indicateurs personnalisés à cet effet, vous devez utiliser le pointeur zéro vers l'objet :
ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod);
A cela la structure générale
#include <CustPrevCalculated.mqh> #include <ATRsample.mqh> CCustPrevCalculated CustPrevCalculated; CCustATR ATR; int OnInit() { CustPrevCalculated.InitData(_Symbol, _Period, iBars, CPCHSM_Normal, 0, 30); ATR.Init(GetPointer(CustPrevCalculated), _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod); } int OnCalculate(...) { CPCPrepareDataResultCode resData = CustPrevCalculated.PrepareData(); CPCPrepareDataResultCode resATR = ATR.PrepareData(resData); if ( (resATR != CPCPDRC_NoData) && (resATR != CPCPDRC_NoRecountNotRequired) ) ATR.Calculate(); }
sera simplifié en :
#include <ATRsample.mqh> CCustATR ATR; int OnInit() { ATR.Init(NULL, _Symbol, _Period, iBars, CPCHSM_Normal, 0, 30, InpAtrPeriod); } int OnCalculate(...) { ATR.Calculate(); }Un exemple pratique est donné dans le fichier Indicator_ATRsample2.mq5.
Impact de la technologie décrite sur les performances dans le testeur de stratégie
Pour vérification, j'ai fait un test Expert Advisor (TestSpeed_IndPrevCalculated.mq5) qui reçoit la valeur de l'indicateur de barre zéro à chaque tick selon l'une des trois variantes :
enum eTestVariant { BuiltIn, // Built-in indicator iATR Custom, // Custom indicator iCustom("ATR") IndClass // Calculation in the class };
Cet Expert Advisor a été exécuté 10 fois sur 1 agent avec les paramètres d'optimisation suivants :
- Symbole EURUSD
- Période : tout l’historique [1993..2001]
- Mode de trade : chaque tick
- Paramètre externe : FalseParameter [0..9]
J'ai mesuré le temps d'optimisation lors de l'utilisation de chacune des trois variantes de l'indicateur. Le résultat de la vérification s'affiche sous la forme d'un histogramme linéaire.
Le code source de l'Expert Advisor utilisé pour mesurer le temps d'optimisation :
//+------------------------------------------------------------------+ //| TestSpeed_IndPrevCalculated.mq5 | //| Copyright 2011, AlexSTAL | //| http://www.alexstal.ru | //+------------------------------------------------------------------+ #property copyright "Copyright 2011, AlexSTAL" #property link "http://www.alexstal.ru" #property version "1.00" //--- connect the include file with the CustATR class #include <ATRsample.mqh> //--- set the selection of the parameter as an enumeration enum eTestVariant { BuiltIn, // Built-in indicator iATR Custom, // Custom indicator iCustom("ATR") IndClass // Calculation withing the class }; //--- input variables input eTestVariant TestVariant; input int FalseParameter = 0; //--- period of the ATR indicator const uchar InpAtrPeriod = 14; //--- handle of the built-in or custom indicator int Handle; //--- indicator based on the class CCustATR *ATR; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- switch(TestVariant) { case BuiltIn: Handle = iATR(_Symbol, _Period, InpAtrPeriod); break; case Custom: Handle = iCustom(_Symbol, _Period, "Examples\ATR", InpAtrPeriod); break; case IndClass: ATR = new CCustATR; ATR.Init(NULL, _Symbol, _Period, 100, CPCHSM_Normal, 0, 30, InpAtrPeriod); break; }; //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { switch(TestVariant) { case IndClass: delete ATR; break; }; } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { double tmpValue[1]; switch(TestVariant) { case BuiltIn: CopyBuffer(Handle, 0, 0, 1, tmpValue); break; case Custom: CopyBuffer(Handle, 0, 0, 1, tmpValue); break; case IndClass: ATR.Calculate(); tmpValue[0] = ATR.GetATR(0, true); break; }; } //+------------------------------------------------------------------+
Comme nous le constatons, cette technologie ne diminue pas les performances du testeur de stratégie de manière considérable par rapport à l'utilisation d'un indicateur personnalisé ordinaire.
Remarques sur l'utilisation pratique de cette technologie
- lors du test d'un Expert Advisor dans le testeur de stratégie, la valeur prev_calculated ne peut pas être mise à zéro dans un indicateur personnalisé, c'est pourquoi la synchronisation de l'historique est désactivée dans ce mode ;
- le calcul de l'indicateur n'est effectué qu'aux dernières 'n' barres qui sont strictement fixées à l'initialisation initiale des classes ;
- le calcul implique une liaison stricte à un certain symbole et période de la classe initialisée. Pour effectuer des calculs sur d'autres symboles ou périodes, vous devez créer de nouvelles instances des classes.
Conclusion
Dans chaque situation, un programmeur doit tenir compte de tous les avantages et inconvénients des différentes variantes d’implémentation de la tâche. L’implémentation suggérée dans l'article n'est qu'un moyen avec ses propres avantages et inconvénients.
P.S. Qui ne fait pas d'erreurs, ne fait rien ! Si vous trouvez des erreurs, merci de m'en informer.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/247
- 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