# Workflow automation (/features/tooling/workflow-automation)

## CLI \[#cli]

New in v2026.4.7

✅ VBT now ships with a [Typer](https://typer.tiangolo.com/)-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.

=== "Chat"
    ```bash title="Ask VBT a question from the terminal"
    vbt chat "How to backtest a weekly rebalancing strategy?"  # (1)
    ```

    1.  Uses the same RAG pipeline as `vbt.chat()` under the hood,
        including document retrieval, ranking using embeddings, and LLM completion.

=== "Quick chat"
    ```bash title="Ask VBT a quick question from the terminal"
    vbt quick-chat "What does size_type='target_percent' do?"  # (1)
    ```

    1.  Uses a faster completion model and BM25 instead of embeddings for fast,
        fully offline lexical search. No prior embedding generation is required.

=== "Interact"
    ```bash title="Ask VBT a question from the terminal with function calling enabled"
    vbt interact "Plot the equity curve for a 10/30 SMA crossover on BTC-USD"  # (1)
    ```

    1.  Wraps `vbt.interact()`, which enables function calling so the model can
        invoke MCP tools, run code, and return results.

=== "MCP"
    ```bash title="Start the MCP server or invoke a registered tool directly from the terminal"
    vbt mcp serve  # (1)

    vbt mcp search "VBTWarning: Object has multiple columns?"  # (2)

    vbt mcp search --call '{"kwargs": {"query": "VBTWarning: Object has multiple columns?"}}'  # (3)
    ```

    1.  Launch the MCP server for use with external clients.
    2.  Invoke any registered MCP tool directly from the command line without starting the server.
    3.  Pass any arguments to the tool using the `--call` option. The value should be a JSON string.

!!! info
    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 \[#configuration-files]

New in v2025.10.15

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

=== "INI"
    ```ini title="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
    ```

=== "YAML"
    ```yaml title="Define global settings in vbt.yml"
    plotting:
      default_theme: dark

    portfolio:
      init_cash: 5000

    data.custom.binance.client_config:
      api_key: &api_key <YOUR_API_KEY>
      api_secret: &api_secret <YOUR_API_SECRET>

    data.custom.ccxt.exchanges.binance.exchange_config:
      apiKey: *api_key
      secret: *api_secret
    ```

=== "TOML"
    ```toml title="Define global settings in vbt.toml"
    [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 = { __vbt_ref__ = "data.custom.binance.client_config.api_key" }
    secret = { __vbt_ref__ = "data.custom.binance.client_config.api_secret" }
    ```

```python title="Verify that the settings have been loaded correctly"
>>> from vectorbtpro import *

>>> vbt.settings.portfolio["init_cash"]
5000
```

## Iterated decorator \[#iterated-decorator]

New in v2024.6.19

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

```python title="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")  # (1)
... @vbt.iterated(over_arg="month", merge_func="concat")  # (2)
... def get_year_month_sharpe(data, year, month):  # (3)
...     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},  # (4)
... )
>>> sharpe_matrix.transpose().vbt.heatmap(
...     trace_kwargs=dict(colorscale="RdBu", zmid=0),
...     yaxis=dict(autorange="reversed")
... ).show()
```

1.  Iterate over years (in parallel).
2.  Iterate over months (sequentially).
3.  The function is called for each combination of year and month.
4.  Map month numbers to names and pass them as a dict. VBT will extract the keys and use them as labels.

Monthly Sharpe ratio heatmap for Bitcoin grouped by year. [Figure data (JSON)](/assets/figures/features/productivity/iterated-decorator.a87ce0622201.json)

## Tasks \[#tasks]

New in v2024.6.19

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

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

>>> task1 = vbt.Task(  # (1)
...     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")  # (2)

>>> 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()
```

1.  A task consists of a function and the arguments you want to pass to that function.
    Just creating a task does not execute the function!
2.  Execute all three tasks using multiprocessing.

Expectancy across stop-loss, trailing stop-loss, and take-profit levels. [Figure data (JSON)](/assets/figures/features/productivity/tasks.f161dc45cfc4.json)

## Nested progress bars \[#nested-progress-bars]

New in v2024.5.15

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

```python title="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:  # (1)
...     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:  # (2)
...             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:  # (3)
...                     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()
```

1.  Track iteration over symbols.
2.  Track iteration over fast windows.
3.  Track iteration over slow windows.

Symbol 2/2: 100%

Fast window 20/20: 100%

Slow window 20/20: 100%

```python
>>> 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
```

## Resource management \[#resource-management]

New in 1.0.0

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

```python title="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
```
