Features

Portfolio accounting

Track positions, weights, records, deposits, earnings, and portfolio state

Asset weighting

✅ Asset weighting lets you fine-tune the influence of individual assets or strategies within your portfolio, giving you enhanced control over your portfolio's overall performance. The key benefit is that these weights are not limited to returns—they are consistently applied to all time series and metrics, including orders, cash flows, and more. This comprehensive approach ensures that every aspect of your portfolio stays precisely aligned.

Maximize Sharpe of a random portfolio
data = vbt.YFData.pull(["AAPL", "MSFT", "GOOG"], start="2020")
pf = data.run("from_random_signals", n=vbt.Default(50), seed=42, group_by=True)
pf.get_sharpe_ratio(group_by=False)  
symbol
AAPL    1.401012
MSFT    0.456162
GOOG    0.852490
Name: sharpe_ratio, dtype: float64
pf.sharpe_ratio  
1.2132857343006869
prices = pf.get_value(group_by=False)
weights = vbt.pypfopt_optimize(prices=prices)  
weights
{'AAPL': 0.85232, 'MSFT': 0.0, 'GOOG': 0.14768}
weighted_pf = pf.apply_weights(weights, rescale=True)  
weighted_pf.weights
symbol
AAPL    2.55696
MSFT    0.00000
GOOG    0.44304
dtype: float64
weighted_pf.get_sharpe_ratio(group_by=True)  
1.426112580298898

Position views

✅ Position views let you analyze your portfolio by focusing on either long or short positions, providing a clear and distinct perspective for each investment strategy.

Separate long and short positions of a basic SMA crossover portfolio
data = vbt.YFData.pull("BTC-USD")
fast_sma = data.run("talib_func:sma", timeperiod=20)
slow_sma = data.run("talib_func:sma", timeperiod=50)
long_entries = fast_sma.vbt.crossed_above(slow_sma)
short_entries = fast_sma.vbt.crossed_below(slow_sma)
pf = vbt.PF.from_signals(
    data,
    long_entries=long_entries,
    short_entries=short_entries,
    fees=0.01,
    fixed_fees=1.0
)

long_pf = pf.long_view
short_pf = pf.short_view

fig = vbt.make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.01)
fig = long_pf.assets.vbt.plot_against(
    0,
    trace_kwargs=dict(name="Long position", line_shape="hv", line_color="mediumseagreen"),
    other_trace_kwargs=dict(visible=False),
    add_trace_kwargs=dict(row=1, col=1),
    fig=fig
)
fig = short_pf.assets.vbt.plot_against(
    0,
    trace_kwargs=dict(name="Short position", line_shape="hv", line_color="coral"),
    other_trace_kwargs=dict(visible=False),
    add_trace_kwargs=dict(row=2, col=1),
    fig=fig
)
fig.show()
BTC-USD SMA crossover portfolio with long and short position assets shown separately Figure data (JSON)
long_pf.sharpe_ratio
0.9185961894435091
short_pf.sharpe_ratio
0.2760864152147919

Index records

✅ How can you backtest time- and asset-anchored queries such as "Order X units of asset Y on date Z"? Typically, you would need to build a full array and set each detail manually. Now, there is a simpler way: with preparers and redesigned smart indexing, you can provide all information in a compressed record format! Behind the scenes, the record array is translated into a set of index dictionaries—one for each argument.

Define a basic signal strategy using records
data = vbt.YFData.pull(["BTC-USD", "ETH-USD"], missing_index="drop")
records = [
    dict(date="2022", symbol="BTC-USD", long_entry=True),  
    dict(date="2022", symbol="ETH-USD", short_entry=True),
    dict(row=-1, exit=True),
]
pf = vbt.PF.from_signals(data, records=records)  
pf.orders.readable
   Order Id   Column              Signal Index            Creation Index
0         0  BTC-USD 2022-01-01 00:00:00+00:00 2022-01-01 00:00:00+00:00  \
1         1  BTC-USD 2023-04-25 00:00:00+00:00 2023-04-25 00:00:00+00:00
2         0  ETH-USD 2022-01-01 00:00:00+00:00 2022-01-01 00:00:00+00:00
3         1  ETH-USD 2023-04-25 00:00:00+00:00 2023-04-25 00:00:00+00:00

                 Fill Index      Size         Price  Fees  Side    Type
0 2022-01-01 00:00:00+00:00  0.002097  47686.812500   0.0   Buy  Market  \
1 2023-04-25 00:00:00+00:00  0.002097  27534.675781   0.0  Sell  Market
2 2022-01-01 00:00:00+00:00  0.026527   3769.697021   0.0  Sell  Market
3 2023-04-25 00:00:00+00:00  0.026527   1834.759644   0.0   Buy  Market

  Stop Type
0      None
1      None
2      None
3      None

Position info

✅ Dynamic signal functions now have access to the current position information, such as (open) P&L.

Enter randomly, exit randomly but only if in profit
@njit
def signal_func_nb(ctx, entries, exits):
    is_entry = vbt.pf_nb.select_nb(ctx, entries)
    is_exit = vbt.pf_nb.select_nb(ctx, exits)
    if is_entry:
        return True, False, False, False
    if is_exit:
        pos_info = ctx.last_pos_info[ctx.col]
        if pos_info["status"] == vbt.pf_enums.TradeStatus.Open:
            if pos_info["pnl"] >= 0:
                return False, True, False, False
    return False, False, False, False

data = vbt.YFData.pull("BTC-USD")
entries, exits = data.run("RANDNX", n=10, seed=42, unpack=True)
pf = vbt.Portfolio.from_signals(
    data,
    signal_func_nb=signal_func_nb,
    signal_args=(vbt.Rep("entries"), vbt.Rep("exits")),
    broadcast_named_args=dict(entries=entries, exits=exits),
    jitted=False  
)
pf.trades.readable[["Entry Index", "Exit Index", "PnL"]]
                Entry Index                Exit Index           PnL
0 2014-11-01 00:00:00+00:00 2016-01-08 00:00:00+00:00     39.134739
1 2016-03-27 00:00:00+00:00 2016-09-07 00:00:00+00:00     61.220063
2 2016-12-24 00:00:00+00:00 2016-12-31 00:00:00+00:00     14.471414
3 2017-03-16 00:00:00+00:00 2017-08-05 00:00:00+00:00    373.492028
4 2017-09-12 00:00:00+00:00 2018-05-05 00:00:00+00:00    815.699284
5 2019-02-15 00:00:00+00:00 2019-11-10 00:00:00+00:00   2107.383227
6 2019-12-04 00:00:00+00:00 2019-12-10 00:00:00+00:00     12.630214
7 2020-07-12 00:00:00+00:00 2021-11-14 00:00:00+00:00  21346.035444
8 2022-01-15 00:00:00+00:00 2023-03-06 00:00:00+00:00 -11925.133817

Cash deposits

✅ Cash can be deposited or withdrawn at any time.

DCA $10 into Bitcoin each month
data = vbt.YFData.pull("BTC-USD")
cash_deposits = data.symbol_wrapper.fill(0.0)
month_start_mask = ~data.index.tz_convert(None).to_period("M").duplicated()
cash_deposits[month_start_mask] = 10
pf = vbt.PF.from_orders(
    data.close,
    init_cash=0,
    cash_deposits=cash_deposits
)

pf.input_value  
1020.0
pf.final_value
20674.328828315127

Cash earnings

✅ Cash can be continuously earned or spent depending on the current position.

Backtest Apple without and with dividend reinvestment
data = vbt.YFData.pull("AAPL", start="2010")

pf_kept = vbt.PF.from_holding(  
    data.close,
    cash_dividends=data.get("Dividends")
)
pf_kept.cash.iloc[-1]  
93.9182408043298
pf_kept.assets.iloc[-1]  
15.37212731743495
pf_reinvested = vbt.PF.from_orders(  
    data.close,
    cash_dividends=data.get("Dividends")
)
pf_reinvested.cash.iloc[-1]
0.0
pf_reinvested.assets.iloc[-1]
18.203284859405468
fig = pf_kept.value.rename("Value (kept)").vbt.plot()
pf_reinvested.value.rename("Value (reinvested)").vbt.plot(fig=fig)
fig.show()
AAPL portfolio value with dividends kept as cash versus reinvested from 2010 onward Figure data (JSON)

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.