Python SDK for the Tonpo Gateway — self-hosted MT5 trading infrastructure
Project description
tonpo-py
Official Python SDK for the Tonpo Gateway — a proprietary MT5 trading infrastructure platform.
What is Tonpo?
The Tonpo Gateway is a Rust server that manages MetaTrader 5 terminal connections through the Tonpo Bridge (C++ DLL + MQL5 EA). Instead of routing through third-party cloud APIs, the gateway runs on your own infrastructure — giving you full control over latency, cost, and data.
Your App (Python)
│
│ HTTPS / WSS
▼
Tonpo Gateway (Rust — your server)
│
│ WebSocket
▼
Tonpo Bridge (C++ DLL + MQL5 EA)
│
▼
MT5 Terminal → Broker
This SDK handles all communication with the gateway so you never write raw HTTP calls.
Installation
pip install tonpo
Install the latest development version from GitHub:
pip install git+https://github.com/TonpoLabs/tonpo-py.git
Clone and install locally for development:
git clone https://github.com/TonpoLabs/tonpo-py.git
cd tonpo-py
pip install -e ".[dev]"
Requirements: Python 3.10+
Quick Start
import asyncio
from tonpo import TonpoClient, TonpoConfig
config = TonpoConfig(
host="gateway.cipherbridge.cloud",
port=443,
use_ssl=True,
)
async def main():
# 1. Create a gateway user — once per user, store the credentials
async with TonpoClient.admin(config) as client:
user = await client.create_user()
print(f"api_key: {user.api_key}")
print(f"user_id: {user.gateway_user_id}")
# 2. Provision an MT5 account — once per account
async with TonpoClient.for_user(config, user.api_key) as client:
account = await client.create_account(
mt5_login="105745233",
mt5_password="YourMT5Password",
mt5_server="FBS-Demo",
)
print(f"account_id: {account.account_id}")
# Wait for MT5 to log in — Windows VPS cold start takes 2–4 minutes
await client.wait_for_active(account.account_id, timeout=180)
print("MT5 connected!")
# 3. Trade — api_key + account_id is all you need from now on
async with TonpoClient.for_user(config, user.api_key) as client:
info = await client.get_account_info()
print(f"Balance: {info.balance} {info.currency}")
result = await client.place_market_buy("EURUSD", volume=0.1, sl=1.0800, tp=1.1000)
print(f"Order placed: ticket={result.ticket}")
asyncio.run(main())
Core Concept — Credential Flow
The gateway owns your MT5 credentials. The SDK reflects this:
Step 1 — Register (once per user)
create_user() → api_key + gateway_user_id (store both)
create_account() → account_id (store this)
wait_for_active() → confirms MT5 is logged in
Step 2 — Every subsequent request
for_user(api_key) → authenticates the user
account_id → tells gateway which MT5 account to act on
MT5 login, password, server — never needed again after Step 1.
Store only these values per user in your database:
| Field | Description |
|---|---|
tonpo_api_key |
Authenticates every request |
tonpo_user_id |
User identity on the gateway |
tonpo_account_id |
Identifies which MT5 account to act on |
Configuration
from tonpo import TonpoConfig
config = TonpoConfig(
host="gateway.cipherbridge.cloud", # gateway hostname or IP
port=443, # 443 for SSL, 8080 for plain HTTP
use_ssl=True, # must match your Nginx setup
api_key_header="X-API-Key", # header name (default is correct)
connect_timeout=10.0, # seconds to establish connection
request_timeout=30.0, # seconds to wait for response
ws_reconnect_delay=5.0, # seconds between WebSocket reconnect attempts
max_reconnect_attempts=5, # max reconnects before raising error
)
Client Modes
Admin client — no authentication
Used only for health_check() and create_user().
async with TonpoClient.admin(config) as client:
healthy = await client.health_check()
user = await client.create_user()
User client — authenticated
Used for all trading and account operations.
async with TonpoClient.for_user(config, api_key="your-api-key") as client:
info = await client.get_account_info()
Manual lifecycle
client = TonpoClient(config, api_key="your-api-key")
await client.start()
try:
await client.get_account_info()
finally:
await client.stop()
API Reference
Health
healthy = await client.health_check() # → bool
User Management
# Create a new gateway user — no auth required
user = await client.create_user()
# user.gateway_user_id — store in DB
# user.api_key — store in DB (shown once — save immediately)
Account Lifecycle
# Provision a new MT5 account
account = await client.create_account(
mt5_login="105745233",
mt5_password="password",
mt5_server="FBS-Demo",
region="eu", # optional — route to specific node region
)
# account.account_id — store in DB
# Wait for MT5 to become active (logged in to broker)
# Default timeout is 180s — Windows VPS cold start takes 2–4 minutes
await client.wait_for_active(account.account_id, timeout=180)
# Check status manually
status = await client.get_account_status(account.account_id)
# status["status"] — "active", "connecting", "paused", "login_failed", "deleted"
# status["last_error"] — error message if status is "login_failed"
# List all accounts for this user
accounts = await client.get_accounts() # → List[dict]
# Pause/resume (keeps MT5 connected, blocks new orders while paused)
await client.pause_account(account.account_id)
await client.resume_account(account.account_id)
# Remove account permanently — deprovisions the MT5 instance
await client.delete_account(account.account_id)
Account Information
info = await client.get_account_info()
# info.login → int
# info.name → str
# info.server → str
# info.balance → float
# info.equity → float
# info.margin → float
# info.free_margin → float
# info.leverage → int
# info.currency → str ("USD", "EUR", ...)
# info.profit → float
# info.margin_level → float (computed: margin / equity * 100)
Positions
positions = await client.get_positions()
for p in positions:
print(p.ticket, p.symbol, p.side, p.volume, p.profit)
# Close a position (full or partial)
result = await client.close_position(ticket=123456)
result = await client.close_position(ticket=123456, volume=0.05) # partial
# Modify SL/TP
result = await client.modify_position(ticket=123456, sl=1.0800, tp=1.1000)
Orders
# Market orders
result = await client.place_market_buy("EURUSD", volume=0.1)
result = await client.place_market_sell("GBPUSD", volume=0.2, sl=1.2500, tp=1.2200)
# Limit orders
result = await client.place_limit_buy("EURUSD", volume=0.1, price=1.0750)
result = await client.place_limit_sell("EURUSD", volume=0.1, price=1.1050)
# Stop orders
result = await client.place_stop_buy("EURUSD", volume=0.1, price=1.0950)
result = await client.place_stop_sell("EURUSD", volume=0.1, price=1.0700)
# All order methods accept optional parameters
result = await client.place_market_buy(
symbol="EURUSD",
volume=0.1,
sl=1.0800, # absolute stop loss price
tp=1.1000, # absolute take profit price
comment="bot", # visible in MT5 order history
magic=12345, # magic number — identifies your bot's orders in MT5
)
# result.ticket → int — MT5 ticket number
# result.success → bool
# result.error → str | None — broker error message on failure
Market Data (REST)
price = await client.get_symbol_price("EURUSD")
# price.symbol → "EURUSD"
# price.bid → float
# price.ask → float
# Automatically falls back to WebSocket price cache if REST returns zeros
Real-Time Data (WebSocket)
# Register callbacks before subscribing
def on_tick(tick):
print(f"{tick.symbol} bid={tick.bid} ask={tick.ask}")
async def on_position_update(position):
print(f"Position {position.ticket}: profit={position.profit}")
client.ws.on_tick("EURUSD", on_tick)
client.ws.on_position(on_position_update)
client.ws.on_candle("EURUSD", "H1", lambda c: print(f"H1 close={c.close}"))
client.ws.on_order_result(lambda r: print(f"Order {r.ticket} ok={r.success}"))
client.ws.on_account(lambda a: print(f"Balance={a.balance} {a.currency}"))
# Start WebSocket connection and subscribe to symbols
await client.subscribe(["EURUSD", "GBPUSD"])
# Keep event loop alive to receive data
await asyncio.sleep(3600)
# Unsubscribe and check connection
await client.unsubscribe(["GBPUSD"])
alive = await client.ping_ws() # → bool
Models
| Model | Key fields |
|---|---|
TonpoConfig |
host, port, use_ssl, api_key_header, connect_timeout, request_timeout, ws_reconnect_delay, max_reconnect_attempts |
UserCredentials |
gateway_user_id, api_key |
AccountCredentials |
account_id, auth_token |
AccountInfo |
login, name, server, balance, equity, margin, free_margin, leverage, currency, profit, margin_level |
Position |
ticket, symbol, side, volume, open_price, current_price, profit, swap, commission, sl, tp, open_time, comment |
OrderResult |
ticket, success, error |
SymbolPrice |
symbol, bid, ask |
Tick |
symbol, bid, ask, last, volume, time |
Quote |
symbol, bid, ask, time, spread, mid |
Candle |
symbol, timeframe, time, open, high, low, close, volume, complete |
Exceptions
All exceptions inherit from TonpoError.
from tonpo import (
TonpoError, # base — catch-all
NotStartedError, # client used before start() / outside async with
AuthenticationError, # invalid or revoked API key
AccountNotFoundError, # account_id does not exist on gateway
AccountLoginFailedError, # MT5 credentials rejected by broker
AccountTimeoutError, # account did not become active in time
OrderError, # order placement/close/modify failed
TonpoConnectionError, # HTTP or WebSocket connection failed
SubscriptionError, # WebSocket market-data subscription failed
TonpoResponseError, # unexpected HTTP response (.status_code, .raw)
)
TonpoConnectionErroris intentionally NOT namedConnectionError— that would shadow Python's built-inbuiltins.ConnectionError.
Error handling
from tonpo import (
TonpoClient,
AccountLoginFailedError,
AccountTimeoutError,
TonpoConnectionError,
AuthenticationError,
TonpoError,
)
# Account provisioning
try:
await client.wait_for_active(account.account_id, timeout=180)
except AccountLoginFailedError as e:
print(f"Wrong MT5 credentials: {e}")
await client.delete_account(account.account_id)
except AccountTimeoutError as e:
print(f"MT5 took too long to connect: {e}")
# Trading
try:
result = await client.place_market_buy("EURUSD", volume=0.1)
except AuthenticationError:
print("API key invalid — re-register the user")
except TonpoConnectionError:
print("Cannot reach gateway — check server")
except TonpoError as e:
print(f"Gateway error: {e}")
Usage in a Telegram Bot
from tonpo import TonpoClient, TonpoConfig, AccountLoginFailedError, AccountTimeoutError
config = TonpoConfig(host="gateway.cipherbridge.cloud", port=443, use_ssl=True)
# ── Registration handler ───────────────────────────────────────────────────────
# Called once when user submits their MT5 credentials.
async def register_user(telegram_id, mt5_login, mt5_password, mt5_server):
async with TonpoClient.admin(config) as c:
user = await c.create_user()
async with TonpoClient.for_user(config, user.api_key) as c:
account = await c.create_account(mt5_login, mt5_password, mt5_server)
try:
await c.wait_for_active(account.account_id, timeout=180)
except AccountLoginFailedError:
await c.delete_account(account.account_id)
raise
# Store only these — credentials never needed again
db.save(
telegram_id = telegram_id,
tonpo_api_key = user.api_key,
tonpo_user_id = user.gateway_user_id,
tonpo_account_id = account.account_id,
)
# ── Trade handler ─────────────────────────────────────────────────────────────
async def place_buy(telegram_id, symbol, volume):
row = db.get(telegram_id=telegram_id)
async with TonpoClient.for_user(config, row.tonpo_api_key) as c:
result = await c.place_market_buy(symbol, volume=volume)
return result.ticket
# ── Balance check ─────────────────────────────────────────────────────────────
async def get_balance(telegram_id):
row = db.get(telegram_id=telegram_id)
async with TonpoClient.for_user(config, row.tonpo_api_key) as c:
info = await c.get_account_info()
return info.balance, info.currency
Project Structure
tonpo-py/
├── pyproject.toml # packaging metadata
├── setup.py # legacy build shim
├── MANIFEST.in # source distribution file list
├── LICENSE
├── README.md
├── CHANGELOG.md
├── .gitignore
├── .github/
│ └── workflows/
│ └── publish.yml # auto-publishes to PyPI on git tag
└── tonpo/
├── __init__.py # public API + __version__
├── client.py # TonpoClient — main entry point
├── models.py # all dataclasses
├── exceptions.py # exception hierarchy
├── transport.py # HTTP layer (httpx)
├── websocket.py # WebSocket layer (auto-reconnection)
└── py.typed # PEP 561 marker — enables IDE type hints
Publishing a Release
# 1. Bump version in pyproject.toml and tonpo/__init__.py
# 2. Add entry to CHANGELOG.md
# 3. Commit, tag, and push
git add .
git commit -m "Release v1.0.5"
git tag v1.0.5
git push origin main
git push origin v1.0.5
# GitHub Actions builds and uploads to PyPI automatically
Development
git clone https://github.com/TonpoLabs/tonpo-py.git
cd tonpo-py
pip install -e ".[dev]"
pytest
pytest tests/test_client.py -v
Dev dependencies:
| Package | Purpose |
|---|---|
httpx>=0.24 |
Async HTTP client |
websockets>=11.0 |
Async WebSocket client |
pytest |
Test runner |
pytest-asyncio |
Async test support |
respx |
httpx request mocking |
Changelog
v1.0.4 — 2026-04-19
- License updated to Proprietary
- Gateway URL updated to
gateway.cipherbridge.cloud wait_for_activedefault timeout raised to 180s
v1.0.0 — 2026-04-10
- Initial release
TonpoClientwithadmin()andfor_user()factory methods- Full account lifecycle:
create_account,wait_for_active,get_account_status,get_accounts,delete_account,pause_account,resume_account - All order types: market, limit, stop (buy and sell)
- Position management:
get_positions,close_position,modify_position - Account info:
get_account_info - Market data:
get_symbol_price(REST + WebSocket cache fallback) - WebSocket real-time data with auto-reconnection: ticks, quotes, candles, positions, order results, account updates
- Typed dataclass models for all gateway responses
py.typedPEP 561 marker for full IDE type hint support- GitHub Actions workflow for automated PyPI publishing on git tag
create_accountsends camelCase keys matching gateway's Rust struct (mt5Login,mt5Password,mt5Server)TonpoConnectionErrornamed to avoid shadowingbuiltins.ConnectionError
License
Proprietary — All rights reserved. © Tonpo. Unauthorised copying, distribution, or use is strictly prohibited.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tonpo-1.0.4.tar.gz.
File metadata
- Download URL: tonpo-1.0.4.tar.gz
- Upload date:
- Size: 43.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3dd1364b79a16e340e5a2242dc65c1cf94c2a0deeb19735ce239874ca19291e
|
|
| MD5 |
a86a3cff59d2d134744ee1feaa78f870
|
|
| BLAKE2b-256 |
57aed75ca6a57376317813fc15c37bdff716fb4aebc1b02e6484e8fd415c209d
|
Provenance
The following attestation bundles were made for tonpo-1.0.4.tar.gz:
Publisher:
publish.yml on TonpoLabs/tonpo-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tonpo-1.0.4.tar.gz -
Subject digest:
e3dd1364b79a16e340e5a2242dc65c1cf94c2a0deeb19735ce239874ca19291e - Sigstore transparency entry: 1368319828
- Sigstore integration time:
-
Permalink:
TonpoLabs/tonpo-py@226c2b86059bbd842a2d3beea7ab5d689fc03809 -
Branch / Tag:
refs/tags/v1.0.4 - Owner: https://github.com/TonpoLabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@226c2b86059bbd842a2d3beea7ab5d689fc03809 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tonpo-1.0.4-py3-none-any.whl.
File metadata
- Download URL: tonpo-1.0.4-py3-none-any.whl
- Upload date:
- Size: 32.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
53f6bb38b63a7c8210b1adbe213572d901e7c3e2cbc80f7d29718854a9514538
|
|
| MD5 |
bc2e12f638a0dc53b8e29aa10ed39f02
|
|
| BLAKE2b-256 |
001b3e737a0e4da339ed448afad4ae9324e76599c6fa94def00d6edaf319824c
|
Provenance
The following attestation bundles were made for tonpo-1.0.4-py3-none-any.whl:
Publisher:
publish.yml on TonpoLabs/tonpo-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tonpo-1.0.4-py3-none-any.whl -
Subject digest:
53f6bb38b63a7c8210b1adbe213572d901e7c3e2cbc80f7d29718854a9514538 - Sigstore transparency entry: 1368319844
- Sigstore integration time:
-
Permalink:
TonpoLabs/tonpo-py@226c2b86059bbd842a2d3beea7ab5d689fc03809 -
Branch / Tag:
refs/tags/v1.0.4 - Owner: https://github.com/TonpoLabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@226c2b86059bbd842a2d3beea7ab5d689fc03809 -
Trigger Event:
push
-
Statement type: