#property copyright   "KK"
#property link        ""
#property version     "1.20"
#property description "FVG"

#property indicator_chart_window
#property indicator_plots 3
#property indicator_buffers 3

// types
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_SOLID = STYLE_SOLID, // Solid
   BORDER_STYLE_DASH  = STYLE_DASH   // Dash
  };

// buffers
double FvgHighPriceBuffer[]; // Higher price of FVG
double FvgLowPriceBuffer[];  // Lower price of FVG
double FvgTrendBuffer[];     // Trend of FVG [0: NO, -1: DOWN, 1: UP]

// config
input group "Section :: Main";
input bool            InpContinueToMitigation = true;            // Continue to mitigation
input ENUM_TIMEFRAMES InpTimeframe            = PERIOD_CURRENT;  // Timeframe (PERIOD_CURRENT = chart TF)

input group "Section :: Style";
input color            InpDownTrendColor = clrYellow;          // Down trend color
input color            InpUpTrendColor   = clrAqua;            // Up trend color
input bool             InpFill           = false;               // Fill solid (true) or transparent (false)
input ENUM_BORDER_STYLE InpBoderStyle    = BORDER_STYLE_SOLID; // Border line style
input int              InpBorderWidth    = 2;                  // Border line width

input group "Section :: Dev";
input bool InpDebugEnabled = false; // Enable debug (verbose logging)
input int  InpBarsToScan   = 200;   // Bars to scan

// constants
const string OBJECT_PREFIX             = "FVG";
const string OBJECT_PREFIX_CONTINUATED = OBJECT_PREFIX + "CNT";
const string OBJECT_SEP                = "#";

// runtime
ENUM_TIMEFRAMES g_tf;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpDebugEnabled)
      Print("Fvg indicator initialization started");

   g_tf = (InpTimeframe == PERIOD_CURRENT) ? Period() : InpTimeframe;

   if(g_tf < Period())
     {
      Alert("FVG: A kiválasztott timeframe alacsonyabb a chart timeframe-nél! Chart TF lesz használva.");
      g_tf = Period();
     }

   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

   ArrayInitialize(FvgHighPriceBuffer, EMPTY_VALUE);
   ArraySetAsSeries(FvgHighPriceBuffer, true);
   SetIndexBuffer(0, FvgHighPriceBuffer, INDICATOR_DATA);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetString(0, PLOT_LABEL, "Fvg High");
   PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE);

   ArrayInitialize(FvgLowPriceBuffer, EMPTY_VALUE);
   ArraySetAsSeries(FvgLowPriceBuffer, true);
   SetIndexBuffer(1, FvgLowPriceBuffer, INDICATOR_DATA);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetString(1, PLOT_LABEL, "Fvg Low");
   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);

   ArrayInitialize(FvgTrendBuffer, EMPTY_VALUE);
   ArraySetAsSeries(FvgTrendBuffer, true);
   SetIndexBuffer(2, FvgTrendBuffer, INDICATOR_DATA);
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetString(2, PLOT_LABEL, "Fvg Trend");
   PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_NONE);

   if(InpDebugEnabled)
      Print("Fvg indicator initialization finished");

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(InpDebugEnabled)
      Print("Fvg indicator deinitialization started");

   ArrayFill(FvgHighPriceBuffer, 0, ArraySize(FvgHighPriceBuffer), EMPTY_VALUE);
   ArrayResize(FvgHighPriceBuffer, 0);
   ArrayFree(FvgHighPriceBuffer);

   ArrayFill(FvgLowPriceBuffer, 0, ArraySize(FvgLowPriceBuffer), EMPTY_VALUE);
   ArrayResize(FvgLowPriceBuffer, 0);
   ArrayFree(FvgLowPriceBuffer);

   ArrayFill(FvgTrendBuffer, 0, ArraySize(FvgTrendBuffer), EMPTY_VALUE);
   ArrayResize(FvgTrendBuffer, 0);
   ArrayFree(FvgTrendBuffer);

   if(!MQLInfoInteger(MQL_TESTER))
      ObjectsDeleteAll(0, OBJECT_PREFIX);

   if(InpDebugEnabled)
      Print("Fvg indicator deinitialization finished");
  }

//+------------------------------------------------------------------+
//| 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[])
  {
   if(rates_total == prev_calculated)
      return rates_total;

   // --- HTF adatok betöltése ---
   datetime htfTime[];
   double   htfHigh[], htfLow[];
   ArraySetAsSeries(htfTime, true);
   ArraySetAsSeries(htfHigh, true);
   ArraySetAsSeries(htfLow,  true);

   int htfBars = MathMin(InpBarsToScan + 5, Bars(_Symbol, g_tf));
   if(CopyTime(_Symbol, g_tf, 0, htfBars, htfTime) <= 0 ||
      CopyHigh(_Symbol, g_tf, 0, htfBars, htfHigh) <= 0 ||
      CopyLow (_Symbol, g_tf, 0, htfBars, htfLow)  <= 0)
     {
      if(InpDebugEnabled)
         Print("FVG: HTF adatok betöltése sikertelen");
      return 0;
     }

   int htfTotal = ArraySize(htfTime);

   ArraySetAsSeries(time, true);

   // --- Nyitott dobozok kiterjesztése mitigációig ---
   if(InpContinueToMitigation)
     {
      int total = ObjectsTotal(0, 0, OBJ_RECTANGLE);
      for(int i = 0; i < total; i++)
        {
         string objName = ObjectName(0, i, 0, OBJ_RECTANGLE);
         if(StringFind(objName, OBJECT_PREFIX_CONTINUATED) != 0)
            continue;

         string result[];
         StringSplit(objName, StringGetCharacter(OBJECT_SEP, 0), result);
         if(ArraySize(result) < 5)
            continue;

         datetime leftTime   = StringToTime(result[1]);
         double   leftPrice  = StringToDouble(result[2]);
         datetime rightTime  = time[0];
         double   rightPrice = StringToDouble(result[4]);

         if(rightPrice < htfHigh[1] && rightPrice > htfLow[1])
           {
            rightTime = htfTime[1];
            if(ObjectDelete(0, objName))
               DrawBox(leftTime, leftPrice, rightTime, rightPrice, false);
           }
         else
           {
            ObjectMove(0, objName, 1, rightTime, rightPrice);
            if(InpDebugEnabled)
               PrintFormat("Expand box %s", objName);
           }
        }
     }

   // --- HTF gyertyák vizsgálata FVG kereséshez ---
   int limit = MathMin(htfTotal - 3, InpBarsToScan);
   if(InpDebugEnabled)
      PrintFormat("HTF bars: %i, Limit: %i, TF: %s", htfTotal, limit, EnumToString(g_tf));

   for(int i = 1; i < limit; i++)
     {
      double rightHighPrice = htfHigh[i];
      double rightLowPrice  = htfLow[i];
      double midHighPrice   = htfHigh[i + 1];
      double midLowPrice    = htfLow[i + 1];
      double leftHighPrice  = htfHigh[i + 2];
      double leftLowPrice   = htfLow[i + 2];

      datetime rightDt = htfTime[i];
      datetime leftDt  = htfTime[i + 2];

      // Up trend FVG
      bool upLeft  = midLowPrice <= leftHighPrice && midLowPrice > leftLowPrice;
      bool upRight = midHighPrice >= rightLowPrice && midHighPrice < rightHighPrice;
      bool upGap   = leftHighPrice < rightLowPrice;
      if(upLeft && upRight && upGap)
        {
         SetBuffers(i + 1, rightLowPrice, leftHighPrice, 1);

         datetime boxRight = time[0];
         if(InpContinueToMitigation)
           {
            for(int j = i - 1; j > 0; j--)
              {
               if((rightLowPrice < htfHigh[j] && rightLowPrice >= htfLow[j]) ||
                  (leftHighPrice  > htfLow[j]  && leftHighPrice <= htfHigh[j]))
                 {
                  boxRight = htfTime[j];
                  break;
                 }
              }
           }
         else
            boxRight = (i > 1) ? htfTime[i - 1] : time[0];

         DrawBox(leftDt, leftHighPrice, boxRight, rightLowPrice,
                 InpContinueToMitigation && boxRight == time[0]);
         continue;
        }

      // Down trend FVG
      bool downLeft  = midHighPrice >= leftLowPrice && midHighPrice < leftHighPrice;
      bool downRight = midLowPrice <= rightHighPrice && midLowPrice > rightLowPrice;
      bool downGap   = leftLowPrice > rightHighPrice;
      if(downLeft && downRight && downGap)
        {
         SetBuffers(i + 1, leftLowPrice, rightHighPrice, -1);

         datetime boxRight = time[0];
         if(InpContinueToMitigation)
           {
            for(int j = i - 1; j > 0; j--)
              {
               if((rightHighPrice <= htfHigh[j] && rightHighPrice > htfLow[j]) ||
                  (leftLowPrice   >= htfLow[j]  && leftLowPrice  < htfHigh[j]))
                 {
                  boxRight = htfTime[j];
                  break;
                 }
              }
           }
         else
            boxRight = (i > 1) ? htfTime[i - 1] : time[0];

         DrawBox(leftDt, leftLowPrice, boxRight, rightHighPrice,
                 InpContinueToMitigation && boxRight == time[0]);
         continue;
        }

      SetBuffers(i + 1, 0, 0, 0);
     }

   return rates_total;
  }

//+------------------------------------------------------------------+
//| Updates buffers with indicator data                              |
//+------------------------------------------------------------------+
void SetBuffers(int index, double highPrice, double lowPrice, double trend)
  {
   if(index >= ArraySize(FvgHighPriceBuffer))
      return;

   FvgHighPriceBuffer[index] = highPrice;
   FvgLowPriceBuffer[index]  = lowPrice;
   FvgTrendBuffer[index]     = trend;

   if(InpDebugEnabled && trend != 0)
      PrintFormat("Time: %s, FvgTrendBuffer: %f, FvgHighPriceBuffer: %f, FvgLowPriceBuffer: %f",
                  TimeToString(iTime(_Symbol, g_tf, index)),
                  FvgTrendBuffer[index], FvgHighPriceBuffer[index], FvgLowPriceBuffer[index]);
  }

//+------------------------------------------------------------------+
//| Draws FVG box                                                    |
//+------------------------------------------------------------------+
void DrawBox(datetime leftDt, double leftPrice, datetime rightDt, double rightPrice, bool continuated)
  {
   string objName = (continuated ? OBJECT_PREFIX_CONTINUATED : OBJECT_PREFIX)
                    + OBJECT_SEP
                    + TimeToString(leftDt)
                    + OBJECT_SEP
                    + DoubleToString(leftPrice)
                    + OBJECT_SEP
                    + TimeToString(rightDt)
                    + OBJECT_SEP
                    + DoubleToString(rightPrice);

   if(ObjectFind(0, objName) < 0)
     {
      ObjectCreate(0, objName, OBJ_RECTANGLE, 0, leftDt, leftPrice, rightDt, rightPrice);

      ObjectSetInteger(0, objName, OBJPROP_COLOR,      leftPrice < rightPrice ? InpUpTrendColor : InpDownTrendColor);
      ObjectSetInteger(0, objName, OBJPROP_FILL,       InpFill);
      ObjectSetInteger(0, objName, OBJPROP_STYLE,      InpBoderStyle);
      ObjectSetInteger(0, objName, OBJPROP_WIDTH,      InpBorderWidth);
      ObjectSetInteger(0, objName, OBJPROP_BACK,       true);
      ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, objName, OBJPROP_SELECTED,   false);
      ObjectSetInteger(0, objName, OBJPROP_HIDDEN,     false);
      ObjectSetInteger(0, objName, OBJPROP_ZORDER,     0);

      if(InpDebugEnabled)
         PrintFormat("Draw box: %s", objName);
     }
  }
//+------------------------------------------------------------------+
