preview
Beyond GARCH (Part VIII): The MMAR Library And Putting it to Work in an Expert Advisor

Beyond GARCH (Part VIII): The MMAR Library And Putting it to Work in an Expert Advisor

MetaTrader 5Trading systems |
828 0
Muhammad Minhas Qamar
Muhammad Minhas Qamar

Introduction

Over the past four articles, we built the MMAR library from the ground up. Part 4 covered partition analysis and scaling extraction; Part 5, spectrum fitting and cascade distribution selection; Part 6, the simulation engine; and Part 7, Monte Carlo volatility forecasting with confidence intervals. Each module was developed, tested, and validated independently. Now we bring them together.

This article completes the series by delivering two components: (1) the CMMAR facade class, which exposes a Fit-and-Forecast API over the four modules; and (2) a working Expert Advisor for MetaTrader 5 that loads data, fits the model, generates a volatility forecast, and reports results. By the end of this article, you will have a complete, self-contained library that any EA can include with a single line and use with three function calls.

We will cover:

  1. The CMMAR Facade Class
  2. The Fit Pipeline
  3. Forecasting and Path Simulation
  4. The Demo Expert Advisor
  5. Results and Interpretation
  6. Conclusion


The CMMAR Facade Class

The facade pattern gives users a single entry point to a complex subsystem. Our CMMAR class wraps CPartitionAnalysis, CSpectrumFitter, CMonteCarlo, and CSimulationEngine behind an interface that requires no knowledge of the internal module interactions. The user provides returns, calls Fit(), then calls Forecast(). Everything else — the tau(q) extraction, the Legendre transform, the distribution competition, the adaptive cascade depth — happens internally:

#ifndef MMAR_MQH
#define MMAR_MQH

#include "MMARConstants.mqh"
#include "PartitionAnalysis.mqh"
#include "SpectrumFitter.mqh"
#include "MonteCarlo.mqh"

//+------------------------------------------------------------------+
//| CMMAR class                                                      |
//+------------------------------------------------------------------+
class CMMAR
  {
private:
   CPartitionAnalysis m_partition;
   CSpectrumFitter    m_spectrum;

   MMARParams         m_params;
   ENUM_MMAR_STATUS   m_status;

   int               m_cascade_b;
   double            m_dt_spacing;

public:
                     CMMAR(void);
                    ~CMMAR(void);

   //--- Configuration (call before Fit)
   void              SetPartitionConfig(int dt_min, int dt_max, double spacing = 1.1);
   void              SetMomentConfig(double q_min, double q_max, double q_step);
   void              SetCascadeBase(int b)  { m_cascade_b = MathMax(2, b); }
   void              SetSeed(int seed)      { MathSrand(seed); }

   //--- Main pipeline
   bool              Fit(const double &returns[], int n_returns);

   //--- Forecasting
   bool              Forecast(int horizon_bars, int n_simulations, MMARForecast &result);

   //--- Single path generation
   bool              SimulatePath(int n_points, double &path[], double &returns[]);

   //--- Diagnostics
   MMARParams        GetParams(void)    { return m_params; }
   ENUM_MMAR_STATUS  GetStatus(void)    { return m_status; }
   bool              IsFitted(void)     { return m_status == MMAR_STATUS_FITTED; }
   bool              IsMultifractal(void) { return m_params.is_multifractal; }
   string            GetDistributionName(void);
  };

The public interface has three layers. The configuration layer (SetPartitionConfig, SetMomentConfig, SetCascadeBase, SetSeed) allows fine-tuning before fitting — all parameters have sensible defaults, so this layer is optional. Note that SetCascadeBase and SetSeed are simple inline methods: SetCascadeBase clamps the branching factor to a minimum of 2, and SetSeed delegates directly to MathSrand for reproducible random number generation. The pipeline layer (Fit, Forecast, SimulatePath) is where the work happens. The diagnostics layer (GetParams, GetStatus, IsFitted, IsMultifractal, GetDistributionName) exposes the internal state for logging, decision-making, or display.

It is worth emphasizing that the facade and all underlying modules rely exclusively on MetaTrader 5's Standard Library. Partition analysis uses native matrix and vector types for OLS regression. Spectrum fitting uses ALGLIB's BLEIC optimizer for bounded nonlinear fitting. The simulation engine uses ALGLIB's FFT and Cholesky implementations, along with the Math\Stat library for random number generation. No external DLLs, Python bridges, or third-party dependencies are required. Everything ships with every MetaTrader 5 installation. This makes the library fully portable across any broker and any VPS — if MetaTrader 5 runs, the MMAR library runs.

The class maintains state through the ENUM_MMAR_STATUS enum, which tracks progress through the pipeline:

  • MMAR_STATUS_NOT_FITTED — initial state, no data has been processed.
  • MMAR_STATUS_PARTITION_ONLY — partition analysis succeeded but spectrum fitting failed. The Hurst exponent and multifractality diagnostics are available, but forecasting is not possible.
  • MMAR_STATUS_FITTED — full pipeline succeeded. Both partition analysis and spectrum fitting completed. Forecasting is available.
  • MMAR_STATUS_ERROR — a fatal error occurred during partition analysis. No results are available.

This graduated status system means the class degrades gracefully. If the spectrum fitting fails (perhaps because the data is strongly monofractal and the Legendre transform produces too few valid points), the user still has access to the Hurst exponent and the multifractality diagnostics from the partition analysis stage.

Figure 1 animates how the CMMAR facade wraps the four internal modules and how data flows through each API call:

Fig. 1. The CMMAR facade wraps four modules behind a simple API. Fit() orchestrates partition analysis and spectrum fitting; Forecast() delegates to CMonteCarlo, which internally creates CSimulationEngine instances. The user interacts only with the top-level CMMAR class.


The Fit Pipeline

The Fit() method orchestrates the two analytical stages in sequence: partition analysis to extract tau(q) and H, then spectrum fitting to select the cascade distribution. It populates the MMARParams structure with everything downstream modules need:

//+------------------------------------------------------------------+
//| Fit - Run partition analysis + spectrum fitting                  |
//+------------------------------------------------------------------+
bool CMMAR::Fit(const double &returns[], int n_returns)
  {
   m_status = MMAR_STATUS_NOT_FITTED;
   ZeroMemory(m_params);

//--- Stage 1: Partition Analysis
   if(!m_partition.Init(returns, n_returns))
     {
      Print("MMAR::Fit - partition init failed");
      m_status = MMAR_STATUS_ERROR;
      return false;
     }

   if(!m_partition.Analyze())
     {
      Print("MMAR::Fit - partition analysis failed");
      m_status = MMAR_STATUS_ERROR;
      return false;
     }

   m_params.H = m_partition.GetH();
   m_params.sample_volatility = m_partition.GetSampleVolatility();
   m_params.mean_r_squared = m_partition.GetMeanRSquared();
   m_params.multifractal_score = m_partition.GetMultifractalScore();
   m_params.is_multifractal = m_partition.IsMultifractal();
   m_partition.GetTauQ(m_params.tau_q, m_params.q_values);

//--- Stage 2: Spectrum Fitting
   double tau[], q_vals[];
   m_partition.GetTauQ(tau, q_vals);
   int n_q = m_partition.GetNumQ();

   if(!m_spectrum.Init(q_vals, tau, n_q, m_params.H))
     {
      Print("MMAR::Fit - spectrum init failed (partition results available)");
      m_status = MMAR_STATUS_PARTITION_ONLY;
      return false;
     }

   if(!m_spectrum.ComputeSpectrum())
     {
      Print("MMAR::Fit - spectrum computation failed (partition results available)");
      m_status = MMAR_STATUS_PARTITION_ONLY;
      return false;
     }

   if(!m_spectrum.FitAllDistributions())
     {
      Print("MMAR::Fit - distribution fitting failed (partition results available)");
      m_status = MMAR_STATUS_PARTITION_ONLY;
      return false;
     }

   m_params.dist_type = m_spectrum.GetBestDistribution();
   m_spectrum.GetBestParams(m_params.dist_params);

//--- Find alpha_0 (spectrum peak)
   double alpha[], f_alpha[];
   m_spectrum.GetSpectrum(alpha, f_alpha);
   int n_spec = m_spectrum.GetSpectrumSize();
   m_params.alpha_0 = alpha[0];
   double f_max = f_alpha[0];
   for(int i = 1; i < n_spec; i++)
     {
      if(f_alpha[i] > f_max)
        {
         f_max = f_alpha[i];
         m_params.alpha_0 = alpha[i];
        }
     }

   m_status = MMAR_STATUS_FITTED;
   return true;
  }

The method fills the MMARParams structure progressively. After partition analysis, we have H, sample_volatility, mean_r_squared, and the multifractality diagnostics. After spectrum fitting, we add the distribution type, its parameters, and the peak alpha_0 value. The alpha_0 extraction at the end (finding the alpha where f(alpha) is maximum) provides a summary statistic that is useful for reporting but is not strictly needed for forecasting — the distribution parameters already encode everything the cascade generator needs.

Notice how each spectrum-stage failure sets the status to MMAR_STATUS_PARTITION_ONLY rather than MMAR_STATUS_ERROR. This means an EA can check the status and decide: "the data is not strongly multifractal and the spectrum fit failed, but I still have the Hurst exponent — perhaps I should use a simpler model for this period."

Figure 2 visualizes the two-stage pipeline. Stage 1 maps raw returns to H, sample volatility, R², and the multifractal score, and also produces the tau(q) curve. Stage 2 fits four candidate distributions and selects the best one (Binomial in the EURUSD run) to populate MMARParams. The animation also shows the status progression: ERROR if Stage 1 fails; PARTITION_ONLY if only Stage 2 fails; and FITTED if both stages succeed.

Fig. 2. The Fit() pipeline: Stage 1 extracts scaling properties, Stage 2 selects the best cascade distribution, and the status degrades gracefully if either stage fails


Forecasting and Path Simulation

Once the model is fitted, the Forecast() method delegates directly to the CMonteCarlo class we built in Part 7:

//+------------------------------------------------------------------+
//| Forecast - Monte Carlo volatility forecast                       |
//+------------------------------------------------------------------+
bool CMMAR::Forecast(int horizon_bars, int n_simulations, MMARForecast &result)
  {
   if(m_status != MMAR_STATUS_FITTED)
     {
      Print("MMAR::Forecast - model not fitted");
      return false;
     }

   CMonteCarlo mc;
   if(!mc.Init(m_params.H, m_params.dist_type, m_params.dist_params,
               m_params.sample_volatility, n_simulations, horizon_bars, m_cascade_b))
     {
      Print("MMAR::Forecast - Monte Carlo init failed");
      return false;
     }

   if(!mc.Run())
     {
      Print("MMAR::Forecast - Monte Carlo run failed");
      return false;
     }

   result = mc.GetForecast();
   return true;
  }

The method creates a fresh CMonteCarlo instance, passes it all the fitted parameters, runs the simulations, and copies the forecast result to the caller's structure. The status check at the top ensures we never attempt to forecast with an unfitted model.

For users who need individual synthetic paths (for visualization, scenario analysis, or feeding into other models), the SimulatePath() method provides direct access to the simulation engine:

//+------------------------------------------------------------------+
//| SimulatePath - Generate a single MMAR sample path                |
//+------------------------------------------------------------------+
bool CMMAR::SimulatePath(int n_points, double &path[], double &returns[])
  {
   if(m_status != MMAR_STATUS_FITTED)
     {
      Print("MMAR::SimulatePath - model not fitted");
      return false;
     }

   int cascade_k = (int)MathCeil(MathLog((double)(n_points + 1)) / MathLog((double)m_cascade_b));
   int actual_n = (int)MathPow(m_cascade_b, cascade_k);

   CSimulationEngine engine;
   if(!engine.Init(m_params.H, m_params.dist_type, m_params.dist_params,
                   m_params.sample_volatility, m_cascade_b, cascade_k))
      return false;

   if(!engine.Generate())
      return false;

   engine.GetProcess(path);

   int n_ret;
   engine.GetReturns(returns, n_ret);

   if(actual_n > n_points)
     {
      ArrayResize(path, n_points);
      ArrayResize(returns, MathMax(0, n_points - 1));
     }

   return true;
  }

This method uses the same adaptive cascade depth logic as the Monte Carlo module: find the minimum k that covers the requested number of points, generate the full b^k path, then truncate to the requested length. The caller receives both the price-level process and the log-returns, and can use either for their purposes.

The GetDistributionName() utility converts the enum to a human-readable string for logging:

//+------------------------------------------------------------------+
//| GetDistributionName - Human-readable name of fitted distribution |
//+------------------------------------------------------------------+
string CMMAR::GetDistributionName(void)
  {
   switch(m_params.dist_type)
     {
      case MMAR_DIST_NORMAL:
         return "Normal";
      case MMAR_DIST_BINOMIAL:
         return "Binomial";
      case MMAR_DIST_POISSON:
         return "Poisson";
      case MMAR_DIST_GAMMA:
         return "Gamma";
     }
   return "Unknown";
  }


The Demo Expert Advisor

With the facade in place, building an EA that uses the MMAR library is straightforward. The demo EA fits the model on initialization and runs a full volatility forecast. In a production setting, you would refit periodically (e.g., daily or weekly) and use the forecast to adjust position sizing, stop-loss distances, or trading frequency:

//+------------------------------------------------------------------+
//|                                                     MMAR Demo EA |
//|                      Copyright 2026, MMQ - Muhammad Minhas Qamar |
//|                           https://www.mql5.com/en/articles/22539 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Muhammad Minhas Qamar - MMQ"
#property link      "https://www.mql5.com/en/articles/22539"
#property version   "1.00"
#property strict

#include <MMAR\MMAR.mqh>

input int    InpBars           = 50000;
input int    InpForecastBars   = 7200;
input int    InpSimulations    = 1000;
input int    InpCascadeB       = 2;
input int    InpSeed           = 42;

input double InpQMin           = 0.01;
input double InpQMax           = 30.0;
input double InpQStep          = 0.5;
input int    InpDtMin          = 1;
input int    InpDtMax          = 150;
input double InpDtSpacing      = 1.1;

CMMAR mmar;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
  {
   PrintFormat("========================================");
   PrintFormat("  MMAR Demo EA");
   PrintFormat("  Symbol: %s | Period: %s", _Symbol, EnumToString(_Period));
   PrintFormat("========================================");

//--- Load historical data
   double close[];
   int copied = CopyClose(_Symbol, _Period, 0, InpBars, close);
   if(copied < 1000)
     {
      PrintFormat("Not enough bars: %d (need 1000+)", copied);
      return;
     }
   PrintFormat("Loaded %d bars", copied);

//--- Compute log returns
   double returns[];
   int n_returns = copied - 1;
   ArrayResize(returns, n_returns);
   for(int i = 0; i < n_returns; i++)
      returns[i] = MathLog(close[i + 1] / close[i]);

//--- Configure and fit
   mmar.SetSeed(InpSeed);
   mmar.SetPartitionConfig(InpDtMin, InpDtMax, InpDtSpacing);
   mmar.SetMomentConfig(InpQMin, InpQMax, InpQStep);
   mmar.SetCascadeBase(InpCascadeB);

   uint t0 = GetTickCount();

   if(!mmar.Fit(returns, n_returns))
     {
      if(mmar.GetStatus() == MMAR_STATUS_PARTITION_ONLY)
        {
         MMARParams params = mmar.GetParams();
         PrintFormat("\nPartial fit (spectrum fitting failed):");
         PrintFormat("  H = %.4f", params.H);
         PrintFormat("  Sample vol = %.6f", params.sample_volatility);
         PrintFormat("  Mean R2 = %.4f", params.mean_r_squared);
         PrintFormat("  Score = %d/9 | Multifractal: %s",
                   params.multifractal_score,
                   params.is_multifractal ? "YES" : "NO");
         Print("Cannot forecast without distribution fit.");
        }
      else
         Print("MMAR fitting failed completely.");
      return;
     }

   uint t_fit = GetTickCount() - t0;

//--- Print fit results
   MMARParams params = mmar.GetParams();

   PrintFormat("\n--- Model Fit (%u ms) ---", t_fit);
   PrintFormat("H (Hurst):      %.4f", params.H);
   PrintFormat("Distribution:   %s", mmar.GetDistributionName());
   PrintFormat("Alpha_0:        %.4f", params.alpha_0);
   PrintFormat("Sample vol:     %.6f", params.sample_volatility);
   PrintFormat("Mean R2:        %.4f", params.mean_r_squared);
   PrintFormat("Multifractal:   %s (score %d/9)",
               params.is_multifractal ? "YES" : "NO",
               params.multifractal_score);

   PrintFormat("Dist params:");
   switch(params.dist_type)
     {
      case MMAR_DIST_NORMAL:
         PrintFormat("  alpha_0 = %.6f", params.dist_params[0]);
         break;
      case MMAR_DIST_BINOMIAL:
         PrintFormat("  alpha_min = %.6f", params.dist_params[0]);
         PrintFormat("  alpha_max = %.6f", params.dist_params[1]);
         break;
      case MMAR_DIST_POISSON:
         PrintFormat("  alpha_0 = %.6f", params.dist_params[0]);
         break;
      case MMAR_DIST_GAMMA:
         PrintFormat("  alpha_0 = %.6f", params.dist_params[0]);
         PrintFormat("  gamma   = %.6f", params.dist_params[1]);
         break;
     }

//--- Forecast
   t0 = GetTickCount();

   MMARForecast forecast;
   if(!mmar.Forecast(InpForecastBars, InpSimulations, forecast))
     {
      Print("Forecast failed");
      return;
     }

   uint t_forecast = GetTickCount() - t0;

   PrintFormat("\n--- Volatility Forecast (%u ms) ---", t_forecast);
   PrintFormat("Horizon:     %d bars", forecast.forecast_horizon);
   PrintFormat("Simulations: %d", forecast.n_simulations);
   PrintFormat("Mean vol:    %.6f", forecast.mean_volatility);
   PrintFormat("Median vol:  %.6f", forecast.median_volatility);
   PrintFormat("Std vol:     %.6f", forecast.std_volatility);
   PrintFormat("95%% CI:      [%.6f, %.6f]", forecast.ci_lower, forecast.ci_upper);
   PrintFormat("Historical:  %.6f", params.sample_volatility);
   PrintFormat("Ratio:       %.2f (forecast / historical)", forecast.mean_volatility / params.sample_volatility);

//--- Summary
   PrintFormat("\n========================================");
   PrintFormat("  Total time: %u ms", t_fit + t_forecast);
   PrintFormat("========================================");
  }
//+------------------------------------------------------------------+

The EA structure is intentionally minimal. All the work happens in OnInit() — the model fits once when the EA is loaded. The CMMAR class cleans up through its destructor. In a production EA, you would store the forecast, refit periodically (perhaps on the first tick of each new day), and use the forecast values to inform trade decisions in OnTick().

The usage pattern reduces to five lines of meaningful code:

//--- Minimal MMAR usage pattern
CMMAR mmar;
mmar.Fit(returns, n_returns);

MMARForecast forecast;
mmar.Forecast(7200, 1000, forecast);

//--- forecast.mean_volatility is now your estimate

Everything else happens behind those two calls: data loading (50,000 bars), partition-function computation (61 moment orders), the Legendre transform, distribution selection, and 1,000 Monte Carlo simulations generating 8,192-point paths.


Results and Interpretation

Running the Demo EA on EURUSD M10 with 50,000 bars, a 7,200-bar horizon, and 1,000 simulations produces:

========================================
  MMAR Demo EA
  Symbol: EURUSD | Period: PERIOD_M10
========================================
Loaded 50000 bars
Multifractality check: concavity=1 linearity=1 variation=1 total=3/9
SpectrumFitter: 58 spectrum points, alpha=[0.1351, 0.5149],
                peak at alpha_0=0.5149 f=0.9923
Fitting spectrum to 4 distributions...
  Normal:   SSE=69.033271  alpha_0=0.514942
  Binomial: SSE=0.123991  alpha_min=0.188628  alpha_max=0.772413
  Poisson:  SSE=10.212081  alpha_0=0.514942
  Gamma:    SSE=0.178587  alpha_0=0.514942  gamma=1.244125
BEST FIT: Binomial (SSE=0.123991, RMSE=0.046236)

--- Model Fit (422 ms) ---
H (Hurst):      0.4817
Distribution:   Binomial
Alpha_0:        0.5149
Sample vol:     0.000419
Mean R2:        0.8838
Multifractal:   NO (score 3/9)
Dist params:
  alpha_min = 0.188628
  alpha_max = 0.772413
MonteCarlo: 1000 simulations, horizon=7200 bars, k=13, N=8192
MonteCarlo complete: 1000 sims in 8984 ms
  Mean vol: 0.000339 | Std: 0.000008 | Median: 0.000339
  95% CI: [0.000322, 0.000353]

--- Volatility Forecast (8984 ms) ---
Horizon:     7200 bars
Simulations: 1000
Mean vol:    0.000339
Median vol:  0.000339
Std vol:     0.000008
95% CI:      [0.000322, 0.000353]
Historical:  0.000419
Ratio:       0.81 (forecast / historical)

========================================
  Total time: 9406 ms
========================================

Let us interpret these results in terms of what they mean for a trading system:

Model Diagnostics. The Hurst exponent of 0.4817 is slightly below 0.5, suggesting very mild anti-persistence — past trends have a weak tendency to reverse at the M10 level. The multifractal score of 3/9 is below the threshold of 5, meaning this particular 50,000-bar window does not exhibit strong multifractal behavior. However, the spectrum fit still succeeds (the Binomial model achieves an excellent RMSE of 0.046), which means the model can still produce meaningful forecasts. The multifractal flag is a diagnostic, not a gate — the library proceeds with fitting regardless.

The Forecast. The MMAR predicts a forward volatility of 0.000339 (per bar), 19% lower than the historical 0.000419. The 95% confidence interval [0.000322, 0.000353] is tight — the model is highly confident in its estimate. In practical terms, if you were sizing positions based on expected volatility, the MMAR would suggest you can take slightly larger positions than a naive historical-volatility approach would recommend, because it predicts the near future will be calmer than the recent past.

Performance. The total time of 9.4 seconds is dominated by the 1,000 Monte Carlo simulations (8.98 seconds, or about 9 ms per simulation). The fitting stage takes only 422 ms. For a live EA that refits daily, this is perfectly acceptable — even on a modest VPS, a 9-second computation once per day is invisible. If you need faster turnaround (say, refitting hourly), reducing to 100 simulations cuts the Monte Carlo time to under 1 second while still producing a reasonably tight confidence interval.

The Ratio. A forecast/historical ratio of 0.81 means the model believes recent volatility was above-average relative to the structural norm. This is the MMAR's key advantage over GARCH: because it derives its estimate from the scaling structure of the data (a long-run property) rather than from recent squared returns (a short-run property), it is less reactive to volatility spikes. A GARCH model that just witnessed high volatility would forecast continued high volatility (mean-reverting slowly). The MMAR instead says: "the structural properties of this data imply a lower long-run volatility level."

Figure 3 makes this comparison visual. The historical volatility and the MMAR forecast stand side by side — the 19% drop is immediate. The tight 95% confidence interval shows the model is highly confident, and the conceptual contrast at the bottom captures the fundamental difference: GARCH reacts to recent shocks, while the MMAR anchors its estimate to the long-run scaling structure of the data.

Fig. 3. Historical volatility vs. MMAR forecast: the model predicts 19% lower volatility with a tight confidence interval, reflecting its structural rather than reactive approach to estimation


Conclusion

This article completes the Beyond GARCH series. Over eight parts, we have traveled from the theoretical foundations of multifractal analysis (Part 1), through empirical validation and Python prototyping (Parts 2-3), to a complete native MQL5 library (Parts 4-8). The result is a self-contained system that:

  • Detects. Multifractal structure in market data using partition function analysis with rigorous statistical diagnostics (Parts 4).
  • Fits. The singularity spectrum to four theoretical distributions using bounded nonlinear optimization (Part 5).
  • Simulates. Synthetic MMAR paths via multiplicative cascades and Fractional Brownian Motion, using both FFT and Cholesky methods (Part 6).
  • Forecasts. Volatility with Monte Carlo simulation, producing point estimates with uncertainty bounds (Part 7).
  • Packages. Everything in a clean facade with a three-call API: configure, fit, forecast (this article).

The library is designed for extensibility. Some natural next steps:

  • Periodic refitting: Call Fit() on a rolling window (e.g., daily) to track how the model parameters evolve over time. Monitor whether H is stable, whether the winning distribution changes, and whether the multifractal score fluctuates.
  • Position sizing: Use the forecast confidence interval to dynamically adjust lot sizes — smaller positions when the CI is wide (high uncertainty), larger when it is narrow.
  • Regime detection: Use the multifractal score and Hurst exponent as regime indicators. A jump from H=0.48 to H=0.55 might signal a shift from mean-reversion to trend-following behavior.
  • Multi-timeframe: Run partition analysis on multiple timeframes simultaneously and compare their Hurst exponents and multifractal scores for a richer market characterization.

The MMAR is not a trading strategy — it is a volatility model. It tells you how much the market is likely to move, not which direction. Combining it with a directional model (trend filters, mean-reversion signals, or machine learning classifiers) is where the real edge emerges. The volatility forecast provides the denominator: given my directional conviction, how much risk should I take? That question is what this library answers.

The programs presented in this series are intended for educational purposes only. They are not designed for use in live trading and are not optimized for performance in real-market conditions. The MMAR library serves as a foundation for volatility research; always validate thoroughly on demo accounts and consider the computational cost of Monte Carlo simulation before deploying in production.


Getting the Source Code via MQL5 Algo Forge

All source files are attached to this article below, but the full repository is also available on MQL5 Algo Forge, the community's Git-based platform for sharing and collaborating on trading projects. 

The table below lists every file in the complete MMAR library, accumulated across all eight parts of this series:

File name
Description
MQL5\Include\MMAR\MMARConstants.mqh
Shared enumerations, structures, and type definitions for the MMAR library (Part 4)
MQL5\Include\MMAR\OLS.mqh
Ordinary Least Squares regression via SVD pseudo-inverse with full diagnostics (Part 4)
MQL5\Include\MMAR\HurstGHE.mqh
Generalized Hurst Exponent estimator, fallback method (Part 4)
MQL5\Include\MMAR\PartitionAnalysis.mqh
Multifractal partition function analysis with scaling diagnostics (Part 4)
MQL5\Include\MMAR\SpectrumFitter.mqh
Multifractal spectrum computation and four-distribution fitting via BLEIC optimization (Part 5)
MQL5\Include\MMAR\SimulationEngine.mqh
MMAR simulation engine: multiplicative cascade, FBM generation (Davies-Harte and Cholesky), and time-deformation composition (Part 6)
MQL5\Include\MMAR\MonteCarlo.mqh
Monte Carlo volatility forecasting: runs N MMAR simulations and produces mean, median, std, and 95% CI (Part 7)
MQL5\Include\MMAR\MMAR.mqh
Top-level facade class: Fit() and Forecast() API (Part 8)
MQL5\Scripts\MMAR\MMAR_PartitionTest.mq5
Test script for partition analysis on live market data (Part 4)
MQL5\Scripts\MMAR\MMAR_SpectrumTest.mq5
Test script for the full partition + spectrum pipeline on live market data (Part 5)
MQL5\Scripts\MMAR\MMAR_SimulationTest.mq5
Test script validating cascade invariants, FBM properties, and multi-run consistency (Part 6)
MQL5\Scripts\MMAR\MMAR_MonteCarloTest.mq5
End-to-end test script: partition analysis + spectrum fitting + Monte Carlo forecasting (Part 7)
MQL5\Experts\MMAR\MMAR_Demo_EA.mq5
Demo Expert Advisor demonstrating the complete MMAR pipeline (Part 8)

Attached files |
MQL5.zip (94.78 KB)
OrderSend retries and circuit breaker in MQL5 OrderSend retries and circuit breaker in MQL5
Volatile-market failures such as requotes, connection drops, and partial fills expose a common weakness in EAs: unclassified retries and no cumulative failure control. This article introduces CRetryExecutor with exponential backoff and explicit error classification, plus a three-state CCircuitBreaker with cooldown and half-open probes, unified in CExecutionGateway. You can plug it into an EA to stop futile retries, prevent duplicate submissions, and improve diagnostics.
Market Simulation (Part 24): Position View (II) Market Simulation (Part 24): Position View (II)
In this article, I will show how to use an indicator to track open positions on the trading server in the simplest and most practical way possible. I am doing this step by step to show that you do not necessarily have to move all of this into an Expert Advisor. Many of you have probably become used to doing that for one reason or another. In fact, that is not really justified, because as this implementation evolves, it will become clear that you can create or implement different types of indicators for this purpose.
Strategy Configuration via External JSON Files in MQL5: Replacing Input Parameters with a Runtime Config Loader Strategy Configuration via External JSON Files in MQL5: Replacing Input Parameters with a Runtime Config Loader
The article presents CJsonConfigLoader and a typed SStrategyConfig that move EA inputs to a shared JSON file. A hand-written, quote-aware tokenizer parses a flat object without any DLLs. A hotkey triggers reload so all instances can pick up new lot size, SL/TP, and spread limits without reattaching the EA. On malformed input, the loader falls back to safe defaults and keeps the previous configuration.
From Basic to Intermediate: Object Events (IV) From Basic to Intermediate: Object Events (IV)
In this article, we will complete what was started in the previous one: a fully interactive way to resize objects directly on the chart. Although many people imagine that creating something like this would require much deeper knowledge of MQL5, you will see that, using simple concepts and basic knowledge, we can implement a way to work with objects directly on the chart. This leads to a very interesting and quite compelling result.