#include <Trade/Trade.mqh>
#include <Trade/OrderInfo.mqh>

enum ZONE_CLOSING_TYPE {
   NO_CLOSING = 0,
   CLOSING_OPEN_CLOSE = 1,
   CLOSING_HIGH_LOW = 2
};

class Zone{

   public: 
      double   High;
      double   Low;
         
      int      BarStart;
      int      BarStop;
   
      datetime TimeStart;
      datetime TimeStop;
      
      bool     isHighClosed;
      bool     isLowClosed;
      bool     isClosed;
   
      bool     isSupplyZone;
      bool     isDemandZone;
      
      double   Volatility;
      
      string   Name;
      
      color    Color;
      
      Zone(){};
};

class ZonesManager{

   private: 
      string            inpSymbol;
      ENUM_TIMEFRAMES   inpTimeFrame;
      
      ZONE_CLOSING_TYPE inpZoneClosingType;
      
      int               inpATRPeriod;
      
      int               inpMinimumBarsToCloseZone;
      
      double            inpMaximumZoneBarHighFactor;
      double            inpMinimumMomentumShiftFactor ;
      double            inpBodyToHighFactor;
      
      color             inpDemandZonesColor;
      color             inpSupplyZonesColor;
      bool              inpFillZone;
      bool              inpShowZones;
      
      string            inpObjectPrefix;
      
      int               inpBarsHistoryStart;
      int               inpBarsHistoryStop;

      int               ATRHandle;

      Zone              Zones[];
      
      void              FindZones(int paBarShift);
      void              CloseZones();
      void              ShowZones();
      double            GetATR(int paBarShift);
      bool              NewBar();
      
   public:
      ZonesManager(){};
      
      int      Init(string inpSymbol, ENUM_TIMEFRAMES paTimeframe, bool paShowZones,
                     int paATRPeriod, double paMaximumBarHighFactor, double paMinimumMomentumShiftFactor, double paBodyToHighFactor,
                     ZONE_CLOSING_TYPE paClosingType, int paMinimumBarsAfterCheckClosingZone,
                     color paDemandZonesColor, color paSupplyZonesColor, bool paFillZone, string paObjectPrefix,                             
                     int paBarsHistoryStart, int paBarsHistoryStop);
      
      void     Tick();
      void     Deinit(const int reason);

};

class ZonesStrategy{

   private:
      
      ZonesManager      CurrentTFZone;
      ZonesManager      HigherTFZone;      

      CTrade            Trade;
      CPositionInfo     Position;
      CDealInfo         Deal;
      COrderInfo        Order;

      int               inpMagic;
            
   public:
     
      ZonesStrategy(){};
      

      int   OnInitEvent(string inpSymbol, ENUM_TIMEFRAMES paCurrentZone_Timeframe, bool paCurrentZone_ShowZones,
                        int paCurrentZone_ATRPeriod, double paCurrentZone_MaximumBarHighFactor, double paCurrentZone_MinimumMomentumShiftFactor, double paCurrentZone_BodyToHighFactor,
                        ZONE_CLOSING_TYPE paCurrentZone_ClosingType, int paCurrentZone_MinimumBarsAfterCheckClosingZone,
                        color paCurrentZone_DemandZonesColor, color paCurrentZone_SupplyZonesColor, bool paCurrentZone_FillZone, string paCurrentZone_ObjectPrefix,   

                        string inpSymbol, ENUM_TIMEFRAMES paHigherZone_Timeframe, bool paHigherZone_ShowZones,
                        int paHigherZone_ATRPeriod, double paHigherZone_MaximumBarHighFactor, double paHigherZone_MinimumMomentumShiftFactor, double paHigherZone_BodyToHighFactor,
                        ZONE_CLOSING_TYPE paHigherZone_ClosingType, int paHigherZone_MinimumBarsAfterCheckClosingZone,
                        color paHigherZone_DemandZonesColor, color paHigherZone_SupplyZonesColor, bool paHigherZone_FillZone, string paHigherZone_ObjectPrefix,                             
                        
                        int paMagic, int paBarsHistoryStart, int paBarsHistoryStop);
                          
      void  OnTickEvent();
      void  OnDeinitEvent(const int reason);
   
};


//
//----------------------------------------------------------------------------------------------------------------------------------------
//

int ZonesStrategy :: OnInitEvent(string paCurrentZone_Symbol, ENUM_TIMEFRAMES paCurrentZone_Timeframe, bool paCurrentZone_ShowZones,
                                 int paCurrentZone_ATRPeriod, double paCurrentZone_MaximumBarHighFactor, double paCurrentZone_MinimumMomentumShiftFactor, double paCurrentZone_BodyToHighFactor,
                                 ZONE_CLOSING_TYPE paCurrentZone_ClosingType, int paCurrentZone_MinimumBarsAfterCheckClosingZone,
                                 color paCurrentZone_DemandZonesColor, color paCurrentZone_SupplyZonesColor, bool paCurrentZone_FillZone, string paCurrentZone_ObjectPrefix,   
         
                                 string paHigherZone_Symbol, ENUM_TIMEFRAMES paHigherZone_Timeframe, bool paHigherZone_ShowZones,
                                 int paHigherZone_ATRPeriod, double paHigherZone_MaximumBarHighFactor, double paHigherZone_MinimumMomentumShiftFactor, double paHigherZone_BodyToHighFactor,
                                 ZONE_CLOSING_TYPE paHigherZone_ClosingType, int paHigherZone_MinimumBarsAfterCheckClosingZone,
                                 color paHigherZone_DemandZonesColor, color paHigherZone_SupplyZonesColor, bool paHigherZone_FillZone, string paHigherZone_ObjectPrefix,                             
                                 
                                 int paMagic, int paBarsHistoryStart, int paBarsHistoryStop){

   
   inpMagic = paMagic;
   
   Trade.SetExpertMagicNumber(inpMagic);
      
   CurrentTFZone.Init(paCurrentZone_Symbol, paCurrentZone_Timeframe, paCurrentZone_ShowZones,
                     paCurrentZone_ATRPeriod, paCurrentZone_MaximumBarHighFactor, paCurrentZone_MinimumMomentumShiftFactor, paCurrentZone_BodyToHighFactor,
                     paCurrentZone_ClosingType, paCurrentZone_MinimumBarsAfterCheckClosingZone,
                     paCurrentZone_DemandZonesColor, paCurrentZone_SupplyZonesColor, paCurrentZone_FillZone, paCurrentZone_ObjectPrefix,
                     paBarsHistoryStart, paBarsHistoryStop);
                     
   HigherTFZone.Init(paHigherZone_Symbol, paHigherZone_Timeframe, paHigherZone_ShowZones,
                     paHigherZone_ATRPeriod, paHigherZone_MaximumBarHighFactor, paHigherZone_MinimumMomentumShiftFactor, paHigherZone_BodyToHighFactor,
                     paHigherZone_ClosingType, paHigherZone_MinimumBarsAfterCheckClosingZone,
                     paHigherZone_DemandZonesColor, paHigherZone_SupplyZonesColor, paHigherZone_FillZone, paHigherZone_ObjectPrefix,                             
                     paBarsHistoryStart, paBarsHistoryStop);
         
   return(INIT_SUCCEEDED);

}

void ZonesStrategy::OnTickEvent(){ 
   
   CurrentTFZone.Tick();
   
   HigherTFZone.Tick();
   
   return;
}

void ZonesStrategy :: OnDeinitEvent(const int reason){

   CurrentTFZone.Deinit(reason);
   
   HigherTFZone.Deinit(reason);
   
}

//
//----------------------------------------------------------------------------------------------------------------------------------------
//

bool ZonesManager :: NewBar(){

   static datetime previous_time = 0;
   datetime current_time = iTime(inpSymbol, inpTimeFrame, 0);
   if(previous_time != current_time){
      previous_time = current_time;
      return(true);
   }
   return(false);
}

void ZonesManager :: FindZones(int paBarShift){

   int ZonesTotal = ArraySize(Zones);
   int LastZoneIndex = ZonesTotal - 1;
   
   if(ZonesTotal == 0) LastZoneIndex = 0;

   int ZoneType = 0;

   int StartZoneBar = paBarShift + 3;
   double Volatility = GetATR(StartZoneBar);
   
   if(iClose(inpSymbol, inpTimeFrame, StartZoneBar - 1) > iOpen(inpSymbol, inpTimeFrame, StartZoneBar - 1))
      if(iClose(inpSymbol, inpTimeFrame, StartZoneBar - 2) > iOpen(inpSymbol, inpTimeFrame, StartZoneBar - 2))
         if(iClose(inpSymbol, inpTimeFrame, StartZoneBar - 3) > iOpen(inpSymbol, inpTimeFrame, StartZoneBar - 3)){

            double ZoneBarHigh = iHigh(inpSymbol, inpTimeFrame, StartZoneBar) - iLow(inpSymbol, inpTimeFrame, StartZoneBar);
            double MomentumShift = iHigh(inpSymbol, inpTimeFrame, StartZoneBar - 3) - iLow(inpSymbol, inpTimeFrame, StartZoneBar - 1);
            
            if(MomentumShift >= Volatility * inpMinimumMomentumShiftFactor)
               if(ZoneBarHigh <= Volatility * inpMaximumZoneBarHighFactor)
               ZoneType = 1;
         }

   if(iClose(inpSymbol, inpTimeFrame, StartZoneBar - 1) < iOpen(inpSymbol, inpTimeFrame, StartZoneBar - 1))
      if(iClose(inpSymbol, inpTimeFrame, StartZoneBar - 2) < iOpen(inpSymbol, inpTimeFrame, StartZoneBar - 2))
         if(iClose(inpSymbol, inpTimeFrame, StartZoneBar - 3) < iOpen(inpSymbol, inpTimeFrame, StartZoneBar - 3)){

            double ZoneBarHigh = iHigh(inpSymbol, inpTimeFrame, StartZoneBar) - iLow(inpSymbol, inpTimeFrame, StartZoneBar);
            double MomentumShift = iHigh(inpSymbol, inpTimeFrame, StartZoneBar - 1) - iLow(inpSymbol, inpTimeFrame, StartZoneBar - 3);
            
            if(MomentumShift >= Volatility * inpMinimumMomentumShiftFactor)
               if(ZoneBarHigh <= Volatility * inpMaximumZoneBarHighFactor)
               ZoneType = -1;
         }

   if(ZoneType == 1){
      
      ArrayResize(Zones, ZonesTotal + 1);

      Zones[LastZoneIndex].isDemandZone = true; 
      
      Zones[LastZoneIndex].High = iHigh(inpSymbol, inpTimeFrame, StartZoneBar);     
      Zones[LastZoneIndex].Low = iLow(inpSymbol, inpTimeFrame, StartZoneBar);      
      
      Zones[LastZoneIndex].TimeStart = iTime(inpSymbol, inpTimeFrame, StartZoneBar);
      Zones[LastZoneIndex].BarStart = StartZoneBar;
      
      Zones[LastZoneIndex].TimeStop = TimeCurrent();
      Zones[LastZoneIndex].BarStop = INT_MIN;
            
      Zones[LastZoneIndex].Name = inpObjectPrefix + " (" + IntegerToString(LastZoneIndex + 1) + ") ";
      
      Zones[LastZoneIndex].isHighClosed = false;
      Zones[LastZoneIndex].isLowClosed = false;
      Zones[LastZoneIndex].isClosed = false;
            
      Zones[LastZoneIndex].Color = inpDemandZonesColor;

      Zones[LastZoneIndex].Volatility = Volatility;
   }

   if(ZoneType == -1){
      
      ArrayResize(Zones, ZonesTotal + 1);

      Zones[LastZoneIndex].isSupplyZone = true; 

      Zones[LastZoneIndex].High = iHigh(inpSymbol, inpTimeFrame, StartZoneBar);     
      Zones[LastZoneIndex].Low = iLow(inpSymbol, inpTimeFrame, StartZoneBar);      
      
      Zones[LastZoneIndex].TimeStart = iTime(inpSymbol, inpTimeFrame, StartZoneBar);
      Zones[LastZoneIndex].BarStart = paBarShift + 1;
      
      Zones[LastZoneIndex].TimeStop = TimeCurrent();
      Zones[LastZoneIndex].BarStop = INT_MIN;
            
      Zones[LastZoneIndex].Name = inpObjectPrefix + " (" + IntegerToString(LastZoneIndex + 1) + ") ";
      
      Zones[LastZoneIndex].isHighClosed = false;
      Zones[LastZoneIndex].isLowClosed = false;
      Zones[LastZoneIndex].isClosed = false;

      Zones[LastZoneIndex].Color = inpSupplyZonesColor;
            
      Zones[LastZoneIndex].Volatility = Volatility;
   }
}

void ZonesManager :: CloseZones(){

   int ZonesTotal = ArraySize(Zones);

   for(int i = 0; i < ZonesTotal; i++){

      if(!Zones[i].isClosed){ 

         for(int j = (Zones[i].BarStart - inpMinimumBarsToCloseZone - 1); j > 0; j--){

            double PriceHigh = 0.0;
            double PriceLow = 0.0; 
            
            if(inpZoneClosingType == CLOSING_OPEN_CLOSE){
               if(iClose(inpSymbol, inpTimeFrame, j) >= iOpen(inpSymbol, inpTimeFrame, j)){ 
                  PriceHigh = iClose(inpSymbol, inpTimeFrame, j);
                  PriceLow = iOpen(inpSymbol, inpTimeFrame, j);
               }else{ 
                  PriceHigh = iOpen(inpSymbol, inpTimeFrame, j);
                  PriceLow = iClose(inpSymbol, inpTimeFrame, j);
               }
            }
            
            if(inpZoneClosingType == CLOSING_HIGH_LOW){
               PriceHigh = iHigh(inpSymbol, inpTimeFrame, j);
               PriceLow = iLow(inpSymbol, inpTimeFrame, j);
            }
            
            if(!Zones[i].isHighClosed && Zones[i].High <= PriceHigh && Zones[i].High >= PriceLow){

               Zones[i].isHighClosed = true;
            }
            
            if(!Zones[i].isLowClosed && Zones[i].Low <= PriceHigh && Zones[i].Low >= PriceLow){

               Zones[i].isLowClosed = true;
            }

            if(!Zones[i].isClosed && Zones[i].isLowClosed && Zones[i].isHighClosed){

               Zones[i].isClosed = true;
               Zones[i].BarStop = j;            
               Zones[i].TimeStop = iTime(inpSymbol, inpTimeFrame, j);  
            }             
         }    
      }
   } 
}

void ZonesManager :: ShowZones(){

   int ZonesTotal = ArraySize(Zones);

   for(int i = 0; i < ZonesTotal; i++){

      string ZoneName = Zones[i].Name + " | Zone";
      
      if(ObjectFind(0, ZoneName) < 0){
         ObjectCreate(0, ZoneName, OBJ_RECTANGLE, 0, Zones[i].TimeStart, Zones[i].High, Zones[i].TimeStop, Zones[i].Low);  
         ObjectSetInteger(0,ZoneName ,OBJPROP_FILL, inpFillZone);           
         ObjectSetInteger(0,ZoneName ,OBJPROP_WIDTH, 1);           
         ObjectSetInteger(0,ZoneName ,OBJPROP_STYLE, STYLE_SOLID);           
         ObjectSetInteger(0,ZoneName ,OBJPROP_COLOR,Zones[i].Color);
      }else{
         ObjectSetInteger(0,ZoneName,OBJPROP_TIME, 0, Zones[i].TimeStart);        
         ObjectSetDouble(0,ZoneName,OBJPROP_PRICE, 0, Zones[i].High);                   
         ObjectSetInteger(0,ZoneName,OBJPROP_TIME, 1, Zones[i].TimeStop);        
         ObjectSetDouble(0,ZoneName,OBJPROP_PRICE, 1, Zones[i].Low);        
      }
   }   
   ChartRedraw(0);
}

double ZonesManager :: GetATR(int paBarShift){
      
   double ATRArray[];
   CopyBuffer(ATRHandle, 0, paBarShift, 1, ATRArray);
   return ATRArray[0];
}

int ZonesManager :: Init(string paSymbol, ENUM_TIMEFRAMES paTimeframe, bool paShowZones,
                     int paATRPeriod, double paMaximumBarHighFactor, double paMinimumMomentumShiftFactor, double paBodyToHighFactor,
                     ZONE_CLOSING_TYPE paClosingType, int paMinimumBarsAfterCheckClosingZone,
                     color paDemandZonesColor, color paSupplyZonesColor, bool paFillZone, string paObjectPrefix,                             
                     int paBarsHistoryStart, int paBarsHistoryStop){
                                 

   inpSymbol = paSymbol;
   inpTimeFrame = paTimeframe;
         
   inpZoneClosingType = paClosingType;
         
   inpATRPeriod = paATRPeriod;
         
   inpMinimumBarsToCloseZone = paMinimumBarsAfterCheckClosingZone;
         
   inpMaximumZoneBarHighFactor = paMaximumBarHighFactor;
   inpMinimumMomentumShiftFactor = paMinimumMomentumShiftFactor;
   inpBodyToHighFactor = paBodyToHighFactor;
         
   inpDemandZonesColor = paDemandZonesColor;
   inpSupplyZonesColor = paSupplyZonesColor;
   inpFillZone = paFillZone; 
   inpShowZones = paShowZones;
         
   inpObjectPrefix = paObjectPrefix;
         
   inpBarsHistoryStart = paBarsHistoryStart;
   inpBarsHistoryStop = paBarsHistoryStop;
   
   ATRHandle = iATR(inpSymbol, inpTimeFrame, paATRPeriod);

   int HistoryBarsStart = paBarsHistoryStart;
   
   if(HistoryBarsStart > iBars(inpSymbol, inpTimeFrame)) HistoryBarsStart = iBars(inpSymbol, inpTimeFrame) - 1;
   
   for(int i = HistoryBarsStart; i >= paBarsHistoryStop; i--)
      FindZones(i);
   
   if(inpZoneClosingType != NO_CLOSING) CloseZones();
   
   if(inpShowZones) ShowZones();

   return (INIT_SUCCEEDED);
}

void ZonesManager :: Tick(){


   if(!NewBar()) return;

   FindZones(1);
   
   if(inpZoneClosingType != NO_CLOSING) CloseZones();
   
   if(inpShowZones) ShowZones();

}

void ZonesManager :: Deinit(const int reason){

   ObjectsDeleteAll(0, inpObjectPrefix); 
   ArrayFree(Zones);
}
