pymt5
Python client for the MT5 Web Terminal via reverse-engineered WebSocket binary protocol.
See the changelog for releases and subsequent changes.
What's new in 1.0.4
- Trade requests retain the legacy wire format by default. An explicit
build_6090profile adds the reported reserved bytes and market-deal action. - Ambiguous or mismatched historical rate response lengths raise
ProtocolError; choosestandardorextendedexplicitly when the server's format is known. - Login rejects nonzero server response codes, and logout clears stored reconnect credentials after success. Switching accounts requires an explicit logout.
close_position()rejects invalid IDs and refuses automatic direction detection when the position or its matching symbol cannot be verified.
These changes have offline test coverage. The new build 6090 profile has not been independently verified against a broker in this release.
Requirements
- Python 3.11+ (3.11, 3.12, 3.13)
- Platforms: Linux, macOS, Windows
- Dependencies:
websockets,cryptography
Features
Core Protocol
- WebSocket transport to
wss://web.metatrader.app/terminal - AES-CBC encryption with zero IV and PKCS7 padding
- Bootstrap handshake (cmd=0) with automatic key exchange
- Login (cmd=28) with UTF-16LE encoded fields
ping,logout,change_password,trader_paramsverify_code(cmd=27) — two-factor authenticationrequest_opening_verification/submit_opening_verification(cmd=27/40)open_demo_account(cmd=30) andopen_real_account(cmd=39)enable_otp/disable_otp(cmd=43)open_demo(cmd=30) — legacy init-style wrapper kept for compatibilitysend_notification(cmd=42) — server notifications
Account Data
get_account(cmd=3) — account metadata and balance state: balance, credit, currency, leverage, account/server/company names, timezone, rights flags, trade settings, leverage rules, commission tablesget_account_summary()—AccountInfodataclass (uses cmd=3 + positions)get_positions_and_orders(cmd=4) — open positions and pending ordersget_positions()— convenience: open positions onlyget_orders()— convenience: pending orders onlyget_trade_history(cmd=5) — closed deals and historical ordersget_deals()— convenience: closed deals only
Market Data
get_symbols(cmd=34, zlib compressed) — full symbol list with metadataget_symbol_groups(cmd=9) — category tree (Forex, Crypto, Indexes, etc.)get_full_symbol_info(cmd=18) — detailed contract specs (tick size, margins, volumes, bond face value/accrued interest, trade schedule, subscription flags)get_spreads(cmd=20) — spread data with proper schema parsingget_rates(cmd=11) — historical OHLCV bars (M1 to MN1), with standard and extended formatssubscribe_ticks(cmd=7) — real-time tick push (cmd=8) with callbacksubscribe_symbols— name-based tick subscription (resolves names via symbol cache)subscribe_book(cmd=22) — order book / depth-of-market subscriptionsubscribe_book_by_name— name-based order book subscription
By default, rate responses are decoded automatically only when their length uniquely
identifies the 48-byte standard or 56-byte extended format. Lengths divisible by both,
including any nonzero multiple of 336 bytes, or by neither, raise ProtocolError rather
than guessing. This is a behavior
change for ambiguous or mismatched responses. Select the format confirmed for your
server with MT5WebClient(rate_bar_format="standard") or "extended"; alternatively,
pass bar_format="standard" or "extended" to get_rates() or the copy_rates_*()
wrappers. The extended format includes real_volume. get_rates_raw() can help inspect
an unknown response, but length alone does not identify an ambiguous format. Both
get_rates() and get_rates_raw() raise ProtocolError for nonzero server response codes.
Length validation cannot detect truncation or an incorrect format selection when the
resulting size happens to match the selected record size.
Official MetaTrader5-style Helpers
- Session/account:
initialize,shutdown,account_info,terminal_info,version - Compatibility state:
last_error - Symbols/history:
symbols_total,symbols_get,symbol_info,positions_total,positions_get,orders_total,orders_get,history_orders_*,history_deals_* - Bars/trading:
copy_rates_range,copy_rates_from,copy_rates_from_pos,copy_ticks_from,copy_ticks_range,order_send,order_check - Local formula helpers:
order_calc_profit,order_calc_margin(best-effort for forex/CFD/futures/stock/bond modes that the current Web Terminal computes client-side) - DOM/cache helpers:
market_book_add,market_book_get,market_book_release,symbol_select,symbol_info_tick terminal_infoandversionare best-effort Web Terminal views derived fromcmd=3plus observed public build metadatacopy_ticks_*are cached views over observedcmd=8pushes, andorder_checkis a local compatibility-layer pre-flight rather than a server RPC
Trading
- Raw:
trade_request— full control over all trade fields, returnsTradeResult TradeResultincludes:deal,order,volume,price,bid,ask,comment,request_id- High-level helpers (auto-resolve digits from symbol cache):
buy_market/sell_market— market orders with optional SL/TPbuy_limit/sell_limit— limit pending ordersbuy_stop/sell_stop— stop pending ordersbuy_stop_limit/sell_stop_limit— stop-limit pending ordersclose_position— close by opposite market order (auto-detects BUY/SELL direction)close_position_by— close by opposite position (hedge netting)modify_position_sltp— modify SL/TP of open positionmodify_pending_order— modify pending order price/SL/TPcancel_pending_order— remove a pending order
Symbol Cache & Info
load_symbols()— fetch and cache all symbols for fast name→id/digits lookupget_symbol_info(name)— lookupSymbolInfoby nameget_symbol_id(name)— lookup symbol ID by namesymbol_names— list all cached symbol names
Miscellaneous Commands
get_corporate_links(cmd=44) — broker links (support, education, social)send_raw_command— send any command with raw payloadsend_bootstrap_command_52— experimental wrapper for reserved bootstrap-onlycmd=52
Push Notifications
on_tick(callback)— real-time tick pushes (cmd=8)on_position_update(callback)— position change pusheson_order_update(callback)— order change pusheson_trade_update(callback)— combined position+order push in single callbackon_trade_transaction(callback)— order add/update/delete + balance updates (cmd=10)on_symbol_update(callback)— symbol change pushes (cmd=13)on_account_update(callback)— account balance/equity/margin pushes (cmd=14)on_login_status(callback)— login status pushes (cmd=15)on_symbol_details(callback)— extended quote data with options greeks (cmd=17)on_trade_result(callback)— async trade execution results (cmd=19)on_book_update(callback)— order book / DOM pushes (cmd=23)
Reliability
- Async context manager (
async with MT5WebClient() as client:) - Auto heartbeat — periodic ping after login (configurable interval)
- Auto reconnect — optional reconnect on disconnect with exponential backoff
- Disconnect callback —
on_disconnect()for custom handling - Python logging — structured logging via
pymt5.clientandpymt5.transportloggers
Constants & Enums
TradeResultdataclass withretcode,description,success,deal,order,volume,pricefields- 33 MT5 return codes with human-readable descriptions
- Complete trade constants:
TRADE_ACTION_*(includingCLOSE_BY),ORDER_TYPE_*(includingSTOP_LIMIT),ORDER_FILLING_*,ORDER_TIME_* - Position/deal enums:
POSITION_TYPE_*,DEAL_TYPE_*(all 15 types),DEAL_ENTRY_* - Order states:
ORDER_STATE_*(all 7 states) - Symbol trade modes:
SYMBOL_TRADE_MODE_* - Command IDs exported:
CMD_GET_ACCOUNT,CMD_GET_SYMBOL_GROUPS,CMD_TRADE_UPDATE_PUSH,CMD_ACCOUNT_UPDATE_PUSH,CMD_SYMBOL_DETAILS_PUSH,CMD_TRADE_RESULT_PUSH,CMD_SUBSCRIBE_BOOK,CMD_BOOK_PUSH,CMD_GET_CORPORATE_LINKS
Tests
- Offline unit tests cover protocol encoding, trade and rate parsing, session handling, reconnect logic, compatibility helpers, and push handlers.
Earlier functionality was tested against MetaQuotes-Demo on 2026-03-16 with Web Terminal build 5687. Those historical results do not verify the changes in 1.0.4.
Install
pip install mt5-web-client
Or install from source:
git clone https://github.com/cloudQuant/pymt5.git
cd pymt5
pip install -e .
For development (tests + type checking):
pip install -e ".[dev]"
make check
Quick Start
import asyncio
from pymt5 import MT5WebClient
async def main():
async with MT5WebClient(auto_reconnect=True) as client:
await client.login(login=12345678, password="your-password")
# Load symbol cache
await client.load_symbols()
print(f"Loaded {len(client.symbol_names)} symbols")
# Full account info (balance, equity, margin, leverage)
acct = await client.get_account()
print(f"Balance: {acct['balance']}, Currency: {acct['currency']}, Leverage: 1:{acct['leverage']}")
# Symbol groups
groups = await client.get_symbol_groups()
print(f"Groups: {groups}")
# Subscribe to ticks
def on_ticks(ticks):
for t in ticks:
print(f"TICK {t.get('symbol', t['symbol_id'])}: bid={t['bid']} ask={t['ask']}")
client.on_tick(on_ticks)
await client.subscribe_symbols(["EURUSD", "GBPUSD"])
# Register for all push notifications
client.on_trade_update(lambda d: print(f"Positions: {len(d['positions'])}"))
client.on_account_update(lambda d: print(f"Balance: {d.get('balance')}"))
client.on_trade_transaction(lambda d: print(f"Transaction: type={d.get('update_type')}"))
client.on_trade_result(lambda d: print(f"Trade result: {d.get('result', {})}"))
# Place a market buy
result = await client.buy_market("EURUSD", 0.01, sl=1.0800, tp=1.1200)
print(f"Trade: {result}")
await asyncio.sleep(10)
asyncio.run(main())
Calling login() while already logged in raises SessionError; call logout()
before switching accounts. A successful logout() clears stored reconnect
credentials. A rejected logout raises SessionError and retains the current
session state and credentials.
Trading Examples
Trade request wire format
MT5WebClient() uses the legacy trade request format by default. For the build 6090
market-execution environment reported in PR #1,
opt in explicitly:
client = MT5WebClient(trade_protocol="build_6090")
This profile adds the observed reserved bytes and uses action TRADE_ACTION_MARKET_DEAL (3)
for buy_market, sell_market, and close_position. The report covers selected symbols
on one demo server; compatibility with other brokers, execution modes, and builds has not
been established. The client does not select a profile from the handshake build number.
trade_request() and order_send() keep the action and fields supplied by the caller.
For example, callers using order_send() for a market-execution deal with this profile
must explicitly set action to TRADE_ACTION_MARKET_DEAL. The profile does not set
action_id or type_flags automatically; their requirements may depend on the server.
The PR's observed official-client requests used an action_id starting at 1000 and
type_flags=2; whether these values are required has not been established.
The buy_market(), sell_market(), and close_position() helpers accept optional
action_id and type_flags keywords when a server requires them; both default to zero.
Trade requests and acknowledgements do not establish that an order was filled; inspect
subsequent trade results and positions.
close_position() rejects non-positive position IDs. If order_type is omitted, it
looks up the position and raises ValidationError without placing a trade if the
position, its direction, or the position query is unavailable, or if the position's
symbol is missing or differs from the requested symbol. Pass an explicit
order_type only when the intended closing side is known.
from pymt5 import (
MT5WebClient, ORDER_TYPE_BUY, ORDER_TYPE_SELL,
TRADE_RETCODE_DONE, ORDER_FILLING_IOC,
)
# Market orders
result = await client.buy_market("XAUUSD", 0.1, sl=2300.0, tp=2400.0)
result = await client.sell_market("EURUSD", 0.05, deviation=30)
# Pending orders
result = await client.buy_limit("EURUSD", 0.1, price=1.0800, sl=1.0750, tp=1.0900)
result = await client.sell_stop("GBPUSD", 0.1, price=1.2500)
# Stop-limit orders (trigger at price, then place limit at stop_limit_price)
result = await client.buy_stop_limit("EURUSD", 0.1, price=1.1000,
stop_limit_price=1.0950, sl=1.0900, tp=1.1100)
result = await client.sell_stop_limit("GBPUSD", 0.1, price=1.2400,
stop_limit_price=1.2450)
# Close position (auto-detects BUY/SELL direction)
result = await client.close_position("EURUSD", position_id=123456, volume=0.1)
# Close by opposite position (hedge netting)
result = await client.close_position_by("EURUSD", position_id=123456,
position_by=789012)
# Modify SL/TP
result = await client.modify_position_sltp("EURUSD", position_id=123456,
sl=1.0850, tp=1.0950)
# Cancel pending order
result = await client.cancel_pending_order(order=789012)
Push Notifications
# Combined position+order updates in one callback
def on_trade_change(data):
print(f"Positions: {len(data['positions'])}, Orders: {len(data['orders'])}")
client.on_trade_update(on_trade_change)
# Trade transaction (order add/update/delete, balance updates)
def on_transaction(data):
if data.get('update_type') == 2:
print(f"Balance update: {data['balance_info']}")
else:
print(f"Order transaction: type={data.get('transaction_type')}")
client.on_trade_transaction(on_transaction)
# Account balance/equity changes
client.on_account_update(lambda d: print(f"Balance: {d.get('balance')}"))
# Extended symbol data with options greeks
client.on_symbol_details(lambda d: print(f"Delta: {d[0].get('delta')}"))
# Async trade execution results
client.on_trade_result(lambda d: print(f"Retcode: {d.get('result', {}).get('retcode')}"))
# Order book (DOM) updates
client.on_book_update(lambda entries: print(f"Book: {len(entries)} symbols"))
# Login status changes (e.g. forced logout)
client.on_login_status(lambda r: print(f"Login status: code={r.code}"))
Protocol Flow
connect()— opens WebSocket, sends bootstrap (cmd=0), receives new AES keylogin()— sends credentials (cmd=28), receives session token, starts heartbeat- Data commands (get_account, get_symbols, get_rates, get_positions_and_orders, etc.)
close()— stops heartbeat, sends logout (cmd=2), closes WebSocket
Important: Do NOT call init_session() (cmd=29) after login — that command is for demo/real account creation, not session initialization.
Logging
import logging
logging.basicConfig(level=logging.DEBUG)
# Loggers: pymt5.client, pymt5.transport
Examples
Run any example from the project root:
pip install -e .
python examples/01_connect_and_account.py
| Example | Description |
|---|---|
01_connect_and_account.py |
Login, full account info (cmd=3), positions/orders/deals, ping |
02_market_data.py |
Symbol groups (cmd=9), symbol cache, spreads (cmd=20), OHLCV rates |
03_realtime_ticks.py |
Tick subscription, account/symbol details/login status push handlers |
04_trading.py |
Market/limit/stop orders, modify SL/TP, position close |
05_order_book.py |
Order book (DOM) subscription (cmd=22/23) |
06_all_push_notifications.py |
All 9 push notification types in one example |
07_all_order_types.py |
All 9 order types + modify + cancel + close + raw trade_request |
08_misc_features.py |
Corporate links, notifications, trader params, verify code, reconnect |
Command Coverage
| CMD | Name | Status |
|---|---|---|
| 0 | Bootstrap | Implemented |
| 2 | Logout | Implemented |
| 3 | Get Account | NEW — balance, credit, currency, leverage, name |
| 4 | Get Positions/Orders | Implemented |
| 5 | Get Trade History | Implemented |
| 6 | Get Symbols | Implemented |
| 7 | Subscribe Ticks | Implemented |
| 8 | Tick Push | Implemented |
| 9 | Get Symbol Groups | NEW — Forex, Metals, Indexes, etc. |
| 10 | Trade Update Push | NEW — order add/update/delete + balance updates |
| 11 | Get Rates | Implemented |
| 12 | Trade Request | Implemented |
| 13 | Symbol Update Push | Implemented |
| 14 | Account Update Push | NEW — real-time balance/equity changes |
| 15 | Login Status Push | Implemented |
| 17 | Symbol Details Push | NEW — extended quotes with options greeks |
| 18 | Get Full Symbols | Implemented |
| 19 | Trade Result Push | NEW — async trade execution results |
| 20 | Get Spreads | IMPROVED — proper schema parsing |
| 22 | Subscribe Book | NEW — order book / DOM subscription |
| 23 | Book Push | NEW — order book push with bid/ask levels |
| 24 | Change Password | Implemented |
| 27 | Verify Code | Implemented |
| 28 | Login | Implemented |
| 29 | Init Session | Implemented |
| 30 | Open Demo | Implemented |
| 39 | Open Real Account | Implemented |
| 40 | Send Verification Codes | Implemented |
| 34 | Get Symbols (gzip) | Implemented |
| 41 | Trader Params | Implemented |
| 42 | Notify | Implemented |
| 43 | OTP Setup / Disable | Implemented |
| 44 | Get Corporate Links | NEW — broker links |
| 51 | Ping | Implemented |
| 52 | Reserved Bootstrap Command | EXPERIMENTAL — bootstrap-only helper with unknown business meaning |
Commands 21, 25, 33, 37, 50, 52, 100-112 are still accepted by the transport allowlist but have no direct usage in the current public Web Terminal JavaScript source. Live probing on 2026-03-16 showed that 21, 25, 33, 37, 50, 100-112 time out with an empty payload, while 52 only returns an empty ACK on a pristine bootstrap-only socket and causes the server to drop the connection after cmd=29 or cmd=28. Commands 39, 40, and 43 are now implemented in pymt5.
Documentation
Full API documentation is available on GitHub Pages.
To build docs locally:
pip install -e ".[docs]"
cd docs && make html
CI/CD
This project uses GitHub Actions for continuous integration, testing across:
| Python 3.11 | Python 3.12 | Python 3.13 | |
|---|---|---|---|
| Linux (ubuntu-latest) | ✅ | ✅ | ✅ |
| macOS (macos-latest) | ✅ | ✅ | ✅ |
| Windows (windows-latest) | ✅ | ✅ | ✅ |
The CI pipeline also runs mypy type checking and builds the Sphinx documentation. For a local pre-push pass, run:
make check
Notes
- This package is experimental and tracks the MT5 Web Terminal protocol as observed in the public Web Terminal build 5687 (built on 2026-03-15)
- MetaQuotes may change the protocol at any time
- Volume encoding: MT5 uses integer volumes where
volume = lots × 10^precision. The MetaQuotes demo server uses precision=8, so 1.0 lot = 100,000,000 and 0.01 lot = 1,000,000 get_account()(cmd=3) returns balance, credit, currency, leverage, and name. Equity, margin, and profit are computed from positions- The Symbol Details push (cmd=17) schema may produce approximate values — the exact byte layout for options greeks varies by server
- Order book (cmd=22/23) may not produce pushes on all servers — depends on broker configuration
- Some commands like
trader_params(cmd=41) may cause the server to close the connection on rapid successive calls
License
MIT License — see LICENSE for details.
Metadata
Release files for mt5-web-client 1.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| mt5_web_client-1.0.4.tar.gz | 171.6 kB | With provenance | Details for mt5_web_client-1.0.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| mt5_web_client-1.0.4-py3-none-any.whl | Python 3 | none | any | Details for mt5_web_client-1.0.4-py3-none-any.whl |
Total release size: 252.4 kB
Release files / mt5_web_client-1.0.4.tar.gz
| Download URL | mt5_web_client-1.0.4.tar.gz |
|---|---|
| Size | 171.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6b84013d7687b6b056dc51f5e1bb4ffc7579a5ea6a8d9c923550da685bd2803d
|
|
BLAKE2b-256 checksum How to use checksums |
f435260cd043153367ca122c41e93be966c37b55fd436e4fc5c1414537247421
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logRelease files / mt5_web_client-1.0.4-py3-none-any.whl
| Download URL | mt5_web_client-1.0.4-py3-none-any.whl |
|---|---|
| Size | 80.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
372e087b7da9375e13473977195dca8790ee12877d13ee9eef0d5d0cf916ae0c
|
|
BLAKE2b-256 checksum How to use checksums |
48143866b18382dce494a7565a40e943b58f89a7de8b51a01718dede1d9e054d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logSecurity for mt5-web-client 1.0.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for mt5_web_client-1.0.4.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.