Features

Configuration and persistence

Configure, format, serialize, compress, and reload reusable research objects

Reference graphs

✅ When analyzing complex codebases like VBT, it can be challenging to keep track of all the interdependencies between modules, classes, and functions. To address this, VBT now includes a functionality that can index the source code of any codebase for object references and generate reference graphs from any specified entry point. These graphs provide a visual representation of how different components relate to each other, making it easier to understand the overall structure and flow of the code.

Generate a reference graph for Pandas and display it in a new browser tab
ref_index = vbt.RefIndex(container_kinds=["module", "class"], incl_modules="pandas")
ref_graph = ref_index.build_graph("pandas")
ref_graph.plot(
    interactive="dash",
    to_dash_kwargs=dict(fit_to_window=True),
    dash_run_kwargs=dict(jupyter_mode="tab")
)
Reference graph of Pandas modules, classes, callables, and data dependencies Figure data (JSON)

Online explorer

Explore the VBT's reference graph in the API → Reference graph.

Annotations

✅ When writing a function, you can specify the meaning of each argument using an annotation immediately next to the argument. VBT now provides a rich set of in-house annotations tailored to specific tasks. For example, whether an argument is a parameter can be specified directly in the function instead of in the parameterized decorator.

Test a cross-validation function with annotations
@vbt.cv_split(
    splitter="from_rolling",
    splitter_kwargs=dict(length=365, split=0.5, set_labels=["train", "test"]),
    parameterized_kwargs=dict(random_subset=100, seed=42),
)
def sma_crossover_cv(
    data: vbt.Takeable,  
    fast_period: vbt.Param(condition="x < slow_period"),  
    slow_period: vbt.Param,  
    metric
) -> vbt.MergeFunc("concat"):
    fast_sma = data.run("sma", fast_period, hide_params=True)
    slow_sma = data.run("sma", slow_period, hide_params=True)
    entries = fast_sma.real_crossed_above(slow_sma)
    exits = fast_sma.real_crossed_below(slow_sma)
    pf = vbt.PF.from_signals(data, entries, exits, direction="both")
    return pf.deep_getattr(metric)

sma_crossover_cv(
    vbt.YFData.pull("BTC-USD", start="4 years ago"),
    np.arange(20, 50),
    np.arange(20, 50),
    "trades.expectancy"
)
split  set    fast_period  slow_period
0      train  25           26               2.822879
       test   25           26               4.740533
1      train  22           37              16.967189
       test   22           37             -16.101936
2      train  30           41             308.879034
       test   30           41              14.445511
3      train  29           49              32.328702
       test   29           49              13.546335
4      train  27           48              20.695292
       test   27           48               6.539824
5      train  22           46              19.143686
       test   22           46              -4.664139
6      train  34           45               5.235739
       test   34           45              -2.697966
dtype: float64

Compression

✅ 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! 🪶

Save data without and with compression
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 MB
file_path = data.save(compression="blosc")
print(vbt.file_size(file_path))
13.3 MB

Faster loading

✅ 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 ⏳

Define importing settings in vbt.cfg
[importing]
auto_import = False
Measure the loading time
start = utc_time()
from vectorbtpro import *
end = utc_time()
end - start
0.580937910079956

Serialization

✅ 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 🏗️

Backtest each month of data and save the results for later
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_pfs
Date
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: object
vbt.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

✅ VBT is a comprehensive library that defines thousands of classes, functions, and objects. When working with these, you may want to "look inside" an object to better understand its attributes and contents. Fortunately, there is a formatting engine that can accurately format any in-house object as a human-readable string. Did you know the API documentation is partly powered by this engine? 😉

Introspect a data instance
data = vbt.YFData.pull("BTC-USD", start="2020", end="2021")

vbt.pprint(data)  
YFData(
    wrapper=ArrayWrapper(...),
    data=symbol_dict({
        'BTC-USD': <pandas.core.frame.DataFrame object at 0x7f7f1fbc6cd0 with shape (366, 7)>
    }),
    single_key=True,
    classes=symbol_dict(),
    fetch_kwargs=symbol_dict({
        'BTC-USD': dict(
            start='2020',
            end='2021'
        )
    }),
    returned_kwargs=symbol_dict({
        'BTC-USD': dict()
    }),
    last_index=symbol_dict({
        'BTC-USD': Timestamp('2020-12-31 00:00:00+0000', tz='UTC')
    }),
    tz_localize=datetime.timezone.utc,
    tz_convert='UTC',
    missing_index='nan',
    missing_columns='raise'
)
vbt.pdir(data)  
                                            type                                             path
attr
align_columns                        classmethod                       vectorbtpro.data.base.Data
align_index                          classmethod                       vectorbtpro.data.base.Data
build_feature_config_doc             classmethod                       vectorbtpro.data.base.Data
...                                          ...                                              ...
vwap                                    property                       vectorbtpro.data.base.Data
wrapper                                 property               vectorbtpro.base.wrapping.Wrapping
xs                                      function          vectorbtpro.base.indexing.PandasIndexer
vbt.phelp(data.get)  
YFData.get(
    columns=None,
    symbols=None,
    **kwargs
):
    Get one or more columns of one or more symbols of data.

Templates

✅ 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! 🧞

Design a template-enhanced resampling functionality
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")
    )
)
Group 7/7
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: float64

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.