Migrating from vectorbt
You have a vectorbt backtest and want to migrate to MTS1B for live trading.
High-level
| vectorbt | MTS1B |
|---|---|
vbt.Portfolio.from_signals(...) | run_single(factor=...) |
vbt.IF (indicator factory) | @register("f_X") factor function |
data.run(...) | lake.build_panel(...) |
signals.dropdown_above(...) | factor returning thresholded ranking |
portfolio.stats() | BacktestResult metrics |
vbt.Portfolio.live(...) | mts1b-oms + mts1b-research/live_executor |
GPU via cupy | GPU via mts1b-GPUbacktester |
Example: simple cross-sectional momentum port
In vectorbt
import vectorbt as vbt
import numpy as np
import pandas as pd
# Data
price = vbt.YFData.download(["AAPL", "MSFT", "GOOG", "META"],
start="2014-01-01").get("Close")
# Momentum factor (12-1)
returns_12_1 = price.shift(21) / price.shift(252+21) - 1
signal = returns_12_1.rank(axis=1, ascending=False) <= 2 # top 2 each day
# Portfolio
portfolio = vbt.Portfolio.from_signals(
close=price,
entries=signal,
exits=~signal,
init_cash=100_000,
fees=0.001,
freq="1D",
)
print(portfolio.stats())
In MTS1B
import numpy as np
from mts1b_quantkit.factors import register, zscore_cross_sectional
from mts1b_foundation.market_data import UniversePanel
from mts1b_datalake import lake
# Factor
@register("f_momentum_12_1")
def f_momentum_12_1(panel: UniversePanel, /, h_long: int = 252, h_skip: int = 21) -> np.ndarray:
close = panel.close
ret = close[-h_skip-1] / close[-h_long-h_skip-1] - 1
return zscore_cross_sectional(ret)
# Panel from data lake
panel = lake.build_panel(
universe=["AAPL", "MSFT", "GOOG", "META"],
interval="daily",
start="2014-01-01", end="2024-01-01",
)
# Backtest
from mts1b_GPUbacktester import run_single
from mts1b_quantkit.factors import get
result = run_single(
factor=get("f_momentum_12_1"),
params={"h_long": 252, "h_skip": 21},
universe=["AAPL", "MSFT", "GOOG", "META"],
start="2014-01-01", end="2024-01-01",
rebal="daily",
sizing={"method": "equal_weight_long", "n": 2, "gross": 1.0},
cost_bps=10,
)
print(f"Sharpe: {result.sharpe:.2f}")
print(f"CAGR: {result.cagr:.2%}")
print(f"MaxDD: {result.max_drawdown:.2%}")
Key differences
vectorbt is fully vectorized; MTS1B is bar-driven
vectorbt creates the full (T, A) matrix of all decisions at once and post-processes. MTS1B processes bars sequentially (with vectorized math inside).
For pure backtest performance:
- vectorbt is slightly faster on small universes
- MTS1B is faster on large universes (GPU)
For live trading:
- vectorbt has no native live execution (vectorbt PRO has limited live support, paid)
- MTS1B has full live OMS + risk + treasury
Indicator factory → factor function
# vectorbt
my_ind = vbt.IndicatorFactory(
class_name="MyIndicator",
input_names=["price"],
param_names=["window"],
output_names=["sma"],
).from_apply_func(lambda price, window: price.rolling(window).mean())
result = my_ind.run(price, window=20)
print(result.sma)
# MTS1B
@register("f_sma")
def f_sma(panel: UniversePanel, /, window: int = 20) -> np.ndarray:
close = panel.close
return np.array([
close[max(0, t-window):t].mean(axis=0) for t in range(len(close))
])
from_signals → factor + sizer
vectorbt's Portfolio.from_signals(entries, exits) is binary in/out. MTS1B factors return continuous rankings; the sizer turns them into weights.
# vectorbt — binary signals
portfolio = vbt.Portfolio.from_signals(close=price, entries=long_entries, exits=long_exits)
# MTS1B — continuous ranking + sizer
@register("f_my_signal")
def f_my_signal(panel, /, **params):
raw = ... # your logic
return zscore_cross_sectional(raw)
# Sizer applies in run_single via sizing={"method": "equal_weight_ls", "n_long": 5, "n_short": 5}
If you have binary signals, set gross=1.0 and use equal_weight_long with n equal to your active position count.
Live trading transition
vectorbt → live requires manual wiring (or vectorbt PRO). MTS1B has the full chain:
# After backtest validation
await StrategyRegistry.register(
StrategySpec(
strategy_id="vectorbt_port_v1",
factor="f_my_signal",
params={},
universe="us-large-cap",
rebal="daily",
sizing="kelly_voltarget_12",
cost_bps=10,
enabled=True,
)
)
# Spin up live executor
await live_executor.run(fund_id="paper-vbt-port",
strategies_from="registry")
Stats
# vectorbt
portfolio.stats()
# Includes: Sharpe, Sortino, MaxDD, Calmar, etc
# MTS1B
result = run_single(...)
result.sharpe
result.sortino # via mts1b_quantkit.metrics.sortino(result.returns)
result.max_drawdown
result.calmar
What MTS1B does better
- Live trading from day 1 (vectorbt requires PRO)
- Per-strategy risk envelopes
- Multi-fund treasury
- 17-pattern dedupe contract (no scattered HRP / Sharpe variants)
- 56 doc pages + 10 reference pages
What vectorbt does better
- Faster on small/medium universes (very-tight inner loops)
- Built-in plotly visualization
- More "out of the box" indicators (700+)
- Smaller install footprint
Migration checklist
- Convert each indicator into a
@register("f_X")factor function - Replace
Portfolio.from_signalswithrun_single - Replace data fetch with
lake.build_panel - If you used GPU (cupy directly), use
mts1b-GPUbacktesterinstead - If you want live trading, register strategy + use
live_executor - Set up
FundConfig+RiskEnvelope - Test on paper