MT5Service - Positions & Orders Methods (Mid-Level API)¶
⚠️ Important: Read This First¶
MT5Service is an architectural layer between high-level (Sugar) and low-level (Account) APIs.
Understanding the 5 Methods:¶
| Method | Direct Use Value | Architectural Role |
|---|---|---|
get_opened_tickets() |
✅ MEDIUM - Converts protobuf repeated → Python lists | ✅ Used by Sugar |
get_positions_total() |
⚪ LOW - Unpacks data.total_positions from protobuf |
✅ Used by Sugar |
get_opened_orders() |
⚪ NONE - Just calls account.opened_orders() |
✅ Used by Sugar |
get_order_history() |
⚪ NONE - Just calls account.order_history() |
✅ Used by Sugar |
get_positions_history() |
⚪ NONE - Just calls account.positions_history() |
✅ Used by Sugar |
What This Means:¶
Architecture (3 layers):
MT5Sugar (HIGH) → service.get_opened_tickets() ← Sugar uses Service methods
MT5Service (MID) → account.opened_orders_tickets() ← Service converts to Python lists
MT5Account (LOW) → gRPC call → protobuf repeated fields
Key difference from Symbol methods:
- ✅ Only 1 method has real value -
get_opened_tickets()converts protobuf repeated fields to Python lists - ⚪ 4 methods are pass-through - identical to calling MT5Account directly
- ⚪ Protobuf unpacking - only
get_positions_total()unpacks one field (data.total_positions)
For direct MT5Service usage:
- ✅ Use
get_opened_tickets()- converts to Python lists (cleaner than protobuf repeated fields) - ⚪ Other 4 methods - no value, just call MT5Account directly
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.
Example files:
examples/2_service/04_service_demo.py- comprehensive demo of all MT5Service methods including positions/orders
Why These Methods Exist¶
Real Value: get_opened_tickets()¶
Only get_opened_tickets() adds real value by converting protobuf repeated fields to Python lists:
Problem with MT5Account (Low-level):
# MT5Account returns protobuf repeated fields - need manual list conversion
tickets_data = await account.opened_orders_tickets()
position_tickets = list(tickets_data.opened_position_tickets) # ← Manual conversion
order_tickets = list(tickets_data.opened_orders_tickets) # ← Manual conversion
Solution with MT5Service (Mid-level):
# Service converts to Python lists automatically
position_tickets, order_tickets = await service.get_opened_tickets()
# ← Already lists, not protobuf repeated fields!
Other Methods: Pass-Through Layer¶
get_opened_orders(), get_order_history(), get_positions_history() are pass-through wrappers:
Implementation:
# get_opened_orders - direct pass-through
async def get_opened_orders(...):
return await self._account.opened_orders(...) # ← No processing!
# get_order_history - direct pass-through
async def get_order_history(...):
return await self._account.order_history(...) # ← No processing!
# get_positions_history - direct pass-through
async def get_positions_history(...):
return await self._account.positions_history(...) # ← No processing!
Why they exist:
- ✅ MT5Sugar (high-level) uses them - maintains layered architecture (Sugar → Service → Account)
- ✅ Unified interface - all methods accessible through one class
- ✅ Consistent naming -
get_*prefix across all Service methods - ⚪ No processing - direct pass-through to MT5Account
For end users: If you're using MT5Service directly (not Sugar), calling MT5Account is identical. These wrappers add no value.
For MT5Sugar: These are essential - Sugar must work through Service, not directly with Account (layered architecture).
All 5 Methods¶
| Method | Returns | Implementation |
|---|---|---|
get_opened_tickets() |
Tuple[List[int], List[int]] |
✅ Converts protobuf repeated → (list(...), list(...)) |
get_positions_total() |
int |
⚪ Unpacks data.total_positions from protobuf |
get_opened_orders() |
OpenedOrdersData |
⚪ return await self._account.opened_orders(...) |
get_order_history() |
OrdersHistoryData |
⚪ return await self._account.order_history(...) |
get_positions_history() |
PositionsHistoryData |
⚪ return await self._account.positions_history(...) |
Key Concepts¶
Positions vs Orders¶
- Position: An open market position (BUY or SELL) that is currently active
- Pending Order: A limit/stop order waiting to be executed
- Historical Order: Past orders (executed, cancelled, or rejected)
- Closed Position: Position that was opened and then closed (has P&L data)
Ticket Numbers¶
- Every position and order has a unique ticket number (int64)
- Ticket numbers are used to identify, modify, and close positions/orders
- Use
get_opened_tickets()for fast existence checks (much faster than full data)
Method Signatures¶
1) get_positions_total¶
async def get_positions_total(
self,
deadline: Optional[datetime] = None,
cancellation_event: Optional[Any] = None,
) -> int
Get total number of open positions.
Returns: int count directly (no Data struct)
Usage: Quick check if there are any open positions before closing all.
Technical: Low-level returns protobuf with data.total_positions wrapper. This auto-extracts the count.
2) get_opened_orders¶
async def get_opened_orders(
self,
sort_mode: account_helper_pb2.BMT5_ENUM_OPENED_ORDER_SORT_TYPE =
account_helper_pb2.BMT5_OPENED_ORDER_SORT_BY_OPEN_TIME_ASC,
deadline: Optional[datetime] = None,
cancellation_event: Optional[Any] = None,
) -> Any # OpenedOrdersData
Get all open positions and pending orders with full details.
Args:
sort_mode: Sort mode for results (default: by open time ascending)
Returns: OpenedOrdersData protobuf with:
position_infos: List of PositionInfo (ticket, symbol, volume, profit, SL/TP, etc.)order_infos: List of OrderInfo (ticket, symbol, volume, type, SL/TP, etc.)
Available sort modes:
BMT5_OPENED_ORDER_SORT_BY_OPEN_TIME_ASC # Oldest first
BMT5_OPENED_ORDER_SORT_BY_OPEN_TIME_DESC # Newest first
BMT5_OPENED_ORDER_SORT_BY_TICKET_ASC # By ticket ascending
BMT5_OPENED_ORDER_SORT_BY_TICKET_DESC # By ticket descending
Technical: Returns full protobuf data with all position/order details. For tickets only (faster), use get_opened_tickets().
3) get_opened_tickets¶
async def get_opened_tickets(
self,
deadline: Optional[datetime] = None,
cancellation_event: Optional[Any] = None,
) -> Tuple[List[int], List[int]]
Get only ticket numbers (lightweight and fast).
Returns: Tuple[position_tickets, order_tickets]
position_tickets: List of position ticket numbersorder_tickets: List of pending order ticket numbers
Technical: Low-level returns OpenedOrdersTicketsData with two repeated int64 fields. This extracts both lists without parsing full position/order details.
Advantage: 10-20x faster than get_opened_orders() when you only need ticket IDs for existence checks or counting.
Use cases:
- Check if specific ticket exists
- Count positions/orders quickly
- Monitor for new positions (compare ticket lists)
- Validate ticket before modification
4) get_order_history¶
async def get_order_history(
self,
from_dt: datetime,
to_dt: datetime,
sort_mode: account_helper_pb2.BMT5_ENUM_ORDER_HISTORY_SORT_TYPE =
account_helper_pb2.BMT5_SORT_BY_CLOSE_TIME_DESC,
page_number: int = 0,
items_per_page: int = 50,
deadline: Optional[datetime] = None,
cancellation_event: Optional[Any] = None,
) -> Any # OrdersHistoryData
Get historical orders and deals for a time period.
Args:
from_dt: Start time (datetime)to_dt: End time (datetime)sort_mode: Sort mode (default: by close time descending)page_number: Page number for pagination (0-based)items_per_page: Items per page (default: 50)
Returns: OrdersHistoryData protobuf with:
- order_history_infos: List of OrderHistoryInfo (orders + related deals)
Available sort modes:
BMT5_SORT_BY_CLOSE_TIME_DESC # Most recent first (default)
BMT5_SORT_BY_CLOSE_TIME_ASC # Oldest first
BMT5_SORT_BY_TICKET_DESC # By ticket descending
BMT5_SORT_BY_TICKET_ASC # By ticket ascending
Technical: Returns protobuf OrdersHistoryData with repeated field. Supports pagination for large result sets.
Note: For closed positions with P&L calculations, use get_positions_history() instead (more detailed profit tracking).
5) get_positions_history¶
async def get_positions_history(
self,
sort_type: account_helper_pb2.AH_ENUM_POSITIONS_HISTORY_SORT_TYPE,
open_from: Optional[datetime] = None,
open_to: Optional[datetime] = None,
page: int = 0,
size: int = 10,
deadline: Optional[datetime] = None,
cancellation_event: Optional[Any] = None,
) -> Any # PositionsHistoryData
Get closed positions history with P&L details.
Args:
sort_type: Sort type (required)open_from: Start of open time filter (optional)open_to: End of open time filter (optional)page: Page number (0-based, default: 0)size: Items per page (default: 10)
Returns: PositionsHistoryData protobuf with:
-
history_positions: List of PositionHistoryInfo with: -
Ticket, symbol, volume, type
- Open time, close time
- Open price, close price
- Profit, commission, swap
- SL, TP
Available sort types:
AH_SORT_BY_OPEN_TIME_ASC # Oldest first
AH_SORT_BY_OPEN_TIME_DESC # Most recent first
AH_SORT_BY_CLOSE_TIME_ASC # By close time ascending
AH_SORT_BY_CLOSE_TIME_DESC # By close time descending
AH_SORT_BY_PROFIT_ASC # Lowest profit first
AH_SORT_BY_PROFIT_DESC # Highest profit first
Technical: Returns protobuf PositionsHistoryData. Each PositionHistoryInfo includes profit, commission, swap, open/close times and prices.
Note: Filters by position open time (not close time). Better than get_order_history() for profit calculations and closed position analysis.
🔗 Usage Examples¶
Example 1: Get Ticket Numbers (Fast Method with Real Value)¶
async def list_open_tickets(service: MT5Service):
"""Get ticket numbers only (10-20x faster than full data)."""
# Fast method - only ticket numbers
position_tickets, order_tickets = await service.get_opened_tickets()
print(f"Open positions ({len(position_tickets)}):")
for ticket in position_tickets:
print(f" Position #{ticket}")
print(f"\nPending orders ({len(order_tickets)}):")
for ticket in order_tickets:
print(f" Order #{ticket}")
return position_tickets, order_tickets
Example 2: Getting Full Position Details¶
from MetaRpcMT5 import mt5_term_api_account_helper_pb2 as account_helper_pb2
async def display_open_positions(service: MT5Service):
"""Get full details for all open positions and orders."""
# Get full data (sorted by open time, newest first)
data = await service.get_opened_orders(
sort_mode=account_helper_pb2.BMT5_OPENED_ORDER_SORT_BY_OPEN_TIME_DESC
)
print("OPEN POSITIONS:")
print("-" * 80)
for pos in data.position_infos:
print(f"Ticket: {pos.ticket}")
print(f" Symbol: {pos.symbol}")
print(f" Type: {'BUY' if pos.type == 0 else 'SELL'}")
print(f" Volume: {pos.volume}")
print(f" Open Price: {pos.price_open}")
print(f" Current Price: {pos.price_current}")
print(f" Profit: ${pos.profit:.2f}")
print(f" SL: {pos.sl}, TP: {pos.tp}")
print()
print("\nPENDING ORDERS:")
print("-" * 80)
for order in data.order_infos:
print(f"Ticket: {order.ticket}")
print(f" Symbol: {order.symbol}")
print(f" Type: {order.type}")
print(f" Volume: {order.volume_current}")
print(f" Price: {order.price_open}")
print(f" SL: {order.sl}, TP: {order.tp}")
print()
Example 3: Getting Closed Positions with P&L¶
from datetime import datetime, timedelta
from MetaRpcMT5 import mt5_term_api_account_helper_pb2 as account_helper_pb2
async def analyze_closed_positions(service: MT5Service):
"""Analyze closed positions from last 30 days."""
to_dt = datetime.now()
from_dt = to_dt - timedelta(days=30)
# Get positions history sorted by profit (highest first)
history = await service.get_positions_history(
sort_type=account_helper_pb2.AH_SORT_BY_PROFIT_DESC,
open_from=from_dt,
open_to=to_dt,
page=0,
size=50
)
total_profit = 0.0
wins = 0
losses = 0
print("CLOSED POSITIONS (Last 30 days):")
print("-" * 80)
for pos in history.history_positions:
profit = pos.profit + pos.swap - pos.commission
print(f"Ticket: {pos.ticket}")
print(f" Symbol: {pos.symbol}")
print(f" Type: {'BUY' if pos.type == 0 else 'SELL'}")
print(f" Volume: {pos.volume}")
print(f" Open: {pos.price_open} -> Close: {pos.price_close}")
print(f" Profit: ${pos.profit:.2f}")
print(f" Swap: ${pos.swap:.2f}")
print(f" Commission: ${pos.commission:.2f}")
print(f" Net P&L: ${profit:.2f}")
print()
total_profit += profit
if profit > 0:
wins += 1
else:
losses += 1
# Statistics
print("=" * 80)
print(f"Total Trades: {wins + losses}")
print(f"Wins: {wins}, Losses: {losses}")
print(f"Win Rate: {wins / (wins + losses) * 100:.1f}%")
print(f"Total P&L: ${total_profit:.2f}")
When to Use Which Method¶
Use get_opened_tickets()¶
Use when:
- You only need ticket numbers (not full details)
- Checking if specific ticket exists
- Counting positions/orders
- Monitoring for new positions (polling)
- Performance is critical
Advantage: 10-20x faster than get_opened_orders()
Example:
pos_tickets, order_tickets = await service.get_opened_tickets()
if 12345 in pos_tickets:
print("Position exists")
Use get_opened_orders()¶
Use when:
- You need full position/order details
- Displaying position information (profit, SL/TP, prices)
- Analyzing open trades
- Building a dashboard
Example:
data = await service.get_opened_orders()
for pos in data.position_infos:
print(f"{pos.symbol}: Profit ${pos.profit:.2f}")
Use get_positions_total()¶
Use when:
- Quick count check
- Simple yes/no check (any positions open?)
- Before closing all positions
Example:
Use get_order_history()¶
Use when:
- Analyzing order execution history
- Checking order states (filled, cancelled, rejected)
- Reviewing all trading activity (orders + deals)
Example:
Use get_positions_history()¶
Use when:
- Calculating trading performance (P&L)
- Analyzing closed positions only
- Need profit/commission/swap details
- Building trade journal
Example:
history = await service.get_positions_history(
sort_type=account_helper_pb2.AH_SORT_BY_PROFIT_DESC
)
Recommendations¶
- For ticket lists - use
get_opened_tickets()(converts protobuf repeated fields to Python lists) - For displaying positions - use
get_opened_orders()or call MT5Account directly (identical) - For quick count - use
get_positions_total()or call MT5Account directly (identical) - For P&L analysis - use
get_positions_history()or call MT5Account directly (identical) - For order tracking - use
get_order_history()or call MT5Account directly (identical)
Bottom line: Only get_opened_tickets() adds value through list conversion. Other methods are pass-through - call MT5Account if using Service directly.
📚 Related Sections¶
- MT5Service Overview - mid-level API overview
- Account Methods (Mid-Level) - account information methods
- Symbol Methods (Mid-Level) - symbol information methods
- Trading Methods (Mid-Level) - placing and modifying orders
- Streaming Methods (Mid-Level) - real-time position updates
- Positions & Orders (Low-Level) - low-level API
- MT5Service API Reference - complete mid-level API reference
Summary¶
Real value for end users:
- ✅
get_opened_tickets()- Converts protobuf repeated fields → Python lists (cleaner than manuallist()conversion) - ⚪
get_positions_total()- Simple unpacking (data.total_positions)
Architectural layer (for MT5Sugar):
- ⚪
get_opened_orders(),get_order_history(),get_positions_history()- Direct pass-through to MT5Account - Used by MT5Sugar to maintain layered architecture (Sugar → Service → Account)
- For direct MT5Service usage: no value, call MT5Account instead
Key difference from Symbol methods:
- Symbol methods: MT5Account returns protobuf Data → Service unpacks + converts → real value
- Positions methods: MT5Account returns protobuf Data → Service mostly pass-through → minimal value
Recommendation:
- Using MT5Sugar? These methods work seamlessly through the architecture
- Using MT5Service directly? Only use
get_opened_tickets()for cleaner list conversion. For other operations, call MT5Account.