tickerall
Official Python client for the TickerAll REST + WebSocket API.
Place trades, stream live market data, and manage broker sessions programmatically — without an MT4/MT5 terminal in the path. No Windows VM, no Wine, no MetaTrader5 terminal to babysit, no thread-safety workarounds.
pip install tickerall
Requires Python 3.9+. Depends only on httpx and websocket-client.
Why
The official MetaTrader5 Python package only runs on Windows, drives a local terminal over a single-threaded IPC channel, and falls over under concurrency. TickerAll hosts the broker connection for you and exposes it as a clean HTTP + WebSocket API, so your bot can run anywhere — Linux, macOS, a container, a Raspberry Pi — and stream ticks instead of polling.
MetaTrader5 (local terminal) |
tickerall |
|
|---|---|---|
| OS | Windows only | anywhere Python runs |
| Live ticks | poll symbol_info_tick() per symbol |
push over WebSocket |
| Concurrency | single-threaded IPC, not thread-safe | stateless HTTP, thread-safe |
| Deploy | a terminal per account to babysit | pip install |
Quickstart
from tickerall import Tickerall
client = Tickerall(api_key="cf_live_...")
# Connect a broker account → get a TickerAll account_id
session = client.sessions.start(
broker="mt5",
server="Exness-MT5Trial7",
account=12345678,
password="...",
)
# Place a market order
order = client.orders.place(
session.account_id,
type="market",
symbol="BTCUSDm",
side="BUY",
volume=0.10,
stop_loss=58000.0,
take_profit=72000.0,
)
print(order.ticket, order.status)
client.sessions.end(session.account_id)
The client is a context manager too:
with Tickerall(api_key="cf_live_...") as client:
...
Terminal type (MOBILE / WEB / CLIENT)
terminal_type picks which client the connection presents AS — "MOBILE" (the
default), "WEB", or "CLIENT" (a desktop terminal). All expose the full surface
(account, quotes, positions, history). The type sets the broker-assigned order
origin (ENUM_DEAL_REASON): "MOBILE" → DEAL_REASON_MOBILE, "WEB" →
DEAL_REASON_WEB, "CLIENT" → DEAL_REASON_CLIENT — useful where a venue
distinguishes desktop-placed orders (e.g. some prop firms).
"WEB" requires the broker's web-terminal URL (web_terminal_url) — web
terminals are per-broker-domain, so the URL must be supplied. "MOBILE" and
"CLIENT" take neither web field:
session = client.sessions.start(
broker="mt5",
server="YourBroker-Server",
account=12345678,
password="...",
terminal_type="WEB",
web_terminal_url="https://mt5.yourbroker.com", # required for WEB
# web_endpoint="wss://host/path", # optional WS override (rare)
)
For a desktop-origin (DEAL_REASON_CLIENT) connection — no web URL needed:
session = client.sessions.start(
broker="mt5",
server="YourBroker-Server",
account=12345678,
password="...",
terminal_type="CLIENT",
)
Streaming — push, not poll
The stream runs on its own background thread. Register callbacks and go; it heartbeats, reconnects with backoff, and re-subscribes automatically.
client = Tickerall(api_key="cf_live_...")
session = client.sessions.start(broker="mt5", server="Exness-MT5Trial7",
account=12345678, password="...")
stream = client.stream.connect()
stream.on("tick", lambda e: print(e.symbol, e.bid, e.ask, e.timestamp))
stream.on("position", lambda e: print(e.event, e.position.ticket, e.position.profit))
stream.subscribe_ticks(session.account_id, ["BTCUSDm", "ETHUSDm"])
stream.subscribe_positions(session.account_id)
# ... your app runs ...
stream.close()
Keep an in-memory tick cache fresh (zero polling)
A common pattern: let the WebSocket fill a dict so price reads are O(1) with no network call — strictly better than polling a terminal per symbol.
latest: dict[str, "TickEvent"] = {}
stream = client.stream.connect()
stream.on("tick", lambda e: latest.__setitem__(e.symbol, e))
stream.subscribe_ticks(session.account_id, ["BTCUSDm", "ETHUSDm", "XAUUSDm"])
# Anywhere in your app — instant, no IPC, no thread-safety dance:
tick = latest.get("BTCUSDm")
Market data & history
# Historical OHLC candles (coarser timeframes reach further back)
bars = client.candles.get(session.account_id, symbol="BTCUSDm", hours=24, timeframe="M5")
for c in bars:
print(c.timestamp, c.open, c.high, c.low, c.close)
# Closed-trade history (recent broker window)
trades = client.history.get(session.account_id, symbol="BTCUSDm", limit=100)
# Tradeable symbols and their volume specs
symbols = client.accounts.symbols(session.account_id)
specs = client.accounts.symbol_specs(session.account_id) # volume min/max/step + base/quote/margin currency (MT5)
# Remove an account from your roster (disconnects it + drops it from your list
# and billing; broker account and open positions are untouched). Reversible —
# reconnect the same login with sessions.start to re-add it.
client.accounts.remove(session.account_id)
Positions
detail = client.accounts.get(session.account_id)
for p in detail.positions:
print(p.ticket, p.symbol, p.side, p.volume, p.profit)
client.positions.modify(session.account_id, ticket=p.ticket, stop_loss=60000.0)
client.positions.close(session.account_id, ticket=p.ticket) # full close
client.positions.close(session.account_id, ticket=p.ticket, volume=0.05) # partial
Bulk operations
Execute one action across many of your accounts in a single request. Bulk requires an eligible plan — a request without it returns 403 BULK_REQUIRES_PRO; see pricing for what's included. A bulk call spans N broker sessions and is not atomic — partial success is the contract: each account's outcome is in results, and a mix of success/failure still returns (nothing is raised).
Bulk trading (place/close/modify/cancel) currently runs on demo accounts; live trading is coming soon. The bulk read works on all your accounts, demo or live.
# Place the same order across several accounts
placed = client.bulk.place([
{"account_id": "acc_1", "type": "market", "symbol": "EURUSDm", "side": "BUY", "volume": 0.1},
{"account_id": "acc_2", "type": "market", "symbol": "EURUSDm", "side": "BUY", "volume": 0.2},
])
# placed.results -> [BulkPlaceResult(account_id, status='filled'|'failed', ticket, price, ...)]
# placed.summary -> BulkPlaceSummary(total, filled, failed)
# Close positions — explicit tickets, or by intent (flatten all / by symbol+side)
client.bulk.close_positions(items=[{"account_id": "acc_1", "ticket": 4072808150}])
client.bulk.close_positions(targets=[{"account_id": "exness_acc", "symbol": "EURUSDm"}, {"account_id": "xm_acc", "symbol": "EURUSD"}]) # each account, its own broker-native symbol
client.bulk.close_positions(targets=[{"account_id": "acc_1"}, {"account_id": "acc_2"}]) # no symbol on a target → flatten that account
# Modify SL/TP, cancel + modify pending orders
client.bulk.modify_positions([{"account_id": "acc_1", "ticket": 4072808150, "stop_loss": 1.0850}])
client.bulk.cancel_pending(targets=[{"account_id": "acc_1", "symbol": "EURUSDm"}])
client.bulk.modify_pending([{"account_id": "acc_1", "ticket": 4072808151, "price": 1.0805}])
# Read live state for many accounts at once (balance/equity/margin + open positions)
roster = client.bulk.read_accounts() # your whole roster
some = client.bulk.read_accounts(ids=["acc_1", "acc_2"], include=["account", "positions"])
# roster.accounts -> [BulkAccountState(id, status='online'|'offline', account, positions, ...)]
# roster.summary -> BulkAccountsSummary(total, online, offline)
In close/cancel by intent, each target carries its own broker-native symbol — so a mixed-broker roster (Exness EURUSDm + XM EURUSD) is handled in one call. Omit a target's symbol to flatten every position on that account; a target whose symbol matches nothing simply reports no matching open positions.
Every write method auto-generates an idempotency key (pass idempotency_key= to dedupe retries), like the single-account methods.
Always-hot sessions & transparent re-arm
For connections that must stay up across restarts, use keep_alive. The
credentials live in this process's memory only (never persisted); if the
account goes cold (e.g. TickerAll restarted), the next call transparently
re-supplies them and retries once.
session = client.sessions.keep_alive(broker="mt5", server="Exness-MT5Trial7",
account=12345678, password="...")
# ... later, after an outage, this just works — the client re-arms under the hood:
client.accounts.get(session.account_id)
# Stop keeping it alive (drops the cached credentials):
client.sessions.stop_keep_alive(session.account_id)
Reliability — idempotency & queue-and-replay
State-changing calls (sessions.start, orders.place, positions.close,
positions.modify) carry a stable Idempotency-Key, so a retried call can't
double-execute. By default a transient connectivity failure
(TickerallServiceUnavailableError, .transient == True) fails fast so you
can re-decide with fresh prices:
from tickerall import TickerallServiceUnavailableError
try:
client.orders.place(account_id, type="market", symbol="BTCUSDm", side="BUY", volume=0.1)
except TickerallServiceUnavailableError:
... # momentary blip — safe to retry
For price-insensitive orders (pending orders, SL/TP edits) you can instead queue-and-replay until connectivity returns:
client.orders.place(
account_id, type="limit", symbol="BTCUSDm", side="BUY", volume=0.1, price=60000.0,
queue_if_reconnecting=True, queue_max_s=60.0,
)
Errors
All errors derive from TickerallApiError and carry .status, .code,
.request_id, .details, and .transient:
| Class | When |
|---|---|
TickerallAuthError |
401 — bad/missing API key |
TickerallForbiddenError |
403 — plan limit / reserved resource |
TickerallValidationError |
400 / 422 — malformed request |
TickerallNotFoundError |
404 — account / position not found |
TickerallBrokerError |
broker rejected or could not satisfy the request |
TickerallServiceUnavailableError |
transient — TickerAll momentarily unreachable (safe to retry) |
Using it from an async app
REST methods are synchronous and thread-safe, so call them from an event loop
via asyncio.to_thread:
detail = await asyncio.to_thread(client.accounts.get, account_id)
The stream is already non-blocking (its own thread) — callbacks fire as events arrive.
License
MIT © Miguel Santos
Release files for tickerall 0.5.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tickerall-0.5.2.tar.gz | 32.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tickerall-0.5.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 73.0 kB
Release files / tickerall-0.5.2.tar.gz
| Download URL | tickerall-0.5.2.tar.gz |
|---|---|
| Size | 32.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
998a868fd09a0e535a523b77f6d5ccaa518e285153d3196c3811bb1c4df51d49
|
|
BLAKE2b-256 checksum How to use checksums |
05dd1ba442555501b8de42a3eb82e3eeef4b2901caa470fae0fd337b72775dab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.2
|
Release files / tickerall-0.5.2-py3-none-any.whl
| Download URL | tickerall-0.5.2-py3-none-any.whl |
|---|---|
| Size | 40.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
65ad32758a86b20b192151e6f4d50f6b21e9cc4d862eca5473cfd99f25e28fab
|
|
BLAKE2b-256 checksum How to use checksums |
b2f072d7408118157cea8ef13532ce97774408c0fed91b32449b53f108b93d8d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.2
|