Waterfall Chart

Investment Portfolio Performance Attribution

Light-themed waterfall chart showing portfolio performance attribution from starting value to ending value.

Output
Investment Portfolio Performance Attribution
Python
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch

# Portfolio performance attribution (basis points)
categories = ['Benchmark\nReturn', 'Equity\nSelection', 'Sector\nAllocation', 'Currency\nEffect', 
              'Fixed Income', 'Alternatives', 'Fees', 'Portfolio\nReturn']
values = [0, 85, 42, -28, 35, 52, -45, 0]

# Calculate running total
initial = 720  # Benchmark return in bps (7.2%)
running_total = initial
bottoms = []
heights = []
colors = []

for i, (cat, val) in enumerate(zip(categories, values)):
    if 'Benchmark' in cat:
        bottoms.append(0)
        heights.append(initial)
        colors.append('#3b82f6')
    elif 'Portfolio' in cat:
        bottoms.append(0)
        heights.append(running_total)
        colors.append('#22c55e')
    elif val > 0:
        bottoms.append(running_total)
        heights.append(val)
        colors.append('#22c55e')
        running_total += val
    else:
        bottoms.append(running_total + val)
        heights.append(abs(val))
        colors.append('#ef4444')
        running_total += val

# Create figure
fig, ax = plt.subplots(figsize=(14, 8), facecolor='#ffffff')
ax.set_facecolor('#ffffff')

x = np.arange(len(categories))
bars = ax.bar(x, heights, bottom=bottoms, color=colors, width=0.65, edgecolor='#e5e7eb', linewidth=1)

# Add value labels
for i, (bar, val, bot, height) in enumerate(zip(bars, values, bottoms, heights)):
    y_pos = bot + height / 2
    if 'Benchmark' in categories[i] or 'Portfolio' in categories[i]:
        label = f"{height/100:.2f}%"
        ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, 
                ha='center', va='center', fontsize=10, fontweight='bold', color='white')
    else:
        label = f"+{val}bp" if val > 0 else f"{val}bp"
        ax.text(bar.get_x() + bar.get_width()/2, y_pos, label, 
                ha='center', va='center', fontsize=9, fontweight='bold', color='white')

# Connect bars
for i in range(len(x) - 1):
    if i == 0:
        y = initial
    else:
        y = bottoms[i] + heights[i]
    ax.plot([x[i] + 0.35, x[i+1] - 0.35], [y, y], 
            color='#9ca3af', linestyle='--', linewidth=1.5, alpha=0.7)

# Styling
ax.set_xlim(-0.6, len(categories) - 0.4)
ax.set_ylim(0, max(bottoms[i] + heights[i] for i in range(len(heights))) * 1.1)
ax.set_xticks(x)
ax.set_xticklabels(categories, fontsize=9, color='#374151')
ax.set_ylabel('Return (basis points)', fontsize=12, color='#374151', fontweight='500')
ax.set_title('Portfolio Performance Attribution Analysis', fontsize=16, color='#111827', fontweight='bold', pad=20)

ax.tick_params(axis='y', colors='#374151', labelsize=10)
ax.yaxis.grid(True, linestyle='--', alpha=0.4, color='#e5e7eb')
ax.set_axisbelow(True)

for spine in ax.spines.values():
    spine.set_color('#d1d5db')

# Alpha annotation
alpha = running_total - initial
ax.annotate(f'Alpha: +{alpha}bp ({alpha/100:.2f}%)', xy=(0.98, 0.95), xycoords='axes fraction',
            fontsize=11, color='#16a34a', ha='right', fontweight='bold',
            bbox=dict(boxstyle='round,pad=0.4', facecolor='#f0fdf4', edgecolor='#22c55e'))

# Legend outside plot
legend_elements = [
    Patch(facecolor='#3b82f6', label='Benchmark'),
    Patch(facecolor='#22c55e', label='Positive Attribution'),
    Patch(facecolor='#ef4444', label='Negative Attribution')
]
ax.legend(handles=legend_elements, loc='upper left', bbox_to_anchor=(0, -0.1), 
          ncol=3, fontsize=9, facecolor='white', edgecolor='#d1d5db', labelcolor='#374151')

plt.tight_layout()
plt.subplots_adjust(bottom=0.15)
plt.show()
Library

Matplotlib

Category

Financial

Did this help you?

Support PyLucid to keep it free & growing

Support