Quant Buffet API
Lab run API
The JSON contract between the browser IDE, the HTTP routes, and the Python runner.
The IDE on every strategy page talks to a Next.js route, which spawns backtest/sandbox_runner.py and exchanges a single JSON object over stdin/stdout. Knowing this contract is useful for debugging and for scripting the runner directly.
HTTP endpoints
| Route | Purpose | Access |
|---|---|---|
POST /api/strategy-lab/backtest | Run edited code for a published strategy | Signed in, subject to the strategy paywall |
POST /api/strategy-lab/fix-code | Ask the AI debugger for a fix | Same as above |
POST /api/draft-preview/backtest | Run code from the internal draft lab | Admin / draft access |
bash
POST /api/strategy-lab/backtest
Content-Type: application/json
{
"slug": "dual-momentum-global-equity",
"locale": "en",
"code": "ASSETS = ['SPY', 'BIL']\n...",
"start": "2000-01-01"
}Every successful run is metered against your account and appears under Account → Backtest Usage, including the simulated fill count.
bash
POST /api/strategy-lab/fix-code
Content-Type: application/json
{
"slug": "dual-momentum-global-equity",
"locale": "en",
"code": "<your edited source>",
"error": {
"type": "NameError",
"message": "name 'ASSETS' is not defined",
"line": 12
}
}Runner request (stdin)
json
{
"code": "ASSETS = ['SPY', 'BIL']\n\ndef make_on_day(prices):\n ...",
"start": "2000-01-01",
"max_points": 90
}| Field | Type | Default | Meaning |
|---|---|---|---|
code | str | "" | Full Python source, sanitised before validation. |
start | str | "2000-01-01" | Passed to load_daily_prices. |
max_points | int | 90 | Chart resolution for equity and benchmark. |
Successful response
json
{
"ok": true,
"assets": ["SPY", "BIL"],
"start": "2000-10-16",
"end": "2026-09-05",
"trades": 148,
"trades_sample": [
{
"date": "2000-10-16",
"symbol": "SPY",
"side": "buy",
"shares": 712.345678,
"price": 140.281234,
"value": 99950.0,
"commission": 49.975
}
],
"trades_sample_note": "Showing first 10 and last 20 of 148 fills",
"metrics": { "cagr": 0.0784, "sharpe": 0.71, "max_drawdown": -0.1932 },
"displayMetrics": {
"annualisedReturn": "7.84%",
"volatility": "10.42%",
"sharpeRatio": "0.71",
"sortinoRatio": "1.03",
"maxDrawdown": "-19.32%",
"beta": "0.48",
"alpha": "1.90%",
"winRate": "54.10%"
},
"equity": [{ "date": "2000-10-16", "equity": 100000.0 }],
"benchmark": [{ "date": "2000-10-16", "equity": 100000.0 }]
}| Field | Description |
|---|---|
assets | Symbols after whitelist validation and de-duplication. |
start, end | Actual first and last date of the equity curve. |
trades | Total fill count for the whole run. |
trades_sample | All fills when there are 36 or fewer, otherwise the first 10 and last 20. |
trades_sample_note | Explanatory string, or null when the sample is complete. |
metrics | The raw compute_metrics dictionary. |
displayMetrics | Pre-formatted strings for the UI tiles. |
equity, benchmark | Downsampled {date, equity} arrays for the chart. |
Error response
json
{
"ok": false,
"error": {
"type": "SyntaxError",
"message": "Syntax error at line 14: invalid syntax",
"line": 14,
"column": 22,
"traceback": "Traceback (most recent call last): ..."
}
}| Field | Notes |
|---|---|
type | Python exception class name, or ContractError / BadPayload. |
message | Human-readable text; shown verbatim in the IDE. |
line | Line in your source, resolved from the <strategy> traceback frame. May be null. |
column | Present for SyntaxError only. |
traceback | Last 2,500 characters of the formatted traceback. |
The line number is recovered by walking the traceback in reverse and taking the last frame whose filename is <strategy>. That means the reported line is the deepest point inside your code, not inside pandas — which is almost always the line you need to edit.
Driving the runner locally
bash
# From the repository root
python -c "import json,sys; print(json.dumps({'code': open('my_strategy.py').read(), 'start': '2005-01-01'}))" \
| python -m backtest.sandbox_runner