Skip to content

MT5Service - Market Depth Methods (Mid-Level API)

⚠️ Important: Read This First

MT5Service is an architectural layer between high-level (Sugar) and low-level (Account) APIs.

Understanding the 3 Methods:

Method Direct Use Value Architectural Role
get_market_depth() HIGH - Converts protobuf repeated → List[BookInfo] dataclass ✅ Used by Sugar
subscribe_market_depth() MEDIUM - Unpacks data.success from protobuf ✅ Used by Sugar
unsubscribe_market_depth() MEDIUM - Unpacks data.success from protobuf ✅ Used by Sugar

What This Means:

Architecture (3 layers):

MT5Sugar (HIGH)     →  service.get_market_depth()      ← Sugar uses Service methods
MT5Service (MID)    →  account.market_book_get()       ← Service unpacks protobuf + creates dataclasses
MT5Account (LOW)    →  gRPC call → protobuf Data objects

Key difference from Account methods:

  • ALL 3 methods have value - MT5Account returns protobuf Data objects that need unpacking
  • Protobuf unpacking - Service extracts data.success and converts data.books to Python lists
  • Dataclass creation - BookInfo dataclass instead of protobuf BookRecord messages
  • Cleaner API - Direct bool/list returns instead of manual Data struct extraction

For direct MT5Service usage:

  • Use all 3 methods - they all add value through protobuf unpacking
  • High value: get_market_depth() - converts protobuf repeated field to clean List[BookInfo]
  • Medium value: subscription methods - unpack bool from Data struct

For MT5Sugar users:

  • ✅ All methods work perfectly - the layer serves its architectural purpose

API Layer: MID-LEVEL - wrappers over MT5Account with Python native types

Implementation:

These methods are implemented in src/pymt5/mt5_service.py, which wraps package/MetaRpcMT5/helpers/mt5_account.py low-level API with dataclass conversion for order book entries.

Example files:

  • examples/2_service/04_service_demo.py - comprehensive demo of all MT5Service methods including market depth (STEP 4)

Why These Methods Exist

Real Value: Protobuf Unpacking + Dataclass Conversion

ALL 3 methods add real value because MT5Account returns protobuf Data objects that need manual unpacking:

Problem with MT5Account (Low-level):

# MT5Account returns protobuf Data objects - need manual unpacking
# Subscribe
add_data = await account.market_book_add("EURUSD", None, None)
success = add_data.success  # ← Manual extraction from protobuf wrapper

# Get DOM
dom_data = await account.market_book_get("EURUSD", None, None)
books = dom_data.books  # ← Protobuf repeated field (not Python list!)
for book in books:
    # book is protobuf BookRecord, not clean dataclass
    print(f"{book.type}: {book.price} x {book.volume}")

# Unsubscribe
release_data = await account.market_book_release("EURUSD", None, None)
success = release_data.success  # ← Manual extraction again

Solution with MT5Service (Mid-level):

# Service unpacks protobuf automatically - clean Python types
# Subscribe
success = await service.subscribe_market_depth("EURUSD")  # ← Already bool, not Data!

# Get DOM - returns List[BookInfo] dataclass
books = await service.get_market_depth("EURUSD")  # ← Python list, not protobuf repeated!
for book in books:
    # Clean dataclass with type hints
    print(f"{book.type}: {book.price} x {book.volume_real}")

# Unsubscribe
success = await service.unsubscribe_market_depth("EURUSD")  # ← Already bool!

What MT5Service provides:

  1. Protobuf unpacking (all 3 methods):

  2. MT5Account: return res.data (protobuf Data objects)

  3. MT5Service: return data.success or List[BookInfo] (native Python types)

  4. Dataclass conversion (1 method):

  5. get_market_depth(): Converts protobuf repeated BookRecord → clean List[BookInfo] dataclass

  6. Cleaner API:

  7. No need to manually extract .success from Data wrappers

  8. No need to work with protobuf repeated fields
  9. Type hints for IDE autocomplete

Architectural purpose:

  • MT5Sugar uses these methods to maintain layered architecture (Sugar → Service → Account)
  • Direct users also benefit - all methods add value through unpacking and conversion

All 3 Methods

Method Returns Description
subscribe_market_depth() bool Subscribe to DOM updates for a symbol
unsubscribe_market_depth() bool Unsubscribe from DOM updates
get_market_depth() List[BookInfo] Get current DOM snapshot (order book)

Key Concepts

What is Market Depth (DOM)?

Market Depth (Depth of Market, DOM, Order Book) shows: - Bid levels: Buy orders waiting at different price levels - Ask levels: Sell orders waiting at different price levels - Volume: How much volume is available at each price level

How it works:

  1. Subscribe to a symbol (subscribe_market_depth)
  2. Get snapshots whenever needed (get_market_depth)
  3. Unsubscribe when done (unsubscribe_market_depth)

Important notes:

  • You MUST subscribe before calling get_market_depth()
  • Always unsubscribe when done to free terminal resources
  • Brokers may limit concurrent DOM subscriptions (typically 5-10 symbols)
  • Not all symbols support DOM (check with broker)

➕ Dataclass (DTO)

BookInfo

@dataclass
class BookInfo:
    """Single Depth of Market (DOM) price level entry."""
    type: Any           # SELL (ask) or BUY (bid)
    price: float        # Price level
    volume: int         # Volume in lots (integer)
    volume_real: float  # Volume with decimal precision

Fields explained:

  • type: 1 = BUY (bid level), 2 = SELL (ask level)
  • price: Price level (e.g., 1.08550 for EURUSD)
  • volume: Volume in integer lots (e.g., 5 lots)
  • volume_real: Volume with decimals (e.g., 5.75 lots)

Book order:

  • Bid levels: sorted from highest to lowest (best bid first)
  • Ask levels: sorted from lowest to highest (best ask first)

Method Signatures

1) subscribe_market_depth

async def subscribe_market_depth(
    self,
    symbol: str,
    deadline: Optional[datetime] = None,
    cancellation_event: Optional[Any] = None,
) -> bool

Subscribe to Depth of Market (DOM) updates.

Args:

  • symbol: Symbol name to subscribe (e.g., "EURUSD", "BTCUSD")

Returns: bool - True if subscription successful, False otherwise

Technical: Low-level returns MarketBookAddData with data.success wrapper. This auto-extracts bool.

Important:

  • Required before calling get_market_depth() to receive DOM snapshots
  • Terminal maintains subscription - call unsubscribe_market_depth() when done
  • Brokers limit concurrent subscriptions (typically 5-10 symbols max)

Usage pattern:

# Subscribe first
if await service.subscribe_market_depth("EURUSD"):
    # Now you can get DOM data
    books = await service.get_market_depth("EURUSD")

2) unsubscribe_market_depth

async def unsubscribe_market_depth(
    self,
    symbol: str,
    deadline: Optional[datetime] = None,
    cancellation_event: Optional[Any] = None,
) -> bool

Unsubscribe from DOM updates.

Args:

  • symbol: Symbol name to unsubscribe

Returns: bool - True if unsubscription successful, False otherwise

Technical: Low-level returns MarketBookReleaseData with data.success wrapper. This auto-extracts bool.

Important:

  • Always unsubscribe when done to free terminal resources
  • Brokers may limit concurrent DOM subscriptions
  • Failing to unsubscribe may prevent subscribing to other symbols

Usage pattern:

try:
    # Work with DOM
    books = await service.get_market_depth("EURUSD")
    # ... process data ...
finally:
    # Always cleanup
    await service.unsubscribe_market_depth("EURUSD")

3) get_market_depth

async def get_market_depth(
    self,
    symbol: str,
    deadline: Optional[datetime] = None,
    cancellation_event: Optional[Any] = None,
) -> List[BookInfo]

Get current DOM snapshot (order book).

Args:

  • symbol: Symbol name

Returns: List[BookInfo] - List of order book entries (bid and ask levels)

Technical: Low-level returns MarketBookGetData with data.books (repeated BookRecord protobuf). This wrapper unpacks each BookRecord into BookInfo dataclass.

Important:

  • Requires prior subscribe_market_depth() subscription
  • Returns current snapshot (not streaming)
  • BookInfo.type: 1 = BUY (bid), 2 = SELL (ask)

Book structure:

SELL levels (asks) - sorted low to high:
  1.08570 x 10.0 lots (type=2)
  1.08560 x 15.5 lots (type=2)
  1.08550 x 8.0 lots  (type=2)  <- Best ask
--------------------
  1.08540 x 12.0 lots (type=1) <- Best bid
  1.08530 x 20.0 lots (type=1)
  1.08520 x 5.0 lots  (type=1)
BUY levels (bids) - sorted high to low

🔗 Usage Examples

Example 1: Basic DOM Subscription and Retrieval

from pymt5 import MT5Service

async def get_dom(service: MT5Service, symbol: str):
    """Basic DOM usage pattern."""

    try:
        # Step 1: Subscribe
        success = await service.subscribe_market_depth(symbol)
        if not success:
            print(f"Failed to subscribe to {symbol} DOM")
            return

        print(f"Subscribed to {symbol} DOM")

        # Step 2: Get DOM data
        books = await service.get_market_depth(symbol)

        print(f"\nMarket Depth for {symbol}:")
        print("-" * 60)

        # Separate bids and asks
        bids = [b for b in books if b.type == 1]
        asks = [b for b in books if b.type == 2]

        # Display asks (reversed to show best ask last)
        print("\nASKS (Sell orders):")
        for book in reversed(asks):
            print(f"  {book.price:.5f} x {book.volume_real:.2f} lots")

        print("\n" + "=" * 60)

        # Display bids
        print("\nBIDS (Buy orders):")
        for book in bids:
            print(f"  {book.price:.5f} x {book.volume_real:.2f} lots")

    finally:
        # Step 3: Always unsubscribe
        await service.unsubscribe_market_depth(symbol)
        print(f"\nUnsubscribed from {symbol} DOM")

Example 2: Getting Best Bid and Ask

async def get_best_prices(service: MT5Service, symbol: str):
    """Get best bid and ask from DOM."""

    try:
        # Subscribe
        if not await service.subscribe_market_depth(symbol):
            return None, None

        # Get DOM
        books = await service.get_market_depth(symbol)

        # Separate bids and asks
        bids = [b for b in books if b.type == 1]
        asks = [b for b in books if b.type == 2]

        # Best bid = highest bid price (first in sorted list)
        best_bid = bids[0] if bids else None

        # Best ask = lowest ask price (first in sorted list)
        best_ask = asks[0] if asks else None

        if best_bid and best_ask:
            print(f"{symbol}:")
            print(f"  Best Bid: {best_bid.price:.5f} ({best_bid.volume_real:.2f} lots)")
            print(f"  Best Ask: {best_ask.price:.5f} ({best_ask.volume_real:.2f} lots)")
            print(f"  Spread: {(best_ask.price - best_bid.price):.5f}")

            return best_bid, best_ask

    finally:
        await service.unsubscribe_market_depth(symbol)

    return None, None

Example 3: Calculating Total Volume at Each Side

async def analyze_dom_liquidity(service: MT5Service, symbol: str):
    """Analyze total bid/ask volume in DOM."""

    try:
        await service.subscribe_market_depth(symbol)
        books = await service.get_market_depth(symbol)

        # Calculate totals
        total_bid_volume = sum(b.volume_real for b in books if b.type == 1)
        total_ask_volume = sum(b.volume_real for b in books if b.type == 2)

        bid_count = len([b for b in books if b.type == 1])
        ask_count = len([b for b in books if b.type == 2])

        print(f"{symbol} DOM Liquidity:")
        print(f"  Bid side: {total_bid_volume:.2f} lots across {bid_count} levels")
        print(f"  Ask side: {total_ask_volume:.2f} lots across {ask_count} levels")
        print(f"  Bid/Ask ratio: {total_bid_volume / total_ask_volume:.2f}")

        # Imbalance analysis
        if total_bid_volume > total_ask_volume * 1.5:
            print("  Status: Strong buying pressure")
        elif total_ask_volume > total_bid_volume * 1.5:
            print("  Status: Strong selling pressure")
        else:
            print("  Status: Balanced")

    finally:
        await service.unsubscribe_market_depth(symbol)

Example 4: Displaying DOM Table

async def display_dom_table(service: MT5Service, symbol: str, levels: int = 5):
    """Display DOM as a formatted table."""

    try:
        await service.subscribe_market_depth(symbol)
        books = await service.get_market_depth(symbol)

        bids = [b for b in books if b.type == 1][:levels]
        asks = [b for b in books if b.type == 2][:levels]

        print(f"\n{symbol} - Market Depth (Top {levels} levels)")
        print("=" * 70)
        print(f"{'BID Volume':<15} {'BID Price':<15} {'ASK Price':<15} {'ASK Volume':<15}")
        print("-" * 70)

        # Reverse asks to show best ask at bottom
        asks_reversed = list(reversed(asks))

        max_levels = max(len(bids), len(asks_reversed))

        for i in range(max_levels):
            bid_vol = f"{bids[i].volume_real:.2f}" if i < len(bids) else ""
            bid_price = f"{bids[i].price:.5f}" if i < len(bids) else ""
            ask_price = f"{asks_reversed[i].price:.5f}" if i < len(asks_reversed) else ""
            ask_vol = f"{asks_reversed[i].volume_real:.2f}" if i < len(asks_reversed) else ""

            print(f"{bid_vol:<15} {bid_price:<15} {ask_price:<15} {ask_vol:<15}")

        print("=" * 70)

    finally:
        await service.unsubscribe_market_depth(symbol)

Example 5: Monitoring DOM Changes

import asyncio

async def monitor_dom_changes(
    service: MT5Service,
    symbol: str,
    interval_seconds: int = 1,
    duration_seconds: int = 60
):
    """Monitor DOM changes over time."""

    try:
        # Subscribe
        if not await service.subscribe_market_depth(symbol):
            print(f"Failed to subscribe to {symbol}")
            return

        print(f"Monitoring {symbol} DOM for {duration_seconds} seconds...")

        start_time = asyncio.get_event_loop().time()

        while True:
            elapsed = asyncio.get_event_loop().time() - start_time
            if elapsed >= duration_seconds:
                break

            # Get current DOM
            books = await service.get_market_depth(symbol)

            bids = [b for b in books if b.type == 1]
            asks = [b for b in books if b.type == 2]

            if bids and asks:
                best_bid = bids[0]
                best_ask = asks[0]

                print(f"[{elapsed:.1f}s] Bid: {best_bid.price:.5f} ({best_bid.volume_real:.2f}) | "
                      f"Ask: {best_ask.price:.5f} ({best_ask.volume_real:.2f}) | "
                      f"Spread: {(best_ask.price - best_bid.price):.5f}")

            await asyncio.sleep(interval_seconds)

    finally:
        await service.unsubscribe_market_depth(symbol)
        print(f"\nStopped monitoring {symbol}")

Example 6: Multiple Symbols DOM

async def monitor_multiple_symbols(service: MT5Service, symbols: list):
    """Monitor DOM for multiple symbols (respecting broker limits)."""

    subscribed = []

    try:
        # Subscribe to all symbols
        for symbol in symbols:
            success = await service.subscribe_market_depth(symbol)
            if success:
                subscribed.append(symbol)
                print(f"Subscribed: {symbol}")
            else:
                print(f"Failed: {symbol} (broker limit reached?)")

        print(f"\nSuccessfully subscribed to {len(subscribed)} symbols")

        # Get DOM for all subscribed symbols
        for symbol in subscribed:
            books = await service.get_market_depth(symbol)

            bids = [b for b in books if b.type == 1]
            asks = [b for b in books if b.type == 2]

            if bids and asks:
                print(f"\n{symbol}:")
                print(f"  Best Bid: {bids[0].price:.5f} x {bids[0].volume_real:.2f}")
                print(f"  Best Ask: {asks[0].price:.5f} x {asks[0].volume_real:.2f}")
                print(f"  Total levels: {len(bids)} bids, {len(asks)} asks")

    finally:
        # Unsubscribe from all
        for symbol in subscribed:
            await service.unsubscribe_market_depth(symbol)
            print(f"Unsubscribed: {symbol}")

Example 7: Finding Large Orders

async def find_large_orders(
    service: MT5Service,
    symbol: str,
    min_volume: float = 10.0
):
    """Find large orders in DOM (potential resistance/support)."""

    try:
        await service.subscribe_market_depth(symbol)
        books = await service.get_market_depth(symbol)

        # Find large orders
        large_bids = [b for b in books if b.type == 1 and b.volume_real >= min_volume]
        large_asks = [b for b in books if b.type == 2 and b.volume_real >= min_volume]

        print(f"\n{symbol} - Large Orders (>= {min_volume} lots):")
        print("=" * 60)

        if large_bids:
            print(f"\nLarge BID orders (potential support):")
            for book in large_bids:
                print(f"  {book.price:.5f} x {book.volume_real:.2f} lots")

        if large_asks:
            print(f"\nLarge ASK orders (potential resistance):")
            for book in large_asks:
                print(f"  {book.price:.5f} x {book.volume_real:.2f} lots")

        if not large_bids and not large_asks:
            print(f"\nNo large orders found (min: {min_volume} lots)")

    finally:
        await service.unsubscribe_market_depth(symbol)

When to Use Each Method

Use subscribe_market_depth()

Use when:

  • Starting DOM monitoring for a symbol
  • Need to receive order book updates
  • Before calling get_market_depth()

Important:

  • Always call BEFORE get_market_depth()
  • Check return value (False = subscription failed)

Example:

if await service.subscribe_market_depth("EURUSD"):
    # Now you can get DOM data
    pass

Use get_market_depth()

Use when:

  • Need current order book snapshot
  • Analyzing market liquidity
  • Finding best bid/ask prices
  • Looking for large orders

Important:

  • Requires prior subscription
  • Returns snapshot (not streaming)
  • Call repeatedly for monitoring

Example:

books = await service.get_market_depth("EURUSD")
best_bid = [b for b in books if b.type == 1][0]

Use unsubscribe_market_depth()

Use when:

  • Done monitoring symbol
  • Cleaning up resources
  • Want to subscribe to different symbol

Important:

  • ALWAYS call when done
  • Free up subscription slot for other symbols
  • Use in finally block for guaranteed cleanup

Example:

try:
    await service.subscribe_market_depth("EURUSD")
    # ... work with DOM ...
finally:
    await service.unsubscribe_market_depth("EURUSD")

Recommendations

  1. Always unsubscribe - Use try/finally to ensure cleanup
  2. Check subscription limits - Brokers typically allow 5-10 concurrent subscriptions
  3. Check return values - subscribe_market_depth() returns False if failed
  4. Not all symbols support DOM - Check with your broker
  5. DOM is a snapshot - Call get_market_depth() repeatedly for monitoring
  6. Use context manager pattern - Ensures proper cleanup

Example pattern:

async def with_dom(service, symbol):
    try:
        if not await service.subscribe_market_depth(symbol):
            return
        # Work with DOM
        books = await service.get_market_depth(symbol)
        # ... process ...
    finally:
        await service.unsubscribe_market_depth(symbol)

Common Pitfalls

  1. Forgetting to subscribe - get_market_depth() will fail without subscription
  2. Not unsubscribing - Wastes resources, may prevent other subscriptions
  3. Exceeding broker limits - Most brokers limit concurrent DOM subscriptions
  4. Assuming all symbols support DOM - Not all symbols have market depth data


Summary

Real Value Assessment

ALL 3 methods add real value through protobuf unpacking and dataclass conversion:

Method Value Level What It Does
get_market_depth() HIGH Converts protobuf repeated BookRecord → clean List[BookInfo] dataclass
subscribe_market_depth() MEDIUM Unpacks data.success from protobuf MarketBookAddData → bool
unsubscribe_market_depth() MEDIUM Unpacks data.success from protobuf MarketBookReleaseData → bool

Why these methods have value:

MT5Account returns protobuf Data objects:

# Low-level returns:
add_data: MarketBookAddData = await account.market_book_add(...)
dom_data: MarketBookGetData = await account.market_book_get(...)
release_data: MarketBookReleaseData = await account.market_book_release(...)

MT5Service unpacks to native Python types:

# Mid-level returns:
success: bool = await service.subscribe_market_depth(...)           # Unpacks data.success
books: List[BookInfo] = await service.get_market_depth(...)         # Unpacks + converts data.books
success: bool = await service.unsubscribe_market_depth(...)         # Unpacks data.success

Key advantages:

  1. Protobuf unpacking - No need to manually extract .success or .books
  2. Dataclass conversion - BookInfo dataclass instead of protobuf BookRecord messages
  3. Clean Python types - bool and List[BookInfo] instead of Data wrappers
  4. Type hints - Full IDE autocomplete support
  5. Easier to use - No protobuf knowledge required

Best practices:

  • Always use try/finally for guaranteed cleanup
  • Check subscription return values
  • Respect broker subscription limits (typically 5-10 symbols)
  • Not all symbols support DOM - verify with broker

Bottom line:

  • For direct users: All 3 methods add value - use them for cleaner API
  • For Sugar users: Methods serve architectural purpose perfectly
  • vs MT5Account: Significant improvement through unpacking and dataclass conversion