//+------------------------------------------------------------------+
//|                                                  TradeLoader.mqh |
//+------------------------------------------------------------------+
#ifndef TRADELOADER_MQH
#define TRADELOADER_MQH

#include "TradeRecord.mqh"

//+------------------------------------------------------------------+
//| Loads and reconstructs closed trades from terminal history       |
//+------------------------------------------------------------------+
class CTradeLoader
  {
private:
   CTradeRecord      m_trades[];
   string            m_symbol;
   long              m_magic;

   int               FindOrAdd(ulong position_id);
   void              InsertionSortSwap(int i, int j);

public:
                     CTradeLoader(void);
                    ~CTradeLoader(void);

   int               Load(const string &symbol, long magic, datetime from_time, datetime to_time);
   bool              GetTrade(int idx, CTradeRecord &out_trade) const;
   int               GetCount(void) const;
   void              SortByEntryTime(void);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTradeLoader::CTradeLoader(void)
  {
   m_symbol = "";
   m_magic  = 0;
   ArrayResize(m_trades, 0);
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTradeLoader::~CTradeLoader(void)
  {
   ArrayFree(m_trades);
  }

//+------------------------------------------------------------------+
//| Locate the array slot for a position id, or append a new one     |
//+------------------------------------------------------------------+
int CTradeLoader::FindOrAdd(ulong position_id)
  {
   int total = ArraySize(m_trades);

   for(int i = 0; i < total; i++)
     {
      if(m_trades[i].position_id == position_id)
         return(i);
     }

   ArrayResize(m_trades, total + 1);
   m_trades[total].position_id = position_id;
   m_trades[total].profit      = 0.0;
   m_trades[total].comment     = "";
   m_trades[total].entry_time  = 0;
   m_trades[total].exit_time   = 0;

   return(total);
  }

//+------------------------------------------------------------------+
//| Load closed trade history and reconstruct trades from deals      |
//+------------------------------------------------------------------+
int CTradeLoader::Load(const string &symbol, long magic, datetime from_time, datetime to_time)
  {
   m_symbol = symbol;
   m_magic  = magic;
   ArrayResize(m_trades, 0);

   if(!::HistorySelect(from_time, to_time))
     {
      ::Print("CTradeLoader::Load - HistorySelect failed, error ", ::GetLastError());
      return(0);
     }

   int deals_total = ::HistoryDealsTotal();

   for(int i = 0; i < deals_total; i++)
     {
      ulong ticket = ::HistoryDealGetTicket(i);
      if(ticket == 0)
         continue;

      string deal_symbol = ::HistoryDealGetString(ticket, DEAL_SYMBOL);
      if(symbol != "" && deal_symbol != symbol)
         continue;

      long deal_magic = ::HistoryDealGetInteger(ticket, DEAL_MAGIC);
      if(magic != 0 && deal_magic != magic)
         continue;

      ulong position_id = (ulong)::HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
      long entry_type    = ::HistoryDealGetInteger(ticket, DEAL_ENTRY);
      int slot           = FindOrAdd(position_id);

      if(entry_type == DEAL_ENTRY_IN)
        {
         m_trades[slot].symbol      = deal_symbol;
         m_trades[slot].order_type  = (::HistoryDealGetInteger(ticket, DEAL_TYPE) == DEAL_TYPE_BUY)
                                      ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
         m_trades[slot].volume      = ::HistoryDealGetDouble(ticket, DEAL_VOLUME);
         m_trades[slot].entry_time  = (datetime)::HistoryDealGetInteger(ticket, DEAL_TIME);
         m_trades[slot].entry_price = ::HistoryDealGetDouble(ticket, DEAL_PRICE);
         m_trades[slot].magic       = deal_magic;
         m_trades[slot].comment     = ::HistoryDealGetString(ticket, DEAL_COMMENT);

         //--- read SL/TP from the deal record first
         double deal_sl = ::HistoryDealGetDouble(ticket, DEAL_SL);
         double deal_tp = ::HistoryDealGetDouble(ticket, DEAL_TP);

         //--- many brokers do not populate DEAL_SL/DEAL_TP on the deal record;
         //--- fall back to the order record that generated this deal
         if(deal_sl == 0.0 || deal_tp == 0.0)
           {
            ulong order_ticket = (ulong)::HistoryDealGetInteger(ticket, DEAL_ORDER);
            if(order_ticket > 0)
              {
               if(deal_sl == 0.0)
                  deal_sl = ::HistoryOrderGetDouble(order_ticket, ORDER_SL);
               if(deal_tp == 0.0)
                  deal_tp = ::HistoryOrderGetDouble(order_ticket, ORDER_TP);
              }
           }

         m_trades[slot].stop_loss   = deal_sl;
         m_trades[slot].take_profit = deal_tp;
        }
      else
         if(entry_type == DEAL_ENTRY_OUT)
           {
            datetime out_time = (datetime)::HistoryDealGetInteger(ticket, DEAL_TIME);

            if(out_time >= m_trades[slot].exit_time)
              {
               m_trades[slot].exit_time  = out_time;
               m_trades[slot].exit_price = ::HistoryDealGetDouble(ticket, DEAL_PRICE);
              }

            m_trades[slot].profit += ::HistoryDealGetDouble(ticket, DEAL_PROFIT);
           }
     }

//--- discard incomplete records: missing entry or missing exit
   int write_idx = 0;
   int total     = ArraySize(m_trades);

   for(int read_idx = 0; read_idx < total; read_idx++)
     {
      bool has_entry = (m_trades[read_idx].entry_time != 0);
      bool has_exit  = (m_trades[read_idx].exit_time  != 0);

      if(has_entry && has_exit)
        {
         if(write_idx != read_idx)
            m_trades[write_idx] = m_trades[read_idx];
         write_idx++;
        }
      else
        {
         ::Print("CTradeLoader::Load - skipping incomplete position ", m_trades[read_idx].position_id);
        }
     }

   ArrayResize(m_trades, write_idx);

   return(ArraySize(m_trades));
  }

//+------------------------------------------------------------------+
//| Copy out a trade record by index                                 |
//+------------------------------------------------------------------+
bool CTradeLoader::GetTrade(int idx, CTradeRecord &out_trade) const
  {
   if(idx < 0 || idx >= ArraySize(m_trades))
      return(false);

   out_trade = m_trades[idx];
   return(true);
  }

//+------------------------------------------------------------------+
//| Return the number of reconstructed trades                        |
//+------------------------------------------------------------------+
int CTradeLoader::GetCount(void) const
  {
   return(ArraySize(m_trades));
  }

//+------------------------------------------------------------------+
//| Swap two elements of the trades array                            |
//+------------------------------------------------------------------+
void CTradeLoader::InsertionSortSwap(int i, int j)
  {
   CTradeRecord temp = m_trades[i];
   m_trades[i] = m_trades[j];
   m_trades[j] = temp;
  }

//+------------------------------------------------------------------+
//| Sort trades by entry_time ascending using insertion sort         |
//+------------------------------------------------------------------+
void CTradeLoader::SortByEntryTime(void)
  {
   int total = ArraySize(m_trades);

   for(int i = 1; i < total; i++)
     {
      int j = i;
      while(j > 0 && m_trades[j - 1].entry_time > m_trades[j].entry_time)
        {
         InsertionSortSwap(j - 1, j);
         j--;
        }
     }
  }

#endif // TRADELOADER_MQH
//+------------------------------------------------------------------+