Skip to content

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 numbers
  • order_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:

if await service.get_positions_total() == 0:
    print("All clear - no positions")

Use get_order_history()

Use when:

  • Analyzing order execution history
  • Checking order states (filled, cancelled, rejected)
  • Reviewing all trading activity (orders + deals)

Example:

history = await service.get_order_history(from_dt, to_dt)

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

  1. For ticket lists - use get_opened_tickets() (converts protobuf repeated fields to Python lists)
  2. For displaying positions - use get_opened_orders() or call MT5Account directly (identical)
  3. For quick count - use get_positions_total() or call MT5Account directly (identical)
  4. For P&L analysis - use get_positions_history() or call MT5Account directly (identical)
  5. 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.



Summary

Real value for end users:

  • get_opened_tickets() - Converts protobuf repeated fields → Python lists (cleaner than manual list() 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.