Tutorials
Portfolio optimization
Design, integrate, and dynamically rebalance optimized portfolios
Portfolio optimization focuses on constructing a portfolio of assets that seeks to maximize returns and minimize risk. In this context, a portfolio refers to the distribution of an investor's assets—a weight vector—which can be optimized for risk tolerance, expected rate of return, cost minimization, and other objectives. This optimization can be performed regularly to reflect recent changes in market behavior.
In VBT, a portfolio is a collection of asset vectors combined into a larger array along the column axis. By default, each of these vectors is treated as a separate backtesting instance, but you can apply a grouping instruction to treat multiple assets as a single unit. Portfolio optimization then becomes the process of converting a set of pricing vectors (information as input) into a set of allocation vectors (actions as output), which can then be provided to any simulator.
Thanks to VBT's modular design (and in line with the key principles of data science), optimization and simulation are handled separately. This enables you to analyze and filter allocation vectors even before they are backtested. This approach is similar to the typical workflow for working with signals: 1) generate, 2) pre-analyze, 3) simulate, and 4) post-analyze. In this example, we will cover how to complete each of these steps for the highest informational yield.
Data
As always, we should begin by obtaining some data. Because portfolio optimization involves working with a group of assets, we need to fetch data for more than one symbol. Here, we will fetch one year of hourly data for 5 different cryptocurrencies:
from vectorbtpro import *
symbols = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "XRPUSDT", "ADAUSDT"]
data = vbt.BinanceData.pull(
symbols,
start="2020-01-01 UTC",
end="2021-01-01 UTC",
timeframe="1h"
)Let's save the data locally to avoid re-fetching it every time we start a new runtime:
data.to_hdf()
data = vbt.HDFData.pull("BinanceData.h5").select(symbols) Allocation
In simple terms, asset allocation is the process of deciding where to invest funds in the market—it is a
horizontal vector composed of weights or amounts of assets at a specific timestamp. For example, to
allocate 50% to BTCUSDT, 20% to ETHUSDT, and distribute the remainder equally among the other assets,
the allocation vector would be [0.5, 0.2, 0.1, 0.1, 0.1]. Frequently, weight allocations sum to 1,
ensuring the entire stake is continuously invested, but you can also choose to invest only a portion of
your balance or specify a particular (continuous or discrete) number of assets instead of weights. Since we
generally want to allocate periodically rather than hold positions indefinitely, we also need to decide on
rebalancing timestamps.
Manually
Let's manually generate and simulate allocations to better understand how everything works together.
Index points
The first step is to decide when to re-allocate. This is straightforward using
ArrayWrapper.get_index_points,
which converts a human-readable query into a list of index positions (also called "index points" or
"allocation points"). These positions are simple numeric indices, where 0 is the first row and
len(index) - 1 is the last.
For example, let's convert the first day of each month into index points:
ms_points = data.wrapper.get_index_points(every="M")
ms_pointsarray([0, 744, 1434, 2177, 2895, 3639, 4356, 5100, 5844, 6564, 7308, 8027])You can check the indices above using Pandas:
data.wrapper.index.get_indexer(
pd.Series(index=data.wrapper.index).resample(vbt.offset("M")).asfreq().index,
method="bfill"
)array([0, 744, 1434, 2177, 2895, 3639, 4356, 5100, 5844, 6564, 7308, 8027])We can also convert these index points back to timestamps:
data.wrapper.index[ms_points]DatetimeIndex(['2020-01-01 00:00:00+00:00', '2020-02-01 00:00:00+00:00',
'2020-03-01 00:00:00+00:00', '2020-04-01 00:00:00+00:00',
'2020-05-01 00:00:00+00:00', '2020-06-01 00:00:00+00:00',
'2020-07-01 00:00:00+00:00', '2020-08-01 00:00:00+00:00',
'2020-09-01 00:00:00+00:00', '2020-10-01 00:00:00+00:00',
'2020-11-01 00:00:00+00:00', '2020-12-01 00:00:00+00:00'],
dtype='datetime64[ns, UTC]', name='Open time', freq=None)ArrayWrapper.get_index_points
always returns indices that can be used on the index, unless skipna is disabled.
In that case, it will return -1 wherever an index point cannot be found.
These are our rebalancing timestamps!
The main advantage of this method is its flexibility. The every argument can be a string,
an integer, a pd.Timedelta object, or a pd.DateOffset object:
Are you looking to build a portfolio that achieves the highest possible return while keeping the risk at a level you are comfortable with?
✅ Learn how to design and implement your own portfolio optimization models 🎂
✅ Discover how VBT integrates with third-party libraries such as PyPortfolioOpt, Riskfolio-Lib, and Universal Portfolios to enable rebalancing with just a few lines of code!
✅ Learn how to rebalance dynamically using Numba. We will implement a threshold rebalancing template that can be used with any optimization function. As a bonus, we will build a mean-variance optimizer (MVO) from scratch for a significant performance boost 💨
Topics
Copyright © 2021–2026 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.