Features
Workflow automation
Run repeatable research tasks through configuration, iteration, progress, and the 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.
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.
✅ 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.
[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_secretfrom vectorbtpro import *
vbt.settings.portfolio["init_cash"]5000✅ Thinking about parallelizing a for-loop? No need to hesitate—VBT has a decorator for that.
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()✅ 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.
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()✅ 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.
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()sharpe_ratios = pd.Series(sharpe_ratios)
sharpe_ratios.index.names = ["symbol", "fast_window", "slow_window"]
sharpe_ratiossymbol 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 🧰
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.0410760501518814print(timer.elapsed())74.15 millisecondsprint(mem_tracer.peak_usage())459.7 kBCopyright © 2021–2026 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.