//+------------------------------------------------------------------+
//|                                             Point and Figure.mq5 |
//|                                             Abioye Israel Pelumi |
//|                                              https://Algoyin.com |
//+------------------------------------------------------------------+
#property copyright "Abioye Israel Pelumi"
#property link      "https://Algoyin.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 0
#property indicator_plots   0

//--- User input parameters
input double InpPFSize  = 5;        // Box size (price movement per brick)
input int    InpNumPF   = 100;      // Maximum number of PF(Point and Figure) displayed
input color  InpUpColor = clrGreen; // Color for upward movement (X)
input color  InpDnColor = clrRed;   // Color for downward movement (O)

//--- Array storing Point & Figure prices
double pfclose[];

//--- Current number of stored pf
int pfSize = 0;

//--- Current trend direction (1 = up, -1 = down, 0 = none)
int trend  = 0;

//--- Prefix for all chart objects
string PREFIX;

//--- Indicator subwindow index
int indicator_window;

//+------------------------------------------------------------------+
//| Draw or update a Point & Figure                                  |
//+------------------------------------------------------------------+
void DrawBrick(int index, datetime time, double price, bool up)
  {
   string name = PREFIX + IntegerToString(index);

//--- Create object if it does not already exist
   if(ObjectFind(0, name) < 0)
      ObjectCreate(0, name, OBJ_TEXT, indicator_window, time, price);

//--- Set color based on direction
   ObjectSetInteger(0, name, OBJPROP_COLOR, up ? InpUpColor : InpDnColor);

//--- Set symbol (X or O)
   ObjectSetString(0, name, OBJPROP_TEXT, up ? "X" : "O");

//--- Move object to correct position
   ObjectMove(0, name, 0, time, price);
  }

//+------------------------------------------------------------------+
//| Add new PF value at beginning of array                           |
//+------------------------------------------------------------------+
void PF_Unshift(double value)
  {
   ArrayResize(pfclose, pfSize + 1);

//--- Shift existing values to the right
   for(int i = pfSize; i > 0; i--)
      pfclose[i] = pfclose[i - 1];

//--- Insert newest value at index 0
   pfclose[0] = value;

   pfSize++;
  }

//+------------------------------------------------------------------+
//| Remove oldest PF value from array                                |
//+------------------------------------------------------------------+
void pf_Pop()
  {
   if(pfSize > 0)
      pfSize--;
  }

//+------------------------------------------------------------------+
//| Delete all indicator objects                                    |
//+------------------------------------------------------------------+
void DeleteAll()
  {
   for(int i = ObjectsTotal(0) - 1; i >= 0; i--)
     {
      string name = ObjectName(0, i);

      //--- Delete only objects created by this indicator
      if(StringFind(name, PREFIX) == 0)
         ObjectDelete(0, name);
     }
  }

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {

//--- Set indicator name
   IndicatorSetString(INDICATOR_SHORTNAME, "PnF");

//--- Get indicator subwindow index
   indicator_window = ChartWindowFind(ChartID(), "PnF");

//--- Create unique prefix for objects
   PREFIX = "PF_" + EnumToString(_Period) + "_";

//--- Clean old objects on start
   DeleteAll();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialization                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
// Remove all objects when indicator is removed
   DeleteAll();
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
  {
//--- Ensure enough data
   if(rates_total < InpNumPF + 2)
      return 0;
   bool need_redraw = false;
//--- First run initialization
   if(prev_calculated == 0)
     {
      ArrayResize(pfclose, 1);
      pfclose[0] = close[0]; // base price
      pfSize = 1;
     }

//--- Set loop start index
   int start = (prev_calculated == 0) ? 1 : prev_calculated;

//--- Build Point & Figure data
   for(int i = start; i < rates_total; i++)
     {
      double price = close[i];
      double pf_size   = InpPFSize;

      double last = pfclose[0];
      double diff = price - last;

      int pf_no = (int)MathFloor(MathAbs(diff) / pf_size);

      if(pf_no <= 0)
         continue;
      //--- Determine direction
      int direction = (diff > 0) ? 1 : -1;

      //--- Initialize trend
      if(trend == 0)
        {
         trend = direction;

         for(int b = 0; b < pf_no; b++)
           {
            last += trend * pf_size;
            PF_Unshift(last);
            need_redraw = true;
           }
         continue;
        }

      //--- Continue trend
      if(direction == trend)
        {
         for(int b = 0; b < pf_no; b++)
           {
            last += trend * pf_size;
            PF_Unshift(last);
            need_redraw = true;
           }
        }
      //--- Reversal condition
      else
         if(pf_no >= 3)
           {
            trend = direction;

            for(int b = 0; b < pf_no; b++)
              {
               last += trend * pf_size;
               PF_Unshift(last);
               need_redraw = true;
              }
           }

      //--- Limit PF size
      while(pfSize > InpNumPF)
         pf_Pop();
     }

//--- Drawing section
   int shift = 1;
   datetime t2 = iTime(_Symbol, PERIOD_CURRENT, shift);

//--- Set indicator window range based on PF data
   if(need_redraw || prev_calculated == 0)
     {
      double maxPrice = 0;
      double minPrice = pfclose[0];

      for(int i = 0; i < pfSize && i < InpNumPF; i++)
        {
         //--- Track maximum price
         if(pfclose[i] > maxPrice)
            maxPrice = pfclose[i];

         //--- Track minimum price
         if(pfclose[i] < minPrice)
            minPrice = pfclose[i];

         //--- Adjust indicator scale
         IndicatorSetDouble(INDICATOR_MAXIMUM, maxPrice + (InpPFSize * 2));
         IndicatorSetDouble(INDICATOR_MINIMUM, minPrice - (InpPFSize * 2));

         //--- Adjust spacing alignment
         if(i - 1 >= 1 && i + 1 < pfSize && i + 1 < InpNumPF
            && pfclose[i + 1] == pfclose[i - 1])
           {
            shift++;
           }

         t2 = iTime(_Symbol, _Period, shift);

         //--- Determine upward movement
         bool up = (i + 1 < pfSize && i + 1 < InpNumPF
                    && pfclose[i] > pfclose[i + 1]);

         DrawBrick(i, t2, pfclose[i], up);
        }

      //--- Refresh chart
      ChartRedraw(0);
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }

//+------------------------------------------------------------------+
