preview
MQL5 Wizard Techniques you should know (Part 98): Using an Unscented Kalman Filter and a Capsule Network in a Custom Signal Class

MQL5 Wizard Techniques you should know (Part 98): Using an Unscented Kalman Filter and a Capsule Network in a Custom Signal Class

MetaTrader 5Trading systems |
100 1
Stephen Njuki
Stephen Njuki

Introduction

In this ongoing series where we dive into advanced custom classes for the MQL5 Wizard, we are progressively constructing a toolkit that can be put to work in various market environments and situations. We build different class types that cater for: money management, trailing stops, and entry signals. In this series we have just covered the former two which means we now rotate back to signals where we look at a new implementation. 

Within the articles where we handle custom signals, we have so far focused on: raw high-speed execution, where we paired bitwise vectorization with perceptron classifiers for markets that were driven by clear binary threshold states. We then covered the B-Tree indexing algorithm that we paired with Bayesian networks, and in this pairing, our aim was to be less deterministic in navigating less certain environments. Recently we explored the Disjoint Set Union algorithm that we paired with deep belief networks which was set up for traders with a discrete-view of market regimes looking to spot changes in volatility.

In principle we keep pivoting between deterministic models and those that depend on probability or that try to make sense of markets in more uncertain times, and in each approach we provide a new algorithm-network pairing.

For this article, we rotate from regime classification (determinism) and return to real-time state estimation where we are introducing a custom signal class that is run by the algorithm Unscented Kalman Filter (UKF) and is paired with a Capsule Neural Network. This expands the toolkit library of what is on offer in prototyping and developing trading systems within MetaTrader and as always, is not intended as a silver bullet. This model is meant to bring to the fold trade set ups that work by stripping away market-noise without adding the lagging problems of many indicators such as the moving average.

Target Environment:

To better appreciate where this custom signal is able to do its best, we may need to contrast it against past models. If we start by only looking at the networks that we have used with custom signals in the past: the perceptron did thrive by working with clean thresholds; the Bayesian NN required historical repetition; the DBN needed unique volatility buckets. In high-frequency (or trading in small timeframes) exotic forex crosses, or crypto etc., rarely cleanly change regimes or repeat patterns precisely. Rather, often the target or "true" price is constantly hidden beneath erratic and localized market noise.

This custom signal is thus intended to thrive in these "noisy middle" environments where a quantitative trader could be looking to capitalize. If one's trading style depends on situations where indicators (esp. MA) get whipsawed or where latency can result in missed entries then this model could be a relief.

More specifically our UKF-CapsNet combination is meant to prevent the need to smooth the price by focusing on estimating the UKF's hidden state. We treat raw price as a noisy sensor reading of a more accurate, non-linear trend. The Capsule Network (CapsNet) is secondarily used to come up with a solution to a different problem when compared to our past networks. Rather than finding hidden categorical connections, the CapsNet serves as a strict spatial validator. This ensures that the algorithm's engine logic generated by our UKF aligns with the current boundaries of volatility (ATR) and the pace of momentum (RSI). So this model can serve traders seeking to avoid lag when making entries and who also need intraday-noise filters if they are day traders or even just to sharpen entries from long-term (or large timeframe) signals.


Model Description

The architecture of the 'CSignalUKFCapsNet' class is best appreciated when one visualizes this model not as a monolithic decision-making block, but two-stage process. We are bringing together a mathematical estimator that gets to manage the "what" aspect of market movements and a validator which manages the "how" aspects of market structure. We thus have, as per usual, engine-1 and engine-2 respectively. 

Engine 1: Unscented Kalman Filter

For a metaphor, imagine a submarine trying to pass through a shallow rocky bay. If the water at the surface would is chaotic with waves and currents making it difficult to know the true position of this vessel, a basic radar system, much like a moving average indicator, would reflect off the waves. This gives a noisy oscillating position report that at any time is bound to be lagging from the submarine's true position. 

The Unscented Kalman Filter, though, serves like an advanced sonar system. Rather than just getting the mean of previous positions, it keeps an inner model of the submarine's state (which we refer to as the "hidden" price state). This means that even when the sensor data is corrupted by noise of the waves, the UKF projects the most likely trajectory of the "submarine's path". 

From a Mathematical standpoint, we show the hidden price state as 'x_k', evolving over time. UKF is also unique in that it engages an 'unscented transform' to propagate the mean and covariance through functions that are not linear. This state transition is defined as follows:

f1

Where:

  • x_{k|k-1} is the predicted (a priori) hidden price state estimate at time step k given the knowledge up to step k-1.
  • f() is the non-linear state transition model/function applied to the previous state.
  • x_{k-1} is the previous hidden state estimate at time step k-1.
  • w_{k-1} is the process noise or uncertainty associated with the transition at time step k-1.

Engine 2: The Capsule Network

So, the UKF provides us with the trajectory but the Capsule Network (CapsNet) gives us structural confirmation. A regular neural network could spot a "Buy" signal because the RSI is on the up, but it is also bound to miss the spatial-hierarchy - meaning it is not versed on whether the current momentum is in the direction of a broader trend or is a pullback.

Capsules are a collection of neurons that stand for the properties of a particular entity within the market. In our case, the CapsNet is on the hunt for the "pose" of the market. This "pose" could be summed up as the relationship between Price, UKF State, and RSI. The CapsNet uses a squashing function that is not linear to ensure short vectors (low-confidence signals) are compressed towards zero. On the other hand, long vectors or high-confidence alignments would get preserved. This squashing function is set by the following formula:

f2

Where:

  • v_j is the final squashed output vector of capsule j, where the vector's length represents the probability of the entity/feature's existence.
  • s_j is the total input vector fed into capsule j (often representing the algorithmic hypothesis or spatial features).
  • ||s_j|| is the norm (magnitude or length) of the input vector s_j.
  • (||s_j||^2)/(1 + ||s_j||^2) acts as the non-linear scalar that shrinks short vectors to almost zero and long vectors to slightly below one.
  • \frac{s_j}{||s_j||} represents the unit vector of s_j, which retains the directional orientation of the original signal.

This formula is our gatekeeper. When the network is in use and the algorithm proposes a trade the CapsNet makes this transformation. If the spatial features of the market - the "pose" - do not align with the algorithm's proposed direction, the network squashes the signal. This prevents the Expert Advisor from getting into a trade in a high-noise structurally invalid environment. From chaining these two functions, we create a model that not only is responsive to price, but can estimate the underlying state and validate the spatial integrity of the state before it is executed.


MQL5 Implementation

With the formal definitions and basic theory out of the way, we can now look to add this to the MetaTrader 5 Standard Library. Whereas in the last custom signal implementation we focused on Disjoint Set Union (DSU) to build a categorization of historical data based on market regimes, for this build our aim is a bit different. We need a real-time tick-by-tick filter.

In order to achieve this, we start by including the Unscented Kalman Filter (UKF) and Capsule Network (CapsNet) within a custom class that is derived from the 'CExpertSignal' base class. This inheritance enables our highly customized and quantitative model to be better connected to other MQL5 Wizard classes on indicators, price data, and other symbol market information. Automatically inheriting the requisite money management or trailing stop or entry signal is almost a prerequisite to building custom MQL5 Wizard classes.

Class Interface:

//+------------------------------------------------------------------+
//| Class CSignalUKFCapsNet.                                         |
//| Purpose: Generator of trade signals based on Unscented Kalman    |
//|          Filter hidden state, 4 algorithms, and a CapsNet filter.|
//| Is derived from the CExpertSignal class.                         |
//+------------------------------------------------------------------+
class CSignalUKFCapsNet : public CExpertSignal
  {
protected:
   //--- Indicator instances
   CiATR             m_atr;
   CiRSI             m_rsi;

   //--- Adjusted parameters
   int               m_ukf_mode;      // UKF Algorithm Iteration Mode
   bool              m_use_capsnet;   // Toggle for Capsule Network
   int               m_period_atr;    // Period for ATR
   int               m_period_rsi;    // Period for RSI

   //--- UKF State Memory
   double            m_ukf_state[10]; // Array keeping the recent hidden states

   //--- "Weights" of market models (0-100)
   int               m_pattern_0;

public:
                     CSignalUKFCapsNet(void);
                    ~CSignalUKFCapsNet(void);

   //--- Methods for setting adjustable parameters via Wizard
   void              UKFMode(int value)                { m_ukf_mode = value; }
   void              UseCapsNet(bool value)            { m_use_capsnet = value; }
   void              PeriodATR(int value)              { m_period_atr = value; }
   void              PeriodRSI(int value)              { m_period_rsi = value; }
   void              Pattern_0(int value)              { m_pattern_0 = value; }

   //--- Initialization and Validation
   virtual bool      ValidationSettings(void);
   virtual bool      InitIndicators(CIndicators *indicators);

   //--- Main signal conditions
   virtual int       LongCondition(void);
   virtual int       ShortCondition(void);

We have a few critical parameters in the interface listing above. 'm_use_capsnet' is a Boolean switch that allows us flexibility in using only the algorithm UKF or both UKF and CapsNet. This ability to switch is something we put to use when testing in order to get a sense of how well the algorithm can forecast on its own versus when it's paired with CapsNet. On paper this means we are testing if, for our test symbol and time window, the noise in price action is only structural or has more layers of complexity where in the later case the CapsNet would be crucial. The other crucial input parameter declared in the interface above is 'm_ukf_mode'. This is an integer that, as has been the case in recent articles, ranges from 1 to 4 and dictates the execution mode of the algorithm.

From the shared code and various indicators available from the built-in library of MQL5, execution modes are never restricted at 4. This is simply a number we have chosen to adopt for brevity that allows us sufficient introduction of the algorithm concepts. Let's now look at our adopted indicators for this UKF-CapsNet pairing.

Why ATR and RSI

When we had Deep Belief Networks in the last article on a custom signal class, we fed a wide array of standard data into the network in order to get hidden category connections. In this case, our neural network needs a more strict, low-dimension "diet". The UKF gives us a baseline state, but without proper context this cannot be very helpful. We need to ask: 'how is price interacting with this baseline'? To help answer this we chose the indicators:

  • Average True Range (ATR): Within UKF's context the ATR is like a proxy. It informs our model the current boundaries of market chaos. If the price deviates from the UKF's hidden state then the ATR would determine whether this deviation is a significant statistical breakout or just gyrations of a noisy market.
  • Relative Strength Index (RSI): The RSI acts like our metric of momentum speed. This tracks the internal strength of price movement relative to the UKF hidden state.

By having restrictions on the inputs to the UKF state, ATR, and RSI, we side-step the "curse of dimensionality". This usually plagues real-time neural networks during inference. We give CapsNet precisely what it requires to grasp the market's "pose". We boil this down to three things: location (State), boundaries (ATR), and velocity (RSI).

Step 1: Estimating the Hidden Price State:

The real Unscented Kalman Filter needs intense matrix inversions, working out sigma points, and transformations that are not linear. Running this non-stop on every new price bar in MQL5 could overwhelm the Strategy Tester if optimization is necessary. But even without the need to tune input parameters execution delays in live trading are bound to happen a lot. Therefore, we make a very efficient mathematically sound estimate of the non-linear state memory.

//+------------------------------------------------------------------+
//| Unscented Kalman Filter: State Estimation                        |
//+------------------------------------------------------------------+
void CSignalUKFCapsNet::EstimateUKFState(int idx)
  {
//--- Simulating non-linear state transition estimation: x_{k|k-1} = f(x_{k-1}) + w_{k-1}
//--- Serves as the mathematical "hidden price state" devoid of standard moving average lag
   double alpha = 0.2;
   m_ukf_state[0] = Close(idx + 9);
   for(int i = 1; i < 10; i++)
     {
      m_ukf_state[i] = alpha * Close(idx + 9 - i) + (1.0 - alpha) * m_ukf_state[i - 1];
     }
  }

Let us go over the listing above. The function 'EstimateUKFState' uses the current bar index ('idx'). Rather than using a flat average of the last 'N' periods, it constructs a recursive projection-array that is non-linear that we label 'm_ukf_state'. In the code block above, we are:

  • look back 9 price bars ('idx + 9') to get the starting state boundary
  • We alpha parameter set to 0.2, where this variable acts like our Kalman gain approximation. It sets how much weight we allocate to raw, noisy current observations as opposed to the prior established state. 
Our 'for-loop' rebuilds dynamically the trajectory. The value at 'm_ukf_state[9]' holds the final, polished estimation of the "true" price state at the given moment, if we are to strip away the microstructure noise while keeping a very tight correlation to price action. A regular Simple Moving Average could struggle to match this without introducing lag.

Step 2: The Four Algorithmic Modes:

Once we establish the hidden state, the algorithm needs to interpret the market action. Since no specific logic or implementation is able to fit every noisy environment, the 'CSignalUKFCapsNet' class allows us to configure this optionality with the parameter 'm_ukf_mode'. Let's look at each of the four modes on offer.

Mode 1: Volatility Breakout

//+------------------------------------------------------------------+
//| Mode 1: Volatility Breakout (ATR Expand + RSI Momentum)          |
//+------------------------------------------------------------------+
double CSignalUKFCapsNet::ModeVolatilityBreakout(int idx)
  {
   double price = Close(idx);
   double ukf_current = m_ukf_state[9]; // Latest hidden state
   bool atr_rising = m_atr.Main(idx) > m_atr.Main(idx + 1);
   double rsi_val = m_rsi.Main(idx);
//--- Breakout through hidden state on high volatility
   if(atr_rising && price > ukf_current && rsi_val > 55.0)
      return 1.0;
   if(atr_rising && price < ukf_current && rsi_val < 45.0)
      return -1.0;
   return 0.0;
  }

Our first mode is intended for entries that capitalize on sudden violent expansions. Our function above does two things in principle:

  • We declare the Boolean 'atr_rising'. This helps us compare current ATR to prior bars ('idx + 1'). This Boolean provides confirmation whether "process noise covariance" is actively expanding.
  • The main logic of the function determines if the raw 'price' pierces through our noise-filtered 'ukf_current' baseline,  and 'atr_rising' is true and also that the momentum ("rsi_val') is increasing beyond its halfway mark in order to confirm a breakout. Returned values are '1.0' for buys and '-1.0' for sells.

Mode 2: Mean Reversion

//+------------------------------------------------------------------+
//| Mode 2: Mean Reversion (Price Stretched vs Hidden State)         |
//+------------------------------------------------------------------+
double CSignalUKFCapsNet::ModeMeanReversion(int idx)
  {
   double price = Close(idx);
   double ukf_current = m_ukf_state[9];
   double deviation = MathAbs(price - ukf_current);
   double atr_val = m_atr.Main(idx);
   double rsi_val = m_rsi.Main(idx);
//--- Reverting exhausted moves back to the UKF hidden baseline
   if(deviation > (atr_val * 1.5))
     {
      if(price < ukf_current && rsi_val < 30.0)
         return 1.0;  // Oversold, Revert Up
      if(price > ukf_current && rsi_val > 70.0)
         return -1.0; // Overbought, Revert Down
     }
   return 0.0;
  }

Our listing above for mode 2 can be contrasted with the clustering behavior the DSU models we looked at in earlier articles. In our current model, we are searching for an elastic snap. This mode has three major steps.

  • We declare deviation as a double that is defined by <code: 'MathAbs(price - ukf_current)'/> This gives us a metric on how far noisy price has strayed from the true mathematical state.
  • We check whether this deviation from the hidden state is by more than 1.5 times of the current True Range. If this is the case then we are overextended.
  • Finally if in addition to being overextended the RSI is exhausted (being either under 30 or over 70) our algorithm would hypothesize that a reversion to the mean ('ukf_current' or baseline) is imminent.

Mode 3: Trend Following

//+------------------------------------------------------------------+
//| Mode 3: Trend Following (Aligned State & Clean Momentum)         |
//+------------------------------------------------------------------+
double CSignalUKFCapsNet::ModeTrendFollowing(int idx)
  {
   double price = Close(idx);
   double ukf_current = m_ukf_state[9];
   double rsi_val = m_rsi.Main(idx);
//--- Cruising smoothly along the UKF trend line
   if(price > ukf_current && rsi_val > 50.0 && rsi_val < 70.0)
      return 1.0;
   if(price < ukf_current && rsi_val < 50.0 && rsi_val > 30.0)
      return -1.0;
   return 0.0;
  }

This mode could be the most conservative, as it is probably suited for steady intraday drift. The logic used here dictates that price remains on the correct side of the 'ukf_current' state. Importantly, we have an RSI constraint. By checking the 70/30 thresholds relative to the parity mark we ensure that the momentum is in the direction we want but not in reversal or extremum territory. This is important given how UKF state is very sensitive. When momentum runs too hot, the risk of a noisy pullback increases exponentially and this can derail state trajectory. In this mode clean, sustainable momentum is preferred to potentially over exhausted parabolic moves.

Mode 4: Consolidation Squeeze

//+------------------------------------------------------------------+
//| Mode 4: Consolidation Squeeze (Anticipating Breakout)            |
//+------------------------------------------------------------------+
double CSignalUKFCapsNet::ModeConsolidation(int idx)
  {
   double atr_current = m_atr.Main(idx);
   double atr_prev = m_atr.Main(idx + 5);
   double rsi_val = m_rsi.Main(idx);
   double price = Close(idx);
   double ukf_current = m_ukf_state[9];
//--- ATR crash implies a squeeze, preparing to explode
   if(atr_current < (atr_prev * 0.7))
     {
      if(price > ukf_current && rsi_val > 50.0)
         return 1.0;  // Anticipate UP
      if(price < ukf_current && rsi_val < 50.0)
         return -1.0; // Anticipate DOWN
     }
   return 0.0;
  }

Our final mode is for noisy environments, and is in part premised on the thesis that prolonged quiet periods precede extreme volatility. In implementing this, we cover three main steps:

  • First we look back 5 bars to establish a baseline volatility metric. This is the ATR reading 5 bars in the past.
  • We then perform a check on the current ATR readings to see if our present volatility has contracted by at least 30%. This would be a sign that the market is squeezing.
  • Finally our algorithm would check the spatial relationship or "the pose" of: 'price'; 'ukf_current' (state);  and 'rsi_val' in order to forecast the release of this energy.

Step 3: The Capsule Network Validation

So far, our model that has constituted only the algorithm will give us outputs of +1.0, or -1.0, or 0.0. In a regular trading setup, this would be passed straight to the execution module. Nonetheless, in markets that are noisy, they are bound to be deceptive at times. An algorithm breakout could have its math checking all the boxes but not have the spatial hierarchy necessary to sustain the move.

This is why the Capsule Network could help. Within this MQL5 implementation, we simulate a forward pass of a basic Caps Net by dwelling on the non-linear "squashing" function that defines vector-based probability.

//+------------------------------------------------------------------+
//| Capsule Network: Simulated Squashing & Spatial Routing           |
//+------------------------------------------------------------------+
double CSignalUKFCapsNet::CapsNetForwardPass(double algo_signal, int idx)
  {
//--- Note: Represents CapsNet activation.
//--- Vector length indicates probability (squashing function).
//--- Formula: v_j = (||s_j||^2 / (1 + ||s_j||^2)) * (s_j / ||s_j||)
   double norm = MathAbs(algo_signal);
   if(norm == 0.0)
      return 0.0;
   double squash = (norm * norm) / (1.0 + norm * norm);
   double output = squash * (algo_signal / norm); // Retains directional sign (+/-)
//--- Simulated network hierarchy layer matching indicators as features
   double rsi_val = m_rsi.Main(idx);
   if(algo_signal > 0.0 && rsi_val > 50.0)
      output += 0.2; // Spatial features align with Long
   if(algo_signal < 0.0 && rsi_val < 50.0)
      output -= 0.2; // Spatial features align with Short
   return output;
  }

Lets unpack the math above that has been translated into code. We begin by extracting the magnitude of the algorithm's forecast. We then squash it using a direct translation of the CapsNet activation function. Whereas typical Sigmoid functions tend to output a flat scalar value between 0 and 1, the squashing function forces short vectors, which are the weak and therefore noisy signals, to shrink towards zero. This is achieved while still permitting the high magnitude or strong/clear signals to be processed to a value just shy of 1. The 'output' variable is defined by re-applying the vector's direction in order to cater for Long or Short signals. Finally we bring in spatial routing. The CapsNet performs checks on the inner components of the market "pose" to ensure they are accurately oriented. When the algorithm proposes a long trade, and yet the RSI is below 50, these spatial features would not be in alignment. The CapsNet would then not add probability to the output. However, when they do align the vector length would be boosted by '0.2'.

This then is our gatekeeper, the CapsNet, that takes a raw hypothesis, squashing it to remove low-confidence noise and routing it spatially against momentum structure.

Step 4: Overarching Logic Execution

The final step is binding our two engines within the MQL5 framework by using the overridden Long and Short condition functions.

//+------------------------------------------------------------------+
//| Long Condition Evaluation                                        |
//+------------------------------------------------------------------+
int CSignalUKFCapsNet::LongCondition(void)
  {
   int result = 0;
   int idx = StartIndex();
   double algo_val = 0.0;
//--- 1. Compute Hidden State
   EstimateUKFState(idx);
//--- 2. Apply chosen UKF iteration
   switch(m_ukf_mode)
     {
      case 1:
         algo_val = ModeVolatilityBreakout(idx);
         break;
      case 2:
         algo_val = ModeMeanReversion(idx);
         break;
      case 3:
         algo_val = ModeTrendFollowing(idx);
         break;
      case 4:
         algo_val = ModeConsolidation(idx);
         break;
      default:
         algo_val = ModeVolatilityBreakout(idx);
         break;
     }
//--- 3. Two Modes Output Resolution (Algo Only vs CapsNet Enabled)
   if(m_use_capsnet)
     {
      double capsnet_score = CapsNetForwardPass(algo_val, idx);
      //--- CapsNet validates the algorithmic intention
      if(capsnet_score > 0.3 && algo_val > 0.0)
         result = m_pattern_0;
     }
   else
     {
      //--- Standalone algorithm check
      if(algo_val > 0.0)
         result = m_pattern_0;
     }
   return(result);
  }

Our flow with these typical overridden functions is sequential and rigid in structure. We start by doing a 'state initialization'. This is performed by calling 'EstimateUKFState(idx)' immediately which anchors our frame of reference. We then move on to 'hypothesis generation'. With this we use the switch directive to dynamically route the execution of the user's chosen environment setup and therefore operation mode. The output from here is the parameter 'algo_val'. With this done we move onto 'the fork'. This is where the use of the dual engine is in play. If the 'm_use_capsnet' parameter is false, the code falls back to the else block.

Essentially our model acts as a pure, standalone UKF estimator. If the 'algo_val' is positive we assign it the built-in pattern weight of 'm_pattern_0', when checking for buys. The inverse applies in the 'ShortCondition' function. If the 'm_use_capsnet' is assigned true, then the 'algo_val' would get forward fed into our network via the function 'CapsNetForwardPass'. The algorithm's input hypothesis needs to survive the squashing function and get a vector probability that is above 0.3 in order to be deemed valid.

If the CapsNet score is high enough  the 'LongCondition' function would return a signal power in the 0 to 100 range back to the 'CExpertSignal' base class. This should trigger the Expert Advisor to open a Long Position. This architecture is, as expected, mirrored when dealing with the 'ShortCondition'. Structuring our code this way not only allows a trader better navigate intraday noise for sharper entries/exits it does provide more explainability which can be crucial in logic-debugging or even upgrading the model.


Post-Optimization Analysis

To be able to validate the efficacy of the 'CSignalUKFCapsNet' class or any dual engine custom class that we build, we perform a pair of stress tests using MetaTrader 5's Strategy Tester. These test runs are done on the 2-hour intraday timeframe with the symbol GBPJPY from 2025.01.01 to 2026.05.01. We reserve the period 2026.01.01 to 2026.05.01 for forward walk testing, with the year 2025 used for tuning the parameters. The first run uses only the UKF algorithm while the second run uses both the algorithm and CapsNet network.

Run 1: The Standalone

r1

In our first run, we disable the Capsule Network ('UseCapsNet = false'). The UKF algorithm is able to successfully track price without the lags often linked to traditional indicators such as the Moving Average. Our optimizer settled on a 93-period ATR and a 6-period RSI in defining the deviations. The results with just the UKF alone do give us highly effective trades. Over the forward walk test window, the raw algorithm was decently precise, executing 14 trades to a flawless win rate of 100%. This yielded a net profit of 1,050. However, the very high precision was not achieved without friction. The model endured an equity drawdown of 1250 that was more than our net profit implying that even though for this test period every trade reverted to the mean and closed in the green, there were major adverse excursions along the way.

Run 2: The Hybrid

r2

In the second run, we enabled the spatial gatekeeper ('Signal_UKF_CapsNet_UseCapsNet = true') and also performed optimizations followed by a forward walk test. Given that the CapsNet needs clear structural boundaries to function in, the MetaTrader 5 optimizer adjusted the context marginally. The ATR period increased to 111 while the RSI was left at 6. The outcome, off-the-bat, seems to be disappointing given that the forward walk was not profitable. However, on closer inspection we can see that our bottom line was defined by only one loss trade. The win rate remained remarkably high at over 90% and since our testing does not use any stop-loss protection, this presents an area where this model could be improved. 


Conclusion

For this article, we have made a transition from discrete regime-clustering of our DSU and DBN models to continuous noise filtering models that rely on the Unscented Kalman Filter paired with Capsule Network. The objective we had was estimating hidden price state and using a neural network to spatially validate it. Nonetheless, our Strategy Tester Results, while never definitive given the small test window and solo-symbol testing, do give us some much-needed caution for the quantitative trader. Added complexity does not always mean more alpha.

Crucially, the Strategy Tester runs made were done without a stop-loss. In fact strictly speaking these test reports are included in the article to demonstrate the Wizard-assembled Expert Advisor's usability as a model, and not a ready-to-deploy trading system. With that disclaimer, neural networks are not silver bullets and our approach here can go in a number of directions as far as improvement is concerned before deployment. While the algorithm and network can always be tweaked, more testing with a systematic stop-loss would be a preferred route. 

Name Description
wz_98.mq5 Wizard Assembled Expert Advisor
SignalUKFCapsNet.mqh Custom Signal Class required for MQL5 Wizard Assembly
r1.set Input settings of first run
r2.set Input settings of second run

Attached files |
MQL5.zip (7.65 KB)
Last comments | Go to discussion (1)
muhammad nasir
muhammad nasir | 24 Jun 2026 at 11:29
Keep dip and silent 
Quantum Neural Network in MQL5 (Part III): A Virtual Quantum Processor Based on Qubits Quantum Neural Network in MQL5 (Part III): A Virtual Quantum Processor Based on Qubits
The article focuses on creating a trading system with a real quantum simulator instead of mathematical analogies. The system uses 3 virtual qubits, quantum gates and superposition principles to analyze markets. It is implemented as a trading EA for MetaTrader 5 in MQL5. The main achievement is the transition from simulation to real quantum principles of financial information processing.
Encoding Candlestick Patterns (Part 3): Frequency Analysis for Single Candlestick Type Structure Encoding Candlestick Patterns (Part 3): Frequency Analysis for Single Candlestick Type Structure
This article introduces a frequency-analysis framework for encoded candlestick patterns in MQL5. By transforming candlesticks into alphabetic symbols, historical price action can be analyzed as a statistical sequence rather than a visual chart. Using GBPUSD and Gold across multiple timeframes, the study examines the occurrence frequency of individual candlestick types, identifies dominant market structures, and reveals the symmetry between bullish and bearish price movements. The results establish a quantitative foundation for pattern discovery and prepare the way for analyzing multi-candlestick sequences and their predictive potential in algorithmic trading systems.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
CSV Data Analysis (Part 5): Real-Time CSV Streaming from Live MetaTrader 5 Sessions CSV Data Analysis (Part 5): Real-Time CSV Streaming from Live MetaTrader 5 Sessions
This article describes a live data export framework for MetaTrader 5 built around a decoupled, three‑layer design. The MQL5 component batches bar and tick records via a write buffer and rotates CSV files daily; a Python daemon tails the stream, renders a live dashboard, and flags anomaly thresholds. The demo indicator illustrates integration points, enabling real‑time monitoring and auditability during trading sessions.