HTTP API for controlling the bot and the tester: settings, strategies, exchange API keys, positions and orders, logs, running the tester and working with its reports. Address — the same as the web interface: http://<host>:<port>, the port is set by the port field in settings_program.json.
The bot creates api_token itself on first start and stores it in settings_program.json. The token works from any address and with any auth_telegram value, and a browser login does not reset it. Keep the token like a password: it gives access to everything, including trading and exchange keys.
You can view and copy the token in the web interface: “Global configuration”, “API and MCP token” block. The “Issue new” button is there too; the old token stops working immediately. With Telegram login disabled, this block only works from the same machine (127.0.0.1). In the config editor, in the log and in the GET /api/settings/config response the token is hidden (the last 4 characters are visible), and it cannot be changed through the editor or POST /api/settings/config.
A new token via the API: POST /api/settings/api-token/regenerate, response { "success": true, "api_token": "..." }.
If Telegram login is disabled ("auth_telegram": false), requests without a token are accepted only from the same machine at 127.0.0.1 or localhost:
curl http://127.0.0.1:5000/api/current/bots
From other addresses without a token the API returns 401. With login disabled the web interface is still open to anyone who can connect to the port, so on a server with an open port keep login enabled.
Requests from pages of other sites (POST with an Origin header that does not match the bot's address) are rejected with 403 — this is CSRF protection. curl, scripts and MCP clients do not send the Origin header, so this does not affect them. If the bot is behind a reverse proxy, the proxy must pass the original Host header.
The body of POST requests is JSON in UTF-8 (Content-Type: application/json).
Names in the URL path are URL-encoded: strategy T4 BTC → /api/settings/strategy/T4%20BTC.
Settings and configs are sent and returned in the same form as in the bot's JSON files.
A successful action returns { "success": true, ... }.
An error returns { "success": false, "error": "error text" } with a code from the Response codes table.
A bot is identified by the account and the strategy name: /{accountName}/{settingsName}. If a strategy trades several pairs, the ?symbol= parameter selects the pair. Without it the first one is used.
The global program config (settings_program.json). The Telegram bot token is masked: the first 4 characters are visible. api_token is masked: the last 4 characters are visible.
Changes the global config and applies it immediately. Send only the fields that change — the rest stay as they were. The Telegram bot token, api_token and tester_data_dir are not changed here: these fields in the body are ignored.
Descriptions of settings fields — the same hints the settings editor shows. {kind}:
settings — strategy settings (bot and tester), for all strategies at once;
config_tester — the tester config, including parameter_mining, walk_forward and the reports_history.csv columns;
settings_program — the global config.
Parameters:
?lang= — en (default), ru or pt;
?filter= — only fields whose path contains one of the comma-separated substrings, for example ?filter=zigzag,close_by_margin.
200 Response
an object “dotted field path → description”:
{
"parameter_mining": "List of parameters to mine. Each has a name (parameter path) and either a start/end/step range or a values list...",
"monte_carlo": "Mining limit: if there are more combinations than this number, a random sample of this size is used..."
}
sections — sections the strategy reads; it does not read sections from ignored_sections. Common sections (basic, options, filter, exchange) are not listed.
position_filter — the strategy respects filter; filter.<name>.* fields take effect only when filter.<name>.use = true.
conditional_fields — fields that take effect only under a condition. A trailing * means all fields with this prefix.
notes — fields that affect the result only in edge cases (mining them often gives identical rows).
tester_ignores — fields the tester does not model.
shared_fields — section fields that other strategies read too.
A field without a rule is considered effective. The same rules produce the warnings when saving settings and starting the tester.
Creates or changes a strategy. The body is a strategy object, as in GET /api/settings/strategy/{name}. Changes apply to running bots immediately, a new strategy starts immediately.
?old_name= — rename the strategy old_name to the name from the body.
Creates a bot strategy from verified tester settings. The settings are copied as is; exchange.name and exchange.account come from the account. By default the strategy is created paused (is_runing: false): the bot loads it but does not trade until it is started (play).
Deletes a strategy and stops its bots. Orders and the position on the exchange are left untouched.
?full=1 — full removal: the strategy is paused, all its orders are cancelled, the position is closed, and only then the strategy is deleted. If cancelling orders or closing the position fails, the strategy stays paused and is not deleted — the position will not be left unmanaged.
How to switch a strategy off correctly. While paused, the position and orders stay on the exchange without the strategy managing them (its stops, take-profits and exits), so they must not be abandoned. The order:
pause;
cancel orders;
close the position;
delete the settings.
?full=1 does exactly this: after the pause it waits for the bot's current cycle to finish so it cannot place a new order. By hand — the same order, and the pause must not be skipped: a running bot places orders again right after the cancellation.
Request
POST /api/settings/strategy/T4%20BTC/delete?full=1
200 Response
{ "success": true }
Errors
404not found
409for full=1 the strategy must be running
500failed to cancel orders, close the position or delete the strategy
Creates or changes an account. Changes apply to running bots immediately.
?old_name= — rename the account old_name to the name from the body.
Keys are sent in plain text. With "is_encrypted": true the bot stores them encrypted. Empty api_key, api_secret, api_passphrase keep the current values on change; when changing is_encrypted the key and secret must be sent again.
is_runing: false — the bot is paused. position: null — no position. orders — number of open orders. sync_missed_cycles — how many cycles in a row failed to get data from the exchange.
wallet — wallet balance, margin — margin balance, as the exchange returns them. For spot both values are the USDT balance. The balance comes from running bots: an account without a running bot is not listed.
A full snapshot of the state of all bots. The field set is detailed and may change between versions — for a permanent integration use GET /api/current/bots, /api/current/positions and /api/current/orders.
Pauses a strategy (for a multi-pair strategy — all its pairs): it stops placing orders. The open position and orders stay on the exchange without the strategy managing them — after pausing either start it again or cancel the orders and close the position (see deletion).
Deletes the bot's strategy and removes the bot from operation. Orders and the position on the exchange are not removed and stay unmanaged. For a safe shutdown use POST /api/settings/strategy/{name}/delete?full=1.
Program version, server resources and counters. The fields pid, exe_path, working_directory, port (the port the request arrived on) and started_utc uniquely identify the process instance: an external script uses them to tell several running bots apart and to notice a restart (pid and started_utc change).
Shuts down the program. Available only with Telegram login. The program does not start by itself: an external supervisor (service, systemd, script) restarts the process. You can check that the supervisor brings the bot back 61 seconds after a stop (RestartSec=61, run_service.cmd) with the tools/check-shutdown-restart.ps1 script (it stops the running bot). There is no restart_program tool. It is usually not needed: changes to strategies, accounts and settings through the API apply to running bots immediately. Update with restart — POST /api/system/update. After a restart, use pid and started_utc from /api/system/status to confirm that a new process came up. Before stopping, check that no tester run is in progress: it will be interrupted, and the queue is not saved.
200 Response
{ "success": true } — the program exits in half a second
Errors for the last ?hours= hours (default 24, at most 48). ?warnings=1 — together with warnings. If Telegram is not configured (telegram.id_chat = 0), messages are not sent and there are no “chat not found” errors: the log gets the warning Telegram chat is not set once per run. A “chat not found” error with a chat set means a wrong chat id or token. Repeats are already grouped: identical messages from one source are merged into one entry, count — how many times it repeated over the whole stored window (up to 48 hours, since program start), first_utc and last_utc — the first and last occurrence. Entries go from newest to oldest.
?since= — UTC time (for example 2026-11-02T06:00:00Z; without a time zone it is treated as UTC): only entries that repeated after it are returned. To check “what is new since last time”, pass the latest last_utc from the previous response. If an entry's first_utc is earlier than since, it is an old error that happened again, and count stays cumulative. An invalid date — 400.
Log files, newest first; each day is a new file. ?contains= — only files whose name contains the text (strategy name, account, pair).
Log types:
strategy log — log_<date>_<Exchange> <account> <pair> <timeframe> <strategy> <name>.log, one per bot: signals, orders, fills, position, strategy errors;
account log — log_<account>_<date>.log: balances and positions from the exchange, exchange API responses and errors, position limiter, close_by_margin;
The tester runs strategies on historical data. Parameter mining, stress tests and snapshot replays run one at a time. Single runs can run in parallel. While historical data is downloading the tester does not start, and vice versa — in these cases the response is 409.
All runs work in the background: the response comes immediately, progress and results are in GET /api/tester/status.
run — parameter mining, a stress test or a snapshot replay. status: Idle, Running, Completed or Failed; runs_done / runs_total — how many parameter combinations have been calculated. report_folders — result folders of the current or last run inside root=report; result_files — final files already written to them since the run started: reports_history.csv, the summary HTML (report_optimizer_*, stress_test_*, walk_forward_*) and the snapshot.
wizard_runs — single runs. Finished ones stay visible for another hour. Metrics (stats) are not sent here — they are in /api/tester/wizard-runs/{run_id}. Batch runs that have not started yet have status: "Queued".
batches — batches of single runs: batch_id, done of total.
download — historical data download.
settings_folder — the current tester settings folder.
Which historical data is already downloaded: ?exchange= (required), ?symbols=A,B (without it — all downloaded pairs of the exchange), ?start=&end= — the period to check.
gap_days — days without data between the first and the last. period is present only with start and end. Only downloaded files count: what is missing is downloaded by the run itself (UpdateData) or by download-data.
Errors
400the exchange is not supported or only one of start / end is set
All requests to tester settings, runs and data downloads accept a settings folder: ?folder= in the URL or the folder field in the run body. It is a path inside tester/settings_strategy, "" is the main folder. Without folder the folder selected in the interface is used (POST /api/tester/settings-folder). This is shared state: a user or a parallel task can change it, so scripts and assistants should always pass folder.
Settings can have the optional fields tags (a list of labels) and owner (the author, for example "MCP"). They are used to filter settings in the list and to delete them in bulk.
Saves tester settings. The body is a settings object. ?old_name= — rename the settings old_name. Existing settings with the same name are not overwritten. ?folder= — the folder. ?owner_default= — the owner value if the settings have none (MCP passes MCP).
warnings — changed (non-default) fields that have no effect: a section of another strategy (one line per section), a field of another zz.type, a disabled use_* switch, basic.leverage (the tester does not model leverage) — see /api/settings/schema. null — no remarks. Warnings do not cancel the save.
The same warnings field is in the settings copy response, and the import has warnings by the name of each settings item.
Copies tester settings within the same folder (?folder=), optionally to another exchange or pair. ?owner_default= — as when saving. To copy to another folder — copy-to-folder.
Which settings to copy — names (names or masks), mask ("*" — all), tag, owner; filters combine, at least one is required. on_conflict — what to do if the name already exists in the target folder: skip (default), replace or error (copy nothing).
Checks the folder's settings without running — the same remarks as warnings when saving. ?folder= — the folder, ?mask= — a name or a mask (default: all).
config is optional: without it the saved tester config is used.
overrides — settings edits for this run only; settings files do not change. Rules apply in order. match — a name or a mask (default: all), tag — only settings with this tag. set replaces values, mul multiplies numbers (an integer field stays integer), cap_pct after them limits basic.balance_percentage_long and _short of these settings — a size multiplier with a per-pair ceiling: { "match": "T_*", "mul": { "basic.balance_percentage_long": 2, "basic.balance_percentage_short": 2 }, "cap_pct": 10 }. exclude: true removes settings from the run. Fields are dotted paths, as in the settings object. If a field is absent from all matching settings, it is an error (usually a typo); if it is absent from only some of them, those settings are listed in not_applied. The edits are written to the run's comment and end up in the report and the snapshot.
What can be mined. Any bot setting (settings[*].zz.depth, settings[T_*].basic.use_short, settings[NAME].…), any tester config field (config_tester.…), settings_program.… and the account. For numbers start/end/step is enough, for others (text, boolean) set values.
Pair screening. You do not need a settings file per pair: take one template setting and mine the symbol as a regular parameter. The list of pairs comes from tester_exchange_symbols:
Together with other parameters (for example, settings[*].zz.depth) this gives a grid per pair. The symbol goes into a reports_history.csv column, so the “pair → grid median” summary is calculated from this file (read_tester_file with columns). tester_download_data downloads the data for all mined pairs. The number of combinations is the product of the value list lengths: limit it with monte_carlo.
The tester runs all value combinations from parameter_mining; if monte_carlo is set, a random sample of that size. Check the number of combinations in advance; to stop a running mining — POST /api/tester/stop with full: true. Progress is in run of the GET /api/tester/status response (runs_done of runs_total).
Results go to a report folder named after the tester config's name_comment (characters invalid in a file name are replaced with _). The run response returns it in report_folders; with walk_forward enabled there is also a second folder <name_comment>_WF_OOS with out-of-sample runs. To keep experiments apart, give each its own name_comment. In the folder:
reports_history.csv — one row per combination: metrics (TotalPnLPercent, MaxDrawdownPercent, ProfitFactor, WinRate, TradingVolume, …), Strategies (settings names of the run separated by |), RunTag and a column with the value of each mined parameter. With single_mode each row has one setting, combination blocks go by settings name;
an HTML report of each combination (if report.enable_html_report is on) and its JSON version (if report.enable_json_report is on); the combination's RunTag is in the file name;
the optimizer summary report report_optimizer_*.html;
run_snapshot_<name_comment>_<time>.json — exact settings and config of the run, see snapshot-run.
periods — mining on several periods: [["2026-02-01","2026-05-01"], ["2026-05-01","2026-08-01"]]. Each period is a separate full run into the folder <name_comment>_<start>_<end>; they go one after another through the queue. The response is { "success": true, "periods": [ { "start", "end", "report_folders", "queued", "queue_id", "position" } ], ... }. To compare periods — /api/tester/mining-robustness.
Run warnings. The run response contains warnings (null — no remarks):
a parameter_mining parameter that is in neither the settings nor the configs (a typo: it will not apply);
a mined field that the matching settings do not read (ignored by all N matching settings: all combinations give the same result) or that not all of them read; conditions on fields that are mined themselves are not checked;
a note about a field that matters only in edge cases (zz.zz6_risk_max);
a field from overrides that the settings do not read.
Warnings do not cancel the run.
Single runs and batches also write these warnings to the run results (warnings of wizard-runs/{run_id}), and a batch also to the common warnings with the runs[i] <name> prefix. A snapshot run is checked the same way.
Portfolio mining. Without single_mode all settings go together in each combination. A parameter named settings[NAME].path changes one setting, and settings[T_*].path changes all settings by a name mask; the rest of the set stays as is. Compare rows by the portfolio MaxDrawdownPercent and UlcerIndex (enable reports_history.ulcer_index).
sides — trading sides for this run: long (long only), short (short only), split (each setting twice — <name> [long] and <name> [short]; with single_mode the sides are mined separately) or both (as in the settings).
?queue=1 — if the tester is busy with another full run or a data download, the run joins the queue instead of getting 409. The response then is { "success": true, "queued": true, "queue_id": "63a8e791", "position": 1, ... }; the run starts by itself, the queue is visible in queue of the GET /api/tester/status response, to cancel one that has not started — POST /api/tester/queue/{queue_id}/cancel.
Errors
400settings not found, invalid config, overrides or sides
404folder not found
409the tester is already running or data is downloading (without ?queue=1)
Stress test: the same settings under worse execution conditions. The body, ?queue=1 and the responses are as in /api/tester/run, except periods (not supported). Results are in the folder <name_comment>_STRESS (report_folders of the response): scenario rows in reports_history.csv and the comparison with the base in stress_test_*.html.
To wait for the run and get its metrics and report paths — GET /api/tester/wizard-runs/{run_id}?wait=. The progress of all runs is in wizard_runs of the GET /api/tester/status response.
A batch of single runs: several sets, each on several periods, through one queue with limited parallelism. This is how to compare variants and check a variant on several periods with one request.
Set fields — as in the /api/tester/run body (settings, folder, config, overrides, sides) plus name and its own periods. What the set lacks is taken from the batch. Each set goes on each period as a separate run; without periods — on the config's period. parallel — how many runs at once (default 2; each keeps its data in memory). An error in any set rejects the whole batch.
The batch and the results of each run. ?wait= — how many seconds (up to 240) to wait for the whole batch to finish.
A long batch (dozens of runs) does not fit in a response in full form (MCP cuts it at 200,000 characters and the JSON becomes invalid), so there is a compact form and pages. The same parameters apply to POST /api/tester/wizard-batch:
Parameter
Description
compact=1
each run has only name, start, end, run_id, status, run_params, key and report paths (message only for failed ones). Without summary, stats, warnings, applied
offset, limit
a page of the runs list; limit=0 — counters only. The response has offset, returned and has_more (there is a next page: offset + returned). done, total, failed always cover the whole batch
If the full response is longer than 150,000 characters, it is replaced with the compact one automatically (compact: true in the response); offset/limit pages are preserved.
key is in every run, including the full response: the main metrics in one form, from the JSON report's summary, or if there is no report, from the final stats: pnl, pnl_percent, max_drawdown_percent, max_drawdown_worst_percent (only with a JSON report), trades, volume, fees, profit_factor (only with a JSON report). There is no need to call reports/json for each run to get them.
summary — as the summary section of the JSON report; it is present if report.enable_json_report is on, otherwise stats is there instead. Batch run rows also go to reports_history.csv of the name_comment folder (columns StartDate, EndDate, Strategies). Batches are kept for an hour.
Errors
404no such batch: invalid batch_id, more than an hour has passed, or the program restarted
batch_id — all runs of the batch: those not started will not start;
full: true — the current full run: mining, stress test, walk-forward, snapshot;
clear_queue: true — also remove full runs that have not started from the queue.
A running run stops at the next candle. An interrupted run writes neither a report nor a reports_history row: a result for part of the period would be misleading. A full run keeps the rows of combinations already calculated; in parallel mode child processes exit. Statuses: a full run — Stopped in run of the /api/tester/status response, a single run — Cancelled in wizard-runs/{run_id}. This request does not stop a historical data download.
status — Running, Completed or Failed; Running after wait means the wait time is over — request again. report_path and report_json_path — paths inside root=report, null if the report was not written. snapshot_path — the run's snapshot: the exact settings and config it ran with. warnings — skipped settings, missing data and the reason there is no report. If no settings started, the run ends with status: "Failed" and the reason in message. Finished runs are kept for an hour (until the program restarts).
Errors
404no such run: invalid run_id, more than an hour has passed, or the program restarted
Repeats a run from a snapshot file. Each run saves a snapshot next to the report — run_snapshot_<name_comment>_<time>.json with the sections settings (settings of all strategies), tester_config and program_config. Tokens (api_token, telegram.bot_token) are not written to the snapshot. A snapshot run is full mode: one at a time, like mining. The file is selected by the ?root=&path= parameters, as in tester files.
The body is optional: you can send a changed snapshot — changes apply to this run only, the file does not change. The same in the interface: Tester, “Results (files)” tab, the “Run” button next to a snapshot opens it in the editor, and the edits go to this run only.
Request
POST /api/tester/snapshot-run?root=report&path=T4/run_snapshot_20261025.json
?queue=1 — join the queue if the tester is busy, as in /api/tester/run.
200 Response
{ "success": true }
Errors
400the snapshot has no tester config or settings
404file not found
409the tester is already running or data is downloading
Downloads historical data for all settings in the folder (?folder=). Progress and result are in download of the GET /api/tester/status response.
Parameter
Description
start
Period start, for example 2026-05-01. Default: the tester config's StartDate minus WarmupDays
end
Period end. Default: the current moment (UTC). EndDate of the saved config is not used, so you do not need to change the config to download fresh days
200 Response
{ "success": true }
When finished, download.message briefly says up to which day the data is on disk, and download.result has first_day, last_day (the last day with candles) and days for each pair:
"download": {
"active": false, "status": "Completed", "message": "Data on disk ends at 2026-10-31..2026-10-31 (last day with candles)",
"result": { "requested_start": null, "requested_end": null,
"pairs": [ { "symbol": "BTCUSDT", "exchange": "bybit", "first_day": "2025-10-01", "last_day": "2026-10-31", "days": 396 } ] }
}
The source publishes a closed day's candles with a delay (usually after 06:00 UTC): until then last_day stays a day behind and the download must be repeated later. A pair without data gets first_day/last_day = null.
Errors
400start or end is not a date, or start is not earlier than end
409a download is already in progress or the tester is running
Results of single runs, newest first. A new run of the same settings set replaces its previous record; the result of a specific run is in /api/tester/wizard-runs/{run_id}.
chartUrl — a link to the HTML report for the browser. report_path — the same report as a path inside root=report, for tester files and /api/tester/reports/summary. report_json_path — its JSON version; the field is present only if it was written.
Contents of a tester folder. ?root= — report (reports, default), data (historical data: tester/data or the folder from tester_data_dir in settings_program.json) or runs (sets for repeat runs); ?path= — a path inside it. ?contains= — only names with this text, ?offset= and ?limit= — part of the list. Folders come first by name, then files, newest first; total — how many items match in total.
Saves a JSON file in runs (?root=runs&path=): changes an existing one or creates a new one. The body is the new file content. If the body for an existing file has no settings section, it is taken from the current file. A new file must be a full snapshot: non-empty settings and tester_config.
This is how a run is prepared for the user: in the interface (Tester, “Results (files)” tab, runs folder) each such file has “Run” and “Edit” buttons. With use_runs in the tester config, a full run from the command line (--run-tester) runs all .json from runs.
200 Response
{ "success": true, "created": false }
Errors
400empty body, invalid JSON, or a new file without settings / tester_config
Detailed results of one run in compact JSON. The same numbers as in the HTML report, but the file is dozens of times smaller: tens of kilobytes instead of megabytes.
The tester writes <report>.json next to the HTML report if report.enable_json_report is on in the tester config (off by default). JSON is written even with report.enable_html_report off: this way a large mining run goes faster and still keeps the details of each combination.
Parameters:
?path= — report path in report: a .json file or its .html (for example, report_path from wizard-results);
?sections= — only these comma-separated sections, for example ?sections=summary or ?sections=metrics,by_symbol;
?trades_offset= and ?trades_limit= — part of the trades.
Sections:
Section
Content
summary
a 1–2 KB summary: PnL, drawdowns (including unrealized and the worst of three, like MaxDrawdownPercent in reports_history.csv), Ulcer Index, profit factor, Sharpe, CAGR, trades, win rate, fees, average and maximum position, worst month, monthly returns. Calculated on read from run, metrics and monthly, so old reports have it too
run
name_comment, comment, run_tag, run_params (values of mined parameters), period, exchanges, strategies and pairs. What the run appended to comment as text ([overrides: …], [sides: …]) is in separate fields, there is no need to parse comment with a regex: overrides (an array of rules as in the request, not truncated; in comment it is cut to 300 characters), sides, and where the run came from: batch_id and name (set name) for tester_run_batch and tester_run_single with periods, run_id for wizard runs. If there is no value, the field is null. Runs from the interface do not fill these fields, a snapshot replay takes them from the snapshot
metrics
the whole statistics table of the HTML report by blocks: PnL, balance, monthly returns, trades, fills, Buy & Hold, risk and exposure, Sharpe, Sortino, CAGR, Calmar, Ulcer Index…
by_symbol
results per pair: trades, win rate, profit factor, PnL, best and worst trade, drawdown, Buy & Hold, fees, volume, position size
monthly
monthly returns, %: { "2025-09": -4.15 }
trades
trades (from opening a position to a full close or reversal), sorted by close time — columns and rows
curves
balance (equity) and open position (open_positions_total, _long, _short) curves, up to 500 points; t — Unix time in seconds, v — values
daily
values at the end of each calendar day of the period, without gaps: days, equity (Margin balance), position (open position, USDT); strategies — strategies (settings) with pairs, code, type (trend / counter-trend) and tags; pnl and position_by_strategy — one array per strategy in the same order: the cumulative contribution to balance since the report start, and the position. Written with report.json_daily (on by default). For large sets the section is big — /api/tester/reports/portfolio gives a summary of it
settings
full strategy settings of the run
The fields v (format version), created_utc and html_report (HTML report name or null) are always returned.
Request
GET /api/tester/reports/json?path=T4/T4_run_001_of_001_BTCUSDT_1h_ee8c3c2e.html§ions=by_symbol,trades&trades_limit=2
In trades: count — the number of all trades, net_pnl — trade result net of fees, mae and mfe — the worst and best intermediate trade result (USDT), time is UTC.
Portfolio analytics of a run based on the daily section of its JSON report. Strategies are collected into groups, and everything below is calculated by group.
Parameters:
?path= — a JSON report (or its .html) in report;
?group_by= — type (default: T — trend, CT — counter-trend, by strategy code), tag (the first settings tag) or strategy (each setting is its own group);
?groups={"T":"T_*","CT":"CT_*"} — groups by name masks instead of group_by; settings without a match go to other;
?worst_days= — how many worst days of each group to take (default 10);
?curves=0, ?leave_one_out=0 — do not calculate these sections.
correlation — correlation of the groups' daily PnL;
worst_week — the worst 7 days in a row for the portfolio and each group's PnL in them;
worst_days — for each group its worst days: its PnL, portfolio PnL and the PnL of other groups on the same days; offset_percent — what share of the loss another group won back (below zero — it added to the loss);
leave_one_out — maximum drawdown and Ulcer of the portfolio without each strategy and each group: drawdown_added above zero — the strategy adds drawdown, below zero — it reduces it (hedges). Replaces a separate run of the set without each strategy;
curves — daily curves without gaps: balance, position, cumulative PnL and position of each group.
All values are based on end-of-day figures: drawdowns here are smaller than the intraday ones from metrics, and leave_one_out is approximate — without the strategies affecting each other through margin and position limits.
Errors
400the report has no daily section (report.enable_json_report and report.json_daily are required; old reports do not have it) or invalid group_by / groups
Mining robustness across several periods: the same parameter_mining grid run on each period into its own folder (periods in /api/tester/run).
Parameters:
?folders=a,b — folders in report, one period in each;
?metric= — a reports_history.csv column, default TotalPnLPercent; for drawdown, risk and UlcerIndex columns lower is better;
?base={"settings[*].zz.depth": 12} — the starting point for comparison;
?top= — how many combinations to show per setting (default 10).
Combinations are matched by parameter values and the Strategies column (with single_mode each setting has its own ranking). A plateau is calculated on each period — the mean metric over a combination and its grid neighbours (as in the robustness section of the optimizer report). Ranking is by the mean plateau of the periods, then by the worst period.
best_by_period — the best point of each period and its place in the overall ranking: a low place means the point is fitted to its period. beats_base — the combination is no worse than the base on any period.
Errors
400fewer than two folders, no metric column, or too few combinations for neighbours
Sends a tester report file to Telegram — to the tester notification chat (telegram.id_chat of the program config, where mining results also arrive). Only a file from report can be sent (.html, .json, .csv, .png, .txt, up to 50 MB); there is no access to trading or strategy chats. The caption is Tester report: <path> and caption.
Request
{ "path": "T4/T4_BTCUSDT_20261025.html", "caption": "Best variant by drawdown" }