//+------------------------------------------------------------------+
//|                                           ReturnSeriesReader.mqh |
//+------------------------------------------------------------------+
#ifndef RETURNSERIESREADER_MQH
#define RETURNSERIESREADER_MQH

#include "CorrelationTypes.mqh"

//+---------------------------------------------------------------------+
//| CReturnSeriesReader                                                 |
//| Builds one symbol's rolling return series from closed bars. As in   |
//| the rest of this project, reading bar data and computing returns    |
//| from it are kept in separate methods: ReadCloses() is the only      |
//| method here that touches the terminal, while ComputeReturns() is a  |
//| pure function that can be exercised directly with synthetic prices  |
//| in a test.                                                          |
//+---------------------------------------------------------------------+
class CReturnSeriesReader
  {
public:
                     CReturnSeriesReader(void);
                    ~CReturnSeriesReader(void);
   bool              ReadCloses(const string symbol, ENUM_TIMEFRAMES timeframe, int closes_needed, double &closes_out[]) const;
   bool              ComputeReturns(const double &closes[], int count, double &returns_out[]) const;
   bool              BuildSeries(const string symbol, ENUM_TIMEFRAMES timeframe, int bar_count, CSymbolReturnSeries &series_out) const;
  };

//+-------------------------------------------------------------------+
//| Constructor                                                       |
//| The reader holds no state between calls, so construction performs |
//| no work beyond default object creation.                           |
//+-------------------------------------------------------------------+
CReturnSeriesReader::CReturnSeriesReader(void)
  {
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//| No resources are owned by this class, so no cleanup is required. |
//+------------------------------------------------------------------+
CReturnSeriesReader::~CReturnSeriesReader(void)
  {
  }

//+------------------------------------------------------------------------+
//| ReadCloses                                                             |
//| Copies closes_needed closed bars for one symbol, starting one bar      |
//| back from the current one. Starting at shift 1 rather than shift 0     |
//| deliberately skips the bar that is still forming: including it would   |
//| let the newest correlation figure shift on every tick rather than only |
//| on every new bar, which is not what a rolling, bar-based correlation   |
//| monitor is meant to do. Returns false when fewer bars than requested   |
//| are available, which happens for a symbol with a short history or a    |
//| newly added chart.                                                     |
//+------------------------------------------------------------------------+
bool CReturnSeriesReader::ReadCloses(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                     const int closes_needed,double &closes_out[]) const
  {
   int copied = ::CopyClose(symbol, timeframe, 1, closes_needed, closes_out);
   if(copied < closes_needed)
      return(false);
//--- CopyClose() always fills the array oldest first; ArraySetAsSeries() makes
//--- index 0 refer to the newest copied bar, matching what the rest of this
//--- class assumes.
   ::ArraySetAsSeries(closes_out, true);
   return(true);
  }

//+------------------------------------------------------------------------+
//| ComputeReturns                                                         |
//| Converts a series of closes into bar-to-bar percentage returns. The    |
//| closes array follows the terminal's own series convention, index 0 is  |
//| the most recent bar and later indices go further back in time, so      |
//| returns_out[0] is the most recent return and is computed from the two  |
//| newest closes. Requires at least two closes to produce one return, and |
//| guards against a zero-priced bar in the denominator, which would       |
//| otherwise divide by zero on corrupted or placeholder data.             |
//+------------------------------------------------------------------------+
bool CReturnSeriesReader::ComputeReturns(const double &closes[],const int count,double &returns_out[]) const
  {
   if(count < 2)
      return(false);
   ::ArrayResize(returns_out, count - 1);
   for(int i = 0; i < count - 1; i++)
     {
      if(closes[i + 1] == 0.0)
         return(false);
      returns_out[i] = (closes[i] - closes[i + 1]) / closes[i + 1];
     }
   return(true);
  }

//+------------------------------------------------------------------------+
//| BuildSeries                                                            |
//| Orchestrates ReadCloses() and ComputeReturns() for one symbol,         |
//| requesting bar_count + 1 closes so that exactly bar_count returns can  |
//| be computed from them.                                                 |
//+------------------------------------------------------------------------+
bool CReturnSeriesReader::BuildSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,
                                      const int bar_count,CSymbolReturnSeries &series_out) const
  {
   double closes[];
   if(!ReadCloses(symbol, timeframe, bar_count + 1, closes))
      return(false);

   double returns[];
   if(!ComputeReturns(closes, ::ArraySize(closes), returns))
      return(false);

   series_out.m_symbol = symbol;
   ::ArrayCopy(series_out.m_returns, returns);
   series_out.m_count = ::ArraySize(returns);
   return(true);
  }

#endif // RETURNSERIESREADER_MQH
//+------------------------------------------------------------------+
