From Static MA to Adaptive Filtering (Part 1): Introducing SAMA with NLMS in MQL5
Introduction
Traders know the pattern: an SMA or EMA can look perfect for a while — until the market regime shifts and the same fixed-period filter either lags badly in impulses or chops accounts in sideways ranges. The issue is not that moving averages are poorly coded, but that their period is static while the market is not. That mismatch forces endless manual retuning: shorten the period and you get noise; lengthen it and you get lag.
This series offers a concrete alternative: a chart-ready Self-Adaptive Moving Average (SAMA) implemented in MQL5, powered by the Normalized Least Mean Squares (NLMS) adaptive-FIR framework. Crucially, this is written with an explicit success criterion: produce a live, non-repainting indicator that adapts to regime changes without per‑regime period tuning, remains stable during price spikes and news, updates weights only on closed bars, and is usable directly on charts, in EAs, and in backtests. Part 1 explains the motivation, the math, and the core NLMS loop. Part 2 builds the full MQL5 indicator; Part 3 benchmarks it against classical filters.
Quickstart: The Core Idea in Code
Before any theory, here is the minimal working version of the adaptive update loop. This is the heart of the entire indicator:
//--- 1. Calculate input vector energy (sum of squares of PAST prices) double energy = 0.0; for(int j = 0; j < inp_filter_length; j++) { double p = GetSourcePrice(close, high, low, i - 1 - j); energy += p * p; } //--- Safeguard against division by zero if(energy < 1e-8) energy = 1e-8; //--- 2. Compute filter output (predicted value for current bar i) using past data double predicted = 0.0; for(int j = 0; j < inp_filter_length; j++) { predicted += g_weights[j] * GetSourcePrice(close, high, low, i - 1 - j); } //--- 3. Compute error on the current bar double src_price = GetSourcePrice(close, high, low, i); double error = src_price - predicted; //--- 4. Update weights via Normalized LMS rule (only permanently commit closed bars) if(i < rates_total - 1) { double normalized_lr = inp_learning_rate / energy; for(int j = 0; j < inp_filter_length; j++) { g_weights[j] += normalized_lr * error * GetSourcePrice(close, high, low, i - 1 - j); } } //--- 5. Store output for plotting g_sama_buffer[i] = predicted;
The core logic is a fast inline sequence. First, it scales the adaptation step by the energy of past prices. Next, it computes a prediction and measures the error. Then, it updates the weights — and crucially, only on finalized bars. If you internalize just these five steps, the rest of the series is a guided expansion of this loop into a stable, deployable indicator.
Section 1: Theoretical Basis of the NLMS Filter
What Is an Adaptive FIR Filter?
Think of a standard simple moving average as a filter: it takes the last N prices, assigns each an equal weight of 1/N, and outputs their sum. The weights never change.
An adaptive FIR (Finite Impulse Response) filter does the same thing — but the weights change on every bar. It continuously tries to minimize the difference between its output and the actual price.
Conceptual Framework: SAMA as an Adaptive FIR Filter
While commonly referred to as a moving average, the Self-Adaptive Moving Average (SAMA) is more accurately classified as an Adaptive Finite Impulse Response (FIR) filter. Traditional moving averages apply static, hardcoded weights to historical price data.
In contrast, SAMA operates on the Normalized Least Mean Squares (NLMS) algorithm. It dynamically recalculates its internal coefficients (weights) on every tick or bar to minimize the error between its projected value and the actual incoming price. This means SAMA does not passively smooth data; it actively learns the current market structure. If the market shifts from a trending phase to a ranging phase, the FIR filter's weights adapt to the new frequency profile. This allows the algorithm to maintain relevance without requiring the user to manually change the period length.
The NLMS Algorithm
Normalized Least Mean Squares (NLMS) enhances the standard adaptive approach by scaling the adjustment step based on the total energy of the input data vector. It operates via three primary steps per bar:
- Predict: Compute output as the dot product of the weight vector and past prices.
- Measure error: error = actual_price - predicted_output
- Update weights: Shift each weight based on a normalized learning rate to maintain scale-invariant convergence.
The weight update formula is:
w[j] ← w[j] + (μ / E) · e · x[j]
Where:
- w[j] is the j-th weight (coefficient) in the filter.
- μ (mu) is the normalized learning rate, kept within a stable range of (0, 2.0), controlling how fast weights adapt safely across varied price metrics.
- E — the input energy: the sum of squared historical prices across the lookback window, E = Σ x[j]². Dividing by E is what normalizes the step and prevents weight divergence.
- e — the prediction error, e = actual_price − predicted_output.
- x[j] — the historical input value at lookback offset j.

Fig. 1: The recursive NLMS optimization cycle processing real-time market data through continuous prediction error correction.
Why Incremental Processing (Instead of Batch Learning)
Batch learning utilizes a static historical dataset and deploys the result as a fixed artifact. It cannot respond to shifting market dynamics without manual intervention or periodic re-training.
Incremental processing updates the model’s parameters with each incoming data point. The NLMS algorithm exemplifies this approach. This method aligns perfectly with a live price feed where data arrives sequentially, one bar at a time. The model is effectively "always on," requiring no re-training and no redeployment.
The NLMS algorithm leverages this incremental cycle to compute weight adjustments natively within OnCalculate(). This architectural efficiency eliminates external dependencies. It ensures stable, real-time adaptation entirely within the MQL5 environment.
Section 2: Architecture Overview (Conceptual)
Before implementing the full code in Part 2, we will review the indicator's skeleton and the safety features needed to run the NLMS loop on live ticks.
Project Structure
Table 1: Structural composition of the Self-Adaptive Moving Average (SAMA) indicator.
| Block | Responsibility |
|---|---|
| #property declarations | Chart plotting and single data buffer configuration |
| input parameters | User-controlled normalized learning parameters |
| Global state arrays | Persistent weight vector array |
| OnCalculate() loop | Energy calculation → Predict → Error → Closed-bar Update → Output |
Table 1 outlines the structural composition of the Self-Adaptive Moving Average (SAMA) indicator. It delineates the four primary functional blocks required to manage data processing and visual output within the MQL5 environment.
Architectural Defenses: ATR-Clamping and Weight Leakage
A known vulnerability of standard adaptive filters is their overreaction to sudden, anomalous price spikes, such as high-impact macroeconomic news events. To solve this, SAMA implements an ATR-Clamped error function. Before the NLMS algorithm updates its weights, the absolute error between the prediction and the price is evaluated against a multiplier of the Average True Range (ATR). If the error exceeds this dynamic threshold, the error signal is clamped. This prevents a single massive price anomaly from distorting the filter's weights, ensuring the indicator remains stable and resumes tracking the true trend once the shock dissipates.
To reduce long-term numerical drift, SAMA uses a leakage factor (γ). Without regularization, weights may become unstable during low-variance or highly collinear input. The leakage factor is a practical stabilization heuristic, not a strict mathematical guarantee of boundedness. By introducing a small leak, the algorithm applies a continuous, slight decay to the weights, pulling them gently toward zero:
w[j] ← (1 − γ) · w[j] + (μ/E) · e · x[j]
This acts as a regularization mechanism, preventing weight saturation and ensuring the filter remains responsive to new data over years of continuous operation.
Input Transformation Modes
SAMA can train on three different representations of price, selectable at input:
- INPUT_PRICE — raw price values. The default; produces a standard smoothing line anchored to the price axis.
- INPUT_DIFF — first-order differences between consecutive bars. This centers learning on momentum rather than absolute level; the output is reconstructed back to price by accumulating predicted differences from the previous bar.
- INPUT_RET — percentage returns. This process makes the filter scale-invariant by normalizing the training signal across different price scales. To reconstruct the final output, the predicted return is simply applied to the previous close.
The reconstruction step always converts the internal representation back into native price units. This ensures the chart line plots correctly, regardless of which mode trained the weights.
Adaptive Learning Rate via Efficiency Ratio
When inp_use_adaptive_lr is enabled, the base learning rate (μ) is scaled dynamically using Kaufman's Efficiency Ratio (ER) on each bar. The ER measures how directional recent price movement is relative to total path length: a value near 1.0 signals a clean, efficient trend, while a value near 0.0 indicates choppy, directionless noise.
The scaling formula used is:
efficiency_ratio = net_direction / net_volatility
μ_effective = μ · (0.5 + efficiency_ratio)
During a sustained trend, the ER approaches 1.0 and the effective learning rate rises toward 1.5× the base value, allowing the filter to track price tightly. During consolidation, the ER collapses and the learning rate is suppressed toward 0.5× base, causing the filter to smooth aggressively and resist noise. This replaces the static μ with a market-state-aware adaptation throttle.
Weight Normalization
When enabled, the algorithm enforces that the filter's internal weight vector sums to exactly 1.0 after every update cycle. This constraint prevents the raw NLMS update rule from producing weight configurations that, when applied to raw prices, generate outputs that drift from the price axis. With normalization active, the weighted sum of historical prices always produces a value on the same scale as the input, making the indicator numerically stable and visually coherent without requiring manual gain tuning.
Dynamic Slope Color Classification
The indicator uses a DRAW_COLOR_LINE plot with three color states. On every completed bar, the derivative (slope) of the SAMA line is evaluated:
- A positive slope assigns MediumSeaGreen — indicating an upward trend phase.
- A negative slope assigns Crimson — indicating a downward trend phase.
- A zero slope assigns SlateGray — indicating a flat or transitional state.
This provides an instant visual regime classifier directly on the line itself, without requiring a separate sub-window indicator.
Linear Buffer Optimization
Inside OnCalculate, the indicator pre-allocates two local arrays — source[] and tr_input[] — sized to rates_total at the start of each call. All price and transformation data is mapped into these contiguous memory blocks before the main compute loop begins. This pattern avoids repeated pointer dereferences into the terminal's series arrays during the inner loop, improving cache locality and reducing per-bar overhead on large datasets.
Warmup Bars
The inp_warmup_bars parameter controls how many bars are processed silently before the indicator line begins drawing. The PLOT_DRAW_BEGIN offset is set to inp_filter_length + inp_warmup_bars + inp_er_period, hiding the period during which the NLMS weight vector is still converging from its flat initial state. This prevents the visually misleading transient behavior that would otherwise appear at the left edge of the chart when the indicator is first applied to a large historical dataset.Conclusion
What you should now have from this part is a clarified problem statement and a precise engineering target: replace a fixed lookback period with an adaptive weight vector and a market-aware adaptation throttle. The NLMS core performs predict → error → normalized weight update on closed bars, and the adaptive mechanism that substitutes for “period tuning” is a combination of per-weight adaptation plus an optional efficiency-scaled learning rate.
Equally important, we established the minimal, deployable protections any adaptive chart indicator needs: ATR-clamping of extreme errors to survive macro shocks, a small leakage term to prevent long‑term drift, optional weight normalization to keep outputs on the price scale, multiple input modes (price/diff/return) with correct reconstruction, an ER-based adaptive μ, and a warmup period to hide convergence transients. Together these form the practical SAMA recipe: an algorithmic skeleton that is robust in real time and ready to be implemented in MQL5. In Part 2 we translate this design into the full SAMA_NLMS.mq5 source: buffers, weight seeding, closed-bar update logic, plotting, and deployment notes so you can drop it onto a chart or reuse the core in an EA or backtest without hidden repainting or unstable learning.
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Swing Extremes and Pullbacks (Part 4): Dynamic Pullback Depth Using Volatility Models
MQL5 Trading Tools (Part 36): Adding Shape and Annotation Tools with In-Place Label Editing to the Canvas Drawing Layer
CSV Data Analysis (Part 4): Building an Automated Python-Driven Comparative Analysis Module for MQL5 Strategy Validation
A Generic Object Pool in MQL5: Eliminating Heap Fragmentation in High-Frequency Indicators
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use