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
| Module | What it gives you | You call it |
|---|---|---|
backtest.data | load_daily_prices, load_price_panel | Rarely — the lab loads ASSETS for you |
backtest.engine | PortfolioEngine, EngineConfig, Trade, BacktestResult | Every day, via set_target_weights |
backtest.metrics | compute_metrics | Rarely — the lab reports metrics for you |
backtest.templates | Nine make_* factories plus build_strategy | When you want a proven pattern |
backtest.universes | Named ETF books and WHITELIST | When 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, readyWhat the sandbox gives you for free
These names are pre-injected into your namespace, so a lab strategy can skip imports entirely:
| Injected name | Bound to |
|---|---|
pd, pandas | the pandas module |
np, numpy | the numpy module |
math, json | the standard modules |
PortfolioEngine, EngineConfig | backtest.engine classes |
load_daily_prices | backtest.data.load_daily_prices |
compute_metrics | backtest.metrics.compute_metrics |
Execution flow
backtest/lab_sanitize.pystrips CLI bootstrap lines (sys.path,__main__, docstring headers) from the submitted source.validate_source()parses the code with `ast` and rejects blocked imports, blocked calls,asyncsyntax, and files over 80 KB.- The source is
exec'd in a restricted namespace with a safe-builtins subset. extract_assets()reads module-levelASSETS, checks each symbol againstWHITELIST, dedupes, and enforces the 15-symbol cap.load_daily_prices(ASSETS, start=…)loads adjusted closes, from cache when available, then drops all-NaN rows.make_on_day(prices)runs once and must return(on_day, ready).PortfolioEngine.run(on_day, start=ready)walks the calendar, callingon_daythen marking the book each day.- A SPY buy-and-hold benchmark is built,
compute_metricsruns, and the equity curve is downsampled for the chart.
Default run environment
| Setting | Value |
|---|---|
| Initial cash | $100,000 |
| Commission | 5 bps of notional per fill |
| Slippage | 2 bps per side |
| Direction | Long-only, no leverage, no shorting |
| Frequency | Daily bars, adjusted close |
| Default start | 2000-01-01, or the date chosen in the IDE |
| Benchmark | SPY 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, readyDocumentation map
- Lab contract — required symbols, signatures,
readysemantics, anti-patterns. - Syntax cookbook — the pandas and numpy idioms for signals, ranking, weights, and cadence.
- Data API —
load_daily_prices, caching, panel conventions. - Engine API —
PortfolioEngine, weight normalisation, the fill algorithm,Trade. - Metrics API — every
compute_metricsfield 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.