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.

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

2. The same idea via a template

Three lines instead of twenty, with identical mechanics. Prefer this once you trust the pattern.

python
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.

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", "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, ready

4. 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.

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", "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, ready

5. 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.

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", "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, ready

6. Template plus a market filter

Delegate the hard part to a factory, then override its output when a regime filter says risk off.

python
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, ready

7. Local script, full pipeline

The same strategy body wired up to loader, engine, and metrics so it runs outside the sandbox.

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", "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.