Module 5: Data Visualization
Learning Objectives
By the end of this module, you will:
- Master Matplotlib for creating professional financial charts
- Use Seaborn for advanced statistical visualizations
- Create interactive dashboards with Plotly
- Build candlestick charts and technical indicators
- Visualize portfolio performance and risk metrics
- Design clear, informative financial presentations
- Apply best practices for data visualization
- Create publication-ready charts and reports
5.1 Introduction to Financial Visualization
Why Visualization Matters in Finance
Numbers alone don't tell the full story. A well-designed chart can reveal patterns, trends, and anomalies that would take hours to discover in raw data. For financial analysts:
Communication: Visualizations help you present findings to clients, managers, and stakeholders who may not be data experts.
Pattern Recognition: Human brains are wired for visual processing. Charts make patterns obvious.
Decision Making: Clear visualizations enable faster, better-informed decisions.
Storytelling: Data tells a story—visualization is how you make it compelling.
The Three Main Libraries
Matplotlib: The foundation. Powerful but requires more code. Best for customization and publication-quality static charts.
Seaborn: Built on Matplotlib. Makes beautiful statistical plots with less code. Perfect for exploratory analysis.
Plotly: Interactive charts. Allows zooming, hovering for details, and exporting. Ideal for dashboards and presentations.
We'll master all three, knowing when to use each.
5.2 Matplotlib Fundamentals
Installation and Setup
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import yfinance as yf
# Set style for better-looking plots
plt.style.use('seaborn-v0_8-darkgrid')
# Set default figure size
plt.rcParams['figure.figsize'] = (12, 6)
Your First Price Chart
# Download stock data
data = yf.download('AAPL', start='2023-01-01', end='2024-01-01', progress=False)
prices = data['Adj Close']
# Create a simple line chart
plt.figure(figsize=(12, 6))
plt.plot(prices.index, prices.values, linewidth=2, color='blue')
plt.title('Apple Stock Price (2023)', fontsize=16, fontweight='bold')
plt.xlabel('Date', fontsize=12)
plt.ylabel('Price ($)', fontsize=12)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Customizing Your Charts
# Create a more professional chart
fig, ax = plt.subplots(figsize=(14, 7))
# Plot the data
ax.plot(prices.index, prices.values, linewidth=2.5, color='#2E86AB', label='AAPL')
# Customize
ax.set_title('Apple Inc. Stock Price Performance', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('Date', fontsize=13, fontweight='bold')
ax.set_ylabel('Adjusted Close Price ($)', fontsize=13, fontweight='bold')
ax.legend(loc='upper left', fontsize=11, frameon=True, shadow=True)
ax.grid(True, alpha=0.3, linestyle='--')
# Format y-axis as currency
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: f'${y:.0f}'))
# Add a horizontal line at the mean price
mean_price = prices.mean()
ax.axhline(y=mean_price, color='red', linestyle='--', linewidth=1.5,
label=f'Mean: ${mean_price:.2f}', alpha=0.7)
plt.tight_layout()
plt.show()
Multiple Lines on One Chart
# Download multiple stocks
tickers = ['AAPL', 'MSFT', 'GOOGL']
data = yf.download(tickers, start='2023-01-01', end='2024-01-01', progress=False)
prices = data['Adj Close']
# Normalize to 100 for comparison
normalized = (prices / prices.iloc[0]) * 100
# Plot
fig, ax = plt.subplots(figsize=(14, 7))
colors = {'AAPL': '#2E86AB', 'MSFT': '#A23B72', 'GOOGL': '#F18F01'}
for ticker in tickers:
ax.plot(normalized.index, normalized[ticker],
linewidth=2.5, label=ticker, color=colors[ticker])
ax.set_title('Tech Stock Performance Comparison (Normalized to 100)',
fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('Date', fontsize=13, fontweight='bold')
ax.set_ylabel('Normalized Price (Base = 100)', fontsize=13, fontweight='bold')
ax.legend(loc='best', fontsize=12, frameon=True, shadow=True)
ax.grid(True, alpha=0.3, linestyle='--')
ax.axhline(y=100, color='gray', linestyle=':', linewidth=1, alpha=0.5)
plt.tight_layout()
plt.show()
Subplots: Multiple Charts
# Download data
data = yf.download('AAPL', start='2023-01-01', end='2024-01-01', progress=False)
# Create subplots
fig, axes = plt.subplots(2, 1, figsize=(14, 10))
# Top chart: Price
axes[0].plot(data.index, data['Adj Close'], linewidth=2, color='#2E86AB')
axes[0].set_title('Apple Stock Price', fontsize=14, fontweight='bold')
axes[0].set_ylabel('Price ($)', fontsize=12)
axes[0].grid(True, alpha=0.3)
# Bottom chart: Volume
axes[1].bar(data.index, data['Volume'], color='#A23B72', alpha=0.7)
axes[1].set_title('Trading Volume', fontsize=14, fontweight='bold')
axes[1].set_xlabel('Date', fontsize=12)
axes[1].set_ylabel('Volume', fontsize=12)
axes[1].grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.show()
Bar Charts for Returns
# Calculate monthly returns
data = yf.download('AAPL', start='2023-01-01', end='2024-01-01', progress=False)
monthly_prices = data['Adj Close'].resample('M').last()
monthly_returns = monthly_prices.pct_change().dropna() * 100
# Create bar chart
fig, ax = plt.subplots(figsize=(14, 6))
# Color bars based on positive/negative
colors = ['green' if x > 0 else 'red' for x in monthly_returns]
ax.bar(monthly_returns.index, monthly_returns.values, color=colors, alpha=0.7, edgecolor='black')
ax.set_title('Apple Monthly Returns (2023)', fontsize=16, fontweight='bold', pad=20)
ax.set_xlabel('Month', fontsize=12)
ax.set_ylabel('Return (%)', fontsize=12)
ax.axhline(y=0, color='black', linewidth=1)
ax.grid(True, alpha=0.3, axis='y')
# Format dates
import matplotlib.dates as mdates
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b'))
plt.tight_layout()
plt.show()
Scatter Plots for Correlation
# Download two stocks
data = yf.download(['AAPL', 'MSFT'], start='2023-01-01', end='2024-01-01', progress=False)
returns = data['Adj Close'].pct_change().dropna() * 100
# Create scatter plot
fig, ax = plt.subplots(figsize=(10, 8))
ax.scatter(returns['AAPL'], returns['MSFT'], alpha=0.5, s=50, edgecolors='black', linewidth=0.5)
ax.set_title('AAPL vs MSFT Daily Returns Correlation', fontsize=16, fontweight='bold', pad=20)
ax.set_xlabel('AAPL Daily Return (%)', fontsize=12)
ax.set_ylabel('MSFT Daily Return (%)', fontsize=12)
ax.grid(True, alpha=0.3)
# Add regression line
from scipy import stats
slope, intercept, r_value, p_value, std_err = stats.linregress(returns['AAPL'], returns['MSFT'])
line_x = np.array([returns['AAPL'].min(), returns['AAPL'].max()])
line_y = slope * line_x + intercept
ax.plot(line_x, line_y, color='red', linewidth=2, label=f'R² = {r_value**2:.3f}')
ax.legend(fontsize=11)
plt.tight_layout()
plt.show()
Histograms for Return Distribution
# Calculate returns
data = yf.download('AAPL', start='2023-01-01', end='2024-01-01', progress=False)
returns = data['Adj Close'].pct_change().dropna() * 100
# Create histogram
fig, ax = plt.subplots(figsize=(12, 6))
ax.hist(returns, bins=50, color='#2E86AB', alpha=0.7, edgecolor='black')
ax.set_title('Distribution of Daily Returns - Apple', fontsize=16, fontweight='bold', pad=20)
ax.set_xlabel('Daily Return (%)', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.grid(True, alpha=0.3, axis='y')
# Add vertical lines for mean and median
ax.axvline(returns.mean(), color='red', linestyle='--', linewidth=2,
label=f'Mean: {returns.mean():.2f}%')
ax.axvline(returns.median(), color='green', linestyle='--', linewidth=2,
label=f'Median: {returns.median():.2f}%')
ax.legend(fontsize=11)
plt.tight_layout()
plt.show()
5.3 Seaborn for Statistical Visualizations
Setup
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import yfinance as yf
# Set Seaborn style
sns.set_style("whitegrid")
sns.set_palette("husl")
Correlation Heatmap
# Download multiple stocks
tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'META', 'TSLA']
data = yf.download(tickers, start='2023-01-01', end='2024-01-01', progress=False)
returns = data['Adj Close'].pct_change().dropna()
# Calculate correlation
correlation = returns.corr()
# Create heatmap
plt.figure(figsize=(10, 8))
sns.heatmap(correlation, annot=True, fmt='.3f', cmap='coolwarm',
center=0, square=True, linewidths=1, cbar_kws={"shrink": 0.8})
plt.title('Stock Returns Correlation Matrix', fontsize=16, fontweight='bold', pad=20)
plt.tight_layout()
plt.show()
Distribution Plot with KDE
# Download data
data = yf.download('AAPL', start='2023-01-01', end='2024-01-01', progress=False)
returns = data['Adj Close'].pct_change().dropna() * 100
# Create distribution plot
plt.figure(figsize=(12, 6))
sns.histplot(returns, bins=50, kde=True, color='#2E86AB', edgecolor='black')
plt.title('Distribution of AAPL Daily Returns', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('Daily Return (%)', fontsize=12)
plt.ylabel('Frequency', fontsize=12)
# Add normal distribution for comparison
from scipy import stats
x = np.linspace(returns.min(), returns.max(), 100)
normal_dist = stats.norm.pdf(x, returns.mean(), returns.std())
plt.plot(x, normal_dist * len(returns) * (returns.max() - returns.min()) / 50,
color='red', linewidth=2, label='Normal Distribution')
plt.legend(fontsize=11)
plt.tight_layout()
plt.show()
Box Plots for Comparison
# Download multiple stocks
tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN']
data = yf.download(tickers, start='2023-01-01', end='2024-01-01', progress=False)
returns = data['Adj Close'].pct_change().dropna() * 100
# Prepare data for boxplot
returns_melted = returns.melt(var_name='Stock', value_name='Return')
# Create boxplot
plt.figure(figsize=(12, 6))
sns.boxplot(data=returns_melted, x='Stock', y='Return', palette='Set2')
plt.title('Distribution of Daily Returns by Stock', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('Stock', fontsize=12)
plt.ylabel('Daily Return (%)', fontsize=12)
plt.axhline(y=0, color='red', linestyle='--', linewidth=1, alpha=0.5)
plt.tight_layout()
plt.show()
Violin Plots
# Violin plot (combines box plot and KDE)
plt.figure(figsize=(12, 6))
sns.violinplot(data=returns_melted, x='Stock', y='Return', palette='muted')
plt.title('Return Distribution Comparison (Violin Plot)', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('Stock', fontsize=12)
plt.ylabel('Daily Return (%)', fontsize=12)
plt.axhline(y=0, color='red', linestyle='--', linewidth=1, alpha=0.5)
plt.tight_layout()
plt.show()
Pair Plot
# Pair plot for multiple stocks
stocks_sample = returns[['AAPL', 'MSFT', 'GOOGL']].sample(100) # Sample for clarity
sns.pairplot(stocks_sample, diag_kind='kde', plot_kws={'alpha': 0.6})
plt.suptitle('Pairwise Return Relationships', y=1.02, fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
Joint Plot
# Joint plot (scatter with marginal distributions)
returns_clean = returns.dropna()
sns.jointplot(data=returns_clean, x='AAPL', y='MSFT', kind='reg', height=8,
joint_kws={'alpha': 0.5}, color='#2E86AB')
plt.suptitle('AAPL vs MSFT Returns with Marginal Distributions', y=1.02,
fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
5.4 Advanced Chart Types
Candlestick Charts
import plotly.graph_objects as go
import yfinance as yf
# Download data
data = yf.download('AAPL', start='2023-11-01', end='2023-12-01', progress=False)
# Create candlestick chart
fig = go.Figure(data=[go.Candlestick(
x=data.index,
open=data['Open'],
high=data['High'],
low=data['Low'],
close=data['Close'],
name='AAPL'
)])
fig.update_layout(
title='Apple Stock - Candlestick Chart',
title_font_size=20,
xaxis_title='Date',
yaxis_title='Price ($)',
template='plotly_white',
height=600,
xaxis_rangeslider_visible=False
)
fig.show()
Area Charts for Cumulative Returns
# Calculate cumulative returns
data = yf.download('AAPL', start='2023-01-01', end='2024-01-01', progress=False)
returns = data['Adj Close'].pct_change()
cumulative = (1 + returns).cumprod()
# Create area chart
fig, ax = plt.subplots(figsize=(14, 7))
ax.fill_between(cumulative.index, 1, cumulative.values, alpha=0.3, color='#2E86AB')
ax.plot(cumulative.index, cumulative.values, linewidth=2, color='#2E86AB')
ax.axhline(y=1, color='black', linestyle='--', linewidth=1, alpha=0.5)
ax.set_title('Apple Cumulative Returns (2023)', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('Date', fontsize=13)
ax.set_ylabel('Growth of $1', fontsize=13)
ax.grid(True, alpha=0.3)
# Shade profitable vs unprofitable regions differently
ax.fill_between(cumulative.index, 1, cumulative.values,
where=(cumulative.values >= 1), alpha=0.3, color='green',
interpolate=True, label='Profit')
ax.fill_between(cumulative.index, 1, cumulative.values,
where=(cumulative.values < 1), alpha=0.3, color='red',
interpolate=True, label='Loss')
ax.legend(fontsize=11)
plt.tight_layout()
plt.show()
Drawdown Chart
# Calculate drawdown
data = yf.download('AAPL', start='2023-01-01', end='2024-01-01', progress=False)
returns = data['Adj Close'].pct_change()
cumulative = (1 + returns).cumprod()
running_max = cumulative.expanding().max()
drawdown = (cumulative - running_max) / running_max * 100
# Create drawdown chart
fig, ax = plt.subplots(figsize=(14, 7))
ax.fill_between(drawdown.index, 0, drawdown.values, color='red', alpha=0.3)
ax.plot(drawdown.index, drawdown.values, linewidth=2, color='darkred')
ax.axhline(y=0, color='black', linestyle='-', linewidth=1)
ax.set_title('Apple Drawdown Analysis', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('Date', fontsize=13)
ax.set_ylabel('Drawdown (%)', fontsize=13)
ax.grid(True, alpha=0.3)
# Highlight maximum drawdown
max_dd = drawdown.min()
max_dd_date = drawdown.idxmin()
ax.scatter(max_dd_date, max_dd, color='red', s=200, zorder=5, marker='v')
ax.annotate(f'Max DD: {max_dd:.2f}%',
xy=(max_dd_date, max_dd),
xytext=(20, 20), textcoords='offset points',
fontsize=11, fontweight='bold',
bbox=dict(boxstyle='round,pad=0.5', facecolor='yellow', alpha=0.7),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0'))
plt.tight_layout()
plt.show()
Moving Average Chart with Crossovers
# Download data
data = yf.download('AAPL', start='2023-01-01', end='2024-01-01', progress=False)
prices = data['Adj Close']
# Calculate moving averages
ma_20 = prices.rolling(window=20).mean()
ma_50 = prices.rolling(window=50).mean()
# Create chart
fig, ax = plt.subplots(figsize=(14, 7))
ax.plot(prices.index, prices.values, linewidth=2, color='black', label='Price', alpha=0.7)
ax.plot(ma_20.index, ma_20.values, linewidth=2, color='blue', label='20-day MA')
ax.plot(ma_50.index, ma_50.values, linewidth=2, color='red', label='50-day MA')
# Find crossovers
signal = pd.DataFrame({'MA20': ma_20, 'MA50': ma_50})
signal['Position'] = 0
signal.loc[signal['MA20'] > signal['MA50'], 'Position'] = 1
signal['Signal'] = signal['Position'].diff()
# Plot buy signals (golden cross)
buys = signal[signal['Signal'] == 1]
ax.scatter(buys.index, prices.loc[buys.index], color='green', marker='^',
s=200, label='Buy Signal', zorder=5)
# Plot sell signals (death cross)
sells = signal[signal['Signal'] == -1]
ax.scatter(sells.index, prices.loc[sells.index], color='red', marker='v',
s=200, label='Sell Signal', zorder=5)
ax.set_title('Apple Stock with Moving Average Crossovers', fontsize=18, fontweight='bold', pad=20)
ax.set_xlabel('Date', fontsize=13)
ax.set_ylabel('Price ($)', fontsize=13)
ax.legend(loc='best', fontsize=11)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
5.5 Interactive Visualizations with Plotly
Interactive Price Chart
import plotly.graph_objects as go
import yfinance as yf
# Download data
data = yf.download('AAPL', start='2023-01-01', end='2024-01-01', progress=False)
# Create interactive line chart
fig = go.Figure()
fig.add_trace(go.Scatter(
x=data.index,
y=data['Adj Close'],
mode='lines',
name='AAPL',
line=dict(color='#2E86AB', width=2),
hovertemplate='Date: %{x}<br>Price: $%{y:.2f}<extra></extra>'
))
fig.update_layout(
title='Apple Stock Price - Interactive Chart',
title_font_size=20,
xaxis_title='Date',
yaxis_title='Price ($)',
template='plotly_white',
hovermode='x unified',
height=600
)
fig.show()
Interactive Comparison Chart
# Download multiple stocks
tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN']
data = yf.download(tickers, start='2023-01-01', end='2024-01-01', progress=False)
prices = data['Adj Close']
# Normalize
normalized = (prices / prices.iloc[0]) * 100
# Create figure
fig = go.Figure()
colors = {'AAPL': '#2E86AB', 'MSFT': '#A23B72', 'GOOGL': '#F18F01', 'AMZN': '#06A77D'}
for ticker in tickers:
fig.add_trace(go.Scatter(
x=normalized.index,
y=normalized[ticker],
mode='lines',
name=ticker,
line=dict(color=colors[ticker], width=2),
hovertemplate=f'{ticker}<br>Value: %{{y:.2f}}<extra></extra>'
))
fig.update_layout(
title='Tech Stocks Performance Comparison (Normalized)',
title_font_size=20,
xaxis_title='Date',
yaxis_title='Normalized Price (Base = 100)',
template='plotly_white',
hovermode='x unified',
height=600,
legend=dict(x=0.01, y=0.99)
)
# Add range slider
fig.update_xaxes(rangeslider_visible=True)
fig.show()
Interactive Candlestick with Volume
# Download data
data = yf.download('AAPL', start='2023-11-01', end='2023-12-01', progress=False)
# Create subplots
from plotly.subplots import make_subplots
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.03,
row_heights=[0.7, 0.3]
)
# Candlestick chart
fig.add_trace(
go.Candlestick(
x=data.index,
open=data['Open'],
high=data['High'],
low=data['Low'],
close=data['Close'],
name='AAPL'
),
row=1, col=1
)
# Volume bar chart
colors = ['red' if data['Close'][i] < data['Open'][i] else 'green'
for i in range(len(data))]
fig.add_trace(
go.Bar(
x=data.index,
y=data['Volume'],
marker_color=colors,
name='Volume',
showlegend=False
),
row=2, col=1
)
fig.update_layout(
title='AAPL - Price and Volume',
title_font_size=20,
template='plotly_white',
height=800,
xaxis_rangeslider_visible=False
)
fig.update_yaxes(title_text="Price ($)", row=1, col=1)
fig.update_yaxes(title_text="Volume", row=2, col=1)
fig.show()
3D Scatter Plot for Portfolio Analysis
import plotly.express as px
# Create sample portfolio data
np.random.seed(42)
n_stocks = 20
portfolio_data = pd.DataFrame({
'Stock': [f'Stock_{i}' for i in range(n_stocks)],
'Return': np.random.normal(0.10, 0.15, n_stocks),
'Volatility': np.random.uniform(0.15, 0.40, n_stocks),
'Sharpe': np.random.uniform(0.5, 2.0, n_stocks),
'Size': np.random.uniform(1000, 10000, n_stocks)
})
# Create 3D scatter plot
fig = px.scatter_3d(
portfolio_data,
x='Return',
y='Volatility',
z='Sharpe',
size='Size',
color='Sharpe',
hover_name='Stock',
hover_data={'Return': ':.2%', 'Volatility': ':.2%', 'Sharpe': ':.2f'},
color_continuous_scale='Viridis',
title='Portfolio Analysis: Risk, Return, and Sharpe Ratio'
)
fig.update_layout(
scene=dict(
xaxis_title='Expected Return',
yaxis_title='Volatility',
zaxis_title='Sharpe Ratio'
),
height=700
)
fig.show()
5.6 Building a Comprehensive Dashboard
Complete Stock Analysis Dashboard
import matplotlib.pyplot as plt
import seaborn as sns
import yfinance as yf
import pandas as pd
import numpy as np
def create_stock_dashboard(ticker, start_date, end_date):
"""
Create a comprehensive 6-panel dashboard for stock analysis
"""
# Download data
data = yf.download(ticker, start=start_date, end=end_date, progress=False)
prices = data['Adj Close']
returns = prices.pct_change().dropna()
# Calculate metrics
cumulative = (1 + returns).cumprod()
running_max = cumulative.expanding().max()
drawdown = (cumulative - running_max) / running_max * 100
ma_20 = prices.rolling(window=20).mean()
ma_50 = prices.rolling(window=50).mean()
# Create figure with subplots
fig = plt.figure(figsize=(18, 12))
gs = fig.add_gridspec(3, 2, hspace=0.3, wspace=0.3)
# 1. Price Chart with Moving Averages
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(prices.index, prices.values, linewidth=2, color='black', label='Price')
ax1.plot(ma_20.index, ma_20.values, linewidth=1.5, color='blue', label='20-day MA', alpha=0.7)
ax1.plot(ma_50.index, ma_50.values, linewidth=1.5, color='red', label='50-day MA', alpha=0.7)
ax1.set_title(f'{ticker} Price Chart with Moving Averages', fontsize=14, fontweight='bold')
ax1.set_ylabel('Price ($)', fontsize=11)
ax1.legend(loc='best', fontsize=10)
ax1.grid(True, alpha=0.3)
# 2. Volume
ax2 = fig.add_subplot(gs[1, 0])
ax2.bar(data.index, data['Volume'], color='#2E86AB', alpha=0.6)
ax2.set_title('Trading Volume', fontsize=14, fontweight='bold')
ax2.set_ylabel('Volume', fontsize=11)
ax2.grid(True, alpha=0.3, axis='y')
# 3. Returns Distribution
ax3 = fig.add_subplot(gs[1, 1])
ax3.hist(returns * 100, bins=50, color='#A23B72', alpha=0.7, edgecolor='black')
ax3.axvline(returns.mean() * 100, color='red', linestyle='--', linewidth=2,
label=f'Mean: {returns.mean()*100:.2f}%')
ax3.set_title('Daily Returns Distribution', fontsize=14, fontweight='bold')
ax3.set_xlabel('Return (%)', fontsize=11)
ax3.set_ylabel('Frequency', fontsize=11)
ax3.legend(fontsize=10)
ax3.grid(True, alpha=0.3)
# 4. Cumulative Returns
ax4 = fig.add_subplot(gs[2, 0])
ax4.fill_between(cumulative.index, 1, cumulative.values, alpha=0.3, color='#06A77D')
ax4.plot(cumulative.index, cumulative.values, linewidth=2, color='#06A77D')
ax4.axhline(y=1, color='black', linestyle='--', linewidth=1)
ax4.set_title('Cumulative Returns', fontsize=14, fontweight='bold')
ax4.set_ylabel('Growth of $1', fontsize=11)
ax4.grid(True, alpha=0.3)
# 5. Drawdown
ax5 = fig.add_subplot(gs[2, 1])
ax5.fill_between(drawdown.index, 0, drawdown.values, color='red', alpha=0.3)
ax5.plot(drawdown.index, drawdown.values, linewidth=2, color='darkred')
ax5.axhline(y=0, color='black', linestyle='-', linewidth=1)
max_dd = drawdown.min()
ax5.set_title(f'Drawdown (Max: {max_dd:.2f}%)', fontsize=14, fontweight='bold')
ax5.set_ylabel('Drawdown (%)', fontsize=11)
ax5.grid(True, alpha=0.3)
# Overall title
fig.suptitle(f'{ticker} Stock Analysis Dashboard', fontsize=20, fontweight='bold', y=0.995)
plt.tight_layout()
plt.show()
# Print summary statistics
print("\n" + "="*60)
print(f"SUMMARY STATISTICS: {ticker}")
print("="*60)
print(f"Period: {start_date} to {end_date}")
print(f"Total Return: {(cumulative.iloc[-1] - 1)*100:.2f}%")
print(f"Annualized Return: {((1 + returns.mean())**252 - 1)*100:.2f}%")
print(f"Annualized Volatility: {returns.std() * np.sqrt(252) * 100:.2f}%")
print(f"Sharpe Ratio: {(returns.mean() / returns.std()) * np.sqrt(252):.2f}")
print(f"Maximum Drawdown: {max_dd:.2f}%")
print(f"Best Day: {returns.max()*100:.2f}%")
print(f"Worst Day: {returns.min()*100:.2f}%")
print("="*60)
# Create dashboard
create_stock_dashboard('AAPL', '2023-01-01', '2024-01-01')
5.7 Best Practices for Financial Visualization
Design Principles
1. Clarity Over Complexity
- Remove chart junk (unnecessary decorations)
- Use clear, descriptive titles and labels
- Choose appropriate scales
- Make legends readable
2. Consistent Color Schemes
# Define a professional color palette
COLORS = {
'primary': '#2E86AB', # Blue
'secondary': '#A23B72', # Purple
'positive': '#06A77D', # Green
'negative': '#E63946', # Red
'neutral': '#6C757D', # Gray
'accent': '#F18F01' # Orange
}
# Use consistently across all charts
3. Context is Critical
# Bad: Just the number
plt.title('Stock Price')
# Good: Provide context
plt.title('Apple Inc. (AAPL) Stock Price - 2023 Performance',
fontsize=16, fontweight='bold')
4. Appropriate Chart Types
| Data Type | Best Chart Type |
|---|---|
| Time series | Line chart |
| Comparisons | Bar chart |
| Distribution | Histogram, box plot |
| Correlation | Scatter plot |
| Composition | Stacked area, pie chart |
| Relationship | Scatter plot, pair plot |
Common Mistakes to Avoid
1. Misleading Scales
# Bad: Y-axis doesn't start at zero for stocks (can exaggerate changes)
# Good: Use appropriate scale based on data range
2. Too Much Information
# Bad: Plotting 20 stocks on one chart
# Good: Focus on 3-5 key comparisons
3. Poor Color Choices
# Bad: Using red and green (colorblind unfriendly)
# Good: Use colorblind-safe palettes
# Colorblind-friendly palette
import seaborn as sns
sns.set_palette("colorblind")
Saving Figures
# Save with high quality
fig, ax = plt.subplots(figsize=(12, 6))
# ... create your plot ...
# Save as PNG (for presentations)
plt.savefig('stock_analysis.png', dpi=300, bbox_inches='tight')
# Save as PDF (for publications)
plt.savefig('stock_analysis.pdf', bbox_inches='tight')
# Save as SVG (vector graphics, scalable)
plt.savefig('stock_analysis.svg', bbox_inches='tight')
5.8 Practice Exercises
Exercise 1: Create a Multi-Stock Comparison Dashboard
# Your task: Build a dashboard comparing 4 stocks
# Include:
# 1. Normalized price comparison (line chart)
# 2. Volatility comparison (bar chart)
# 3. Correlation heatmap
# 4. Return distribution (box plots)
# Make it professional and publication-ready
Exercise 2: Technical Analysis Visualization
# Your task: Create a comprehensive technical analysis chart
# Include:
# 1. Candlestick chart
# 2. Volume bars (colored by price direction)
# 3. Multiple moving averages (20, 50, 200-day)
# 4. Bollinger Bands
# 5. RSI in a subplot
# Use a stock of your choice
Exercise 3: Portfolio Performance Report
# Your task: Create a visual portfolio performance report
# Given a portfolio of 5-10 stocks with weights:
# 1. Show individual stock performance
# 2. Show weighted portfolio returns over time
# 3. Display drawdown analysis
# 4. Create a risk-return scatter plot
# 5. Show correlation matrix
# Make it look like a professional report
Exercise 4: Interactive Dashboard
# Your task: Build an interactive Plotly dashboard
# Features:
# 1. Dropdown to select different stocks
# 2. Date range slider
# 3. Candlestick chart with volume
# 4. Moving averages (toggleable)
# 5. Zoom and pan functionality
# 6. Hover details showing OHLC and volume
Module 5 Summary
Congratulations! You've mastered financial data visualization.
What You've Accomplished
Matplotlib Mastery
- Creating professional static charts
- Customizing every element for clarity
- Building complex multi-panel layouts
- Using appropriate chart types for different data
Seaborn Proficiency
- Making beautiful statistical visualizations
- Creating correlation heatmaps
- Building distribution comparisons
- Using advanced plot types (pair plots, joint plots)
Plotly Expertise
- Creating interactive, explorable charts
- Building candlestick charts with volume
- Designing responsive dashboards
- Adding user interaction (sliders, dropdowns)
Advanced Skills
- Designing comprehensive dashboards
- Following visualization best practices
- Choosing appropriate colors and styles
- Creating publication-quality outputs
Real-World Capabilities
You can now:
- Present data insights to any audience effectively
- Create reports that tell compelling stories
- Build interactive tools for exploration
- Design professional-quality visualizations
- Communicate complex analysis clearly
What's Next
In Module 6, we'll dive into portfolio analytics—combining all your skills to analyze, optimize, and visualize complete investment portfolios. You'll learn Modern Portfolio Theory, calculate efficient frontiers, and build tools for professional portfolio management.
Before Moving Forward
Ensure you're comfortable with:
- Creating various chart types in Matplotlib
- Building statistical visualizations with Seaborn
- Making interactive charts with Plotly
- Applying design best practices
- Choosing the right visualization for your data
Practice Recommendations
- Daily Visualization: Create one chart per day analyzing different stocks
- Recreate Professional Charts: Find charts from Bloomberg or Financial Times and recreate them
- Build a Library: Create templates for your most-used chart types
- Get Feedback: Share visualizations and ask for critiques
- Iterate: Continuously improve your designs based on what communicates best
The Power of Visual Communication
Data analysis discovers insights; visualization communicates them. You now have both skills. The charts you create can influence investment decisions, inform strategy, and drive business outcomes. Use this power wisely and always prioritize clarity and honesty in your visualizations.
Your analytical toolkit is nearly complete. Now let's apply everything to the art and science of portfolio management.
Continue to Module 6: Portfolio Analytics →

