Extend mt5-httpapi with backtest endpoints on top of v4 config#2
Conversation
9952de1 to
939fe8f
Compare
|
Hey, @Marinski ! Just one thing before tho: Once that's fixed, I'll merge xD |
…unch
Adds POST /backtest/build-ini, POST /backtest, and GET /backtest/<job>{,/report,/log} endpoints, served per terminal. INI builder is stateless; the runner injects the URL-selected account's credentials into [Common] and writes the file as UTF-16-LE+BOM+CRLF (MT5 silently rejects [Tester] Login under UTF-8). One tester runs at a time per API process; extra submissions queue.
Adds a 'mode: live' (default) | 'mode: backtest' field on each terminal in config.yaml. MT5 is single-instance per portable data directory, so a Strategy Tester subprocess against a directory already owned by a live terminal64.exe exits silently with code 0 and produces no report. 'mode: backtest' prepares the same portable dir but skips the auto-launch and the live-mode SDK init in mt5api.main, leaving the dir free for the tester subprocess. The mode flows from config.yaml -> config_helper.py -> start.bat -> api_runner.bat -> mt5api --mode and is echoed back from GET /ping.
Asset uploads are accepted inline (multipart) or referenced by name from a host-managed pool mounted at ./assets:/shared/assets:ro; *_name path traversal is rejected.
Tests: 28 new backtest unit tests (handler/ini_builder/jobs); full suite 121 passed.
README + SKILL: new Backtest sections covering POST /backtest/build-ini, POST /backtest, GET status/report/log, asset sources (inline vs host-managed pool), and a worked NZDJPY M15 example. Adds 'terminals[].mode' to the config.yaml example and a per-field note explaining the single-instance lock that makes 'mode: backtest' a hard requirement for tester runs. setup.md: minor reference fixes.
- add DEFAULT_BACKTEST_TIMEOUT with config and per-request override support - reuse parse_duration_to_seconds for backtest timeout parsing - support per-terminal symbol suffix remapping for backtests - respect reboot_interval overrides in startup for backtest use - add configurable nginx upload size/timeouts for larger EA and set files - include bars/ticks in parsed backtest report labels
|
Hey @psyb0t , very happy that you like the PR idea. I implemented the requested backtest timeout change with the fallback chain you asked for: POST override first, then config, then the hardcoded 6h default, and it reuses the existing duration parser instead of introducing a new format. Besides that, while testing the backtester flow I fixed a few runtime issues I hit in practice: per-terminal symbol suffix support so the same test inputs can run across multiple brokers with different symbol naming (in case someone wants to bulk backtest same EA/set against multiple terminals), startup now respects reboot_interval overrides so backtest instances are not interrupted when config sets it to 0, nginx upload/body limits are configurable for larger hosted EA/set uploads, and the report parsing now includes bars/ticks so missing history is visible in the parsed result. |
|
@Marinski awesome! Merged! Happy backtestin'! |
Single readable digest of every release. Annotated git tags remain the canonical source of truth -- each entry links to its tag for full detail. The v4.3.0 entry summarizes PR #2 (backtester) which landed earlier in this release cycle.
|
@Marinski also bumped version + updated the clawhub skill btw |
Check brief video explanation below:

Summary
This PR extends
mt5-httpapiwith HTTP-driven MT5 Strategy Tester support and an opt-in per-terminal execution mode (live/backtest).mt5-httpapialready runs real MT5 terminals inside a Windows VM and exposes them over HTTP for live trading and market data. This PR keeps that model intact and adds a second execution mode focused on isolated tester runs, served behind the same/<broker>/<account>/...routing, the same Docker / Windows VM runtime, and the same single-file YAML config.The branch lands as 2 commits on top of
v4.0.1:feat(backtest): MT5 Strategy Tester HTTP API + mode-aware terminal launchdocs(backtest): document mode field, endpoints, and asset workflowMotivation
The reason
modeexists at all is structural: MT5 is single-instance per portable data directory. Ifterminal64.exeis already running to back the live SDK on a portable dir, a Strategy Tester subprocess against the same dir exits silently with exit code0and produces no report, no journal entry, nothing. There is no way to run a tester on a directory currently held by a live terminal.The fix is to declare intent up front:
mode: liveterminal keepsterminal64.exerunning so the MT5 SDK stays initialized for live trading endpointsmode: backtestterminal prepares the same portable dir but does not auto-launchterminal64.exe, leaving the data dir free for the tester subprocess that the/backtestendpoint spawns on demandA single installation can run both kinds of terminal side by side.
What this PR adds
1. Per-terminal execution mode
A terminal entry in config.yaml can now declare a
mode. Default islive(fully backwards-compatible).The mode flows config.yaml → config_helper.py → start.bat → api_runner.bat →
mt5api --mode→MODEconstant in config.py. main.py branches on it: backtest-mode skipsinit_mt5, the background init thread, and the live monitor; sweep_orphans still runs.2. Backtest HTTP endpoints
Routes are registered on every terminal (the Flask blueprints don't change shape per terminal), but
POST /backtestonly runs to completion on amode: backtestterminal — see Motivation above. On amode: liveterminal the tester subprocess will be denied the data dir and the job will fail.Endpoints introduced by this PR:
POST /backtest/build-ini— stateless helper that turns a small JSON spec (symbol,timeframe, expert, date range, modelling, latency, deposit, currency, leverage, set, report name) into a fully-formedtester.ini. Optional; you can also write the INI yourself.POST /backtest— multipart submission. Returns202 Acceptedwith a job payload andRetry-After: 60.GET /backtest/<job_id>— job state + parsed summary on completion.GET /backtest/<job_id>/report— the MT5 HTML report.GET /backtest/<job_id>/log— the terminal log captured for the run.GET /pingechoes back{"status":"ok","mode":"<live|backtest>"}so callers can verify a terminal is configured the way they expect.Concurrency: only one tester runs at a time per API process (serialized by an internal
RUN_LOCK); additional submissions queue.POST /backtestMultipart fields:
ini— required. Submitted as UTF-8 text. The runner re-encodes to UTF-16-LE + BOM + CRLF before handing it to MT5, because MT5 silently rejects[Tester] Loginunder UTF-8.expert— optional uploaded.ex5expert_name— optional filename resolved from expertsset— optional uploaded.setset_name— optional filename resolved from setsRules:
expertorexpert_nameis requiredset/set_nameare optional*_nameis rejected[Common]Login/Password/Serverare always overwritten with the URL-selected account's credentialsGET /backtest/<job_id>Status lifecycle:
queued→running→completed|failed. Whenstatus: completed, the payload includes asummaryparsed from the HTML (netProfit,profitFactor,recoveryFactor,expectedPayoff,sharpeRatio,maxDrawdown,totalTrades,profitTrades,lossTrades, …). Jobs left running when the API restarts are markedfailedon the next startup.3. MT5 tester execution flow
The backtest handler:
optionxform = strso MT5 case sensitivity is preserved)Login/Password/Serverinto[Common]ReporttoReports\<jobId>.htmunder the portable dirterminal64.exe /portable /config:<ini>under the run lock4. Optional host-managed asset pool
For repeated runs of the same EA, this PR adds an optional asset model:
Mounted via
./assets:/shared/assets:roin docker-compose.yml, referenced fromPOST /backtestviaexpert_name/set_name. Inline upload still works exactly as documented; the two are interchangeable per request.5. Integration with the v4 single-file config model
Uses the v4 config.yaml model. config_helper.py gained a
terminalscommand that emitsbroker / account / port / utc / modeforstart.batandapi_runner.batto consume.API behavior
All terminals, regardless of
mode, expose the same Flask blueprints. The difference is runtime state, not route surface:mode: live—terminal64.exeis up, MT5 SDK is initialized, all live-trading + market-data endpoints function normally.POST /backtestwill fail because the live process owns the portable dir.mode: backtest—terminal64.exeis not running, MT5 SDK is not initialized. Live-trading and market-data endpoints will return errors (no SDK).POST /backtestworks because the data dir is free for the tester subprocess.Example requests
Build INI from a JSON spec
Submit with uploaded files
Submit with host-managed assets
Poll status, fetch report and log
Example queued response
{ "jobId": "4e3a7f5a1b0d4f6b8c9d2e1f3a4b5c6d", "status": "queued", "broker": "roboforex", "account": "tester", "submittedAt": "2026-05-08T12:34:56Z", "reportName": "backtest-report.htm", "reportPath": "C:\\...\\Reports\\4e3a7f5a....htm", "logPath": "C:\\...\\logs\\backtest-roboforex-tester-4e3a7f5a....log", "statusUrl": "/backtest/4e3a7f5a1b0d4f6b8c9d2e1f3a4b5c6d", "reportUrl": "/backtest/4e3a7f5a1b0d4f6b8c9d2e1f3a4b5c6d/report", "logUrl": "/backtest/4e3a7f5a1b0d4f6b8c9d2e1f3a4b5c6d/log", "pollAfterSeconds": 60, "queuePosition": 0 }POST /backtestalso setsRetry-After: 60.Configuration changes
config.yaml
New optional terminal field:
mode: live(default) |backtestOptional host-managed asset folders
Mounted into the VM via
./assets:/shared/assets:ro. Used only when the caller suppliesexpert_name/set_name.Documentation updates included in this PR
terminals[].modefield documented in the config example with an explanation of the single-instance lock that makesmode: backtestmandatory for tester runs../assets:/shared/assets:romount.Compatibility
Fully additive:
modedefaults tolive; existing config files keep working as-is./<broker>/<account>/...routing is unchanged.Validation
python3 -m py_compileacross mt5api,bash -n run.sh,docker compose -f docker-compose.yml(.example) config -q.mode: backtest:8992f6ec…completed successfully.terminal64.exeexit code0, wall time114.5 s.backtest-report.htmwritten, 276,090 bytes.netProfit: 129.89,profitFactor: 1.34,totalTrades: 123.Tester automatical testing started→last test passed with result successfully finished.GET /pingon the same terminal returned{"status":"ok","mode":"backtest"}.Review notes
The PR crosses a few layers (API routes, MT5 execution flow, startup scripts, compose, docs) but the core idea is narrow: let a terminal declare whether it's there to serve live trading or to run tester jobs, because MT5's single-instance lock means a single portable dir can't do both at once.
The branch is structured as
feat:thendocs:so the implementation can be reviewed independently of the documentation surface.