run_backtest
Ship a backtest profile to a vike-datahub server and get the report back, optionally with a Rhai script injected into it.
Runs one backtest on the remote vike-datahub server named by --addr and returns the
BacktestReport JSON. Read-only in the MCP sense — a backtest mutates nothing — and annotated
readOnlyHint: true in tools_spec().
Input
| Field | Type | Required | Meaning |
|---|---|---|---|
profile | string | yes | A backtest profile TOML: [data], [strategy], optional [engine] |
script | string | no | Rhai source, injected as [strategy.params].src |
tool_run_backtest reads profile, and a missing one is a tool error before anything connects.
With script present it rewrites the profile through
crate::cmd::backtest::inject_script_src: the profile is parsed as a toml::Value (a malformed
one is a clean error, not a panic), [strategy] and [strategy.params] are created if absent,
and src is set — existing params, the numeric knobs a [sweep] varies, are preserved, and any
pre-existing inline src is overwritten.
An injected script only runs if strategy.name is rhai
src is read by exactly one arm of vike_backtest::harness::registry's strategy_by_name — the
"rhai" arm, which is deliberately absent from the STRATEGIES roster because it is not
resolvable with default params. Inject a script under name = "buy_hold" and the script is
carried along as an unread param while the built-in strategy runs.
A call
The transport is newline-delimited JSON-RPC 2.0 — one message per line, no embedded newlines — so each of these goes on the wire as a single line.
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"run_backtest","arguments":{
"profile": "[data]\nvenue = \"binance\"\nsymbols = [\"BTCUSDT\"]\nkind = \"bar\"\ninterval = \"1d\"\nfrom = \"2024-01-01T00\"\nto = \"2024-06-01T00\"\n\n[strategy]\nname = \"buy_hold\"\n"
}}}The profile crosses the wire verbatim. The server's own BacktestProfile::from_toml_str is
the only parser in the loop, so a profile-shape error — a bad range, an unknown strategy.name, a
[data] table naming a series the store does not hold — arrives from the server as
profile parse/validate failed: … and reaches the agent as an isError result. One parser, one
error source. Run profiles documents the file itself.
What comes back
{ "report": … }, wrapping the BacktestReport exactly as the server serialized it. Its fields
are fixed by vike_analytics::report::BacktestReport; the numbers are your run's.
| Field | Meaning |
|---|---|
name | the profile's free-form name, or null |
final_equity | closing equity |
total_return | fractional return from the first to the last equity-curve point |
n_trades · win_rate | trade count, and the fraction with positive PnL |
sharpe | annualized Sharpe of per-bar returns; 252 for daily bars |
max_drawdown | largest peak-to-trough drop, a positive fraction of the peak |
profit_factor | gross profit over gross loss — null when non-finite, so a run with no losing trades but some profit reads as "no meaningful ratio" rather than a number |
funding_paid | net perp funding cashflow, received-positive; 0.0 for a spot run |
per_symbol_pnl | multi-symbol event runs only; empty otherwise |
zero_trade | present only when the run closed no trades and equity never moved — a ranked list of probable causes, not an empty stats block |
That last field is the one worth reading first on a disappointing run: ZeroTradeReport::analyze
composes it from diagnostic counters the run already carried, so "why did nothing happen" has an
answer instead of a table of zeros. The report and
metrics define every number.
A keyed datahub refuses this tool at connect
Every Run* verb is control scope on the server, because it compiles client-supplied Rhai —
vike_datahub::server's required_scope says so, and calls that a design decision rather than an
oversight. The MCP server connects with DatahubClient::connect, which against a datahub
advertising the auth feature fails immediately with an actionable message rather than connecting
and having every verb refused a round trip later. Against a key-less datahub — the default, and
the usual localhost or SSH-tunnelled shape — nothing changes.
An unreachable server is likewise a clean tool error, never a panic: tool_run_backtest maps the
connect failure to cannot connect to datahub at …. The pin that drives that shape,
remote_run_tools_without_a_reachable_datahub_are_clean_errors, points at the sibling run tools —
run_sweep, run_walk_forward and list_series — rather than at this one.
The prompt
Backtest this script on BTCUSDT daily bars for the first half of 2024 and tell me whether it traded at all.
Pair it with validate_strategy before the run and
run_walk_forward after it. The human twin of this tool is
vike-cli backtest, which ships the same TOML over the same
verb.
list_series
Enumerate every stored data series the remote vike-datahub server holds, each with its cheap coverage, so an agent can pick a real date range before it writes a profile.
run_sweep
Expand a profile's parameter grid on the datahub and get every point back ranked best-first, each with its own report.