Quant Buffet API

Overview

What the Quant Buffet backtest API is, what the sandbox gives you for free, and how a run executes end to end.

Quant Buffet strategies run inside a Python sandbox built on the in-house backtest.* package. You write daily-rebalance logic; the platform loads prices, simulates fills with commission and slippage, tracks equity, and computes performance metrics. The same code runs in the browser IDE on every strategy page and in local scripts.

The whole API in one screen

ModuleWhat it gives youYou call it
backtest.dataload_daily_prices, load_price_panelRarely — the lab loads ASSETS for you
backtest.enginePortfolioEngine, EngineConfig, Trade, BacktestResultEvery day, via set_target_weights
backtest.metricscompute_metricsRarely — the lab reports metrics for you
backtest.templatesNine make_* factories plus build_strategyWhen you want a proven pattern
backtest.universesNamed ETF books and WHITELISTWhen choosing ASSETS

What you write

  • Module-level `ASSETS` — a list of whitelisted ETF/crypto tickers (1–15 symbols).
  • `make_on_day(prices)` — builds signals from the price panel and returns (on_day, ready).
  • `on_day(engine, dt)` — called once per trading day; call engine.set_target_weights() to rebalance.
  • `ready` — the first date your signals are valid, usually after your longest lookback.
python
# 1. Module-level ASSETS — the sandbox reads this before running anything
ASSETS = ["SPY", "BIL"]


# 2. Factory: precompute every indicator here, exactly once
def make_on_day(prices: pd.DataFrame):

    # 3. Daily callback: cheap lookups only
    def on_day(engine: PortfolioEngine, dt: pd.Timestamp) -> None:
        engine.set_target_weights(dt, {"SPY": 1.0})

    # 4. Warmup date: the backtest starts here
    ready = prices.index[0]

    # 5. Always return the pair, in this order
    return on_day, ready

What the sandbox gives you for free

These names are pre-injected into your namespace, so a lab strategy can skip imports entirely:

Injected nameBound to
pd, pandasthe pandas module
np, numpythe numpy module
math, jsonthe standard modules
PortfolioEngine, EngineConfigbacktest.engine classes
load_daily_pricesbacktest.data.load_daily_prices
compute_metricsbacktest.metrics.compute_metrics

Execution flow

  1. backtest/lab_sanitize.py strips CLI bootstrap lines (sys.path, __main__, docstring headers) from the submitted source.
  2. validate_source() parses the code with `ast` and rejects blocked imports, blocked calls, async syntax, and files over 80 KB.
  3. The source is exec'd in a restricted namespace with a safe-builtins subset.
  4. extract_assets() reads module-level ASSETS, checks each symbol against WHITELIST, dedupes, and enforces the 15-symbol cap.
  5. load_daily_prices(ASSETS, start=…) loads adjusted closes, from cache when available, then drops all-NaN rows.
  6. make_on_day(prices) runs once and must return (on_day, ready).
  7. PortfolioEngine.run(on_day, start=ready) walks the calendar, calling on_day then marking the book each day.
  8. A SPY buy-and-hold benchmark is built, compute_metrics runs, and the equity curve is downsampled for the chart.

Default run environment

SettingValue
Initial cash$100,000
Commission5 bps of notional per fill
Slippage2 bps per side
DirectionLong-only, no leverage, no shorting
FrequencyDaily bars, adjusted close
Default start2000-01-01, or the date chosen in the IDE
BenchmarkSPY buy-and-hold, else ASSETS[0]

Minimal working strategy

python
from __future__ import annotations

import numpy as np
import pandas as pd

from backtest.data import load_daily_prices
from backtest.engine import EngineConfig, PortfolioEngine
from backtest.metrics import compute_metrics

ASSETS = ["SPY", "QQQ", "TLT", "GLD", "BIL"]


def make_on_day(prices: pd.DataFrame):
    cols = [c for c in ASSETS if c in prices.columns]
    sma = prices[cols].rolling(200, min_periods=200).mean()
    state = {"last": None}

    def on_day(engine: PortfolioEngine, dt: pd.Timestamp) -> None:
        if sma.loc[dt].isna().all():
            return
        key = (dt.year, dt.month)
        if state["last"] == key:
            return
        state["last"] = key
        long = [s for s in cols if prices.at[dt, s] > sma.at[dt, s]]
        w = 1.0 / len(long) if long else 0.0
        engine.set_target_weights(dt, {s: w for s in long})

    ready = sma.dropna(how="all").index.min()
    return on_day, ready

Documentation map

  • Lab contract — required symbols, signatures, ready semantics, anti-patterns.
  • Syntax cookbook — the pandas and numpy idioms for signals, ranking, weights, and cadence.
  • Data APIload_daily_prices, caching, panel conventions.
  • Engine APIPortfolioEngine, weight normalisation, the fill algorithm, Trade.
  • Metrics API — every compute_metrics field and how it is calculated.
  • Templates — the nine make_* factories with their exact parameter names.
  • Universes — all 81 whitelisted symbols and the named books.
  • Sandbox rules — allowed imports, available builtins, hard limits.
  • Lab run API — the JSON contract of the runner and the HTTP endpoints.
  • Errors — every error type you can hit, with its cause and fix.
  • Examples — complete, runnable strategy patterns.