Features

Workflow automation

Run repeatable research tasks through configuration, iteration, progress, and the CLI

CLI

✅ VBT now ships with a Typer-based command-line interface that lets you chat, search, and invoke MCP tools directly from the terminal. No Python script required. After installing the package, you can run the vbt command in your terminal to see all available commands.

Ask VBT a question from the terminal
vbt chat "How to backtest a weekly rebalancing strategy?"  

The first time you run most of these commands, it may take a while to prepare documents. However, most of the preparation steps are cached and stored, so future calls will be much faster and will not require repeating the process.

Configuration files

✅ VBT extends popular configuration formats (INI, YAML, TOML) to define its own configuration format that allows users to save, introspect, modify, and load any complex in-house object. The main advantages of this format are readability and round-tripping: any object can be encoded and then decoded back without loss of information. The main features include nested structures, references, literal parsing, and evaluation of arbitrary Python expressions. Additionally, you can now create a configuration file for VBT and place it in the working directory— it will be used to update the default settings whenever the package is imported.

Define global settings in vbt.cfg
[plotting]
default_theme = "dark"

[portfolio]
init_cash = 5000

[data.custom.binance.client_config]
api_key = "<YOUR_API_KEY>"
api_secret = "<YOUR_API_SECRET>"

[data.custom.ccxt.exchanges.binance.exchange_config]
apiKey = &data.custom.binance.client_config.api_key
secret = &data.custom.binance.client_config.api_secret
Verify that the settings have been loaded correctly
from vectorbtpro import *

vbt.settings.portfolio["init_cash"]
5000

Iterated decorator

✅ Thinking about parallelizing a for-loop? No need to hesitate—VBT has a decorator for that.

Emulate a parallelized nested loop to get Sharpe by year and month
import calendar

data = vbt.YFData.pull("BTC-USD")

@vbt.iterated(over_arg="year", merge_func="column_stack", engine="pathos")  
@vbt.iterated(over_arg="month", merge_func="concat")  
def get_year_month_sharpe(data, year, month):  
    mask = (data.index.year == year) & (data.index.month == month)
    if not mask.any():
        return np.nan
    year_returns = data.loc[mask].returns
    return year_returns.vbt.returns.sharpe_ratio()

years = data.index.year.unique().sort_values().rename("year")
months = data.index.month.unique().sort_values().rename("month")
sharpe_matrix = get_year_month_sharpe(
    data,
    years,
    {calendar.month_abbr[month]: month for month in months},  
)
sharpe_matrix.transpose().vbt.heatmap(
    trace_kwargs=dict(colorscale="RdBu", zmid=0),
    yaxis=dict(autorange="reversed")
).show()
Monthly Sharpe ratio heatmap for Bitcoin grouped by year Figure data (JSON)

Tasks

✅ Testing multiple parameter combinations usually involves using the @vbt.parameterized decorator. But what if you want to test entirely uncorrelated configurations or even different functions? The latest addition to VBT lets you execute any sequence of unrelated tests in parallel by assigning each test to a task.

Simulate SL, TSL, and TP parameters in three separate processes and compare their expectancy
data = vbt.YFData.pull("BTC-USD")

task1 = vbt.Task(  
    vbt.PF.from_random_signals,
    data,
    n=100, seed=42,
    sl_stop=vbt.Param(np.arange(1, 51) / 100)
)
task2 = vbt.Task(
    vbt.PF.from_random_signals,
    data,
    n=100, seed=42,
    tsl_stop=vbt.Param(np.arange(1, 51) / 100)
)
task3 = vbt.Task(
    vbt.PF.from_random_signals,
    data,
    n=100, seed=42,
    tp_stop=vbt.Param(np.arange(1, 51) / 100)
)
pf1, pf2, pf3 = vbt.execute([task1, task2, task3], engine="pathos")  

fig = pf1.trades.expectancy.rename("SL").vbt.plot()
pf2.trades.expectancy.rename("TSL").vbt.plot(fig=fig)
pf3.trades.expectancy.rename("TP").vbt.plot(fig=fig)
fig.show()
Expectancy across stop-loss, trailing stop-loss, and take-profit levels Figure data (JSON)

Nested progress bars

✅ Progress bars are now aware of each other. When a new progress bar starts, it checks whether another progress bar with the same identifier has already finished its task. If so, the new progress bar will close itself and delegate its progress to the existing one.

Display progress of three parameters using nested progress bars
symbols = ["BTC-USD", "ETH-USD"]
fast_windows = range(5, 105, 5)
slow_windows = range(5, 105, 5)
sharpe_ratios = dict()

with vbt.ProgressBar(total=len(symbols), bar_id="pbar1") as pbar1:  
    for symbol in symbols:
        pbar1.set_description(dict(symbol=symbol), refresh=True)
        data = vbt.YFData.pull(symbol)

        with vbt.ProgressBar(total=len(fast_windows), bar_id="pbar2") as pbar2:  
            for fast_window in fast_windows:
                pbar2.set_description(dict(fast_window=fast_window), refresh=True)

                with vbt.ProgressBar(total=len(slow_windows), bar_id="pbar3") as pbar3:  
                    for slow_window in slow_windows:
                        if fast_window < slow_window:
                            pbar3.set_description(dict(slow_window=slow_window), refresh=True)
                            fast_sma = data.run("talib_func:sma", fast_window)
                            slow_sma = data.run("talib_func:sma", slow_window)
                            entries = fast_sma.vbt.crossed_above(slow_sma)
                            exits = fast_sma.vbt.crossed_below(slow_sma)
                            pf = vbt.PF.from_signals(data, entries, exits)
                            sharpe_ratios[(symbol, fast_window, slow_window)] = pf.sharpe_ratio
                        pbar3.update()

                pbar2.update()

        pbar1.update()
Symbol 2/2
Fast window 20/20
Slow window 20/20
sharpe_ratios = pd.Series(sharpe_ratios)
sharpe_ratios.index.names = ["symbol", "fast_window", "slow_window"]
sharpe_ratios
symbol   fast_window  slow_window
BTC-USD  5            10             1.063616
                      15             1.218345
                      20             1.273154
                      25             1.365664
                      30             1.394469
                                          ...
ETH-USD  80           90             0.582995
                      95             0.617568
         85           90             0.701215
                      95             0.616037
         90           95             0.566650
Length: 342, dtype: float64

✅ New profiling tools help you measure the execution time and memory usage of any code block 🧰

Profile getting the Sharpe ratio of a random portfolio
data = vbt.YFData.pull("BTC-USD")

with (
    vbt.Timer() as timer,
    vbt.MemTracer() as mem_tracer
):
    print(vbt.PF.from_random_signals(data.close, n=100, seed=42).sharpe_ratio)
1.0410760501518814
print(timer.elapsed())
74.15 milliseconds
print(mem_tracer.peak_usage())
459.7 kB

Copyright © 20212026 Oleg Polakow. All rights reserved.

Site content and documentation are provided for using and evaluating VectorBT PRO and for educational purposes. Any other use, including building or supporting competing products or services, requires prior written consent.