Introduction: Why Most Traders Ignore the Order Book (And Why You Shouldn't)
Every tick you see on your MT5 chart is a ghost—a record of a transaction that already happened. What if you could peek at the intentions of traders before those transactions occur? That's exactly what the Depth of Market (DOM) offers: a live feed of pending limit orders sitting on the bid and ask sides.
In my 12 years of building Expert Advisors, I've seen countless strategies that rely on lagging indicators like moving averages or RSI. They work—until they don't. The DOM is different. It's a leading indicator of supply and demand imbalance. When I first coded a simple DOM-based scalper for EUR/USD in 2021, I was shocked at how often the order book predicted short-term reversals that no oscillator caught.
Yet, most algo traders ignore the DOM because it's not a simple "buy when cross over" signal. It requires interpretation. In this guide, I'll show you exactly how to access DOM data in MQL5, calculate a bid-ask imbalance ratio, and build a working strategy that trades based on order book pressure.
But before we dive into code, let me give you a reality check. DOM trading is not a magic bullet. It works best on highly liquid instruments during active market hours. On low-liquidity pairs or during news events, the DOM can flip from extreme buying to extreme selling in milliseconds. You've been warned.
Understanding the Depth of Market in MT5
What the DOM Actually Shows
The DOM in MT5 displays aggregated limit orders from your broker's liquidity providers. Each row shows a price level and the total volume of pending buy or sell limit orders at that price. The bid side shows buy limits (orders waiting to buy at lower prices), while the ask side shows sell limits (orders waiting to sell at higher prices).
Key data points the DOM provides:
- Price levels with order volume
- Cumulative volume at each price level
- Market depth (how many levels are available)
- Spread between best bid and best ask
Not all brokers provide DOM data. You need a broker that offers Market Depth access—typically ECN/STP brokers. Check your broker's specifications under the "Market Watch" section. If you see a "Depth" column, you're good. Also note that MT4 does not natively support DOM—this is strictly an MT5 feature, which is why we're using MQL5 here.
How DOM Differs from Tick Volume
A common misconception is that tick volume (the number of price changes) is the same as DOM depth. It's not. Tick volume tells you how many transactions occurred, but DOM tells you the size of pending orders waiting to be filled. Think of tick volume as the exhaust fumes of a car, while DOM is the fuel in the tank. Both are useful, but DOM gives you forward-looking information.
For example, you might see 100 ticks per second on EUR/USD during London open, but the DOM could show 50,000 contracts on the bid side versus 10,000 on the ask side. That imbalance tells you a big buyer is accumulating, something tick volume alone would never reveal.
The Core Concept: Bid-Ask Imbalance
Think of the order book as a tug-of-war. When there are more buy limit orders (volume on bid side) than sell limit orders (volume on ask side), buyers are "pulling" harder. The price tends to move up as market orders eat through the thinner ask side. Conversely, heavy ask volume suggests sellers are in control.
The imbalance ratio is calculated as:
Imbalance = (Total Bid Volume - Total Ask Volume) / (Total Bid Volume + Total Ask Volume)
Values range from -1 (extreme selling pressure) to +1 (extreme buying pressure). A value near zero means the book is balanced—no clear edge.
I've found that when this ratio crosses above +0.6, price tends to push upward within the next 3-5 ticks. Below -0.6, expect a drop. These thresholds work well on liquid pairs like EUR/USD, GBP/USD, and XAU/USD. However, you should backtest your own thresholds because different instruments have different DOM profiles.
Why Imbalance Works: The Mechanics
Imagine the bid side has 10,000 contracts at 1.1000, and the ask side has only 2,000 contracts at 1.1001. A market buy order of 3,000 contracts will eat through the entire ask side, pushing price higher. The DOM captures this imbalance before the market order arrives. This is why DOM signals are leading rather than lagging.
There's a nuance here: the DOM shows limit orders, not market orders. A large bid wall doesn't guarantee price will rise—it could be a spoof order placed by a market maker to trick algos. That's why I always use a confirmation filter, like waiting for the imbalance to persist for at least two consecutive DOM updates before entering. This reduces false signals from spoofing.
Practical Implementation in MQL5
Accessing DOM Data in MQL5
MT5 provides the MarketBookGet() and MarketBookAdd() functions to subscribe to DOM updates. Here's the skeleton code to fetch DOM data:
//+------------------------------------------------------------------+
//| Get DOM cumulative volumes |
//+------------------------------------------------------------------+
bool GetDOMImbalance(string symbol, double &imbalance)
{
if(!MarketBookAdd(symbol))
{
Print("Failed to subscribe to DOM for ", symbol);
return false;
}
MqlBookInfo book[];
if(!MarketBookGet(symbol, book))
{
Print("Failed to get DOM data for ", symbol);
return false;
}
double bidVolume = 0, askVolume = 0;
int depth = ArraySize(book);
for(int i = 0; i < depth; i++)
{
if(book[i].type == BOOK_TYPE_BUY)
bidVolume += book[i].volume;
else if(book[i].type == BOOK_TYPE_SELL)
askVolume += book[i].volume;
}
if(bidVolume + askVolume == 0)
{
imbalance = 0;
return false;
}
imbalance = (bidVolume - askVolume) / (bidVolume + askVolume);
return true;
}
Notice I use MarketBookAdd() before MarketBookGet(). This is critical—you must subscribe to the symbol's DOM before reading it. The MqlBookInfo structure contains the price, volume, and type (buy/sell) for each level.
Understanding MqlBookInfo Structure
The MqlBookInfo array contains entries for each DOM level. Each entry has three key fields:
| Field | Type | Description |
|---|---|---|
| price | double | The price level of the order. |
| volume | long | Total volume of orders at this price level. |
| type | ENUM_BOOK_TYPE | BOOK_TYPE_BUY for bid side, BOOK_TYPE_SELL for ask side. |
Building a Simple DOM-Based Scalper EA
Let's create a strategy that enters a buy when the imbalance ratio exceeds +0.65 and exits when it drops below +0.2. For sells, the thresholds are -0.65 and -0.2. This is a mean-reversion approach—we're betting that extreme imbalance will correct.
Here's the core trading logic for OnTick():
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
double imbalance;
if(!GetDOMImbalance(Symbol(), imbalance))
return;
// Check position existence
bool hasBuy = false, hasSell = false;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetString(POSITION_SYMBOL) == Symbol())
{
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
hasBuy = true;
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
hasSell = true;
}
}
}
// Entry conditions
if(!hasBuy && imbalance > 0.65)
{
double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
double sl = ask - 20 * point;
double tp = ask + 40 * point;
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = Symbol();
request.volume = 0.1;
request.type = ORDER_TYPE_BUY;
request.price = ask;
request.sl = sl;
request.tp = tp;
request.deviation = 10;
request.comment = "DOM Scalper";
OrderSend(request, result);
if(result.retcode != TRADE_RETCODE_DONE)
Print("Buy order failed: ", result.retcode);
}
if(!hasSell && imbalance < -0.65)
{
double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
double sl = bid + 20 * point;
double tp = bid - 40 * point;
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = Symbol();
request.volume = 0.1;
request.type = ORDER_TYPE_SELL;
request.price = bid;
request.sl = sl;
request.tp = tp;
request.deviation = 10;
request.comment = "DOM Scalper";
OrderSend(request, result);
if(result.retcode != TRADE_RETCODE_DONE)
Print("Sell order failed: ", result.retcode);
}
// Exit conditions
if(hasBuy && imbalance < 0.2)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = Symbol();
request.volume = PositionGetDouble(POSITION_VOLUME);
request.type = ORDER_TYPE_SELL;
request.price = SymbolInfoDouble(Symbol(), SYMBOL_BID);
request.deviation = 10;
OrderSend(request, result);
break;
}
}
}
}
if(hasSell && imbalance > -0.2)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionSelectByTicket(ticket))
{
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = Symbol();
request.volume = PositionGetDouble(POSITION_VOLUME);
request.type = ORDER_TYPE_BUY;
request.price = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
request.deviation = 10;
OrderSend(request, result);
break;
}
}
}
}
}
This is a bare-bones version. In production, you'd add position management, time filters, and slippage protection. But the core logic is sound.
Input Parameters for Your EA
| Parameter | Type | Default | Description |
|---|---|---|---|
| ImbalanceBuyThreshold | double |






