Skip to main content

Python SDK for integrating with Tonpo Gateway, enabling secure access to MetaTrader 5 trading services.

Project description

tonpo-py

PyPI version Python License

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)
)

TonpoConnectionError is intentionally NOT named ConnectionError — that would shadow Python's built-in builtins.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_active default timeout raised to 180s

v1.0.0 — 2026-04-10

  • Initial release
  • TonpoClient with admin() and for_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.typed PEP 561 marker for full IDE type hint support
  • GitHub Actions workflow for automated PyPI publishing on git tag
  • create_account sends camelCase keys matching gateway's Rust struct (mt5Login, mt5Password, mt5Server)
  • TonpoConnectionError named to avoid shadowing builtins.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

tonpo-1.0.5.tar.gz (43.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tonpo-1.0.5-py3-none-any.whl (32.1 kB view details)

Uploaded Python 3

File details

Details for the file tonpo-1.0.5.tar.gz.

File metadata

  • Download URL: tonpo-1.0.5.tar.gz
  • Upload date:
  • Size: 43.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tonpo-1.0.5.tar.gz
Algorithm Hash digest
SHA256 d611ae68bf54510837dd84645e879be7fe5445c6ba541205173fe865ab4c2546
MD5 ed5d0c8723b8ea2fe312dfcd191f4109
BLAKE2b-256 017cf0ecaaec927bf2b7cdcbec0764087ff861778956cc1677c209d72e15ca15

See more details on using hashes here.

Provenance

The following attestation bundles were made for tonpo-1.0.5.tar.gz:

Publisher: publish.yml on TonpoLabs/tonpo-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tonpo-1.0.5-py3-none-any.whl.

File metadata

  • Download URL: tonpo-1.0.5-py3-none-any.whl
  • Upload date:
  • Size: 32.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tonpo-1.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 dc0bcd0f9473e3b1b395f6c3cb9d0c8f7a0e7bd3b22e6a2be8c099f7af5c05d9
MD5 002fe0f9a50c7fe2da0ac4b2fd347aaf
BLAKE2b-256 187faccd960183eeb22e3c8ef6251387a359167b4420aeac425f4ea4443f5b0c

See more details on using hashes here.

Provenance

The following attestation bundles were made for tonpo-1.0.5-py3-none-any.whl:

Publisher: publish.yml on TonpoLabs/tonpo-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page