# Portfolio accounting (/features/backtesting/portfolio-accounting)

## Asset weighting \[#asset-weighting]

New in v2024.8.20

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

```python title="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)  # (1)
symbol
AAPL    1.401012
MSFT    0.456162
GOOG    0.852490
Name: sharpe_ratio, dtype: float64

>>> pf.sharpe_ratio  # (2)
1.2132857343006869

>>> prices = pf.get_value(group_by=False)
>>> weights = vbt.pypfopt_optimize(prices=prices)  # (3)
>>> weights
{'AAPL': 0.85232, 'MSFT': 0.0, 'GOOG': 0.14768}

>>> weighted_pf = pf.apply_weights(weights, rescale=True)  # (4)
>>> weighted_pf.weights
symbol
AAPL    2.55696
MSFT    0.00000
GOOG    0.44304
dtype: float64

>>> weighted_pf.get_sharpe_ratio(group_by=True)  # (5)
1.426112580298898
```

1.  Sharpe ratio for each individual asset.
2.  Sharpe ratio for the combined portfolio. The goal is to maximize this value.
3.  Maximize Sharpe based on equity (that is, portfolio value) development.
4.  Rescale the weights for multiplication and create a new portfolio with the weights applied.
5.  Sharpe ratio for the optimized portfolio.

## Position views \[#position-views]

New in v2024.8.20

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

```python title="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)](/assets/figures/features/portfolio/position-views.e9ab9910acba.json)

```python
>>> long_pf.sharpe_ratio
0.9185961894435091

>>> short_pf.sharpe_ratio
0.2760864152147919
```

## Index records \[#index-records]

New in 1.12.0

✅ 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](/features/tooling/time-series-operations/#index-dictionaries)—one for each argument.

```python title="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),  # (1)
...     dict(date="2022", symbol="ETH-USD", short_entry=True),
...     dict(row=-1, exit=True),
... ]
>>> pf = vbt.PF.from_signals(data, records=records)  # (2)
>>> 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
```

1.  Every broadcastable argument is supported. Rows can be specified using "row", "index", "date", or
    "datetime". Columns can be specified with "col", "column", or "symbol". If you do not provide a row
    or column, the entire row or column will be set, respectively. If neither is provided, the whole
    array will be set. Rows and columns can be given as integer positions, labels, datetimes, or even
    complex indexers!
2.  Arguments not used in records can still be provided as usual. Arguments used in records can
    also be provided to serve as defaults.

## Position info \[#position-info]

New in 1.11.0

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

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

1.  Disable Numba during testing to avoid compilation.

## Cash deposits \[#cash-deposits]

New in 1.0.0

✅ Cash can be deposited or withdrawn at any time.

```python title="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  # (1)
1020.0

>>> pf.final_value
20674.328828315127
```

1.  Total invested.

## Cash earnings \[#cash-earnings]

New in 1.0.0

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

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

>>> pf_kept = vbt.PF.from_holding(  # (1)
...     data.close,
...     cash_dividends=data.get("Dividends")
... )
>>> pf_kept.cash.iloc[-1]  # (2)
93.9182408043298

>>> pf_kept.assets.iloc[-1]  # (3)
15.37212731743495

>>> pf_reinvested = vbt.PF.from_orders(  # (4)
...     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()
```

1.  Keep dividends as cash.
2.  Final cash balance.
3.  Final number of shares in the portfolio.
4.  Reinvest dividends at the next bar.

AAPL portfolio value with dividends kept as cash versus reinvested from 2010 onward. [Figure data (JSON)](/assets/figures/features/portfolio/dividends.5b1cc406d059.json)
