//+------------------------------------------------------------------+
//|                                 PearsonCorrelationCalculator.mqh |
//+------------------------------------------------------------------+
#ifndef PEARSONCORRELATIONCALCULATOR_MQH
#define PEARSONCORRELATIONCALCULATOR_MQH

#include "CorrelationTypes.mqh"

//+--------------------------------------------------------------------+
//| CPearsonCorrelationCalculator                                      |
//| Computes the Pearson correlation coefficient between two return    |
//| series, and assembles a full CCorrelationMatrix from an array of   |
//| symbols' return series. Both methods are pure functions over plain |
//| data, with no dependency on CCanvas or any live account, which is  |
//| what makes this class directly testable with synthetic series.     |
//+--------------------------------------------------------------------+
class CPearsonCorrelationCalculator
  {
public:
                     CPearsonCorrelationCalculator(void);
                    ~CPearsonCorrelationCalculator(void);
   bool              ComputePairCorrelation(const double &series_a[], const double &series_b[], int count, double &correlation_out) const;
   bool              BuildMatrix(const CSymbolReturnSeries &series[], int count, CCorrelationMatrix &matrix_out) const;
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//| The calculator holds no state between calls, so construction     |
//| performs no work beyond default object creation.                 |
//+------------------------------------------------------------------+
CPearsonCorrelationCalculator::CPearsonCorrelationCalculator(void)
  {
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//| No resources are owned by this class, so no cleanup is required. |
//+------------------------------------------------------------------+
CPearsonCorrelationCalculator::~CPearsonCorrelationCalculator(void)
  {
  }

//+---------------------------------------------------------------------------+
//| ComputePairCorrelation                                                    |
//| Computes the standard Pearson correlation coefficient: the covariance     |
//| of the two series divided by the product of their standard deviations.    |
//| A series with zero variance, meaning every return in it is identical,     |
//| most commonly a symbol whose price did not move at all across the         |
//| window, makes that division mathematically undefined, so this method      |
//| reports the result as undefined through its return value rather than      |
//| forcing a default correlation of zero that could be misread as a genuine  |
//| finding of "no relationship." When a result is defined, it is clamped to  |
//| the theoretical [-1, 1] range before being returned, since accumulated    |
//| floating-point rounding across many terms can otherwise push a result     |
//| fractionally outside that range even when the true correlation is exactly |
//| 1.0 or -1.0.                                                              |
//+---------------------------------------------------------------------------+
bool CPearsonCorrelationCalculator::ComputePairCorrelation(const double &series_a[],const double &series_b[],
      const int count,double &correlation_out) const
  {
   correlation_out = 0.0;
   if(count < 2)
      return(false);

//--- compute both series' means
   double sum_a = 0.0, sum_b = 0.0;
   for(int i = 0; i < count; i++)
     {
      sum_a += series_a[i];
      sum_b += series_b[i];
     }
   double mean_a = sum_a / count;
   double mean_b = sum_b / count;

//--- accumulate covariance and both variances in a single pass
   double covariance = 0.0;
   double variance_a = 0.0;
   double variance_b = 0.0;
   for(int i = 0; i < count; i++)
     {
      double deviation_a = series_a[i] - mean_a;
      double deviation_b = series_b[i] - mean_b;
      covariance += deviation_a * deviation_b;
      variance_a += deviation_a * deviation_a;
      variance_b += deviation_b * deviation_b;
     }

//--- a zero-variance series makes the denominator zero and the ratio undefined
   double denominator = ::MathSqrt(variance_a * variance_b);
   if(denominator == 0.0)
      return(false);

   double correlation = covariance / denominator;
//--- clamp away any floating-point overshoot past the theoretical [-1, 1] range
   if(correlation > 1.0)
      correlation = 1.0;
   if(correlation < -1.0)
      correlation = -1.0;

   correlation_out = correlation;
   return(true);
  }

//+---------------------------------------------------------------------------+
//| BuildMatrix                                                               |
//| Computes every pairwise correlation across the supplied return series     |
//| and assembles them into a symmetric CCorrelationMatrix. Only the upper    |
//| triangle, including the diagonal, is actually computed; each result is    |
//| mirrored into its transposed cell, since correlation between A and B is   |
//| identical to correlation between B and A by definition. The diagonal      |
//| cells, a symbol against itself, are computed with the same                |
//| ComputePairCorrelation() call as every other pair rather than being       |
//| hardcoded to 1.0: a symbol whose returns have zero variance is exactly as |
//| undefined against itself as it is against any other symbol, and reusing   |
//| the same method keeps that rule in one place instead of two.              |
//+---------------------------------------------------------------------------+
bool CPearsonCorrelationCalculator::BuildMatrix(const CSymbolReturnSeries &series[],const int count,
      CCorrelationMatrix &matrix_out) const
  {
   if(count <= 0)
      return(false);

   string symbols[];
   ::ArrayResize(symbols, count);
   for(int i = 0; i < count; i++)
      symbols[i] = series[i].m_symbol;
   matrix_out.Initialize(symbols, count);

   for(int row = 0; row < count; row++)
     {
      for(int col = row; col < count; col++)
        {
         int shared_count = ::MathMin(series[row].m_count, series[col].m_count);
         double correlation = 0.0;
         bool   is_defined  = ComputePairCorrelation(series[row].m_returns, series[col].m_returns, shared_count, correlation);
         matrix_out.SetValue(row, col, correlation, is_defined);
         if(col != row)
            matrix_out.SetValue(col, row, correlation, is_defined);
        }
     }
   return(true);
  }

#endif // PEARSONCORRELATIONCALCULATOR_MQH
//+------------------------------------------------------------------+