Skip to main content

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:

  1. Pulls Postgres 16, NATS 2.10, MinIO, Caddy, and the MTS1B images for foundation/platform/marketdata/quantkit/datalake/GPUbacktester.
  2. Brings up docker-compose.yml with health checks.
  3. Runs mts1b-deploy migrate to apply schema migrations.
  4. 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:

  1. Calls mts1b-research to 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.close
    ret_12 = close[-h_skip-1] / close[-h_long-h_skip-1] - 1
    return zscore_cross_sectional(ret_12)
  2. Asks mts1b-GPUbacktester to 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
  3. Writes result to data/backtests/demo-momentum-<timestamp>.parquet.
  4. 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