Performance¶
The sunburst chart from the previous page is good for getting an overview of the portfolio but not ideal for identifying the out- and under-performers. I like the interactive “heatmap” from Finviz in the sense that it shows the stake of each equity position in the underlining indices. Here I will use the treemap in plotly
to create a similar visualization based on hierarchical dataframes.
Create the randomized portoflio¶
First let’s regenerate the same randomized portfolio used for the sunburst chart (Click on “+” to see the code). The cash positions won’t be added this time since we’d like to focus on investment performance only.
# HIDE CODE
import pandas as pd
import random
def build_hierarchical_dataframe(df, levels, value_column,
custom_column=None, cal_tot=True):
"""Build a hierarchy of levels for Sunburst and Treemap charts.
"""
df_all_trees = pd.DataFrame(columns=['id', 'parent', 'value', 'custom'])
for i, level in enumerate(levels):
df_tree = pd.DataFrame(columns=['id', 'parent', 'value', 'custom'])
dfg = df.groupby(levels[i:]).sum()
dfg = dfg.reset_index()
df_tree['id'] = dfg[level].copy()
if i < len(levels) - 1:
df_tree['parent'] = dfg[levels[i+1]].copy()
else:
if cal_tot:
df_tree['parent'] = 'Total'
df_tree['value'] = dfg[value_column]
if custom_column:
df_tree['custom'] = dfg[custom_column]
df_all_trees = df_all_trees.append(df_tree, ignore_index=True)
if cal_tot:
total = pd.Series(dict(id='Total', parent='',
value=df[value_column].sum(),
custom=df[custom_column].sum()))
df_all_trees = df_all_trees.append(total, ignore_index=True)
return df_all_trees
# set the seed to make the result reproducible
random.seed(110)
portfolio = pd.read_pickle("../src/data/portfolio.pkl")
# assign a random number of shares to each symbol
portfolio["Quantity"] = portfolio["Symbol"].apply(lambda x: random.randint(2, 10))
# assign market value based on previousClose
portfolio["Market Value"] = portfolio["Info"].apply(lambda x: x["previousClose"])*portfolio["Quantity"]
# assign cost basis based on random price between 52 low and high
portfolio["Cost Basis"] = portfolio["Info"].apply(lambda x: random.uniform(x["fiftyTwoWeekLow"], x["fiftyTwoWeekHigh"]))*portfolio["Quantity"]
# calculate the gain/loss
portfolio["Gain/Loss"] = portfolio["Market Value"] - portfolio["Cost Basis"]
portfolio.drop(columns="Info", inplace=True)
Create the treemap chart¶
We will first transform the portfolio into a hierarchical dataframe and then use Treemap
from plotly
to visualize it. Mouse over each rectangle to see details and click on them to interact.
# HIDE CODE
import plotly.graph_objects as go
# define the structure
levels = ["Symbol", "Sector", "Category"]
value_column = "Market Value"
custom_column = "Gain/Loss"
# reform the dataframe
df_all_trees = build_hierarchical_dataframe(portfolio, levels, value_column, custom_column=custom_column)
# add additional information to the dataframe
df_all_trees['value_dollar'] = df_all_trees['value'].apply(
lambda x: f"$ {x:,.0f}")
df_all_trees['change'] = df_all_trees['custom'].apply(
lambda x: f"$ {x:,.0f}" if x != 0 else "-")
df_all_trees['change_per'] = (df_all_trees['custom']/(
df_all_trees['value']-df_all_trees['custom'])).apply(
lambda x: f"{x*100:.1f}%" if x else '-').replace("nan", "-")
df_all_trees['change'] = df_all_trees['change'].replace("$ nan", "-")
df_all_trees['change_per'] = df_all_trees['change_per'].replace(
"nan%", "-")
# make the plot
fig = go.Figure(go.Treemap(
labels=df_all_trees['id'],
parents=df_all_trees['parent'],
values=df_all_trees['value'],
branchvalues='total',
textinfo='label+percent entry',
marker=dict(
colors=df_all_trees['change_per'].str.rstrip("%").apply(
lambda x: float(x) if x != '-' else 0),
colorscale=[[0, 'red'], [0.5, 'white'], [1.0, 'green']],
cauto=False,
cmax=100,
cmin=-100,
cmid=0),
customdata=df_all_trees[['value_dollar', 'change',
'change_per']],
# customize tooltip
hovertemplate="<b>%{label}</b><br><br>"
+ "Market value: %{customdata[0]}<br>"
+ "Change: %{customdata[1]} (%{customdata[2]})<br>"
+ "<extra></extra>"
))
fig.update_traces(textfont_size=14)
fig.update_layout(margin=dict(t=20, b=38, r=10, l=10))
fig.show()
Conclusion¶
Instead of using the default color palette (which I quite like), I used the marker
argument to define a continuous colorscale (from green to white to red). This way without the tooltip we can already identify the best performers (the greenest rectangles) and the worst ones (the reddest). It’s also easy to see which sector performs the best. One could also use other information stored in the “Info” column of the original portfolio dataframe to create different flavors of categorization (such as by market capitalization or by country of origin). I used the percentage change here to create the colorscale and set “100” (%) on both ends. This can be easily customized to change the linearity of the colorscale for different visualization purposes. Refer to the official documentation of treemap for a full list of customization possibilities.