//+------------------------------------------------------------------+
//|                                        ExitLadderCalibrator.mqh  |
//|                                Copyright 2026, Tola Moses Hector |
//|                                     https://t.me/tolahector      |
//+------------------------------------------------------------------+
#ifndef EXITLADDERCALIBRATOR_MQH
#define EXITLADDERCALIBRATOR_MQH

#include "ExcursionTracker.mqh"

//+------------------------------------------------------------------+
//| One rung of the exit ladder                                      |
//+------------------------------------------------------------------+
struct SLadderRung
  {
   double            r_level;         // trigger, in R, at which this rung fires
   double            close_fraction;  // fraction of the ORIGINAL volume to close here
  };

//+------------------------------------------------------------------+
//| CExitLadderCalibrator                                            |
//| Derives exit-ladder rungs from the empirical distribution of     |
//| historical MFE-in-R samples, using percentile thresholds.        |
//+------------------------------------------------------------------+
class CExitLadderCalibrator
  {
private:
   double            m_percentiles[];     // e.g. {30, 60, 85}
   double            m_close_fractions[]; // matching close fraction per rung
   int               m_min_samples;
   int               m_lookback_trades;   // use at most the N most recent samples

   //--- fallback ladder used until enough samples exist
   SLadderRung       m_fallback[];

   double            Percentile(double &sorted_values[], double pct) const;
   void              SortAscending(double &values[]) const;

public:
                     CExitLadderCalibrator(void);
                    ~CExitLadderCalibrator(void);

   void              Configure(const double &percentiles[], const double &close_fractions[],
                               const int min_samples, const int lookback_trades);
   void              SetFallback(const SLadderRung &fallback[]);

   //--- calibration
   int               Calibrate(const SExcursionSample &samples[], SLadderRung &out_ladder[]) const;
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CExitLadderCalibrator::CExitLadderCalibrator(void) : m_min_samples(30),
   m_lookback_trades(200)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CExitLadderCalibrator::~CExitLadderCalibrator(void)
  {
  }
//+------------------------------------------------------------------+
//| Configure                                                        |
//+------------------------------------------------------------------+
void CExitLadderCalibrator::Configure(const double &percentiles[], const double &close_fractions[],
                                      const int min_samples, const int lookback_trades)
  {
   ArrayResize(m_percentiles, ArraySize(percentiles));
   ArrayResize(m_close_fractions, ArraySize(close_fractions));
   ArrayCopy(m_percentiles, percentiles);
   ArrayCopy(m_close_fractions, close_fractions);
   m_min_samples     = (min_samples > 0 ? min_samples : 30);
   m_lookback_trades = (lookback_trades > 0 ? lookback_trades : 200);
  }
//+------------------------------------------------------------------+
//| SetFallback                                                      |
//+------------------------------------------------------------------+
void CExitLadderCalibrator::SetFallback(const SLadderRung &fallback[])
  {
   int n = ArraySize(fallback);
   ArrayResize(m_fallback, n);
   for(int i = 0; i < n; i++)
      m_fallback[i] = fallback[i];
  }
//+------------------------------------------------------------------+
//| SortAscending                                                    |
//| Simple insertion sort — sample counts here run to the hundreds,  |
//| not millions, so O(n^2) is not a practical concern.              |
//+------------------------------------------------------------------+
void CExitLadderCalibrator::SortAscending(double &values[]) const
  {
   int n = ArraySize(values);
   for(int i = 1; i < n; i++)
     {
      double key = values[i];
      int j = i - 1;
      while(j >= 0 && values[j] > key)
        {
         values[j + 1] = values[j];
         j--;
        }
      values[j + 1] = key;
     }
  }
//+------------------------------------------------------------------+
//| Percentile                                                       |
//| Linear-interpolation percentile on an already-sorted array,      |
//| pct expressed 0-100.                                             |
//+------------------------------------------------------------------+
double CExitLadderCalibrator::Percentile(double &sorted_values[], double pct) const
  {
   int n = ArraySize(sorted_values);
   if(n == 0)
      return(0.0);
   if(n == 1)
      return(sorted_values[0]);

   double rank = (pct / 100.0) * (n - 1);
   int lower = (int)MathFloor(rank);
   int upper = (int)MathCeil(rank);
   if(lower < 0)
      lower = 0;
   if(upper > n - 1)
      upper = n - 1;

   if(lower == upper)
      return(sorted_values[lower]);

   double weight = rank - lower;
   return(sorted_values[lower] + weight * (sorted_values[upper] - sorted_values[lower]));
  }
//+------------------------------------------------------------------+
//| Calibrate                                                        |
//| Builds out_ladder[] from percentiles of mfe_r across the most    |
//| recent m_lookback_trades samples. Falls back to the configured   |
//| static ladder when fewer than m_min_samples samples are          |
//| available. Rungs are always returned in ascending R order.       |
//+------------------------------------------------------------------+
int CExitLadderCalibrator::Calibrate(const SExcursionSample &samples[], SLadderRung &out_ladder[]) const
  {
   int total = ArraySize(samples);

   if(total < m_min_samples)
     {
      int fb_count = ArraySize(m_fallback);
      ArrayResize(out_ladder, fb_count);
      for(int i = 0; i < fb_count; i++)
         out_ladder[i] = m_fallback[i];
      return(fb_count);
     }

//--- take the most recent m_lookback_trades samples (samples[] is assumed
//--- ordered oldest to newest, as produced by CExcursionTracker::LoadSamples())
   int start = (total > m_lookback_trades) ? (total - m_lookback_trades) : 0;
   int used  = total - start;

   double mfe_values[];
   ArrayResize(mfe_values, used);
   for(int i = 0; i < used; i++)
      mfe_values[i] = samples[start + i].mfe_r;

   SortAscending(mfe_values);

   int rung_count = ArraySize(m_percentiles);
   ArrayResize(out_ladder, rung_count);

   for(int i = 0; i < rung_count; i++)
     {
      out_ladder[i].r_level        = Percentile(mfe_values, m_percentiles[i]);
      out_ladder[i].close_fraction = m_close_fractions[i];
     }

//--- enforce ascending R order regardless of configured percentile order —
//--- a simple bubble pass since rung counts are always small (2-5 rungs)
   for(int i = 0; i < rung_count - 1; i++)
     {
      for(int j = 0; j < rung_count - i - 1; j++)
        {
         if(out_ladder[j].r_level > out_ladder[j + 1].r_level)
           {
            SLadderRung tmp = out_ladder[j];
            out_ladder[j] = out_ladder[j + 1];
            out_ladder[j + 1] = tmp;
           }
        }
     }

   return(rung_count);
  }

#endif // EXITLADDERCALIBRATOR_MQH
