preview
From Static MA to Adaptive Filtering (Part 2): Implementing the SAMA_NLMS Indicator in MQL5

From Static MA to Adaptive Filtering (Part 2): Implementing the SAMA_NLMS Indicator in MQL5

MetaTrader 5Indicators |
344 0
Ushana Kevin Iorkumbul
Ushana Kevin Iorkumbul

Introduction

You already saw the NLMS idea in Part 1. In practice, however, transplanting the update equation from a notebook into MQL5 often breaks in ways traders and developers care about: the model can train on the still-forming bar, blow up on flat data, be skewed by single-bar spikes, and produce different live values after a terminal reload because its internal weight state depends on the exact history processed. This part stops treating SAMA as a formula and treats it as production software. It embeds NLMS into a chart-ready MQL5 indicator with proper buffers, input validation, ATR-based error clamping, an Efficiency‑Ratio adaptive μ, weight leakage and optional normalization, a mandatory warm-up phase, and a rule to train only on closed bars. The goal is explicit: provide a SAMA_NLMS.mq5 you can compile, attach to a chart, and use in trading scenarios without obvious instability or surprise re‑rendering.


Section 1: Full Indicator Code

Create a new Indicator file in MetaEditor named SAMA_NLMS.mq5 and paste the following:

//+------------------------------------------------------------------+
//|                                                    SAMA_NLMS.mq5 |
//|                     Self-Adaptive Moving Average via NLMS Online |
//+------------------------------------------------------------------+
#property description "Self-Adaptive Moving Average using Normalized LMS (NLMS) algorithm."

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots   1

//--- Plot 0: SAMA Line with dynamic slope color formatting
#property indicator_label1  "SAMA"
#property indicator_type1   DRAW_COLOR_LINE
#property indicator_color1  clrMediumSeaGreen,clrCrimson,clrSlateGray
#property indicator_width1  2
#property indicator_style1  STYLE_SOLID

//+------------------------------------------------------------------+
//|Enumerations for Input Transformations                            |
//+------------------------------------------------------------------+
enum ENUM_INPUT_MODE
  {
   INPUT_PRICE = 0, // Option A: Raw Prices
   INPUT_DIFF  = 1, // Option B: Differences (Delta)
   INPUT_RET   = 2  // Option C: Returns (Percentage Change)
  };

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input ENUM_INPUT_MODE inp_mode            = INPUT_PRICE; // Data transformation input mode
input int             inp_filter_length   = 14;          // Filter length (Adaptive weight count)
input double          inp_learning_rate   = 0.05;        // Learning rate (Base Step Size Mu)
input bool            inp_use_close       = true;        // Price: true = Close; false = Typical (H+L+C)/3
input double          inp_epsilon         = 1e-8;        // Epsilon for numerical division safety
input double          inp_leak            = 0.0001;      // Forgetting factor / Leakage weight clamp
input bool            inp_use_atr_clamp   = true;        // Use ATR error clamping option
input int             inp_atr_period      = 14;          // ATR Period for error boundaries
input double          inp_error_atr_mult  = 3.0;         // Max allowed error multiplier (Mult * ATR)
input bool            inp_use_adaptive_lr = true;        // Dynamic step size adjustment via ER
input int             inp_er_period       = 10;          // Efficiency Ratio window period
input bool            inp_normalize_weights = true;      // Force adaptive weights sum to 1.0
input int             inp_warmup_bars     = 500;         // Historical background initialization loop

//+------------------------------------------------------------------+
//| Indicator Buffers                                                |
//+------------------------------------------------------------------+
double g_sama_buffer[];                                  // Main display buffer
double g_color_buffer[];                                 // Derivative slope color assignment matrix

//+------------------------------------------------------------------+
//| Global State Variables                                           |
//+------------------------------------------------------------------+
double g_weights[];                                      // Memory matrix containing adaptive weight vector
int    g_atr_handle = INVALID_HANDLE;                    // Managed calculation pointer handle for core system ATR
double g_atr_buffer[];                                   // Direct memory block array for streaming ATR inputs

//+------------------------------------------------------------------+
//| Helper: Get the raw localized price                              |
//+------------------------------------------------------------------+
double GetSourcePrice(const double &close[], const double &high[], const double &low[], const int i)
  {
   return(inp_use_close ? close[i] : (high[i] + low[i] + close[i]) / 3.0);
  }

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Parameter Bounds Sanitization Checking
   if(inp_filter_length < 2 || inp_learning_rate <= 0.0 || inp_learning_rate > 2.0 || inp_warmup_bars < 0 || inp_er_period < 2)
     {
      Alert("SAMA_NLMS Error: Invalid configuration values detected.");
      return(INIT_PARAMETERS_INCORRECT);
     }

//--- Initialize ATR Handle if required
   if(inp_use_atr_clamp)
     {
      g_atr_handle = iATR(_Symbol, _Period, inp_atr_period);
      if(g_atr_handle == INVALID_HANDLE)
        {
         Print("SAMA_NLMS: Failed to open system iATR structural handle.");
         return(INIT_FAILED);
        }
     }

//--- Bind structural index buffer handles to terminal engine
   SetIndexBuffer(0, g_sama_buffer, INDICATOR_DATA);
   SetIndexBuffer(1, g_color_buffer, INDICATOR_COLOR_INDEX);

//--- Shift processing view beyond window boundaries to hide processing artifacts
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, inp_filter_length + inp_warmup_bars + inp_er_period);

//--- Allocate structural filter depth to weight vectors
   ArrayResize(g_weights, inp_filter_length);
   ArrayInitialize(g_weights, 1.0 / (double)inp_filter_length);

   IndicatorSetString(INDICATOR_SHORTNAME, StringFormat("SAMA_NLMS(Len=%d, Leak=%.5f)", inp_filter_length, inp_leak));
   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[])
  {
   int structural_minimum = inp_filter_length + 2;
   if(inp_use_adaptive_lr)
      structural_minimum = MathMax(structural_minimum, inp_er_period + 2);

   if(rates_total <= (structural_minimum + inp_warmup_bars))
      return(0);

//--- Linear continuous buffer optimization strategy
   double source[], tr_input[];
   if(ArrayResize(source, rates_total) < 0 || ArrayResize(tr_input, rates_total) < 0)
      return(0);

//--- Map transformations into contiguous execution memory cache
   for(int i = 0; i < rates_total; i++)
     {
      source[i] = GetSourcePrice(close, high, low, i);
      if(i > 0)
        {
         if(inp_mode == INPUT_PRICE)
            tr_input[i] = source[i];
         else
            if(inp_mode == INPUT_DIFF)
               tr_input[i] = source[i] - source[i - 1];
            else
               if(inp_mode == INPUT_RET)
                  tr_input[i] = (source[i - 1] != 0.0) ? ((source[i] - source[i - 1]) / source[i - 1]) : 0.0;
        }
      else
         tr_input[i] = 0.0;
     }

//--- Handle core execution loops state initializations
   int start_idx;
   if(prev_calculated == 0)
     {
      ArrayInitialize(g_weights, 1.0 / (double)inp_filter_length);
      start_idx = structural_minimum;

      //--- Wipe stale arrays out of drawing tracks
      for(int i = 0; i < start_idx; i++)
        {
         g_sama_buffer[i] = EMPTY_VALUE;
         g_color_buffer[i] = EMPTY_VALUE;
        }
     }
   else
     {
      start_idx = prev_calculated - 1;
     }

//--- Safely retrieve ATR mapping parameters (Non-Series Normal Handling)
   if(inp_use_atr_clamp)
     {
      ArrayResize(g_atr_buffer, rates_total);
      if(CopyBuffer(g_atr_handle, 0, 0, rates_total, g_atr_buffer) <= 0)
         return(0);
     }

//--- Main Core Adaptive Filtering Compute Execution
   for(int i = start_idx; i < rates_total; i++)
     {
      double target = tr_input[i];

      //--- Step 1: Compute total vector transformation energy
      double energy = 0.0;
      for(int j = 0; j < inp_filter_length; j++)
        {
         double p = tr_input[i - 1 - j];
         energy += p * p;
        }

      //--- Step 2: Extract current structural adaptation state prediction
      double predicted = 0.0;
      for(int j = 0; j < inp_filter_length; j++)
        {
         predicted += g_weights[j] * tr_input[i - 1 - j];
        }

      //--- Step 3: Quantify dynamic estimation error deviation metric
      double error = target - predicted;

      //--- Strict ATR volatility boundary error calculation clamp
      if(inp_use_atr_clamp && i >= inp_atr_period)
        {
         double atr = g_atr_buffer[i];
         double max_error = inp_error_atr_mult * atr;

         if(inp_mode == INPUT_RET && source[i - 1] != 0.0)
            max_error /= source[i - 1];

         error = MathMin(MathMax(error, -max_error), max_error);
        }

      //--- Step 4: Perform optimization adjustment updates on completed intervals
      if(i < rates_total - 1)
        {
         double mu = inp_learning_rate;

         //--- Process Kaufman Efficiency Ratio Adaptive Engine
         if(inp_use_adaptive_lr && i >= inp_er_period)
           {
            double net_direction = MathAbs(source[i] - source[i - inp_er_period]);
            double net_volatility = 0.0;

            for(int k = 0; k < inp_er_period; k++)
              {
               net_volatility += MathAbs(source[i - k] - source[i - k - 1]);
              }

            double efficiency_ratio = (net_volatility > 0.0) ? (net_direction / net_volatility) : 0.0;
            mu *= (0.5 + efficiency_ratio);
           }

         //--- Standardized Classical NLMS normalization step
         double normalized_lr = mu / (inp_epsilon + energy);

         //--- Apply optimization weight corrections
         for(int j = 0; j < inp_filter_length; j++)
           {
            g_weights[j] = (1.0 - inp_leak) * g_weights[j] + (normalized_lr * error * tr_input[i - 1 - j]);
           }

         //--- Dynamic execution bounds weight normalization protection
         if(inp_normalize_weights)
           {
            double weight_sum = 0.0;
            for(int j = 0; j < inp_filter_length; j++)
               weight_sum += g_weights[j];

            if(MathAbs(weight_sum) > 1e-12)
              {
               for(int j = 0; j < inp_filter_length; j++)
                  g_weights[j] /= weight_sum;
              }
           }
        }

      //--- Step 5: Convert state matrix evaluations to absolute price coordinates
      double transformed_output = 0.0;
      if(inp_mode == INPUT_PRICE)
         transformed_output = predicted;
      else
         if(inp_mode == INPUT_DIFF)
            transformed_output = source[i - 1] + predicted;
         else
            if(inp_mode == INPUT_RET)
               transformed_output = source[i - 1] * (1.0 + predicted);

      //--- Sanitize anomalous computations
      if(!MathIsValidNumber(transformed_output))
         g_sama_buffer[i] = (i > 0) ? g_sama_buffer[i - 1] : source[i];
      else
         g_sama_buffer[i] = transformed_output;

      //--- Multi-Buffer dynamic derivative trend color classification
      if(i > structural_minimum && g_sama_buffer[i - 1] != EMPTY_VALUE)
        {
         double slope = g_sama_buffer[i] - g_sama_buffer[i - 1];
         if(slope > 0.0)
            g_color_buffer[i] = 0.0;
         else
            if(slope < 0.0)
               g_color_buffer[i] = 1.0;
            else
               g_color_buffer[i] = 2.0;
        }
      else
         g_color_buffer[i] = 2.0;
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+

Input Parameter Reference

Before stepping through the logic, here is what every input does and how it changes the filter's behavior. Read this as the configuration map you'll return to when tuning the indicator on different instruments.

Parameter Type Default Valid range Practical effect
inp_mode enum INPUT_PRICE PRICE / DIFF / RET Selects what the filter trains on. PRICE = raw price; DIFF = bar-to-bar change (momentum); RET = percentage return (scale-invariant).
inp_filter_length int 14 ≥ 2 Number of adaptive weights (the lookback). Longer = smoother but heavier; SAMA stays effective at short lengths because it adapts rather than widening.
inp_learning_rate double 0.05 (0, 2.0) Base step size μ. Higher tracks faster but risks oscillation; ≤ 1.0 is the safe responsive zone.
inp_use_close bool true Price source: true = Close, false = Typical price (H+L+C)/3.
inp_epsilon double 1e-8 small > 0 Division-by-zero floor in the normalization step; matters only when input energy collapses to near zero.
inp_leak double 0.0001 (0, ~0.01) Forgetting factor γ. Larger values decay weights faster toward zero, trading some responsiveness for long-run stability.
inp_use_atr_clamp bool true Enables capping the error at a multiple of ATR so a single spike can't distort the weights.
inp_atr_period int 14 ≥ 1 Lookback for the ATR used by the clamp.
inp_error_atr_mult double 3.0 > 0 How many ATRs of error are allowed before clamping. Lower = stricter spike rejection.
inp_use_adaptive_lr bool true Scales μ each bar by Kaufman's Efficiency Ratio.
inp_er_period int 10 ≥ 2 Window for the Efficiency Ratio calculation.
inp_normalize_weights bool true Forces the weight vector to sum to 1.0 each update, keeping output on the price scale. Strongly recommended in PRICE mode.
inp_warmup_bars int 500 ≥ 0 Bars processed silently before plotting, hiding the convergence transient.

Table 1: Input parameter reference

A practical starting profile for most FX symbols: keep the defaults, but switch inp_mode to INPUT_RET on instruments with very different price scales or during thin sessions.


Section 2: Code Walkthrough — Key Design Decisions

Streamlined Architecture: Inline Execution

All structural vector operations run directly inside the main OnCalculate loop. This delivers two concrete benefits:

  • Execution efficiency — processing inline eliminates function-call overhead and external circular buffers, cutting CPU use on fast tick feeds.
  • Direct offset indexing — querying historical bars with simple index arithmetic (i − 1 − j) avoids array-shifting overhead and improves calculation stability.

Inline Weight Seeding

Initializing an adaptive model with zeroed states causes erratic tracking and slow convergence. On the first pass (prev_calculated == 0) the code seeds g_weights with a uniform prior of 1/N. This creates a neutral baseline equivalent to a simple moving average, eliminating warm-up artifacts and producing clean plots as soon as the lookback requirement is met.

Real-Time Stability and Multi-Tick Safeguards

The condition if(i < rates_total - 1) updates weights only on closed bars. The active, still-forming bar is used to predict but never to train. This separation prevents intra-bar weight drift and eliminates the data corruption caused by rapid back-and-forth ticks within a single candle — the historical line stays reproducible while the live bar still gets a prediction from the established weights.

Learning-Rate Sensitivity and Stability Bounds

The normalized learning rate μ governs tracking responsiveness. Because the update is scaled by input energy, the step stays structurally scale-invariant. In DIFF and RET modes this yields strong consistency across asset classes. In INPUT_PRICE mode, some price-scale sensitivity remains because the ATR clamp is denominated in price units. However, energy normalization still prevents numerical overflow on both low-priced (EURUSD) and high-priced (gold, indices) instruments.

While the mathematical stability limit of NLMS extends to a learning rate of 2.0, keeping μ at or below 1.0 delivers highly responsive, stable tracking.

Table 2: Sensitivity analysis of the normalized learning rate μ (mu).

μ Value Behavior
0.001 – 0.010 Conservative adaptation, smooth output curves
0.010 – 0.100 Balanced configuration — optimal configuration for standard market regimes
0.100 – 1.000 Highly responsive tracking, tightly adjusting during major breakouts
1.000 – 2.000 Aggressive adaptation approaching the theoretical boundary limit

Sensitivity analysis of the normalized learning rate (μ). This guide categorizes the filter’s behavioral response from conservative smoothing to aggressive tracking. By normalizing step adjustments dynamically against price vector energy, the engine maintains scale-invariant stability across varied instruments. The mathematical boundary extends safely up to 2.0. Setting values above this limit may cause weight oscillations or gradient explosions.


Section 3: Installing and Viewing the Indicator

  1. Open MetaEditor (F4 in MetaTrader 5).
  2. Go to File → New → Custom Indicator.
  3. Name it SAMA_NLMS, paste the full code, and press Compile (F7).
  4. In MetaTrader 5, open any chart, go to Insert → Indicators → Custom, and select SAMA_NLMS.
  5. Set your parameters in the input dialog and click OK.

The SAMA line will appear on the chart with slope-based color coding. It renders in MediumSeaGreen when the slope is rising (uptrend), Crimson when the slope is falling (downtrend), and SlateGray during flat or transitional states. It will look similar to an EMA at first glance, but observe its behavior across regime changes — you will notice it tightens during strong trends and widens its lag during choppy periods.

The SAMA indicator plotted on a EURUSD H1 chart

Fig. 1: The SAMA indicator plotted on a EURUSD H1 chart, demonstrating tight tracking velocity during an aggressive uptrend and smooth stabilization within a flat consolidation range.


Section 4: Practical Usage Patterns

As a Dynamic Trend Filter

Use SAMA as a structural regime classifier. If the price is above the SAMA line, look for long entries only. If the price falls below the line, focus purely on short positions. Because the filter adapts dynamically, it tightens automatically during fast trends. It also flattens during messy ranges. This behavior eliminates false regime flips compared to traditional, fixed-period averages.

//+------------------------------------------------------------------+
//| Example: Querying the SAMA line from a prior indicator handle    |
//+------------------------------------------------------------------+
double sama_value[1];
if(CopyBuffer(sama_handle, 0, 1, 1, sama_value) > 0)
  {
   if(close_price > sama_value[0])
     {
      //--- Bullish regime: Allow long execution pathways only
     }
   else
     {
      //--- Bearish regime: Allow short execution pathways only
     }
  }

As a Dynamic Support/Resistance Reference

The SAMA weights evolve continuously toward recent price action. This behavior causes the indicator line to gravitate naturally toward structural price acceptance zones. In trending markets, the line acts as a highly responsive trailing support or resistance curve. In ranging markets, it clusters cleanly near the asset's structural mean price.

Combining With a Fixed MA for Crossover Signals

You can create robust crossover signals by combining SAMA with a standard, fixed 50-period SMA. This dual-indicator structure generates trend-change signals that are highly resistant to whipsaws. The primary advantage is that SAMA adjusts its internal tracking speed dynamically. It does not trail behind the market at a rigid, static pace.


Section 5: Understanding the Limitations

No algorithmic indicator provides a flawless trading solution. The SAMA_NLMS architecture has specific mathematical properties you must account for before deployment:

  • Heavy Dependency on Training History Paths: SAMA uses an online, recursive optimization engine. The precise structure of the weight matrix on the current bar depends entirely on the unbroken sequence of historical updates processed since chart initialization. This creates a critical operational subtlety for practical trading:

  • History Depth: Loading a chart with 5,000 bars will yield slightly different current weight vectors than loading a chart with 50,000 bars.
  • Timeframe Switching: Switching timeframes forces a complete memory purge. The algorithm must rebuild its adaptation history from zero on the new time intervals.
  • Terminal Recalculations: If your broker connection drops and the terminal forces a chart data update, the internal weight trajectory will recalculate from the beginning of the available history.

  • Trading Implications and Mitigations: Because of this path dependency, live execution signals can temporarily diverge from historical backtest lines if the historical depth does not match precisely. To mitigate this risk, the indicator implements a strict, mandatory initialization loop via inp_warmup_bars. This parameter forces the algorithm to process a minimum background buffer of 500 bars before plotting execution values. This background training phase stabilizes the weight array, neutralizing initial condition variance and ensuring signal uniformity across terminal reloads.
  • Over-Adaptation in Low-Volume Markets: If you use raw price mode (INPUT_PRICE) during illiquid, flat market holiday hours, the weights can adjust too aggressively to minor structural noise. This issue occurs because the underlying data lacks variance. You can easily fix this by switching the transformation mode to percentage returns (INPUT_RET).
  • Dependence on Epsilon for Division Safety: The NLMS normalization step divides the learning rate by the input vector energy. If the market becomes completely flat, this energy approaches zero. The calculation relies entirely on your epsilon input (inp_epsilon) to prevent catastrophic divide-by-zero errors.


Section 6: Extending the Indicator

The SAMA_NLMS.mq5 framework provides an excellent, modular foundation for further quantitative research:

  • Alternative Data Transformation Pipelines: The inp_mode enumeration can be expanded. You can easily integrate advanced math transformations. These include logarithmic price returns, Z-score normalizations, or detrended price series before passing arrays to the core filter.
  • Asymmetric Error Penalization: You can modify the weight update loop to treat positive and negative errors differently. This adjustment creates an asymmetric adaptive filter. It tracks rapid bearish liquidations faster than gradual bullish expansions.
  • Multi-Timeframe Regime Hierarchies: You can initialize multiple SAMA handles across different timeframes inside a single Expert Advisor. This structure lets you use an H4 SAMA for macro-trend direction, an H1 SAMA for structural pullback tracking, and an M15 SAMA for execution triggers.


Conclusion

After this part you have a working SAMA NLMS.mq5: it compiles, plots with slope-based coloring, and implements the engineering protections required for practical deployment. Concretely, the indicator enforces input-parameter bounds, initializes weights to a neutral prior, updates weights only on closed bars (no intra-bar learning), clamps extreme errors using ATR, optionally normalizes the weight vector to keep outputs on price scale, and supports an Efficiency‑Ratio scaled learning rate. These measures mitigate the most common failure modes but do not eliminate a fundamental property: SAMA's weights are path-dependent. To reduce divergence between sessions, use the warm-up bars parameter to force background training (default 500), prefer INPUT RET for scale-invariance on thin or widely scaled instruments, and keep μ in the conservative-to-balanced band (≤ 1.0) unless you intentionally need very aggressive tracking.

Operational checklist before deployment: compile and load SAMA NLMS, verify no training on the open bar, inspect behavior during a reconnect or timeframe change (use warm-up), and test parameter sensitivity (inp mode, inp filter length, inp learning rate, inp leak, inp error atr mult). In the final part we will quantify whether these adaptive behaviors change trading outcomes: we will build a parameter-matched EA benchmarking SAMA vs SMA/EMA/KAMA, export diagnostics to CSV, and run a Python analysis pipeline for baseline and walk‑forward comparisons across multiple assets.


Program used in the article:

# Name Type Description
1 SAMA_NLMS.mq5 Custom Indicator The native custom indicator source code implementing the Normalized LMS adaptive filtering engine. It features built-in ATR error clamping, input transformation modes, and dynamic learning rate scaling using the Kaufman Efficiency Ratio.
Attached files |
SAMA_NLMS.mq5 (11.79 KB)
Price Action Analysis Toolkit Development (Part 73): Building a Weekend Gap Trading Signal System in MQL5 Price Action Analysis Toolkit Development (Part 73): Building a Weekend Gap Trading Signal System in MQL5
We extend the weekend gap toolkit with an indicator that turns gap structure into tradeable signals. When price confirms back into the gap, the indicator issues buy/sell arrows, sets TP at the opposite edge, and places SL using current-week extremes. It maintains non-repainting behavior, reconstructs historical signals, updates live, and provides EA-ready buffers for entry markers and TP/SL to support automation.
Overcoming Accessibility Problems in MQL5 Trading Tools (Part V): Gesture-Based Trading With Computer Vision Overcoming Accessibility Problems in MQL5 Trading Tools (Part V): Gesture-Based Trading With Computer Vision
This article shows how to build a hands-free trading workflow for MetaTrader 5 by translating webcam-tracked hand gestures into MQL5 trade commands. We cover the architecture (MediaPipe/OpenCV in Python plus an MQL5 EA), gesture-to-action mapping, and interprocess communication via Global Variables or HTTP polling. You will implement the EA, execute BUY/SELL/CLOSE actions, and validate latency and reliability under real‑time conditions.
MQL5 Trading Tools (Part 37): Adding a Per-Object Property-Editing Ribbon to the Canvas Drawing Layer MQL5 Trading Tools (Part 37): Adding a Per-Object Property-Editing Ribbon to the Canvas Drawing Layer
We add a descriptor-driven property stack and a floating ribbon that binds to the current selection on the drawing layer. The article covers the descriptor list for each tool, the engine get/set API with snapshot-and-restore live preview, and widget renderers for color, opacity, line width, line style, fonts, and level visibility. You get in-place, real-time editing of object appearance via a compact, draggable panel.
Feature Engineering for ML (Part 6): Microstructural Features in MQL5 Feature Engineering for ML (Part 6): Microstructural Features in MQL5
The article introduces CMicrostructureFeatures, an MQL5 class for bar‑level microstructure features: Roll spread/impact, Corwin‑Schultz spread and sigma, Kyle's Lambda, Amihud's ILLIQ, and Hasbrouck's Lambda. Calculations rely solely on OHLCV using rolling windows. It clarifies the implications of MT5 tick volume for lambda estimators and keeps spread estimators volume‑independent. A validation script asserts sizing and basic bounds on outputs.