Features
Configuration and persistence
Save any research object with compression, reload it fast, and keep settings in files
How do you save research state and make it reproducible? VBT can save any of its objects, from data to a 10,000-column portfolio, to one file with optional compression, reload it for analysis, and keep settings in configuration files instead of scattered through code.
import time
from itertools import product
data = vbt.YFData.pull("BTC-USD", start="2020-01-01", end="2025-01-01")
pairs = list(product(range(2, 102), range(105, 305, 2)))
fast = vbt.MA.run(data.close, window=[f for f, _ in pairs], short_name="fast")
slow = vbt.MA.run(data.close, window=[s for _, s in pairs], short_name="slow")
entries = fast.ma_crossed_above(slow)
exits = fast.ma_crossed_below(slow)
pf = vbt.PF.from_signals(data, entries, exits, fees=0.001)
rows = {}
for compression in [None, "blosc2", "gzip", "lzma"]:
start = time.perf_counter()
path = pf.save("sweep", compression=compression)
save_s = time.perf_counter() - start
start = time.perf_counter()
loaded = vbt.PF.load(path)
load_s = time.perf_counter() - start
rows[str(compression)] = dict(
file=str(path),
size=vbt.file_size(path),
save_s=round(save_s, 2),
load_s=round(load_s, 2),
)
pd.DataFrame(rows).T file size save_s load_s
None sweep.pickle 11.7 MB 0.01 0.0
blosc2 sweep.pickle.blosc2 2.2 MB 0.06 0.09
gzip sweep.pickle.gzip 1.9 MB 1.15 0.01
lzma sweep.pickle.lzma 1.0 MB 1.93 0.06loaded.wrapper.shape, np.allclose(loaded.total_return, pf.total_return)((1827, 10000), True)Every file reloads as a full portfolio with all 10,000 strategies, ready for metrics and charts. Measured on an Apple M3, blosc2 cut the file to a fifth of its size and still saved and loaded in under 0.1 seconds. lzma made the smallest file but took about two seconds to write.
Saving and loading
Every VBT object, including data, indicators, portfolios, and splitters, has save and load, and
vbt.save and vbt.load handle any Python object, such as a dictionary of portfolios. Only an
object's state is saved, not its class, so files load with the current version of VBT, and renamed
or moved classes can be mapped to their new names when loading older files. VBT includes
reconstruction hooks for loading older objects when class arguments change. Use matching package
versions when moving an experiment between machines, or register a state conversion when an older
object needs it.
- Compression: zip, gzip, bz2, and lzma, plus lz4, blosc, and blosc2 when installed.
- Formats: pickle by default, or readable
.cfg,.yaml, and.tomlfiles withfile_format, which round-trip back to the same object. - Containers:
vbt.pdictis a dictionary that saves and loads like any VBT object. - Data updates:
vbt.CSVDataSaver,vbt.HDFDataSaver, andvbt.DuckDBDataSaverkeep a dataset current on disk, as the Data storage page shows.
Keep an experiment together
Save the inputs, parameters, and result in one dictionary so reopening a backtest also tells you what produced it. The objects keep their own shapes and labels, so prices and parameterized indicator outputs can sit together without being forced into one table:
import os
import tempfile
close = pd.Series([100.0, 105.0, 103.0], index=pd.date_range("2025-01-01", periods=3))
parameters = dict(fees=0.001, init_cash=1000)
pf = vbt.PF.from_holding(close, **parameters)
research = vbt.pdict(close=close, parameters=parameters, portfolio=pf, version=vbt.__version__)
with tempfile.TemporaryDirectory() as directory:
path = research.save(os.path.join(directory, "research"), compression="gzip")
restored = vbt.pdict.load(path)
print(list(restored))['close', 'parameters', 'portfolio', 'version']print(restored["parameters"]){'fees': 0.001, 'init_cash': 1000}print(restored["close"].equals(close), np.isclose(restored["portfolio"].total_return, pf.total_return))True TrueThe temporary directory keeps this example self-contained. Use a project path to keep the file between sessions. Add indicator outputs, signals, or your own notes to the same dictionary, then load the portfolio for new charts and metrics without repeating the backtest.
Restart data updates
A saved research object and a growing market-data store serve different purposes. To restart a
data-saving job, load the stored data, create a new saver around it, and resume scheduled updates
with init_save=False. This continues the save cycle without writing the initial dataset again.
Data storage covers the storage choices, and
Workflow automation covers scheduling.
Settings
vbt.settings holds the defaults of the whole package, grouped by area, such as
vbt.settings.portfolio["fees"] or vbt.settings.plotting["default_theme"]. Change a value once
and later calls that use that default pick it up. Explicit call arguments let you override it for
one experiment. To make the change permanent, put it in a vbt.cfg, vbt.yml, or vbt.toml file
in the working directory, or point the VBT_SETTINGS_PATH environment variable at a file. VBT
applies it on import, as the Configuration files highlight below shows. A
.env file in the working directory is loaded on import as well, which keeps API keys out of code.
You can save just one settings section, such as the portfolio defaults, and reload it with
load_update. Use reset() on that section to return to its initial settings. This makes it easy
to reuse a project's fees, starting capital, and plotting preferences across notebooks.
Configured objects and templates
Most VBT objects are immutable: a portfolio, indicator, or data instance does not change after it is
created. replace returns a copy with some arguments changed, such as pf.replace(init_cash=500),
and the original stays untouched, so earlier results can always be inspected. replace rebuilds the
object from its configuration. It does not rerun a strategy's order decisions, so test new trading
rules with a new simulation. Immutability is a convention, not a copy: inputs are stored as passed,
so changing a DataFrame in place after creating an object from it can leave the object out of step
with results it has already cached. Copy the input or create the object again.
Settings and arguments are merged with vbt.merge_dicts, which combines nested dictionaries key by
key, so an argument you pass overrides only that key of the default. Pass vbt.unsetkey as a value
to remove a default key, and wrap a dictionary in vbt.atomic_dict to replace it whole.
Templates delay a value until the information it needs exists. vbt.Rep("name") is replaced by a
value from the current context, vbt.RepEval("expression") evaluates an expression in it, and
vbt.RepFunc(func) calls a function with it. The
Portfolio optimization page uses one to give each
rebalancing window its slice of returns, as the Templates highlight below explains.
Cookbook
The members-only Persistence and Configuration cookbook pages collect short recipes for saving objects and changing settings.
✅ 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✅ Serialized VBT objects can sometimes use a lot of disk space. With this update, VBT now supports a variety of compression algorithms to make files as light as possible! 🪶
data = vbt.RandomOHLCData.pull(
"RAND",
start="2022",
end="2023",
timeframe="1 minute",
seed=42
)
file_path = data.save()
print(vbt.file_size(file_path))21.0 MBfile_path = data.save(compression="blosc")
print(vbt.file_size(file_path))13.3 MB✅ If your pipeline does not need accessors, Plotly graphs, or most other optional features, you can disable the auto-import feature entirely to reduce VBT's loading time to under a second ⏳
[importing]
auto_import = Falsestart = utc_time()
from vectorbtpro import *
end = utc_time()
end - start0.580937910079956✅ Just like machine learning models, every native VBT object can be serialized and saved to a binary file. It has never been easier to share data and insights! Another benefit is that only the actual content of each object is serialized, not its class definition, so the loaded object always uses the most up-to-date class definition. There is also special logic implemented to help you "reconstruct" objects if VBT introduces any breaking API changes 🏗️
data = vbt.YFData.pull("BTC-USD", start="2022-01-01", end="2022-06-01")
def backtest_month(close):
return vbt.PF.from_random_signals(close, n=10, seed=42)
month_pfs = data.close.resample(vbt.offset("M")).apply(backtest_month)
month_pfsDate
2022-01-01 00:00:00+00:00 Portfolio(\n wrapper=ArrayWrapper(\n ...
2022-02-01 00:00:00+00:00 Portfolio(\n wrapper=ArrayWrapper(\n ...
2022-03-01 00:00:00+00:00 Portfolio(\n wrapper=ArrayWrapper(\n ...
2022-04-01 00:00:00+00:00 Portfolio(\n wrapper=ArrayWrapper(\n ...
2022-05-01 00:00:00+00:00 Portfolio(\n wrapper=ArrayWrapper(\n ...
Freq: MS, Name: Close, dtype: objectvbt.save(month_pfs, "month_pfs")
month_pfs = vbt.load("month_pfs")
month_pfs.apply(lambda pf: pf.total_return)Date
2022-01-01 00:00:00+00:00 -0.083672
2022-02-01 00:00:00+00:00 0.173909
2022-03-01 00:00:00+00:00 -0.006249
2022-04-01 00:00:00+00:00 -0.057868
2022-05-01 00:00:00+00:00 0.019209
Freq: MS, Name: Close, dtype: float64✅ It is easy to extend classes, but since VBT revolves around functions, how do we enhance them or change their workflow? The easiest way is to introduce a small function (i.e., callback) that the user can provide and that the main function calls at some point. However, this would require the main function to know what arguments to pass to the callback and how to handle its outputs. Here is a better idea: allow most arguments of the main function to become callbacks, then execute those to obtain their actual values. These arguments are called "templates" and this process is known as "substitution". Templates are especially useful when some arguments (such as arrays) should be built only once all required information is available, for example, when other arrays have already been broadcast. Each substitution opportunity has its own identifier so you can control when a template should be substituted. In VBT, templates are first-class citizens and are integrated into most functions for unmatched flexibility! 🧞
def resample_apply(index, by, apply_func, *args, template_context={}, **kwargs):
grouper = index.vbt.get_grouper(by)
results = {}
with vbt.ProgressBar() as pbar:
for group, group_idxs in grouper:
group_index = index[group_idxs]
context = {"group": group, "group_index": group_index, **template_context}
final_apply_func = vbt.substitute_templates(apply_func, context, eval_id="apply_func")
final_args = vbt.substitute_templates(args, context, eval_id="args")
final_kwargs = vbt.substitute_templates(kwargs, context, eval_id="kwargs")
results[group] = final_apply_func(*final_args, **final_kwargs)
pbar.update()
return pd.Series(results)
data = vbt.YFData.pull(["BTC-USD", "ETH-USD"], missing_index="drop")
resample_apply(
data.index, "Y",
lambda x, y: x.corr(y),
vbt.RepEval("btc_close[group_index]"),
vbt.RepEval("eth_close[group_index]"),
template_context=dict(
btc_close=data.get("Close", "BTC-USD"),
eth_close=data.get("Close", "ETH-USD")
)
)2017 0.808930
2018 0.897112
2019 0.753659
2020 0.940741
2021 0.553255
2022 0.975911
2023 0.974914
Freq: A-DEC, dtype: float64Related pages
- Backtesting › Backtesting engineSimulate orders, signals, and callbacks across many assets and parameters at once
- Data › Data storage and databasesSave and query market data in files, DuckDB, SQL databases, and ArcticDB
- Developer toolsSearch the API, read source, draw reference graphs, and extend classes with factories
- Multidimensional researchRun assets, parameters, and strategies as labeled columns, then index and stack them
Copyright © 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.