Trade MetaTrader 5 from Python with the fxapis SDK
pip install fxapis: connect an MT5 account, place orders that never double-fill, and let the SDK retry only what is safe — from Python on Linux, macOS or Windows.
In short
- pip install fxapis gives you sync and async clients for the hosted MT5 API, fully typed, on Python 3.10 or newer — with no MetaTrader terminal on your machine.
- Orders get an idempotency key automatically, so a retried order returns the first answer instead of opening a second position.
- Each failure is its own exception. OrderUnresolvedError is never retried — wait_until_resolved() polls it; codes that prove nothing was done are retried for you.
- It runs anywhere Python runs, including Linux and macOS, unlike the official MetaTrader5 package.
On this page
The official Python SDK for fxapis is a typed client for the hosted MetaTrader 5 REST API: sync and async clients on httpx, idempotency keys added for you, and one exception class per error code. It needs Python 3.10 or newer and is open source at github.com/FXapis/fxapis-python.
Unlike the official MetaTrader5 package, it does not drive a terminal on your machine. The terminals run in fxapis's cloud; the SDK makes HTTPS requests — so it runs on Linux, macOS, Windows, a container or a scheduled job. The MetaTrader5 package vs a REST API compares the two in detail.
Install it
pip install fxapis
export FXAPIS_API_KEY="fx_test_…" # create one at fxapis.com/dashboard/keys
Use a broker demo account while you build: every fxapis key, fx_test_ keys included, reaches a real broker. There is no simulated market.
From an MT5 login to a closed trade
from fxapis import Fxapis
client = Fxapis() # reads FXAPIS_API_KEY
# 1. Connect the account once. The trading password, not the investor password.
account = client.accounts.connect(
login="26177561",
server="VantageMarkets-Demo",
password="your-mt5-trading-password",
mode="warm_on_demand", # online when needed, offline after 15 idle minutes
)
# 2. Bring it online and wait for `ready` — about ten seconds for a typical broker.
client.accounts.warm(account["id"])
client.accounts.wait_until_ready(account["id"])
# 3. Trade. Volumes and prices are strings: "0.01", not 0.01.
order = client.orders.market(account["id"], symbol="EURUSD", side="buy", volume="0.01")
print(order["state"], order["filledPrice"])
# 4. Read the positions, then close them.
for position in client.positions.list(account["id"]):
print(position["symbol"], position["volume"], position["profit"])
client.positions.close(account["id"], position["brokerPositionId"])
with Fxapis() as client: closes the connection pool for you. Connecting does not log in: it stores the account and encrypts its password, and warm starts the login. wait_until_ready() raises if the account needs a person — a wrong password, 2FA, a certificate — instead of waiting forever.
Orders that never double-fill
client.orders.market() sends an Idempotency-Key on every order, generated for you. If the connection drops after the order was sent, the SDK resends it with the same key, and fxapis answers with the first result instead of opening a second position. When the key should mean something in your system — a signal and a member, a copied deal — pass your own:
key = f"signal_{signal_id}:member_{member_id}"
order = client.orders.market(account_id, symbol="XAUUSD", side="buy", volume="0.05", idempotency_key=key)
A member who taps twice, or a job that runs twice, gets the same order back. Idempotent orders explains why this matters.
Handling each outcome
from fxapis import OrderRejectedError, OrderUnresolvedError, SendFailedError, AccountNotReadyError
try:
order = client.orders.market(account_id, symbol="XAUUSD", side="buy", volume="0.05", idempotency_key=key)
except OrderRejectedError as err:
print("rejected:", err.message) # nothing opened; a new attempt needs a NEW key
except OrderUnresolvedError as err:
order = client.orders.wait_until_resolved(err.order_id) # never resend: wait for the answer
except (SendFailedError, AccountNotReadyError) as err:
print("not sent:", err.code) # already retried with the same key; safe to try later
The SDK retries only what is provably safe: codes that mean nothing was done are resent with the same key, and server errors and lost connections are retried on reads and on requests carrying a key. ORDER_UNRESOLVED is never retried — the order may be live at the broker — and fxapis confirms an opening order against the broker's own deal history.
Async, pages and many accounts
import asyncio
from fxapis import AsyncFxapis
async def main() -> None:
async with AsyncFxapis() as client:
async for order in client.orders.iter(state="unknown"):
print(order["id"], order["symbol"])
asyncio.run(main())
iter() walks a paginated list for you. For one trade on many accounts — a copier, a signal service — client.multi_account_orders.create() sends a single order to up to 1,000 accounts, within your plan's limit, with a volume per account.
Where to go next
- fxapis-examples has a quickstart, click-to-trade and a trade copier in Python — see the examples post.
- SDKs and examples in the docs, and the Python guide to see every request with plain
requests. - Trading covers every outcome an order can have, and Errors every code.