# Developer tools (/features/tooling/developer-tools)

How do you find your way around a library with thousands of classes and functions, and extend it?
VBT includes developer tools for API search and Python object inspection: search the package, read
any object's source and documentation, and draw what each function depends on.

```python title="Find every function that takes freq, read one, and graph its dependencies"
>>> import inspect
>>> from vectorbtpro.utils.module_ import search_package
>>> from vectorbtpro.utils.source import get_source

>>> def accepts_freq(name, obj):
...     return inspect.isfunction(obj) and "freq" in inspect.signature(obj).parameters

>>> found = search_package("vectorbtpro", accepts_freq)  # (1)
>>> names = sorted({f"{func.__module__}.{func.__name__}" for func in found.values()})
>>> len(names)
14
>>> print("\n".join(names[-6:]))
vectorbtpro.utils.datetime_.readable_datetime
vectorbtpro.utils.datetime_.to_freq
vectorbtpro.utils.datetime_.to_offset
vectorbtpro.utils.datetime_.to_timedelta
vectorbtpro.utils.datetime_.to_timedelta64
vectorbtpro.utils.schedule_.wait

>>> from vectorbtpro.utils.datetime_ import infer_index_freq
>>> print("\n".join(get_source(infer_index_freq).splitlines()[:7]))
def infer_index_freq(
    index: tp.Index,
    freq: tp.Optional[tp.FrequencyLike] = None,
    allow_offset: bool = True,
    allow_numeric: bool = True,
    freq_from_n: tp.Union[None, bool, int] = None,
) -> tp.Union[None, int, float, tp.PandasFrequency]:

>>> ref_index = vbt.RefIndex(incl_modules="vectorbtpro")
>>> graph = ref_index.build_graph(infer_index_freq)  # (2)
>>> print("\n".join(graph.get_dependencies(graph.root, relation="direct")))
vectorbtpro._typing.FrequencyLike
vectorbtpro._typing.PandasFrequency
vectorbtpro.utils.checks.is_number
vectorbtpro.utils.datetime_.auto_detect_freq
vectorbtpro.utils.datetime_.freq_depends_on_index
vectorbtpro.utils.datetime_.parse_index_freq
vectorbtpro.utils.datetime_.to_freq
>>> fig = graph.plot(interactive=False, showlegend=False)
>>> for trace in fig.data:  # (3)
...     if trace.name.endswith("_nodes_hl") and len(trace.x) > 0:
...         labels = [info[1] or info[0] for info in trace.customdata]
...         trace.update(mode="markers+text", text=labels, textposition="top center")
>>> fig.show()
```

1.  Walks every module of the package and keeps the module-level functions that match. Methods of
    classes need a separate search.
2.  Indexes the source code for references and follows those defined in the function's own module.
3.  Names show on hover by default. This loop prints them next to each node.

Reference graph of the infer\_index\_freq function and the functions and types it uses. [Figure data (JSON)](/assets/figures/features/tooling/reference-graph-infer-freq.11ed1169f1ab.json)

Fourteen module-level functions accept a `freq` argument. The graph places `infer_index_freq` and
the seven names it uses inside the modules that define them, which shows where to look when a
frequency is not what you expected.

## Discovery \[#discovery]

Most questions about VBT can be answered from Python itself:

| Tool                                            | Answers                                                                |
| ----------------------------------------------- | ---------------------------------------------------------------------- |
| `vbt.phelp(obj)`                                | What a function takes and what its docstring says                      |
| `vbt.pdir(obj)`                                 | Which attributes an object has, their kinds, and where each is defined |
| `vbt.pprint(obj)`                               | What an object holds, formatted as readable nested text                |
| `vbt.ptable(obj)`                               | What an array, DataFrame, or record array holds, as a table            |
| `get_source(obj)`                               | The source code of any function, class, or module                      |
| `vbt.get_api_ref(obj)`, `vbt.open_api_ref(obj)` | The API documentation page for an object                               |
| `vbt.RefIndex`                                  | What an object uses, what uses it, and how modules connect             |
| `vbt.get_capabilities()`                        | How many indicators, data sources, metrics, and engines are installed  |

`deep_getattr` resolves a dotted path of attributes and method calls, such as
`pf.deep_getattr("trades.expectancy")`. For questions in plain language, the
[Knowledge search](/features/ai/knowledge-search/) page covers `vbt.find_api` and `vbt.search`.

These tools work together when an unfamiliar object or argument blocks your research. Start with
`phelp` for the call signature, use `pdir` to find the relevant property or method, then open the
source or API reference for the details. Configured objects expose their constructor arguments in
`config`, so you can inspect the settings that created a data instance, indicator, or portfolio.

## Formatting and annotations \[#formatting-and-annotations]

The formatting engine behind `vbt.pprint` and `vbt.phelp` renders configured objects, dictionaries,
arrays, and signatures the same way in a terminal and in a notebook, as the
[Formatting engine](#formatting-engine) highlight below shows. Annotations carry meaning in a
function's signature: `vbt.Param`, `vbt.Takeable`, and `vbt.MergeFunc` tell the parameterization and
splitting decorators how to treat each argument, as the [Annotations](#annotations) highlight below
shows.

## Validation and debugging \[#validation-and-debugging]

*   **Equality:** `vbt.is_deep_equal` compares nested objects, arrays, and their metadata, and every
    configured object has `equals`.
*   **Warnings:** VBT warnings share one format, and `vbt.WarningsFiltered()` silences them inside a
    `with` block.
*   **Hashing:** `vbt.hdict` and configured objects can be hashed, which is how VBT caches results by
    their arguments.
*   **Bounds checks:** an invalid array index in a custom Numba callback can return incorrect data or
    crash the kernel. Setting `boundscheck` under `numba` in a configuration file makes compiled
    functions raise an error instead, and Numba's `NUMBA_DISABLE_JIT=1` environment variable disables
    Numba compilation for Python-level debugging. Set it before importing VBT.
*   **Preparers:** simulation methods split argument checking from execution. Pass
    `return_preparer=True` to inspect the prepared arguments of a portfolio before it runs, or
    `return_prep_result=True` to get the final arrays the simulator would receive, in `target_args`.

### Inspect what the simulator will receive \[#inspect-what-the-simulator-will-receive]

Preparers let you check shapes, defaults, and converted arguments before running a long backtest.
Here, two assets have different order values but share the same fee:

```python title="Inspect prepared order sizes and fees without running the simulation"
>>> close = pd.DataFrame(
...     {"A": [100.0, 105.0, 103.0], "B": [50.0, 49.0, 52.0]},
...     index=pd.date_range("2025-01-01", periods=3),
... )
>>> prepared = vbt.PF.from_orders(
...     close,
...     size=np.array([[100.0, 200.0]]),
...     size_type="value",
...     fees=0.001,
...     return_prep_result=True,
... )
>>> print(prepared.pf_args["wrapper"].shape)
(3, 2)
>>> print(prepared.target_args["size"])
[[100. 200.]]
>>> print(prepared.target_args["fees"])
[[0.001]]
```

The target has three rows and two assets. Order values stay in a single row, and the shared fee
stays in a single cell, ready for flexible indexing inside the simulator. This makes it easier to
see whether an unexpected result starts with the inputs or with the trading logic. The
[Backtesting engine](/features/backtesting/backtesting-engine/#portfolio-preparers) page shows how
to modify a preparation result and run it.

Runtime checks also validate shapes, indexes, and data types. You can use the same assertion helpers
in your own functions, while deep equality checks help compare nested outputs after a refactor. For
a callback that behaves unexpectedly, inspect a small input slice first, then use bounds checking or
Python-level debugging to follow its decisions.

## Extending \[#extending]

VBT is built from the same factories it exposes. `vbt.IF` builds indicator classes, as the
[Custom indicators](/features/indicators/indicator-development/) page shows, and `vbt.SignalFactory`
builds signal generators. Configured classes get declarative fields, cached properties, and shortcut
methods from decorators, and `vbt.evaluate` evaluates expressions against a context, which is how
indicator expressions and templates find their inputs.

You can keep a custom calculation as a function, expose it as an indicator with named inputs and
parameters, or register a compiled implementation and its chunking rules. This lets custom work
participate in the same parameter searches and labeled results as built-in calculations.
[Compute backends](/features/performance/compute-backends/) and
[Parallel execution](/features/performance/parallel-execution-and-caching/) cover those execution
choices. For logic that needs current positions and cash, extend the simulation through
[Event-driven callbacks](/features/backtesting/event-driven-backtesting/).

## Utilities \[#utilities]

Smaller tools cover everyday tasks: searching and replacing values deep inside nested objects,
reading and writing nested keys by path, matching names with regular expressions, managing files and
folders, and rescaling arrays. They are the same helpers VBT uses internally. To save memory,
`vbt.settings.wrapping["max_precision"] = 32` casts results to 32-bit floats when they are wrapped
into pandas, at the cost of precision.

Member pages with Python downloads provide examples in Jupytext format, so you can open them as
notebooks and adapt them locally. The [tutorials](/tutorials/) give you complete research workflows
to inspect alongside the individual API objects.

## Reference graphs \[#reference-graphs]

New in v2025.12.31

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

```python title="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)](/assets/figures/features/productivity/reference-graph.0345cbe42aad.json)

!!! info "Online explorer"
    Explore the VBT's reference graph in the [`API`](/api/) → Reference graph.

## Annotations \[#annotations]

New in v2023.12.23

✅ 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](/features/optimization/strategy-optimization/#parameterized-decorator).

```python title="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,  # (1)
...     fast_period: vbt.Param(condition="x < slow_period"),  # (2)
...     slow_period: vbt.Param,  # (3)
...     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
```

1.  The passed argument must be takeable (for selecting subsets by split).
2.  Parameter with a condition requiring it to be less than the slow period.
3.  Parameter for the slow period.

## Formatting engine \[#formatting-engine]

New in 1.0.2

✅ 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? 😉

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

>>> vbt.pprint(data)  # (1)
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)  # (2)
                                            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)  # (3)
YFData.get(
    columns=None,
    symbols=None,
    **kwargs
):
    Get one or more columns of one or more symbols of data.
```

1.  Similar to Python's `print` command, pretty-prints the contents of any VBT object.
2.  Similar to Python's `dir` command, pretty-prints the attributes of a class, object, or module.
3.  Similar to Python's `help` command, pretty-prints the signature and docstring of a function.


## Related pages

*   [Backtesting engine](/features/backtesting/backtesting-engine/): Simulate orders, signals, and callbacks across many assets and parameters at once
*   [Workflow automation](/features/tooling/workflow-automation/): Schedule data updates, send Telegram alerts, run tasks in parallel, and track progress
*   [Configuration and persistence](/features/tooling/configuration-and-persistence/): Save any research object with compression, reload it fast, and keep settings in files
*   [Parameter optimization](/features/optimization/strategy-optimization/): Sweep millions of parameter combinations with grids, conditions, and random search