//+------------------------------------------------------------------+
//|                                          TradeHistoryReader.mqh  |
//|                     Reads completed trades from account history  |
//+------------------------------------------------------------------+
#ifndef TRADE_HISTORY_READER_MQH
#define TRADE_HISTORY_READER_MQH

#include "RollingPFTypes.mqh"
#include "ITradeSource.mqh"

//+------------------------------------------------------------------+
//| Reads closed trading history and exposes it as trade records     |
//+------------------------------------------------------------------+
class CTradeHistoryReader : public ITradeSource
  {
private:
   STradeRecord      m_trades[];

public:
                     CTradeHistoryReader(void);
                    ~CTradeHistoryReader(void);

   bool              ReadHistory(const datetime from_date, const datetime to_date);
   virtual int       TradeCount(void) const;
   virtual bool      GetTrade(const int index, STradeRecord &trade) const;

private:
   bool              IsClosingDeal(const ulong deal_ticket) const;
   void              SortByCloseTime(void);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTradeHistoryReader::CTradeHistoryReader(void)
  {
   ArrayResize(m_trades, 0);
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTradeHistoryReader::~CTradeHistoryReader(void)
  {
  }

//+------------------------------------------------------------------+
//| IsClosingDeal                                                    |
//| Determine whether a deal closes a position and carries a         |
//| realized result. DEAL_ENTRY_OUT and DEAL_ENTRY_OUT_BY are the    |
//| ordinary closing cases; DEAL_ENTRY_INOUT is included as well,    |
//| since a position reversal also realizes a result on the same     |
//| deal that flips the position's direction.                        |
//+------------------------------------------------------------------+
bool CTradeHistoryReader::IsClosingDeal(const ulong deal_ticket) const
  {
   long entry = HistoryDealGetInteger(deal_ticket, DEAL_ENTRY);
   return(entry == DEAL_ENTRY_OUT || entry == DEAL_ENTRY_OUT_BY || entry == DEAL_ENTRY_INOUT);
  }

//+------------------------------------------------------------------+
//| SortByCloseTime                                                  |
//| Sort collected trades by closing time.                           |
//| The rolling calculation depends critically on chronological      |
//| order, so this guarantees it explicitly rather than relying on   |
//| the order deals happen to be returned in by the history cache.   |
//+------------------------------------------------------------------+
void CTradeHistoryReader::SortByCloseTime(void)
  {
   int total = ArraySize(m_trades);
   for(int i = 1; i < total; i++)
     {
      STradeRecord key = m_trades[i];
      int j = i - 1;
      while(j >= 0 && m_trades[j].close_time > key.close_time)
        {
         m_trades[j + 1] = m_trades[j];
         j--;
        }
      m_trades[j + 1] = key;
     }
  }

//+------------------------------------------------------------------+
//| ReadHistory                                                      |
//| Read completed trades within the given date range                |
//| Parameters: from_date - start of the analysis period             |
//|             to_date   - end of the analysis period               |
//| Returns: true if history was successfully selected               |
//| Assumptions: only BUY and SELL closing deals count as trades     |
//+------------------------------------------------------------------+
bool CTradeHistoryReader::ReadHistory(const datetime from_date, const datetime to_date)
  {
   ArrayResize(m_trades, 0);

   if(!HistorySelect(from_date, to_date))
      return(false);

   int total = HistoryDealsTotal();
   if(total <= 0)
      return(true);

   for(int i = 0; i < total; i++)
     {
      ulong ticket = HistoryDealGetTicket(i);
      if(ticket == 0)
         continue;

      long deal_type = HistoryDealGetInteger(ticket, DEAL_TYPE);
      if(deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL)
         continue;

      if(!IsClosingDeal(ticket))
         continue;

      //--- net profit includes the trade result plus commission and swap
      //--- recorded on this closing deal; fee or correction entries some
      //--- brokers post as separate, non-trading deals are not captured
      double profit      = HistoryDealGetDouble(ticket, DEAL_PROFIT);
      double commission  = HistoryDealGetDouble(ticket, DEAL_COMMISSION);
      double swap_amount = HistoryDealGetDouble(ticket, DEAL_SWAP);
      double net_profit  = profit + commission + swap_amount;

      datetime close_time = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);

      int size = ArraySize(m_trades);
      ArrayResize(m_trades, size + 1);
      m_trades[size].close_time = close_time;
      m_trades[size].net_profit = net_profit;
     }

   SortByCloseTime();

   return(true);
  }

//+------------------------------------------------------------------+
//| Number of completed trades currently held by the reader          |
//+------------------------------------------------------------------+
int CTradeHistoryReader::TradeCount(void) const
  {
   return(ArraySize(m_trades));
  }

//+------------------------------------------------------------------+
//| Retrieve a trade by index                                        |
//| Returns: false if the index is out of range                      |
//+------------------------------------------------------------------+
bool CTradeHistoryReader::GetTrade(const int index, STradeRecord &trade) const
  {
   if(index < 0 || index >= ArraySize(m_trades))
      return(false);

   trade = m_trades[index];
   return(true);
  }

#endif // TRADE_HISTORY_READER_MQH
//+------------------------------------------------------------------+