Extend mt5-httpapi with backtest endpoints on top of v4 config#2
Open
Marinski wants to merge 2 commits into
Open
Extend mt5-httpapi with backtest endpoints on top of v4 config#2Marinski wants to merge 2 commits into
Marinski wants to merge 2 commits into
Conversation
cdfe3ee to
7f3f456
Compare
…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.
Owner
|
Hey, @Marinski ! Just one thing before tho: Once that's fixed, I'll merge xD |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.