//+------------------------------------------------------------------+
//|                                                  TradeLoader.mqh |
//+------------------------------------------------------------------+
#ifndef TRADE_LOADER_MQH
#define TRADE_LOADER_MQH

#include "TradeRecord.mqh"

//+------------------------------------------------------------------+
//| Class responsible for reconstructing closed trades from history  |
//+------------------------------------------------------------------+
class CTradeLoader
  {
private:
   string            m_symbol;
   long              m_magic;
   datetime          m_from_time;
   datetime          m_to_time;

   double            GetDealSL(ulong deal_ticket);
   double            GetDealTP(ulong deal_ticket);

public:
                     CTradeLoader(const string symbol, long magic, datetime from_time, datetime to_time);
                    ~CTradeLoader(void);

   int               Load(CTradeRecord &trades[]);
  };

//+------------------------------------------------------------------+
//| Constructor: stores the filter parameters for this load session  |
//+------------------------------------------------------------------+
CTradeLoader::CTradeLoader(const string symbol, long magic, datetime from_time, datetime to_time)
  {
   m_symbol    = symbol;
   m_magic     = magic;
   m_from_time = from_time;
   m_to_time   = to_time;
  }

//+------------------------------------------------------------------+
//| Destructor: no dynamic resources to release                      |
//+------------------------------------------------------------------+
CTradeLoader::~CTradeLoader(void)
  {
  }

//+------------------------------------------------------------------+
//| Reads DEAL_SL from the deal record, falling back to the order    |
//+------------------------------------------------------------------+
double CTradeLoader::GetDealSL(ulong deal_ticket)
  {
   double sl = HistoryDealGetDouble(deal_ticket, DEAL_SL);
   if(sl != 0.0)
      return(sl);

   ulong order_ticket = (ulong)HistoryDealGetInteger(deal_ticket, DEAL_ORDER);
   if(order_ticket == 0)
      return(0.0);

   return(HistoryOrderGetDouble(order_ticket, ORDER_SL));
  }

//+------------------------------------------------------------------+
//| Reads DEAL_TP from the deal record, falling back to the order    |
//+------------------------------------------------------------------+
double CTradeLoader::GetDealTP(ulong deal_ticket)
  {
   double tp = HistoryDealGetDouble(deal_ticket, DEAL_TP);
   if(tp != 0.0)
      return(tp);

   ulong order_ticket = (ulong)HistoryDealGetInteger(deal_ticket, DEAL_ORDER);
   if(order_ticket == 0)
      return(0.0);

   return(HistoryOrderGetDouble(order_ticket, ORDER_TP));
  }

//+------------------------------------------------------------------+
//| Loads and reconstructs all closed trades matching the filters    |
//+------------------------------------------------------------------+
int CTradeLoader::Load(CTradeRecord &trades[])
  {
   ArrayResize(trades, 0);

   if(!HistorySelect(m_from_time, m_to_time))
      return(0);

   int total_deals = HistoryDealsTotal();
   ulong entry_tickets[];
   ulong entry_position_ids[];
   ArrayResize(entry_tickets, 0);
   ArrayResize(entry_position_ids, 0);

//--- first pass: collect entry deals keyed by position id
   for(int i = 0; i < total_deals; i++)
     {
      ulong ticket = HistoryDealGetTicket(i);
      if(ticket == 0)
         continue;

      long entry_type = HistoryDealGetInteger(ticket, DEAL_ENTRY);
      if(entry_type != DEAL_ENTRY_IN)
         continue;

      string deal_symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
      long   deal_magic   = HistoryDealGetInteger(ticket, DEAL_MAGIC);

      if(m_symbol != "" && deal_symbol != m_symbol)
         continue;
      if(m_magic != 0 && deal_magic != m_magic)
         continue;

      int size = ArraySize(entry_tickets);
      ArrayResize(entry_tickets, size + 1);
      ArrayResize(entry_position_ids, size + 1);
      entry_tickets[size]      = ticket;
      entry_position_ids[size] = HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
     }

//--- second pass: find matching exit deal for each entry
   int count = 0;
   for(int i = 0; i < total_deals; i++)
     {
      ulong exit_ticket = HistoryDealGetTicket(i);
      if(exit_ticket == 0)
         continue;

      long exit_entry_type = HistoryDealGetInteger(exit_ticket, DEAL_ENTRY);
      if(exit_entry_type != DEAL_ENTRY_OUT)
         continue;

      long exit_position_id = HistoryDealGetInteger(exit_ticket, DEAL_POSITION_ID);

      int match_index = -1;
      for(int j = 0; j < ArraySize(entry_position_ids); j++)
        {
         if(entry_position_ids[j] == exit_position_id)
           {
            match_index = j;
            break;
           }
        }

      if(match_index == -1)
         continue;

      ulong entry_ticket = entry_tickets[match_index];

      CTradeRecord record;
      record.position_id  = exit_position_id;
      record.symbol       = HistoryDealGetString(entry_ticket, DEAL_SYMBOL);
      record.direction    = (HistoryDealGetInteger(entry_ticket, DEAL_TYPE) == DEAL_TYPE_BUY) ? 0 : 1;
      record.volume       = HistoryDealGetDouble(entry_ticket, DEAL_VOLUME);
      record.open_time    = (datetime)HistoryDealGetInteger(entry_ticket, DEAL_TIME);
      record.close_time   = (datetime)HistoryDealGetInteger(exit_ticket, DEAL_TIME);
      record.open_price   = HistoryDealGetDouble(entry_ticket, DEAL_PRICE);
      record.close_price  = HistoryDealGetDouble(exit_ticket, DEAL_PRICE);
      record.stop_loss    = GetDealSL(entry_ticket);
      record.take_profit  = GetDealTP(entry_ticket);
      record.profit       = HistoryDealGetDouble(exit_ticket, DEAL_PROFIT);
      record.commission   = HistoryDealGetDouble(exit_ticket, DEAL_COMMISSION);
      record.swap         = HistoryDealGetDouble(exit_ticket, DEAL_SWAP);
      record.magic        = HistoryDealGetInteger(entry_ticket, DEAL_MAGIC);
      record.comment       = HistoryDealGetString(exit_ticket, DEAL_COMMENT);

      ArrayResize(trades, count + 1);
      trades[count] = record;
      count++;
     }

   return(count);
  }

#endif // TRADE_LOADER_MQH
//+------------------------------------------------------------------+