//+------------------------------------------------------------------+
//|                                            SpreadHistogram.mqh   |
//+------------------------------------------------------------------+
#ifndef SPREADHISTOGRAM_MQH
#define SPREADHISTOGRAM_MQH
//+------------------------------------------------------------------+
//| Class CSpreadHistogram                                           |
//| Rolling histogram accumulator for tick-level spread observations |
//+------------------------------------------------------------------+
class CSpreadHistogram
  {
private:
   int               m_bins[];
   int               m_circular[];
   int               m_head;
   int               m_num_bins;
   double            m_bin_width;
   double            m_min_spread;
   double            m_max_spread;
   int               m_window_size;
   int               m_obs_count;
   long              m_total_ticks;
   int               PriceToBin(double spread_pts);
   double            BinToPrice(int bin);
public:
                     CSpreadHistogram(void);
                    ~CSpreadHistogram(void);
   bool              Init(int num_bins,double min_spread_pts,double max_spread_pts,int window_size);
   void              Update(double spread_pts);
   double            GetPercentileRank(double spread_pts);
   double            GetPercentileValue(double pct);
   double            GetMedian(void);
   double            GetMean(void);
   bool              IsReady(void);
   void              Reset(void);
   int               GetObsCount(void);
   bool              GetBinCounts(int &out_counts[]);
   int               GetNumBins(void);
   double            GetBinWidth(void);
   double            GetMinSpread(void);
   double            GetMaxSpread(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSpreadHistogram::CSpreadHistogram(void)
  {
   m_head=0;
   m_num_bins=0;
   m_bin_width=0.0;
   m_min_spread=0.0;
   m_max_spread=0.0;
   m_window_size=0;
   m_obs_count=0;
   m_total_ticks=0;
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CSpreadHistogram::~CSpreadHistogram(void)
  {
   ArrayFree(m_bins);
   ArrayFree(m_circular);
  }
//+------------------------------------------------------------------+
//| Init                                                             |
//+------------------------------------------------------------------+
bool CSpreadHistogram::Init(int num_bins,double min_spread_pts,double max_spread_pts,int window_size)
  {
   if(num_bins<=0 || window_size<=0 || max_spread_pts<=min_spread_pts)
      return(false);
   m_num_bins=num_bins;
   m_min_spread=min_spread_pts;
   m_max_spread=max_spread_pts;
   m_bin_width=(m_max_spread-m_min_spread)/m_num_bins;
   m_window_size=window_size;
   m_obs_count=0;
   m_head=0;
   m_total_ticks=0;
   if(ArrayResize(m_bins,m_num_bins)!=m_num_bins)
      return(false);
   if(ArrayResize(m_circular,m_window_size)!=m_window_size)
      return(false);
   ArrayInitialize(m_bins,0);
   ArrayInitialize(m_circular,-1);
   return(true);
  }
//+------------------------------------------------------------------+
//| PriceToBin                                                       |
//+------------------------------------------------------------------+
int CSpreadHistogram::PriceToBin(double spread_pts)
  {
   int bin=(int)((spread_pts-m_min_spread)/m_bin_width);
   if(bin<0)
      bin=0;
   if(bin>m_num_bins-1)
      bin=m_num_bins-1;
   return(bin);
  }
//+------------------------------------------------------------------+
//| BinToPrice                                                       |
//+------------------------------------------------------------------+
double CSpreadHistogram::BinToPrice(int bin)
  {
   return(m_min_spread+(bin+0.5)*m_bin_width);
  }
//+------------------------------------------------------------------+
//| Update                                                           |
//+------------------------------------------------------------------+
void CSpreadHistogram::Update(double spread_pts)
  {
   int new_bin=PriceToBin(spread_pts);
   if(m_obs_count>=m_window_size)
     {
      int old_bin=m_circular[m_head];
      if(old_bin>=0)
         m_bins[old_bin]--;
     }
   else
      m_obs_count++;
   m_bins[new_bin]++;
   m_circular[m_head]=new_bin;
   m_head=(m_head+1)%m_window_size;
   m_total_ticks++;
  }
//+------------------------------------------------------------------+
//| GetPercentileRank                                                |
//+------------------------------------------------------------------+
double CSpreadHistogram::GetPercentileRank(double spread_pts)
  {
   if(m_obs_count<=0)
      return(0.0);
   int target_bin=PriceToBin(spread_pts);
   long cum=0;
   for(int i=0;i<=target_bin;i++)
      cum+=m_bins[i];
   return(100.0*(double)cum/(double)m_obs_count);
  }
//+------------------------------------------------------------------+
//| GetPercentileValue                                               |
//+------------------------------------------------------------------+
double CSpreadHistogram::GetPercentileValue(double pct)
  {
   if(m_obs_count<=0)
      return(0.0);
   long target=(long)MathCeil(pct/100.0*m_obs_count);
   if(target<1)
      target=1;
   long cum=0;
   for(int i=0;i<m_num_bins;i++)
     {
      cum+=m_bins[i];
      if(cum>=target)
         return(BinToPrice(i));
     }
   return(BinToPrice(m_num_bins-1));
  }
//+------------------------------------------------------------------+
//| GetMedian                                                        |
//+------------------------------------------------------------------+
double CSpreadHistogram::GetMedian(void)
  {
   return(GetPercentileValue(50.0));
  }
//+------------------------------------------------------------------+
//| GetMean                                                          |
//+------------------------------------------------------------------+
double CSpreadHistogram::GetMean(void)
  {
   if(m_obs_count<=0)
      return(0.0);
   double weighted_sum=0.0;
   for(int i=0;i<m_num_bins;i++)
      weighted_sum+=BinToPrice(i)*m_bins[i];
   return(weighted_sum/m_obs_count);
  }
//+------------------------------------------------------------------+
//| IsReady                                                          |
//+------------------------------------------------------------------+
bool CSpreadHistogram::IsReady(void)
  {
   return(m_obs_count>=m_window_size/2);
  }
//+------------------------------------------------------------------+
//| Reset                                                            |
//+------------------------------------------------------------------+
void CSpreadHistogram::Reset(void)
  {
   ArrayInitialize(m_bins,0);
   ArrayInitialize(m_circular,-1);
   m_obs_count=0;
   m_head=0;
  }
//+------------------------------------------------------------------+
//| GetObsCount                                                      |
//+------------------------------------------------------------------+
int CSpreadHistogram::GetObsCount(void)
  {
   return(m_obs_count);
  }
//+------------------------------------------------------------------+
//| GetBinCounts                                                     |
//+------------------------------------------------------------------+
bool CSpreadHistogram::GetBinCounts(int &out_counts[])
  {
   if(m_num_bins<=0)
      return(false);
   if(ArrayResize(out_counts,m_num_bins)!=m_num_bins)
      return(false);
   for(int i=0;i<m_num_bins;i++)
      out_counts[i]=m_bins[i];
   return(true);
  }
//+------------------------------------------------------------------+
//| GetNumBins                                                       |
//+------------------------------------------------------------------+
int CSpreadHistogram::GetNumBins(void)
  {
   return(m_num_bins);
  }
//+------------------------------------------------------------------+
//| GetBinWidth                                                      |
//+------------------------------------------------------------------+
double CSpreadHistogram::GetBinWidth(void)
  {
   return(m_bin_width);
  }
//+------------------------------------------------------------------+
//| GetMinSpread                                                     |
//+------------------------------------------------------------------+
double CSpreadHistogram::GetMinSpread(void)
  {
   return(m_min_spread);
  }
//+------------------------------------------------------------------+
//| GetMaxSpread                                                     |
//+------------------------------------------------------------------+
double CSpreadHistogram::GetMaxSpread(void)
  {
   return(m_max_spread);
  }
#endif
//+------------------------------------------------------------------+