Portfolio performance treemap

q.plot.tree_plot shows portfolio performance in two synchronized views. Accumulated is the default: it shows held-period performance for every asset that has appeared in the portfolio. Point in time uses the same accumulated held-period returns through the selected date, but includes only assets held at that date and sizes them by current absolute weight. The date slider controls both views and starts at the terminal date.

Tile color is centered at zero: blue is positive and red is negative. The input is a wide DataFrame with timestamps on the index and assets in the columns. A finite return means that the asset was held during that period; NaN means that no return accrues for the asset.

In accumulated mode, an asset remains in every frame after it first appears. When it exits, its cumulative return is frozen at the terminal value until a later holding period adds another return.

Build a changing portfolio

This deterministic example has five assets. AAPL and MSFT are present from the start, NVDA enters later, and JPM leaves before the final observation.

import numpy as np
import pandas as pd

import qrt as q

dates = pd.date_range("2025-01-31", periods=6, freq="ME")
asset_returns = pd.DataFrame(
    {
        "AAPL": [0.042, -0.018, 0.031, 0.012, -0.026, 0.038],
        "MSFT": [0.025, 0.011, -0.022, 0.044, 0.019, -0.008],
        "NVDA": [np.nan, np.nan, 0.083, -0.051, 0.067, 0.029],
        "JPM": [-0.012, 0.027, 0.016, -0.009, np.nan, np.nan],
        "XOM": [np.nan, 0.034, -0.015, 0.021, -0.011, 0.006],
    },
    index=dates,
)
asset_returns
AAPL MSFT NVDA JPM XOM
2025-01-31 0.042 0.025 NaN -0.012 NaN
2025-02-28 -0.018 0.011 NaN 0.027 0.034
2025-03-31 0.031 -0.022 0.083 0.016 -0.015
2025-04-30 0.012 0.044 -0.051 -0.009 0.021
2025-05-31 -0.026 0.019 0.067 NaN -0.011
2025-06-30 0.038 -0.008 0.029 NaN 0.006

Equal-area accumulated performance

Without weights, every asset held on or before the selected date gets equal area. Return magnitude controls color, not size, so the chart does not imply portfolio weights that were never supplied. The initial view is the terminal date and includes all historical holdings, including JPM after exit.

fig = q.plot.tree_plot(asset_returns)
fig.show()

Size tiles by historical portfolio exposure

Pass a matching weight DataFrame when holdings are available. A finite return and nonzero finite weight mark a held period. Tile area is the cumulative average absolute weight through the selected date, so exited assets remain visible in proportion to their historical exposure. Color remains the asset’s compounded held-period return. Negative weights are treated by absolute exposure, and a static Series indexed by asset can be used when weights do not vary through time.

asset_weights = pd.DataFrame(
    {
        "AAPL": [0.40, 0.35, 0.30, 0.25, 0.30, 0.30],
        "MSFT": [0.35, 0.30, 0.25, 0.25, 0.25, 0.25],
        "NVDA": [0.00, 0.00, 0.20, 0.25, 0.30, 0.30],
        "JPM": [0.25, 0.20, 0.15, 0.10, 0.00, 0.00],
        "XOM": [0.00, 0.15, 0.10, 0.15, 0.15, 0.15],
    },
    index=dates,
)

fig = q.plot.tree_plot(
    asset_returns,
    weights=asset_weights,
    title="Accumulated Portfolio Performance",
)
fig.show()

Add the treemap to a backtest report

q.bt.report automatically reads conventional Asset Returns and Asset Weights charts when a LEAN algorithm records them. The bundled SMA demo does this for every invested symbol, so its report includes accumulated performance without additional arguments:

report = q.bt.report("backtests/latest")
report.display()

For other backtest engines, or older LEAN artifacts without those charts, pass the same explicit asset_returns and optional asset_weights inputs. The resulting BacktestReport exposes the figure as report.performance_treemap.

Back to top