MetaTrader 5 läuft nur unter Windows. Die offizielle Python-Bibliothek läuft nur unter Windows. Die Skriptsprache MQL5 ist ein C++-Abklatsch von 2005, bei dem du dir die Augen mit einer rostigen Gabel auskratzen willst. Und wenn du irgendwas Programmatisches damit machen willst, Kerzen ziehen, Orders setzen, Positionen prüfen, wird von dir erwartet, dass du entweder MQL5 schreibst oder Python auf einer Windows-Kiste mit offenem Terminal laufen lässt.
Ich wollte einen HTTP-Endpoint von jeder beliebigen Maschine aus anfassen, in jeder beliebigen Sprache, und JSON zurückbekommen. Wie ein normaler Mensch.
Also habe ich mt5-httpapi gebaut. Eine echte Windows-VM, die per QEMU/KVM in Docker läuft, mit dem vollständigen MT5-Terminal im Portable-Modus und einer Flask-REST-API obendrauf. Kein Wine, keine Emulations-Tricks, keine wackligen Umwege. Eine echte Windows-11-Umgebung, die das richtige MetaTrader-5-Binary fährt, von überall über schlichtes HTTP/JSON erreichbar.
Mehrere Broker. Mehrere Konten. Jedes Terminal bekommt seinen eigenen API-Prozess in der VM, und ein immer laufender nginx-Sidecar stellt sie alle hinter einen einzigen Host-Port unter http://localhost:8888/<broker>/<account>/.... Fahr zwei FTMO-Challenges gleichzeitig, oder misch Broker, oder lass zehn Terminals auf einer Kiste laufen, ganz wie du willst.
Wie Dieses Monstrum Funktioniert
Der Container fährt dockurr/windows, ein Docker-Image, das eine komplette Windows-VM mit QEMU/KVM-Hardwarevirtualisierung startet. Beim ersten Lauf lädt es tiny11 herunter (ein abgespecktes Windows 11, ~4 GB), installiert es, richtet dann von selbst Python 3.12 ein, installiert MetaTrader5, räumt den ganzen Scheiß aus Windows raus, wirft Defender komplett raus und startet alles.
Nach dem ersten Boot (~10 Minuten) dauern die folgenden Starts etwa eine Minute. Der Container ist mit 2 vCPUs, 512 MB echtem RAM und 5 GB Swap konfiguriert. Klingt verflucht, läuft einwandfrei. tiny11 plus das Entrümpelungsskript liegt im Leerlauf bei ~1.4 GB, und MT5 plus die Python-API legen kaum etwas drauf. Windows und MT5 sind nicht latenzempfindlich genug, als dass Swap auffallen würde.
Ein zwischen Host und Windows-VM geteilter Ordner (/shared → C:UsersDockerDesktopShared) hält alles: Skripte, Configs, Broker-Installer, den Code des API-Servers und die Logs. Das Skript run.sh synchronisiert alles in diesen Ordner, erzeugt iptables-NAT-Regeln für das Port-Forwarding vom Container in die VM und fährt docker-compose hoch.
noVNC auf Port 8006 gibt dir eine Browser-Sicht auf den Windows-Desktop. Praktisch, um beim Installieren zuzusehen und zu bestätigen, dass alles gestartet ist. Danach vergisst du, dass die Oberfläche existiert, und fasst nur noch die REST-API an.
Mehrere Terminals Hinter Einem Port
Architekturänderung (v4.0+): das ursprüngliche Layout legte jedes Terminal auf seinen eigenen Host-Port (6542, 6543, 6544…). Vorbei. Alles liegt jetzt hinter einem einzigen Host-Port (standardmäßig 127.0.0.1:8888), davor ein immer laufender nginx-Sidecar, der nach Pfadpräfix routet:
http://localhost:8888/<broker>/<account>/...v4.4 hat das auf /<broker>/<account>/<instance>/... erweitert, wobei die schlichte Form /<broker>/<account>/ als Alias für die Standardinstanz bleibt, bestehende URLs laufen also weiter. Die Form ist modusunabhängig: Live- und Backtest-Terminals routen identisch.
Ein vom Host weitergeleiteter Port, eine TLS-Fläche, um die du dich kümmern musst, eine Regel in deiner Firewall. Jede Anfrage trifft nginx, das Präfix /<broker>/<account>/ fällt weg, und der Rest geht an den Python-API-Prozess genau dieses Terminals in der VM, über die Docker-Bridge. Die nginx-Config wird bei jedem make up aus config.yaml erzeugt, ein Terminal hinzuzufügen oder wegzunehmen erfordert also keine Handarbeit am Reverse-Proxy.
Config-Konsolidierung (v4.0): die alte Aufteilung accounts.json + terminals.json ist weg. Es gibt jetzt eine einzige config/config.yaml (gitignored) als Quelle der Wahrheit:
# Bearer token for API auth. Empty = no auth.
api_token: "paste-the-output-of-openssl-rand-hex-32-here"
# VM auto-reboot every N minutes (flushes DWM/VirtIO-GPU state). 0 = disable.
reboot_interval: 30
# Default Strategy Tester timeout. POST /backtest can override per job.
backtest_timeout: "6h"
tailscale:
auth_key: "" # tskey-auth-... — empty disables the tailscale sidecar
login_server: "" # Headscale URL; empty = Tailscale cloud
# Extra pip packages installed in the VM.
requirements: []
# Broker credentials, organized by broker → account name.
accounts:
roboforex:
main:
login: 12345678
server: "RoboForex-Pro"
password: "your_password"
demo:
login: 87654321
server: "RoboForex-Demo"
password: "demo_password"
# Terminal instances — one MT5 + one API process per entry.
terminals:
- broker: roboforex
account: main
port: 6542 # container-internal only, not exposed to host
utc_offset: "3h"
- broker: roboforex
account: demo
port: 6543
utc_offset: "3h"
- broker: roboforex
account: tester
port: 6544
utc_offset: "3h"
mode: backtest # don't auto-launch terminal64.exe — reserved for /backtest jobs
symbol_suffix: ".r" # explicit suffix for tester symbol remap (e.g. EURUSD → EURUSD.r)Das Feld broker passt sowohl auf den Schlüssel unter accounts: als auch auf den Dateinamen des Installers (mt5setup-roboforex.exe, mt5setup-ftmo.exe). Das Terminal jedes Brokers installiert sich einmal nach <broker>/base/ und wird beim Start nach <broker>/<account>/ kopiert, damit mehrere Konten desselben Brokers sich nicht in die Quere kommen.
utc_offset ist pro Terminal, weil Broker in komischen Zeitzonen leben (RoboForex/FTMO auf UTC+3, TeleTrade auf UTC+2). Jeder Zeitstempel, der über die Leitung geht, Kerzen, Ticks, Historie, Positionen, wird serverseitig auf echtes UTC normalisiert. Dein Client muss nie wieder über die Broker-Ortszeit nachdenken. port ist der containerinterne Port, mit dem nginx spricht; er ist nicht zum Host freigegeben.
reboot_interval ist der neue Auto-Reboot-Hebel für die VM: der DWM/VirtIO-GPU-Stack von MetaQuotes sammelt über lange Uptimes kernelseitig Zustand an und wird irgendwann seltsam, also wird die VM nach Plan durchgestartet (standardmäßig alle 30 Minuten). Auf 0 setzen schaltet das ab.
mode pro Terminal (v4.3): live ist der Standard, terminal64.exe bleibt laufen, damit das MT5-SDK für die Live-Trading-Endpoints initialisiert ist. backtest bereitet dasselbe portable Datenverzeichnis vor, startet aber nicht terminal64.exe und lässt das Verzeichnis für einen Strategy-Tester-Subprozess frei. MT5 ist pro portablem Datenverzeichnis Single-Instance, und genau darauf kommt es an: läuft terminal64.exe schon, beendet sich ein Tester-Subprozess auf demselben Verzeichnis still mit Code 0 und liefert keinen Bericht. Fahr ein eigenes roboforex/tester neben deinem Live-roboforex/main und du hast Backtests über HTTP, ohne das Live-SDK zu zerschießen.
symbol_suffix kümmert sich um die Broker, die im Tester alles umbenennen. Nutzt deiner EURUSDp, EURUSD.p, EURUSD-mini oder welches Scheiß-Suffix auch immer in seinem Strategy-Tester-Symbolpool, setzt du es hier und mt5-httpapi hängt es automatisch an, wenn [Tester].Symbol in der INI es nicht schon hat. Leerer String = kein Suffix. backtest_timeout ist die Standardobergrenze für POST /backtest, dieselbe Dauer-Grammatik wie bei utc_offset ("6h", "30m", "3h30m", nackte Zahlen werden als Stunden gelesen), pro Job überschreibbar.
Vier Terminals gleichzeitig auf 2 vCPUs mit 512 MB echtem RAM: die CPU schießt beim Start auf 100%, während sich alles initialisiert, und fällt dann auf ~15% im Leerlauf. Speicher gesamt: 2.1 GB, komplett vom Swap getragen. So könntest du 10+ Terminals fahren, ohne ins Schwitzen zu kommen, solange du nicht auf allen gleichzeitig tiefe Historie abgräbst (MT5 cached jeden geladenen Chart und gibt ihn nie wieder frei; tiefe Backfills sprengen das 512-MB-Limit nach unten durch).
Einrichtung
Voraussetzungen: Linux-Host mit aktiviertem KVM (/dev/kvm), Docker + Compose, ~20 GB Platte, 5 GB RAM.
# Clone it
git clone https://github.com/psyb0t/mt5-httpapi
cd mt5-httpapi
# Single config file now — copy and edit
cp config/config.yaml.example config/config.yaml
# Set api_token, accounts, terminals
# Drop your broker's MT5 installer
cp ~/Downloads/mt5setup.exe mt5installers/mt5setup-roboforex.exe
# Fire it up
make upDer erste Lauf lädt die Windows-ISO, installiert sie, entrümpelt, installiert MT5, startet ein paarmal neu und fährt dann alles hoch. Danach:
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 ISOAuthentifizierung
Die API läuft standardmäßig offen. Wenn du sie in ein Netz hängst (auch ein lokales mit anderen Maschinen darin), setz das Token in config/config.yaml:
api_token: "$(openssl rand -hex 32)"Ist api_token nicht leer, verlangt jeder Endpoint Authorization: Bearer <token>. Leerer String = keine Authentifizierung, was für ein Einzelmaschinen-Setup völlig reicht, an dessen Port sonst nichts herankommt.
Mit aktivierter Authentifizierung setzt du das Token in deiner Shell und hängst es an jede Anfrage:
export MT5_API_TOKEN=$(grep ^api_token config/config.yaml | awk -F'"' '{print $2}')
curl -H "Authorization: Bearer $MT5_API_TOKEN"
http://localhost:8888/roboforex/main/ping
# {"status": "ok"}Alle curl-Beispiele unten gehen davon aus, dass keine Authentifizierung läuft. Hast du ein Token konfiguriert, stell -H "Authorization: Bearer $MT5_API_TOKEN" jedem voran.
Die API
Alle Terminals werden über nginx hinter einem einzigen Host-Port ausgeliefert. Standard-Einstiegspunkt: http://localhost:8888 (nur Loopback). Jedes Terminal liegt auf seinem eigenen Pfadpräfix, http://localhost:8888/<broker>/<account>/.... Die Beispiele unten nehmen roboforex/main; setz deine eigenen ein. GET zum Lesen, POST zum Anlegen, PUT zum Ändern, DELETE zum Schließen. Alles JSON.
Zustand und Terminal
# Health check
curl http://localhost:8888/roboforex/main/ping
# {"status": "ok"}
# Last MT5 error
curl http://localhost:8888/roboforex/main/error
# {"code": 1, "message": "Success"}
# Terminal info (connected, trade_allowed, build, company)
curl http://localhost:8888/roboforex/main/terminal
# Force re-init, shutdown, or restart
curl -X POST http://localhost:8888/roboforex/main/terminal/init
curl -X POST http://localhost:8888/roboforex/main/terminal/shutdown
curl -X POST http://localhost:8888/roboforex/main/terminal/restartDie API initialisiert sich bei der ersten Anfrage selbst. Ist MT5 noch nicht verbunden, versucht es ein Hintergrund-Thread alle 30 Sekunden erneut. /terminal/init musst du praktisch nie von Hand aufrufen.
Ein Health-Monitor läuft im Hintergrund, alle 60 Sekunden prüft er, ob das Terminal lebt, eingeloggt ist und Algo-Trading aktiviert hat. Ist das Terminal bei 5 Prüfungen in Folge tot, startet es sich selbst neu: Prozess killen, Terminal neu starten, warten bis das Journal bestätigt, dass es oben ist, und die API neu verbinden. Einen Neustart von Hand kannst du auch über POST /terminal/restart auslösen.
Dieser Monitor lebt innerhalb der VM, er kann also nur reparieren, wofür die VM noch gesund genug ist. v4.13.0 hat die Etage darüber ergänzt: einen von Compose verwalteten vm-watchdog-Sidecar, der die Windows-VM-Container vom Host aus über den Docker-Socket beobachtet und einen davon über den bestehenden recreate-vm.sh-Pfad neu erzeugt, wenn er lange genug ungesund war, dass es etwas heißt. Nicht bei der ersten fehlgeschlagenen Prüfung. WATCHDOG_MIN_FAILING_STREAK steht standardmäßig auf 10 Fehlschläge in Folge im 30-Sekunden-Takt, dann geht er exponentiell zurück, 5 Minuten, 15 Minuten, eine Stunde, und gibt nach 3 Versuchen auf. Eine VM, die sich von selbst fängt, bleibt in Ruhe.
Der Filter ist der interessante Teil. WATCHDOG_IMAGE_FILTER steht standardmäßig auf dockurr/windows, und ein leerer Wert wird abgelehnt statt als “passt auf alles” gelesen, denn die Version, die einen leeren Filter als Platzhalter behandelt, ist die Version, die dir um 4 Uhr morgens jeden Container im Projekt neu erzeugt.
Er beaufsichtigt außerdem die Sidecars, die sich den Netzwerk-Namespace einer VM teilen (network_mode: service:<vm>), sobald die VM durchgehend gesund war. Ein Sidecar, der in einem veralteten Namespace hängt, wird für sich repariert, ohne die VM darunter neu zu erzeugen. Das kam aus zwei Tagen toter TA-Aufrufe: die VM war in Ordnung, der wickworks-Sidecar zeigte auf einen Netzwerk-Namespace, den es nicht mehr gab, und niemand merkte es, weil die Health Checks der VM selbst die ganze Zeit grün waren. WATCHDOG_WATCH_SIDECARS=0, wenn du zurück zur reinen VM-Wiederherstellung willst. Insgesamt gibt es 15 WATCHDOG_*-Variablen, darunter ein WATCHDOG_DRY_RUN, damit du zusehen kannst, was er getan hätte, bevor du ihn irgendetwas tun lässt.
Konto
curl http://localhost:8888/roboforex/main/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
}Marktdaten
# List all symbols (or filter)
curl http://localhost:8888/roboforex/main/symbols
curl "http://localhost:8888/roboforex/main/symbols?group=*USD*"
# Symbol details (bid, ask, spread, contract size, tick value, lot constraints)
curl http://localhost:8888/roboforex/main/symbols/EURUSD
# Latest tick
curl http://localhost:8888/roboforex/main/symbols/EURUSD/tick
# OHLCV candles
curl "http://localhost:8888/roboforex/main/symbols/EURUSD/rates?timeframe=H4&count=100"
# OHLCV + indicators in one shot (server-side TA via wickworks)
curl -X POST "http://localhost:8888/roboforex/main/symbols/EURUSD/rates/ta?timeframe=H1&count=200"
-H "Content-Type: application/json"
-d '{"indicators":{"rsi":true,"macd":true,"bbands":{"length":20,"std":2}}}'
# Tick history
curl "http://localhost:8888/roboforex/main/symbols/EURUSD/ticks?count=100"Zeiteinheiten: M1 M2 M3 M4 M5 M6 M10 M12 M15 M20 M30 H1 H2 H3 H4 H6 H8 H12 D1 W1 MN1. Die time einer Kerze ist ihre Eröffnungszeit, in Unix-Epoch-Sekunden.
Orders Setzen
# Market buy
curl -X POST http://localhost:8888/roboforex/main/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:8888/roboforex/main/orders
-H "Content-Type: application/json"
-d '{"symbol": "ADAUSD", "type": "BUY_LIMIT", "volume": 1000, "price": 0.28, "sl": 0.25, "tp": 0.35}'Pflichtfelder: symbol, type, volume. Der Preis füllt sich bei Market-Orders von selbst. Order-Typen: BUY, SELL, BUY_LIMIT, SELL_LIMIT, BUY_STOP, SELL_STOP, BUY_STOP_LIMIT, SELL_STOP_LIMIT. Fill-Policies: FOK, IOC (Standard), RETURN. Gültigkeit: GTC (Standard), DAY, SPECIFIED, SPECIFIED_DAY.
Jede Handelsoperation liefert ein Ergebnis mit retcode zurück, 10009 heißt Erfolg, alles andere heißt, dass etwas schiefging. Nimm GET /error zum Debuggen.
Positionen und Orders Verwalten
# List open positions
curl http://localhost:8888/roboforex/main/positions
curl "http://localhost:8888/roboforex/main/positions?symbol=EURUSD"
# Move SL/TP
curl -X PUT http://localhost:8888/roboforex/main/positions/12345
-H "Content-Type: application/json"
-d '{"sl": 0.27, "tp": 0.36}'
# Close full position
curl -X DELETE http://localhost:8888/roboforex/main/positions/12345
# Partial close
curl -X DELETE http://localhost:8888/roboforex/main/positions/12345
-H "Content-Type: application/json"
-d '{"volume": 500}'
# Modify pending order
curl -X PUT http://localhost:8888/roboforex/main/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:8888/roboforex/main/orders/67890Historie
# Order history (last 24h)
curl "http://localhost:8888/roboforex/main/history/orders?from=$(date -d '1 day ago' +%s)&to=$(date +%s)"
# Deal history (last 24h)
curl "http://localhost:8888/roboforex/main/history/deals?from=$(date -d '1 day ago' +%s)&to=$(date +%s)"from und to sind Pflicht, in Unix-Epoch-Sekunden. Deals haben ein entry (0 = Eröffnung, 1 = Schließung) und ein profit (0 bei Eröffnungen, realisierter G&V bei Schließungen).
Technische Analyse
Zwei Wege, Indikatoren auf deine Kerzen zu bekommen, serverseitig oder selbst gemacht.
Serverseitige TA: POST /symbols/:symbol/rates/ta (v4.1+)
Ein HTTP-Aufruf, die Kerzen kommen bereits mit Indikatoren angereichert zurück. Der mt5-Container bringt einen wickworks-TA-Sidecar mit, eingesperrt in den Netzwerk-Namespace von mt5, ohne veröffentlichte Ports, ohne separates Deployment, ohne externen Verkehr. Dieselben Query-Parameter wie bei GET /rates (timeframe, count, from, to), dazu ein JSON-Body, der wickworks sagt, welche Indikatoren zu rechnen sind:
curl -X POST "$MT5_API_URL/symbols/EURUSD/rates/ta?timeframe=H1&count=200"
-H "Content-Type: application/json"
-d '{
"indicators": {
"rsi": true,
"rsi21": {"type": "rsi", "length": 21},
"macd": true,
"bbands": {"length": 20, "std": 2}
}
}'Die Antwort trägt die rohen Bars und die wickworks-Ausgabe nebeneinander:
{
"symbol": "EURUSD",
"timeframe": "H1",
"bars": [ { "time": 1771146000, "open": 1.0832, "high": 1.0840, "low": 1.0828, "close": 1.0835, ... } ],
"ta": { "indicators": { "rsi": [...], "macd": {...}, "bbands": {...} } }
}Jeder Eintrag unter indicators bildet einen Ausgabeschlüssel entweder auf true (die Standardwerte) oder auf ein flaches Parameterobjekt ab. "type": "<name>" ergänzt du nur, wenn der Ausgabeschlüssel vom Indikatornamen abweicht, etwa um einen zweiten RSI unter rsi21 zu fahren. Der wickworks-Katalog deckt die üblichen Verdächtigen ab (RSI, MACD, Bollinger-Bänder, ADX, VWAP, Ichimoku, ATR, Stochastik, MFI, dutzende gleitende Durchschnitte) plus Smart-Money-Primitive (Order Blocks, Fair Value Gaps, BOS, CHoCH, Swing-Struktur, S/R-Niveaus). Nur Primitive, die deutenden Signale (Divergenzen, Kreuzungsereignisse) gehören in deinen Konsumenten. Vollständige Liste der Typen, Parameter und Ausgabeformen auf github.com/psyb0t/docker-wickworks.
Die Sidecar-URL konfigurierst du über wickworks: in config.yaml, standardmäßig http://20.20.20.1:8000/, die dockurr-Gateway-IP, wie sie aus dem Inneren der Windows-VM aussieht.
Clientseitige TA: rohe Kerzen ziehen und selbst durchrechnen
Willst du volle Kontrolle oder steckst du ohnehin bis zum Hals in pandas, liefert das Repo ein vollständiges Beispiel in examples/python/, das Kerzen über GET /rates zieht und sie durch pandas-ta und smartmoneyconcepts schickt.
Enthaltene Indikatoren: EMA 21, SMA 50/100/200, ATR, RSI, MACD, Bollinger-Bänder, MFI, Stochastik, ADX, VWAP, dazu Smart Money Concepts: Order Blocks, Fair Value Gaps, Break of Structure, Change of Character und Liquiditätsniveaus.
# 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.pngDer TA-Bericht gibt für jeden Indikator die Werte der letzten Kerze aus und fährt dann die Signalerkennung: RSI überkauft/überverkauft, MACD-Histogramm-Kreuzungen, EMA/SMA Golden/Death Cross, Ausbrüche aus den Bollinger-Bändern, Stochastik-Extreme, ADX-Trendstärke. Der Chart rendert Kerzen auf dunklem Thema mit gleitenden Durchschnitten, Bollinger-Bändern, VWAP, SMC-Overlays (Order Blocks, FVGs, BOS/CHoCH-Linien, Liquiditäts-Sweeps), RSI-Panel und MACD-Panel. PNGs in Publikationsqualität mit 1920×1080.
Die Indikator- und Signalmodule sind als Bausteine gedacht. Du importierst add_rsi(df) oder detect_signals(df) in deine eigenen Skripte und nutzt die API als Datenquelle. Kerzen ziehen, beliebige Analyse anwenden, Trades setzen, alles aus einem Python-Skript auf irgendeiner Maschine.
Go-Client: GetRatesTA (v4.2+)
Der typisierte Go-Client unter clients/go/ kapselt den wickworks-Endpoint in einer eigenen Methode:
resp, err := c.GetRatesTA(ctx, "EURUSD",
mt5.RatesQuery{Timeframe: "H1", Count: 200},
map[string]any{
"indicators": map[string]any{
"rsi": true,
"macd": true,
"bbands": map[string]any{"length": 20, "std": 2},
},
},
)Derselbe Client deckt GetRates, GetTicks, GetAccount, CreateOrder, ListPositions, UpdatePosition, ClosePosition, die Historien-Endpoints und die Lebenszyklus-Aufrufe des Terminals ab. Fehler bilden auf typisierte Sentinels ab, gegen die du errors.Is() prüfen kannst, aichteeteapee.ErrUnauthorized für 401, aichteeteapee.ErrBadRequest für 400, und ein eigenes mt5httpapi.ErrNotInitialized für den 503, den du siehst, solange die VM noch hochfährt und das MT5-SDK nicht bereit ist.
Strategy Tester / Backtesting (v4.3+)
Einen EA auf MT5 zu backtesten heißt normalerweise, sich wie ein Tier durch das Strategy-Tester-Fenster zu klicken. v4.3 verdrahtet das Ganze in die HTTP-API: du lädst eine INI hoch, einen .ex5-Experten, optional eine .set-Parameterdatei, bekommst eine jobId zurück, fragst bis zur Fertigstellung ab und holst den HTML-Bericht und das Terminal-Log. Derselbe Ablauf wie in der GUI, nur kopflos und skriptbar.
Warum mode: backtest auf einem eigenen Terminal. MT5 ist pro portablem Datenverzeichnis Single-Instance. Läuft dort schon terminal64.exe, um das Live-SDK zu tragen, beendet sich ein Strategy-Tester-Subprozess auf demselben Verzeichnis still mit Code 0 und du bekommst nichts. Also widmest du in config.yaml ein Terminal mit mode: backtest, es bekommt dieselbe portable Installation, dieselben Broker-Zugangsdaten, aber kein automatisch gestartetes terminal64.exe. Der Tester-Subprozess hat das Datenverzeichnis exklusiv und liefert tatsächlich einen Bericht. Fahr es neben deinen Live-Terminals; sie sehen sich gegenseitig nicht.
Zweistufiger Ablauf. Zuerst macht POST /backtest/build-ini aus einer kleinen JSON-Spec eine vollständig ausgeformte tester.ini (keine Zugangsdaten, keine Auflösung des Expertenpfads, es ist ein zustandsloser Helfer, du kannst die INI auch von Hand schreiben). Dann nimmt POST /backtest einen Multipart-Upload aus INI plus Experte, stellt den Job in die Warteschlange und gibt eine jobId zurück.
export URL=http://localhost:8888/roboforex/tester
export TOK=$MT5_API_TOKEN
# 1. Build the INI: 5-year NZDJPY M15 open-prices run with 5 ms latency.
curl -sS -X POST "$URL/backtest/build-ini"
-H "Authorization: Bearer $TOK" -H "Content-Type: application/json"
-d '{
"symbol": "NZDJPY",
"timeframe": "M15",
"expert": "EA Studio NZDJPY M15 1615044595.ex5",
"lastYears": 5,
"modelling": "open-prices",
"latencyMs": 5,
"expertParameters": "ea studio nzdjpy m15 1615044595.set"
}' > tester.ini
# 2. Submit using a host-managed expert + set file from assets/.
JOB=$(curl -sS -X POST "$URL/backtest"
-H "Authorization: Bearer $TOK"
-F "[email protected]"
-F "expert_name=EA Studio NZDJPY M15 1615044595.ex5"
-F "set_name=ea studio nzdjpy m15 1615044595.set"
| jq -r .jobId)
# 3. Poll until done.
while :; do
STATUS=$(curl -sS -H "Authorization: Bearer $TOK" "$URL/backtest/$JOB" | jq -r .status)
echo "$STATUS"
[[ "$STATUS" == completed || "$STATUS" == failed ]] && break
sleep 30
done
# 4. Fetch the report + terminal log.
curl -sS -H "Authorization: Bearer $TOK" "$URL/backtest/$JOB/report" -o report.htm
curl -sS -H "Authorization: Bearer $TOK" "$URL/backtest/$JOB/log" -o tester.logDer Experte und die Set-Datei lassen sich inline mitschicken (bevorzugt für Ad-hoc-Läufe) oder per Name aus einem host-verwalteten Pool referenzieren, assets/experts/*.ex5 und assets/sets/*.set, schreibgeschützt in die VM unter /shared/assets gemountet. Path Traversal in expert_name / set_name wird abgelehnt.
Was der Server für dich erledigt. [Common] Login / Password / Server in der hochgeladenen INI werden mit den Zugangsdaten aus config.yaml für diesen Broker und dieses Konto überschrieben, die INI, die du hochlädst, muss deine echten Zugangsdaten also nicht kennen und du committest sie nicht versehentlich in ein Repo. Der Expertenpfad wird auf Uploaded<basename> umgeschrieben. Die Set-Datei bekommt pro Job einen eigenen Namensraum, um Kollisionen zu vermeiden. Nutzt dein Broker Symbole mit Suffix (EURUSDp, EURUSD.p, EURUSD-mini), wird das konfigurierte symbol_suffix automatisch an [Tester].Symbol gehängt, wenn es fehlt.
Das Status-Payload. GET /backtest/<jobId> liefert status ∈ queued / running / completed / failed. Ist er fertig, wird ein summary-Objekt aus dem HTML-Bericht geparst, netProfit, profitFactor, recoveryFactor, expectedPayoff, sharpeRatio, maxDrawdown, totalTrades, profitTrades, lossTrades, damit du Läufe programmatisch sortieren kannst, ohne selbst HTML zu parsen.
Nebenläufigkeit. Pro API-Prozess läuft immer nur ein Tester, serialisiert über ein internes Lock. Weitere Einreichungen warten in der Schlange. Der Standard-Timeout kommt aus backtest_timeout in config.yaml (standardmäßig 6h, falls nicht gesetzt); pro Job überschreibbar über das Multipart-Feld timeout. Startet die API neu, während ein Backtest unterwegs ist, wird der verwaiste Job beim nächsten Start mit API restarted before completion als failed markiert, keine Zombies.
Optimierung und Terminal-Instanzen (v4.4)
v4.3 hat dir Einzeldurchlauf-Backtests mit HTML-Bericht gebracht. Das ist die leichte Hälfte. Optimierungsläufe sind ein anderes Tier: Tester.Optimization auf 1 oder 2 wirft einen XML-Optimierungsbericht aus, und Modus 3, “alle Symbole”, wirft eine .symbols.xml aus plus einen binären .opt-Cache, den MT5 über keine dokumentierte API offenlegt. v4.4 parst beides.
POST /backtest/build-ini nimmt jetzt optimization (0..3) und optimizationCriterion (0..7) neben den Einzeldurchlauf-Feldern aus v4.3. Und es gibt ein neues POST /backtest/build-set, das aus einer JSON-Parameterliste eine Strategy-Tester-.set-Datei erzeugt, sowohl feste Werte als auch Optimierungsbereiche (start / step / stop / optimize), im nativen MT5-Format name=value||.... Du kannst den Parameter-Sweep also programmatisch erzeugen, statt Set-Dateien von Hand in der GUI zu editieren.
GET /backtest/<jobId> bekommt bei Optimierungsläufen drei Felder dazu: optimizationType (0..3), optimizationResults (die besten N Durchläufe, sortiert) und optimizationCache, Metadaten aus dem .opt-Parse: Profilname, Byte-Offsets, MT5-Build, Symbolanzahl. Alle drei sind bei Läufen ohne Optimierung None. POST /backtest nimmt ein optionales topPasses (1..500, Standard 50), um zu deckeln, wie viele Zeilen zurückkommen.
Der Cache-Parser ist der spaßige Teil. mt5api/backtest/cache_parser.py betreibt Reverse Engineering am binären .opt-Format von MT5. Er ist profilgetrieben statt fest verdrahtet: Kandidaten-Layouts von Byte-Offsets werden gegen die echte Datei bewertet, und das beste gewinnt. Bringt MT5 einen neuen Build, der das Layout verschiebt, hängst du einen Kandidaten an OPT_CACHE_PROFILE_CANDIDATES an, keine Codepfade pro Build, kein Versionsschnüffeln.
Live-Log-Tail. GET /backtest/<jobId>/tail?lines=N führt das Lauf-Log (stdout von terminal64.exe), das MT5-Terminal-Journal und das Strategy-Tester-Sublog zu einem Strom zusammen. Begrenzt auf 10..1000 Zeilen. Funktioniert, während der Job in der Schlange steht, läuft oder fertig ist, du kannst einer sechsstündigen Optimierung also zusehen, statt auf status: running zu starren und zu hoffen.
Terminal-Instanzen. Ein Eintrag in terminals[] nimmt jetzt einen optionalen instance-Namen, wodurch dasselbe Broker/Konto-Paar mehrfach auftauchen darf, mit unterschiedlichen Ports und unterschiedlichen portablen Datenverzeichnissen. Genau darum geht es: ein Live-Terminal kann mit einem oder mehreren Backtest-Terminals auf demselben Login koexistieren. Lässt du ihn weg, löst er auf default auf und der alte Routen-Alias bleibt erhalten. Mehrere Einträge auf einem Broker/Konto-Paar brauchen unterschiedliche Instanzwerte, sonst kollidieren sie auf terminals/<broker>/<account>/<instance>/.
Positionsgröße
Der Symbol-Endpoint gibt dir alles, was du brauchst, um ordentliche Positionsgrößen zu rechnen:
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_lotAuf volume_step abrunden, auf [volume_min, volume_max] begrenzen. Plausibilitätsprüfung: volume * trade_contract_size * price sollte im Verhältnis zu deinem Kontostand Sinn ergeben. Ein Lot EURUSD sind 100.000 EUR, nicht 1 EUR, und trade_contract_size sagt dir das. Prüf es, bevor du versehentlich dein ganzes Konto auf etwas setzt, das du für eine Mikroposition gehalten hast.
KI-Skill
Das Repo bringt ein Verzeichnis .agents/skills/ mit, das eine Skill-Definition für KI-Coding-Agenten enthält. Es funktioniert in allem, was .agents/skills/ liest, OpenClaw eingeschlossen, und installiert sich nativ in Claude Code und Codex aus einem gemeinsamen Marketplace:
claude plugin marketplace add psyb0t/agents
claude plugin install mt5-httpapi@psyb0tCodex nutzt denselben Marketplace mit einem anderen Verb, codex plugin marketplace add psyb0t/agents und dann codex plugin add mt5-httpapi@psyb0t, weil es codex plugin install nicht gibt. Codex greift sich den Skill außerdem von selbst in einem Checkout des Repos, da es .agents/skills/ nativ scannt und gar keine Installation im Spiel ist. Du richtest ihn über MT5_API_URL auf deine laufende Instanz, ergänzt MT5_API_TOKEN, falls die Auth an ist, und der Agent bekommt die vollständige API-Referenz, die Sicherheitscheckliste vor dem Trade, die Formeln für die Positionsgröße und die Nutzungsmuster. Er weiß, welche Endpoints es gibt, welche Felder vor einem Trade zu prüfen sind und wie die Ergebnisse zu lesen sind.
Das heißt, du kannst deinem KI-Agenten sagen “kauf 0.1 Lot EURUSD mit einem Stop Loss von 2 ATR”, und er hat alles, was er braucht, um die Symbolinfos zu ziehen, den SL-Preis zu rechnen, die Order zu setzen und das Ergebnis zu prüfen. Kein händisches Lesen von API-Dokumentation, der Skill gibt ihm das komplette Drehbuch.
MCP: Ein Endpoint, Alle Terminals
Ein Skill bringt einem Agenten bei, die REST-API zu fahren. v4.7 ging weiter und ließ ihn die HTTP-Schicht ganz überspringen: jedes Terminal hängt seinen eigenen MCP-Server unter /<broker>/<account>/mcp/ ein, neben der REST-API, im selben Prozess. nginx schneidet das Präfix ab und reicht den Rest unverändert durch, die erreichbare URL ist also schlicht die normale Basis des Terminals plus /mcp/.
Es startete als generischer Passthrough, ein einziges request-Werkzeug, das jeden Endpoint aufrufen konnte, und v4.8 ersetzte das durch ~24 dedizierte typisierte Werkzeuge, gruppiert nach Familie: Marktdaten, Konto, Positionen, Orders, Historie, Terminal, Backtest. Der Name jedes Werkzeugs, seine typisierten Parameter und seine Beschreibung sind das, was der Agent liest, es wird also nicht mehr an rohen Pfaden geraten. Jedes Werkzeug läuft durch genau denselben Handler, dieselbe Auth und dasselbe MT5-Locking wie eine echte HTTP-Anfrage. Das generische request plus ein endpoints-Katalog blieben als Auffangnetz. Eine bewusste Auslassung: einen Backtest einzureichen ist ein Multipart-Upload, das bleibt also REST-only, get_backtest fragt Status, Bericht und Log ab, aber neue Läufe gehen über POST /backtest.
Womit eine ehrlich gesagt nervige Einschränkung blieb. Eine MCP-Session hat einen festen Werkzeugkatalog, es gibt also keinen Platz pro Aufruf, um ein Konto zu benennen, eine Session war dauerhaft an das Terminal gebunden, mit dem sie sich verbunden hatte. Sechs Terminals hießen sechs Sessions. v4.9 behebt das mit einem mcpunifier-Service: ein Linux-Container neben der Windows-VM, der dieselben Werkzeuge unter einem einzigen /mcp mit den Parametern broker und account ausliefert, dazu ein list_terminals-Werkzeug, damit ein Aufrufer herausfinden kann, was konfiguriert ist und welche Konten leben, bevor er irgendetwas anderes tut.
Die URL, die du dem Client gibst, entscheidet über deinen Explosionsradius:
http://host:8888/<broker>/<account>/mcp/ one terminal, no account param to get wrong
http://host:8888/mcp/ every terminal, + broker/account + list_terminalsDer Unifier liest dieselbe config/config.yaml, die auch das nginx-Routing erzeugt, er kann also physisch nirgendwohin routen, wohin nginx nicht routet, und schickt direkt an den Port jedes Terminals. Die Routing-Tabelle löst einmal beim Start auf und wird nie neu abgefragt, der Service wartet also nie auf ein Terminal, ein ausgefallenes Terminal lässt nur die Aufrufe scheitern, die es benennen, statt die anderen mitzureißen, und jede erfolgreiche Antwort trägt das Terminal, das tatsächlich geantwortet hat. Fragst du nach einem Broker/Konto-Paar, das nicht konfiguriert ist, wirst du abgelehnt, mit der Liste dessen, wonach du hättest fragen können, statt still auf etwas Plausibles, aber Falsches geroutet zu werden. Nichts Bestehendes hat sich geändert: die Endpoints pro Terminal und die gesamte REST-Fläche sind unangetastet.
v4.9.1 ist die Sorte Bug, die man aufschreiben sollte. Die von v4.9.0 erzeugte nginx-Config enthielt ein wörtliches proxy_pass http://mcpunifier:6600/, und nginx löst einen wörtlichen Upstream-Hostnamen auf, während es die Config parst, nicht zur Anfragezeit. Auf jedem Deployment ohne diesen Container brach nginx also mit host not found in upstream "mcpunifier" ab, und jede Route pro Terminal plus die gesamte REST-API dahinter gingen auf 502. Da docker-compose.yml gitignored ist, bekamst du beim Ziehen von v4.9.0 den neuen Generator ohne den Service, auf den er verweist, und der nächste Neustart legte dir den Stack lahm. Ein fehlender Unifier ist jetzt einfach nur ein fehlender Unifier. v4.9.2 ergänzte dafür ein End-to-End-Harness als Shell-Skript, das v4.10 dann wieder abschaffte, seine sieben Assertions wanderten unverändert in tests/integration/test_mcpunifier.py, sodass das Projekt ein Integrations-Harness in einer Sprache fährt statt eines Shell-Skripts daneben. Es heißt jetzt make test-integration, eine containergestützte pytest-Suite, die echtes nginx gegen die erzeugte Config hochfährt, mit einer absichtlich fehlenden VM.
Und es stellte sich heraus, dass dieser nginx-Bug einen größeren Bruder hatte. Dieselbe Auflösung des wörtlichen proxy_pass zur Parse-Zeit, die den Stack wegen eines fehlenden Unifiers lahmlegte, tat das auch für Terminal-Routen: ein einziger fehlender VM-Container und nginx startete überhaupt nicht mehr, riss die Routen jeder gesunden VM, die REST-API und /mcp/ mit. v4.10 sorgte dafür, dass Terminal-Routen ihren Upstream pro Anfrage auflösen, was auch das Nächste möglich machte.
v4.10 ging auf mehrere VMs. Eine vms.yaml deklariert die Ressourcen jeder Windows-VM, und jedes Terminal bindet sich über ein neues Feld vm: an eine davon; config_helper.py erzeugt nginx-Routen auf den Container der besitzenden VM, run.sh läuft für DNAT und VM-eigene Gruppendateien über jede VM, und docker-compose.yml wird aus einem Jinja-Template gerendert. Keine vms.yaml heißt Einzel-VM, und ein Terminal ohne vm:-Feld routet auf mt5, bestehende Deployments merken also nichts. Dieselbe Version ergänzte Contract-Tests für die Handler, die Geld bewegen: sie fahren die echten Flask-Routen mit dem MT5-SDK an der m()-Naht gefälscht und prüfen die exakte Anfrage, die bei order_send ankäme, ein Market-BUY zum Ask, ein SELL zum Bid, ein Teilschluss, der nur das angeforderte Volumen schickt, eine reine sl-Änderung, die das bestehende tp erhält. Jeder Fehlerpfad prüft außerdem, dass order_send nie aufgerufen wurde, denn ein Handler, der nach dem Senden abstürzt, hat bereits gehandelt. Die CI fährt das jetzt wirklich bei Pushes und PRs; pipeline.yml löste vorher nur bei v*-Tags aus, was hieß, dass die Suite unter tests/ nie in der CI gelaufen war.
v4.11 schloss die letzte Lücke zwischen den beiden MCP-Katalogen: die typisierten Werkzeuge pro Terminal und die vereinheitlichten legen jetzt übereinstimmende from/to-Bereichsparameter für Ticks und TA-Rates offen, mit Schema- und Paritätstests, die beide abdecken.
Das Entrümpeln
Die Windows-VM geht beim ersten Boot durch ein aggressives Entrümpeln. Alle Animationen, die Transparenz, das Hintergrundbild abschalten. SysMain, Audio, Spooler, Suche, Telemetrie und rund fünfzig weitere nutzlose Dienste killen. Windows Defender komplett entfernen, nicht deaktivieren, entfernen. Die Defender-Verzeichnisse in Besitz nehmen und die Binaries löschen. Den ganzen Privatsphäre-Mist rauswerfen: Werbe-ID, Aktivitätsverlauf, Diagnosedaten, sämtliche Capability-Berechtigungen. Jede geplante Microsoft-Spionageaufgabe abschalten. Prozessorpriorität auf Vordergrund, Kill-Timeouts verkürzen, NTFS-Zeitstempel abschalten.
Das Ergebnis ist ein Windows 11, das schnell bootet, im Leerlauf niedrig liegt und nicht alle 30 Sekunden bei Microsoft anruft. Gerade genug Betriebssystem, um MT5 und die Python-API zu fahren.
Jedes Mitgelieferte Binary Muss Sich Jetzt Erklären
Ein Repo, das eine Windows-VM hochfährt und ihr Defender herausreißt, sammelt ausführbare Dateien an. Nicht viele, aber die, die es hat, sind genau die, denen du am wenigsten blind vertrauen wolltest, und sie lagen ungeprüft im Baum.
v4.12.0 ergänzte make verify-binaries. Jede versionierte ausführbare Datei muss in assets/binaries.lock.json deklariert sein, mit ihrem sha256, ihrer Upstream-Quelle und ihrem Signaturzustand. Ein undeklariertes Binary bricht den Build. Ein verändertes bricht den Build. Eine beschädigte Signatur bricht den Build. Es läuft zuerst innerhalb von make test, die CI erzwingt es also bei jedem PR statt dann, wenn jemand daran denkt.
Der Sinn einer solchen Schranke ist nicht die Regel, sondern das, was die Regel in dem Moment findet, in dem du sie einschaltest. Hier dokumentierte sie sofort scripts/defender-remover/PowerRun.exe:
"path": "scripts/defender-remover/PowerRun.exe",
"product": "PowerRun",
"vendor": "Sordum Software",
"signature": "malformed",
"note": "REPACKED, NOT PRISTINE..."Es kam mitgeliefert im Inneren des defender-remover-Werkzeugkastens statt direkt von Sordum. Sein Zertifikatsverzeichnis ist kein wohlgeformtes WIN_CERTIFICATE, eine deklarierte Länge von 776284822, Revision 0xc496, Typ 14951, gegen ein gefordertes 0x200 und eine 2, und sein Hash passt zu keinem Upstream-Release von Sordum, die Signatur lässt sich also gegen nichts prüfen.
Um klar zu sein, was das heißt und was nicht: es ist nicht als bösartig bekannt. Jede Menge umgepacktes Werkzeug sieht so aus. Geändert hat sich, dass es nicht mehr still ist, der Zustand steht schwarz auf weiß, im Repo, neben der Datei, und die CI schlägt fehl, falls er sich je bewegt. Ein unerklärtes Binary, von dem du weißt, ist ein anderes Risiko als eines, von dem du nichts weißt.
Dieselbe Version brachte die Unit-Suite von 244 Tests auf 379. Alles, was vorher nur gegen ein lebendes Terminal lief, läuft jetzt in der CI und fährt die echte Flask-App gegen ein geskriptetes SDK, sodass mt5client, der Monitor und der Go-Client ihre allererste Abdeckung bekamen.
Logs
Alles läuft auf dem Host in data/metatrader5/logs/ zusammen:
- install.log: der Fortschritt der MT5-Installation
- start-mt5.log: das Log der Boot-Sequenz
- pip.log: die Installation der Python-Pakete
- api-<broker>-<account>.log: die API-Logs pro Terminal
- full.log: der zusammengeschaltete Feuerwehrschlauch aus allem oben plus den Windows-Event-Log-Einträgen, aus dem Inneren der VM abgegriffen. Das ist das, was die OOM-Kills und die still von Defender getöteten Prozesse fängt, die sonst nirgends auftauchen.
Ein eigener Log-Rotations-Sidecar läuft neben der VM und rotiert alles täglich mit 7 Tagen Aufbewahrung. Schluss mit 4-GB-Logdateien, die dir nach einer Woche laufendem Stack die Platte auffressen. Wenn etwas kaputtgeht, ist full.log die erste Anlaufstelle, chronologisch, eine Datei, alles in einem Strom.
Tailscale-Sidecar
Eine Trading-API öffentlich zu exponieren heißt, um einen Überfall zu bitten. Die meisten Leute wollen das Ding von ihrem Laptop aus erreichbar haben und von sonst nirgendwo. Also gibt es einen eingebauten Tailscale-Sidecar, der deinem Tailnet beitritt und die API unter einem nackten MagicDNS-Hostnamen ausliefert:
http://mt5-httpapi/roboforex/main/account
http://mt5-httpapi/roboforex/main/symbols/EURUSD/rates?count=100
http://mt5-httpapi/ftmo/challenge1/positionsAuth-Key in config.yaml setzen, den tailscale-Block in docker-compose.yml auskommentieren, make up. Läuft mit normalem Tailscale und mit selbst betriebenem Headscale (für Letzteres setzt du login_server). Schlichtes HTTP mit Absicht, die WireGuard-Schicht verschlüsselt innerhalb des Tailnets ohnehin alles, und nackte MagicDNS-Hostnamen haben sowieso keine passenden TLS-Zertifikate.
Der Sidecar läuft in seinem eigenen netns (Bridge-Modus, nicht das Host-Netz), bekommt also seine eigene Tailnet-Identität. Deine ACLs beschränken sich auf den Knoten des Sidecars, das Tailscale des Hosts (falls es eines hat) bleibt komplett außen vor, und jeglicher tailnet-gerichteter Verkehr aus dem Sidecar geht über dessen eigenes tailscale0-Interface, nicht über das des Hosts. Tailscale Serve lauscht innerhalb des netns auf Port 80 und reicht über das interne Docker-Netz an den immer laufenden nginx-Sidecar weiter. Der Zustand bleibt in .data/tailscale/state/, make down / make up nutzt also die bestehende Anmeldung weiter, der Auth-Key wird nur beim ersten Login verbraucht.
Das API-Token (falls gesetzt) gilt weiterhin obendrauf, Tailscale regelt die Erreichbarkeit im Netz, das Bearer-Token die Anwendung. Verteidigung in Schichten.
Cloudflare Tunnel (Wenn Du Wirklich Öffentlich Brauchst)
Wenn du das Ding tatsächlich aus dem offenen Internet erreichbar brauchst, sagen wir für einen gehosteten Bot oder ein Frontend auf Vercel, gibt es eine Cloudflare-Tunnel-Option. cloudflared wählt sich nach außen zur Cloudflare-Edge und reicht an den immer laufenden nginx-Sidecar weiter. Ein Tunnel, ein Hostname, jedes Terminal hinter /<broker>/<account>/ erreichbar:
https://mt5-api.yourdomain.com/roboforex/main/account
https://mt5-api.yourdomain.com/ftmo/challenge1/positionsKeine Firewall-Ports geöffnet. Kein NAT-Durchstechen. Keine Zertifikate zu verwalten, Cloudflare terminiert TLS an der Edge kostenlos unter seinem Universal SSL. Einrichtung: einmal cloudflared auf dem Host installieren, einen Tunnel anlegen, einen Hostnamen darauf routen, die Zugangsdaten nach .data/cloudflared/ legen, den cloudflared-Block in Compose auskommentieren, make up.
Behandle den öffentlichen Hostnamen als feindlich und setz immer api_token in config.yaml, wenn du das benutzt. Cloudflare regelt die öffentliche Erreichbarkeit; das Bearer-Token regelt die Anwendung. Lässt du das Token hier weg, kann jeder, der den Hostnamen findet, dein Konto leerräumen.
Unterm Strich
MetaTrader 5 in Docker mit einer REST-API. Echte Windows-VM per KVM, nicht Wine. Mehrere Broker und mehrere Konten gleichzeitig auf minimalen Ressourcen. Vollständige Marktdaten, Order-Verwaltung, Positionsverfolgung, Handelshistorie, serverseitige TA über den wickworks-Sidecar und eine vollständige Strategy-Tester-Pipeline über HTTP, alles hinter schlichtem JSON. Dazu ein typisierter Go-Client, ein KI-Agenten-Skill und typisierte MCP-Werkzeuge, pro Terminal oder über alle Terminals hinweg vereinheitlicht, damit du ein LLM Trades gegen die API fahren lassen kannst, ohne ihm die Doku vorzukauen.
Kein MQL5. Kein Windows-Desktop. Keine MT5-Bibliotheken auf der Client-Seite. Nur curl und los.
Hol es dir hier: github.com/psyb0t/mt5-httpapi
Lizenziert unter WTFPL, weil Trading einen Haftungsausschluss verlangen sollte, keine Softwarelizenz.