//+------------------------------------------------------------------+
//| 74_Reinit_Consistency_Auditor_v1_00.mq5                        |
//| Compares incremental calculation with a full-history reference. |
//+------------------------------------------------------------------+
#property copyright "FXおもしろラボ"
#property version   "1.00"
#property strict
#property description "Audits whether incremental and full recalculation produce identical closed-bar values."

#property indicator_separate_window
#property indicator_minimum -3.0
#property indicator_maximum  3.0
#property indicator_buffers 12
#property indicator_plots   10
#property indicator_level1  0.0
#property indicator_levelcolor clrDimGray
#property indicator_levelstyle STYLE_DOT

#property indicator_label1 "Full-history reference"
#property indicator_type1  DRAW_LINE
#property indicator_color1 clrGold
#property indicator_width1 4

#property indicator_label2 "Incremental candidate"
#property indicator_type2  DRAW_LINE
#property indicator_color2 clrDeepSkyBlue
#property indicator_width2 2

#property indicator_label3 "Mismatch"
#property indicator_type3  DRAW_ARROW
#property indicator_color3 clrRed
#property indicator_width3 2

#property indicator_label4 "Mismatch count"
#property indicator_type4  DRAW_NONE
#property indicator_label5 "Compared closed bars"
#property indicator_type5  DRAW_NONE
#property indicator_label6 "Candidate bars this call"
#property indicator_type6  DRAW_NONE
#property indicator_label7 "Reference bars this call"
#property indicator_type7  DRAW_NONE
#property indicator_label8 "Incremental calls"
#property indicator_type8  DRAW_NONE
#property indicator_label9 "Reset calls"
#property indicator_type9  DRAW_NONE
#property indicator_label10 "Maximum absolute error"
#property indicator_type10 DRAW_NONE

enum ENUM_AUDIT_RECALC_MODE
{
   AUDIT_INCREMENTAL = 0,    // Recalculate only the dependent tail
   AUDIT_FULL_EACH_CALL = 1 // Rebuild the candidate on every call
};

input int InpLookback = 20;                         // Rolling high/low window
input double InpSmoothing = 0.33;                  // Recursive smoothing, (0,1]
input int InpAuditBars = 500;                       // Closed bars to compare
input double InpTolerance = 0.0000000001;           // Absolute error tolerance
input ENUM_AUDIT_RECALC_MODE InpMode = AUDIT_INCREMENTAL;

double g_full_reference[];
double g_candidate[];
double g_mismatch_marker[];
double g_mismatch_count[];
double g_compared_count[];
double g_candidate_bars[];
double g_reference_bars[];
double g_incremental_calls_buffer[];
double g_reset_calls_buffer[];
double g_max_error_buffer[];
double g_candidate_state[];
double g_reference_state[];

long g_incremental_calls = 0;
long g_reset_calls = 0;

bool FiniteValue(const double value)
{
   return value != EMPTY_VALUE && MathIsValidNumber(value);
}

double ClampState(const double value)
{
   return MathMax(-0.999, MathMin(0.999, value));
}

bool RawPosition(const int index,
                 const double &high[],
                 const double &low[],
                 const double &close[],
                 double &result)
{
   const int first_valid = InpLookback - 1;
   if(index < first_valid || index >= ArraySize(close) ||
      index >= ArraySize(high) || index >= ArraySize(low))
      return false;

   double highest = high[index];
   double lowest = low[index];
   if(!MathIsValidNumber(highest) || !MathIsValidNumber(lowest) ||
      !MathIsValidNumber(close[index]))
      return false;

   for(int offset = 1; offset < InpLookback; ++offset)
   {
      const int source = index - offset;
      if(source < 0 || !MathIsValidNumber(high[source]) ||
         !MathIsValidNumber(low[source]))
         return false;
      highest = MathMax(highest, high[source]);
      lowest = MathMin(lowest, low[source]);
   }

   const double span = highest - lowest;
   if(span <= 0.0 || !MathIsValidNumber(span))
      result = 0.0;
   else
      result = 2.0 * (close[index] - lowest) / span - 1.0;
   result = ClampState(result);
   return MathIsValidNumber(result);
}

bool CalculatePoint(const int index,
                    const double &high[],
                    const double &low[],
                    const double &close[],
                    double &state_buffer[],
                    double &output_buffer[])
{
   double raw = 0.0;
   if(!RawPosition(index, high, low, close, raw))
   {
      state_buffer[index] = EMPTY_VALUE;
      output_buffer[index] = EMPTY_VALUE;
      return false;
   }

   const int first_valid = InpLookback - 1;
   double state = raw;
   if(index > first_valid && FiniteValue(state_buffer[index - 1]))
      state = InpSmoothing * raw + (1.0 - InpSmoothing) * state_buffer[index - 1];
   state = ClampState(state);
   const double transformed = 0.5 * MathLog((1.0 + state) / (1.0 - state));
   if(!MathIsValidNumber(transformed))
   {
      state_buffer[index] = EMPTY_VALUE;
      output_buffer[index] = EMPTY_VALUE;
      return false;
   }

   state_buffer[index] = state;
   output_buffer[index] = transformed;
   return true;
}

void ClearDiagnostics()
{
   ArrayInitialize(g_mismatch_marker, EMPTY_VALUE);
   ArrayInitialize(g_mismatch_count, EMPTY_VALUE);
   ArrayInitialize(g_compared_count, EMPTY_VALUE);
   ArrayInitialize(g_candidate_bars, EMPTY_VALUE);
   ArrayInitialize(g_reference_bars, EMPTY_VALUE);
   ArrayInitialize(g_incremental_calls_buffer, EMPTY_VALUE);
   ArrayInitialize(g_reset_calls_buffer, EMPTY_VALUE);
   ArrayInitialize(g_max_error_buffer, EMPTY_VALUE);
}

int OnInit()
{
   if(InpLookback < 2 || InpLookback > 500 ||
      InpSmoothing <= 0.0 || InpSmoothing > 1.0 ||
      InpAuditBars < 10 || InpAuditBars > 5000 ||
      InpTolerance < 0.0 || InpTolerance > 0.01)
      return INIT_PARAMETERS_INCORRECT;

   if(!SetIndexBuffer(0, g_full_reference, INDICATOR_DATA) ||
      !SetIndexBuffer(1, g_candidate, INDICATOR_DATA) ||
      !SetIndexBuffer(2, g_mismatch_marker, INDICATOR_DATA) ||
      !SetIndexBuffer(3, g_mismatch_count, INDICATOR_DATA) ||
      !SetIndexBuffer(4, g_compared_count, INDICATOR_DATA) ||
      !SetIndexBuffer(5, g_candidate_bars, INDICATOR_DATA) ||
      !SetIndexBuffer(6, g_reference_bars, INDICATOR_DATA) ||
      !SetIndexBuffer(7, g_incremental_calls_buffer, INDICATOR_DATA) ||
      !SetIndexBuffer(8, g_reset_calls_buffer, INDICATOR_DATA) ||
      !SetIndexBuffer(9, g_max_error_buffer, INDICATOR_DATA) ||
      !SetIndexBuffer(10, g_candidate_state, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(11, g_reference_state, INDICATOR_CALCULATIONS))
      return INIT_FAILED;

   ArraySetAsSeries(g_full_reference, false);
   ArraySetAsSeries(g_candidate, false);
   ArraySetAsSeries(g_mismatch_marker, false);
   ArraySetAsSeries(g_mismatch_count, false);
   ArraySetAsSeries(g_compared_count, false);
   ArraySetAsSeries(g_candidate_bars, false);
   ArraySetAsSeries(g_reference_bars, false);
   ArraySetAsSeries(g_incremental_calls_buffer, false);
   ArraySetAsSeries(g_reset_calls_buffer, false);
   ArraySetAsSeries(g_max_error_buffer, false);
   ArraySetAsSeries(g_candidate_state, false);
   ArraySetAsSeries(g_reference_state, false);

   for(int plot = 0; plot < 10; ++plot)
      PlotIndexSetDouble(plot, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   for(int plot = 0; plot < 10; ++plot)
      PlotIndexSetInteger(plot, PLOT_SHOW_DATA, false);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, InpLookback - 1);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, InpLookback - 1);
   PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, InpLookback - 1);
   PlotIndexSetInteger(2, PLOT_ARROW, 251);
   IndicatorSetInteger(INDICATOR_DIGITS, 8);
   IndicatorSetString(INDICATOR_LEVELTEXT, 0, "zero");
   IndicatorSetString(INDICATOR_SHORTNAME, "Reinit consistency audit #74 | waiting");
   g_incremental_calls = 0;
   g_reset_calls = 0;
   return INIT_SUCCEEDED;
}

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   const int first_valid = InpLookback - 1;
   if(rates_total <= first_valid + 1)
      return 0;

   ArraySetAsSeries(time, false);
   ArraySetAsSeries(open, false);
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(low, false);
   ArraySetAsSeries(close, false);
   ArraySetAsSeries(tick_volume, false);
   ArraySetAsSeries(volume, false);
   ArraySetAsSeries(spread, false);

   const bool reset = prev_calculated <= 0 || prev_calculated > rates_total;
   if(reset)
   {
      ++g_reset_calls;
      ArrayInitialize(g_candidate, EMPTY_VALUE);
      ArrayInitialize(g_candidate_state, EMPTY_VALUE);
   }
   else
      ++g_incremental_calls;

   int candidate_start = first_valid;
   if(!reset && InpMode == AUDIT_INCREMENTAL)
      candidate_start = MathMax(first_valid, prev_calculated - 1);
   else
   {
      ArrayInitialize(g_candidate, EMPTY_VALUE);
      ArrayInitialize(g_candidate_state, EMPTY_VALUE);
   }

   for(int index = candidate_start; index < rates_total; ++index)
      CalculatePoint(index, high, low, close, g_candidate_state, g_candidate);

   ArrayInitialize(g_full_reference, EMPTY_VALUE);
   ArrayInitialize(g_reference_state, EMPTY_VALUE);
   for(int index = first_valid; index < rates_total; ++index)
      CalculatePoint(index, high, low, close, g_reference_state, g_full_reference);

   ClearDiagnostics();
   const int audit_end = rates_total - 2; // Never certify the forming bar.
   const int audit_start = MathMax(first_valid, audit_end - InpAuditBars + 1);
   int compared = 0;
   int mismatches = 0;
   double max_error = 0.0;
   for(int index = audit_start; index <= audit_end; ++index)
   {
      ++compared;
      if(!FiniteValue(g_candidate[index]) || !FiniteValue(g_full_reference[index]))
      {
         ++mismatches;
         g_mismatch_marker[index] = 0.0;
         continue;
      }
      const double error = MathAbs(g_candidate[index] - g_full_reference[index]);
      max_error = MathMax(max_error, error);
      if(error > InpTolerance)
      {
         ++mismatches;
         g_mismatch_marker[index] = g_full_reference[index];
      }
   }

   const int latest = rates_total - 1;
   g_mismatch_count[latest] = mismatches;
   g_compared_count[latest] = compared;
   g_candidate_bars[latest] = rates_total - candidate_start;
   g_reference_bars[latest] = rates_total - first_valid;
   g_incremental_calls_buffer[latest] = (double)g_incremental_calls;
   g_reset_calls_buffer[latest] = (double)g_reset_calls;
   g_max_error_buffer[latest] = max_error;

   const string verdict = mismatches == 0 ? "MATCH" : "MISMATCH";
   const string mode = InpMode == AUDIT_INCREMENTAL ? "incremental" : "full each call";
   IndicatorSetString(
      INDICATOR_SHORTNAME,
      StringFormat("Reinit audit #74 | %s %d/%d | %s %d vs ref %d bars",
                   verdict, compared - mismatches, compared, mode,
                   rates_total - candidate_start, rates_total - first_valid));
   return rates_total;
}
//+------------------------------------------------------------------+
