Portfolio Aligned Universes

Portfolio Aligned Universes#

In this notebook we’ll demonstrate how to define universes based on existing portfolio data.

For this purpose we will first upload some sample portfolio holdings and subsequently use them to define a universe filter.

import io

import polars as pl

from bayesline.api.equity import (
    PortfolioOrganizerSettings,
    UniverseSettings,
)
from bayesline.apiclient import BayeslineApiClient
bln = BayeslineApiClient.new_client(
    endpoint="https://[ENDPOINT]",
    api_key="[API-KEY]",
)

Uploading Portfolios#

We’ll upload two portfolios PORT_1 and PORT_2 which contain assets Apple (IC83A1B819), Microsoft (ICF982536B) and Alphabet (ICA17F00B9).

portfolio_uploaders = bln.equity.uploaders.get_data_type("portfolios")
portfolio_uploader = portfolio_uploaders.create_or_replace_dataset("US-Portfolios")
data_csv = """
portfolio_id	date	asset_id	asset_id_type	value
PORT_1	2025-01-01	IC83A1B819	bayesid	.3
PORT_1	2025-01-01	ICA17F00B9	bayesid	.7
PORT_2	2025-01-01	IC83A1B819	bayesid	.4
PORT_2	2025-01-01	ICF982536B	bayesid	.6
PORT_1	2025-02-01	IC83A1B819	bayesid	1
PORT_2	2025-02-01	ICF982536B	bayesid	1
"""

portfolios_df = pl.read_csv(io.StringIO(data_csv.strip()), separator="\t", try_parse_dates=True)

portfolios_df.pivot(index=["date", "portfolio_id"], on="asset_id", values="value").fill_null(0)
shape: (4, 5)
dateportfolio_idIC83A1B819ICA17F00B9ICF982536B
datestrf64f64f64
2025-01-01"PORT_1"0.30.70.0
2025-01-01"PORT_2"0.40.00.6
2025-02-01"PORT_1"1.00.00.0
2025-02-01"PORT_2"0.00.01.0
portfolio_uploader.fast_commit(portfolios_df, mode="append")
UploadCommitResult(version=1, committed_names=[])

Creating the Universe#

Note that below we could still use other filters in addition to the portfolio filter. This way we could for instance express the energy sector within the Russell 3000.

universe_settings = UniverseSettings(
    dataset="Bayesline-US-All-1y",
    portfolio_filter=PortfolioOrganizerSettings(
        # filters against the point in time superset 
        # of all portfolios contained in `US-Portfolios`
        enabled_portfolios="US-Portfolios"
    ),
)
universe_api = bln.equity.universes.load(universe_settings)

When obtaining the universe data note how even though we only specified holdings for 2025-01-01 and 2025-02-01 (which could be rebalance dates) the resulting universe contains entries for each day. Underneath the holdings are forward filled and delisted assets are dropped.

universe_api.get().to_pandas().assign(value=1.).set_index(["date", "bayesid"]).unstack()
value
bayesid IC83A1B819 ICA17F00B9 ICF982536B
date
2025-01-01 1.0 1.0 1.0
2025-01-02 1.0 1.0 1.0
2025-01-03 1.0 1.0 1.0
2025-01-04 1.0 1.0 1.0
2025-01-05 1.0 1.0 1.0
... ... ... ...
2025-08-21 1.0 NaN 1.0
2025-08-22 1.0 NaN 1.0
2025-08-23 1.0 NaN 1.0
2025-08-24 1.0 NaN 1.0
2025-08-25 1.0 NaN 1.0

237 rows × 3 columns

# asset counts over
universe_api.counts().to_pandas().set_index("date")["count"].sort_index().plot()
<Axes: xlabel='date'>
../_images/7c72e49471128a705a6e4a691ff639b1cd7c139d8475cb07cc3c97ce957e7583.png