//+------------------------------------------------------------------+
//|                                              DailyAggregator.mqh |
//+------------------------------------------------------------------+
#ifndef DAILYAGGREGATOR_MQH
#define DAILYAGGREGATOR_MQH

#include "CalendarTypes.mqh"

//+------------------------------------------------------------------+
//| CDailyAggregator                                                 |
//| Groups deal samples by calendar date and reduces each group to a |
//| single CDailyRecord holding daily P&L and trade count. Grouping  |
//| requires a stable date-only key, so every deal's close time is   |
//| first normalized to midnight on the same calendar date. The      |
//| normalization helper is exposed as its own pure method so it can |
//| be unit tested independently of the grouping logic that consumes |
//| it.                                                              |
//+------------------------------------------------------------------+
class CDailyAggregator
  {
private:
   int               FindDateIndex(const CDailyRecord &days[], int count, datetime normalized_date) const;

public:
                     CDailyAggregator(void);
                    ~CDailyAggregator(void);
   datetime          NormalizeToMidnight(datetime timestamp) const;
   bool              Aggregate(const CDealSample &deals[], int count, CDailyRecord &days_out[]);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//| The aggregator holds no state between calls, so construction     |
//| performs no work beyond default object creation.                 |
//+------------------------------------------------------------------+
CDailyAggregator::CDailyAggregator(void)
  {
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//| No resources are owned by this class, so no cleanup is required. |
//+------------------------------------------------------------------+
CDailyAggregator::~CDailyAggregator(void)
  {
  }

//+------------------------------------------------------------------+
//| NormalizeToMidnight                                              |
//| Removes the hour, minute, and second components from a datetime, |
//| producing a stable date-only key. Two timestamps that fall on the|
//| same calendar date always normalize to an identical result, which|
//| is exactly the property the grouping logic in Aggregate depends  |
//| on. This method is pure: it reads its input and returns a value  |
//| without touching any array or member state, which is what makes  |
//| it straightforward to test in isolation.                         |
//+------------------------------------------------------------------+
datetime CDailyAggregator::NormalizeToMidnight(datetime timestamp) const
  {
//--- decompose the timestamp into its calendar and clock components
   MqlDateTime time_parts;
   ::TimeToStruct(timestamp, time_parts);
//--- discard the time-of-day components to obtain a date-only key
   time_parts.hour = 0;
   time_parts.min  = 0;
   time_parts.sec  = 0;
//--- reassemble a datetime representing exactly midnight on that date
   return(::StructToTime(time_parts));
  }

//+------------------------------------------------------------------+
//| FindDateIndex                                                    |
//| Performs a linear search for a normalized date inside a partially|
//| built result array. A linear search is adequate here because the |
//| number of distinct calendar dates in a typical analysis range is |
//| small (at most a few hundred for a multi-year history), and the  |
//| method only ever reads the array, never writes to it.            |
//+------------------------------------------------------------------+
int CDailyAggregator::FindDateIndex(const CDailyRecord &days[],const int count,const datetime normalized_date) const
  {
   for(int i = 0; i < count; i++)
     {
      if(days[i].m_date == normalized_date)
         return(i);
     }
   return(-1);
  }

//+-------------------------------------------------------------------+
//| Aggregate                                                         |
//| Normalizes every deal's close time, groups deals that share a     |
//| normalized date, and produces one CDailyRecord per distinct date. |
//| Daily P&L is the sum of net profit contributions from every deal  |
//| on that date; trade count is the number of deals on that date.    |
//| Multiple deals on the same date are folded into the same record,  |
//| including the case where the deals exactly offset one another and |
//| the resulting daily P&L is precisely zero: the record still exists|
//| and still carries the correct trade count, so a break-even day is |
//| never confused with a day that had no trading activity at all.    |
//| Because this method resizes and writes into days_out, it cannot be|
//| declared const: MQL5 treats a reference array parameter as        |
//| effectively read-only when the enclosing method is const, even if |
//| the parameter itself carries no const qualifier.                  |
//+-------------------------------------------------------------------+
bool CDailyAggregator::Aggregate(const CDealSample &deals[],const int count,CDailyRecord &days_out[])
  {
//--- start from an empty result set regardless of any prior contents
   ::ArrayResize(days_out, 0);
//--- fold every sampled deal into its matching daily record
   for(int i = 0; i < count; i++)
     {
      datetime normalized_date = NormalizeToMidnight(deals[i].m_close_time);
      int existing_index = FindDateIndex(days_out, ::ArraySize(days_out), normalized_date);
      if(existing_index >= 0)
        {
         //--- accumulate into the calendar date already present in the result
         days_out[existing_index].m_daily_pnl   += deals[i].m_net_profit;
         days_out[existing_index].m_trade_count += 1;
        }
      else
        {
         //--- create a new daily record the first time this date is encountered
         int new_index = ::ArraySize(days_out);
         ::ArrayResize(days_out, new_index + 1);
         days_out[new_index].m_date        = normalized_date;
         days_out[new_index].m_daily_pnl   = deals[i].m_net_profit;
         days_out[new_index].m_trade_count = 1;
        }
     }
   return(true);
  }

#endif // DAILYAGGREGATOR_MQH
//+------------------------------------------------------------------+