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.successand convertsdata.booksto Python lists - ✅ Dataclass creation -
BookInfodataclass instead of protobufBookRecordmessages - ✅ 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:
-
Protobuf unpacking (all 3 methods):
-
MT5Account:
return res.data(protobuf Data objects) -
MT5Service:
return data.successorList[BookInfo](native Python types) -
Dataclass conversion (1 method):
-
get_market_depth(): Converts protobuf repeatedBookRecord→ cleanList[BookInfo]dataclass -
Cleaner API:
-
No need to manually extract
.successfrom Data wrappers - No need to work with protobuf repeated fields
- 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:¶
- Subscribe to a symbol (
subscribe_market_depth) - Get snapshots whenever needed (
get_market_depth) - 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:
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:
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
finallyblock for guaranteed cleanup
Example:
try:
await service.subscribe_market_depth("EURUSD")
# ... work with DOM ...
finally:
await service.unsubscribe_market_depth("EURUSD")
Recommendations¶
- Always unsubscribe - Use
try/finallyto ensure cleanup - Check subscription limits - Brokers typically allow 5-10 concurrent subscriptions
- Check return values -
subscribe_market_depth()returns False if failed - Not all symbols support DOM - Check with your broker
- DOM is a snapshot - Call
get_market_depth()repeatedly for monitoring - 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¶
- Forgetting to subscribe -
get_market_depth()will fail without subscription - Not unsubscribing - Wastes resources, may prevent other subscriptions
- Exceeding broker limits - Most brokers limit concurrent DOM subscriptions
- Assuming all symbols support DOM - Not all symbols have market depth data
📚 Related Sections¶
- MT5Service Overview - mid-level API overview
- Symbol Methods (Mid-Level) - symbol information methods
- Streaming Methods (Mid-Level) - real-time tick streaming
- Market Depth (Low-Level) - low-level DOM API
- MT5Service API Reference - complete mid-level API reference
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:
- Protobuf unpacking - No need to manually extract
.successor.books - Dataclass conversion -
BookInfodataclass instead of protobufBookRecordmessages - Clean Python types -
boolandList[BookInfo]instead of Data wrappers - Type hints - Full IDE autocomplete support
- Easier to use - No protobuf knowledge required
Best practices:
- Always use
try/finallyfor 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