//+------------------------------------------------------------------+
//|                                                 ZigZagModule.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <ChartObjects\ChartObjectsLines.mqh>
//+------------------------------------------------------------------+
//| Class for obtaining ZigZag indicator data                        |
//+------------------------------------------------------------------+
class CZigZagModule
  {
protected:
   //--- Segment lines
   CChartObjectTrend m_trend_lines[];
   //---
   int               m_copy_extremums;    // Number of saved highs/lows
   int               m_segments_total;    // Number of segments
   int               m_direction;         // Direction
   int               m_counter_lows;      // Low counter
   int               m_counter_highs;     // High counter

   //--- Extremum prices
   double            m_zz_low[];
   double            m_zz_high[];
   //--- Extremum bars' indices
   int               m_zz_low_bar[];
   int               m_zz_high_bar[];
   //--- Extremum bars' time
   datetime          m_zz_low_time[];
   datetime          m_zz_high_time[];
   //--- Segment lines color
   color             m_lines_color;
   //---
private:
   //--- Arrays for obtaining source data
   double            m_zz_lows_temp[];
   double            m_zz_highs_temp[];
   datetime          m_zz_time_temp[];
   //---
public:
                     CZigZagModule(void);
                    ~CZigZagModule(void);

   //--- Number of extremums for copying
   void              CopyExtremums(const int total);
   //--- Number of (1) extremums and (2) segments copied
   int               CopyExtremums(void) { return(m_copy_extremums); }
   int               SegmentsTotal(void) { return(m_segments_total); }

   //--- Get data
   void              GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[]);
   void              GetZigZagData(const int handle,int buffer_num_highs,int buffer_num_lows,
                                   const string symbol,const ENUM_TIMEFRAMES period,
                                   const datetime start_time,const datetime stop_time);

   //--- Update the structure
   void              ZeroZigZagData(void);
   //---
public:
   //--- Last segment direction
   int               Direction(void) const { return(m_direction); }

   //--- Index of an extremum bar by a specified index
   int               LowBar(const int index);
   int               HighBar(const int index);
   //--- Price of extremums by a specified index
   double            LowPrice(const int index);
   double            HighPrice(const int index);
   //--- Time of an extremum bar by a specified index
   datetime          LowTime(const int index);
   datetime          HighTime(const int index);

   //--- Segment size by a specified index
   double            SegmentSize(const int index);

   //--- Sum of all segments
   double            SegmentsSum(void);
   //--- Sum of segments directed (1) upwards and (2) downwards
   double            SumSegmentsUp(void);
   double            SumSegmentsDown(void);

   //--- Direction of a specified segment
   int               SegmentDirection(const int index);
   //--- Return the start and end bar of a specified segment
   bool              SegmentBars(const int index,int &start_bar,int &stop_bar);
   //--- Return the start and end prices of a specified segment
   bool              SegmentPrices(const int index,double &start_price,double &stop_price);
   //--- Return the start and end time of a specified segment
   bool              SegmentTimes(const int index,datetime &start_time,datetime &stop_time);
   //--- Return the start and end time of a specified segment considering a lower timeframe
   bool              SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                  const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                  const int index,datetime &start_time,datetime &stop_time);

   //--- Percentage ratio of the segment sums to the total sum of all segments within a set
   double            PercentSumSegmentsUp(void);
   double            PercentSumSegmentsDown(void);
   //--- Difference between segment sums
   double            PercentSumSegmentsDifference(void);

   //--- Number of bars in a specified segment
   int               SegmentBars(const int index);
   //--- (1) Number of bars and (2) seconds in a set of segments
   int               SegmentsTotalBars(void);
   long              SegmentsTotalSeconds(void);

   //--- (1) Minimum and (2) maximum values in the set
   double            LowMinimum(void);
   double            HighMaximum(void);
   //--- Price range
   double            PriceRange(void);

   //--- Smallest minimum time
   datetime          SmallestMinimumTime(void);
   //--- Largest maximum time
   datetime          LargestMaximumTime(void);

   //--- Smallest segment in the set
   double            SmallestSegment(void);
   //--- Largest segment in the set
   double            LargestSegment(void);
   //--- Smallest number of segment bars in the set
   int               LeastNumberOfSegmentBars(void);
   //--- Largest number of segment bars in the set 
   int               MostNumberOfSegmentBars(void);

   //--- Deviation in percentage
   double            PercentDeviation(const int index);
   //---
public:
   //--- Line color
   void              LinesColor(const color clr) { m_lines_color=clr; }

   //--- (1) Display and (2) delete objects
   void              ShowSegments(const string suffix="");
   void              DeleteSegments(void);

   //--- Comment on a chart
   void              CommentZigZagData();
   void              CommentShortZigZagData();
   //---
private:
   //--- Copy source data to the passed arrays
   void              CopyData(const int handle,const int buffer_index,const string symbol,
                              const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                              double &zz_array[],datetime &time_array[]);
   //--- Return index of the (1) minimum and (2) maximum values from the passed array
   int               GetMinValueIndex(double &zz_lows[]);
   int               GetMaxValueIndex(double &zz_highs[]);

   //--- Create objects
   void              CreateSegment(const int segment_index,const string suffix="");
   //--- Segment size to the string
   string            SegmentSizeToString(const int index);
   //---
public:
   //--- Pattern 1
   int               Pattern1(const double outer_dev,const double inner_dev);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CZigZagModule::CZigZagModule(void) : m_copy_extremums(1),
                                     m_segments_total(1),
                                     m_lines_color(clrGray)
  {
   CopyExtremums(m_copy_extremums);
   ZeroZigZagData();
//--- Set the reverse indexation order (... 3 2 1 0)
   ::ArraySetAsSeries(m_zz_lows_temp,true);
   ::ArraySetAsSeries(m_zz_highs_temp,true);
   ::ArraySetAsSeries(m_zz_time_temp,true);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CZigZagModule::~CZigZagModule(void)
  {
  }
//+------------------------------------------------------------------+
//| Number of extremums for work                                     |
//+------------------------------------------------------------------+
void CZigZagModule::CopyExtremums(const int total)
  {
   if(total<1)
      return;
//---
   m_copy_extremums =total;
   m_segments_total =total*2-1;
//---
   ::ArrayResize(m_zz_low,total);
   ::ArrayResize(m_zz_high,total);
   ::ArrayResize(m_zz_low_bar,total);
   ::ArrayResize(m_zz_high_bar,total);
   ::ArrayResize(m_zz_low_time,total);
   ::ArrayResize(m_zz_high_time,total);
   ::ArrayResize(m_trend_lines,m_segments_total);
  }
//+------------------------------------------------------------------+
//| Reset ZigZag variables                                           |
//+------------------------------------------------------------------+
void CZigZagModule::ZeroZigZagData(void)
  {
   m_counter_lows  =0;
   m_counter_highs =0;
//---
   ::ArrayInitialize(m_zz_low,0);
   ::ArrayInitialize(m_zz_high,0);
   ::ArrayInitialize(m_zz_low_bar,0);
   ::ArrayInitialize(m_zz_high_bar,0);
   ::ArrayInitialize(m_zz_low_time,0);
   ::ArrayInitialize(m_zz_high_time,0);
  }
//+------------------------------------------------------------------+
//| Get ZZ data from passed arrays                                   |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const double &zz_h[],const double &zz_l[],const datetime &time[])
  {
   int h_total =::ArraySize(zz_h);
   int l_total =::ArraySize(zz_l);
   int total   =h_total+l_total;
//--- Reset ZZ variables
   ZeroZigZagData();
//--- Move along the copied ZZ values in a loop
   for(int i=0; i<total; i++)
     {
      //--- If the necessary number of ZZ highs and lows is already received, exit the loop
      if(m_counter_highs==m_copy_extremums && m_counter_lows==m_copy_extremums)
         break;
      //--- Manage moving beyond the array
      if(i>=h_total || i>=l_total)
         break;
      //--- Fill in the high value array till the necessary amount is copied
      if(zz_h[i]>0 && m_counter_highs<m_copy_extremums)
        {
         m_zz_high[m_counter_highs]      =zz_h[i];
         m_zz_high_bar[m_counter_highs]  =i;
         m_zz_high_time[m_counter_highs] =time[i];
         //--- Increase the counter of highs
         m_counter_highs++;
        }
      //--- Fill in the array of lows till the necessary amount is copied
      if(zz_l[i]>0 && m_counter_lows<m_copy_extremums)
        {
         m_zz_low[m_counter_lows]      =zz_l[i];
         m_zz_low_bar[m_counter_lows]  =i;
         m_zz_low_time[m_counter_lows] =time[i];
         //--- Increase the counter of lows
         m_counter_lows++;
        }
     }
//--- Determine the price direction
   m_direction=(m_zz_high_time[0]>m_zz_low_time[0])? 1 : -1;
  }
//+------------------------------------------------------------------+
//| Get ZZ data from passed data                                     |
//+------------------------------------------------------------------+
void CZigZagModule::GetZigZagData(const int handle,int buffer_num_highs,int buffer_num_lows,
                                  const string symbol,const ENUM_TIMEFRAMES period,
                                  const datetime start_time,const datetime stop_time)
  {
//--- Get source data
   int times_total =::CopyTime(symbol,period,start_time,stop_time,m_zz_time_temp);
   int highs_total =::CopyBuffer(handle,buffer_num_highs,start_time,stop_time,m_zz_highs_temp);
   int lows_total  =::CopyBuffer(handle,buffer_num_lows,start_time,stop_time,m_zz_lows_temp);
//--- Maximum number of extremums
   int max_items =(int)::fmax((double)highs_total,(double)lows_total);
//--- If not enough, try again
   if(times_total<max_items)
     {
      while(true)
        {
         ::Sleep(100);
         times_total=::CopyTime(symbol,period,start_time,stop_time,m_zz_time_temp);
         if(times_total>=max_items)
            break;
        }
     }
//--- Counters
   int lows_counter  =0;
   int highs_counter =0;
//--- Calculate highs
   int h_total=::ArraySize(m_zz_highs_temp);
   for(int i=0; i<h_total; i++)
     {
      if(m_zz_highs_temp[i]>0)
         highs_counter++;
     }
//--- Calculate lows
   int l_total=::ArraySize(m_zz_lows_temp);
   for(int i=0; i<l_total; i++)
     {
      if(m_zz_lows_temp[i]>0)
         lows_counter++;
     }
//--- Get the number of extremums
   int copy_extremums=(int)::fmin((double)highs_counter,(double)lows_counter);
   CopyExtremums(copy_extremums);
//--- Move along the copied ZZ values in a loop
   GetZigZagData(m_zz_highs_temp,m_zz_lows_temp,m_zz_time_temp);
  }
//+------------------------------------------------------------------+
//| Low value by a specified index                                   |
//+------------------------------------------------------------------+
double CZigZagModule::LowPrice(const int index)
  {
   if(index>=::ArraySize(m_zz_low))
      return(0.0);
//---
   return(m_zz_low[index]);
  }
//+------------------------------------------------------------------+
//| High value by a specified index                                  |
//+------------------------------------------------------------------+
double CZigZagModule::HighPrice(const int index)
  {
   if(index>=::ArraySize(m_zz_high))
      return(0.0);
//---
   return(m_zz_high[index]);
  }
//+------------------------------------------------------------------+
//| Low bar number by a specified index                              |
//+------------------------------------------------------------------+
int CZigZagModule::LowBar(const int index)
  {
   if(index>=::ArraySize(m_zz_low_bar))
      return(0);
//---
   return(m_zz_low_bar[index]);
  }
//+------------------------------------------------------------------+
//| High bar number by a specified index                             |
//+------------------------------------------------------------------+
int CZigZagModule::HighBar(const int index)
  {
   if(index>=::ArraySize(m_zz_high_bar))
      return(0);
//---
   return(m_zz_high_bar[index]);
  }
//+------------------------------------------------------------------+
//| Low bar time by a specified index                                |
//+------------------------------------------------------------------+
datetime CZigZagModule::LowTime(const int index)
  {
   if(index>=::ArraySize(m_zz_low_time))
      return(0);
//---
   return(m_zz_low_time[index]);
  }
//+------------------------------------------------------------------+
//| High bar time by a specified index                               |
//+------------------------------------------------------------------+
datetime CZigZagModule::HighTime(const int index)
  {
   if(index>=::ArraySize(m_zz_high_time))
      return(0);
//---
   return(m_zz_high_time[index]);
  }
//+------------------------------------------------------------------+
//| Return segment size by index                                     |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentSize(const int index)
  {
   if(index>=m_segments_total)
      return(-1);
//---
   double size=0;
//--- In case of an even number
   if(index%2==0)
     {
      int i=index/2;
      size=::fabs(m_zz_high[i]-m_zz_low[i]);
     }
//--- In case of an odd number
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
        }
      //---
      size=::fabs(m_zz_high[h]-m_zz_low[l]);
     }
//---
   return(size);
  }
//+------------------------------------------------------------------+
//| Sum of all segments                                              |
//+------------------------------------------------------------------+
double CZigZagModule::SegmentsSum(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_segments_total; i++)
      sum+=SegmentSize(i);
//---
   return(sum);
  }
//+------------------------------------------------------------------+
//| Return the size of all upward segments                           |
//+------------------------------------------------------------------+
double CZigZagModule::SumSegmentsUp(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_copy_extremums; i++)
     {
      if(Direction()>0)
         sum+=::fabs(m_zz_high[i]-m_zz_low[i]);
      else
        {
         if(i>0)
            sum+=::fabs(m_zz_high[i-1]-m_zz_low[i]);
        }
     }
//---
   return(sum);
  }
//+------------------------------------------------------------------+
//| Return the size of all downward segments                         |
//+------------------------------------------------------------------+
double CZigZagModule::SumSegmentsDown(void)
  {
   double sum=0.0;
//---
   for(int i=0; i<m_copy_extremums; i++)
     {
      if(Direction()<0)
         sum+=::fabs(m_zz_high[i]-m_zz_low[i]);
      else
        {
         if(i>0)
            sum+=::fabs(m_zz_high[i]-m_zz_low[i-1]);
        }
     }
//---
   return(sum);
  }
//+------------------------------------------------------------------+
//| Direction of a specified segment                                 |
//+------------------------------------------------------------------+
int CZigZagModule::SegmentDirection(const int index)
  {
   return((index%2==0)? Direction() : -Direction());
  }
//+------------------------------------------------------------------+
//| Return the start and end bar of a specified segment              |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentBars(const int index,int &start_bar,int &stop_bar)
  {
   if(index>=m_segments_total)
      return(false);
//--- In case of an even number
   if(index%2==0)
     {
      int i=index/2;
      //---
      start_bar =(Direction()>0)? m_zz_low_bar[i] : m_zz_high_bar[i];
      stop_bar  =(Direction()>0)? m_zz_high_bar[i] : m_zz_low_bar[i];
     }
//--- In case of an odd number
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
         //---
         start_bar =m_zz_high_bar[h];
         stop_bar  =m_zz_low_bar[l];
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
         //---
         start_bar =m_zz_low_bar[l];
         stop_bar  =m_zz_high_bar[h];
        }
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Return the start and end prices of a specified segment           |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentPrices(const int index,double &start_price,double &stop_price)
  {
   if(index>=m_segments_total)
      return(false);
//--- In case of an even number
   if(index%2==0)
     {
      int i=index/2;
      //---
      start_price =(Direction()>0)? m_zz_low[i] : m_zz_high[i];
      stop_price  =(Direction()>0)? m_zz_high[i] : m_zz_low[i];
     }
//--- In case of an odd number
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
         //---
         start_price =m_zz_high[h];
         stop_price  =m_zz_low[l];
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
         //---
         start_price =m_zz_low[l];
         stop_price  =m_zz_high[h];
        }
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Return the start and end time of a segment                       |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentTimes(const int index,datetime &start_time,datetime &stop_time)
  {
   if(index>=m_segments_total)
     {
      Print(__FUNCTION__," > index: ",index,"; m_segments_total: ",m_segments_total);
      return(false);
     }
//--- In case of an even number
   if(index%2==0)
     {
      int i=index/2;
      //---
      start_time =(Direction()>0)? m_zz_low_time[i] : m_zz_high_time[i];
      stop_time  =(Direction()>0)? m_zz_high_time[i] : m_zz_low_time[i];
     }
//--- In case of an odd number
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
         //---
         start_time =m_zz_high_time[h];
         stop_time  =m_zz_low_time[l];
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
         //---
         start_time =m_zz_low_time[l];
         stop_time  =m_zz_high_time[h];
        }
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Return the start and end time of a specified segment             |
//| considering a lower timeframe                                    |
//+------------------------------------------------------------------+
bool CZigZagModule::SegmentTimes(const int handle,const int highs_buffer_index,const int lows_buffer_index,
                                 const string symbol,const ENUM_TIMEFRAMES period,const ENUM_TIMEFRAMES in_period,
                                 const int index,datetime &start_time,datetime &stop_time)
  {
//--- Get time without considering the current timeframe
   datetime l_start_time =NULL;
   datetime l_stop_time  =NULL;
   if(!SegmentTimes(index,l_start_time,l_stop_time))
      return(false);
//---
   double   zz_lows[];
   double   zz_highs[];
   datetime zz_lows_time[];
   datetime zz_highs_time[];
   datetime start =NULL;
   datetime stop  =NULL;
   int      period_seconds=::PeriodSeconds(period);
//--- Get source data in case of an upward direction
   if(SegmentDirection(index)>0)
     {
      //--- Data on the higher timeframe's first bar
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
      //--- Data on the higher timeframe's last bar
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
     }
//--- Get source data in case of a downward direction
   else
     {
      //--- Data on the higher timeframe's first bar
      start =l_start_time;
      stop  =l_start_time+period_seconds;
      CopyData(handle,highs_buffer_index,symbol,in_period,start,stop,zz_highs,zz_highs_time);
      //--- Data on the higher timeframe's last bar
      start =l_stop_time;
      stop  =l_stop_time+period_seconds;
      CopyData(handle,lows_buffer_index,symbol,in_period,start,stop,zz_lows,zz_lows_time);
     }
//--- Search for a high (maximum value) index
   int max_index=GetMaxValueIndex(zz_highs);
//--- Search for a low (minimum value) index
   int min_index=GetMinValueIndex(zz_lows);
//--- 
   if(min_index>=::ArraySize(zz_lows_time) || max_index>=::ArraySize(zz_highs_time))
      return(false);
//--- Get segment start and end time
   start_time =(SegmentDirection(index)>0)? zz_lows_time[min_index] : zz_highs_time[max_index];
   stop_time  =(SegmentDirection(index)>0)? zz_highs_time[max_index] : zz_lows_time[min_index];
//--- Successful
   return(true);
  }
//+------------------------------------------------------------------+
//| Copy source data to the passed arrays                            |
//+------------------------------------------------------------------+
void CZigZagModule::CopyData(const int handle,const int buffer_index,const string symbol,
                             const ENUM_TIMEFRAMES period,datetime start_time,datetime stop_time,
                             double &zz_array[],datetime &time_array[])
  {
   ::CopyBuffer(handle,buffer_index,start_time,stop_time,zz_array);
   ::CopyTime(symbol,period,start_time,stop_time,time_array);
  }
//+------------------------------------------------------------------+
//| Return a high index from the passed array                        |
//+------------------------------------------------------------------+
int CZigZagModule::GetMaxValueIndex(double &zz_highs[])
  {
   int    max_index =0;
   double max_value =0;
   int total=::ArraySize(zz_highs);
   for(int i=0; i<total; i++)
     {
      if(zz_highs[i]>0)
        {
         if(zz_highs[i]>max_value)
           {
            max_index =i;
            max_value =zz_highs[i];
           }
        }
     }
//---
   return(max_index);
  }
//+------------------------------------------------------------------+
//| Return a low index from the passed array                         |
//+------------------------------------------------------------------+
int CZigZagModule::GetMinValueIndex(double &zz_lows[])
  {
   int    min_index =0;
   double min_value =INT_MAX;
   int total=::ArraySize(zz_lows);
   for(int i=0; i<total; i++)
     {
      if(zz_lows[i]>0)
        {
         if(zz_lows[i]<min_value)
           {
            min_index =i;
            min_value =zz_lows[i];
           }
        }
     }
//---
   return(min_index);
  }
//+------------------------------------------------------------------+
//| Return the percentage of the sum of all upward segments          |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsUp(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsDown()/sum*100);
  }
//+------------------------------------------------------------------+
//| Return the percentage of the sum of all downward segments        |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDown(void)
  {
   double sum=SegmentsSum();
   if(sum<=0)
      return(0);
//---
   return(SumSegmentsUp()/sum*100);
  }
//+------------------------------------------------------------------+
//| Return the difference of the sum of all segments in percentage   |
//+------------------------------------------------------------------+
double CZigZagModule::PercentSumSegmentsDifference(void)
  {
   return(::fabs(PercentSumSegmentsUp()-PercentSumSegmentsDown()));
  }
//+------------------------------------------------------------------+
//| Return segment duration in bars by index                         |
//+------------------------------------------------------------------+
int CZigZagModule::SegmentBars(const int index)
  {
   if(index>=m_copy_extremums*2-1)
      return(-1);
//---
   int bars=0;
//--- In case of an even number
   if(index%2==0)
     {
      int i=index/2;
      bars=int(::fabs(m_zz_high_bar[i]-m_zz_low_bar[i]));
     }
//--- In case of an odd number
   else
     {
      int l=0,h=0;
      //---
      if(Direction()>0)
        {
         h=(index-1)/2+1;
         l=(index-1)/2;
        }
      else
        {
         h=(index-1)/2;
         l=(index-1)/2+1;
        }
      //---
      bars=int(::fabs(m_zz_high_bar[h]-m_zz_low_bar[l]));
     }
//---
   return(bars);
  }
//+------------------------------------------------------------------+
//| Number of bars of all segments                                   |
//+------------------------------------------------------------------+
int CZigZagModule::SegmentsTotalBars(void)
  {
   int begin =0;
   int end   =0;
   int l     =m_copy_extremums-1;
//---
   begin =(m_zz_high_bar[l]>m_zz_low_bar[l])? m_zz_high_bar[l] : m_zz_low_bar[l];
   end   =(m_zz_high_bar[0]>m_zz_low_bar[0])? m_zz_low_bar[0] : m_zz_high_bar[0];
//---
   return(begin-end);
  }
//+------------------------------------------------------------------+
//| Number of seconds of all segments                                |
//+------------------------------------------------------------------+
long CZigZagModule::SegmentsTotalSeconds(void)
  {
   datetime begin =NULL;
   datetime end   =NULL;
   int l=m_copy_extremums-1;
//---
   begin =(m_zz_high_time[l]<m_zz_low_time[l])? m_zz_high_time[l] : m_zz_low_time[l];
   end   =(m_zz_high_time[0]<m_zz_low_time[0])? m_zz_low_time[0] : m_zz_high_time[0];
//---
   return(long(end-begin));
  }
//+------------------------------------------------------------------+
//| Minimum value in a set                                           |
//+------------------------------------------------------------------+
double CZigZagModule::LowMinimum(void)
  {
   return(m_zz_low[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| Maximum value in a set                                           |
//+------------------------------------------------------------------+
double CZigZagModule::HighMaximum(void)
  {
   return(m_zz_high[::ArrayMaximum(m_zz_high)]);
  }
//+------------------------------------------------------------------+
//| Price range                                                      |
//+------------------------------------------------------------------+
double CZigZagModule::PriceRange(void)
  {
   return(HighMaximum()-LowMinimum());
  }
//+------------------------------------------------------------------+
//| Smallest minimum time                                            |
//+------------------------------------------------------------------+
datetime CZigZagModule::SmallestMinimumTime(void)
  {
   return(m_zz_low_time[::ArrayMinimum(m_zz_low)]);
  }
//+------------------------------------------------------------------+
//| Largest maximum time                                             |
//+------------------------------------------------------------------+
datetime CZigZagModule::LargestMaximumTime(void)
  {
   return(m_zz_high_time[::ArrayMaximum(m_zz_high)]);
  }
//+------------------------------------------------------------------+
//| Smallest segment in a set                                        |
//+------------------------------------------------------------------+
double CZigZagModule::SmallestSegment(void)
  {
   double min_size=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         min_size=SegmentSize(0);
         continue;
        }
      //---
      double size=SegmentSize(i);
      min_size=(size<min_size)? size : min_size;
     }
//---
   return(min_size);
  }
//+------------------------------------------------------------------+
//| Largest segment in the set                                       |
//+------------------------------------------------------------------+
double CZigZagModule::LargestSegment(void)
  {
   double max_size=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         max_size=SegmentSize(0);
         continue;
        }
      //---
      double size=SegmentSize(i);
      max_size=(size>max_size)? size : max_size;
     }
//---
   return(max_size);
  }
//+------------------------------------------------------------------+
//| Smallest number of segment bars in the set                       |
//+------------------------------------------------------------------+
int CZigZagModule::LeastNumberOfSegmentBars(void)
  {
   int min_bars=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         min_bars=SegmentBars(0);
         continue;
        }
      //---
      int bars=SegmentBars(i);
      min_bars=(bars<min_bars)? bars : min_bars;
     }
//---
   return(min_bars);
  }
//+------------------------------------------------------------------+
//| Largest number of segment bars in the set                        |
//+------------------------------------------------------------------+
int CZigZagModule::MostNumberOfSegmentBars(void)
  {
   int max_bars=0;
   for(int i=0; i<m_segments_total; i++)
     {
      if(i==0)
        {
         max_bars=SegmentBars(0);
         continue;
        }
      //---
      int bars=SegmentBars(i);
      max_bars=(bars>max_bars)? bars : max_bars;
     }
//---
   return(max_bars);
  }
//+------------------------------------------------------------------+
//| Deviation in percentage                                          |
//+------------------------------------------------------------------+
double CZigZagModule::PercentDeviation(const int index)
  {
   return(SegmentSize(index)/SegmentSize(index+1)*100);
  }
//+------------------------------------------------------------------+
//| Show ZZ segments on a chart                                      |
//+------------------------------------------------------------------+
void CZigZagModule::ShowSegments(const string suffix="")
  {
   DeleteSegments();
   for(int i=0; i<m_segments_total; i++)
      CreateSegment(i,suffix);
  }
//+------------------------------------------------------------------+
//| Remove segments                                                  |
//+------------------------------------------------------------------+
void CZigZagModule::DeleteSegments(void)
  {
   for(int i=0; i<m_segments_total; i++)
     {
      string name="zz_"+string(::ChartID())+"_"+string(i);
      ::ObjectDelete(::ChartID(),name);
     }
  }
//+------------------------------------------------------------------+
//| Create a segment by index                                        |
//+------------------------------------------------------------------+
void CZigZagModule::CreateSegment(const int segment_index,const string suffix="")
  {
   if(segment_index>=m_segments_total)
      return;
//---
   double   hp=0.0,lp=0.0;
   datetime hd=NULL,ld=NULL;
//--- In case of an even number
   if(segment_index%2==0)
     {
      int i=segment_index/2;
      hp =m_zz_high[i];
      lp =m_zz_low[i];
      hd =m_zz_high_time[i];
      ld =m_zz_low_time[i];
     }
//--- In case of an odd number
   else
     {
      int h=0,l=0;
      if(Direction()>0)
        {
         h=(segment_index-1)/2+1;
         l=(segment_index-1)/2;
        }
      else
        {
         h=(segment_index-1)/2;
         l=(segment_index-1)/2+1;
        }
      //---
      hp =m_zz_high[h];
      lp =m_zz_low[l];
      hd =m_zz_high_time[h];
      ld =m_zz_low_time[l];
     }
//--- Set objects
   long id=::ChartID();
   string name="zz_"+string(id)+"_"+string(segment_index)+suffix;
//--- If an object is already present, simply update its location 
   if(::ObjectFind(id,name)>=0)
     {
      ::ObjectSetDouble(id,name,OBJPROP_PRICE,0,hp);
      ::ObjectSetDouble(id,name,OBJPROP_PRICE,1,lp);
      ::ObjectSetInteger(id,name,OBJPROP_TIME,0,hd);
      ::ObjectSetInteger(id,name,OBJPROP_TIME,1,ld);
     }
//--- Create a new object
   else
     {
      m_trend_lines[segment_index].Create(id,name,0,hd,hp,ld,lp);
      m_trend_lines[segment_index].Background(false);
      m_trend_lines[segment_index].Width(2);
      m_trend_lines[segment_index].Color(m_lines_color);
      m_trend_lines[segment_index].Style(STYLE_SOLID);
      m_trend_lines[segment_index].Description("zz: "+string(segment_index));
     }
  }
//+------------------------------------------------------------------+
//| Display ZigZag data in a chart comment                           |
//+------------------------------------------------------------------+
void CZigZagModule::CommentZigZagData(void)
  {
   string comment="Current direction : "+string(m_direction)+"\n"+
                  "Copy extremums: "+string(m_copy_extremums)+"\n"+
                  "SegmentSize(0): "+::DoubleToString(SegmentSize(0),_Digits)+
                  "\n---\n"+
                  "HighPrice(0): "+::DoubleToString(HighPrice(0),_Digits)+"\n"+
                  "LowPrice(0): "+::DoubleToString(LowPrice(0),_Digits)+
                  "\n---\n"+
                  "HighBar(0): "+string(HighBar(0))+"\n"+
                  "LowBar(0): "+string(LowBar(0))+
                  "\n---\n"+
                  "HighTime(0): "+::TimeToString(HighTime(0),TIME_DATE|TIME_MINUTES)+"\n"+
                  "LowTime(0): "+::TimeToString(LowTime(0),TIME_DATE|TIME_MINUTES)+
                  "\n---\n"+
                  "SegmentsTotalBars(): "+string(SegmentsTotalBars())+"\n"+
                  "SegmentsTotalSeconds(): "+string(SegmentsTotalSeconds())+"\n"+
                  "SegmentsTotalMinutes(): "+string(SegmentsTotalSeconds()/60)+"\n"+
                  "SegmentsTotalHours(): "+string(SegmentsTotalSeconds()/60/60)+"\n"+
                  "SegmentsTotalDays(): "+string(SegmentsTotalSeconds()/60/60/24)+
                  "\n---\n"+
                  "LowMinimum(): "+::DoubleToString(LowMinimum(),_Digits)+"\n"+
                  "HighMaximum(): "+::DoubleToString(HighMaximum(),_Digits)+"\n"+
                  "PriceRange(): "+::DoubleToString(PriceRange(),_Digits)+
                  "\n---\n"+
                  "SegmentsSum(): "+::DoubleToString(SegmentsSum()/_Point,0)+"\n"+
                  "SegmentsSumUp(): "+::DoubleToString(SumSegmentsUp()/_Point,0)+"\n"+
                  "SegmentsSumDown(): "+::DoubleToString(SumSegmentsDown()/_Point,0)+
                  "\n---\n"+
                  "PercentSumUp(): "+::DoubleToString(SumSegmentsUp()/SegmentsSum()*100,2)+"\n"+
                  "PercentSumDown(): "+::DoubleToString(SumSegmentsDown()/SegmentsSum()*100,2)+"\n"+
                  "PercentDifference(): "+::DoubleToString(PercentSumSegmentsDifference(),2);
//---
   if(m_copy_extremums>1)
     {
      comment+="\n---\n";
      for(int i=0; i<m_segments_total-1; i++)
         comment=comment+"segment_size["+string(i)+"]: "+SegmentSizeToString(i)+"; ("+string(SegmentBars(i))+"); "+::DoubleToString(PercentDeviation(i),2)+" %\n";
      //---
      comment+="...";
     }
//---
   ::Comment(comment);
  }
//+------------------------------------------------------------------+
//| Display ZigZag data in a chart comment                           |
//+------------------------------------------------------------------+
void CZigZagModule::CommentShortZigZagData(void)
  {
   string comment="Current direction : "+string(m_direction)+"\n"+
                  "Copy extremums: "+string(m_copy_extremums)+
                  "\n---\n"+
                  "SegmentsTotalBars(): "+string(SegmentsTotalBars())+"\n"+
                  "SegmentsTotalSeconds(): "+string(SegmentsTotalSeconds())+"\n"+
                  "SegmentsTotalMinutes(): "+string(SegmentsTotalSeconds()/60)+"\n"+
                  "SegmentsTotalHours(): "+string(SegmentsTotalSeconds()/60/60)+"\n"+
                  "SegmentsTotalDays(): "+string(SegmentsTotalSeconds()/60/60/24)+
                  "\n---\n"+
                  "PercentSumUp(): "+::DoubleToString(SumSegmentsUp()/SegmentsSum()*100,2)+"\n"+
                  "PercentSumDown(): "+::DoubleToString(SumSegmentsDown()/SegmentsSum()*100,2)+"\n"+
                  "PercentDifference(): "+::DoubleToString(PercentSumSegmentsDifference(),2)+
                  "\n---\n"+
                  "SmallestSegment(): "+::DoubleToString(SmallestSegment()/_Point,0)+"\n"+
                  "LargestSegment(): "+::DoubleToString(LargestSegment()/_Point,0)+"\n"+
                  "LeastNumberOfSegmentBars(): "+string(LeastNumberOfSegmentBars())+"\n"+
                  "MostNumberOfSegmentBars(): "+string(MostNumberOfSegmentBars());
//---
   ::Comment(comment);
  }
//+------------------------------------------------------------------+
//| Segment size to the string                                       |
//+------------------------------------------------------------------+
string CZigZagModule::SegmentSizeToString(const int index)
  {
   return((string)(int)(SegmentSize(index)/_Point));
  }
//+------------------------------------------------------------------+
//| Pattern 1                                                        |
//+------------------------------------------------------------------+
int CZigZagModule::Pattern1(const double outer_dev,const double inner_dev)
  {
   if(m_segments_total<4)
     {
      ::Print(__FUNCTION__," > Insufficient segments for defining a pattern! A minimum of 4 (5) segments (3 high/low extremums) required.");
      return(0);
     }
//---
   if(SegmentSize(1)<=0 || SegmentSize(2)<=0 || SegmentSize(3)<=0)
      return(0);
//---
   bool condition1=::fabs(SegmentSize(0)/SegmentSize(1)*100-100) > outer_dev;
   bool condition2=::fabs(SegmentSize(2)/SegmentSize(3)*100-100) > outer_dev;
   bool condition3=::fabs(SegmentSize(1)/SegmentSize(2)*100-100) < inner_dev;
//---
   if(m_direction<0)
     {
      bool condition4=m_zz_low[0]<m_zz_low[1] && m_zz_low[2]<m_zz_low[1];
      //---
      if(condition1 && condition2 && condition3 && condition4)
        {
         ::Print(__FUNCTION__,"(): >>> BUY SIGNAL");
         return(1);
        }
     }
   else if(m_direction>0)
     {
      bool condition4=m_zz_high[0]>m_zz_high[1] && m_zz_high[2]>m_zz_high[1];
      //---     
      if(condition1 && condition2 && condition3 && condition4)
        {
         ::Print(__FUNCTION__,"(): >>> SELL SIGNAL");
         return(-1);
        }
     }
//---
   return(0);
  }
//+------------------------------------------------------------------+
