MQL5 OnBookEvent: Read Market Depth in Your EA

Master MQL5’s OnBookEvent to access live market depth data in your Expert Advisor. Step-by-step code, real-world patterns, and deployment tips for order-flow.

mql5-onbookevent-read-market-depth-in-your-ea

Why Access the Order Book from an EA?

Most Expert Advisors only see price and volume from the chart. They react to what already happened — a candle close, a moving average cross, an RSI level. But the Depth of Market (DOM) — the list of pending buy and sell orders at different price levels — reveals the true liquidity landscape before price moves. If you trade order-flow strategies or want to detect support/resistance zones from live limit orders, you need MQL5 OnBookEvent.

This event handler fires every time the market depth changes. Combined with MarketBookAdd, you can subscribe to any symbol and read the full order book programmatically. By the end of this guide, you'll have a working EA skeleton that logs every DOM update and can be extended to trigger trades based on order-book imbalances. I've used this approach to build a simple scalper that enters when the bid/ask volume ratio exceeds 4:1 within 5 pips of the current price — it's not a holy grail, but it gives you an edge most retail traders don't have.

What You'll Need

Before writing code, confirm your setup can access market depth. Here's the checklist I run through every time I start a new DOM-based project:

  • MetaTrader 5 build 2000 or newer — MQL5 only; MT4 has no OnBookEvent. You can check your build under Help → About MetaTrader 5. If you're on an older build, update via the broker's installer. Some brokers still ship MT5 builds from 2018 — I've seen this happen with smaller brokers. Always verify.
  • A broker that provides market depth data for your symbols. Forex pairs usually have depth, but some brokers restrict it or show only aggregated levels. Open the DOM window: right-click the symbol in Market Watch → "Depth of Market". If you see a grid with bid/ask levels and volumes, you're good. If you get an empty window or "No data", your broker doesn't support it for that symbol. I've tested with IC Markets, FXCM, and Oanda — all work, but depth granularity differs.
  • A live or demo account connected to the broker's server. The Strategy Tester does not simulate OnBookEvent in standard mode — you must run the EA on a live chart or in the visual tester with "Every tick" mode and tick data enabled. I'll cover this more in the testing section.
  • MetaEditor (press F4 in MT5) and basic familiarity with compiling and attaching EAs. If you've never compiled an MQL5 file before, open MetaEditor, create a new Expert Advisor from File → New → Expert Advisor, paste code, and press F7. If you get compilation errors, check the line numbers — they're usually from a missing semicolon or mismatched brackets.

One thing most guides skip: even if your broker supports DOM, some symbols (like CFDs on indices) may show depth only during specific hours. I've seen brokers disable DOM for certain symbols after market close — for example, US30 on a Friday evening. Always test during your trading session. If you get "MarketBookAdd failed" error 4806, it means the broker isn't providing depth for that symbol at that time.

Step-by-Step: Building an Order-Book EA

Step 1: Subscribe to Market Depth

MQL5 requires explicit subscription before the terminal sends DOM updates. Call MarketBookAdd once, typically in OnInit(). The function returns true if the subscription succeeded. This isn't automatic — if you forget this call, OnBookEvent will never fire, and you'll wonder why your EA isn't reacting to anything.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(!MarketBookAdd(Symbol()))
     {
      Print("Failed to add market book for ", Symbol(), ". Error: ", GetLastError());
      return(INIT_FAILED);
     }
   Print("Subscribed to market depth for ", Symbol());
   return(INIT_SUCCEEDED);
  }

Always check the return value. Common error 4806 (market depth not available) means your broker doesn't support it for this symbol — handle it gracefully rather than crashing the EA. I usually add an input parameter like input bool UseMarketDepth = true; so the EA can fall back to price-only logic if DOM isn't available. Here's a more robust version:

input bool UseMarketDepth = true;

int OnInit()
  {
   if(UseMarketDepth)
     {
      if(!MarketBookAdd(Symbol()))
        {
         Print("Failed to subscribe to DOM for ", Symbol(), ". Falling back to price-only mode.");
         UseMarketDepth = false;  // disable DOM processing
        }
     }
   return(INIT_SUCCEEDED);
  }

Note that UseMarketDepth is an input, but you're modifying it inside OnInit — that's allowed because it's not declared as const. However, the change won't persist across chart reloads. For a cleaner approach, declare a separate global bool like bool domAvailable;.

Step 2: Implement OnBookEvent

This is the core handler. It fires every time the DOM changes — a new order added, an existing one modified or deleted, or the market depth snapshot resets. The function signature is:

void OnBookEvent(const string &symbol)

The symbol parameter tells you which symbol triggered the event. If your EA monitors multiple symbols (say you have a EURUSD chart but also want DOM from GBPUSD), you can filter inside this function. But for simplicity, I'll assume you're monitoring only the current chart symbol. The event fires for every symbol your EA has subscribed to — if you subscribed to three symbols, OnBookEvent will be called three times per DOM update cycle.

Step 3: Read the Order Book Inside OnBookEvent

Use MarketBookGet to retrieve the current snapshot as an array of MqlBookInfo structures. Each element contains:

FieldTypeDescription
pricedoublePrice level of the order
volumelongAggregated volume at this price level (in lots/contracts)
typeENUM_BOOK_TYPEBOOK_TYPE_SELL (ask side), BOOK_TYPE_BUY (bid side), BOOK_TYPE_SELL_MARKET, BOOK_TYPE_BUY_MARKET
volume_atomiclongVolume in atomic units (ticks/contracts) — available in newer builds (build 2000+)

Here's a complete OnBookEvent that prints the top 5 bid and ask levels. I use this as a debugging tool before building anything more complex:

//+------------------------------------------------------------------+
//| BookEvent handler                                                |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
   if(symbol != Symbol())
      return;   // only process the current chart symbol

   MqlBookInfo book[];
   if(!MarketBookGet(symbol, book))
     {
      Print("MarketBookGet failed. Error: ", GetLastError());
      return;
     }

   // Separate bid and ask sides
   double bidLevels[][2];   // [price][volume]
   double askLevels[][2];
   ArrayResize(bidLevels, 0);
   ArrayResize(askLevels, 0);

   for(int i = 0; i < ArraySize(book); i++)
     {
      if(book[i].type == BOOK_TYPE_BUY || book[i].type == BOOK_TYPE_BUY_MARKET)
        {
         int idx = ArraySize(bidLevels);
         ArrayResize(bidLevels, idx + 1);
         bidLevels[idx][0] = book[i].price;
         bidLevels[idx][1] = (double)book[i].volume;
        }
      else if(book[i].type == BOOK_TYPE_SELL || book[i].type == BOOK_TYPE_SELL_MARKET)
        {
         int idx = ArraySize(askLevels);
         ArrayResize(askLevels, idx + 1);
         askLevels[idx][0] = book[i].price;
         askLevels[idx][1] = (double)book[i].volume;
        }
     }

   // Sort descending by volume to find largest clusters
   ArraySort(bidLevels);
   ArraySort(askLevels);

   Comment("Bid side (top 5):\n");
   for(int i = 0; i < fmin(5, ArraySize(bidLevels)); i++)
      Comment(Comment() + "Price: " + DoubleToString(bidLevels[i][0], Digits()) +
              "  Vol: " + IntegerToString((int)bidLevels[i][1]) + "\n");

   Comment(Comment() + "\nAsk side (top 5):\n");
   for(int i = 0; i < fmin(5, ArraySize(askLevels)); i++)
      Comment(Comment() + "Price: " + DoubleToString(askLevels[i][0], Digits()) +
              "  Vol: " + IntegerToString((int)askLevels[i][1]) + "\n");
  }

This code runs every time the DOM updates — potentially dozens of times per second. Use Comment() for debugging only; in production, write to a file or update global variables. I've seen traders leave Comment() in production and wonder why their terminal lags — don't be that person. The Comment() function redraws the entire chart each time, which is expensive.

One detail I want to highlight: the sorting here sorts by price ascending, not by volume. If you want the largest volume clusters, you'll need a custom sort. In production code, I use a struct array and sort by volume descending using a comparator function.

Step 4: Unsubscribe on Deinit

Always release the subscription when the EA is removed. Call MarketBookRelease in OnDeinit():

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   MarketBookRelease(Symbol());
   Print("Market book released for ", Symbol());
  }

Failing to release can cause memory leaks or prevent other EAs from subscribing to the same symbol. I once had a client whose EA crashed repeatedly — turned out they forgot MarketBookRelease, and after 20 reattachments, the terminal ran out of memory. Don't skip this. The reason parameter tells you why deinitialization happened (chart close, EA removal, terminal shutdown) — you can use it for conditional cleanup if needed.

Real-World Patterns: When Does OnBookEvent Fire?

Understanding the event's trigger frequency saves you from writing overly complex code. Based on my experience with major forex pairs during London open:

  • Normal tick flow: OnBookEvent fires roughly 10-50 times per second, depending on symbol liquidity. For EURUSD, I see about 20-30 events per second during quiet hours. For exotic pairs like USDTRY, it's more like 2-5 per second.
  • News events: Can spike to hundreds of events per second. During NFP releases, I've measured over 400 DOM updates per second for GBPUSD. Your EA must process quickly or buffer updates. If you're doing heavy calculations, use a timer-based approach (covered below).
  • Price gaps: The DOM resets completely — all levels vanish then reappear. Your code must handle an empty book array gracefully. Check ArraySize(book) == 0 before iterating. I've seen EAs crash with array-out-of-bounds errors during gap events.
  • MarketBookGet returns false: This happens if the subscription was lost (rare) or the terminal is overloaded. Always check the return value. I log the error code to a file for later analysis. Error 4807 means the book data is temporarily unavailable.

A common mistake is placing heavy logic (file writes, complex calculations) directly inside OnBookEvent. Instead, store the snapshot in global variables and process it in OnTick() or a timer. I learned this the hard way when my EA froze during a fast market — the DOM handler was trying to write to a CSV file on every update. The file I/O blocked the event queue, and the terminal became unresponsive for 30 seconds.

Best Practices from Real Deployment

Use a Timer to Throttle Processing

OnBookEvent can fire faster than your EA can process. Offload analysis to a one-second timer. This is the pattern I use in all my DOM-based EAs:

MqlBookInfo lastBook[];
datetime lastBookTime;

// In OnInit:
EventSetTimer(1);

// In OnTimer:
void OnTimer()
  {
   if(TimeCurrent() - lastBookTime < 1)
      return;   // still in same second, skip
   // Process lastBook here
  }

// In OnBookEvent:
void OnBookEvent(const string &symbol)
  {
   // ... update lastBook ...
   lastBookTime = TimeCurrent();
  }

This prevents your EA from choking during high-frequency DOM updates. Adjust the timer interval based on your strategy — for scalping, I use 0.5 seconds

Frequently Asked Questions

Can I use OnBookEvent in MT4?

No. OnBookEvent is exclusive to MQL5. MT4 has no depth-of-market event handler. You must run your EA in MetaTrader 5.

Why does MarketBookAdd return false for some symbols?

The broker must provide market depth data for that symbol. Forex majors usually work, but exotic pairs, CFDs, or some brokers' synthetic instruments may not support DOM. Open the Depth of Market window manually to verify.

How often does OnBookEvent fire?

It fires on every DOM change — typically 10–50 times per second for liquid forex pairs during active hours. During news events, it can spike to hundreds of updates per second. Use a timer to throttle processing.

Does the Strategy Tester simulate OnBookEvent?

Only in visual mode with "Every tick" enabled and provided the tester has historical tick data containing market depth. Most brokers do not supply depth data in their tick history, so forward testing on a demo chart is more reliable.

Community

Clap for the article and open comments only when you want to read them.

0 claps0 comments

Related articles