//+------------------------------------------------------------------+
//|                                        TestDrawdownAnalytics.mq5 |
//+------------------------------------------------------------------+

#include <DrawdownDashboard/DrawdownTypes.mqh>
#include <DrawdownDashboard/DrawdownAnalyzer.mqh>
#include <DrawdownDashboard/DrawdownStatsCalculator.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);
     }
  }

//+------------------------------------------------------------------+
//| DayOffset                                                        |
//| Converts a plain day number into a datetime, used to build a     |
//| fixed synthetic equity curve for the tests below.                |
//+------------------------------------------------------------------+
datetime DayOffset(int day)
  {
   return((datetime)(day * 86400));
  }

//+------------------------------------------------------------------+
//| OnStart                                                          |
//| Builds a synthetic equity curve with a deep short drawdown, a    |
//| shallow long drawdown, and a still-open drawdown, then checks    |
//| the analyzer and stats calculator against hand-worked values.    |
//+------------------------------------------------------------------+
void OnStart(void)
  {
//--- build the synthetic curve: day offset and equity value pairs
   int    d[12] = {0, 3, 8, 10, 20, 30, 40, 50, 57, 60, 65, 70};
   double e[12] = {10000.0, 8500.0, 10050.0, 10050.0, 9246.0, 9500.0,
                   9800.0, 9950.0, 10050.0, 10500.0, 10100.0, 10200.0
                  };
   CEquityPoint curve[12];
   for(int i = 0; i < 12; i++)
     {
      curve[i].time   = DayOffset(d[i]);
      curve[i].equity = e[i];
     }
//--- run the analyzer against the synthetic curve
   CDrawdownAnalyzer analyzer;
   CDrawdownEpisode  episodes[];
   int count = analyzer.Analyze(curve, 12, episodes);
//--- test 1: exactly three episodes should be found
   ASSERT(count == 3, "three drawdown episodes are identified from the synthetic curve");
//--- test 2 and 3: the deep short episode is 15.0% deep over 8 days
   ASSERT(::MathAbs(episodes[0].depth_percent - 15.0) < 0.001,
          "episode 1 depth computes to 15.0%");
   ASSERT(::MathAbs(episodes[0].duration_days - 8.0) < 0.001,
          "episode 1 duration computes to 8.0 days");
//--- test 4 and 5: the shallow long episode is 8.0% deep over 47 days
   ASSERT(::MathAbs(episodes[1].depth_percent - 8.0) < 0.001,
          "episode 2 depth computes to 8.0%");
   ASSERT(::MathAbs(episodes[1].duration_days - 47.0) < 0.001,
          "episode 2 duration computes to 47.0 days");
//--- test 6 and 7: the still-open episode is flagged open with a 10 day elapsed duration
   ASSERT(episodes[2].is_open == true,
          "episode 3 is correctly flagged as still open");
   ASSERT(::MathAbs(episodes[2].duration_days - 10.0) < 0.001,
          "episode 3 elapsed duration computes to 10.0 days");
//--- run the stats calculator against the same episodes
   CDrawdownStatsCalculator stats;
   double max_depth        = 0.0;
   double longest_duration = 0.0;
   double avg_recovery     = 0.0;
   int    open_count       = 0;
   stats.ComputeSummary(episodes, count, max_depth, longest_duration, avg_recovery, open_count);
//--- test 8, 9, 10, 11: summary statistics match the hand-worked values
   ASSERT(::MathAbs(max_depth - 15.0) < 0.001,
          "max depth across all episodes computes to 15.0%");
   ASSERT(::MathAbs(longest_duration - 47.0) < 0.001,
          "longest duration across all episodes computes to 47.0 days");
   ASSERT(::MathAbs(avg_recovery - 27.5) < 0.001,
          "average recovery time across closed episodes computes to 27.5 days");
   ASSERT(open_count == 1,
          "exactly one episode is still open");
//--- print the final summary of pass and fail counts
   ::Print("TestDrawdownAnalytics: ", g_pass_count, " passed, ", g_fail_count, " failed");
  }
//+------------------------------------------------------------------+