//+------------------------------------------------------------------+
//|                                          TrailingSparseTable.mqh |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include <Expert\ExpertTrailing.mqh>
#include "MarketDatabase.mqh" // Include the database class

// wizard description start
//+------------------------------------------------------------------+
//| Description of the class                                         |
//| Title=Trailing Stop based on Sparse Table RMQ with Excursion     |
//| Type=Trailing                                                    |
//| Name=SparseTable                                                 |
//| Class=CTrailingSparseTableEx                                     |
//| Page=                                                            |
//| Parameter=WindowSize,int,20,Lookback window for RMQ              |
//| Parameter=DbName,string,MarketData.sqlite,Database filename      |
//| Parameter=Excursion,double,2.0,Excursion threshold               |
//+------------------------------------------------------------------+
// wizard description end

//+------------------------------------------------------------------+
//| Class CTrailingSparseTableEx.                                    |
//| Purpose: $O(1)$ Trailing stop using Range Minimum/Maximum Queries|
//+------------------------------------------------------------------+
class CTrailingSparseTableEx : public CExpertTrailing
  {
protected:
   matrix            m_st;             // Sparse Table for storage
   int               m_window_size;    // Trailing lookback window
   int               m_log_table[];    // Pre-computed Log2 table
   CMarketDatabase   m_db;             // Database accessor
   string            m_db_name;
   //
   double            m_excursion;
   CiATR             m_atr;

public:
                     CTrailingSparseTableEx(void);
                    ~CTrailingSparseTableEx(void);

   //--- Setters for Wizard parameters
   void              WindowSize(int size)
     {
      m_window_size = size;
     }
   void              DbName(string name)
     {
      m_db_name = name;
     }
   void              Excursion(double excursion)
     {
      m_excursion = excursion;
     }

   virtual bool      InitIndicators(CIndicators *indicators);
   virtual bool      CheckTrailingStopLong(CPositionInfo *position, double &sl, double &tp);
   virtual bool      CheckTrailingStopShort(CPositionInfo *position, double &sl, double &tp);

   bool              BuildTable(MqlRates &R[], bool is_min);
   double            QueryRMQ(int L, int R, bool is_min);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTrailingSparseTableEx::CTrailingSparseTableEx(void) : m_window_size(20), m_db_name("MarketData.sqlite")
  {
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTrailingSparseTableEx::~CTrailingSparseTableEx(void)
  {
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CTrailingSparseTableEx::InitIndicators(CIndicators *indicators)
  {
   if(!CExpertTrailing::InitIndicators(indicators))
      return false;
// 1. Initialize ATR for the Geometric Gatekeeper
   if(!m_atr.Create(m_symbol.Name(), m_period, m_window_size))
      return false;
// 2. Pre-compute Log2 table for O(1) bitwise speed
   ArrayResize(m_log_table, 1001);
   m_log_table[1] = 0;
   for(int i = 2; i <= 1000; i++)
      m_log_table[i] = m_log_table[i / 2] + 1;
// 3. Pre-load Historical Cleaned Data from SQLite
   MqlRates rates[];
// Load 3x the window size to allow for multi-term query depth
   int initial_size = 3 * m_window_size;
   int count = m_db.GetCleanedRates(m_symbol.Name(), TimeCurrent() - (PeriodSeconds() * initial_size), initial_size, rates);
   if(count >= initial_size)
     {
      // Build the initial table so the first tick is O(1)
      BuildTable(rates, true);
      printf("Sparse Table pre-initialized with %d bars from SQLite.", count);
     }
   return true;
  }

//+------------------------------------------------------------------+
//| Build Sparse Table from price array: O(N log N)                  |
//+------------------------------------------------------------------+
bool CTrailingSparseTableEx::BuildTable(MqlRates &R[], bool is_min)
  {
   int n = int(R.Size());
   if(n <= 0)
      return false;
   int max_j = m_log_table[n] + 1;
   m_st.Resize(n, max_j);
// Base case: intervals of length 1
   for(int i = 0; i < n; i++)
     {
      if(is_min)
        {
         m_st[i][0] = R[i].low;
        }
      else
        {
         m_st[i][0] = R[i].high;
        }
     }
// Compute intervals of length 2^j
   for(int j = 1; j < max_j; j++)
     {
      for(int i = 0; i + (1 << j) <= n; i++)
        {
         if(is_min)
           {
            // Storing min values for Long positions;
            m_st[i][j] = MathMin(m_st[i][j - 1], m_st[i + (1 << (j - 1))][j - 1]);
           }
         else
           {
            // Storing max values for Short positions;
            m_st[i][j] = MathMax(m_st[i][j - 1], m_st[i + (1 << (j - 1))][j - 1]);
           }
        }
     }
   return true;
  }

//+------------------------------------------------------------------+
//| O(1) Range Query                                                 |
//+------------------------------------------------------------------+
double CTrailingSparseTableEx::QueryRMQ(int L, int R, bool is_min)
  {
   int length = R - L + 1;
   int k = m_log_table[length];
   if(is_min)
      return MathMin(m_st[L][k], m_st[R - (1 << k) + 1][k]);
   else
      return MathMax(m_st[L][k], m_st[R - (1 << k) + 1][k]);
  }

//+------------------------------------------------------------------+
//| Check Long: Uses SQLite data to find Lowest price in window      |
//+------------------------------------------------------------------+
bool CTrailingSparseTableEx::CheckTrailingStopLong(CPositionInfo *position, double &sl, double &tp)
  {
   if(position == NULL)
      return false;
   static datetime last_bar_time = 0;
   datetime current_bar_time = iTime(m_symbol.Name(), m_period, 0);
// Only rebuild the table if a new bar has formed
   if(current_bar_time != last_bar_time)
     {
      MqlRates rates[];
      int size = 3 * m_window_size;
      int count = m_db.GetCleanedRates(m_symbol.CurrencyMargin() + m_symbol.CurrencyProfit(), TimeCurrent() - (PeriodSeconds() * size), size, rates);
      if(count >= size)
        {
         BuildTable(rates, true); // O(N log N) happens once per bar
         last_bar_time = current_bar_time;
        }
     }
// --- O(1) CONSTANT TIME QUERIES ---
// This section executes every tick without iterative overhead
   int total_elements = int(m_st.Rows());
   if(total_elements > 1)
     {
      double short_term = QueryRMQ(total_elements - m_window_size, total_elements - 1, true);
      double long_term  = QueryRMQ(0, total_elements - 1, true);
      double new_sl = (short_term + long_term) / 2.0;
      // Apply Geometric Excursion Validation
      sl = EMPTY_VALUE;
      tp = EMPTY_VALUE;
      double current_sl = position.StopLoss();
      double limit = m_symbol.Bid() - m_symbol.StopsLevel() * m_symbol.Point();
      if((new_sl > current_sl || current_sl == 0.0) && new_sl < limit)
        {
         m_atr.Refresh(-1);
         double excursion = fabs(m_symbol.Bid() - new_sl) / m_atr.Main(StartIndex());
         if(excursion >= m_excursion)
           {
            sl = NormalizeDouble(new_sl, m_symbol.Digits());
            return true;
           }
        }
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Check Short: Uses SQLite data to find Highest price in window    |
//+------------------------------------------------------------------+
bool CTrailingSparseTableEx::CheckTrailingStopShort(CPositionInfo *position, double &sl, double &tp)
  {
   if(position == NULL)
      return false;
   static datetime last_bar_time = 0;
   datetime current_bar_time = iTime(m_symbol.Name(), m_period, 0);
// Only rebuild the table if a new bar has formed
   if(current_bar_time != last_bar_time)
     {
      MqlRates rates[];
      int size = 3 * m_window_size;
      int count = m_db.GetCleanedRates(m_symbol.CurrencyMargin() + m_symbol.CurrencyProfit(), TimeCurrent() - (PeriodSeconds() * size), size, rates);
      if(count >= size)
        {
         BuildTable(rates, true); // O(N log N) happens once per bar
         last_bar_time = current_bar_time;
        }
     }
// --- O(1) CONSTANT TIME QUERIES ---
// This section executes every tick without iterative overhead
   int total_elements = int(m_st.Rows());
   if(total_elements > 1)
     {
      double short_term = QueryRMQ(total_elements - m_window_size, total_elements - 1, true);
      double long_term  = QueryRMQ(0, total_elements - 1, true);
      double new_sl = (short_term + long_term) / 2.0;
      // Apply Geometric Excursion Validation
      sl = EMPTY_VALUE;
      tp = EMPTY_VALUE;
      double current_sl = position.StopLoss();
      double limit = m_symbol.Bid() - m_symbol.StopsLevel() * m_symbol.Point();
      if((new_sl > current_sl || current_sl == 0.0) && new_sl > limit)
        {
         m_atr.Refresh(-1);
         double excursion = fabs(m_symbol.Ask() - new_sl) / m_atr.Main(StartIndex());
         if(excursion >= m_excursion)
           {
            sl = NormalizeDouble(new_sl, m_symbol.Digits());
            return true;
           }
        }
     }
   return false;
  }
//+------------------------------------------------------------------+
