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

RoutePurposeAccess
POST /api/strategy-lab/backtestRun edited code for a published strategySigned in, subject to the strategy paywall
POST /api/strategy-lab/fix-codeAsk the AI debugger for a fixSame as above
POST /api/draft-preview/backtestRun code from the internal draft labAdmin / 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
}
FieldTypeDefaultMeaning
codestr""Full Python source, sanitised before validation.
startstr"2000-01-01"Passed to load_daily_prices.
max_pointsint90Chart 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 }]
}
FieldDescription
assetsSymbols after whitelist validation and de-duplication.
start, endActual first and last date of the equity curve.
tradesTotal fill count for the whole run.
trades_sampleAll fills when there are 36 or fewer, otherwise the first 10 and last 20.
trades_sample_noteExplanatory string, or null when the sample is complete.
metricsThe raw compute_metrics dictionary.
displayMetricsPre-formatted strings for the UI tiles.
equity, benchmarkDownsampled {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): ..."
  }
}
FieldNotes
typePython exception class name, or ContractError / BadPayload.
messageHuman-readable text; shown verbatim in the IDE.
lineLine in your source, resolved from the <strategy> traceback frame. May be null.
columnPresent for SyntaxError only.
tracebackLast 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