
Figures et exemples (partie I) : Multiple Top, ou Hauts Multiples
Sommaire
- Résumé
- A propos des motifs d'inversion
- Pourquoi le Multiple Top — ses caractéristiques spécifiques
- Le concept Double Top peut-il être étendu ?
- Écrire du code pour afficher un Multiple Top
- Autres idées de développement
- Conclusion
Résumé
Les modèles sont souvent discutés sur Internet, car ils sont utilisés par de nombreux traders. Ils peuvent être considérés comme des critères d'analyse visuelle permettant de déterminer l'orientation à venir des prix. Le trading algorithmique est différent de cela. Il ne peut pas y avoir de critères visuels pour le trading algorithmique. Les Expert Advisors et les indicateurs disposent de méthodes individuelles pour travailler avec les séries de prix. Il y a des avantages et des inconvénients des deux côtés. Le code n'a pas l'ampleur de la pensée humaine ni la qualité de l'analyse humaine, mais il a d'autres avantages précieux : une vitesse incomparable et une quantité incomparable de données numériques ou logiques traitées par unité de temps. Il n'est pas facile d'indiquer à la machine ce qu'elle doit faire. Cela demande un peu de pratique. Au fil du temps, le programmeur commence à comprendre la machine, et la machine commence à comprendre le programmeur. Cette série d'articles sera utile aux débutants, qui apprendront à structurer leurs pensées et à diviser les tâches complexes en étapes plus simples.
A propos des motifs d'inversion
Pour moi, les schémas d'inversion ont une définition trop vague. Et ils n'ont pas de mathématiques sous-jacentes non plus. Pour être honnête, tout modèle n'a pas de mathématiques sous-jacentes et les seules mathématiques qui peuvent être prises en compte ici sont les statistiques. Les statistiques sont le seul critère de vérité. Mais elles sont établies sur la base des échanges réels. Il est évident qu'il n'existe pas de sources pouvant fournir des statistiques très précises. Il est même inutile de fournir de telles données pour un problème de recherche spécifique. La seule solution est le backtesting et la visualisation dans le testeur de stratégie. Bien que cette approche offre une qualité de données moindre, elle présente un avantage indéniable, à savoir la rapidité et la quantité de données.
Bien entendu, les figures de renversement ne constituent pas un outil suffisant pour déterminer les renversements de tendance. Mais en combinaison avec d'autres méthodes d'analyse, telles que l'analyse des niveaux ou l'analyse des chandeliers, elles peuvent produire le résultat souhaité. Dans cette série d'articles, les modèles ne sont pas considérés comme une méthode d'analyse particulièrement intéressante, mais ils peuvent être utilisés pour s'entraîner aux techniques de trading algorithmique. En plus de la pratique, vous obtiendrez un outil auxiliaire intéressant et utile - si ce n'est pas pour l'algo-trading, c'est pour l'œil du trader. Les indicateurs utiles sont très appréciés.
Pourquoi le Multiple Top — ses caractéristiques spécifiques
Ce modèle est devenu très populaire sur Internet en raison de sa simplicité. Ce schéma est assez courant sur différents instruments de trading et sur différents horizons graphiques, simplement parce qu'il n'y a rien de compliqué à cela. Si vous regardez de plus près le modèle, vous pouvez même voir que le concept peut être élargi en utilisant les capacités du trading algorithmique et du langage MQL5. Nous pouvons essayer de créer un code général qui ne sera pas limité uniquement par un double sommet. Un prototype judicieusement créé peut être utilisé pour explorer tous les modèles hybrides et successeurs.
Le successeur classique du haut multiple est le très populaire modèle "tête et épaules". Malheureusement, il n'existe pas d'informations structurées sur la manière de trader ce modèle. Ce problème est commun à de nombreuses stratégies populaires - parce qu'il y a beaucoup de beaux mots mais pas de statistiques. Je vais essayer de comprendre dans cet article s'il est possible de les utiliser dans le cadre du trading algorithmique. La seule méthode pour collecter des statistiques sans trader sur un compte de démo ou sur un compte réel est d'utiliser les capacités du testeur de stratégie. Sans cet outil, vous ne pourrez pas tirer de conclusions complexes concernant une stratégie particulière.
Le concept Double Top peut-il être étendu ?
En ce qui concerne le sujet de cet article, j'essaierai de dessiner un diagramme sous la forme d'un arbre de motifs qui part d'un double sommet. Cela permettra de mieux comprendre l'étendue des possibilités de ce concept :
J'ai décidé de combiner le concept de plusieurs modèles en partant du principe qu'ils sont basés approximativement sur la même idée. Cette idée a un début simple : trouver un bon mouvement dans n'importe quelle direction et déterminer correctement l'endroit où il est censé s'inverser. Après un contact visuel avec la configuration proposée, le trader doit tracer correctement quelques lignes auxiliaires, qui l'aideront à évaluer si la configuration répond à certains critères et à déterminer le point d'entrée sur le marché, ainsi que l'objectif et le niveau de perte. Le take profit peut être utilisé ici à la place de l'objectif.
Les modèles peuvent avoir des principes de construction communs, sur la base desquels le concept de ces modèles peut être combiné. Cette définition claire est ce qui différencie les traders algorithmiques des traders manuels. L'incertitude et l'interprétation multiple des mêmes principes peuvent avoir des conséquences décevantes.
Les modèles de base sont les suivants :
- Double Top, ou Double Sommets
- Triple Top, ou Triple Sommets
- Tête et épaules
Ces modèles ont des structures et des principes d'utilisation similaires. Tous visent à identifier les inversions de tendance. Les trois modèles ont une logique similaire en ce qui concerne les lignes auxiliaires. Prenons l'exemple du Double Top :
Dans la figure ci-dessus, toutes les lignes nécessaires sont numérotées et signifient ceci :
- Résistance de la tendance
- Ligne auxiliaire pour définir un pic pessimiste (quelqu'un pense qu'il s'agit d'un cou)
- Ligne de cou
- Objectif optimiste (il s'agit également d'un niveau de prise de bénéfice pour le trading)
- Le niveau maximum de stop-loss autorisé (il est fixé tout en haut)
- Ligne de prévision optimiste (égale à la tendance précédente)
Un objectif pessimiste est déterminé par rapport au point d'intersection de la ligne de cou à partir du bord le plus proche du marché - nous prenons la distance entre "1" et "2", qui est indiquée par "t", et nous mesurons la même distance dans la direction du renversement proposé. Le minimum de la cible optimiste est déterminé de la même manière, mais la distance est mesurée entre "5" et "3", ce qui est indiqué par "s".
Écrire du code pour afficher un Multiple Top
Commençons par définir la logique de raisonnement qui permet de définir ces modèles. Pour trouver un modèle, nous devons nous en tenir à la logique "barre par barre", c'est-à-dire que nous ne travaillerons pas par ticks, mais par barres. Dans ce cas, cela réduira considérablement la charge du terminal en évitant des calculs inutiles. Tout d'abord, déterminons une classe symbolisant un observateur indépendant qui cherchera le modèle. Toutes les opérations nécessaires à une détection correcte des motifs feront partie de l'instance. La recherche sera donc effectuée à l'intérieur de celle-ci. J'ai choisi cette solution afin de permettre des modifications ultérieures du code, par exemple lorsque nous devons étendre les fonctionnalités ou modifier les caractéristiques existantes.
Structure de la classe
Commençons par examiner le contenu de la classe :
class ExtremumsPatternFamilySearcher// class simulating an independent pattern search { private: int BarsM;// how many bars on chart to use int MinimumSeriesBarsM;// the minimum number of bars in a row to detect a top int TopsM;// number of tops in the pattern int PointsPessimistM;// minimum distance in points to the nearest target double RelativeUnstabilityM;// maximum excess of the head size relative to the minimum shoulder double RelativeUnstabilityMinM;// minimum excess of the head size relative to the minimum shoulder double RelativeUnstabilityTimeM;// maximum excess of head and shoulders sizes bool bAbsolutelyHeadM;// whether a pronounced head is required bool bRandomExtremumsM;// random selection of extrema struct Top// top data { datetime Datetime0;// time of the candlestick closest to the market datetime Datetime1;// time of the next candlestick int Index0;// index of the candlestick closest to the market int Index1;// index of the next candlestick datetime DatetimeExtremum;// time of the top int IndexExtremum;// index of the top double Price;// price of the top bool bActive;// if the top is active (if not, then it does not exist) }; struct Line// line { double Price0;// price of the candlestick closest to the market, to which the line is bound datetime Time0;// time of the candlestick closest to the market, to which the line is bound double Price1;// price of the farthest candlestick to which the line is bound datetime Time1;// time of the farthest candlestick to which the line is bound datetime TimeX;// time of the X point int Index1;// index of the left edge bool DirectionOfFormation;// direction double C;// free coefficient in the equation double K;// aspect ratio void CalculateKC()// find unknowns in the equation { if ( Time0 != Time1 ) K=double(Price0-Price1)/double(Time0-Time1); else K=0.0; C=double(Price1)-K*double(Time1); } double Price(datetime T)// function of line depending on time { return K*T+C; } }; public: ExtremumsPatternFamilySearcher(int BarsI,int MinimumSeriesBarsI,int TopsI,int PointsPessimistI, double RelativeUnstabilityI, double RelativeUnstabilityMinI,double RelativeUnstabilityTimeI,bool bAbsolutelyHeadI,bool bRandomExtremumsI)// parametric constructor { BarsM=BarsI; MinimumSeriesBarsM=MinimumSeriesBarsI; TopsM=TopsI; PointsPessimistM=PointsPessimistI; RelativeUnstabilityM=RelativeUnstabilityI; RelativeUnstabilityMinM=RelativeUnstabilityMinI; RelativeUnstabilityTimeM=RelativeUnstabilityTimeI; bAbsolutelyHeadM=bAbsolutelyHeadI; bRandomExtremumsM=bRandomExtremumsI; bPatternFinded=bFindPattern(); } int FormationDirection;// direction of the formation (multiple top or bottom, or none at all) ( -1,1,0 ) bool bPatternFinded;// if the pattern was found during formation Top TopsUp[];// required upper extrema Top TopsDown[];// required lower extrema Top TopsUpAll[];// all upper extrema Top TopsDownAll[];// all lower extrema int RandomIndexUp[];// array for the random selection of the tops index int RandomIndexDown[];// array for the random selection of the bottoms index Top StartTop;// where the formation starts (top farthest from the market) Top EndTop;// where the formation ends (top closest to the market) Line Neck;// neck Top FarestTop;// top farthest from the neck (will be used to determine the head or the formation size) or the same as the head Line OptimistLine;// line of optimistic forecast Line PessimistLine;// line of pessimistic forecast Line BorderLine;// line at the edge of the pattern Line ParallelLine;// line parallel to the trend resistance private: void SetTopsSize();// setting sizes for arrays with tops bool SearchFirstUps();// search for tops bool SearchFirstDowns();// search for bottoms void CalculateMaximum(Top &T,int Index0,int Index1);// calculate the maximum price between two bars void CalculateMinimum(Top &T,int Index0,int Index1);// calculate the minimum price between two bars bool PrepareExtremums();// prepare extrema bool IsExtremumsAbsolutely();// control the priority of tops void DirectionOfFormation();// determine the direction of the formation void FindNeckUp(Top &TStart,Top &TEnd);// find neck for the bullish pattern void FindNeckDown(Top &TStart,Top &TEnd);// find neck for the bearish pattern void SearchFarestTop();// find top farthest from the neck bool bBalancedExtremums();// initial balancing of extrema (so that they do not differ much) bool bBalancedExtremumsHead();// if a pattern has more than 2 tops, we can check for a pronounced head bool bBalancedExtremumsTime();// require that the extrema be not very far in time relative to the minimum distance bool bBalancedHead();// balance the head (in other words, require that it be neither the first nor the last one on the list of tops, if there are more than three of them) bool CorrectNeckUpLeft();// adjust the neck so as to find the intersection of price and neck (this creates prerequisites for the previous trend) bool CorrectNeckDownLeft();// similarly for the bottom int CorrectNeckUpRight();// adjust the neck so as to find the intersection of price and neck on the right or at the current price position, which is the same (to determine the entry point) int CorrectNeckDownRight();// similarly for the bottom void SearchLineOptimist();// calculate the optimistic forecast line bool bWasTrend();// determine whether a trend preceded the pattern definition (in this case the optimistic target line is considered as the trend beginning) void SearchLineBorder();// determine trend resistance or support (usually a sloping line) void CalculateParallel();// determine a line parallel to support or resistance (crosses the neck at the pattern low or high) bool bCalculatePessimistic();// calculate the line of the pessimistic target bool bFindPattern();// perform all the above actions int iFindEnter();// find intersection with the neck public: void CleanAll();// clean up objects void DrawPoints();// draw points void DrawNeck();// draw the neck void DrawLineBorder();// line at the border void DrawParallel();// line parallel to the border void DrawOptimist();// line of optimistic forecast void DrawPessimist();// line of pessimistic forecast };
Une classe représente des opérations séquentielles qu'une personne effectuerait si elle était à la place d'une machine. Quoi qu'il en soit, la détection d'une formation peut être divisée en une série d'opérations simples qui se succèdent les unes aux autres. Il existe une règle en mathématiques : si vous ne savez pas comment résoudre une équation, simplifiez-la. Cette règle s'applique non seulement aux mathématiques, mais aussi aux algorithmes. La logique de détection n'est pas claire au départ. Mais si vous savez par où commencer la détection, la tâche devient beaucoup plus simple. Dans ce cas, pour trouver l'ensemble du modèle, nous recherchons soit les sommets, soit les creux, soit les deux.
Déterminer les hauts et les bas
En l'absence de sommets et de creux, l'ensemble de la configuration n'a pas de sens, puisque la présence de sommets et de creux est une condition nécessaire à la configuration, bien que cette condition ne suffise pas à elle seule. Il existe différentes manières de déterminer les sommets. La condition la plus importante est la présence d'une demi-vague prononcée. La demi-vague est déterminée par 2 mouvements opposés prononcés, qui dans notre cas doivent être plusieurs barres à la suite, dans une seule direction. Nous devons déterminer pour cela le nombre minimum de barres dans une direction, ce qui indique la présence d'un mouvement. Pour ce faire, nous allons fournir une variable d'entrée.
bool ExtremumsPatternFamilySearcher::SearchFirstUps()// find tops { int NumUp=0;// the number of found tops int NumDown=0;// the number of found bottoms bool bDown=false;// an auxiliary boolean which shows if a segment of bearish candlesticks has been found bool bUp=false;// an auxiliary boolean which shows if a segment of bullish candlesticks has been found bool bNextUp=true;// can we move on to searching for the next top bool bNextDown=true;// can we move on to searching for the next bottom for(int i=0;i<ArraySize(TopsUp);i++)// before search, set all necessary tops to an inactive state { TopsUp[i].bActive=false; } for(int i=0;i<ArraySize(TopsUpAll);i++)// before search, set all tops to an inactive state { if (!TopsUpAll[i].bActive) break; TopsUpAll[i].bActive=false; } for(int i=0;i<BarsM;i++) { if ( i+MinimumSeriesBarsM-1 < BarsM )// if remaining bars are enough to determine the extremum and we can start searching for the next top { if ( bNextUp )// if it is allowed to search for the next top { bDown=true; for(int j=i;j<i+MinimumSeriesBarsM;j++)// determine the first extrema for upper tops { if ( Open[j]-Close[j] < 0 )// if at least one of the selected candlesticks was upward { bDown=false; break; } } if ( bDown ) { TopsUpAll[NumUp].Datetime0=Time[i+MinimumSeriesBarsM-1]; TopsUpAll[NumUp].Index0=i+MinimumSeriesBarsM-1; bNextUp=false; } } } if ( MinimumSeriesBarsM+i < BarsM && bDown )// if the remaining bars are enough to determine the second half of the extremum and the previous half has been found { bUp=true; for(int j=i;j<MinimumSeriesBarsM+i;j++)//determine further candlesticks in the opposite direction { if ( Open[j]-Close[j] > 0 )//if at least one of the selected candlesticks was downward { bUp=false; break; } } if ( bUp ) { TopsUpAll[NumUp].Datetime1=Time[i]; TopsUpAll[NumUp].Index1=i; TopsUpAll[NumUp].bActive=true; bNextUp=false; } } // after that, register the found formation as a top, if it is a top if ( bDown && bUp ) { CalculateMaximum(TopsUpAll[NumUp],TopsUpAll[NumUp].Index0,TopsUpAll[NumUp].Index1);// calculate extremum between two bars bNextUp=true; bDown=false; bUp=false; NumUp++; } } if ( NumUp >= TopsM ) return true;// if the required number of tops have been found else return false; }
Les creux sont définis de manière opposée :
bool ExtremumsPatternFamilySearcher::SearchFirstDowns()// find bottoms { int NumUp=0; int NumDown=0; bool bDown=false;// an auxiliary boolean which shows if a segment of bearish candlesticks has been found bool bUp=false;// an auxiliary boolean which shows if a segment of bullish candlesticks has been found bool bNextUp=true;// can we move on to searching for the next top bool bNextDown=true;// can we move on to searching for the next bottom for(int i=0;i<ArraySize(TopsDown);i++)// before search, set all necessary bottoms to an inactive state { TopsDown[i].bActive=false; } for(int i=0;i<ArraySize(TopsDownAll);i++)// before search, set all bottoms to an inactive state { if (!TopsDownAll[i].bActive) break; TopsDownAll[i].bActive=false; } for(int i=0;i<BarsM;i++) { if ( i+MinimumSeriesBarsM-1 < BarsM )// if remaining bars are enough to determine the extremum and we can start searching for the next top { if ( bNextDown )// if it is allowed to search for the next bottom { bUp=true; for(int j=i;j<i+MinimumSeriesBarsM;j++)// determine the first extrema for upper tops { if ( Open[j]-Close[j] > 0 )//if at least one of the selected candlesticks was downward { bUp=false; break; } } if ( bUp ) { TopsDownAll[NumDown].Datetime0=Time[i+MinimumSeriesBarsM-1]; TopsDownAll[NumDown].Index0=i+MinimumSeriesBarsM-1; bNextDown=false; } } } if ( MinimumSeriesBarsM+i < BarsM && bUp )// if the remaining bars are enough to determine the second half of the extremum and the previous half has been found { bDown=true; for(int j=i;j<MinimumSeriesBarsM+i;j++)//determine further candlesticks in the opposite direction { if ( Open[j]-Close[j] < 0 )// if at least one of the selected candlesticks was upward { bDown=false; break; } } if ( bDown ) { TopsDownAll[NumDown].Datetime1=Time[i]; TopsDownAll[NumDown].Index1=i; TopsDownAll[NumDown].bActive=true; bNextDown=false; } } // after that, register the found formation as a bottom, if it is a bottom if ( bDown && bUp ) { CalculateMinimum(TopsDownAll[NumDown],TopsDownAll[NumDown].Index0,TopsDownAll[NumDown].Index1);// calculate extremum between two bars bNextDown=true; bDown=false; bUp=false; NumDown++; } } if ( NumDown == TopsM ) return true;//if the required number of bottoms have been found else return false; }
Dans ce cas, je n'ai pas utilisé la logique des fractales. Au lieu de cela, j'ai créé ma propre logique pour déterminer les hauts et les bas. Je ne pense pas que ce soit mieux ou moins bien que les fractales. Mais au moins il n'est pas nécessaire d'utiliser une fonctionnalité externe. Il n'est pas nécessaire non plus d'utiliser les fonctions intégrées du langage, qui ne sont pas toujours indispensables. Ces fonctions pourraient être utiles, mais dans ce cas, elles sont redondantes. La fonction détermine tous les hauts et les bas avec lesquels nous travaillerons à l'avenir. L'image suivante donne une représentation visuelle de ce qui se passe dans cette fonction :
Elle recherche d'abord les mouvements 1, puis les mouvements 2, et enfin 3, ce qui implique la détermination du haut ou du bas. La logique pour 3 est mise en œuvre dans deux fonctions distinctes :
void ExtremumsPatternFamilySearcher::CalculateMaximum(Top &T,int Index0,int Index1)// if 2 intermediate points are found, find High between them { double MaxValue=High[Index0]; datetime MaxTime=Time[Index0]; int MaxIndex=Index0; for(int i=Index0;i<=Index1;i++) { if ( High[i] > MaxValue ) { MaxValue=High[i]; MaxTime=Time[i]; MaxIndex=i; } } T.DatetimeExtremum=MaxTime; T.IndexExtremum=MaxIndex; T.Price=MaxValue; } void ExtremumsPatternFamilySearcher::CalculateMinimum(Top &T,int Index0,int Index1)//if 2 intermediate points are found, find Low between them { double MinValue=Low[Index0]; datetime MinTime=Time[Index0]; int MinIndex=Index0; for(int i=Index0;i<=Index1;i++) { if ( Low[i] < MinValue ) { MinValue=Low[i]; MinTime=Time[i]; MinIndex=i; } } T.DatetimeExtremum=MinTime; T.IndexExtremum=MinIndex; T.Price=MinValue; }
Placez ensuite le tout dans un conteneur préparé à l'avance. La logique est la suivante : toutes les structures utilisées dans la classe nécessitent l'ajout progressif de données. Après avoir franchi toutes les étapes et tous les stades, les données requises sont produites. À l'aide de ces données, le modèle peut être représenté graphiquement sur le graphique. Bien entendu, la logique déterminante du haut et du bas peut être différente. Mon but est seulement de montrer une logique de détection simple pour des choses complexes.
Sélection des tops à travailler
Les hauts et les bas que nous avons trouvés ne sont que des intermédiaires. Après les avoir trouvés, nous devons sélectionner les sommets que nous considérons comme les plus appropriés pour jouer le rôle d'épaules. Nous ne pouvons pas le déterminer avec certitude car le code ne comporte pas de vision artificielle (en général, l'utilisation de techniques aussi complexes n'est pas susceptible d'améliorer les performances). Pour l'instant, sélectionnons les sommets les plus proches du marché :
bool ExtremumsPatternFamilySearcher::PrepareExtremums()// assign the tops with which we will work { int Quantity;// an auxiliary counter for random tops int PrevIndex;// an auxiliary index for maintaining the order of indexes (increment only) for(int i=0;i<TopsM;i++)// simply select the tops that are closest to the market { TopsUp[i]=TopsUpAll[i]; TopsDown[i]=TopsDownAll[i]; } return true; }
Visuellement, sur le tableau des symboles, la logique sera équivalente à la variante dans le cadre violet. Je vais dessiner d'autres variantes pour la sélection :
Dans ce cas, la logique de sélection est très simple. Les variantes sélectionnées sont 0 et 1 car elles sont les plus proches du marché. Ici, tout s'applique à un double sommet. Mais la même logique sera utilisée pour les triples sommets ou les sommets multiples, la seule différence étant le nombre de sommets sélectionnés.
Cette fonction sera étendue à l'avenir pour permettre de sélectionner les sommets de manière aléatoire, comme indiqué en bleu dans l'image ci-dessus. Cela permettra de simuler plusieurs instances de chercheurs de motifs. Cela permet une recherche plus efficace et plus fréquente de tous les modèles dans le mode automatisé.
Détermination de la direction du motif
Une fois que nous avons identifié les sommets et les creux, nous devons déterminer la direction de la formation, si une telle formation existe à un moment donné du marché. À ce stade, j'envisage d'accorder une plus grande priorité à la direction dont le type d'extremum est le plus proche du marché. Sur la base de cette logique, utilisons la variante 0 de la figure, car le plus proche du marché est le bas et non le haut (à condition que la situation sur le marché soit exactement la même que dans la figure). Cette partie est simple dans le code :
void ExtremumsPatternFamilySearcher::DirectionOfFormation()// determine whether it is a double top (1) or double bottom (-1) (only if all tops and bottoms are found - if not found, then 0) { if ( TopsDown[0].DatetimeExtremum > TopsUp[0].DatetimeExtremum && TopsDown[ArraySize(TopsDown)-1].bActive ) { StartTop=TopsDown[ArraySize(TopsDown)-1]; EndTop=TopsDown[0]; FormationDirection=-1; } else if ( TopsDown[0].DatetimeExtremum < TopsUp[0].DatetimeExtremum && TopsUp[ArraySize(TopsUp)-1].bActive ) { StartTop=TopsUp[ArraySize(TopsUp)-1]; EndTop=TopsUp[0]; FormationDirection=1; } else FormationDirection=0; }
Les actions ultérieures nécessitent une orientation clairement définie. La direction est équivalente au type du motif :
- Sommets multiples
- Creux multiples
Ces règles s'appliquent également à la figure tête-épaules et à toutes les autres formations hybrides. La classe était censée être commune à tous les modèles de cette famille — cette généralité fonctionne déjà en partie.
Filtres permettant d'écarter les modèles incorrects :
Allons plus loin. Sachant que nous disposons d'une direction et d'une des façons de sélectionner les sommets et les creux, nous devons prévoir ce qui suit pour un sommet multiple : les sommets qui se trouvent entre les sommets sélectionnés doivent être plus bas que le plus bas des sommets sélectionnés. Pour des creux multiples, ces creux doivent être plus élevés que le plus élevé des creux sélectionnés. Dans ce cas, si les sommets sont choisis au hasard, tous les sommets sélectionnés seront clairement distingués. Dans le cas contraire, cette vérification n'est pas nécessaire :
bool ExtremumsPatternFamilySearcher::IsExtremumsAbsolutely()// require the selected extrema to be the most extreme ones { if ( bRandomExtremumsM )// check only if we have a random selection of tops (in other case the check should be considered completed) { if ( FormationDirection == 1 ) { int StartIndex=RandomIndexUp[0]; int EndIndex=RandomIndexUp[ArraySize(RandomIndexUp)-1]; for(int i=StartIndex+1;i<EndIndex;i++)// check all tops between the selected ones { for(int j=0;j<ArraySize(TopsUp);j++) { if ( TopsUpAll[i].Price >= TopsUp[j].Price ) { for(int k=0;k<ArraySize(RandomIndexUp);k++) { if ( i != RandomIndexUp[k] ) return false; } } } } return true; } else if ( FormationDirection == -1 ) { int StartIndex=RandomIndexDown[0]; int EndIndex=RandomIndexDown[ArraySize(RandomIndexDown)-1]; for(int i=StartIndex+1;i<EndIndex;i++)// check all tops between the selected ones { for(int j=0;j<ArraySize(TopsDown);j++) { if ( TopsDownAll[i].Price <= TopsDown[j].Price ) { for(int k=0;k<ArraySize(RandomIndexDown);k++) { if ( i != RandomIndexDown[k] ) return false; } } } } return true; } else return false; } else { return true; } }
Si nous affichons visuellement la variante correcte et la variante incorrecte de la sélection aléatoire du sommet, qui est effectuée par la dernière fonction prédicat, cela se présente comme suit :
Ces critères se retrouvent dans les schémas haussiers et baissiers. La figure montre un schéma haussier à titre d'exemple. Le deuxième cas peut être facilement imaginé.
Après avoir effectué toutes les procédures préparatoires, nous pouvons procéder à la recherche du cou. Les traders ne tracent pas tous le cou de la même manière. J'ai déterminé sous conditions plusieurs types de construction :
- Visuellement incliné (pas par les ombres)
- Visuellement, horizontal (pas par les ombres)
- Point le plus haut ou le plus bas, incliné (par les ombres)
- Point le plus haut ou le plus bas, horizontal (par les ombres)
Pour des raisons de sécurité et pour augmenter les chances de gain, je pense que la variante optimale est la 4. J'ai choisi cette option pour les raisons suivantes :
- Le début d'un mouvement d'inversion est plus clair
- Cette approche est plus facile à mettre en œuvre dans le code
- La pente est déterminée sans ambiguïté (horizontalement)
Ce n'est peut-être pas tout à fait correct du point de vue de la construction, mais je n'ai pas trouvé de règles claires. Ceci n'est pas critique du point de vue du trading algorithmique. Si nous trouvons quelque chose de rationnel dans ce modèle, le testeur ou la visualisation nous montrera certainement quelque chose. La poursuite de cette tâche implique le renforcement des résultats de trading, ce qui est toutefois une tâche tout à fait différente.
J'ai créé 2 fonctions miroir pour les modèles haussiers et baissiers qui définissent tous les paramètres nécessaires du cou :
void ExtremumsPatternFamilySearcher::FindNeckUp(Top &TStart,Top &TEnd)// find the neck line based on the two extreme tops (for the classic multiple top) { double PriceMin=Low[TStart.IndexExtremum]; datetime TimeMin=Time[TStart.IndexExtremum]; for(int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)// define the lowest point { if ( Low[i] < PriceMin ) { PriceMin=Low[i]; TimeMin=Time[i]; } } // define the parameters of the anchor point and all parameters of the line equation Neck.Price0=PriceMin; Neck.TimeX=TimeMin; Neck.Time0=Time[0]; Neck.Price1=PriceMin; Neck.Time1=TStart.DatetimeExtremum; Neck.DirectionOfFormation=true; Neck.CalculateKC(); } void ExtremumsPatternFamilySearcher::FindNeckDown(Top &TStart,Top &TEnd)// find the neck line based on two extreme bottoms (for the classic multiple bottom) { double PriceMax=High[TStart.IndexExtremum]; datetime TimeMax=Time[TStart.IndexExtremum]; for(int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)// define the lowest point { if ( High[i] > PriceMax ) { PriceMax=High[i]; TimeMax=Time[i]; } } // define the parameters of the anchor point and all parameters of the line equation Neck.Price0=PriceMax; Neck.TimeX=TimeMax; Neck.Time0=Time[0]; Neck.Price1=PriceMax; Neck.Time1=TStart.DatetimeExtremum; Neck.DirectionOfFormation=false; Neck.CalculateKC(); }
Pour un tracé correct et simple du cou, il est préférable d'utiliser les mêmes règles de construction du cou pour tous les modèles de la famille choisie. Cela permet d'éliminer les détails inutiles qui, dans notre cas, n'apporteront rien. Pour construire un cou pour des sommets multiples de n'importe quelle complexité, il est préférable d'utiliser 2 hauts extrêmes du modèle. Les indices de ces sommets seront les indices entre lesquels nous rechercherons le prix le plus bas ou le plus élevé dans le segment de marché sélectionné. Le cou sera une ligne horizontale régulière. Les premiers points d'ancrage doivent se situer exactement à ce niveau, et le temps d'ancrage doit être exactement égal au temps des sommets ou des creux extrêmes (en fonction du modèle considéré). C'est ce que l'on voit sur la photo :
La fenêtre de recherche du bas ou du haut se situe exactement entre le premier et le dernier sommet. Cette règle est valable pour n'importe quel motif de cette famille, pour n'importe quel nombre de hauts et de bas.
Pour déterminer la cible optimiste, il faut d'abord définir la taille du modèle. La taille du motif est la distance verticale entre la tête et le cou en points. Pour déterminer la distance, il faut d'abord trouver le sommet qui est le plus éloigné du cou. Ce haut sera la bordure du motif :
void ExtremumsPatternFamilySearcher::SearchFarestTop()// define the farthest top { double MaxTranslation;// temporary variable to determine the highest top if ( FormationDirection == 1 )// if we deal with a multiple top { MaxTranslation=TopsUp[0].Price-Neck.Price0;// temporary variable to determine the highest top FarestTop=TopsUp[0]; for(int i=1;i<ArraySize(TopsUp);i++) { if ( TopsUp[i].Price-Neck.Price0 > MaxTranslation ) { MaxTranslation=TopsUp[i].Price-Neck.Price0; FarestTop=TopsUp[i]; } } } if ( FormationDirection == -1 )// if we deal with a multiple bottom { MaxTranslation=Neck.Price0-TopsDown[0].Price;// temporary variable to determine the lowest bottom FarestTop=TopsDown[0]; for(int i=1;i<ArraySize(TopsDown);i++) { if ( Neck.Price0-TopsDown[i].Price > MaxTranslation ) { MaxTranslation=Neck.Price0-TopsDown[0].Price; FarestTop=TopsDown[i]; } } } }
Une vérification supplémentaire est nécessaire pour s'assurer que les sommets ne diffèrent pas trop. Nous ne pouvons passer aux étapes suivantes que si le contrôle est réussi. Plus précisément, il devrait y avoir 2 contrôles : l'un pour la taille verticale des extrêmes, l'autre pour l'horizontale (temps). Si les sommets sont trop éloignés dans le temps, cette variante ne convient pas non plus. Voici une vérification de la taille verticale :
bool ExtremumsPatternFamilySearcher::bBalancedExtremums()// balance the tops { double Lowest;// the lowest top for the multiple top double Highest;// the highest bottom for the multiple bottom double AbsMin;// distance from the neck to the nearest top if ( FormationDirection == 1 )// for the multiple top { Lowest=TopsUp[0].Price; for(int i=1;i<ArraySize(TopsUp);i++)// find the lowest top { if ( TopsUp[i].Price < Lowest ) Lowest=TopsUp[i].Price; } AbsMin=Lowest-Neck.Price0;// determine distance from the lowest top to the neck if ( AbsMin == 0.0 ) return false; if ( ((FarestTop.Price - Neck.Price0)-AbsMin)/AbsMin >= RelativeUnstabilityM ) return false;// if the head is too much bigger than the lowest leverage } else if ( FormationDirection == -1 )// for the multiple bottom { Highest=TopsDown[0].Price; for(int i=1;i<ArraySize(TopsDown);i++)// find the highest top { if ( TopsDown[i].Price > Highest ) Highest=TopsDown[i].Price; } AbsMin=Neck.Price0-Highest;// determine distance from the highest top to the neck if ( AbsMin == 0.0 ) return false; if ( ((Neck.Price0-FarestTop.Price)-AbsMin)/AbsMin >= RelativeUnstabilityM ) return false;// if the head is too much bigger than the lowest leverage } else return false; return true; }
Pour déterminer la taille verticale correcte des sommets, nous avons besoin de 2 sommets. Le premier est celui qui est le plus éloigné du cou, et le second celui qui en est le plus proche. Si ces tailles diffèrent fortement, cette formation peut s'avérer invalide et il est préférable de ne pas prendre de risque et de la marquer comme invalide. Comme pour le prédicat précédent, tout cela peut s'accompagner d'un graphisme approprié de ce qui est bien et de ce qui est mal :
Ils sont faciles à déterminer visuellement, mais le code a besoin d'une mesure quantitative. Dans ce cas, c'est aussi simple que cela :
- K = (Max - Min) / Min
- K <= RelativeUnstabilityM
Cette métrique est assez efficace pour filtrer un grand nombre de faux motifs. Or, même le code le plus sophistiqué ne peut être plus efficace que notre œil. La seule chose que nous puissions faire est de rendre la logique aussi proche que possible de la réalité, mais il faut savoir s'arrêter.
Le contrôle horizontal se présente de la même manière. La seule différence est que nous utilisons des indices de barres comme tailles (vous pouvez utiliser le temps, il n'y a pas de différence fondamentale) :
bool ExtremumsPatternFamilySearcher::bBalancedExtremumsTime()// balance the sizes of shoulders and head along the horizontal axis { double Lowest;// minimum distance between the tops double Highest;// maximum distance between the tops if ( FormationDirection == 1 )// for the multiple top { Lowest=TopsUp[1].IndexExtremum-TopsUp[0].IndexExtremum; Highest=TopsUp[1].IndexExtremum-TopsUp[0].IndexExtremum; for(int i=1;i<ArraySize(TopsUp)-1;i++)// find the lowest top { if ( TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum < Lowest ) Lowest=TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum; if ( TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum > Highest ) Highest=TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum; } if ( double(Highest-Lowest)/double(Lowest) > RelativeUnstabilityTimeM ) return false;// if the width of one of the waves differs much } else if ( FormationDirection == -1 )// for the multiple bottom { Lowest=TopsDown[1].IndexExtremum-TopsDown[0].IndexExtremum; Highest=TopsDown[1].IndexExtremum-TopsDown[0].IndexExtremum; for(int i=1;i<ArraySize(TopsDown)-1;i++)// find the lowest top { if ( TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum < Lowest ) Lowest=TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum; if ( TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum > Highest ) Highest=TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum; } if ( double(Highest-Lowest)/double(Lowest) > RelativeUnstabilityTimeM ) return false;// if the width of one of the waves differs much } else return false; return true; }
Pour ce contrôle, nous pouvons utiliser une mesure similaire. Visuellement, elle peut être exprimée comme suit :
Dans ce cas, les critères quantitatifs seront les mêmes. Mais cette fois-ci, nous utilisons des indices ou le temps au lieu de points. Il serait peut-être préférable de mettre en œuvre séparément le nombre avec lequel nous effectuons la comparaison, ce qui laisserait une marge de manœuvre pour un ajustement flexible :
- K = (Max - Min) / Min
- K <= RelativeUnstabilityTimeM
La ligne de cou doit croiser le prix à gauche, ce qui signifie que le schéma a été précédé d'une tendance :
bool ExtremumsPatternFamilySearcher::CorrectNeckUpLeft()// next the neck line must be corrected so that it finds an intersection with the price on the left { bool bCrossNeck=false;// indicates if the neck was crossed if ( Neck.DirectionOfFormation )// if the neck is found for a double top { for(int i=StartTop.Index1;i<BarsM;i++)// define the intersection point { if ( High[i] >= FarestTop.Price )// if the movement goes beyond the formation, then the formation is fake { return false; } if ( Close[i] < Neck.Price0 && Open[i] < Neck.Price0 && High[i] < Neck.Price0 && Low[i] < Neck.Price0 ) { Neck.Time1=Time[i]; Neck.Index1=i; return true; } } } return false; } bool ExtremumsPatternFamilySearcher::CorrectNeckDownLeft()// next the neck line must be corrected so that it finds an intersection with the price on the left { bool bCrossNeck=false;// indicates if the neck was crossed if ( !Neck.DirectionOfFormation )// if the neck is found for a double bottom { for(int i=StartTop.Index1;i<BarsM;i++)// define the intersection point { if ( Low[i] <= FarestTop.Price )// if the movement goes beyond the formation, then the formation is fake { return false; } if ( Close[i] > Neck.Price0 && Open[i] > Neck.Price0 && High[i] > Neck.Price0 && Low[i] > Neck.Price0 ) { Neck.Time1=Time[i]; Neck.Index1=i; return true; } } } return false; }
Là encore, il existe 2 fonctions miroir pour les schémas haussiers et baissiers. Vous trouverez ci-dessous une illustration graphique de ce prédicat et du suivant :
Les cases bleues indiquent les segments de marché dont nous contrôlons l'intersection. Les deux segments se trouvent derrière le modèle, à gauche et à droite des sommets extrêmes.
Il ne reste plus que 2 vérifications à faire :
- Nous avons besoin d'un modèle qui croise la ligne de cou à l'instant présent (à la barre zéro).
- Le motif doit être précédé d'un mouvement supérieur ou égal au motif lui-même.
Le premier point est nécessaire pour le trading algorithmique. Je ne pense pas qu'il soit utile de détecter des formations uniquement pour les visualiser, bien que cette fonction soit également prévue. Nous avons besoin à la fois de détection et de trouver exactement le point à partir duquel nous pouvons négocier - où nous pouvons immédiatement ouvrir une position, sachant que nous sommes au point d'entrée. Le deuxième point est l'une des conditions nécessaires, car le motif lui-même est inutile sans un bon mouvement précédent.
Le croisement de la barre zéro (en vérifiant l'intersection à droite) est déterminé comme suit :
int ExtremumsPatternFamilySearcher::CorrectNeckUpRight()// next the neck line must be corrected so that it finds an intersection with the price on the right bool bCrossNeck=false;// indicates if the neck was crossed if ( Neck.DirectionOfFormation )// if the neck is found for a double top { for(int i=EndTop.IndexExtremum;i>1;i--)// define the intersection point { if ( High[i] > FarestTop.Price || Low[i] < Neck.Price0 )// if the movement goes beyond the formation, then the formation is fake { return -1; } } } if ( Close[0] <= Neck.Price0 ) { Neck.Time0=Time[0]; return 1; } return 0; } int ExtremumsPatternFamilySearcher::CorrectNeckDownRight()// next the neck line must be corrected so that it finds an intersection with the price on the right { bool bCrossNeck=false;// indicates if the neck was crossed if ( !Neck.DirectionOfFormation )// if the neck is found for a double bottom { for(int i=EndTop.IndexExtremum;i>1;i--)// define the intersection point { if ( Low[i] < FarestTop.Price || High[i] > Neck.Price0 )// if the movement goes beyond the formation, then the formation is fake { return -1; } } } if ( Close[0] >= Neck.Price0 ) { Neck.Time0=Time[0]; return 1; } return 0; }<
Là encore, nous avons 2 fonctions miroirs. Veuillez noter que l'intersection à droite n'est pas considérée comme valide si le prix a dépassé le modèle et est ensuite revenu - ce comportement est couvert ici et est illustré dans la figure précédente.
Déterminons maintenant comment trouver la tendance précédente. Jusqu'à présent, j'utilise la ligne de prévision optimiste à cette fin. S'il existe un segment de marché entre le cou et la ligne de la prévision optimiste, il s'agit du mouvement souhaité. Ce mouvement ne doit pas être trop étendu dans le temps, sinon il ne s'agit évidemment pas d'un mouvement :
bool ExtremumsPatternFamilySearcher::bWasTrend()// did we find the movement preceding the formation (also move here the anchor point to the intersection) { bool bCrossOptimist=false;// denotes if the neck is crossed if ( FormationDirection == 1 )// if the optimistic forecast is at the double top { for(int i=Neck.Index1;i<BarsM;i++)// define the intersection point { if ( High[i] > Neck.Price0 )// if the movement goes beyond the neck, then the formation is fake { return false; } if ( Low[i] < OptimistLine.Price0 ) { OptimistLine.Time1=Time[i]; return true; } } } else if ( FormationDirection == -1 )// if the optimistic forecast is at the double bottom { for(int i=Neck.Index1;i<BarsM;i++)// define the intersection point { if ( Low[i] < Neck.Price0 )// if the movement goes beyond the neck, then the formation is fake { return false; } if ( High[i] > OptimistLine.Price0 ) { OptimistLine.Time1=Time[i]; return true; } } } return false; }
Le dernier prédicat peut également être représenté graphiquement comme suit :
Terminons la revue du code ici et passons aux évaluations visuelles. Je pense que les idées principales de la méthode ont été suffisamment décrites dans cet article. D'autres idées seront examinées dans le prochain article de cette série.
Vérifions le résultat dans le testeur visuel de MetaTrader 5 :
J'utilise toujours le dessin au trait sur le graphique, car il est rapide, simple et clair. L'Aide MQL5 fournit des exemples d'utilisation de tous les objets graphiques, y compris des lignes. Je ne fournirai pas le code de dessin ici, mais vous pouvez voir le résultat de son exécution. Bien sûr, tout peut être amélioré, mais nous n'avons qu'un prototype. Je crois donc que nous pouvons utiliser ici le principe de "nécessité et de suffisance" :
Voici un exemple avec un triple sommet. Cet exemple m'a semblé plus intéressant. Les doubles sommets sont détectés de la même manière - il suffit de définir le nombre de sommets souhaité dans les paramètres. Le code ne trouve pas souvent de telles formations, mais ce n'est qu'une démonstration. Le code peut être affiné (ce que je prévois de faire plus tard).
Autres idées de développement
Plus tard, nous examinerons ce qui n'a pas été dit dans cet article et nous améliorerons la qualité de la recherche pour toutes les formations. Nous allons également affiner la classe pour lui permettre de détecter les formations tête-épaules. Nous essaierons également de trouver des fonctions hybrides possibles pour ces formations ; l'une d'entre elles pourrait être "N sommets et épaules multiples". La série n'est pas consacrée uniquement à cette famille de motifs et comprendra du nouveau matériel intéressant et utile. Il existe différentes approches de la recherche de motifs. L'idée de cette série est de montrer autant de motifs que possible à l'aide de différents exemples et de couvrir ainsi les différentes manières possibles de décomposer une tâche complexe en une série de tâches plus simples. La série comprendra :
- D’autres modèles intéressants
- D’autres méthodes de détection des différents types de formation
- Négocier en utilisant des données historiques et en collectant des statistiques pour différents instruments et différentes périodes.
- Il existe de nombreux modèles, et je ne les connais pas tous (je peux donc potentiellement prendre en compte votre modèle).
- Les niveaux (car ils sont souvent utilisés pour détecter les renversements).
Conclusion
J'ai essayé de rendre cet article simple et compréhensible pour tout le monde. J'espère que vous y trouverez quelque chose d'utile. La conclusion de cet article est que, comme le montre le testeur visuel de stratégie, un code simple est capable de trouver des formations complexes. Nous n'avons pas nécessairement besoin d'utiliser des réseaux neuronaux ou d'écrire/utiliser des algorithmes complexes de vision artificielle. Le langage MQL5 possède de nombreuses fonctionnalités permettant de mettre en œuvre les algorithmes les plus complexes. Les possibilités ne sont limitées que par votre imagination et votre diligence.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/9394





- 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