Tutorial 1: First backtest
This walkthrough runs your first backtest end-to-end through mts1b-GPUbacktester. By the end you'll have:
- The 12 v1 services running locally (Docker Compose)
- A 10-year SPY momentum backtest result
- A grafana dashboard showing the equity curve
Time: ~30 minutes (most of it spent pulling images). Prerequisites: Docker 24+, 16 GB RAM, 20 GB disk, a CUDA-capable GPU if you want GPU acceleration (optional).
Step 1 — Install mts1b-deploy
pip install mts1b-deploy
mts1b-deploy --version
# mts1b-deploy 0.1.0
Step 2 — Configure with menuconfig
mts1b-deploy menuconfig
For this tutorial pick:
- Target:
docker-compose - Profile:
backtest-only(no live trading needed) - Asset classes:
equities - Optional services: leave all off
- Secrets source:
plain .env(we're local; do NOT use this in production)
Press <Save>. You'll have a mts1b.config file.
Step 3 — Install
mts1b-deploy install --config mts1b.config
This:
- Pulls Postgres 16, NATS 2.10, MinIO, Caddy, and the MTS1B images for foundation/platform/marketdata/quantkit/datalake/GPUbacktester.
- Brings up
docker-compose.ymlwith health checks. - Runs
mts1b-deploy migrateto apply schema migrations. - Seeds a tiny equities universe (SPY, QQQ, IWM, GLD, TLT) into the datalake.
Progress is streamed. Expected duration: 5-15 minutes depending on bandwidth.
Step 4 — Verify
mts1b-deploy status
Expected output:
✓ postgres port=5432 ready
✓ nats port=4222 ready
✓ minio port=9000 ready
✓ mts1b-foundation loaded pkg=0.1.0
✓ mts1b-platform /healthz ready
✓ mts1b-marketdata /healthz ready feeds=fmp
✓ mts1b-quantkit loaded pkg=0.1.0
✓ mts1b-datalake /healthz ready rows=22 (5 symbols × ~252 days × ~22 yrs)
✓ mts1b-GPUbacktester /healthz ready cuda=available
8/8 services healthy
If any are red, run mts1b-deploy logs <service> to see why.
Step 5 — Run the backtest
mts1b-deploy demo backtest-equities
What this does:
- Calls
mts1b-researchto register a simple momentum factor:def f_momentum_12_1(panel, h_long=252, h_skip=21):"""Classic 12-1 momentum: 12-month return excluding the most recent month."""close = panel.closeret_12 = close[-h_skip-1] / close[-h_long-h_skip-1] - 1return zscore_cross_sectional(ret_12) - Asks
mts1b-GPUbacktesterto run:- Universe:
equities-spy-qqq-iwm-gld-tlt - Period: 2014-01-01 → 2024-01-01
- Rebalance: monthly
- Cost: 5 bps per side
- Position sizing: Kelly with VolTarget 12% annualized
- Universe:
- Writes result to
data/backtests/demo-momentum-<timestamp>.parquet. - Generates a one-page HTML report via
mts1b-reportslibrary.
Expected output:
== mts1b-deploy demo backtest-equities ==
Registering factor: f_momentum_12_1
Universe: equities-spy-qqq-iwm-gld-tlt (5 symbols, 2014-01-01 to 2024-01-01)
Rebal: monthly | Cost: 5bps/side | Sizing: kelly + voltarget12
[backtest] Loading 11 years of daily bars... OK (~9700 rows)
[backtest] Computing factor (CUDA)... OK (1.2s)
[backtest] Walk-forward CV (252/63)... OK (40 folds, 1.8s)
[backtest] Portfolio simulation... OK (0.4s)
[backtest] Done: 3.3s total
Results:
Sharpe: 1.12
Calmar: 0.84
Max DD: -18.4%
Total return: +194.5%
CAGR: +11.4%
IC: 0.041 (t-stat: 4.2)
Result written: data/backtests/demo-momentum-1716496837.parquet
Report: http://localhost:8001/reports/demo-momentum-1716496837.html
Step 6 — Inspect the result
Open the report URL:
xdg-open http://localhost:8001/reports/demo-momentum-1716496837.html
# or
open http://localhost:8001/reports/demo-momentum-1716496837.html
You'll see:
- Equity curve (and benchmark = SPY buy-and-hold)
- Drawdown chart
- Monthly returns heatmap
- Rolling 252-day Sharpe
- Per-symbol contribution
Step 7 — Explore further
Tweak the factor in mts1b.config's strategy section:
strategies:
demo_momentum:
factor: f_momentum_12_1
params:
h_long: 126 # try 6-month momentum instead
h_skip: 5
sizing: kelly_voltarget_12
universe: equities-spy-qqq-iwm-gld-tlt
Then re-run:
mts1b-deploy demo backtest-equities
Step 8 — Programmatic API
For more control, drop into Python:
from mts1b_quantkit.factors import register, get
from mts1b_GPUbacktester import run_single
from mts1b_quantkit.metrics import sharpe, calmar, max_drawdown
@register("my_first_factor")
def my_first_factor(panel, h=63):
"""3-month momentum."""
close = panel.close
return zscore_cross_sectional(close[-1] / close[-h] - 1)
result = run_single(
factor=get("my_first_factor"),
params={"h": 63},
universe="equities-spy-qqq-iwm-gld-tlt",
start="2014-01-01",
end="2024-01-01",
rebal="monthly",
cost_bps=5,
)
print(f"Sharpe: {sharpe(result.returns):.2f}")
print(f"Calmar: {calmar(result):.2f}")
print(f"Max DD: {max_drawdown(result.returns):.2%}")
Cleanup
mts1b-deploy down # stop services, keep data
mts1b-deploy destroy # stop + delete data (irreversible)
What's next
- Tutorial 2: Paper trading — wire the same factor to a paper broker
- Tutorial 3: Custom strategy — write + walk-forward-validate your own
- Concept: Factor system — deeper dive on factor API