//+------------------------------------------------------------------+
//|                                      LotCalculationTests.mqh     |
//| CLotCalculationTests: validates CalcLotSize(), NormalizeLot(),   |
//| and RoundLotToStep() with deterministic input/output pairs.      |
//|                                                                  |
//| Test 13 (TestLotRounding_Defect) contains a deliberately         |
//| injected error in the expected value to demonstrate that the     |
//| framework detects exactly one failure with line attribution.     |
//+------------------------------------------------------------------+
#ifndef LOTCALCULATIONTESTS_MQH
#define LOTCALCULATIONTESTS_MQH

#include "ITestSuite.mqh"
#include "TestRunner.mqh"
#include "MathUtilities.mqh"

//+------------------------------------------------------------------+
//| Class CLotCalculationTests                                       |
//| Purpose: Test suite specialized in validating risk algorithms,   |
//|          clamping logic boundaries, and volume step adjustments. |
//+------------------------------------------------------------------+
class CLotCalculationTests : public ITestSuite
  {
public:
   //--- Returns the custom descriptive identifier of this suite
   virtual string    GetName(void) { return("CLotCalculationTests"); }

   //--- Executes all internal assertion vectors sequentially
   virtual void      Execute(CTestRunner *runner);
  };

//+------------------------------------------------------------------+
//| Execute                                                          |
//| Purpose: Houses and processes all structural regression tests.   |
//+------------------------------------------------------------------+
void CLotCalculationTests::Execute(CTestRunner *runner)
  {
   string sn = GetName();

//--- Test 1: Standard lot normalization, half-up rounding
     {
      double result = NormalizeLot(0.1049, 0.01, 0.01, 100.0);
      ASSERT_NEAR(runner, sn, "NormalizeLot_StandardRound", 0.10, result, 0.0001);
     }

//--- Test 2: Rounding up at midpoint
     {
      double result = NormalizeLot(0.1050, 0.01, 0.01, 100.0);
      ASSERT_NEAR(runner, sn, "NormalizeLot_MidpointUp", 0.11, result, 0.0001);
     }

//--- Test 3: Clamp to minimum lot
     {
      double result = NormalizeLot(0.001, 0.01, 0.01, 100.0);
      ASSERT_NEAR(runner, sn, "NormalizeLot_ClampMin", 0.01, result, 0.0001);
     }

//--- Test 4: Clamp to maximum lot
     {
      double result = NormalizeLot(150.0, 0.01, 0.01, 100.0);
      ASSERT_NEAR(runner, sn, "NormalizeLot_ClampMax", 100.0, result, 0.0001);
     }

//--- Test 5: CalcLotSize standard case
//--- equity=10000, risk=1%, sl=50pts, point_value=10.0
//--- expected: (10000*0.01)/(50*10.0) = 100/500 = 0.20
     {
      bool   err    = false;
      double result = CalcLotSize(10000.0, 0.01, 50.0, 10.0, 0.01, 0.01, 100.0, err);
      ASSERT_FALSE(runner, sn, "CalcLotSize_NoError", err);
      ASSERT_NEAR(runner, sn, "CalcLotSize_Standard", 0.20, result, 0.0001);
     }

//--- Test 6: CalcLotSize with zero equity triggers error
     {
      bool   err    = false;
      double result = CalcLotSize(0.0, 0.01, 50.0, 10.0, 0.01, 0.01, 100.0, err);
      ASSERT_THROWS(runner, sn, "CalcLotSize_ZeroEquity_Throws", err);
      ASSERT_NEAR(runner, sn, "CalcLotSize_ZeroEquity_Returns0", 0.0, result, 0.0001);
     }

//--- Test 7: CalcLotSize with zero stop-loss triggers error
     {
      bool   err    = false;
      double result = CalcLotSize(10000.0, 0.01, 0.0, 10.0, 0.01, 0.01, 100.0, err);
      ASSERT_THROWS(runner, sn, "CalcLotSize_ZeroSL_Throws", err);
     }

//--- Test 8: CalcLotSize small account risk
//--- equity=1000, risk=0.5%, sl=20pts, point_value=10.0
//--- expected: (1000*0.005)/(20*10.0) = 5/200 = 0.025 -> rounds to 0.03
     {
      bool   err    = false;
      double result = CalcLotSize(1000.0, 0.005, 20.0, 10.0, 0.01, 0.01, 100.0, err);
      ASSERT_NEAR(runner, sn, "CalcLotSize_SmallAccount", 0.03, result, 0.0001);
     }

//--- Test 9: RoundLotToStep floor rounding
     {
      double result = RoundLotToStep(0.1099, 0.01);
      ASSERT_NEAR(runner, sn, "RoundLotToStep_Floor", 0.10, result, 0.0001);
     }

//--- Test 10: RoundLotToStep exact step
     {
      double result = RoundLotToStep(0.12, 0.01);
      ASSERT_NEAR(runner, sn, "RoundLotToStep_Exact", 0.12, result, 0.0001);
     }

//--- Test 11: RoundLotToStep at lower boundary of next step
     {
      double result = RoundLotToStep(0.1200, 0.01);
      ASSERT_NEAR(runner, sn, "RoundLotToStep_LowerBoundary", 0.12, result, 0.0001);
     }

//--- Test 12: NormalizeLot with non-standard lot step (0.1)
     {
      double result = NormalizeLot(0.74, 0.10, 0.10, 10.0);
      ASSERT_NEAR(runner, sn, "NormalizeLot_LargeStep", 0.70, result, 0.0001);
     }

//--- Test 13: DELIBERATE DEFECT UNIT
//--- RoundLotToStep(0.125, 0.01) should return 0.12 (floor truncation)
//--- The expected baseline metric below is intentionally forced to 0.13 to
//--- verify the testing framework catches failures with exact line mapping.
     {
      double result = RoundLotToStep(0.125, 0.01);
      ASSERT_NEAR(runner, sn, "TestLotRounding_Defect", 0.13, result, 0.0001);
     }
  }

#endif // LOTCALCULATIONTESTS_MQH
//+------------------------------------------------------------------+
