preview
MQL5 Wizard Techniques you should know (Part 99): Using a KD-Tree and an Echo State Network in a Custom Money Management Class

MQL5 Wizard Techniques you should know (Part 99): Using a KD-Tree and an Echo State Network in a Custom Money Management Class

MetaTrader 5Trading systems |
114 0
Stephen Njuki
Stephen Njuki

Introduction

As the approach to Money Management continues to evolve, the pursuit of risk-adjusted returns usually leads us to rethink how we filter market noise. In the last take on this subject, we addressed high-frequency environment challenges by engaging a variety of algorithms that have included Fenwick Trees, Wavelet Denoising, and even Suffix Automata. These algorithms run within models that were purpose-built to capture hidden micro-patterns and remove noise from short-term signal sequences. These approaches were inherently deterministic in that they leaned towards precise pattern matching within high-velocity trending markets.

Today, we pan into a similar but slightly different class of market environment. The systemic-risk-heavy, high-volatility regime. We are moving from sequential pattern matching to a  topology that is more structural and introduces a framework merging KD-Tree proximity analysis with the recurrent dynamism of Echo State Networks. Our approach here is meant for traders grappling with tail-risk environments - those that require traders to gauge how closely their current price action aligns with historical liquidity events. We merge a spatial mapping engine with a lightweight reservoir-computing network. This gives us a defensive system meant for adaptive position sizing depending on when the market landscape gets to a "critical failure point".

Retrospective vs. Perspective: From Micro-Patterns to Regime-Topology

In our prior explorations on custom money management, we have built fairly robust models using algorithms like: Fenwick Trees, Wavelet Thresholding, and Suffix Automata. These algorithms were paired with deep-learning capabilities of CNNs and LSTMs. For the most part those models focused on sequential memory and micro-pattern denoising, an approach that can be suitable for interpreting markets as a continuous stream of signals that overlap and thus need high-frequency filtering. With today 's introduction of the KD-Tree paired with an Echo State Network, we are not discrediting the prior frameworks but we are simply adding a vital expansion to the money management toolkit.

Arguably, financial markets can be very subjective; two traders can observe identical price action and draw very different, though equally valid, conclusions. Though in our prior models we interpreted data sequentially, the KD-Tree gives us a spatial, topological lens. So, for our algorithm, rather than asking 'what exact sequence of patterns just played out?', we ask 'where are we standing currently on the historical map of market volatility?'. Supplementing it with an ESN network that uses low-latency temporal rhythm to process data, means our model acts as a dynamic liquid reservoir instead of the heavy iterative training that we had with past networks like the LSTM. We are introducing not necessarily a better method but an alternative. The two network methods also have overlap and this is intended to empower the developer to choose a particular analytical lens that better suits their unique view of the market structure.

Intended Market Conditions:

Given that this dual-engine model is an extension of what we have put together so far plus the built-in MQL5 library, we are bound to have overlap in the market environments that are suitable for this one. We have already considered, even for custom money management, a variety of markets that included high-volatility, trending crypto or ranging-arbitrage etc. So the net benefit in this new additional model will not necessarily be from trading a new asset class or type but with how this model is applied by its different users depending on their immediate situational priorities.

For example, a systematic swing trader could depend on our prior Wavelet/LSTM class in regular mean-reverting market conditions to tightly regulate position lot sizes. However, the same trader - holding a disparate view of risk during high-stakes periods like central bank announcements or NFP releases could with a parallel Expert Advisor trade at a different timeframe, the same asset but with this KD-Tree/ESN class. In this particular situations, the trader's priority would have shifted from maximizing trend capture to defensive spatial awareness. They would have to engage an algorithmic circuit breaker that monitors if the current volatility coordinates are moving uncomfortably close to a tipping point. Our class here is meant for the developer who takes the view that money management can be modular, adapting not just to the market being traded, but also to particular risk thresholds, as they unfold, when they unfold.

Problem: Inadequacy of Linear Lot Scaling:

The main challenge we try to address here is the fragility of "static" or "linear" money management rules especially in events that are non-linear. Many Expert Advisors use either fixed lot sizes, Martingale progressions, or basic percentage-of-equity scaling. Even though these models have decent performance in "mean-reverting" or "consistent trend" market regimes, they are prone to fail when the market undergoes a significant regime shift. Some economic calendar events tend to make such events more repetitive than many anticipate.

If one were to consider a situation where a trading strategy spots an excellent long entry from standard indicator signals, usually the money management logic would determine the position size based on a fixed risk percentage of the available free margin. Nonetheless, if the broader market is rapidly getting to a "crash profile" -  signs of this can include unnatural contractions in price followed by a spike in the ATR, one can make the case that this standard lot size is no longer optimized for risk. It is effectively blind to a potential structural collapse.

Regular models usually treat all trades as a separate event. They do not properly consider the 'geography' of a given price action. If the market makes a move into a zone of high volatility, linear models usually maintain a high leverage at a time when exposure ought to be dialed down. And this could stem from these models having no "spatial memory". The inability to contextualize the current price action with similar historical patterns that led to liquidity droughts, is our core problem.

This is where the need for position sizing that is dynamic would come to the fore. We thus seek a method that acts as a circuit breaker, doing a pre-flight check on all the open signals. Since we map current market coordinates onto historical failure nodes, we can on paper force a reduction in position size prior to trades being dispatched to the broker. This should allow our strategy to survive systemic tail risk events that are always poised to drain an account's margin.


Model Description

To overcome limits of linear risk scaling, we approach this in two parts: the KD-Tree (the "Cartographer") and the Echo State Network (the "Beating Drum"). Let's unpack these.

Engine 1: The Cartographer:

Supposing the market was a huge three-dimensional landscape, marked by hills and valleys that represent price tops and "crash zones" respectively, then we could think of the KD-Tree as a cartographer. This algorithm does not look at price data points as a sequence; rather it looks at the geometry. By viewing price's Log Returns and ATR values as a fixed coordinate system (x, y), the tree recursively partitions historical data into a spatial grid. Whenever the Expert Advisor would thus prepare to place a trade, the KD-Tree would perform a quick search to mark the k-nearest neighbors that were labeled as "crashes". When the current market conditions (now identified as coordinates) are too close to any such historical danger nodes, our model would identify this at once as a structural risk pattern, regardless of the prevalent price action.

Engine 2: The Beating Drum:

Whereas the KD-Tree gives us a map, the Echo State Network gives us the temporal "pulse". Usually deep neural networks need a lot of computational training, that the ESN does not require given that it is a reservoir system. To illustrate this, picture a large liquid-filled drum where if you drop a stone (this stone can be our input signal), water ripples would form. These ripples, that can be also thought of as internal states, would move over the water in complex recursive patterns. The ESN does not try to alter the drum's structure; it only observes how these ripples resonate over a fixed network of internal neurons.

The Integrated Workflow

The synergy of the KD-Tree and ESN is where our model strength lies. To recap our points above, the KD-Tree algorithm gives us a "safety distance" by asking the question are we in a topological space where markets have historically broken down? For its part, the ESN which gives us momentum modulation poses the question: "Is the current sequential flow of returns transient or a sustainable regime shift"?

By passing our trade volume through both filters, we create a double-layered defense. The KD-Tree flags danger based on historical geometry, and the ESN fine-tunes the position size basing on the immediate temporal "rhythm" of the price action. Our model is thus highly responsive, and can scale lots down to maintain margin in real volatility events. Position sizing can also still be aggressive if the KD-Tree and ESN signal a clear, safe runway.


Model Formulae

Transitioning from conceptual metaphor to working code usually involves first laying out the model's backbone or mathematics. The KD-Tree and ESN work on fundamentally different principles. One can be thought of as geometric while the other is "dynamic".

KD-Tree Dimensional Splitting

This tree organizes historical data by having the 2D feature space partitioned in a form where our (x, y) coordinates map to (log returns, ATR) giving us hyperrectangles. At every depth d of the tree we switch the splitting axis in order to have uniform coverage. This is set by the modulus operator:

f1

Where:

  • Axis: This is our dimension coordinate that can be 0 for log-return or 1 for ATR. This helps draw the spatial border
  • d: This is the current recursive depth level of the tree that begins with the root node where d is zero.
  • mod 2: The modulo when applied to the total number of dimensions (2 in our case). This makes our algorithm alternate the splitting plane.


Our formula ensures that the tree is able to balance the spatial distribution of data points. This allows O(log n) search efficiency when querying for the nearest historical points where we had price crashes. The closeness of our current market state P(r, a) to the closest crash point Q, is measured by the typical Euclidean distance:

f2

Where:

  • D: Our Euclidean distance, that serves as the model's "danger proximity score", between the state right now and a historical node.
  • r_P: This is the Log Return coordinate of the present market price action.
  • r_Q: The Log Return of the historical market data point, stored in a tree node.
  • a_P: ATR coordinate for present real-time volatility
  • a_Q: ATR coordinate for historical data point (Node Q)


If the system works out the average distance to the k-nearest points, it gives us a "proximity score" that marks how structurally alike the current market state is to past price-failures.

Echo State Network (ESN) Dynamics

The ESN takes us to the temporal domain. Relating price to time. Our network is made of a reservoir with N neurons that have fixed, random internal connections (W_res) and input connections of (W_in). The internal state vector x(t) gets updated whenever we get a new price bar by based on the prior state and current input u(t).

f3

Where:

  • x(t): Updated inner state vector or ripples in reservoir at time t
  • tanh(): Hyperbolic Tangent Activation function, serving as a linear stabilizer reducing values to range [-1, 1]
  • W_in: Input weight matrix, randomly initialized.
  • u(t): Input vector at the time step t
  • W_res: Fixed Recurrent Weight matrix
  • x(t-1): Reservoir's internal state vector of prior time step.


Using the hyperbolic tangent function (tanh) serves as the non-linear "squashing" method that ensures the reservoir operates within a stable range, to prevent the network from exploding (like the exploding gradient problem).

Risk Readout Mapping

Finally, we map the high-dimensioned reservoir state to one lot-sizing multiplier y(t). We work this out by using the output weight matrix W_out and a Sigmoid scaling envelope that guarantees the final coefficient will remain within a safe, controlled range of [0.5, 1.5].

f4

Where:

  • y(t): Output lot-size multiplier produced by the network
  • 0.5: Mathematic floor offset. Ensures most pessimistic projections cannot be below 50%
  • e: Euler's number (approx. 2.71828).
  • W_out: Output weight matrix.
  • x(t): Final inner state vector of reservoir of fully processed history data.


With our ESN able to produce a "smooth" adjustment factor - in essence telling the system to dampen exposure when the temporal sequence of returns seems unstable, we have a safety gate in place that can sift out dangerous volatility landscapes even though the spatial coordinates alone might have appeared safe.


MQL5 Implementation

Moving our dual-engine model from theory to a working custom money management class needs careful structuring, as always. The class we build 'CMoneyKDTreeESN', inherits from the standard base class 'CExpertMoney'. This inheritance is key in allowing seamless integration to the MQL5 ecosystem of indicators and symbol functions but is also a prerequisite in building a wizard assembly custom class. Before we get into the code, let us consider the rationale behind our indicator selection for this model.

Why ATR and MACD

Our spatial tree needs two coordinates (x, y) in order to plot on the map. The X coordinate is the price log-returns. We calculate this from price history in order to get a sense of the raw price move. The Y coordinate, however, is the ATR. We chose the ATR because absolute price drops are not pertinent without volatility context. A drop of -1% on low volatility is no more than a slow bleed; a -1% drop accompanied by an explosive ATR spike, on the other hand, is the signature of a liquidity crash.  Including the ATR therefore helps normalize this across different asset types. The MACD is used as a directional vector. While we use the KD-Tree to map the geography, the Moving Average Convergence Divergence (MACD) gets to map the 'momentum vector'. The gap between the MACD main line and the Signal line gives a pure mathematical representation of trend exhaustion or acceleration. The four algorithm modes that we cover below get to use this in deciding how to approach a "crash node".

Step 1: Building the KD-Tree

We implement the KD-Tree via two classes. 'CKDNode' the data wrapper and 'CKDTreeCrash' the logic wrapper. Our data wrapper shapes up as follows:

//+------------------------------------------------------------------+
//| KD-Tree Node Class                                               |
//+------------------------------------------------------------------+
class CKDNode
  {
public:
   double            point[2]; // Feature 0: Log Return, Feature 1: ATR
   bool              is_crash;
   CKDNode           *left;
   CKDNode           *right;

                     CKDNode(double f1, double f2, bool crash)
     {
      point[0] = f1;
      point[1] = f2;
      is_crash = crash;
      left = NULL;
      right = NULL;
     }
                    ~CKDNode(void)
     {
      if(CheckPointer(left) != POINTER_INVALID)
         delete left;
      if(CheckPointer(right) != POINTER_INVALID)
         delete right;
     }
  };

Every node has: a 2D coordinate 'point[2]'; a flag of a Boolean value indicates whether this specific historical moment was a "crash" (which we define via user input threshold 'm_crash_threshold'); and pointers to its geometric children. The most important function in this engine is 'InsertRec', and this builds the spatial map line by line.:

   //--- Simple recursive insertion
   CKDNode*          InsertRec(CKDNode *node, double f1, double f2, bool crash, unsigned int depth)
     {
      if(node == NULL)
         return new CKDNode(f1, f2, crash);
      unsigned int cd = depth % 2;
      double feature = (cd == 0) ? f1 : f2;
      if(feature < node.point[cd])
         node.left = InsertRec(node.left, f1, f2, crash, depth + 1);
      else
         node.right = InsertRec(node.right, f1, f2, crash, depth + 1);
      return node;
     }

From the listing above, we start by checking whether the input node is null. By design, whenever we get to a blank space on our map, we insert a new node. We declare an unsigned integer cd and it acts like the engine's heartbeat. It stores the modulus of the current tree depth. When the depth is 0, 'cd' is also 0 (we divide the map vertically by Log Return). If the depth is 1, cd will be 1 where the split will be horizontal by ATR.

Alternating the hyperplane is what makes this a 'k-dimensional' Tree instead of a regular binary tree. We then use the active axis ('cd') to decide whether the historical price action belongs on the left side of the map (as a lower value) or the right side (higher value). This runs recursively until the functions finds its "structural home". Evaluation of the danger level is done via 'SearchRec' where we seek the k-nearest crash nodes and then call 'GetCrashRiskDistance' to get the Euclidean distance to these nodes. When this distance is low, we are standing on the edge of a historical cliff! 

Step 2: Programming the Echo State Network

While the KD-Tree gets rebuilt on every new price bar in order to maintain the spatial map, the Echo State Network('CEchoStateNet') depends on fixed, randomly initialized internal weights to create a "reservoir" of temporal memory.

   void              InitBaseline()
     {
      //--- Random baseline initialization for reservoir
      MathSrand(1337);
      for(int i = 0; i < m_res_size; i++)
        {
         m_W_in[i] = (double(MathRand()) / 32767.0) * 0.2 - 0.1;
         m_W_out[i] = (double(MathRand()) / 32767.0) * 0.2 - 0.1;
         for(int j = 0; j < m_res_size; j++)
           {
            m_W_res[i * m_res_size + j] = (double(MathRand()) / 32767.0) * 0.4 - 0.2;
           }
        }
     }

From the code above, we can see that tight boundaries are applied to the weights (0.4 - 0.2). This dampening allows the reservoir to satisfy the "Echo State Property". When the weights' size tends to infinity, internal ripples would increase even more so, towards infinity presenting the exploding gradients problem. If they are too small on the other hand then the memory would fade at once. The main temporal evaluation takes place in the Calculate() function:

   //--- Processes a sequence, returns a risk-modulated multiplier [0.5, 1.5]
   double            Calculate(const double &sequence[])
     {
      double state[];
      double next_state[];
      ArrayResize(state, m_res_size);
      ArrayResize(next_state, m_res_size);
      ArrayInitialize(state, 0.0);
      for(int t = 0; t < ArraySize(sequence); t++)
        {
         double u = sequence[t];
         for(int i = 0; i < m_res_size; i++)
           {
            double res_sum = 0;
            for(int j = 0; j < m_res_size; j++)
               res_sum += m_W_res[i * m_res_size + j] * state[j];
            next_state[i] = MathTanh(m_W_in[i] * u + res_sum);
           }
         ArrayCopy(state, next_state);
        }
      //--- Readout
      double output = 0.0;
      for(int i = 0; i < m_res_size; i++)
         output += m_W_out[i] * state[i];
      //--- Sigmoid scale to [0.5, 1.5]
      return 0.5 + (1.0 / (1.0 + MathExp(-output)));
     }
  };

In our code above, we start off by iterating chronologically through the log-return sequence, where we feed the data points 'u' one at a time. Within this loop, the current state interacts with itself basing on the recurrent weight matrix. This could be thought of as the "liquid" sloshing the drum. We assign the next state value by using the hyperbolic function that binds the neuron's activation between -1 and 1. This ensures the reservoir stays stable despite the recursive  calculations. We conclude, by aggregating the readout and applying a sigmoid envelope. Our output will natively range between 0 and 1. The addition of 0.5 therefore adjusts this to a divisor safe range of [0.5, 1.5].

Step 3: The 4 Operational Modes

Our model, keeping with the tradition, has four distinct operating modes or four different ways of interpreting structural data. They are not assigned randomly but are meant to cater to different asset behaviors.

Mode 1: Momentum Divergence

//+------------------------------------------------------------------+
//| Mode 1: Momentum Divergence                                      |
//| Scales lot sizes based on MACD trends vs Proximity to a Crash.   |
//+------------------------------------------------------------------+
double CMoneyKDTreeESN::Mode1_MomentumDivergence(double crash_dist, double atr, double macd_main, double macd_sig)
  {
//--- If price footprint is heavily aligned with historical crash areas
   if(crash_dist < 0.05)
     {
      //--- MACD shows bullish resilience, scale safely
      if(macd_main > macd_sig && macd_main > 0)
         return 1.0;
      //--- Downward momentum + Crash proximity = Extreme cut
      return 0.3;
     }
//--- Low crash risk, momentum carries the size
   return 1.2;
  }

When the KD-Tree indicates that we are very close to a historical crash ('dist < 0.05'), we would look at MACD. If the momentum is still aggressively bullish, then position sizing would be maintained by having the scale factor at 1.0. However, when momentum is downward, the alignment of spatial danger and bearish momentum would necessitate a drastic cut in our position sizing by dropping to 30% of the starting volume. In safe situations we can slightly over-allocate to 1.2.

Mode 2: Volatility Spike

//+------------------------------------------------------------------+
//| Mode 2: Volatility Spike                                         |
//| Looks at ATR expansion. Filters fakeouts using KD-Tree Risk.     |
//+------------------------------------------------------------------+
double CMoneyKDTreeESN::Mode2_VolatilitySpike(double crash_dist, double atr, double macd_main, double macd_sig)
  {
   double avg_atr_pseudo = atr * 1.5; // Context baseline threshold
//--- Breakout: High ATR but far away from historical crash topologies
   if(atr > avg_atr_pseudo && crash_dist > 0.1)
      return 1.5;
//--- High volatility near crash zones is toxic
   if(atr > avg_atr_pseudo && crash_dist < 0.05)
      return 0.5;
   return 0.8;
  }

This mode depends a lot on ATR. A surge in the ATR where it goes above 'avg_atr_pseudo', would mean we have a healthy breakout or a toxic dump. We would thus query KD-Tree to classify the spike. If the distance to a crash node is significant, i.e. more than '0.1', this would indicate its a healthy breakout and therefore we would scale up our position sizing by 150%. If on the other hand this distance is small, say less than '0.05', this could portend wild volatility and we would proceed to slash our exposure by half.

Mode 3: Trend Exhaustion

//+------------------------------------------------------------------+
//| Mode 3: Trend Exhaustion                                         |
//| Reduces exposure when MACD Histogram flips near risk nodes.      |
//+------------------------------------------------------------------+
double CMoneyKDTreeESN::Mode3_TrendExhaustion(double crash_dist, double atr, double macd_main, double macd_sig)
  {
   double histogram = macd_main - macd_sig;
//--- Bearish histogram reversal happening close to a crash profile
   if(histogram < 0 && crash_dist < 0.08)
      return 0.4;
//--- Bullish histogram expanding with safety distance
   if(histogram > 0 && crash_dist > 0.1)
      return 1.25;
   return 0.9;
  }

Rather than look at only MACD direction, this mode checks the 'derivative' (the histogram) as well. When the histogram turns negative and we are close to a spatial risk zone, we scale down to 40%. It serves as an early warning system before the main trend breaks down.

Mode 4: Conservative Risk Aversion

//+------------------------------------------------------------------+
//| Mode 4: Conservative Risk Aversion                               |
//| Strict algorithmic check relying mainly on KD-tree distance.     |
//+------------------------------------------------------------------+
double CMoneyKDTreeESN::Mode4_ConservativeAversion(double crash_dist, double atr, double macd_main, double macd_sig)
  {
//--- In conservative mode, if we are even remotely close to a crash profile, halve the size.
   if(crash_dist < 0.1)
      return 0.5;
//--- Even if safe, only allocate 100% of the calculated lot (no overleveraging)
   return 1.0;
  }

Our logic here is for the very strict portfolio managers. The indicators are ignored. When the KD-Tree spots any proximity to a historical failure, for distances that are sub '0.1' , the capital allocation gets instantly halved. We never allocate more than 100% of base risk i.e. our multiple never exceeds 1.0.

Step 4: The Core Workflow Execution (`Optimize`)

The bringing together of algorithm and network to set the ideal position size in our Expert Advisor takes place in the 'Optimize' method. Whenever the Expert Advisor needs to open a position, this function is called to adjust the requested lot volume. This takes place in five steps that we can quickly go over below.

1. Data Ingestion

We use the 'CopyClose and 'CopyBuffer' built-in functions to pull price history of length 'm_history_length' into local arrays for price and the indicators ATR & MACD.

2. Tree Reconstruction

      //--- 2. Build tree and sequence
      for(int i = 0; i < m_history_length; i++)
        {
         log_returns[i] = MathLog(closes[i] / closes[i + 1]);
         m_kdtree.InsertPoint(log_returns[i], atr_buf[i]);
        }

Given that the market is an 'evolving organism', our map should ideally reflect the most recent information. We thus clear the old tree, we calculate log-returns, the KD-Tree is reconstructed point by point. This then logs the newest spatial relations.

3. Spatial Query & Algorithm Application

      //--- 3. Assess Current Environment
      double curr_return = log_returns[0];
      double curr_atr = atr_buf[0];
      double curr_macd_main = macd_main_buf[0];
      double curr_macd_sig = macd_sig_buf[0];
      // Distance to nearest Crash
      double crash_dist = m_kdtree.GetCrashRiskDistance(curr_return, curr_atr);
      //--- 4. Apply Selected Iteration Mode (Algorithm)
      double scale_factor = 1.0;
      switch(m_algo_mode)
        {
         case 1:
            scale_factor = Mode1_MomentumDivergence(crash_dist, curr_atr, curr_macd_main, curr_macd_sig);
            break;
         case 2:
            scale_factor = Mode2_VolatilitySpike(crash_dist, curr_atr, curr_macd_main, curr_macd_sig);
            break;
         case 3:
            scale_factor = Mode3_TrendExhaustion(crash_dist, curr_atr, curr_macd_main, curr_macd_sig);
            break;
         case 4:
            scale_factor = Mode4_ConservativeAversion(crash_dist, curr_atr, curr_macd_main, curr_macd_sig);
            break;
        }
      lot = lot * scale_factor;

Our model would get the mean distance to the closest crash node by engaging the "current bar's" return and ATR. These spatial coordinates would then be passed through an iteration mode as determined by user inputs, via the switch directive. This gives us our first safety multiplier.

4. Temporal Evaluation (The ESN Filter)

      //--- 5. Apply the Echo State Network Filter if toggled on
      if(m_use_esn)
        {
         double esn_input[];
         ArrayResize(esn_input, m_history_length);
         for(int i = 0; i < m_history_length; i++)
            esn_input[i] = log_returns[m_history_length - 1 - i] * 100.0; // Chronological order
         double esn_coef = m_esn.Calculate(esn_input);
         lot = lot * esn_coef;
        }

When the trader has toggled the neural network on, our model would reverse the log return array to feed it into the ESN in strict chronological order, from oldest to latest. The input would be scaled by 100 so that the values are large enough to reasonably excite the reservoir's tanh activations. Our end result 'esn_coef' serves as the last temporal modulation against the starting lot size.

5. Broker Validation

For the last step, the adjusted lot size is pushed through the standard functions of 'LotStep()', 'LotsMin()', and 'LotsMax()' to ensure our volume is within the permissible thresholds as defined by the broker and its decimal/ fraction size is also acceptable. 


Strategy Tester Runs

Validating the efficacy of our dual engine model involves performing optimizations followed by forward walk tests. We are testing with the forex pair GBPJPY on the 2-hour timeframe from 2025.01.01 to 2026.05.01. The optimization is done in 2025 and the forward walk is in 2026. Given that this is a money management custom class, we would still need a signal class to create an Expert Advisor in the wizard and for this we use the built-in Envelopes and RSI signal classes for our testing. We also as usual do two tests one with just the KD-Tree algorithm and the other with the algorithm and the Echo State Network.

The KD-Tree Algorithm Only-

r1

The KD-Tree Algorithm and Echo State  Network-

r2

Key Takeaways

Our forward walk tests do lend credence to the idea that the ESN module can survive out-of-sample data without entirely collapsing. Adding this second safety gate flipped a -975 loss to a slight +26 profit and our drawdown also reduced from 13.2% to 9.1%. Nonetheless a profit factor of 1.02 and a win rate of 41.6% could be a sign that this model is merely treading water and not generating reliable alpha.

Big picture though, regardless of our test results above, we would not yet be in a position to deploy our Expert Advisor to live settings given the statistical insignificance of only 12 trades. The small profit generated over 3 months can also be wiped out by variable spreads, slippage, commissions etc. We would need to test on high quality tick data, and probably simulate trade latency as this is also a real feature that many seeking to automate need to be prepared for. Our testing also uses no stop-loss.


Conclusion

Our journey on identifying a conceptual vulnerability - where linear position sizing fails amidst structural market shifts - has led us to build a dual-engine model and has been very instructive. Combining the spatial risk mapping of a KD-Tree with the temporal resilience of an Echo State Network has given us a theoretical framework capable of significantly changing a strategy's survival trajectory, in tail-risk/uncertain environments.

In the end one can make the case that algorithmic trading is an exercise in iterative survival. We have added another framework for position sizing to the MQL5 library that allows us to navigate non-linear dangers of the financial markets. It is not a silver bullet but a custom class that could be put to work by the tail-risk-aware trader.

namedescription
wz_99.mq5Wizard Assembled Expert Advisor
MoneyKDTreeESN.mqhCustom Money Management Class used in Wizard Assembly
r1.setInput settings for 1st test run
r2.setInput settings for 2nd test run
Attached files |
MQL5.zip (9.77 KB)
Risk Manager for Trading Robots (Part I): Risk Control Include File for Expert Advisors Risk Manager for Trading Robots (Part I): Risk Control Include File for Expert Advisors
Trading is characterized by high demands on risk management discipline. The article presents an analysis of the main reasons for traders' failures and proposes a technical solution in the form of the CEnhancedRiskManager class for the MQL5 platform. It includes practical testing on an aggressive grid EA.
Automating Classic Market Methods in MQL5 (Part 2): Wyckoff Cause and Effect—Point and Figure Price Targets Automating Classic Market Methods in MQL5 (Part 2): Wyckoff Cause and Effect—Point and Figure Price Targets
This article builds a self-contained MQL5 Expert Advisor that completes the Wyckoff cycle: it detects accumulation/distribution with a finite state machine, enters at the last point of support/supply, and calculates exit point-and-figure counts under Wyckoff's Cause and Effect. We detail the box size from range ATR, a 1-box reversal, target validation, and a 2R fallback. Readers get runnable code without external dependencies.
Persistent Key-Value Store in MQL5: Using Flat Files as a Lightweight Database for EA State Persistent Key-Value Store in MQL5: Using Flat Files as a Lightweight Database for EA State
A lightweight persistence design lets EAs retain counters, flags, and timestamps between terminal restarts. Using only MQL5, CPersistentStore writes a human-readable key=value file in MQL5/Files and serves reads from a CHashMap write-through cache via a typed API. The article analyzes O(1)/O(n) operations, partial‑write risks, and lack of locking, compares with GlobalVariables/SQLite, and provides a demo that reloads state deterministically.
Creating an HTML Dashboard for Strategy Tester and Prop Firm Challenge Analysis in MQL5 Creating an HTML Dashboard for Strategy Tester and Prop Firm Challenge Analysis in MQL5
This article demonstrates how to build a reusable prop‑firm evaluation module for MQL5 Expert Advisors and export results to an HTML dashboard. The module monitors balance and equity during backtests, simulates single or rolling challenges, checks profit target, daily and overall drawdown, and minimum trading days, then outputs both a terminal summary and a browser‑readable report.