//+------------------------------------------------------------------+
//|                                          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 Iterations                          |
//| Type=Trailing                                                    |
//| Name=SparseTable                                                 |
//| Class=CTrailingIteration                                         |
//| Page=                                                            |
//| Parameter=WindowSize,int,20,Lookback window for RMQ              |
//| Parameter=DbName,string,MarketData.sqlite,Database filename      |
//+------------------------------------------------------------------+
// wizard description end

//+------------------------------------------------------------------+
//| Class CTrailingIteration.                                        |
//| Purpose: $O(1)$ Trailing stop using Iterations                   |
//+------------------------------------------------------------------+
class CTrailingIteration : public CExpertTrailing
  {
protected:
   int               m_window_size;    // Trailing lookback window
   CMarketDatabase   m_db;             // Database accessor
   string            m_db_name;
   //

public:
                     CTrailingIteration(void);
                    ~CTrailingIteration(void);

   //--- Setters for Wizard parameters
   void              WindowSize(int size)
     {
      m_window_size = size;
     }
   void              DbName(string name)
     {
      m_db_name = name;
     }

   virtual bool      InitIndicators(CIndicators *indicators);
   virtual bool      CheckTrailingStopLong(CPositionInfo *position, double &sl, double &tp);
   virtual bool      CheckTrailingStopShort(CPositionInfo *position, double &sl, double &tp);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTrailingIteration::CTrailingIteration(void) : m_window_size(20), m_db_name("MarketData.sqlite")
  {
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTrailingIteration::~CTrailingIteration(void)
  {
  }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CTrailingIteration::InitIndicators(CIndicators *indicators)
  {
   if(!CExpertTrailing::InitIndicators(indicators))
      return false;
   return true;
  }

//+------------------------------------------------------------------+
//| Check Long: Uses SQLite data to find Lowest price in window      |
//+------------------------------------------------------------------+
bool CTrailingIteration::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
   MqlRates rates[];
   int size = 3 * m_window_size;
   if(current_bar_time != last_bar_time)
     {
      int count = m_db.GetCleanedRates(m_symbol.CurrencyMargin() + m_symbol.CurrencyProfit(), TimeCurrent() - (PeriodSeconds() * size), size, rates);
     }
   int term_4 = iL(rates, 0, size - (4 * m_window_size) - 1); //ighest(m_symbol.Name(), m_period, MODE_LOW, size - m_window_size - 1, 0);
   int term_3 = iL(rates, 0, size - (3 * m_window_size) - 1); //ighest(m_symbol.Name(), m_period, MODE_LOW, size - m_window_size - 1, 0);
   int term_2 = iL(rates, 0, size - (2 * m_window_size) - 1); //ighest(m_symbol.Name(), m_period, MODE_LOW, size - m_window_size - 1, 0);
   int term_1 = iL(rates, 0, size - (1 * m_window_size) - 1); //ighest(m_symbol.Name(), m_period, MODE_LOW, size - m_window_size - 1, 0);
   int term_0  = iL(rates, 0, size - 1); //ighest(m_symbol.Name(), m_period, MODE_HIGH, size - 1, 0);
   double new_sl = (rates[term_4].low + rates[term_3].low + rates[term_2].low + rates[term_1].low + rates[term_0].low) / 5.0;
   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)
     {
      sl = NormalizeDouble(new_sl, m_symbol.Digits());
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Check Short: Uses SQLite data to find Highest price in window    |
//+------------------------------------------------------------------+
bool CTrailingIteration::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
   MqlRates rates[];
   int size = 5 * m_window_size;
   if(current_bar_time != last_bar_time)
     {
      int count = m_db.GetCleanedRates(m_symbol.CurrencyMargin() + m_symbol.CurrencyProfit(), TimeCurrent() - (PeriodSeconds() * size), size, rates);
     }
   int term_4 = iH(rates, 0, size - (4 * m_window_size) - 1); //ighest(m_symbol.Name(), m_period, MODE_HIGH, size - m_window_size - 1, 0);
   int term_3 = iH(rates, 0, size - (3 * m_window_size) - 1); //ighest(m_symbol.Name(), m_period, MODE_HIGH, size - m_window_size - 1, 0);
   int term_2 = iH(rates, 0, size - (2 * m_window_size) - 1); //ighest(m_symbol.Name(), m_period, MODE_HIGH, size - m_window_size - 1, 0);
   int term_1 = iH(rates, 0, size - (1 * m_window_size) - 1); //ighest(m_symbol.Name(), m_period, MODE_HIGH, size - m_window_size - 1, 0);
   int term_0  = iH(rates, 0, size - 1); //ighest(m_symbol.Name(), m_period, MODE_HIGH, size - 1, 0);
   double new_sl = (rates[term_4].high + rates[term_3].high + rates[term_2].high + rates[term_1].high + rates[term_0].high) / 5.0;
   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)
     {
      sl = NormalizeDouble(new_sl, m_symbol.Digits());
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int iH(MqlRates &R[], int Start, int Stop)
  {
   int h = Start;
   for(int i = Start; i <= Stop; i++)
     {
      if(R[h].high < R[i].high)
        {
         h = i;
        }
     }
   return(h);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int iL(MqlRates &R[], int Start, int Stop)
  {
   int l = Start;
   for(int i = Start; i <= Stop; i++)
     {
      if(R[l].low > R[i].low)
        {
         l = i;
        }
     }
   return(l);
  }
//+------------------------------------------------------------------+
