### Contents

- Abstract
- About the reversal patterns
- Why Multiple Top — its specific features
- Can the Double Top concept be extended?
- Writing code to render Multiple Top
- Further development ideas
- Conclusion

### Abstract

Patterns are often discussed on the internet, because they are used by many traders. Patterns can be referred to as visual analysis criteria for determining the direction of the pricing that follows. Algo trading is different from that. There cannot be visual criteria for algorithmic trading. Expert Advisors and indicators have individual methods for working with the price series. There are advantages and disadvantages at both ends. The code lacks the breadth of human thinking and the quality of human analysis, but the code has other valuable advantages: incomparable speed and incomparable amount of numerical or logical data processed per unit of time. It is not easy to instruct the machine what to do. This takes some practice. Over time, the programmer begins to understand the machine, and the machine begins to understand the programmer. This series of articles will be useful for beginners, who will learn how to structure their thoughts and to split complex tasks into simpler steps.

### About the reversal patterns

For me personally, reversal patterns have a too vague definition. Furthermore, they do not have any underlying mathematics. To be honest, any pattern has no underlaying mathematics and thus the only mathematics which can be considered here is statistics. Statistics are the only criteria for truth, but statistics are compiled based on the real trading. Obviously, there are no sources which can provide very precise statistics. There is even no point in providing such data for one specific research problem. The only solution here is backtesting and visualization in the strategy tester. Although the approach offers lower data quality, it has an undeniable advantage which is speed along with data amount.

Of course, reversal patterns do not serve as a sufficient tool for determining trend reversals, but in combination with other methods of analysis, such as levels or candlestick analysis, they can produce the desired result. Within this series of article, the patterns are not considered as a specifically interesting method of analysis, but they can be used for practicing algorithmic trading skills. In addition to practicing, you will obtain an interesting and useful auxiliary tool - if not for algo-trading, then for the trader eye. Useful indicators are greatly valued.

### Why Multiple Top — its specific features

This pattern has become quite popular on the internet due to its simplicity. The pattern is quite common on different trading instruments and on various chart timeframes, simply because there is nothing complicated about it. Furthermore, if you look closer at the pattern, you can see that the method concept can be expanded by using algo-trading and MQL5 language capabilities. We can try to create some general code which will not be limited only by a double top. A wisely created prototype can be used for exploring all pattern hybrids and successors.

The classic successor to the multiple top is the very popular "Head and Shoulders" pattern. Unfortunately, there is no structured information on how to trade this pattern. This problem is common for a lot of popular strategies - because there are many beautiful words but no statistics. I will try to understand in this article whether it is possible to use them in the framework of algorithmic trading. The only method to collect statistics without trading on a demo or real account is to use the capabilities of the strategy tester. Without this tool, you will not be able to draw any complex conclusions regarding a particular strategy.

### Can the Double Top concept be extended?

Regarding the topic of the article, I will try to draw a diagram as a tree of patterns that starts from a double top. This will assist in understanding how broad the possibilities of this concept are:

I decided to combine the concept of several patterns with the assumption that they are based on approximately the same idea. This idea has a simple beginning - find a good movement in any direction and correctly determine the place where it is supposed to reverse. After visual contact with the proposed pattern, the trader should correctly draw some auxiliary lines, which should assist in evaluating whether the pattern meets certain criteria as well as in determining the market entry point along with the target and stop loss level. Take profit can be used here instead of the target.

Patterns can have some common construction principles, based on which the concept of these patterns can be combined. Such clear definition is what differs algorithmic traders from manual traders. Uncertainty and multiple interpretation of the same principles may lead to disappointing consequences.

The basic patters are as follows:

- Double Top
- Triple Top
- Head and Shoulders

These patterns have similar structures and usage principles. All of them are aimed at identifying reversals. All three patterns have a similar logic regarding auxiliary lines. Please consider an example of the Double Top:

In the above figure, all the required lines are numbered and mean the following:

- Trend resistance
- Auxiliary line for defining a pessimistic peak (someone thinks it is a neck)
- Neck line
- Optimistic target (it is also a take profit level for trading)
- The maximum allowable stop-loss level (it is set at the far top)
- Optimistic forecast line (equal to the previous trend movement)

A pessimistic target is determined relative to the point of the neck line intersection from the edge which is nearest to the market - we take the distance between "1" and "2", which is indicated as **"t"**, and measure the same distance in the direction of the proposed reversal. The minimum of the optimistic target is determined in a similar way, but the distance is measured between "5" and "3", which is indicated as **"s"**.

### Writing code to render Multiple Top

Let us begin by defining the reasoning logic to define these patterns. In order to find a pattern, we should stick to the bar-by-bar logic, that is, we will work not by ticks, but by bars. In this case, it will greatly reduce the load on the terminal as this will avoid unnecessary calculations. First, let us determine a class symbolizing some independent observer who will look for the pattern. All operations required for a correct pattern detection will be part of the instance, so search will be performed inside it. I have chosen this solution in order to enable further code modifications, for example, when we need to expand the functionality or to modify existing features.

** Class map**

Let's start with considering the class contents:

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 };

A class represents sequential operations which a person would perform if the person were in the place of a machine. Anyway, the detection of any formation can be split into a set of simple operations that follow one another. There is a rule in mathematics: if you don't know how to solve an equation, simplify it. This rule applies not only to mathematics, but also to any algorithm. The detection logic is not clear first. But if you know where to start detection, the task becomes much simpler. In this case, in order to find the whole pattern, we search for either tops or bottoms, or actually both.

** Determining tops and bottoms**

Without tops and bottoms, the whole pattern is meaningless, since the presence of tops and bottoms is a required condition for the pattern, although this condition alone is not enough. There are different ways to determining tops. The most important condition is the presence of a pronounced half-wave, while the half-wave is determined by two pronounced opposite movements, which in our case should be several bars in a row, in one direction. For this purpose, we need to determine the minimum number of bars in one direction, which indicate the presence of movement. For this, let's provide an input variable.

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; }

Bottoms are defined in the opposite way:

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; }

In this case I didn't use the logic of fractals. Instead, I created my own logic for determining tops and bottoms. I don't think it's better or worse than fractals, but at least there is no need to use any external functionality. Furthermore, there is no need to use unnecessary built-in language functions, which sometimes are not necessary. These functions might be good, but in this case they are redundant. The function determines all tops and bottoms, with which we will work in the future. The following image provides a visual representation of what is happening in this function:

First, it searches for movements 1; then it searches for movement 2, and finally 3 implies determining of the top or bottom. Logic for 3 is implemented in two separate functions that look like this:

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; }

Then, put all this into a pre-prepared container. The logic is as follows: all structures used within the class require gradual addition of data. After passing all the steps and stages, the required data is output. Using this data, the pattern can be graphically displayed on the chart. Of course, top and bottom determining logic can be different. My purpose is only to show a simple detection logic for complex things.

** Selecting tops to work with**

The tops and bottoms which we have found are only intermediate. After finding them, we need to select the tops which we consider as most appropriate to act as shoulders. We can't determine this for sure because the code does not have machine vision (in general, the usage of such complex techniques is unlikely to benefit the performance). For now, let's select the tops that are closest to the market:

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; }

Visually on the symbol chart, the logic will be equivalent to the variant in the purple frame. I will draw some more variants for selection:

In this case, the selection logic is very simple. The selected variants are 0 and 1 because they are closest to the market. Here everything applies to a double top. But the same logic will be used for triple or greater multiple top, the only difference being in the number of selected tops.

This function will be expanded in the future, to enable the ability to select tops randomly, as shown in blue in the image above. This will simulate multiple instances of pattern finders. This allows a more efficient and more frequent finding of all patterns in the automated mode.

** Determining the pattern direction**

Once we have identified the tops and bottoms, we must determine the direction of the formation, if such a formation exists at a given point in the market. At this stage, I consider assigning greater priority to the direction whose extremum type is closest to the market. Based on this logic, let's use variant 0 from the figure, because the closest to the market is the bottom, not the top (provided that the situation on the market is exactly the same as in the figure). This part is simple in the 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; } elseFormationDirection=0; }

Further actions require a clearly determined direction. The direction is equivalent to the pattern type:

- Multiple top
- Multiple bottom

These rules also apply for the Head and Shoulders pattern and all other hybrid formations. The class was supposed to be common for all patterns of this family — this generality is already working in part.

** Filters to discard incorrect patterns:**

Now let's go further. Knowing that we have a direction and one of the ways to select tops and bottoms, we must provide the following for a multiple top: the tops that are between the selected ones should be lower than the lowest of the selected ones. For a multiple bottom, such bottoms should be higher than the highest of the selected ones. In this case, if tops are selected randomly, all the selected tops would be clearly distinguished. Otherwise, this check is not required:

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; } }

If we visually display the correct and incorrect variant of random top selection, which is performed by the last predicate function, it will look like this:

These criteria are mirrored for the bullish and bearish patterns. The figure shows a bullish pattern as an example. The second case can be easily imagined.

After completing all preparatory procedures, we can proceed to searching for the neck. Different traders plot the neck in different ways. I have conditionally determined several types of construction:

- Visually tilted (not by shadows)
- Visually, horizontal (not by shadows)
- Highest or lowest point, tilted (by shadows)
- Highest or lowest point, horizontal (by shadows)

For safety reasons and to increase the chances of profit, I believe that the optimal variant is 4. I have chosen this due to the following:

- The beginning of a reversal movement is found more clearly
- This approach is easier to implement in code
- The slope is determined unambiguously (horizontally)

Perhaps, this is not entirely correct from the point of view of construction, but I haven't found any clear rules. This is not critical from the point of view of algo-trading. If we find something rational in this pattern, the tester or visualization will definitely show us something. Further task implies strengthening of trading results, which is however an absolutely different task.

I have created two mirror functions for the bullish and bearish patterns that define all the necessary parameters of the neck:

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 equationNeck.Price0=PriceMax;Neck.TimeX=TimeMax;Neck.Time0=Time[0];Neck.Price1=PriceMax;Neck.Time1=TStart.DatetimeExtremum;Neck.DirectionOfFormation=false;Neck.CalculateKC(); }

For correct and simple plotting of the neck, it's better to use the same rules for neck construction for all patterns of the selected family. On the one hand, this eliminates unnecessary details, which in our case will give nothing. To build a neck for a multiple top of any complexity, it is better to use two extreme tops of the pattern. The indices of these peaks will be the indices between which we will search for the lowest or highest price in the selected segment of the market. The neck will be a regular horizontal line. The first anchor points should be exactly at this level, while the anchor time should better be exactly equal to the time of the extreme tops or bottoms (depending on which pattern we are considering). This is how it will look in the picture:

The window to search for low or high is exactly between the first and the last top. This rule is valid for any pattern of this family, for any number of tops and bottoms.

To determine the optimistic target, first you should define the pattern size. The pattern size is the vertical distance from head to neck in points. To determine the distance, we first need to find the top which is farthest from the neck. This top will be the border of the pattern:

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]; } } } }

An additional check is needed to make sure the tops do not differ too much. We can proceed to further steps only if the check is successful. More precisely, there should be two checks: one for the vertical size of the extrema, the other for the horizontal (time). If tops are too distant in time, such a variant does not suit either. Here is a check for the vertical size:

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; }

To determine the correct vertical size of the tops, we need two tops. The first one is the farthest one from the neck, and the second one if the closest to it. If these sizes differ greatly, then this formation may turn out to be invalid, and it is better not to risk and mark it as invalid. Similarly to the previous predicate, all this can be accompanied by an appropriate graphics of what is right and what is wrong:

They are easy to determine visually, but the code needs a quantitative metric. In this case, it is as simple as follows:

- K = (
**Max**-**Min**)/**Min** - K <=
**RelativeUnstabilityM**

This metric is quite efficient to filter out quite a large number of false patterns. Well, even the most sophisticated code cannot be more efficient than our eye. The only thing we can do is make the logic as close to reality as we can — but here we must know where to stop.

The horizontal check will look similar. The only difference is that we use bar indices as sizes (you can use time, there is no fundamental difference):

bool ExtremumsPatternFamilySearcher::bBalancedExtremumsTime()// balance the sizes of shoulders and head along the horizontal axis { doubleLowest;// minimum distance between the tops doubleHighest;// 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; }

For this check, we can use a similar metric. Visually, it can be expressed as follows:

In this case, the quantitative criteria will be the same. However, this time we use indices or time instead of points. It might be better to implement the number, with which we are comparing, separately, which would give room for flexible adjustment:

- K = (
**Max**-**Min**)/**Min** - K <=
**RelativeUnstabilityTimeM**

The neck line must cross the price on the left — this means that the pattern was preceded by a trend:

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; }

Again, there are two mirror functions for the bullish and bearish patterns. Below is a graphical illustration of this predicate and the next one:

The blue boxes mark the market segments where we control the intersection. Both segments are behind the pattern, to the left and to the right of the extreme tops.

There are only two checks left:

- We need a pattern that crosses the neck line at the current moment (at the zero candlestick)
- The pattern must be preceded by a movement greater than or equal to the pattern itself

The first point is needed for algorithmic trading. I don't think it's worth detecting formations only for viewing them, although this function is also provided. We need both detection and finding exactly at the point from which we can trade — where we can immediately open a position, knowing that we are at the entry point. The second point is one of the necessary conditions, because the pattern itself is useless without a good preceding movement.

Zero candlestick cross (checking the intersection on the right) is determined as follows:

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; }

Again, we have two mirror functions. Please note that the intersection on the right is not considered valid if the price has moved beyond the pattern and then returned back - this behavior is covered here and is shown in the previous figure.

Now, let's determine how to find the preceding trend. SO far I am using the optimistic forecast line for this purpose. If there is a market segment between the neck and the line of the optimistic forecast, then this is the desired movement. This movement must not be too extended in time, otherwise it is obviously not a movement:

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; }

The last predicate can also be represented graphically as follows:

Let's finish reviewing the code here and move on to visual assessments. I think the main ideas of the method have been sufficiently described in this article. Further ideas will be considered in the next article of this series.

** Let's check the result in the MetaTrader 5 visual tester:**

I always use line drawing on the chart, as it is fast, simple, and clear. The MQL5 Help provides examples of using any graphical objects, including lines. I will not provide the drawing code here, but you can see its execution result. Of course, everything could be done better, but we only have a prototype. So, I believe here we can use the "necessity and sufficiency" principle:

Here is an example with a triple top. This example seemed more interesting to me. Double tops are detected in a similar way — you only need to set the desired number of tops in parameters. The code does not find such formations often, but it is only a demonstration. The code can be further refined (which I am planning to do later).

### Further development ideas

Later, we will consider what was left unsaid in this article, and will improve search quality for all formations. We will also refine the class to enable it to detect the Head and Shoulders formations. We will also try to find possible hybrid functions of these formations; one of them might be "N tops and multiple shoulders". The series is not devoted to only this family of patterns and will include new interesting and useful material. There are different approaches to pattern search, and the idea of this series is to show as many patterns as possible using different examples and thus to cover different possible ways to breaking down a complex task into a set of simpler ones. The series will include:

- Other interesting patterns
- Other methods for detecting different formation types
- Trading using historical data and collecting statistics for different instruments and timeframes
- There are a lot of patterns, and I don't know them all (so I can potentially consider your pattern)
- We will also consider levels (as levels are often used to detect reversals)

### Conclusion

I tried to make the material simple and understandable for everyone. I hope anyone can find something useful here. The conclusion of this particular article is that, as can be seen from the visual strategy tester, a simple code is able to find complex formations. We so not necessarily need to use neural networks or write/use some complex machine vision algorithms. The MQL5 language has rich functionality to implement even the most complex algorithms. The possibilities are only limited by your imagination and diligence.

Translated from Russian by MetaQuotes Software Corp.

Original article: https://www.mql5.com/ru/articles/9394

**Attached files**|