//+------------------------------------------------------------------+
//|                                       AdaptiveKalmanSmoother.mq5 |
//| Adaptive Kalman Filter Price Smoother — Scalar State-Space      |
//| Process noise Q and measurement noise R estimated from rolling   |
//| return variance and price deviation variance respectively.       |
//| Designed for MQL5 native execution — zero external dependencies.|
//+------------------------------------------------------------------+
#property description "Adaptive Kalman Filter Price Smoother with dynamic Q/R estimation."
#property description "Q estimated from rolling return variance; R from rolling price variance."

//--- Indicator window configurations
#property indicator_chart_window
#property indicator_buffers 4
#property indicator_plots   2

//--- Plot 0: Kalman Smoother Line (chart overlay)
#property indicator_label1  "Kalman Smoother"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

//--- Plot 1: Kalman Gain (Data Window only — invisible on chart)
#property indicator_label2  "Kalman Gain"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrNONE
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input int      inp_ProcessNoiseWindow = 20;    // Process Noise (Q) Lookback Window
input int      inp_MeasureNoiseWindow = 20;    // Measurement Noise (R) Lookback Window
input double   inp_InitialCovariance  = 1.0;   // Baseline Initial Error Covariance
input double   inp_MinProcessNoise    = 1e-10; // Floor boundary for Process Noise
input double   inp_MinMeasureNoise    = 1e-10; // Floor boundary for Measurement Noise

//+------------------------------------------------------------------+
//| Global State Tracking Variables                                  |
//+------------------------------------------------------------------+
double         g_KalmanLine[];
double         g_GainLine[];                   // Kalman Gain — INDICATOR_DATA, clrNONE
double         g_QBuffer[];
double         g_RBuffer[];

double         g_StateEstimate;
double         g_ErrorCovariance;
bool           g_IsInitialized;
int            g_WarmupBars;

//+------------------------------------------------------------------+
//| ComputeRollingVariance                                           |
//| Purpose: Single-pass rolling variance computation algorithm.     |
//+------------------------------------------------------------------+
double ComputeRollingVariance(const double &arr[], const int endIdx, const int window)
  {
//--- Guard against insufficient data depth for variance equations
   if(endIdx < window - 1)
      return(0.0);

   int    startIdx = endIdx - window + 1;
   double sum      = 0.0;
   double sumSq    = 0.0;
   double val      = 0.0;

//--- Accumulate variance state variables across the defined window
   for(int i = startIdx; i <= endIdx; i++)
     {
      val    = arr[i];
      sum   += val;
      sumSq += val * val;
     }

   double mean     = sum / (double)window;
   double variance = (sumSq / (double)window) - (mean * mean);

//--- Defensively filter floating point inaccuracies below absolute zero
   if(variance < 0.0)
      variance = 0.0;

   return(variance);
  }

//+------------------------------------------------------------------+
//| ComputeRollingMean                                               |
//| Purpose: Standard simple moving average helper function.         |
//+------------------------------------------------------------------+
double ComputeRollingMean(const double &arr[], const int endIdx, const int window)
  {
//--- Return raw value if dataset has not populated the lookback boundary
   if(endIdx < window - 1)
      return(arr[endIdx]);

   int    startIdx = endIdx - window + 1;
   double sum      = 0.0;

//--- Perform linear sum extraction
   for(int i = startIdx; i <= endIdx; i++)
      sum += arr[i];

   return(sum / (double)window);
  }

//+------------------------------------------------------------------+
//| Custom Indicator Initialization Function                         |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Validate architectural limits of input constraints
   if(inp_ProcessNoiseWindow < 2)
     {
      Print("AdaptiveKalmanSmoother: inp_ProcessNoiseWindow must be >= 2. Received: ", inp_ProcessNoiseWindow);
      return(INIT_PARAMETERS_INCORRECT);
     }
   if(inp_MeasureNoiseWindow < 2)
     {
      Print("AdaptiveKalmanSmoother: inp_MeasureNoiseWindow must be >= 2. Received: ", inp_MeasureNoiseWindow);
      return(INIT_PARAMETERS_INCORRECT);
     }
   if(inp_InitialCovariance <= 0.0)
     {
      Print("AdaptiveKalmanSmoother: inp_InitialCovariance must be > 0. Received: ", inp_InitialCovariance);
      return(INIT_PARAMETERS_INCORRECT);
     }

//--- Buffer 0: Kalman smoother line (rendered on main chart)
   SetIndexBuffer(0, g_KalmanLine, INDICATOR_DATA);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN,
                       inp_ProcessNoiseWindow > inp_MeasureNoiseWindow
                       ? inp_ProcessNoiseWindow : inp_MeasureNoiseWindow);
   ArraySetAsSeries(g_KalmanLine, false);

//--- Buffer 1: Kalman Gain (INDICATOR_DATA + clrNONE = Data Window visible, chart invisible)
   SetIndexBuffer(1, g_GainLine, INDICATOR_DATA);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN,
                       inp_ProcessNoiseWindow > inp_MeasureNoiseWindow
                       ? inp_ProcessNoiseWindow : inp_MeasureNoiseWindow);
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, clrNONE);
   ArraySetAsSeries(g_GainLine, false);

//--- Buffer 2: Returns series for Process Noise (Q) estimation
   SetIndexBuffer(2, g_QBuffer, INDICATOR_CALCULATIONS);
   ArraySetAsSeries(g_QBuffer, false);

//--- Buffer 3: Measurement noise (R) base calculations
   SetIndexBuffer(3, g_RBuffer, INDICATOR_CALCULATIONS);
   ArraySetAsSeries(g_RBuffer, false);

//--- Format indicator display identifiers
   IndicatorSetString(INDICATOR_SHORTNAME,
                      StringFormat("AdaptKalman(Q=%d,R=%d)", inp_ProcessNoiseWindow, inp_MeasureNoiseWindow));
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

//--- Establish baseline tracker state limits
   g_StateEstimate   = 0.0;
   g_ErrorCovariance = inp_InitialCovariance;
   g_IsInitialized   = false;

   g_WarmupBars      = (inp_ProcessNoiseWindow > inp_MeasureNoiseWindow)
                       ? inp_ProcessNoiseWindow + 1
                       : inp_MeasureNoiseWindow + 1;

   ArrayInitialize(g_KalmanLine, EMPTY_VALUE);
   ArrayInitialize(g_GainLine,   EMPTY_VALUE);
   ArrayInitialize(g_QBuffer,    0.0);
   ArrayInitialize(g_RBuffer,    0.0);

//--- Suppress gain line rendering explicitly from bar zero
   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom Indicator Iteration Function                              |
//+------------------------------------------------------------------+
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[])
  {
//--- Ensure adequate data frames exist before parsing history
   if(rates_total < g_WarmupBars + 1)
      return(0);

   int startBar;

//--- Flush baseline data elements if handling a complete recalculation pass
   if(prev_calculated == 0)
     {
      g_StateEstimate   = close[0];
      g_ErrorCovariance = inp_InitialCovariance;
      g_IsInitialized   = false;
      startBar          = 1;
     }
   else
     {
      //--- Backtrack slightly to handle the currently developing unclosed bar
      startBar = prev_calculated - 1;
     }

//--- Populate the differential process noise calculation array (Q buffer)
   if(prev_calculated == 0)
     {
      g_QBuffer[0] = 0.0;
      for(int i = 1; i < rates_total; i++)
         g_QBuffer[i] = close[i] - close[i - 1];
     }
   else
     {
      int updateFrom = (startBar > 1) ? startBar - 1 : 1;
      for(int i = updateFrom; i < rates_total; i++)
         g_QBuffer[i] = close[i] - close[i - 1];
     }

//--- Primary analytical loop execution
   for(int bar = startBar; bar < rates_total; bar++)
     {
      //--- Step 1: Evaluate Adaptive Process Noise Q_t
      double Qt = ComputeRollingVariance(g_QBuffer, bar, inp_ProcessNoiseWindow);
      Qt = (Qt < inp_MinProcessNoise) ? inp_MinProcessNoise : Qt;

      //--- Step 2: Evaluate Adaptive Measurement Noise R_t
      double Rt = ComputeRollingVariance(close, bar, inp_MeasureNoiseWindow);
      Rt = (Rt < inp_MinMeasureNoise) ? inp_MinMeasureNoise : Rt;

      //--- Step 3: Predictive Time Update Covariance Profile
      double P_prior = g_ErrorCovariance + Qt;

      //--- Step 4: Construct Kalman Filtering Gain Multiplier
      double Kt = P_prior / (P_prior + Rt);

      //--- Step 5: Process Corrective Measurement Update Vector
      double innovation = close[bar] - g_StateEstimate;
      g_StateEstimate   = g_StateEstimate + Kt * innovation;
      g_ErrorCovariance = (1.0 - Kt) * P_prior;

      //--- Step 6: Map resultant structural data states onto rendering buffers
      if(bar >= g_WarmupBars)
        {
         g_KalmanLine[bar] = g_StateEstimate;
         g_GainLine[bar]   = Kt;   // Raw K_t in [0,1] — visible in Data Window on hover
         g_RBuffer[bar]    = Rt;
         g_IsInitialized   = true;
        }
      else
        {
         g_KalmanLine[bar] = EMPTY_VALUE;
         g_GainLine[bar]   = EMPTY_VALUE;
         g_RBuffer[bar]    = Rt;
        }
     }

   return(rates_total);
  }

//+------------------------------------------------------------------+
//| Custom Indicator Deinitialization Function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Reset runtime indices to absolute zero
   g_StateEstimate   = 0.0;
   g_ErrorCovariance = 0.0;
   g_IsInitialized   = false;

//--- Formulate clean analytical diagnostic termination payload
   string reasonStr;
   switch(reason)
     {
      case REASON_REMOVE:
         reasonStr = "Indicator removed from chart";
         break;
      case REASON_CHARTCLOSE:
         reasonStr = "Chart closed";
         break;
      case REASON_RECOMPILE:
         reasonStr = "Recompile triggered";
         break;
      case REASON_PARAMETERS:
         reasonStr = "Input parameters changed";
         break;
      case REASON_CHARTCHANGE:
         reasonStr = "Symbol or timeframe changed";
         break;
      case REASON_ACCOUNT:
         reasonStr = "Account changed";
         break;
      case REASON_TEMPLATE:
         reasonStr = "New template applied";
         break;
      default:
         reasonStr = "Unknown reason (" + (string)reason + ")";
         break;
     }
   Print("AdaptiveKalmanSmoother deinitialized: ", reasonStr);
  }
//+------------------------------------------------------------------+