MetaTrader 5 only runs on Windows. The official Python library only works on Windows. The MQL5 scripting language is a C++ knockoff from 2005 that makes you want to gouge your eyes out with a rusty fork. And if you want to do anything programmatic with it — pull candles, place orders, check positions — you’re expected to either write MQL5 or run Python on a Windows box with the terminal open.
I wanted to hit an HTTP endpoint from any machine, in any language, and get JSON back. Like a normal human being.
So I built mt5-httpapi. A real Windows VM running inside Docker via QEMU/KVM, with the full MT5 terminal in portable mode and a Flask REST API on top. No Wine, no emulation hacks, no janky workarounds. A legit Windows 11 environment running the actual MetaTrader 5 binary, accessible over plain HTTP/JSON from anywhere.
Multiple brokers. Multiple accounts. Each terminal gets its own API process on its own port. Run two FTMO challenges simultaneously, or mix brokers, or run ten terminals on one box — whatever you need.
How This Abomination Works
The container runs dockurr/windows — a Docker image that boots a full Windows VM using QEMU/KVM hardware virtualization. On first run, it downloads tiny11 (a stripped-down Windows 11, ~4 GB), installs it, then automatically sets up Python 3.12, installs MetaTrader5, debloats the living shit out of Windows, removes Defender entirely, and starts everything up.
After the first boot (~10 minutes), subsequent starts take about a minute. The container is configured with 2 vCPUs, 512 MB real RAM, and 5 GB swap. Sounds cursed, works fine. tiny11 plus the debloat script idles at ~1.4 GB, and MT5 plus the Python API barely add anything. Windows and MT5 are not latency-sensitive enough for swap to matter.
A shared folder between the host and the Windows VM (/shared → C:\Users\Docker\Desktop\Shared) holds everything — scripts, configs, broker installers, the API server code, and logs. The run.sh script syncs everything into this folder, generates iptables NAT rules for port forwarding from the container to the VM, and fires up docker-compose.
noVNC on port 8006 gives you a browser-based view of the Windows desktop. Useful for watching the install progress and confirming everything started. After that, forget the UI exists and just hit the REST API.
Multi-Terminal
This isn’t a one-account toy. Each entry in terminals.json gets its own MT5 terminal instance and its own Flask API process on a dedicated port:
// config/terminals.json
[
{ "broker": "roboforex", "account": "main", "port": 6542 },
{ "broker": "roboforex", "account": "demo", "port": 6543 },
{ "broker": "ftmo", "account": "challenge", "port": 6544 }
]// config/accounts.json
{
"roboforex": {
"main": {
"login": 12345678,
"password": "your_password",
"server": "RoboForex-Pro"
},
"demo": {
"login": 87654321,
"password": "demo_password",
"server": "RoboForex-Demo"
}
},
"ftmo": {
"challenge": {
"login": 11111111,
"password": "ftmo_password",
"server": "FTMO-Server"
}
}
}The broker field matches both the key in accounts.json and the installer filename (mt5setup-roboforex.exe, mt5setup-ftmo.exe). Each broker’s terminal installs to <broker>/base/ once, then gets copied to <broker>/<account>/ at startup so multiple accounts of the same broker don’t step on each other.
Four terminals running simultaneously on 2 vCPUs with 512 MB real RAM — CPU spikes to 100% during startup while everything initializes, then drops to ~15% idle. Total memory: 2.1 GB, all handled by swap. You could run 10+ terminals like this without breaking a sweat.
Setup
Requirements: Linux host with KVM enabled (/dev/kvm), Docker + Compose, ~20 GB disk, 5 GB RAM.
# Clone it
git clone https://github.com/psyb0t/mt5-httpapi
cd mt5-httpapi
# Set up your broker credentials
cp config/accounts.json.example config/accounts.json
cp config/terminals.example.json config/terminals.json
# Edit both files
# Drop your broker's MT5 installer
cp ~/Downloads/mt5setup.exe mt5installers/mt5setup-roboforex.exe
# Fire it up
make upFirst run downloads the Windows ISO, installs it, debloats, installs MT5, reboots a couple times, then starts everything. After that:
make up # start
make down # stop
make logs # tail logs
make status # check VM and API status
make clean # nuke VM disk (keeps ISO)
make distclean # nuke everything including ISOThe API
Each terminal’s API runs on its configured port. GET for reading, POST for creating, PUT for modifying, DELETE for closing. All JSON.
Health and Terminal
# Health check
curl http://localhost:6542/ping
# {"status": "ok"}
# Last MT5 error
curl http://localhost:6542/error
# {"code": 1, "message": "Success"}
# Terminal info (connected, trade_allowed, build, company)
curl http://localhost:6542/terminal
# Force re-init or shutdown
curl -X POST http://localhost:6542/terminal/init
curl -X POST http://localhost:6542/terminal/shutdownThe API auto-initializes on first request. If MT5 isn’t connected yet, a background thread retries every 30 seconds. You almost never need to call /terminal/init manually.
Account
curl http://localhost:6542/account{
"login": 12345678,
"balance": 10000.0,
"equity": 10000.0,
"margin": 0.0,
"margin_free": 10000.0,
"leverage": 500,
"currency": "USD",
"trade_allowed": true,
"margin_so_call": 70.0,
"margin_so_so": 20.0
}Market Data
# List all symbols (or filter)
curl http://localhost:6542/symbols
curl "http://localhost:6542/symbols?group=*USD*"
# Symbol details (bid, ask, spread, contract size, tick value, lot constraints)
curl http://localhost:6542/symbols/EURUSD
# Latest tick
curl http://localhost:6542/symbols/EURUSD/tick
# OHLCV candles
curl "http://localhost:6542/symbols/EURUSD/rates?timeframe=H4&count=100"
# Tick history
curl "http://localhost:6542/symbols/EURUSD/ticks?count=100"Timeframes: M1 M2 M3 M4 M5 M6 M10 M12 M15 M20 M30 H1 H2 H3 H4 H6 H8 H12 D1 W1 MN1. Candle time is the open time, unix epoch seconds.
Placing Orders
# Market buy
curl -X POST http://localhost:6542/orders \
-H "Content-Type: application/json" \
-d '{"symbol": "ADAUSD", "type": "BUY", "volume": 1000, "sl": 0.25, "tp": 0.35}'
# Pending buy limit
curl -X POST http://localhost:6542/orders \
-H "Content-Type: application/json" \
-d '{"symbol": "ADAUSD", "type": "BUY_LIMIT", "volume": 1000, "price": 0.28, "sl": 0.25, "tp": 0.35}'Required fields: symbol, type, volume. Price auto-fills for market orders. Order types: BUY, SELL, BUY_LIMIT, SELL_LIMIT, BUY_STOP, SELL_STOP, BUY_STOP_LIMIT, SELL_STOP_LIMIT. Fill policies: FOK, IOC (default), RETURN. Expiration: GTC (default), DAY, SPECIFIED, SPECIFIED_DAY.
Every trade operation returns a result with retcode — 10009 means success, anything else means something went wrong. Use GET /error to debug.
Managing Positions and Orders
# List open positions
curl http://localhost:6542/positions
curl "http://localhost:6542/positions?symbol=EURUSD"
# Move SL/TP
curl -X PUT http://localhost:6542/positions/12345 \
-H "Content-Type: application/json" \
-d '{"sl": 0.27, "tp": 0.36}'
# Close full position
curl -X DELETE http://localhost:6542/positions/12345
# Partial close
curl -X DELETE http://localhost:6542/positions/12345 \
-H "Content-Type: application/json" \
-d '{"volume": 500}'
# Modify pending order
curl -X PUT http://localhost:6542/orders/67890 \
-H "Content-Type: application/json" \
-d '{"price": 0.29, "sl": 0.26, "tp": 0.36}'
# Cancel pending order
curl -X DELETE http://localhost:6542/orders/67890History
# Order history (last 24h)
curl "http://localhost:6542/history/orders?from=$(date -d '1 day ago' +%s)&to=$(date +%s)"
# Deal history (last 24h)
curl "http://localhost:6542/history/deals?from=$(date -d '1 day ago' +%s)&to=$(date +%s)"from and to are required, unix epoch seconds. Deals have entry (0 = opening, 1 = closing) and profit (0 for entries, realized P&L for exits).
Technical Analysis
The API gives you raw market data — it doesn’t do TA. But there’s a full working example in examples/python/ that pulls candles and crunches them with pandas-ta and smartmoneyconcepts.
Indicators included: EMA 21, SMA 50/100/200, ATR, RSI, MACD, Bollinger Bands, MFI, Stochastic, ADX, VWAP — plus Smart Money Concepts: order blocks, fair value gaps, break of structure, change of character, and liquidity levels.
# TA report with signal detection
python ta.py # EURUSD H4 200 candles (default)
python ta.py BTCUSD H1 100 # custom symbol/timeframe/count
python ta.py ADAUSD D1 200
# 1920x1080 candlestick chart with all overlays
python chart.py ADAUSD
python chart.py BTCUSD H1 100
python chart.py EURUSD D1 200 -o eurusd.pngThe TA report prints the latest candle’s values for every indicator and then runs signal detection — RSI overbought/oversold, MACD histogram crossovers, EMA/SMA golden/death crosses, Bollinger Band breakouts, Stochastic extremes, ADX trend strength. The chart renders dark-themed candlesticks with moving averages, Bollinger Bands, VWAP, SMC overlays (order blocks, FVGs, BOS/CHoCH lines, liquidity sweeps), RSI panel, and MACD panel. Publication-quality PNGs at 1920×1080.
The indicator and signal modules are designed as building blocks. Import add_rsi(df) or detect_signals(df) into your own scripts and use the API as your data source. Pull candles, apply whatever analysis you want, place trades — all from a Python script running on any machine.
Position Sizing
The symbol endpoint gives you everything you need to calculate proper position sizes:
risk_amount = balance * risk_pct
sl_distance = ATR * multiplier
ticks_in_sl = sl_distance / trade_tick_size
risk_per_lot = ticks_in_sl * trade_tick_value
volume = risk_amount / risk_per_lotRound down to volume_step, clamp to [volume_min, volume_max]. Sanity check: volume * trade_contract_size * price should make sense relative to your balance. One lot of EURUSD is 100,000 EUR, not 1 EUR — trade_contract_size tells you this. Check it before you accidentally YOLO your entire account on what you thought was a micro position.
AI Skill
The repo ships with a .skills/ directory containing a skill definition for AI coding agents. Install it in OpenClaw or any other agent that supports the skills format, point it at your running instance via MT5_API_URL, and the agent gets the full API reference, pre-trade safety checklist, position sizing formulas, and usage patterns. It knows what endpoints exist, what fields to check before placing a trade, and how to interpret the results.
This means you can tell your AI agent “buy 0.1 lots of EURUSD with a 2 ATR stop loss” and it has everything it needs to pull the symbol info, calculate the SL price, place the order, and verify the result. No manual API documentation reading required — the skill gives it the complete playbook.
The Debloat
The Windows VM goes through an aggressive debloat on first boot. Disable all animations, transparency, wallpaper. Kill SysMain, audio, spooler, search, telemetry, and about 50 other useless services. Remove Windows Defender entirely — not disable, remove. Take ownership of the Defender directories and delete the binaries. Nuke all the privacy-invading bullshit: advertising ID, activity history, diagnostic data, all capability permissions. Disable every Microsoft spying scheduled task. Set processor priority to foreground, reduce kill timeouts, disable NTFS timestamps.
The result is a Windows 11 that boots fast, idles low, and doesn’t phone home to Microsoft every 30 seconds. Just enough OS to run MT5 and the Python API.
Logs
Inside the VM’s shared folder (data/metatrader5/logs/):
- install.log — MT5 installation progress
- pip.log — Python package installation
- api.log — API server output (single-terminal mode)
- api-<broker>-<account>.log — per-terminal API logs (multi-terminal mode)
When shit breaks, check these first.
The Bottom Line
MetaTrader 5 in Docker with a REST API. Real Windows VM via KVM, not Wine. Multiple brokers and accounts running simultaneously on minimal resources. Full market data, order management, position tracking, and trade history — all over plain HTTP/JSON. Plus a TA example with 20+ indicators, Smart Money Concepts, signal detection, and publication-quality charting.
No MQL5. No Windows desktop. No MT5 libraries on the client side. Just curl and go.
Go grab it: github.com/psyb0t/mt5-httpapi
Licensed under WTFPL — because trading should require a disclaimer, not a software license.