Migrating from backtrader
You have a working backtrader strategy and want to run it on MTS1B. Here's the mapping.
High-level mapping
| backtrader | MTS1B equivalent |
|---|---|
bt.Strategy subclass | A factor (@register("f_X")) + a strategy spec (StrategyRegistry.register(...)) |
self.buy() / self.sell() | mts1b-oms.submit(Order(...)) |
bt.Cerebro | mts1b-GPUbacktester.run_single |
bt.feeds.PandasData | UniversePanel from mts1b-datalake.build_panel |
bt.indicators.SMA(...) | factor function with rolling computation |
bt.Analyzer.SharpeRatio | mts1b_quantkit.metrics.sharpe |
cerebro.broker | mts1b-brokers.paper.PaperClient (backtest) or live broker |
cerebro.broker.setcash(...) | FundConfig.target_nav_usd |
cerebro.broker.setcommission(...) | cost_bps parameter to run_single |
Example: SMA crossover
In backtrader
import backtrader as bt
class SMACross(bt.Strategy):
params = (("p_fast", 20), ("p_slow", 50))
def __init__(self):
sma_fast = bt.indicators.SMA(period=self.p.p_fast)
sma_slow = bt.indicators.SMA(period=self.p.p_slow)
self.crossover = bt.indicators.CrossOver(sma_fast, sma_slow)
def next(self):
if not self.position:
if self.crossover > 0:
self.buy()
elif self.crossover < 0:
self.close()
cerebro = bt.Cerebro()
cerebro.addstrategy(SMACross)
data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data)
cerebro.broker.setcash(100000)
cerebro.broker.setcommission(commission=0.001)
cerebro.run()
print(cerebro.broker.getvalue())
In MTS1B
import numpy as np
from mts1b_quantkit.factors import register
@register("f_sma_cross")
def f_sma_cross(panel, /, p_fast: int = 20, p_slow: int = 50):
"""Long when fast SMA > slow SMA; flat otherwise."""
close = panel.close # (T, A)
sma_fast = np.array([
close[max(0, t - p_fast):t].mean(axis=0) for t in range(len(close))
])
sma_slow = np.array([
close[max(0, t - p_slow):t].mean(axis=0) for t in range(len(close))
])
signal = (sma_fast > sma_slow).astype(float) * 2.0 - 1.0 # +1 / -1
return signal
# Backtest
from mts1b_GPUbacktester import run_single
from mts1b_quantkit.factors import get
result = run_single(
factor=get("f_sma_cross"),
params={"p_fast": 20, "p_slow": 50},
universe="us-large-cap",
start="2014-01-01", end="2024-01-01",
rebal="daily",
sizing={"method": "equal_weight_long", "gross": 1.0},
cost_bps=10,
)
print(f"Sharpe: {result.sharpe:.2f}")
print(f"Final NAV: ${100000 * (1 + result.cagr) ** 10:,.0f}")
Key differences
1. Factor functions are cross-sectional
backtrader runs ONE strategy per symbol in parallel. MTS1B factors operate on (T, A) panels — they produce CROSS-SECTIONAL rankings (z-score across the universe).
This makes MTS1B better at long/short, pair trades, and basket strategies; less natural for "trade SPY in isolation".
If you want backtrader-style single-symbol behavior, set universe=["SPY"] (universe of one).
2. Position state lives outside the strategy
backtrader's self.position is per-strategy state. In MTS1B:
mts1b-omsowns positions- Factor output is just target WEIGHTS
- The OMS computes the delta vs current positions and emits orders
You don't track "are we long?" in the factor — you set the target weight (-1, 0, or +1 in the SMA example) and the OMS handles the rest.
3. Real risk gates
backtrader has minimal risk controls (commission, slippage). MTS1B has 7 gates including drawdown halt, position-pct cap, broker allowlist, etc.
When migrating, configure a RiskEnvelope for your fund to match what your backtrader strategy implicitly assumed (no drawdown halt, unlimited position size).
4. Multiple funds, multiple strategies
In backtrader, one Cerebro = one strategy. In MTS1B:
- A fund has a NAV and a broker
- Multiple strategies can write into one fund (sleeves)
- Composer (
mts1b-portfolio.multi_sleeve.compose) combines them
You can model "backtrader-style" by creating one fund per strategy.
Where backtrader is better
| Use case | Recommendation |
|---|---|
| Single-symbol strategy, simple SMA cross / RSI | Stay with backtrader; MTS1B is overkill |
| Event-driven (per-tick logic) | Stay with backtrader; MTS1B is bar-driven |
| Live trading with one symbol | Stay with backtrader |
Where MTS1B is better
| Use case | Recommendation |
|---|---|
| Cross-sectional / multi-asset | MTS1B |
| Large universes (>100 symbols) | MTS1B with GPU backend |
| Parameter sweeps (>1k combos) | MTS1B with ladder |
| Multi-fund treasury allocation | MTS1B |
| Production risk gates + halt | MTS1B |
| Audit chain for compliance | MTS1B |
Migration checklist
- Convert backtrader Strategy into a factor function (cross-sectional output)
- Map indicators (SMA, RSI, MACD) to factor primitives in
mts1b_quantkit.factors - Wrap the factor with
@register("f_your_name") - Build a
UniversePanelfrom your data source - Run
mts1b_GPUbacktester.run_singlefor backtest validation - Run
mts1b_quantkit.cv.walk_forwardfor OOS validation - Create a
FundConfig+RiskEnvelopematching your trading assumptions - Register the strategy with
mts1b_research.strategies.StrategyRegistry - Test on paper broker (
broker="paper") before live
See also
- Tutorial: First backtest
- Tutorial: Custom strategy
mts1b-quantkit.factors- Migrating from zipline — similar architecture, more code to port