MQL5 Wizard Techniques you should know (Part 88): Using Blooms Filter with a Custom Trailing Class
Introduction
Regular trailing stops often depend on distance-based logic, that processes every tick/bar linearly. This memoryless method does struggle to set apart real structural trends from repetitive market noise. In this article we explore the Bloom Filter as nother novel alternative for this form of trade management. Rather than tracking pip distances, we use a probability-based membership testing to only move the stops on prices analysed to be unique. We pair this with an RNN in MQL5, that acts as a state-aware gatekeeper reacting only to novel price moves while also resolving secondary problems of computational latency.
Problem
Trading on smaller timeframes is gaining traction, partly due to prop firms, and is closer to high-frequency trading than to daily or weekly trading. The need to manage position drawdowns by tracking positions more frequently is a theme that is gaining more attention among some traders, and this has tended to require trading at or tracking trades at the smaller timeframes.
When such a system is automated, market conditions can introduce compute latency due to more frequent bar and tick processing. The unfortunate result is that Expert Advisors are susceptible to executing redundant logical loops that use up finite CPU resources without yielding any useful info. Quite often traditional algorithmic trailing stops employ rigid, memoryless thresholds. These can have the tendency to fail to adapt to evolving market microstructures. A static fixed-point or ATR-based stop loss can prematurely liquidate positions in a volatility spike that was transient, or about to fade off. They effectively ignore broader structural trends in favour of immediate noise.
The indiscriminate processing of price-bars can prevent integration of complex predictive models within real-time execution constraints. If one was to evaluate neural network matrices on each new price-bar/tick this would clearly lead to hardware bottlenecks. This can cause desynchronization between the local trading terminal and the broker’s server.
When seeking the right price point at which to add or adjust existing stop loss levels, unfiltered market noise can generate false positive signals, and these can distort the protocols of sequential trade management. An algorithm reacting to micro-fluctuations will trail a stop-loss too aggressively, thus failing to give the excursion space necessary for an asset to realize its macro direction momentum.
This article addresses these systemic inefficiencies by proposing a dual-gate architecture that is part of a custom MQL5 trailing class. We adopt a probabilistic Bloom filter for an O(1) ‘tick-deduplication’ as well as a simple Recurrent Neural Network (RNN) for threshold moderation. We aim for more precise trade management. Our methodology ensures that computational effort is reserved mathematically for special events such that stop-loss adjustments are set only by sequential market memory as opposed to stochastic fluctuations.
The Framework and Memory Management
The structural integrity of a custom trailing module in MQL5 depends on its ability to stick to the Standard Library’s inheritance protocols and its internal strategy for memory allocation. By defining the class as ‘CTrailingBloomRNN : public CExpertTrailing’ our Expert Advisor ensures that the module is recognized by the MQL5 Wizard for seamless integration in the Wizard assembly.
This inheritance of our custom class gives us direct access to important trade objects that include ‘m_position’, as well as ‘m_symbol’. This is essential for querying market prices as well as executing and modifying orders. Implementing this manually, despite the available libraries, increases development time and raises the risk of memory leaks and terminal–server desynchronization because the built-in MQL5 libraries are optimized for low latency.
Like many MQL5 Wizard classes, 'CTrailingBloomRNN' inherits a performance-oriented memory layout. In this class we declare and manage only the elements required by the strategy. In our custom class we use an unsigned character array ‘m_bitset[]’, as a key design choice for byte-level memory packing with the probabilistic Bloom filter.
Unlike the regular boolean or integer arrays, the ‘uchar’ array does allow our algorithm to fine-tune individual bits across a continuous memory block which means the deduplication gatekeeper remains lean or not as busy regardless of the ‘m_filter_size’ parameter. At the same time the declaration of ‘m_hidden_state’ acts as a constant repository for the RNN’s sequential memory. The state of this variable outlives destroyed function scopes that run on every new bar. This allows our custom class to maintain mathematical memory of market momentum over several price bars/ticks.
The initialization logic uses pre-allocation by pre-sizing the 'm_bitset' array. The function converts the bit requirement into a byte count. This has the effect of reducing the memory footprint required by a factor of eight. This ensures that even a substantial bitset for thousands of unique price points will consume minimal resources.
Following this allocation, the array resizing, we reset the probabilistic memory to a clean slate, thus preventing our custom class from reacting to ‘junk’ data from RAM residue. We do this by filling this array with zero. Furthermore, the initialization of ‘m_hidden_state = 0.5’ means the neutral baseline equilibrium is started with for the RNN. This numerical value means the first unique price bar processed by the EA does not encounter a saturated state. It gives us a balanced starting point for the hyperbolic tangent activation function.
Probabilistic Price Deduplication
The mitigation of compute redundancy within a high-frequency execution setting is attained through the ‘IsDuplicateTick’ and ‘Hash’ functions. These methods operate as an O(1) gatekeeper, utilizing a Bloom filter to spot and discard redundant price data; in essence we only register significant price swings from previous support/resistance levels before considering whether the trailing stop needs to be adjusted.
The ‘Hash’ method gives us our foundation for probability-based membership testing through the mapping of floating-point data into a discrete integer space. We code this in MQL5 as follows:
//+------------------------------------------------------------------+ //| FNV-1a Style Hash for Bloom Filter | //+------------------------------------------------------------------+ uint CTrailingBloomRNN::Hash(double val, int seed) { uint hash = 2166136261 + seed; string s = DoubleToString(val, 8); for(int i = 0; i < StringLen(s); i++) hash = (hash ^ s[i]) * 16777619; return hash; }
Using the FNV-1a, we initialize an unsigned integer, named hash. We use the FNV offset basis, a regular value in the hashing process that sees the hash value begin at a non-zero high-entropy point. Converting the input double to a string is important for maintaining the integrity of the tick data; whereby enforcing 8 digits of precision where small fluctuations are captured as relatively distinct representations.
The iterative loop,
hash = (hash ^ s[i]) * 16777619;
Performs a bitwise XOR operation against every character. This particular sequence is meant to maximize dispersion across the ‘uint’ bitspace thus minimizing the likelihood of collisions where two different price points map to a similar filter index.
The deduplication engine subsequently performs a logical verification of tick uniqueness. This is done within the function ‘IsDuplicateTick()’, where the translation of generated hash values into specific memory coordinates within a bitset is done. The code for this is given below:
//+------------------------------------------------------------------+ //| Bloom Filter Logic: Tick Deduplication | //+------------------------------------------------------------------+ bool CTrailingBloomRNN::IsDuplicateTick(double price) { uint price_bits = (uint)MathRound(price / m_symbol.Point()); bool already_exists = true; for(int i = 0; i < m_hash_count; i++) { uint h = Hash(price_bits, i) % m_filter_size; int byte_idx = int(h / 8); int bit_idx = int(h % 8); if(!(m_bitset[byte_idx] & (1 << bit_idx))) { already_exists = false; m_bitset[byte_idx] |= uchar(1 << bit_idx); // Mark as seen } } return already_exists; }
In this function, our operations start with rounding off the input price into points, effectively converting it to an integer value. Rounding to raw points could be deemed noisy by some who would feel a renko-like threshold would make more sense and that is a valid argument. However we chose a very rudimentary illustration here and since the code is shared, the reader can make appropriate adjustments. In the subsequent for loop, the algorithm computes the bit coordinates - ‘byte_idx’, identifying specific unsigned characters within the array while the ‘bit_idx’ calculates the position, between 0 and 7, within the byte while using the modulo operation.
The condition if(!(m_bitset[byte_idx] & (1 << bit_idx))) is the core of the O(1) membership check. Here, we are using a bitwise AND to establish if the bit at the calculated coordinate is currently set to zero. If the bit is zero, then the tick is confirmed as unique and as a result the bit gets flipped to one by using bitwise OR operator. m_bitset[byte_idx] |= uchar(1 << bit_idx); . If all hashed bits for a given price are already found to be one, the function will return true. This effectively signals to the calling method that the current tick is a redundant pulse and should be ignored. This then ends up preserving CPU cycles for more meaningful market shifts.
Sequential Neural Moderation
It could be argued that the ‘strategic intelligence’ of our custom trailing class, ‘CTrailingBloomRNN’ is in the ‘UpdateRNN’ method. We have a recurrent neural network here that gives us stop-loss logic with some persistent ‘market-memory’. By processing the deduplication data stream through a hidden state transition matrix, our algorithm is able to overcome the limitations of memoryless, reactive execution.
Before price data can be processed by the recurrent network it should be changed to a format that is scale invariant. The first operation within the neural module is the following declaration of a normalized input:
double input_normalized = (price - m_symbol.Bid()) / m_symbol.Point();
This code line performs a very important normalization by working out the instant distance between the current price and the Symbol’s bid price in terms of raw points. This conversion means the neural network’s input is in effect dimensionless. The intended benefit of this is to prevent the absolute magnitude of the asset price (think $1.0850 vs $150.250) from skewing the weighted sum of the hidden state. Without this step, the network would necessitate retraining for every unique instrument so as to make up for the varying price scales and decimal precision.
The core mathematical transformation of the neural filter is executed in a single high-density line of code as follows:
M_hidden_state = MathTanh(0.5 * m_hidden_state + 0.5 * input_normalized);
This code line implements a simplified version of an RNN state update. The variable ‘m_hidden_state’ stands for the cumulative memory of the price sequence, which is updated by calculating the weighted sum of the prior and current normalized input. With our implementation, the algorithm weights past momentum and current price action equally.
The selection of the ‘MathTanh’ (Hyperbolic Tangent) activation function, is vital for a couple of important reasons:
-
Saturation Bounding: The Tanh function maps all inputs into a tight range of -1 to +1 . This prevents the vanishing or exploding gradient problems that can happen in recurrent architectures, but because we have it the m_hidden_state remains stable even in periods of extreme market volatility.
-
Symmetry: Unlike the Sigmoid function for instance that bounds from 0 - 1, the Tanh is zero-centred and this allows the hidden state to more impartially represent both positive/ bullish momentum as well as negative/bearish momentum with equal sensitivity.
The persistence of the ‘m_hidden_state’ member variable means that every unique tick leaves a recursive mark on the algorithm’s logic. Because h_t depends on h_(t−1), the trailing decision reflects all unique price bars that have passed through the Bloom filter since the position was opened.
This sequential dependency enables the Expert Advisor to spot when a price move is significant, statistically, so as to justify a stop loss adjustment. Effectively we are filtering out stochastic ‘flicker’ that would often trigger premature liquidation in a non-recurrent system. This neural moderation layer changes the trailing stop from a rigid math offset into a dynamic, sequence-aware protocol.
Execution Logic and Overrides
Deduplication and neural moderation are combined in overridden virtual methods of 'CExpertTrailing'. This is in the ‘CheckTrailingStopLong’ and ‘CheckTrailingStopShort’ functions. Overriding replaces the default method behavior; in this implementation we keep the added compute minimal.
The ‘CheckTrailingStopLong’ as well as the ‘CheckTrailingStopShort’ methods are primary conduits for the logic already presented in previous sections above. If we go over the code in these functions, a hierarchical nature of the execution gets laid out:
//+------------------------------------------------------------------+ //| Check Long: Bloom -> RNN -> Execution | //+------------------------------------------------------------------+ bool CTrailingBloomRNN::CheckTrailingStopLong(CPositionInfo *position, double &sl, double &tp) { if(position == NULL) return false; double price = m_symbol.Bid(); //--- 1. Bloom Filter Deduplication if(IsDuplicateTick(price)) return false; //--- 2. Sequential Neural Filter if(MathAbs(m_rnn_threshold) > 0.0)//zero means threshold is not used { UpdateRNN(price); if(MathAbs(m_hidden_state) < m_rnn_threshold) return false; } //--- 3. Trailing Execution double new_sl = price - (m_symbol.StopsLevel() * m_symbol.Point()); double current_sl = position.StopLoss(); if(new_sl > current_sl || current_sl == 0.0) { sl = NormalizeDouble(new_sl, m_symbol.Digits()); return true; } return false; }
The starting validation of if a position is NULL, return false is a requisite safety check that ensures our trailing logic is not applied to non-existent objects. From this, the assignment of price as the current symbol’s Bid captures the most recent action. Following this, we try to filter this price point as to whether or not it is significant i.e. has moved significantly from prior levels, by running the function introduced above ‘if(IsDuplicateTick(price)) return false;’. This also acts as an early cutoff: if the tick is a duplicate, the function returns immediately and saves CPU. By leveraging an O(1) Bloom filter, our algorithm immediately stops the execution loop.
Once a price-bar/tick is identified as unique, it then gets to pass through a secondary ‘gate of intelligence’. Here, we are calling the UpdateRNN function to give us a threshold that also acts as a check that the current state is refreshed with the latest data. It also is an optional step in that if the optimized threshold is set to zero then we do not filter unique prices through the RNN meaning we only use the deduplication algorithm.
If this moderation is active, i.e. the m_rnn_threshold is not zero, we perform an absolute value verification. This ensures that unless the internal ‘memory’ of the price sequence is strong enough to breach the user-defined activation gate, the stop-loss remains as is. This specific mechanism prevents the EA from chasing stochastic ‘flicker’ and we restrict modifications to periods of actual momentum.
Once a price bar survives both filters, the custom class then goes on to calculate physical stop-loss parameters. The line ‘new_sl = price - (m_symbol.StopsLevel() * m_symbol.Point());’ works out the new level while keeping within broker required Stops Level. The condition ‘if(new_sl > current_sl || current_sl == 0.0)’ enforces the core directive of a trailing stop - price may only move in the direction of the open position so as to lock in gains. Finally, the command ‘sl = NormalizeDouble(new_sl, m_symbol.Digits());’ is crucial since we round the floating point price to a price acceptable by the broker trade server. This override function only returns true if all probability-based conditions are satisfied.
Empirical Analysis
The transition from theoretical code to empirical validation is important for sizing up the impact of probability-based filters as well as neural networks. We can track the risk-reward tradeoffs made by our trailing class over a standard test period from 2023.03.01 to 2026.04.15 for the symbol GBPJPY. We test it in two modes. The first is with just the baseline Bloom filter, while the 2nd uses this baseline plus the RNN filter.
In the first test iteration, the EA used the Bloom filter for tick deduplication and no RNN and the test report indicated a highly efficient although aggressive risk management profile:
Our Expert Advisor achieved a profit factor of 3.68 and a Sharpe ratio of 3.89, which is consistent given the 12 trades in the forward walk. Other good points of the baseline were strict drawdown control where maximum balance drawdown was restricted to 1.05%. Without the RNN thresholding the trailing stop, the EA remained highly sensitive to the most recent price action. From our report, this prevented large adverse drawdowns, and also resulted in a ‘tight’ trade management that limited the gross profit to just over USD 1,000. This seems to imply that the EA prioritized immediate protection of realized gains instead of the pursuit of broader extensions. When we introduce the RNN filter we are presented with the following post optimization report:
This iteration introduced the ‘m_rnn_threshold’ so as to moderate stop-loss adjustments. These changes are based on the hidden state of the price sequence. This modification fundamentally altered the strategy's geometric outcomes. Shockingly, despite the extra filter, it was not for the better. While the gross profit increased by 80% to 1,833.02, the net profit shrunk from more than $700 to barely $100. This could be down to the introduction of a math ‘memory lag’.
Also noteworthy is that the maximum equity drawdown expanded to 11.65% while the Sharpe ratio collapsed to 0.12. The gross loss also ballooned to -$1,719.48 from just -$275.54 despite the total number of trades remaining constant at 12. This could suggest that in this particular instance where we tested the GBPJPY, the RNN moderation caused the Expert Advisor to hold positions longer as it awaited ‘m_hidden_state’ confirmation of a trend reversal.
To draw firmer conclusions, the approach should be tested on more symbols and across longer periods. This divergence though between these reports could point to the ‘double-edged-sword’ nature of neural network moderation. While the Bloom filter regularly reduced compute overhead, the RNN layer of our pipeline appears to have transitioned the EA from a ‘scalper’ to a ‘trend-follower’. This underscores the importance of rigorous threshold calibration for the RNN. Should the input threshold vary across different market settings?
Take Aways
The implementation of our custom trailing class ‘CTrailingBloomRNN’, gives us some resolving to the systemic inefficiencies of compute latency as well as memoryless execution protocols as identified in our opening problem statement. By bringing together probability-based data structures and a recurrent neural network this article attempts to make the case that trade logic can be both computationally lean and strategically aware.
The FNV-1a hashed Bloom filter tackles hardware bottlenecks that are linked with high-frequency price streams, ensuring O(1) deduplication. In parallel to this the hyperbolic tangent-activated RNN gives us a dynamic sequence-aware threshold that works towards resolving the limitations of rigid, static trailing stops.
The empirical evidence from our forward-testing reports confirms that while the Bloom filter consistently optimizes CPU resource use, the neural network moderation layer adds a complex tradeoff between immediate preservation of capital and capturing the macro-trend. The large increase in gross profitability observed in the moderated test (2nd report) does, to a point, validate the efficacy of the hidden state in filtering out stochastic market noise.
However, the linked expansion in equity drawdown is effectively an important reminder of the ‘memory-lag’ inherent to sequential models. This could necessitate precise calibration of the input parameter ‘m_rnn_threshold’ in order to better align with particular risk-tolerance parameters.
Conclusion
In summary, the transition from reactive math offsets to proactive, intelligent gatekeeping could represent an evolution in MQL5 algorithmic design. The custom class prototyped here could be a key starting point in equipping traders with tools necessary to navigate complexities of modern market microstructures with more precision. Reserving compute resources for necessary events while executing through a lens of market memory, we sought to establish a robust framework for trade management primarily for testing environments and hopefully live execution eventually.
| name | description |
|---|---|
| wz_88.mq5 | Wizard Assembled Expert Advisor |
| TrailingBlooming.mqh | Custom Trailing Class used in MQL5 Wizard Assembly |
The attached custom class TrailingBlooming.mqh is meant to be assembled into an Expert Advisor using the MQL5 Wizard. The custom signals we used in this testing were SignalEnvelopes.mqh and SignalRSI.mqh. Guidance on how to assemble an Expert Advisor with the MQL5 Wizard can be found here and here for new readers.
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Features of Custom Indicators Creation
Price Action Analysis Toolkit Development (Part 68): Price-Attached RSI Panel in MQL5
Features of Experts Advisors
Feature Engineering for ML (Part 2): Implementing Fixed-Width Fractional Differentiation in MQL5
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use

