open-api-mt5
MetaTrader 5 API service with FastAPI.
Important limitation
This API controls a single MT5 terminal/session instance per running service process.
- A single API instance can be connected to only one account at a time.
/account/connectswitches that single active session.- If you run multiple API services, use separate MT5 terminal instances/data folders for reliable isolation.
Setup
- Create a virtual environment:
- Windows PowerShell:
python -m venv .venv
- Windows PowerShell:
- Activate it:
.\.venv\Scripts\Activate.ps1
- Install dependencies:
python -m pip install -U pippip install -e .
MT5 startup config
The API initializes MetaTrader 5 when FastAPI starts and closes it when FastAPI stops. It also checks MT5 connection every 5 seconds and tries to reconnect automatically if disconnected.
You can configure startup with a server_config.json file in the project directory:
{
"path": "C:\\Program Files\\MetaTrader 5\\terminal64.exe",
"username": "12345678",
"password": "your-password",
"server": "YourBroker-Server",
"account": "12345678",
"multiTerminal": true,
"portable": true,
"port": 8000,
"apiKey": "optional-api-key"
}
account is required and is always checked against the connected MT5 account after initialization. The API loads MT5 and server startup settings from the JSON config file only; environment variables are not used for these settings. When the connected account does not match, it restarts the configured terminal path and retries up to 3 times; if the account still does not match, the API exits. If path is set, the API starts MT5 with portable=true by default so separate API instances can attach to separate MT5 terminal folders. Set "portable": false only if you intentionally want the normal shared MT5 data-directory behavior.
When running multiple API instances on the same Windows machine, start each process with its own explicit --config file containing a different path, account, and port.
Mode, apiKey, apiSecret, and registryUrl are config-file startup settings and are not stored by /account/connect.
Virtual terminal mode
Virtual mode runs the same HTTP market-data API without starting MetaTrader 5. Dukascopy supplies symbols, quotes, ticks, and OHLC bars; POST /orders sends an MT5-shaped order request as JSON through ZeroMQ or Kafka.
{
"virtual": true,
"dataSource": "dukascopy",
"port": 8000,
"dukascopy": {
"baseUrl": "https://jetta.dukascopy.com/v1",
"timeoutSeconds": 15,
"maxBucketsPerRequest": 400
},
"orderTransport": "kafka",
"zeroMq": {
"host": "127.0.0.1",
"port": 5555,
"topic": "ORDER",
"socketType": "PUSH",
"mode": "connect",
"lingerMilliseconds": 1000
},
"kafka": {
"bootstrapServers": "broker.example.com:9093",
"topic": "orders",
"clientId": "open-api-mt5",
"securityProtocol": "SASL_SSL",
"saslMechanism": "PLAIN",
"saslUsername": "your-kafka-username",
"saslPassword": "your-kafka-password",
"sslCaBase64": "base64-encoded-ca-pem",
"acks": "all",
"deliveryTimeoutSeconds": 10
}
}
The Dukascopy data path is keyless: it uses the same jetta.dukascopy.com/v1 instrument and compact historical/live bucket infrastructure used by current dukascopy-node, so no Freeserv API key is required. maxBucketsPerRequest prevents a single ranged query from generating an unbounded number of upstream requests.
dataSource accepts both the correct dukascopy spelling and the legacy/requested ducascopy spelling. zeroMq.endpoint may be supplied instead of host and port (for example, "ipc:///tmp/orders.sock"). socketType is any socket type exposed by pyzmq; PUSH with connect is the default. Orders are sent as a multipart ZeroMQ message whose first frame is the configurable ORDER topic and whose second frame is the JSON payload.
orderTransport selects kafka (the default) or zeromq. For Kafka, set bootstrapServers, topic, and SASL credentials in the kafka block. securityProtocol supports the librdkafka values such as SASL_SSL and SASL_PLAINTEXT; saslMechanism can be PLAIN, SCRAM-SHA-256, or SCRAM-SHA-512, depending on the broker. Set sslCaLocation to a CA file path, or omit it and provide the complete PEM certificate encoded as Base64 in sslCaBase64. The location takes priority when both are present; Base64 certificates are decoded in memory and no temporary file is created. Other optional TLS client settings are sslCertificateLocation, sslKeyLocation, and sslKeyPassword. The producer waits up to deliveryTimeoutSeconds for broker acknowledgement and uses the order symbol as the Kafka message key unless messageKey is configured. Set securityProtocol to PLAINTEXT or SSL for a broker that does not use SASL.
The transport message contains the same request keys sent to MetaTrader5.order_send, including action, symbol, volume, type, price, deviation, magic, comment, type_time, type_filling, and optional sl, tp, or stoplimit. After a successful publish, the API returns an MT5-shaped mock result with retcode: 10009, success: true, and message: "order created". Transport details and the transmitted request remain available in the result. A delivery failure is returned as an error and is never reported as a created order.
The Kafka value JSON Schema is stored at schemas/order.schema.json. When registering it with Aiven Schema Registry, use schema type JSON and the conventional subject name orders-value.
Stateless virtual account
Virtual mode does not simulate or retain trading state. /account always returns an MT5-shaped account with a 100000 balance and equity. /tickets, /positions/open, /orders/pending, /exposure, and /trades/history return empty collections. Orders are still delivered through the configured Kafka or ZeroMQ transport, then acknowledged with an MT5-shaped mock creation result.
Trading dashboard
Open http://127.0.0.1:8000/trading for the responsive trading dashboard. The page uses the public REST API only and therefore works in normal MT5 and virtual modes. Select a symbol, timeframe, and bar count to display OHLC candles together with open-position, SL/TP, pending-order, and historical-deal overlays. The tables show the same MT5-compatible position, order, and history payloads returned by the API. Swagger always links to this dashboard; in virtual mode the dashboard also links to the transport message monitor.
Virtual mode returns MT5-shaped mock successes for position stop modifications and money-based adjustments. It returns HTTP 501 for close operations, MT5 calendar data, market depth, news-close jobs, and the open-positions WebSocket. Position details return HTTP 404 because virtual state is always empty. The Fair Economy calendar remains available. Dukascopy virtual market-data responses keep the existing /symbols, /quotes, /ticks, and /bars schemas.
Run
open-api-mt5
Optional flags:
open-api-mt5 --reload
open-api-mt5 --config C:\path\to\server_config.json
Default port is 8000. Set host and port in the config file to change the bind address.
Run unit tests with coverage:
pip install -e ".[test]"
pytest
The calendar, bars, and trade services require 100% statement coverage.
Run only the mocked news-close integration scenarios:
pytest -m integration --no-cov
API docs:
- Swagger UI:
http://127.0.0.1:8000/docs - WebSocket docs in Swagger:
GET /ws/positions/open/docs
Health endpoint:
GET http://127.0.0.1:8000/health
Bars endpoints:
GET http://127.0.0.1:8000/bars/{symbol}?timeframe=M1&n=100GET http://127.0.0.1:8000/bars/{symbol}/range?timeframe=M1&fromDate=2026-03-20T08:00:00Z&toDate=2026-03-20T12:00:00ZGET http://127.0.0.1:8000/quotes/{symbol}GET http://127.0.0.1:8000/ticks/{symbol}?count=100GET http://127.0.0.1:8000/market-depth/{symbol}GET http://127.0.0.1:8000/exposureGET http://127.0.0.1:8000/exposure/{symbol}
MT5/Dukascopy spread tracing
In normal MT5 mode, GET /spread-trace/{symbol} fetches both quotes and records bidSpread, askSpread, and closeSpread. Each spread is signed and calculated as MT5 - Dukascopy. The response also includes avarageBidSpread and avarageAskSpread, calculated from samples taken during the preceding 60 seconds. For tick quotes without a last-traded price, closeSpread uses the quote midpoint.
Samples are collected only when the fresh-sample endpoint is called, retained in memory, and reset when the service restarts. The only spread-trace configuration is symbolMap, which supports broker-specific MT5 symbol suffixes.
{
"spreadTrace": {
"symbolMap": {"GBPUSD.a": "GBPUSD"}
}
}
GET /spread-trace/{symbol}records and returns a fresh sample.GET /spread-trace/{symbol}/history?limit=100returns retained samples in chronological order.
Account connection endpoints:
POST http://127.0.0.1:8000/account/connect- Body:
username,password,server, optionalpath, optionalportable - Example body:
{ "username": "12345678", "password": "your-password", "server": "YourBroker-Server", "path": "C:\\Program Files\\MetaTrader 5\\terminal64.exe", "portable": true, "account": "12345678" }
- Body:
POST http://127.0.0.1:8000/account/disconnect- If
registryUrlis set in config, the API sends aPOSTcall to that URL with JSON body fieldsaddress,port,apiKey, andaccountId
Security modes:
- Set
modein config tostandalone,secure, orsecure-client - Set
apiKeyin config and, forsecure-client,apiSecret - Set the registry target with
registryUrlin config standalone: default mode, noX-apiKeyorX-apiSecretheader checkssecure: every HTTP endpoint and the open positions WebSocket requireX-apiKeyto match the locally storedapiKeysecure-client: every HTTP endpoint and the open positions WebSocket require bothX-apiKeyandX-apiSecretto match the locally stored values
Server info endpoint:
GET http://127.0.0.1:8000/api/server/info- Returns JSON with the locally stored
apiKeyand the detected machineipAddress
Trade history endpoint:
GET http://127.0.0.1:8000/trades/history- Optional query params:
fromDate,toDate(ISO datetime, UTC recommended) - If omitted, it returns the last 7 days by default
Trade MAE/MFE endpoint:
GET http://127.0.0.1:8000/trades/{positionId}/mae-mfe?timeframe=M5&horizonBars=12- Available only in MT5 mode; virtual/Dukascopy mode returns HTTP 501.
- Reconstructs a closed position from its MT5 deals and calculates directional MAE/MFE from OHLC bars.
tradePeriodcovers entry through exit.extendedPeriodtreats the trade as if it remained open for the requested bars after exit.postExit.potentialGainandpostExit.potentialLossmeasure the favorable/adverse move from the actual exit price during those bars.extendedRRRis extended MFE money divided by extended MAE money.postExit.potentialRRRis potential gain divided by potential loss from the exit price. A ratio isnullwhen no adverse excursion occurred.- Excursions include price distance, points, percent, and estimated account-currency value. Because OHLC bars do not contain the tick path, the entry and exit candles can include extremes that occurred outside the exact trade timestamps; use a smaller timeframe for greater precision.
Modify an open position's stop loss / take profit:
POST http://127.0.0.1:8000/positions/modify- Body:
ticket, optionalsl, optionaltp, optionalcomment - At least one of
slortpis required. If one is omitted, its current MT5 value is kept. - Example body:
{ "ticket": 123456789, "sl": 1.0825, "tp": 1.095 }
- Body:
Open position details:
GET http://127.0.0.1:8000/positions/{ticket}/details- Returns
entryPrice,stopLossPrice,takeProfitPrice,volume,contractSize,stopLossValue, andtakeProfitValue - Value formula:
- Buy stop loss:
(entryPrice - stopLossPrice) * volume * contractSize - Buy take profit:
(takeProfitPrice - entryPrice) * volume * contractSize - Sell stop loss:
(stopLossPrice - entryPrice) * volume * contractSize - Sell take profit:
(entryPrice - takeProfitPrice) * volume * contractSize
- Buy stop loss:
Adjust an open position's stop loss / take profit by money values:
POST http://127.0.0.1:8000/positions/adjust-by-money- Body:
ticket, optionalstopLossValueInMoney, optionaltakeProfitValueInMoney, optionalcomment - Defaults:
stopLossValueInMoney = 10,takeProfitValueInMoney = 30 - The API converts money values to SL/TP price distances in account currency using MT5 profit calculation when available, falling back to position entry price, volume, and symbol contract size.
- If the exact value cannot be represented by the symbol price step, the API uses the nearest lower value.
- Example body:
{ "ticket": 123456789, "stopLossValueInMoney": 10, "takeProfitValueInMoney": 30 }
- Response includes
stopLossPrice,takeProfitPrice,stopLossValueInMoney, andtakeProfitValueInMoneyafter rounding.
- Body:
Close orders before economic news:
POST /ordersaccepts optionalcloseOnNews, for exampleM15_High,M10_Medium, orM5_Low- New orders with
closeOnNewsare rejected with HTTP 409 when a matching event is already inside the configured close window GET /orders/news-close/jobslists the current tagged pending orders and open positions watched by the news-close checker- The policy is stored in a durable SQLite file at
.db/news_close_jobs.sqlite3under the folder where the API process was started - Restart the API from the same folder to reuse the existing news-close jobs; set
NEWS_CLOSE_DB_PATHonly if you need a custom database path Highcloses only for high-impact events;Mediumcloses for medium/high;Lowcloses for low/medium/high- Events are matched against the symbol's base, profit, or margin currency
- Tagged open positions are closed and tagged pending orders are cancelled when an event enters the configured time window
- Modifying SL/TP values, including money-based adjustments, keeps the stored news policy unchanged
- The check runs once at startup and every 60 seconds; orders without
closeOnNewsare unchanged - Fair Economy events include an explicit timezone and are compared in UTC. If you test with timezone-less event timestamps, the API also considers the local computer timezone; set
LOCAL_TIMEZONE(for exampleEurope/Paris) only if you need to override the OS timezone.
Calendar events endpoint:
GET http://127.0.0.1:8000/calendar/events- The default source is the Fair Economy current-week feed, cached in
calendar_cache.json - The cache is loaded at startup, retried once on startup failure, refreshed every Monday at 00:00 UTC, and loaded lazily when missing or stale
- Optional query params:
source(fairEconomyormt5),fromDate,toDate(ISO datetime),country/currency(example:USD), andimpact(Low,Medium,High, orHoliday) - Fair Economy defaults to the current Monday-through-Sunday UTC week
- Use
source=mt5for the previous MT5 calendar behavior (default range: last 7 days to next 7 days) - Optional environment variables:
FAIR_ECONOMY_CALENDAR_URLandFAIR_ECONOMY_CALENDAR_CACHE_PATH
WebSocket streams
Open positions stream:
ws://127.0.0.1:8000/ws/positions/open- Optional query param:
intervalSeconds(poll interval, bounded to 0.2..60) - Events:
subscribedpositionsSnapshoterror
positionsSnapshotincludes:positions[].pnl(position PnL, sourced from MT5profit)totalPnl(sum of all open positions PnL)
Example JavaScript client:
const ws = new WebSocket("ws://127.0.0.1:8000/ws/positions/open?intervalSeconds=1");
ws.onmessage = (event) => {
const payload = JSON.parse(event.data);
console.log(payload.event, payload);
};
Build and publish package
Build distribution files locally:
python -m pip install --upgrade build
python -m build
Install from the local build to smoke-test it:
python -m pip install --force-reinstall dist/*.whl
open-api-mt5 --help
Install from PyPI and run:
pip install open-api-mt5
open-api-mt5 --config C:\path\to\server_config.json
Release to PyPI
The repository includes .github/workflows/publish.yml, which runs tests, builds the package, and publishes to PyPI with a PyPI API token. It only runs from release branches named like release/0.5.
One-time PyPI token setup:
- Create or open your PyPI account.
- Create a PyPI API token. If the project already exists on PyPI, prefer a project-scoped token for
open-api-mt5; otherwise create an account-scoped token for the first upload. - In GitHub, open repository
Settings->Secrets and variables->Actions. - Add a repository secret named
PYPI_API_TOKENwith the token value from PyPI. The workflow uses PyPI username__token__and this secret as the password. - Optional: create a GitHub environment named
pypiand add required reviewers if you want manual approval before publishing.
Prepare a release:
- Update
versioninpyproject.toml. - Run tests:
python -m pip install -e ".[test]" python -m pytest
- Build locally:
python -m pip install --upgrade build python -m build
- Commit the version change:
git add pyproject.toml git commit -m "Release 0.6.1"
- Create and push a release branch:
git checkout -b release/0.6.1 git push origin release/0.6.1
- The
Publish to PyPIworkflow runs on pushes torelease/**. You can also run it manually from GitHub Actions, but select arelease/**branch such asrelease/0.6.1.
PyPI rejects reused versions, so every release must have a new pyproject.toml version.
Manual upload with a token, if needed:
python -m pip install --upgrade twine
python -m twine upload dist/* -u __token__ -p "<your-pypi-token>"
Optional: standalone executable (no Python required on target machine)
If you want users to run it without installing Python, build an executable:
python -m pip install pyinstaller
pyinstaller --onefile --name open-api-mt5 app/cli.py
The executable will be in dist/open-api-mt5.exe.
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 open_api_mt5-0.10.2.tar.gz.
File metadata
- Download URL: open_api_mt5-0.10.2.tar.gz
- Upload date:
- Size: 95.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
453413c6e97f344c093f0a8f0c8e63ec2920602c445c5058215d14dc876c3e79
|
|
| MD5 |
72d1d1d67789cb71a4bff76cdb0b84e1
|
|
| BLAKE2b-256 |
ed4a846332baf0ab7224ee7787036bb98754857dc6479730d2621d356b776630
|
File details
Details for the file open_api_mt5-0.10.2-py3-none-any.whl.
File metadata
- Download URL: open_api_mt5-0.10.2-py3-none-any.whl
- Upload date:
- Size: 68.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a5aa5ee0697bf7217fb016ce78a60ddf62613a68911d97358b8d904d5dc928e3
|
|
| MD5 |
19963d3017d703de26631a58f9d20f6d
|
|
| BLAKE2b-256 |
e551200262c18f06ddedfabc7f06cfc45a5b3805e7be30a6a145915c0aea3aaf
|