Quant Buffet API
Templates
The nine make_* factories in backtest.templates, with exact parameter names and rebalance cadence.
`backtest.templates` provides reusable **make_*** factories that return (on_day, ready) — the same contract your own make_on_day must satisfy. Import one and delegate instead of rewriting a common pattern.
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})Every factory has the identical signature: make_x(prices, assets, params) -> (on_day, ready). params is a plain dict and unknown keys are ignored silently, which makes a misspelled parameter name invisible — the run succeeds using the default.
Complete parameter reference
| Factory | Parameters (default) | Rebalance | Behaviour |
|---|---|---|---|
make_sma_trend | sma_days (200) | Monthly | Equal weight across assets trading above their own SMA; cash when none qualify. |
make_dual_ma | fast (50), slow (200) | Daily | Equal weight across assets whose fast MA exceeds their slow MA. |
make_abs_momentum | lookback (252) | Monthly | Equal weight across assets with a positive trailing return. |
make_dual_momentum | lookback (252), cash_symbol ("BIL") | Monthly | Single best performer if its return is positive, otherwise 100% cash. |
make_momentum_rotation | lookback (126), top_n (1), invert (falsy) | Monthly | Top-N by trailing return; negative names are dropped unless that would empty the book. |
make_equal_weight | rebalance ("once" for buy-and-hold) | Monthly, or once | Equal weight across every asset that has a price that day. |
make_mean_reversion | lookback (20), entry_z (-1.0), exit_z (0.0) | Daily | Stateful: enters a name at or below entry_z, exits at or above exit_z. |
make_vol_target | target_vol (0.10), vol_lookback (63) | Monthly | Each asset gets min(1, target / vol); normalised down if the total exceeds 1. |
make_risk_parity | vol_lookback (63) | Monthly | Inverse-volatility weights, normalised to sum to 1. |
How each factory computes `ready`
| Factory | ready is |
|---|---|
make_sma_trend, make_dual_ma | First date any SMA (the slow one) is non-NaN |
make_abs_momentum, make_dual_momentum, make_momentum_rotation | First date any trailing return is non-NaN |
make_mean_reversion | First date any z-score is non-NaN |
make_vol_target, make_risk_parity | First date any volatility estimate is non-NaN |
make_equal_weight | First date at least half the book has prices |
Dispatching by name
from backtest.templates import TEMPLATES, build_strategy
sorted(TEMPLATES)
# ['abs_momentum', 'dual_ma', 'dual_momentum', 'equal_weight',
# 'mean_reversion', 'momentum_rotation', 'risk_parity',
# 'sma_trend', 'vol_target']
ASSETS = ["SPY", "EFA", "TLT", "GLD", "BIL"]
def make_on_day(prices: pd.DataFrame):
# build_strategy(name, prices, assets, params) -> (on_day, ready)
return build_strategy("dual_momentum", prices, ASSETS,
{"lookback": 252, "cash_symbol": "BIL"})build_strategy(template, prices, assets, params) looks the name up in the TEMPLATES dict and raises ValueError: Unknown template: … for anything unrecognised. This is how the batch catalog runner drives all nine factories from JSON configuration.
Wrapping a template with your own overlay
Because factories return a plain callable, you can call one and then intercept its decisions — the cleanest way to add a market filter, a volatility cap, or a cash floor to a proven pattern.
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, readyCustom parameters
from backtest.templates import make_abs_momentum
ASSETS = ["SPY", "EFA", "EEM", "TLT", "GLD", "BIL"]
def make_on_day(prices: pd.DataFrame):
# make_abs_momentum reads only 'lookback' — top_n / cash_symbol are ignored
return make_abs_momentum(prices, ASSETS, {"lookback": 126})