//+------------------------------------------------------------------+
//|                                         TestHeatmapAnalytics.mq5 |
//+------------------------------------------------------------------+

#include <HeatmapDashboard/HeatmapTypes.mqh>
#include <HeatmapDashboard/HeatmapAggregator.mqh>
#include <HeatmapDashboard/HeatmapChart.mqh>

#define ASSERT(condition, message) TestAssert((condition), (message))

int g_pass_count = 0;
int g_fail_count = 0;

//+------------------------------------------------------------------+
//| TestAssert                                                       |
//| Prints a pass or fail message for a single test condition and    |
//| tracks the running pass and fail counts.                         |
//+------------------------------------------------------------------+
void TestAssert(bool condition, string message)
  {
   if(condition)
     {
      g_pass_count++;
      ::Print("PASS: ", message);
     }
   else
     {
      g_fail_count++;
      ::Print("FAIL: ", message);
     }
  }

//+------------------------------------------------------------------+
//| FindCellIndex                                                    |
//| Searches cells for the entry matching the given symbol and hour, |
//| used only to locate results in this test script.                 |
//+------------------------------------------------------------------+
int FindCellIndex(const CHeatmapCell &cells[], int count, string symbol, int hour)
  {
   for(int i = 0; i < count; i++)
     {
      if(cells[i].symbol == symbol && cells[i].hour == hour)
         return(i);
     }
   return(-1);
  }

//+------------------------------------------------------------------+
//| OnStart                                                          |
//| Builds a synthetic set of 22 deals across four symbol/hour       |
//| combinations, then checks the aggregator's win rate and edge     |
//| ratio output, and the color interpolation formula, against       |
//| hand-worked values.                                              |
//+------------------------------------------------------------------+
void OnStart(void)
  {
//--- build the 22 synthetic deals: EURUSD/8 (10 trades), EURUSD/14 (5 trades),
//--- GBPUSD/8 (4 all-loss trades), GBPUSD/20 (3 all-win trades)
   CDealSample deals[22];
   int idx = 0;
   for(int i = 0; i < 7; i++)
     {
      deals[idx].symbol = "EURUSD";
      deals[idx].hour_utc = 8;
      deals[idx].profit = 100.0;
      idx++;
     }
   for(int i = 0; i < 3; i++)
     {
      deals[idx].symbol = "EURUSD";
      deals[idx].hour_utc = 8;
      deals[idx].profit = -50.0;
      idx++;
     }
   deals[idx].symbol = "EURUSD";
   deals[idx].hour_utc = 14;
   deals[idx].profit = 40.0;
   idx++;
   for(int i = 0; i < 4; i++)
     {
      deals[idx].symbol = "EURUSD";
      deals[idx].hour_utc = 14;
      deals[idx].profit = -60.0;
      idx++;
     }
   double gbp8[4] = {-20.0, -30.0, -10.0, -15.0};
   for(int i = 0; i < 4; i++)
     {
      deals[idx].symbol = "GBPUSD";
      deals[idx].hour_utc = 8;
      deals[idx].profit = gbp8[i];
      idx++;
     }
   double gbp20[3] = {25.0, 35.0, 15.0};
   for(int i = 0; i < 3; i++)
     {
      deals[idx].symbol = "GBPUSD";
      deals[idx].hour_utc = 20;
      deals[idx].profit = gbp20[i];
      idx++;
     }
//--- aggregate the synthetic deals
   CHeatmapAggregator aggregator;
   CHeatmapCell        cells[];
   int cell_count = aggregator.Aggregate(deals, 22, cells);
//--- test 1: exactly four symbol/hour cells should be produced
   ASSERT(cell_count == 4, "four symbol/hour cells are produced from the synthetic deals");
//--- tests 2-5: EURUSD hour 8, a strong high-win-rate, high edge ratio cell
   int i1 = FindCellIndex(cells, cell_count, "EURUSD", 8);
   ASSERT(i1 >= 0, "EURUSD hour 8 cell exists");
   ASSERT(cells[i1].trade_count == 10 && cells[i1].win_count == 7,
          "EURUSD hour 8 has 10 trades and 7 wins");
   ASSERT(::MathAbs(cells[i1].win_rate_percent - 70.0) < 0.001,
          "EURUSD hour 8 win rate computes to 70.0%");
   ASSERT(cells[i1].has_rr && ::MathAbs(cells[i1].avg_rr - 2.0) < 0.001,
          "EURUSD hour 8 edge ratio computes to 2.0");
//--- tests 6-8: EURUSD hour 14, a weak low-win-rate, low edge ratio cell
   int i2 = FindCellIndex(cells, cell_count, "EURUSD", 14);
   ASSERT(i2 >= 0, "EURUSD hour 14 cell exists");
   ASSERT(::MathAbs(cells[i2].win_rate_percent - 20.0) < 0.001,
          "EURUSD hour 14 win rate computes to 20.0%");
   ASSERT(cells[i2].has_rr && ::MathAbs(cells[i2].avg_rr - 0.666667) < 0.001,
          "EURUSD hour 14 edge ratio computes to about 0.6667");
//--- tests 9-10: GBPUSD hour 8, all losses, edge ratio correctly undefined
   int i3 = FindCellIndex(cells, cell_count, "GBPUSD", 8);
   ASSERT(i3 >= 0 && cells[i3].win_count == 0, "GBPUSD hour 8 has zero wins");
   ASSERT(!cells[i3].has_rr, "GBPUSD hour 8 edge ratio is correctly undefined with zero wins");
//--- tests 11-12: GBPUSD hour 20, all wins, edge ratio correctly undefined
   int i4 = FindCellIndex(cells, cell_count, "GBPUSD", 20);
   ASSERT(i4 >= 0 && ::MathAbs(cells[i4].win_rate_percent - 100.0) < 0.001,
          "GBPUSD hour 20 win rate computes to 100.0%");
   ASSERT(!cells[i4].has_rr, "GBPUSD hour 20 edge ratio is correctly undefined with zero losses");
//--- tests 13-14: the color interpolation formula matches its endpoints exactly
   CHeatmapChart chart("TestHeatmapPanel");
   uint c_low  = chart.InterpolateColor(0.0,   0.0, 50.0, 100.0);
   uint c_high = chart.InterpolateColor(100.0, 0.0, 50.0, 100.0);
   int  low_r  = (int)((c_low  >> 16) & 0xFF);
   int  low_g  = (int)((c_low  >> 8)  & 0xFF);
   int  low_b  = (int)(c_low   & 0xFF);
   int  high_r = (int)((c_high >> 16) & 0xFF);
   int  high_g = (int)((c_high >> 8)  & 0xFF);
   int  high_b = (int)(c_high  & 0xFF);
   ASSERT(low_r == 210 && low_g == 60 && low_b == 60,
          "interpolated color at the low end matches the red endpoint exactly");
   ASSERT(high_r == 60 && high_g == 160 && high_b == 90,
          "interpolated color at the high end matches the green endpoint exactly");
//--- print the final summary of pass and fail counts
   ::Print("TestHeatmapAnalytics: ", g_pass_count, " passed, ", g_fail_count, " failed");
  }
//+------------------------------------------------------------------+