From Novice to Expert: Developing a Liquidity Strategy
Contents
Introduction
Today, we focus on modeling an Expert Advisor (EA) based on liquidity zones. In previous discussions, we concentrated on identifying and validating these zones, and the results have been both encouraging and promising. However, one critical challenge remains: automating the trading process. This can only be achieved by translating the strategy into executable logic using MQL5, including robust trade execution and risk management rules.
To do this effectively, we will first establish a clear conceptual understanding of the strategy. Once the logic is well defined, we will proceed to implement it in code.
Understanding the strategy
Through our analysis, we have established that a liquidity zone can often be represented by a single candle. Such a candle typically emerges after a period of price pause or consolidation and encapsulates liquidity that has formed over time or across lower timeframes. This behavior is precisely what produces the structural model we are working with in this study.
For clarity and accessibility, we deliberately adopt a simplified approach. Rather than relying on complex multi-bar formations, we define our conditions using minimal price information. This allows us to focus on the core mechanics of the strategy while making the initial implementation in code straightforward and easier to validate.
The objective of this strategy is to return price to the identified liquidity structure (see Fig. 1 below) and build positions from that area, ultimately targeting the most recent swing high or swing low, depending on whether the setup is bullish or bearish. While the concept of a retest is central to this model, it is also important to recognize that it has become a common and widely studied idea. As a result, such levels are increasingly vulnerable to manipulation. Market participants with significantly larger capital can push prices beyond obvious boundaries, triggering stop losses and forcing weaker positions out of the market before the intended move unfolds.

Fig. 1. Swing B-C return to test liquidity zone
For this reason, risk management cannot be treated as an afterthought. It must be an integral part of both the strategy design and its algorithmic execution. How the Expert Advisor defines risk, places stop losses, and manages exposure is just as critical as how it identifies valid trade setups.
It is also essential to acknowledge that not every setup will succeed. No single trade guarantees a profitable outcome. Instead, performance emerges over time through the aggregation of wins and losses. It is this cumulative result—measured across a sufficiently large sample of trades—that ultimately determines whether a strategy is profitable or unprofitable.
With this foundation established, the next section will focus on translating the strategy into MQL5 code. Once implemented, we will proceed to test and evaluate its behavior under realistic market conditions.
Implementation
Before implementing the trading logic, we will create a new Expert Advisor project in MetaEditor using the MQL5 Wizard. This wizard automatically generates a default template that includes event handlers and placeholder code. For the purpose of this article, we remove all auto-generated logic and leave only the metadata section, as shown below.
Code:
//+------------------------------------------------------------------+ //| Single_Candle_Liquidity_Trader.mq5 | //| Copyright 2025, Clemence Benjamin | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Clemence Benjamin" #property link "https://www.mql5.com" #property version "1.00"
This keeps the focus on understanding and building the strategy step by step as the implementation unfolds in the following sections. It is wise to manually type the code to gain familiarity with the structure; however, for convenience, the complete source file is provided in the attachments at the end of the article for those who prefer not to type everything manually.
Once the EA metadata and inputs are defined, we include the trading library responsible for all order execution. Rather than working with low-level trade functions, we rely on the CTrade class, which provides a safer and more structured interface.
Code:
#include <Trade/Trade.mqh> CTrade trade;
By declaring a single global CTrade object, we centralize all trading operations—pending orders, stop losses, take profits, and magic number assignment—making the code cleaner and easier to maintain.
Declaring Inputs and Strategy Controls
Before we write any execution logic, we first declare the inputs. This is a deliberate step. Inputs define the external behavior of the Expert Advisor and allow traders to adapt the strategy without touching the source code. By exposing these parameters, we separate strategy logic from user control, which is a key principle in professional EA design.
Code:
input double LotSize = 0.10; input int MaxSpreadPoints = 30; input bool AllowBuy = true; input bool AllowSell = true; input double RatioMultiplier = 3.0; input double SL_Offset_Points = 20; input int PendingExpiryHours = 24; input ulong MagicNumber = 777333;
Here, we declare each parameter with a clear operational purpose:
- We declare LotSize to control the fixed trade volume per pending order. This keeps risk sizing predictable and avoids dynamic lot calculation at this stage.
- We introduce MaxSpreadPoints to prevent order placement during unfavorable liquidity conditions, such as news spikes or market rollovers.
- We use AllowBuy and AllowSell as directional filters, giving the trader the option to disable one side of the market without modifying the logic.
- Likewise, we define RatioMultiplier as the core structural control of the strategy. This input determines how much larger the impulse candle must be relative to the base candle, allowing us to tune the sensitivity of liquidity compression detection.
- We add SL_Offset_Points to deliberately push the stop loss beyond the liquidity zone, reducing the likelihood of being removed by short-term manipulation or false breakouts.
- We include PendingExpiryHours as a temporal risk control. If the price fails to return to the liquidity zone within this time window, the setup is no longer considered valid.
- Finally, we declare MagicNumber to uniquely tag all orders and positions opened by this EA, ensuring clean separation from other strategies running on the same account.
By structuring inputs this way, we make the EA flexible, testable, and user-friendly, while keeping the internal logic intact.
Declaring Global Variables
Next, we declare the global variables that must persist across ticks. In this strategy, we deliberately keep global state minimal.
Code:
datetime lastBarTime = 0;
This variable is used exclusively to detect the opening of a new candle. Controlling execution frequency is critical in automated systems to prevent duplicated trades and inconsistent backtesting results.
Detecting the Opening of a New Candle
Before any trading logic is evaluated, we enforce a one-candle-one-execution rule. This ensures that the EA only reacts once per completed candle, regardless of tick frequency.
Code:
bool IsNewBar() { datetime currentBar = iTime(_Symbol, _Period, 0); if(currentBar != lastBarTime) { lastBarTime = currentBar; return true; } return false; }
Here, we compare the timestamp of the current candle with the last processed one. When a new candle forms, we update the stored time and allow execution to proceed.
This mechanism guarantees:
- Non-repainting logic
- Stable Strategy Tester behavior
- Predictable order placement timing
Measuring Candle Range
The core of this strategy is range-based comparison, not candle bodies or indicators. To support this, we introduce a helper function that returns the full high–low range of a candle.
Code:
double CandleRange(int index) { return MathAbs(iHigh(_Symbol,_Period,index) - iLow(_Symbol,_Period,index)); }
Encapsulating this calculation improves readability and allows us to reuse the logic consistently throughout the EA.
Multi-Level Liquidity Entry for Buy Setups
Rather than entering at a single price, we distribute limit orders across the base candle range. This models how liquidity is typically absorbed by the market.
Code:
void PlaceBuyLimits(double highB, double lowB, double sl, double tp, datetime expiry) { double mid = (highB + lowB) / 2.0; trade.BuyLimit(LotSize, highB, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, expiry); trade.BuyLimit(LotSize, mid, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, expiry); trade.BuyLimit(LotSize, lowB, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, expiry); }
By placing orders at the high, midpoint, and low of the liquidity candle, we:
- Improve average entry price
- Reduce dependency on perfect timing
- Capture partial fills even during shallow pullbacks
Multi-Level Liquidity Entry for Sell Setups
We apply the same logic symmetrically for bearish conditions.
Code:
void PlaceSellLimits(double highB, double lowB, double sl, double tp, datetime expiry) { double mid = (highB + lowB) / 2.0; trade.SellLimit(LotSize, highB, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, expiry); trade.SellLimit(LotSize, mid, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, expiry); trade.SellLimit(LotSize, lowB, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, expiry); }
This symmetry ensures consistent behavior across both market directions and simplifies validation and optimization.
The Main Execution Loop: OnTick()
All strategy logic is coordinated inside the OnTick() function. This is where conditions are evaluated, risk is checked, and orders are placed.
Code:
void OnTick() { if(!IsNewBar()) return;
We immediately enforce new-bar execution. If no new candle has formed, the EA exits without further processing.
Spread Protection and Trade Identification
Before evaluating market structure, we validate trading conditions.
Code:
if(SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) > MaxSpreadPoints) return; trade.SetExpertMagicNumber(MagicNumber);
This prevents entries during unfavorable spreads and ensures all trades are uniquely identifiable.
Defining Candle Roles
We now explicitly define which candles participate in the model.
Code:
int A = 1; // Impulse candle (closed) int B = 2; // Base candle
- Candle B represents liquidity compression.
- Candle A represents impulse expansion.
The currently forming candle (index 0) is intentionally ignored.
Applying the Candle Ratio Filter
This is the structural backbone of the strategy.
Code:
double rangeA = CandleRange(A); double rangeB = CandleRange(B); if(rangeB > rangeA / RatioMultiplier) return;
Here, we enforce that the base candle must be significantly smaller than the impulse candle. The sensitivity of this condition is fully controlled by RatioMultiplier.
Directional Confirmation
We now confirm that both candles agree directionally.
Code:
double openA = iOpen(_Symbol,_Period,A); double closeA = iClose(_Symbol,_Period,A); double openB = iOpen(_Symbol,_Period,B); double closeB = iClose(_Symbol,_Period,B);
This step ensures we are trading continuation, not exhaustion.
Stop Loss Offset and Order Expiry
To account for manipulation and false breaks, we introduce a configurable stop offset and time-based invalidation.
Code:
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double offset = SL_Offset_Points * point; datetime expiry = TimeCurrent() + PendingExpiryHours * 3600;
These two parameters work together to control risk in both price and time dimensions.
Bullish Setup Execution
Code:
if(AllowBuy && closeA > openA && closeB > openB) { double sl = lowB - offset; double tp = highA; PlaceBuyLimits(highB, lowB, sl, tp, expiry); }
We place buy limits inside the liquidity zone, protect beyond it, and target the impulse high.
Bearish Setup Execution
Code:
if(AllowBuy && closeA > openA && closeB > openB) { double sl = lowB - offset; double tp = highA; PlaceBuyLimits(highB, lowB, sl, tp, expiry); }
The logic mirrors the bullish case, preserving structural consistency.
At this stage, we have fully modeled a single-candle liquidity strategy, from structure detection to execution and risk control. The system intentionally avoids indicators and focuses purely on price behavior, liquidity, and disciplined execution.
In the next section, we can proceed to testing methodology, parameter sensitivity, and result interpretation.
Testing
Here, I present the initial test results obtained by running the Expert Advisor in the MetaTrader 5 Strategy Tester. For readers who are new to the platform, the first step is to locate the EA associated with this project in the Navigator window under the Experts directory. Once found, right-click on the EA and select Test from the context menu. This action launches the Strategy Tester, where various types of tests can be performed.
Before considering any form of optimization, our primary objective is to verify that the EA executes mechanically and consistently according to the intended strategy rules. At this stage, we are not concerned with profitability; instead, we want to confirm that the logic translates correctly from concept to code and that orders are placed, managed, and expired exactly as designed. Only after this validation step is completed do we move on to optimization and parameter refinement.
It is worth noting that some strategies fail at this early stage. In certain cases, even after genetic optimization, a strategy may prove structurally unsound and therefore unsuitable for further development. Identifying such outcomes early saves time and prevents unnecessary overfitting.
The screencast below demonstrates the strategy in action and confirms that the conceptual model has been successfully brought to life. Although additional work is required before the system can be classified as a profitable trading model, the optimization results presented below—shown in graphical form—already indicate promising regions of performance. With further filtering and refinement aimed at eliminating negative outcomes, these results provide encouraging signals for continued development.

Fig. 2. Testing
Conclusion
In this article, we demonstrated how a single-candle liquidity pattern, consisting of a base candle and an impulse candle, can be transformed from a conceptual trading idea into a fully automated trading model using MQL5. The resulting Expert Advisor executes the strategy rules as intended: pending orders are placed according to the defined structure, some trades are triggered and closed profitably, others result in losses, and some orders remain unfilled.
Visual testing over an extended historical period (from 2005 to the present) reveals that not all identified liquidity zones are revisited by price. In some cases, the retest may occur after a long delay or not occur at all. To manage this behavior, a time-based expiry mechanism was implemented to remove pending orders automatically after a predefined duration, preventing outdated trade ideas from remaining active indefinitely.
From an educational standpoint, this work illustrates the process of converting abstract trading concepts into precise, rule-based execution logic. The presented Expert Advisor is intended for learning and research purposes only and should be tested on a demo account. Several areas remain open for further development, including session-based trading filters, additional market condition filters, and integration with complementary analytical tools.
Overall, this study confirms that trading strategies can be automated once their conditions are clearly defined and expressed programmatically. It is hoped that this article provides practical insights into both strategy design and Expert Advisor implementation.
Your feedback and contributions are welcome in the comments section below.
Key lessons and related attachments are provided in the following sections.
Key Lessons
| Key Lessons | Description: |
|---|---|
| Single-Candle Liquidity: | Liquidity does not always require complex multi-bar structures; a single candle can encapsulate sufficient consolidation and resting orders to define a valid liquidity zone. |
| Range Over Body | Measuring the full high-to-low range provides a more objective representation of volatility compression than candle body size alone. |
| Impulse–Base Ratio: | A configurable ratio between impulse and base candles transforms a discretionary visual concept into a precise and testable trading condition. |
| Closed-Candle Logic: | Evaluating conditions only on closed candles eliminates repainting and ensures consistency between backtesting and live execution. |
| Multi-limit entries: | Distributing pending orders across the liquidity range improves fill probability and average entry quality compared to single-price execution. |
| Directional Alignment: | Requiring both the base candle and impulse candle to share directional bias helps filter noise and strengthens continuation setups. |
| Structural targets: | Using impulse candle extremes as profit targets anchors exits to real market structure instead of arbitrary reward assumptions. |
| Stop-Loss Buffering: | An adjustable stop-loss offset accounts for liquidity sweeps and reduces premature exits caused by short-term manipulation. |
| Order Expiry Control: | Pending orders lose relevance over time; applying expiry ensures outdated setups are automatically removed from the market. |
| Sample size reality: | Strategy profitability emerges only when results are aggregated over many trades, not from isolated outcomes. |
| Rule-Based Automation: | Clearly defined rules allow discretionary ideas to be converted into repeatable, emotion-free execution models. |
| Execution Discipline: | Automated execution enforces consistency and prevents discretionary interference that often degrades performance. |
| Selective Filtering: | Not every detected structure should be traded; filters such as spread limits and session rules improve robustness. |
| Strategy Validation: | Visual testing and optimization are essential to confirm that the strategy behaves mechanically as intended. |
| Iterative Development: | Trading strategies evolve through continuous testing, feedback, and refinement rather than one-time implementation. |
Attachments
| Source File Name | Version | Description: |
|---|---|---|
| Single_Candle_Liquidity_Trader.mq5 | 1.0 | Implements the Single Candle Liquidity Trader Expert Advisor. The file contains the complete strategy logic for identifying liquidity compression using a base candle, validating impulse continuation via a configurable range ratio, and executing multi-level pending limit orders with built-in risk management, stop-loss offset protection, and time-based order expiry. |
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 58): Range Contraction Analysis and Maturity Classification Module
Features of Experts Advisors
Introduction to MQL5 (Part 38): Mastering API and WebRequest Function in MQL5 (XII)
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use