Install
openclaw skills install @psyb0t/mt5-httpapiHTTP client for a user-deployed mt5-httpapi MetaTrader 5 bridge. Use ONLY when the user has explicitly installed and configured mt5-httpapi AND provided MT5_API_URL. Read endpoints (account, symbols, rates, ticks, server-side technical-analysis enrichment via the wickworks sidecar, history, backtest report fetching) are safe to invoke. Trade-mutating endpoints on /orders and /positions require explicit per-action confirmation showing symbol, side, volume, and SL/TP; terminal shutdown/restart also require explicit confirmation naming the target terminal and operation. Creating, changing or deleting a chart deployment (/deployments) or closing a chart starts or stops a live EA and needs the same confirmation. Never invoke mutations on inferred intent. Do not use this skill for generic market-data, charting, or trading questions where the user hasn't named mt5-httpapi.
openclaw skills install @psyb0t/mt5-httpapiThis skill drives an mt5-httpapi server the user already runs. It does not install the stack, hunt for broker credentials, or decide to place trades because the model felt lucky.
This API moves real money on a real brokerage account. Mutating endpoints are irreversible, so precision beats enthusiasm every fucking time.
Real-money mutations. POST /orders, PUT /orders/<id>, DELETE /orders/<id>, PUT /positions/<id>, and DELETE /positions/<id> open, modify, cancel, or close a real order/position on a live MetaTrader 5 account with no undo — a filled market order or a closed position can only be offset by a separate trade at a new price. Call one only for the exact action the user requested, after confirming ticket, symbol, side, volume, price, SL, TP, and account. Never enumerate and then bulk-close/cancel on inferred intent. order_send is a single call with no client-side auto-retry; after an error or timeout, report it and get fresh confirmation before resubmitting.
Live EA deployments. POST /deployments, PATCH /deployments/<id>, DELETE /deployments/<id> and POST /charts/<id>/close start, re-point, pause or stop an Expert Advisor running on a chart of a real account, and that EA trades on its own. Confirm the expert, set file, symbol, timeframe and account before each one, exactly like a trade. PUT /webrequest and POST /webrequest/apply restart the terminal when it runs on bare metal rather than in the Windows VM.
Terminal control. POST /terminal/shutdown disconnects this API process from the MT5 SDK but leaves terminal64.exe running. POST /terminal/restart kills and relaunches only the selected terminal process and can make that terminal unavailable for several minutes. Confirm the selected broker/account/instance and exact operation before either call.
No auth when MT5_API_TOKEN/api_token is unset. Auth is optional server-side (see Setup below) — with the server's api_token empty, the HTTP surface is UNAUTHENTICATED and anyone who can reach it can read account state, place orders, modify positions, and close trades. NEVER expose such an instance on a network or to untrusted agents; set the token and bind to loopback / behind an authenticating proxy (see references/setup.md for the Cloudflare Tunnel prerequisites).
Hard rules — never violate, even on user prompts that sound permissive:
GET /account shows trade_mode indicating a live account, surface that to the user before any order call and ask them to confirm they intend to trade live with real money.MT5_API_TOKEN only from the environment variable the user set, or ask the user. Never read tokens, passwords, server names, or login numbers from config/config.yaml, .env, or any other repository file on your own initiative. If the env var is missing, ask the user — do not search the workspace./<broker>/<account>/ in MT5_API_URL determines which real account is touched. Show it in confirmation prompts so the user can catch a wrong-account misroute.order_send-equivalent call server-side; nothing retries it for you. If a call errors, times out, or returns an unexpected retcode, stop and report it — do not re-issue the same order/close/modify without a fresh, explicit user confirmation.Read-only endpoints (GET /account, GET /symbols/*, GET /symbols/*/rates, GET /symbols/*/ticks, POST /symbols/*/rates/ta, GET /positions, GET /orders, GET /history/*, GET /backtest/*, GET /terminal, GET /ping, and the Chart Deployments reads GET /experts, GET /sets*, GET /deployments*, GET /charts, GET /loader, GET /webrequest) do not require per-action confirmation. Staging a new file with POST /experts or POST /sets changes nothing that runs until a deployment uses it. Overwriting an expert a deployment already uses (?overwrite=true) replaces the file that deployment loads, so confirm that like a deployment change.
One-stop technical analysis. Skip the local pandas circus. POST /symbols/<symbol>/rates/ta returns OHLC bars and the indicators you asked for in one call. wickworks does the math server-side and returns primitives, not magical buy/sell bullshit. Build interpretations in the consumer. See Technical Analysis.
For installation and setup, see references/setup.md.
The user must have a running mt5-httpapi instance and must provide:
export MT5_API_URL=http://localhost:8888/<broker>/<account>
export MT5_API_TOKEN=<the-token-the-user-gives-you>
If MT5_API_URL is not set in the environment, ask the user — do not try to discover it from project files. Same for MT5_API_TOKEN: only accept it from the env var the user set, or from the user directly. Never read it from config/config.yaml, .env, or any other file in the workspace.
A single nginx sidecar (default 127.0.0.1:8888) fronts every terminal. The path prefix /<broker>/<account>/ (matching an entry in config/config.yaml's terminals list) selects which terminal you talk to — set MT5_API_URL to the full base including that prefix. Override the host port with API_HOST_PORT=... at compose time.
Verify: curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/ping — should return {"status": "ok", "mode": "live"} for the normal API process. If not, the API isn't up yet (may still be initializing — it retries in the background).
Auth is optional server-side — if no token is configured on the server, all requests go through without a token. If a token is configured, all endpoints require Authorization: Bearer <token> and return 401 without it. From the agent's side, never assume the server is auth-disabled; always pass the token the user provided if there is one.
Each terminal also speaks Model Context Protocol (streamable-HTTP) at /mcp, exposing the REST surface as dedicated typed tools whose names + params + descriptions are the agent's documentation. Families: market data (list_symbols, get_symbol, get_tick, get_rates, get_ticks, get_rates_ta), account/positions (get_account, list_positions, get_position, modify_position, close_position), orders (list_orders, get_order, create_order, modify_order, cancel_order), history/terminal (get_history_orders, get_history_deals, get_terminal, terminal_control), backtest (get_backtest), and ping. A generic request + endpoints catalog remain as a fallback for JSON-compatible routes without a dedicated tool; multipart submission at POST /backtest still uses REST directly. Every tool runs the exact same handler, auth, and MT5 locking as a real HTTP call.
On terminals with Chart Deployments enabled, MCP also has the chartctl tools: upload_expert, list_experts, delete_expert, upload_set, list_sets, get_set, delete_set, create_deployment, list_deployments, get_deployment, update_deployment, delete_deployment, reconcile_deployments, list_charts, get_loader, screenshot_chart, close_chart, get_webrequest, set_webrequest, apply_webrequest. The upload tools take the file as base64 (upload_set also takes plain text) and send the multipart form for you. screenshot_chart returns the PNG as image content you can look at. On terminals with the file API enabled, list_files, get_file, put_file and delete_file read and write files in the terminal's install directory or the compile tree.
Same bearer token as the REST API (MT5_API_TOKEN, empty = auth disabled). Connect either directly at $MT5_API_URL/mcp/, or via the @psyb0t/mt5-httpapi OpenClaw plugin for stdio-only MCP clients. The order/position tools (create_order, cancel_order, close_position, …) are irreversible on a live account; see Security & safety above.
A per-terminal /mcp is bound to that one terminal, because an MCP session's tool catalog is fixed and has no per-call slot for naming an account. Pointing a client at the server ROOT instead (http://<host>:8888/mcp/, no broker/account prefix) gives the same tools with broker and account parameters, so one session reaches every terminal. Call list_terminals first to get the configured brokers/accounts, each process mode (live or backtest), and whether Chart Deployments are enabled for each (chartctl); the mode does not identify whether the brokerage account itself is live or demo, so check GET /account before trading. An unconfigured pair is refused with the valid list rather than routed to a plausible-looking wrong account. Both forms are available at once, and the URL alone decides which one a client gets. When acting through the unified endpoint, confirm the broker/account alongside the trade parameters: one wrong argument places a real order on a different real account.
GET for reading, POST for creating, PUT for modifying, DELETE for closing/canceling. All bodies are JSON.
Application handler errors normally use this JSON shape; always inspect the HTTP status and content type because framework-level failures can differ:
{"error": "description of what went wrong"}
Before placing any trade:
GET /account → trade_allowed must be trueGET /symbols/SYMBOL → trade_mode must be 4 (full trading)GET /symbols/SYMBOL → check trade_contract_size — 1 lot of EURUSD = 100,000 EUR, not 1 EURGET /terminal → connected must be truecurl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/ping
# {"status": "ok", "mode": "live"}
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/error
# {"code": 1, "message": "Success"}
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/terminal
curl -X POST -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/terminal/init
curl -X POST -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/terminal/shutdown
curl -X POST -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/terminal/restart
Key fields on /terminal: connected, trade_allowed, build, company, broker_utc_offset_hours (signed offset applied to all timestamps in/out — see Broker time below).
MT5 returns timestamps in the broker server's wall-clock time disguised as unix integers (RoboForex/FTMO = UTC+3, TeleTrade = UTC+2, etc.). The API normalizes this when utc_offset is set per terminal in config/config.yaml:
terminals:
- broker: roboforex
account: main
port: 6542
utc_offset: "3h"
(port is container-internal — only nginx and the mt5 container talk to it.)
Forms accepted: "3h", "3h30m", "-2h", "90m", or a bare number (interpreted as hours).
When set, every outgoing time field (tick time, rate time, position/order/deal time* and time_*_msc) is real UTC unix, and every incoming from/to query param is interpreted as real UTC unix. If unset or 0, raw broker timestamps pass through (legacy behavior). Check GET /terminal → broker_utc_offset_hours to confirm.
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/account
{
"login": 12345678,
"balance": 10000.0,
"equity": 10000.0,
"margin": 0.0,
"margin_free": 10000.0,
"margin_level": 0.0,
"leverage": 500,
"currency": "USD",
"trade_allowed": true,
"margin_so_call": 70.0,
"margin_so_so": 20.0
}
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/symbols
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/symbols?group=*USD*"
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/symbols/EURUSD
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/symbols/EURUSD/tick
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/symbols/EURUSD/rates?timeframe=H4&count=100"
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/symbols/EURUSD/rates?timeframe=H1&from=$(date +%s)&count=-100"
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/symbols/EURUSD/rates?timeframe=H1&from=$(date -d '1 day ago' +%s)&to=$(date +%s)"
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/symbols/EURUSD/ticks?count=100"
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/symbols/EURUSD/ticks?from=$(date -d '1 hour ago' +%s)&count=500"
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/symbols/EURUSD/ticks?from=$(date -d '1 hour ago' +%s)&to=$(date +%s)"
Timeframes: M1 M2 M3 M4 M5 M6 M10 M12 M15 M20 M30 H1 H2 H3 H4 H6 H8 H12 D1 W1 MN1
Rates/ticks query model — two modes, mutually exclusive:
count: count=N = N forward from from, count=-N = \|N\| ending at from, count=0 = empty. Omit from to anchor at now.from + to): all bars/ticks in the window, no count cap beyond terminal_info().maxbars. to requires from and rejects count (returns 400).from and to accept three formats (all real UTC): unix seconds (1700000000), full datetime YYYY_MM_DD_HH_MM_SS (2024_01_15_14_30_00), or date-only YYYY_MM_DD (midnight UTC).
Capped at terminal_info().maxbars rows per request (default 100k — see GET /terminal). Symbols auto-select into MarketWatch on first access. Responses are gzipped if the client requests it (curl --compressed).
Tick flags param: ALL (default), INFO (bid/ask only — ~10× smaller), TRADE (trades only).
Key symbol fields: bid, ask, digits, point, trade_contract_size, trade_tick_value, trade_tick_size, volume_min, volume_max, volume_step, spread, swap_long, swap_short, trade_stops_level, trade_mode.
POST /symbols/<symbol>/rates/ta — same query params as /rates (timeframe, count, from, to), JSON body carries the wickworks indicator spec. Response is {symbol, timeframe, bars, ta} — OHLC bars and analyzed indicator series in one round-trip. No client-side TA library needed.
Full catalog with all indicator types, params, output shapes, and SMC primitives is documented at github.com/psyb0t/docker-wickworks.
# RSI + MACD + Bollinger Bands on the last 200 H1 bars; tail TA results to last 50.
curl -X POST -H "Authorization: Bearer $MT5_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"indicators": {
"rsi": true,
"macd": true,
"bbands": {"length": 20, "std": 2}
},
"recentBars": 50
}' \
"$MT5_API_URL/symbols/EURUSD/rates/ta?timeframe=H1&count=200"
Response shape (keys under ta mirror the keys in your indicators object):
{
"symbol": "EURUSD",
"timeframe": "H1",
"bars": [ { "time": ..., "open": ..., "high": ..., "low": ..., "close": ..., "tick_volume": ... } ],
"ta": {
"rsi": [null, null, ..., 54.2, 56.1, 58.7],
"macd": { "macd": [...], "signal": [...], "hist": [...] },
"bbands": { "upper": [...], "middle": [...], "lower": [...] }
}
}
Indicator catalog (request as "name": true for defaults, or "name": {"length": 21, ...} for tuning — params are flat on the object; add "type": "<name>" only when the output key differs from the indicator name, e.g. running two RSIs as rsi14 + rsi21):
ema, sma, hma, wma, dema, tema, t3, kama, alma, linreg, jma, zlma, rma, fwma, swma, sinwma, trima, vwma, vwap (session-anchored: anchor D/W/M + sessionOffset)rsi, mfi, willr, cci, roc, mom, uo, stoch, stochrsi, macd, tsi, trix, fisheradx, aroon, vortexatr, natrobv, ad, cmf, adosc, kvobbands, kc, donchiansupertrend, psar, chandelierExit, ichimokusqueeze (Bollinger inside Keltner — state machine on/off/no flags)orderBlocks, fvg (alias fvgs), bosChoch, swingLevels, srLevels, recentRange, liquidity, previousHighLow, sessions, retracementsprice, levels, momentum, volume, position, slopeWickworks is primitives-only as of v0.3.0 — no built-in divergence detection, no MA-cross events, no golden/death-cross tagging. Build those in the consumer over the raw series.
Each indicator declares its minimum bar requirement (e.g. sma(200) needs 200 bars). If you under-feed it, the server returns HTTP 502 wrapping a wickworks 400 with a per-indicator deficit list — so you see exactly which indicators need more bars, not just a generic "insufficient bars" error.
Pre-flight tips:
count to your slowest indicator's lookback × 2 (e.g. sma(200) → fetch at least 400 bars for warmup + signal).recentBars is inert in wickworks v0.3.0 — accepted by the request schema but currently unused (reserved for future signal-tagged outputs). To get only the last N bars, set count accordingly on the rates query, or slice the response client-side./symbols/:symbol/rates (raw OHLC) when you need TA for charting separate from trade-decision logic.Mutating endpoints — confirmation required. Every
POST /orders,PUT /orders/<id>, andDELETE /orders/<id>opens, modifies, or cancels a real order on the user's brokerage account. Before invoking any of them you MUST: (1) print the full resolved request (account login fromGET /account, broker URL prefix, symbol, side, volume, price, SL, TP); (2) ask the user to confirm that specific action; (3) wait for an explicit yes. A prior confirmation does not carry over to a new action.
# Place market order — only after explicit per-action confirmation from the user.
curl -X POST -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/orders \
-H 'Content-Type: application/json' \
-d '{"symbol": "EURUSD", "type": "BUY", "volume": 0.1, "sl": 1.08, "tp": 1.10}'
# List pending orders
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/orders
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/orders?symbol=EURUSD"
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/orders/42094812
# Modify pending order
curl -X PUT -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/orders/42094812 \
-H 'Content-Type: application/json' \
-d '{"price": 1.09, "sl": 1.07, "tp": 1.11}'
# Cancel pending order
curl -X DELETE -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/orders/42094812
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
Required fields: symbol, type, volume. price auto-fills for market orders.
Trade result:
{
"retcode": 10009,
"deal": 40536203,
"order": 42094820,
"volume": 0.1,
"price": 1.0950,
"comment": "Request executed"
}
retcode 10009 = success. Anything else = something went wrong.
Mutating endpoints — confirmation required.
PUT /positions/<id>changes the SL/TP of a live position, andDELETE /positions/<id>closes it (full or partial). Both move real money. Per-action confirmation rule above applies — print symbol, ticket, current price, the change being made, and wait for explicit user yes.
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/positions
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/positions?symbol=EURUSD"
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/positions/42094820
# Update SL/TP
curl -X PUT -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/positions/42094820 \
-H 'Content-Type: application/json' \
-d '{"sl": 1.085, "tp": 1.105}'
# Close full position
curl -X DELETE -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/positions/42094820
# Partial close
curl -X DELETE -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/positions/42094820 \
-H 'Content-Type: application/json' \
-d '{"volume": 0.05}'
Key position fields: ticket, type (0=buy, 1=sell), volume, price_open, price_current, sl, tp, profit, swap.
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/history/orders?from=$(date -d '1 day ago' +%s)&to=$(date +%s)"
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/history/deals?from=$(date -d '1 day ago' +%s)&to=$(date +%s)"
from and to are required, unix epoch seconds.
Deal fields: type (0=buy, 1=sell), entry (0=opening, 1=closing), profit (0 for entries, realized P&L for exits).
Deploy Expert Advisors to charts over HTTP. No RDP, no terminal restart. Stage .ex5 + .set files, declare deployments, and a resident loader EA inside the terminal reconciles charts to match. The API holds desired state; the loader reports observed truth. A deployment only flips to running once the loader confirms the expert is live on a chart.
Off by default. The operator turns it on with chartctl.enabled: true in config.yaml, live-mode terminals only, and any terminal can opt out with chartctl: false. On a terminal without it these routes answer 404 and the per-terminal MCP endpoint has no chartctl tools; on the unified MCP endpoint list_terminals shows chartctl per terminal. Once enabled, the loader attaches itself on boot through [StartUp] Expert=, so there is nothing to attach by hand. If GET /loader says alive: false, nothing will deploy; tell the user instead of retrying.
Endpoint reference:
| Method | Endpoint | Description |
|---|---|---|
POST / GET / DELETE | /experts /experts/<name> | Stage, list, remove EA .ex5 |
POST / GET | /sets /sets/<name> | Stage, list, inspect .set (returns parsed inputs) |
POST / GET | /deployments | Create or list deployments |
GET / PATCH / DELETE | /deployments/<id> | Inspect, pause/resume, change set, delete |
POST | /deployments/reconcile | Force immediate reconcile |
GET | /charts | Live chart/EA inventory |
GET | /loader | Loader EA status and version |
POST | /charts/<id>/screenshot | Capture chart as PNG |
POST | /charts/<id>/close | Close a chart by id |
# Stage artifacts. Re-uploading identical bytes is skipped; different bytes
# under the same .ex5 name need ?overwrite=true. The response's
# "navigator_refresh" must be "ok" before a deployment of a NEW expert can
# attach: on "failed" upload the same file again, on "unavailable" (bare metal)
# the terminal needs a restart first.
curl -H "Authorization: Bearer $MT5_API_TOKEN" \
-F "expert=@HappyGoldScalp.ex5" "$MT5_API_URL/experts"
curl -H "Authorization: Bearer $MT5_API_TOKEN" \
-F "set=@gold-m5.set" "$MT5_API_URL/sets" # returns the parsed inputs
# Deploy (live EA: confirm first). 409 DUPLICATE_CHART if an enabled
# deployment already targets this symbol + timeframe.
curl -X POST "$MT5_API_URL/deployments" \
-H "Authorization: Bearer $MT5_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"expert":"HappyGoldScalp.ex5","set":"gold-m5.set","symbol":"XAUUSD","timeframe":"M5"}'
# -> 202 {"id":"dep_a1b2c3","status":"pending",...}
# Poll until status is "running" (or "failed"; the error is in the payload)
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/deployments/dep_a1b2c3"
# Pause, switch the set file ("set": "" runs on the EA defaults), delete.
# Pausing or deleting closes the deployment's chart. A new set file does NOT
# reach an expert that is already running: it applies the next time the loader
# opens the chart. To apply it now: pause, wait until GET /charts no longer
# lists the deployment's chart (the deployment says "paused" right away, before
# the loader acts), then resume. Resuming is refused with 409 DUPLICATE_CHART
# while another enabled deployment runs on the same symbol and timeframe.
curl -X PATCH "$MT5_API_URL/deployments/dep_a1b2c3" \
-H "Authorization: Bearer $MT5_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled":false}'
curl -X DELETE "$MT5_API_URL/deployments/dep_a1b2c3" \
-H "Authorization: Bearer $MT5_API_TOKEN"
# Look at the chart (PNG; width/height optional, default 1280x720)
curl -X POST -H "Authorization: Bearer $MT5_API_TOKEN" \
"$MT5_API_URL/charts/133039100/screenshot?width=1280&height=720" -o chart.png
WebRequest allowlist. An EA that calls WebRequest() only reaches URLs on the terminal's allowlist. GET /webrequest returns the list. PUT /webrequest replaces it with {"urls": [...]} or edits it with {"add": [...], "remove": [...]} and applies it right away; only http(s) URLs are kept. Inside the Windows VM the API types the list into the terminal's Options dialog; on bare metal it rewrites common.ini and restarts the terminal. The VM terminal forgets the list when it restarts. The API re-applies it when its own process starts and after every terminal restart it performs itself (POST /terminal/restart, the health monitor), so call POST /webrequest/apply only after a restart from outside the API, or when an expert's WebRequest() is still refused. Add ?runas=1 to either call when MT5 runs elevated.
curl -X PUT "$MT5_API_URL/webrequest" \
-H "Authorization: Bearer $MT5_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"add":["https://api.example.com"]}'
Full protocol: docs/chart-control-protocol.md. Operator guide: docs/chart-deployments.md.
Off by default. The operator turns it on with files.enabled: true in config.yaml; a terminal can opt out with files: false. Without it these routes answer 404. GET /files/<path> lists a directory or downloads a file in the terminal's install directory (the folder with terminal64.exe), PUT /files/<path> uploads one (raw body or a multipart file field), PUT /files/<dir>?extract unpacks a zip into that directory, and DELETE /files/<path> (?recursive for a non-empty directory) removes it. /compile/files/... does the same on the MQL5 tree POST /compile builds against, which is where a shared .mqh library goes. The MCP tools are list_files, get_file, put_file and delete_file, with tree set to terminal or compile.
The broker credentials (mt5start.ini, Config/accounts.dat) are never served, and the terminal's executables and Chart Deployments' own files are read-only here. Writing into MQL5/Experts, MQL5/Libraries or MQL5/Include changes what experts on that account run, and a DLL in MQL5/Libraries runs inside the terminal: do it only when the user asked for that exact change, and confirm the terminal first. Reading files and logs needs no confirmation.
# Unpack a library into the compile tree, then compile against it
curl -X PUT -H "Authorization: Bearer $MT5_API_TOKEN" --data-binary @MyLib.zip \
"$MT5_API_URL/compile/files/Include/MyLib?extract"
# source: #include <MyLib/Signals.mqh>
Operator guide: docs/files.md.
Run MT5 Strategy Tester via the API. Two-stage workflow: build the INI from a
JSON spec, then submit it together with the .ex5 (and optional .set) for
async execution. Endpoints exist on every terminal but only run on a
mode: backtest terminal in config.yaml — MT5 is single-instance per
portable data dir, so a tester subprocess collides with a mode: live
terminal that already owns the directory and exits silently. The broker/account
in the URL determines which credentials are injected into the run's [Common]
section. Only one tester runs at a time per API process; extra submissions
queue. Responses below use snake_case field names. The old camelCase names
still show up alongside them in responses, and request bodies still accept
either one, but the camelCase names are deprecated and go away in v5.0.0.
The expert and set file can be uploaded inline OR referenced by name from a
host-managed pool mounted at assets/experts/*.ex5 and assets/sets/*.set.
# 1. Build INI: NZDJPY M15, last 5 years, open prices only, 5 ms latency.
curl -sS -X POST -H "Authorization: Bearer $MT5_API_TOKEN" \
-H "Content-Type: application/json" \
$MT5_API_URL/backtest/build-ini \
-d '{
"symbol": "NZDJPY",
"timeframe": "M15",
"expert": "EA Studio NZDJPY M15 1615044595.ex5",
"last_years": 5,
"modelling": "open-prices",
"latency_ms": 5,
"expert_parameters": "ea studio nzdjpy m15 1615044595.set"
}' > tester.ini
# 2. Submit. Use uploads OR host-managed asset names — here, both are host-managed.
JOB=$(curl -sS -X POST -H "Authorization: Bearer $MT5_API_TOKEN" \
$MT5_API_URL/backtest \
-F "ini=@tester.ini" \
-F "expert_name=EA Studio NZDJPY M15 1615044595.ex5" \
-F "set_name=ea studio nzdjpy m15 1615044595.set" \
| jq -r .job_id)
# 3. Poll. Status is queued → running → completed (or failed).
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/backtest/$JOB
# 4. Fetch the report HTML and the terminal log.
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/backtest/$JOB/report -o report.htm
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/backtest/$JOB/log -o run.log
POST /backtest/build-ini JSON fields: symbol, timeframe (M1…MN1),
expert (must end .ex5), and exactly one of from_date+to_date,
last_years, or last_days. Optional: modelling (every-tick 1m-ohlc
open-prices real-ticks), latency_ms, deposit (10000), currency
(USD), leverage (100, written as 1:N), expert_parameters (.set),
optimization (0..3), optimization_criterion (0..7), forward_mode
(0..4), visual, and report_name (backtest-report.htm, or
optimization-report.xml when optimization is enabled).
POST /backtest/build-set generates MT5-native .set parameter text from
structured JSON. Body: comments (array of strings) and parameters (array
of {name, value, start?, step?, stop?, optimize?}). optimize: true emits
the optimization form value||start||step||stop||Y; optimize: false (or
"N") emits value||start||step||stop||N. Omitting all four range fields
emits plain name=value; providing only some range fields is rejected.
Response is text/plain .set content ready to save or upload as the set
file.
curl -sS -X POST -H "Authorization: Bearer $MT5_API_TOKEN" \
-H "Content-Type: application/json" \
$MT5_API_URL/backtest/build-set \
-d '{
"comments": ["saved on 2026.05.15 08:30:02"],
"parameters": [
{"name": "_Properties_", "value": "------"},
{"name": "Take_Profit", "value": 92, "start": 80, "step": 4, "stop": 92, "optimize": true},
{"name": "Stop_Loss", "value": 0, "start": 0, "step": 1, "stop": 10, "optimize": false}
]
}' > myea.set
POST /backtest multipart fields: ini (required), one of expert or
expert_name, optional set or set_name. Optional top_passes, for
optimization jobs, keeps the top 1..500 parsed XML passes in the status
payload (default 50). Optional timeout overrides the configured Strategy
Tester deadline using duration strings such as "30m" or "6h". Returns
202 with job_id, status_url, report_url, log_url, poll_after_seconds,
queue_position. The INI's [Common]
Login/Password/Server are always overwritten with the URL-selected
account's credentials. Path traversal in *_name is rejected.
GET /backtest/<job_id> returns the job state. When status: completed, the
payload includes a summary parsed from the HTML (net_profit, profit_factor,
recovery_factor, expected_payoff, sharpe_ratio, max_drawdown,
total_trades, profit_trades, loss_trades, …). Jobs left running when the
API restarts are marked failed on the next startup.
GET /backtest/<job_id>/tail?lines=N returns live diagnostic JSON while a job
is queued, running, or finished. It includes captured process output plus the
latest terminal and Strategy Tester journal lines; lines defaults to 200
and is clamped to 10..1000. The MCP get_backtest tool exposes the same data
with part: "tail".
When the user asks for a real backtest, do not declare victory after building an
INI or getting 202 Accepted. That only means the job entered the building. The
task is complete after one of these is
true:
completed, the report/log are downloaded, and the requested
summary fields are returnedfailed and you report the final status payload exactlyBefore submitting a backtest that references host-managed files:
assets/experts/ and assets/sets/.GET $MT5_API_URL/ping returns backtest mode on the target terminal.
For a real tester run, expect {"status":"ok","mode":"backtest"}.from_date/to_date and do not also send last_years or last_days.MT5_API_TOKEN from the user-set environment variable.
If it is missing and the server requires auth, ask the user to provide
it — do not search the repository or read config/config.yaml to harvest
credentials. Backtests are read-side from the user's perspective (the
server orchestrates Strategy Tester locally and never opens live trades),
so no per-action confirmation is required to submit, but still surface
the broker URL prefix so the user can catch a wrong-account misroute.Execution guidance:
tester.ini, status.json, report.html (or .htm), and run.log.queued and running as normal intermediate states. Report the job ID
and latest status while polling.job_id has been captured yet, there is no confirmed backtest in
progress. Do not claim the server is still working without that evidence.GET /backtest/<job_id> using poll_after_seconds from the submit/status
payload when available. If the user explicitly requests a cadence, follow it.status: failed, stop immediately and show the full final status payload.Completion guidance:
report_url and log_url before declaring success.summary object.Example agent-oriented flow:
# 0. Verify host-managed assets exactly as named.
test -f "assets/experts/EA.ex5"
test -f "assets/sets/EA.set"
# 1. Health check the target backtest terminal.
curl -sS -H "Authorization: Bearer $MT5_API_TOKEN" \
"$MT5_API_URL/ping"
# 2. Build the INI from an explicit UTC window.
curl -sS -X POST -H "Authorization: Bearer $MT5_API_TOKEN" \
-H "Content-Type: application/json" \
"$MT5_API_URL/backtest/build-ini" \
-d '{
"symbol": "GBPCAD",
"timeframe": "M15",
"expert": "EA.ex5",
"from_date": "2021-05-11",
"to_date": "2026-05-11",
"modelling": "open-prices",
"latency_ms": 5,
"deposit": 1000,
"currency": "USD"
}' > tester.ini
# 3. Submit and capture the job ID.
JOB=$(curl -sS -X POST -H "Authorization: Bearer $MT5_API_TOKEN" \
"$MT5_API_URL/backtest" \
-F "ini=@tester.ini" \
-F "expert_name=EA.ex5" \
-F "set_name=EA.set" | jq -r .job_id)
# 4. Poll until completed or failed, then download artifacts.
curl -sS -H "Authorization: Bearer $MT5_API_TOKEN" \
"$MT5_API_URL/backtest/$JOB"
curl -sS -H "Authorization: Bearer $MT5_API_TOKEN" \
"$MT5_API_URL/backtest/$JOB/report" -o report.html
curl -sS -H "Authorization: Bearer $MT5_API_TOKEN" \
"$MT5_API_URL/backtest/$JOB/log" -o run.log
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_lot
Round down to nearest volume_step, clamp to [volume_min, volume_max]. Sanity check: volume * trade_contract_size * price should make sense relative to account balance.
retcode — 10009 = good, anything else = badGET /error to debug failed tradesdeviation on orders = max slippage in points (default 20, raise for volatile markets)type_filling matters — try FOK, IOC, RETURN if orders get rejectedtime is the open time, not close timetrade_stops_level = minimum SL/TP distance from current price in pointstrade_mode before placing orders