﻿//+------------------------------------------------------------------+
//|                                                  AdaptiveHedge.mqh |
//|                                        Copyright 2025, Clemence Benjamin |
//|                                        https://www.mql5.com       |
//+------------------------------------------------------------------+
//| Rolling-window ridge regression hedge-ratio estimator.            |
//| Uses flat arrays for all matrix operations.                      |
//|                                                                   |
//| The system maintains a circular buffer of the most recent         |
//| 'window' observations. Each call to Update() rebuilds the        |
//| Gram matrix and RHS vector from the buffer, then solves           |
//| (X'X + λI) w = X'y via Cholesky decomposition.                   |
//+------------------------------------------------------------------+
#ifndef __ADAPTIVE_HEDGE_MQH__
#define __ADAPTIVE_HEDGE_MQH__

#include <Math\Alglib\ap.mqh>

//+------------------------------------------------------------------+
//| CAdaptiveHedge class                                              |
//+------------------------------------------------------------------+
class CAdaptiveHedge
{
protected:
   //--- Configuration
   int               m_window;           // Rolling window length (bars)
   int               m_nFactors;         // Number of hedging instruments
   double            m_lambda;           // Ridge regularisation parameter

   //--- Circular buffer (flat arrays)
   double            m_bufferX[];        // Flattened: [m_window * m_nFactors]
   double            m_bufferY[];        // Target returns: [m_window]
   int               m_head;             // Insertion index (0-based)
   int               m_count;            // Number of valid samples

   //--- Working arrays (flat)
   double            m_XtX[];            // Gram matrix + ridge diagonal
   double            m_Xty[];            // RHS vector
   double            m_weights[];        // Current hedge ratios
   double            m_work[];           // Temporary for Cholesky

   //--- Internal helpers
   void              RebuildSystem();    // Fills m_XtX and m_Xty from buffer
   bool              SolveCholesky();    // Solves system, result in m_weights

public:
                     CAdaptiveHedge();
                    ~CAdaptiveHedge();

   //--- Initialisation
   bool              Init(int windowSize, int nFactors, double lambda);
   void              Reset();

   //--- Feed a new observation
   bool              Update(const double &factors[], double target);

   //--- Retrieve the current hedge weights
   bool              GetWeights(double &out[]) const;

   //--- Status queries
   int               SampleCount() const { return m_count; }
   bool              IsReady() const { return m_count >= m_window; }
};

//+------------------------------------------------------------------+
//| Constructor / Destructor                                          |
//+------------------------------------------------------------------+
CAdaptiveHedge::CAdaptiveHedge()
   : m_window(0)
   , m_nFactors(0)
   , m_lambda(0.0)
   , m_head(0)
   , m_count(0)
{
}

CAdaptiveHedge::~CAdaptiveHedge()
{
}

//+------------------------------------------------------------------+
//| Initialise the hedge estimator                                    |
//+------------------------------------------------------------------+
bool CAdaptiveHedge::Init(int windowSize, int nFactors, double lambda)
{
   if(windowSize < 3 || nFactors < 1 || lambda < 0.0)
   {
      Print("Error: Invalid parameters for CAdaptiveHedge::Init");
      Print("  windowSize: ", windowSize, " (min 3)");
      Print("  nFactors: ", nFactors, " (min 1)");
      Print("  lambda: ", lambda, " (min 0)");
      return false;
   }

   m_window = windowSize;
   m_nFactors = nFactors;
   m_lambda = lambda;

   //--- Allocate buffer
   ArrayResize(m_bufferX, m_window * m_nFactors);
   ArrayResize(m_bufferY, m_window);

   //--- Allocate working arrays
   ArrayResize(m_XtX, m_nFactors * m_nFactors);
   ArrayResize(m_Xty, m_nFactors);
   ArrayResize(m_weights, m_nFactors);
   ArrayResize(m_work, m_nFactors * m_nFactors);

   Reset();
   return true;
}

//+------------------------------------------------------------------+
//| Reset the estimator (clear all data)                              |
//+------------------------------------------------------------------+
void CAdaptiveHedge::Reset()
{
   m_head = 0;
   m_count = 0;
   ArrayInitialize(m_bufferX, 0.0);
   ArrayInitialize(m_bufferY, 0.0);
   ArrayInitialize(m_weights, 0.0);
}

//+------------------------------------------------------------------+
//| Add a new observation and update the hedge ratio                  |
//+------------------------------------------------------------------+
bool CAdaptiveHedge::Update(const double &factors[], double target)
{
   if(m_window == 0 || m_nFactors == 0)
   {
      Print("Error: CAdaptiveHedge not initialized");
      return false;
   }

   //--- Validate input
   if(ArraySize(factors) < m_nFactors)
   {
      Print("Error: Insufficient factors provided. Need ", m_nFactors, ", got ", ArraySize(factors));
      return false;
   }

   //--- Store in circular buffer (flat array)
   int offset = m_head * m_nFactors;
   for(int i = 0; i < m_nFactors; i++)
      m_bufferX[offset + i] = factors[i];
   m_bufferY[m_head] = target;

   //--- Advance head
   m_head++;
   if(m_head >= m_window)
      m_head = 0;

   if(m_count < m_window)
      m_count++;

   //--- Rebuild system and solve
   RebuildSystem();

   if(!SolveCholesky())
   {
      Print("Warning: Cholesky solve failed. Retaining previous weights.");
      return false;
   }

   return true;
}

//+------------------------------------------------------------------+
//| Rebuild the Gram matrix and RHS vector from the buffer           |
//+------------------------------------------------------------------+
void CAdaptiveHedge::RebuildSystem()
{
   int n = m_nFactors;
   int m = m_count;

   if(m < 3)
      return;

   //--- Initialize arrays
   ArrayInitialize(m_XtX, 0.0);
   ArrayInitialize(m_Xty, 0.0);

   //--- Build Gram matrix: X'X
   // X is m x n, stored row-major in buffer
   for(int k = 0; k < m; k++)
   {
      int rowOffset = k * m_nFactors;

      //--- Update X'X
      for(int i = 0; i < n; i++)
      {
         for(int j = 0; j < n; j++)
         {
            m_XtX[i * n + j] += m_bufferX[rowOffset + i] * m_bufferX[rowOffset + j];
         }
      }

      //--- Update X'y
      for(int i = 0; i < n; i++)
      {
         m_Xty[i] += m_bufferX[rowOffset + i] * m_bufferY[k];
      }
   }

   //--- Add ridge regularisation: λI to diagonal
   for(int i = 0; i < n; i++)
   {
      m_XtX[i * n + i] += m_lambda * m_count;
   }
}

//+------------------------------------------------------------------+
//| Solve the system using Cholesky decomposition                    |
//+------------------------------------------------------------------+
bool CAdaptiveHedge::SolveCholesky()
{
   int n = m_nFactors;

   if(m_count < 3)
   {
      //--- Not enough data, use equal weights
      for(int i = 0; i < n; i++)
         m_weights[i] = 1.0 / n;
      return true;
   }

   //--- Copy XtX to work array
   for(int i = 0; i < n; i++)
      for(int j = 0; j < n; j++)
         m_work[i * n + j] = m_XtX[i * n + j];

   //--- Cholesky decomposition: A = L * L^T
   for(int i = 0; i < n; i++)
   {
      for(int j = 0; j <= i; j++)
      {
         double sum = m_work[i * n + j];

         for(int k = 0; k < j; k++)
            sum -= m_work[i * n + k] * m_work[j * n + k];

         if(i == j)
         {
            if(sum <= 0.0)
            {
               Print("Cholesky: Matrix is not positive definite at i=", i);
               return false;
            }
            m_work[i * n + i] = MathSqrt(sum);
         }
         else
         {
            m_work[i * n + j] = sum / m_work[j * n + j];
         }
      }
   }

   //--- Forward solve: L * z = Xty
   double z[];
   ArrayResize(z, n);
   for(int i = 0; i < n; i++)
   {
      double sum = m_Xty[i];
      for(int j = 0; j < i; j++)
         sum -= m_work[i * n + j] * z[j];
      z[i] = sum / m_work[i * n + i];
   }

   //--- Backward solve: L^T * w = z
   for(int i = n - 1; i >= 0; i--)
   {
      double sum = z[i];
      for(int j = i + 1; j < n; j++)
         sum -= m_work[j * n + i] * m_weights[j];
      m_weights[i] = sum / m_work[i * n + i];
   }

   return true;
}

//+------------------------------------------------------------------+
//| Get current hedge weights                                         |
//+------------------------------------------------------------------+
bool CAdaptiveHedge::GetWeights(double &out[]) const
{
   if(m_nFactors == 0)
   {
      Print("Error: Hedge estimator not initialized");
      return false;
   }

   if(ArraySize(out) < m_nFactors)
      ArrayResize(out, m_nFactors);

   for(int i = 0; i < m_nFactors; i++)
      out[i] = m_weights[i];

   return true;
}

#endif // __ADAPTIVE_HEDGE_MQH__
//+------------------------------------------------------------------+