//+------------------------------------------------------------------+
//|                                       Time_Resolution_Arrows.mq4 |
//|                                                      nicholishen |
//|                                   www.reddit.com/u/nicholishenFX |
//+------------------------------------------------------------------+
#property copyright "nicholishen"
#property link      "www.reddit.com/u/nicholishenFX"
#property version   "1.00"
#property strict
#property indicator_chart_window
#include <Arrays\ArrayObj.mqh>
#include <ChartObjects\ChartObjectsArrows.mqh>
//--- input parameters
input ENUM_TIMEFRAMES high_tf    = PERIOD_H1;
input int             Depth      = 12;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum SwingType{SWING_HIGH,SWING_LOW};
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class ZigZag : public CObject
{
protected:
   CChartObjectArrow *m_arrow;
   
public:
   double         _price;
   datetime       _time_orig;
   datetime       _time;
   SwingType      _swing_type;
                  ZigZag(){}
                  ZigZag(double price,datetime time,SwingType swing_type)
                  :_price(price),_time_orig(time),_swing_type(swing_type){}
                 ~ZigZag(){ delete m_arrow;}
   int            Compare(const CObject *node,const int mode=0)const override;
   ZigZag*        Draw();
protected:
   void           FixTimeResolution(const datetime time,double &price,SwingType mode);
   int            BarCount(const ENUM_TIMEFRAMES period);
};
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class ArrowVector: public CArrayObj
{
public:
   ZigZag* operator[](const int i)const{return (ZigZag*)At(i);}
   ArrowVector* Add(double price,datetime time,SwingType swing_type)
   {
      ZigZag *zig = new ZigZag(price,time,swing_type);
      zig.Draw();
      CArrayObj::Add(zig);
      return &this; 
   }
};
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
ArrowVector arrows;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   
//---
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason)
{
   arrows.Clear();
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   int total_rates = CopyRates(Symbol(),high_tf,0,Bars(Symbol(),high_tf),rates);
   
   bool refresh = true;
   int total = arrows.Total();
   if(total > 0)
   {
      refresh = false;
      arrows.Sort();  
      for(int i=0;i<total_rates;i++)
      {
         double zz = ZigInd(i);
         if(zz != EMPTY_VALUE && zz != 0.0 && zz != NULL)
         {
            if(arrows[0]._price != zz || arrows[0]._time_orig != rates[i].time)
            {
               refresh = true;
            }
            break;
         }
      }
   }
   if(refresh)
   {
      arrows.Clear();
      for(int i=0;i<total_rates;i++)
      {
         double zz = ZigInd(i);
         if(zz != EMPTY_VALUE && zz != 0.0 && zz != NULL)
         {
            SwingType swing;
            if(rates[i].close < zz)
               swing = SWING_HIGH;
            else
               swing = SWING_LOW;
               
            arrows.Add(zz,rates[i].time,swing);
         }
      }
   }
   return(rates_total);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double ZigInd(int i)
{
   return iCustom(Symbol(),high_tf,"ZigZag",Depth,5,3,0,i);
}
//+------------------------------------------------------------------+
int ZigZag::Compare(const CObject *node,const int mode=0)const override
{
   ZigZag *that = (ZigZag*)node;
   if(this._time > that._time)
      return -1;
   if(this._time < that._time)
      return 1;
   return 0;
}
//+------------------------------------------------------------------+
ZigZag* ZigZag::Draw()
{
   FixTimeResolution(_time_orig,_price,_swing_type);
   string name = EnumToString(_swing_type)+string(_time)+"_"+string(_price)+"_"+string(Period());
   if(m_arrow != NULL)
      delete m_arrow;
   if(_swing_type == SWING_HIGH)
   {
      m_arrow = new CChartObjectArrow();
      m_arrow.Create(0,name,0,_time,_price,char(234));
      m_arrow.Anchor(ANCHOR_BOTTOM);
   }
   else if(_swing_type == SWING_LOW)
   {
      m_arrow = new CChartObjectArrow();
      m_arrow.Create(0,name,0,_time,_price,char(233));
      m_arrow.Anchor(ANCHOR_TOP);
      m_arrow.Color(clrLimeGreen);
   }
   return &this;
}
//+------------------------------------------------------------------+
void ZigZag::FixTimeResolution(const datetime time,double &price,SwingType mode)
{
   _time = time;
   int digits = (int)SymbolInfoInteger(::Symbol(),SYMBOL_DIGITS);
   ENUM_TIMEFRAMES tfs[]={PERIOD_M1,PERIOD_M5,PERIOD_M15,
                          PERIOD_M30,PERIOD_H1,PERIOD_H4,
                          PERIOD_D1,PERIOD_W1,PERIOD_MN1,};
   int nTimeFrames = ::ArraySize(tfs);    // sets the tfs array to the min thru max timeframe for resolution steps
   int iStart = 0;
   for(int i=0;i<nTimeFrames;i++)
   {
      if(high_tf == tfs[i])
      {
         iStart = i -1;
         break;
      }
   }
   
   for(int tf = iStart; tf>=0;tf--)                    // for each timeframe in array from lowest to highest resolution
   {
      int startBar     = 0;
      int barIteration = 0;
      ENUM_TIMEFRAMES timeFrameDebug = tfs[tf];
      datetime        swingTimeDebug = _time;
      startBar = ::iBarShift(::Symbol(),tfs[tf],_time);
 
      if(startBar > -1)
      {
         int bCnt = BarCount(tfs[tf]);
         if(mode == SWING_HIGH)
         { 
            barIteration=0;
            for(int j=startBar; j >= startBar-bCnt; j--)
            {
               double ihigh = ::NormalizeDouble(::iHigh(::Symbol(),tfs[tf],j),digits);
               double swing = price;
               if( ihigh == swing )
               {
                  startBar = startBar - barIteration;
                  datetime newSwingTime = ::iTime(::Symbol(),tfs[tf],j);
                  _time = newSwingTime;
                  timeFrameDebug = tfs[tf];
                 
                  break;
               }
               barIteration++;
            }
         }
         else if(mode==SWING_LOW)
         { 
            barIteration = 0;
            for(int j=startBar; j >= startBar-bCnt; j--)
            { 
               double ilow = ::NormalizeDouble(::iLow(::Symbol(),tfs[tf],j),digits);
               double swing = price;
               if(ilow == swing )
               {
                  startBar = startBar - barIteration;
                  datetime newSwingTime = ::iTime(::Symbol(),tfs[tf],j);
                  _time = newSwingTime;
                  timeFrameDebug = tfs[tf];
                  break;
               }
               barIteration++;
            }
         }
      }
   }
}
//+------------------------------------------------------------------+
int ZigZag::BarCount(const ENUM_TIMEFRAMES period)
{
   switch(period)
   {
      case PERIOD_M1:
         return 5;
      case PERIOD_M5:
         return 3;
      case PERIOD_M15:
         return 2;
      case PERIOD_M30:
         return 2;
      case PERIOD_H1:
         return 4;
      case PERIOD_H4:
         return 6;
      case PERIOD_D1:
         return 6;
      case PERIOD_W1:
         return 4;
      default:
         return 1;
   }
   return -1;
}