Page 1 of 1

Mahalanobis Distance Outlier Oscillator (MDOO) – Find the Hidden Extremes

Posted: Tue Aug 11, 2026 9:23 pm
by FTtrader
Hey fellow scalpers,
We all use standard deviation (like Bollinger Bands) or ATR to measure volatility. But standard deviation only looks at one variable in a vacuum. What if we want to know when the relationship between a candle's body size and its total range goes completely out of whack?Enter the Mahalanobis Distance Outlier Oscillator (MDOO).What is Mahalanobis Distance?Originally developed by an Indian statistician in 1936, Mahalanobis Distance measures how far a point is from the center of a data distribution, taking into account the correlation of the dataset.In formal math, the distance $D_M$ from a vector $x$ to a distribution with mean $\mu$ and covariance matrix $S$ is calculated as:$$D_M = \sqrt{(x - \mu)^T S^{-1} (x - \mu)}$$In plain scalping terms: It doesn't just measure if a candle is big.

It measures if a candle is acting weird based on recent market behavior.For this MT4 indicator, I've plotted Mahalanobis Distance across two dimensions:Candle Body (Close - Open)Candle Range (High - Low)How to Scalp with the MDOOBecause Mahalanobis Distance is absolute, the oscillator rests near 0 and spikes upwards when an outlier occurs.The Exhaustion Fade: Watch for a massive spike in the oscillator (usually > 2.5 or 3.0) hitting simultaneously with major Support/Resistance. This usually indicates an exhaustion candle (a "blow-off top" or "capitulation bottom") where the body/range relationship is completely abnormal.The Breakout Confirmation: If price has been in a tight consolidation box and you see the MDOO spike hard exactly as price breaches the box, it confirms real, statistically significant volume/momentum has entered the market. Don't fade it—ride it.

The MT4 Code (MQL4)

Here is the source code.
Open your MetaEditor, create a new Custom Indicator named MahalanobisOscillator, paste this in, and hit compile.

Code: Select all

//+------------------------------------------------------------------+
//|                                       MahalanobisOscillator.mq4  |
//|                                                                  |
//+------------------------------------------------------------------+
#property strict
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_color1 clrDodgerBlue
#property indicator_width1 2

//--- input parameters
input int LookbackPeriod = 20; // Period for Means and Covariance

//--- indicator buffers
double MDBuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetIndexBuffer(0, MDBuffer);
   SetIndexStyle(0, DRAW_LINE);
   IndicatorShortName("Mahalanobis Dist (" + IntegerToString(LookbackPeriod) + ")");
   
   // Optional: Add horizontal levels to easily spot outliers
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, 2.0);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, 3.0);
   
   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 we have enough bars to calculate
   if(rates_total < LookbackPeriod) return(0);

   int limit = rates_total - prev_calculated;
   if(limit > rates_total - LookbackPeriod)
      limit = rates_total - LookbackPeriod;

   for(int i = limit; i >= 0; i--)
     {
      // 1. Calculate Means for X (Body) and Y (Range)
      double sumX = 0, sumY = 0;
      for(int k = 0; k < LookbackPeriod; k++)
        {
         sumX += (close[i+k] - open[i+k]);
         sumY += (high[i+k] - low[i+k]);
        }
      double meanX = sumX / LookbackPeriod;
      double meanY = sumY / LookbackPeriod;

      // 2. Calculate Variances and Covariance
      double varX = 0, varY = 0, covXY = 0;
      for(int k = 0; k < LookbackPeriod; k++)
        {
         double dx = (close[i+k] - open[i+k]) - meanX;
         double dy = (high[i+k] - low[i+k]) - meanY;
         varX += dx * dx;
         varY += dy * dy;
         covXY += dx * dy;
        }
      varX /= LookbackPeriod;
      varY /= LookbackPeriod;
      covXY /= LookbackPeriod;

      // 3. Calculate Determinant of the Covariance Matrix
      double det = varX * varY - covXY * covXY;
      
      // 4. Calculate Mahalanobis Distance for current candle
      if(det == 0) // Prevent division by zero in dead markets
        {
         MDBuffer[i] = 0;
        }
      else
        {
         double currentX = close[i] - open[i];
         double currentY = high[i] - low[i];
         double dx = currentX - meanX;
         double dy = currentY - meanY;

         // Explicit 2D inverse covariance matrix applied to dx, dy
         double md_sq = (varY * dx * dx - 2 * covXY * dx * dy + varX * dy * dy) / det;
         
         if(md_sq > 0)
            MDBuffer[i] = MathSqrt(md_sq);
         else
            MDBuffer[i] = 0;
        }
     }
   return(rates_total);
  }
//+------------------------------------------------------------------+

Tips for use:

Lookback Period: The default is 20. For sub-5-minute scalping, you might want to drop this to 14 to make it more sensitive to micro-structure changes.

Pairing: Don't use this blindly! Pair it with a momentum oscillator like RSI or a volume indicator. It tells you when something strange is happening, but you still need your price action skills to tell you what direction to trade it.

Give it a spin on your demo accounts and post some screenshots of how it lines up with your setups. Happy pip hunting!

Re: Mahalanobis Distance Outlier Oscillator (MDOO) – Find the Hidden Extremes

Posted: Tue Aug 11, 2026 9:24 pm
by FTtrader
Here is the MQL5 version of the Mahalanobis Distance Outlier Oscillator, ready to be added to your forum thread for the MT5 users.

MQL5 handles data arrays and properties a bit differently than MQL4. I have added the explicit #property tags required by MT5 for plotting, and used ArraySetAsSeries() to ensure the arrays are read right-to-left (newest to oldest) just like MT4, which keeps the mathematical loop intact.

MT5 Code (MQL5)
Open MetaEditor 5, create a new Custom Indicator named MahalanobisOscillator, paste this over the default template, and compile.

Code: Select all

//+------------------------------------------------------------------+
//|                                       MahalanobisOscillator.mq5  |
//+------------------------------------------------------------------+
#property indicator_separate_window
#property indicator_buffers 1
#property indicator_plots   1

//--- plot Mahalanobis
#property indicator_label1  "MD"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

//--- horizontal levels for easy outlier spotting
#property indicator_level1  2.0
#property indicator_level2  3.0
#property indicator_levelcolor clrSilver
#property indicator_levelstyle STYLE_DOT

//--- input parameters
input int LookbackPeriod = 20; // Period for Means and Covariance

//--- indicator buffers
double MDBuffer[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- indicator buffers mapping
   SetIndexBuffer(0, MDBuffer, INDICATOR_DATA);
   
   //--- set series flag for buffer so index 0 is the newest candle
   ArraySetAsSeries(MDBuffer, true);
   
   //--- set short name
   IndicatorSetString(INDICATOR_SHORTNAME, "Mahalanobis Dist (" + IntegerToString(LookbackPeriod) + ")");
   
   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 we have enough bars to calculate
   if(rates_total < LookbackPeriod) return(0);

   // Set timeseries arrays as series (index 0 = current bar) to match MQL4 logic
   ArraySetAsSeries(time, true);
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);

   // Determine the limit for the calculation loop
   int limit = rates_total - prev_calculated;
   if(limit > rates_total - LookbackPeriod - 1)
      limit = rates_total - LookbackPeriod - 1;
      
   if(limit < 0) limit = 0;

   // Main calculation loop
   for(int i = limit; i >= 0; i--)
     {
      // 1. Calculate Means for X (Body) and Y (Range)
      double sumX = 0, sumY = 0;
      for(int k = 0; k < LookbackPeriod; k++)
        {
         sumX += (close[i+k] - open[i+k]);
         sumY += (high[i+k] - low[i+k]);
        }
      double meanX = sumX / LookbackPeriod;
      double meanY = sumY / LookbackPeriod;

      // 2. Calculate Variances and Covariance
      double varX = 0, varY = 0, covXY = 0;
      for(int k = 0; k < LookbackPeriod; k++)
        {
         double dx = (close[i+k] - open[i+k]) - meanX;
         double dy = (high[i+k] - low[i+k]) - meanY;
         varX += dx * dx;
         varY += dy * dy;
         covXY += dx * dy;
        }
      varX /= LookbackPeriod;
      varY /= LookbackPeriod;
      covXY /= LookbackPeriod;

      // 3. Calculate Determinant of the Covariance Matrix
      double det = varX * varY - covXY * covXY;
      
      // 4. Calculate Mahalanobis Distance for current candle
      if(det == 0) // Prevent division by zero in dead/flat markets
        {
         MDBuffer[i] = 0;
        }
      else
        {
         double currentX = close[i] - open[i];
         double currentY = high[i] - low[i];
         double dx = currentX - meanX;
         double dy = currentY - meanY;

         // Explicit 2D inverse covariance matrix applied to dx, dy
         double md_sq = (varY * dx * dx - 2 * covXY * dx * dy + varX * dy * dy) / det;
         
         if(md_sq > 0)
            MDBuffer[i] = MathSqrt(md_sq);
         else
            MDBuffer[i] = 0;
        }
     }
     
   return(rates_total);
  }
//+------------------------------------------------------------------+

Re: Mahalanobis Distance Outlier Oscillator (MDOO) – Find the Hidden Extremes

Posted: Tue Aug 11, 2026 9:25 pm
by FTtrader
Here is the C# code for the Mahalanobis Distance Outlier Oscillator, built specifically for the cTrader platform using the modern cTrader Automate API.

In cTrader, the indexing works backwards compared to MetaTrader (index 0 is the oldest candle, and the current candle is the maximum index). The loop inside the Calculate method has been adjusted using index - k to ensure the math perfectly mirrors the MQL4/MQL5 logic.

cTrader Code (C#)
Open the Automate tab in your cTrader platform, click New Indicator, name it MahalanobisOscillator, and paste the following code over the default template.

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Internals;
using cAlgo.API.Indicators;
using cAlgo.Indicators;

namespace cAlgo
{
    // Adding horizontal levels at 2.0 and 3.0 to visually highlight outliers
    [Levels(2.0, 3.0)]
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class MahalanobisOscillator : Indicator
    {
        [Parameter("Lookback Period", DefaultValue = 20)]
        public int LookbackPeriod { get; set; }

        [Output("MD", LineColor = "DodgerBlue", Thickness = 2)]
        public IndicatorDataSeries Result { get; set; }

        protected override void Initialize()
        {
            // Initialization is handled by cTrader attributes
        }

        public override void Calculate(int index)
        {
            // Ensure we have enough historical bars to calculate
            if (index < LookbackPeriod)
            {
                Result[index] = 0;
                return;
            }

            // 1. Calculate Means for X (Body) and Y (Range)
            double sumX = 0;
            double sumY = 0;

            for (int k = 0; k < LookbackPeriod; k++)
            {
                sumX += (Bars.ClosePrices[index - k] - Bars.OpenPrices[index - k]);
                sumY += (Bars.HighPrices[index - k] - Bars.LowPrices[index - k]);
            }

            double meanX = sumX / LookbackPeriod;
            double meanY = sumY / LookbackPeriod;

            // 2. Calculate Variances and Covariance
            double varX = 0, varY = 0, covXY = 0;

            for (int k = 0; k < LookbackPeriod; k++)
            {
                double dx = (Bars.ClosePrices[index - k] - Bars.OpenPrices[index - k]) - meanX;
                double dy = (Bars.HighPrices[index - k] - Bars.LowPrices[index - k]) - meanY;

                varX += dx * dx;
                varY += dy * dy;
                covXY += dx * dy;
            }

            varX /= LookbackPeriod;
            varY /= LookbackPeriod;
            covXY /= LookbackPeriod;

            // 3. Calculate Determinant of the Covariance Matrix
            double det = varX * varY - covXY * covXY;

            // 4. Calculate Mahalanobis Distance for current candle
            if (det == 0) // Prevent division by zero during flat/dead markets
            {
                Result[index] = 0;
            }
            else
            {
                double currentX = Bars.ClosePrices[index] - Bars.OpenPrices[index];
                double currentY = Bars.HighPrices[index] - Bars.LowPrices[index];
                double dx = currentX - meanX;
                double dy = currentY - meanY;

                // Explicit 2D inverse covariance matrix applied to dx, dy
                double md_sq = (varY * dx * dx - 2 * covXY * dx * dy + varX * dy * dy) / det;

                if (md_sq > 0)
                {
                    Result[index] = Math.Sqrt(md_sq);
                }
                else
                {
                    Result[index] = 0;
                }
            }
        }
    }
}
1.) Open cTrader and navigate to the Automate application (the robot icon on the left panel).

2.) Expand the Indicators list, click on the New Indicator (+) button.

3.) Replace the auto-generated code with the C# code above.

4.) Click the Build button (or press Ctrl+B).

5.) The indicator will now be available under Custom indicators in your chart interface. It automatically plots the 2.0 and 3.0 trigger levels.

Re: Mahalanobis Distance Outlier Oscillator (MDOO) – Find the Hidden Extremes

Posted: Tue Aug 11, 2026 9:26 pm
by FTtrader
Here is the Pine Script v5 version for TradingView.

One of the great things about Pine Script is that it has built-in functions for Variance (ta.variance) and Covariance (ta.covariance). This means we don't have to write the manual for loops that we needed in MQL and C#, making the code much cleaner and faster to execute on TradingView's servers.

TradingView Code (Pine Script v5)

Open your TradingView chart, click on the Pine Editor tab at the very bottom, paste this code over the default script, and click Add to Chart.

Code: Select all

//@version=5
indicator("Mahalanobis Distance Outlier Oscillator", shorttitle="MDOO", overlay=false)

// --- Input parameters ---
lookback = input.int(20, title="Lookback Period", minval=2)

// --- 1. Define Dimensions ---
x = close - open  // Candle Body
y = high - low    // Candle Range

// --- 2. Calculate Means, Variances, and Covariance ---
meanX = ta.sma(x, lookback)
meanY = ta.sma(y, lookback)

varX  = ta.variance(x, lookback)
varY  = ta.variance(y, lookback)
covXY = ta.covariance(x, y, lookback)

// --- 3. Calculate Determinant of the Covariance Matrix ---
det = (varX * varY) - (covXY * covXY)

// --- 4. Calculate Mahalanobis Distance ---
float md = 0.0

if det != 0
    dx = x - meanX
    dy = y - meanY
    
    // Explicit 2D inverse covariance matrix applied to dx, dy
    md_sq = ((varY * dx * dx) - (2 * covXY * dx * dy) + (varX * dy * dy)) / det
    
    if md_sq > 0
        md := math.sqrt(md_sq)

// --- 5. Plotting ---
plot(md, title="Mahalanobis Distance", color=color.blue, linewidth=2)
hline(2.0, title="Alert Level 2.0", color=color.silver, linestyle=hline.style_dotted)
hline(3.0, title="Alert Level 3.0", color=color.silver, linestyle=hline.style_dotted)
How this specific version works:
Dynamic Calculations: It uses the same 2-dimensional math (Body and Range) as the MT4/cTrader versions, mapping the current candle against the expanding distribution of the lookback period.

Automatic scaling: Because it calculates distance mathematically via the covariance matrix determinant, the oscillator output remains scale-independent. You can switch from the 1-minute chart to the Daily chart and the 2.0/3.0 threshold spikes will still mean exactly the same thing.

Re: Mahalanobis Distance Outlier Oscillator (MDOO) – Find the Hidden Extremes

Posted: Tue Aug 11, 2026 9:27 pm
by FTtrader
Take a care and have a great trades :-)

And please let me know about your experience with this oscilator.

Re: Mahalanobis Distance Outlier Oscillator (MDOO) – Find the Hidden Extremes

Posted: Fri Aug 14, 2026 12:41 pm
by PTScalper
What is this? i hear about it for first time :D

I will check it.