Quant Buffet API
Examples
Complete, runnable strategy patterns for the lab and for local scripts.
Each example below is a complete file. Paste one into the IDE on any strategy page and press run, then change a single parameter and compare the metrics.
1. SMA trend, written from scratch
Monthly rebalance, equal weight across whichever ETFs are above their own 200-day average. The state dict is the cadence gate; ready waits for the first valid SMA.
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, ready2. The same idea via a template
Three lines instead of twenty, with identical mechanics. Prefer this once you trust the pattern.
from backtest.templates import make_sma_trend
ASSETS = ["SPY", "QQQ", "IWM"]
def make_on_day(prices: pd.DataFrame):
return make_sma_trend(prices, ASSETS, {"sma_days": 200})3. Dual momentum, written from scratch
Relative strength picks the leader; absolute strength decides whether to hold it at all. This is the most common shape in the strategy library.
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", "EFA", "EEM", "TLT", "GLD", "BIL"]
LOOKBACK = 252
CASH = "BIL"
def make_on_day(prices: pd.DataFrame):
cols = [c for c in ASSETS if c in prices.columns]
risky = [c for c in cols if c != CASH]
mom = prices[cols].pct_change(LOOKBACK)
state = {"last": None}
def on_day(engine: PortfolioEngine, dt: pd.Timestamp) -> None:
key = (dt.year, dt.month)
if state["last"] == key:
return
state["last"] = key
scores = {s: float(mom.at[dt, s]) for s in risky
if pd.notna(mom.at[dt, s])}
if not scores:
engine.set_target_weights(dt, {CASH: 1.0})
return
best = max(scores, key=scores.get) # relative momentum
if scores[best] > 0: # absolute momentum
engine.set_target_weights(dt, {best: 1.0})
else:
engine.set_target_weights(dt, {CASH: 1.0})
ready = mom.dropna(how="all").index.min()
return on_day, ready4. Cross-sectional mean reversion
Buys the names whose recent returns are stretched to the downside. Note the sigma.replace(0, np.nan) guard and the monthly gate that keeps turnover survivable.
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", "IWM", "TLT"]
LOOKBACK = 20
ENTRY_Z = 1.0
def make_on_day(prices: pd.DataFrame):
cols = [c for c in ASSETS if c in prices.columns]
rets = prices[cols].pct_change()
mu = rets.rolling(LOOKBACK, min_periods=LOOKBACK).mean()
sigma = rets.rolling(LOOKBACK, min_periods=LOOKBACK).std()
z = (rets - mu) / sigma.replace(0, np.nan)
state = {"last": None}
def on_day(engine: PortfolioEngine, dt: pd.Timestamp) -> None:
if z.loc[dt].isna().all():
return
key = (dt.year, dt.month)
if state["last"] == key:
return
state["last"] = key
# Buy recent losers (negative z), equal weight
picks = [s for s in cols if z.at[dt, s] < -ENTRY_Z]
w = 1.0 / len(picks) if picks else 0.0
engine.set_target_weights(dt, {s: w for s in picks})
ready = z.dropna(how="all").index.min()
return on_day, ready5. Risk parity with a volatility budget
Inverse-volatility weights, then a second scaling step that caps the whole book's estimated volatility and parks the remainder in cash.
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", "TLT", "GLD", "DBC"]
VOL_LOOKBACK = 63
TARGET_VOL = 0.10
def make_on_day(prices: pd.DataFrame):
cols = [c for c in ASSETS if c in prices.columns]
rets = prices[cols].pct_change()
vol = rets.rolling(VOL_LOOKBACK, min_periods=VOL_LOOKBACK).std() * np.sqrt(252)
state = {"last": None}
def on_day(engine: PortfolioEngine, dt: pd.Timestamp) -> None:
key = (dt.year, dt.month)
if state["last"] == key:
return
state["last"] = key
inv = {s: 1.0 / float(vol.at[dt, s]) for s in cols
if pd.notna(vol.at[dt, s]) and vol.at[dt, s] > 1e-8}
total = sum(inv.values())
if total <= 0:
return
weights = {s: v / total for s, v in inv.items()}
# Scale the whole book to a volatility budget, keep rest in cash
book_vol = sum(weights[s] * float(vol.at[dt, s]) for s in weights)
scale = min(1.0, TARGET_VOL / book_vol) if book_vol > 1e-8 else 0.0
engine.set_target_weights(dt, {s: w * scale for s, w in weights.items()})
ready = vol.dropna(how="all").index.min()
return on_day, ready6. Template plus a market filter
Delegate the hard part to a factory, then override its output when a regime filter says risk off.
from backtest.templates import make_momentum_rotation
ASSETS = ["XLK", "XLF", "XLE", "XLV", "XLI", "XLP", "XLU", "XLY", "BIL"]
def make_on_day(prices: pd.DataFrame):
base_on_day, ready = make_momentum_rotation(
prices, ASSETS, {"lookback": 126, "top_n": 3}
)
# Add your own risk overlay on top of a template
spy_sma = prices["XLK"].rolling(200, min_periods=200).mean()
def on_day(engine: PortfolioEngine, dt: pd.Timestamp) -> None:
m = spy_sma.at[dt] if dt in spy_sma.index else np.nan
if pd.notna(m) and prices.at[dt, "XLK"] < m:
engine.set_target_weights(dt, {"BIL": 1.0}) # market filter: risk off
return
base_on_day(engine, dt)
return on_day, ready7. Local script, full pipeline
The same strategy body wired up to loader, engine, and metrics so it runs outside the sandbox.
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", "TLT"]
def make_on_day(prices: pd.DataFrame):
sma = prices[ASSETS].rolling(200, min_periods=200).mean()
def on_day(engine: PortfolioEngine, dt: pd.Timestamp) -> None:
risk_on = prices.at[dt, "SPY"] > sma.at[dt, "SPY"]
engine.set_target_weights(dt, {"SPY": 1.0} if risk_on else {"TLT": 1.0})
return on_day, sma.dropna(how="all").index.min()
if __name__ == "__main__":
prices = load_daily_prices(ASSETS, start="2010-01-01")
on_day, ready = make_on_day(prices)
engine = PortfolioEngine(prices, EngineConfig())
result = engine.run(on_day, start=ready)
print(compute_metrics(result.equity, trades_count=len(result.trades)))Where to go next
- Syntax cookbook — more indicator, ranking, and weighting expressions.
- Templates — exact parameter names for all nine factories.
- Errors — the fix for whatever your first edit breaks.
- Learn course — the concepts behind these patterns, with real market history.