Returns Distribution Histogram — Matplotlib

A histogram showing the distribution of financial returns (daily, weekly, or monthly) with a normal distribution overlay for comparison. This visualization is essential for risk analysis, allowing analysts to assess whether returns follow a normal distribution, identify fat tails indicating higher-than-expected extreme events, and measure asymmetry through skewness. Key statistics are displayed directly on the plot for quick interpretation.

Returns Distribution Histogram rendered with Matplotlib

Python source (Matplotlib)

""" anyplot.ai
histogram-returns-distribution: Returns Distribution Histogram
Library: matplotlib 3.10.9 | Python 3.13.13
Quality: 90/100 | Updated: 2026-05-20
"""

import os

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Patch
from scipy import stats


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"

BRAND = "#009E73"  # Okabe-Ito pos 1 — main histogram bars
TAIL_COLOR = "#C475FD"  # Okabe-Ito pos 2 — tail regions
CURVE_COLOR = "#4467A3"  # Okabe-Ito pos 3 — normal distribution curve

# Data — simulate daily stock returns with slight fat tails (t-distribution df=8)
np.random.seed(42)
n_days = 252
returns = np.random.standard_t(df=8, size=n_days) * 0.012 + 0.0004  # ~1.2% daily vol

# Key statistics
mean_ret = np.mean(returns) * 100
std_ret = np.std(returns) * 100
skewness = stats.skew(returns)
kurtosis = stats.kurtosis(returns)
returns_pct = returns * 100

# Tail thresholds (±2σ) and observation counts
lower_tail = mean_ret - 2 * std_ret
upper_tail = mean_ret + 2 * std_ret
n_left = int(np.sum(returns_pct < lower_tail))
n_right = int(np.sum(returns_pct > upper_tail))
n_tail = n_left + n_right
tail_pct = n_tail / n_days * 100
expected_tail_pct = (1 - stats.norm.cdf(2)) * 2 * 100  # ≈ 4.55% beyond ±2σ under normality

# Normal distribution overlay range
x_lo = returns_pct.min() - 0.5
x_hi = returns_pct.max() + 0.5
x_range = np.linspace(x_lo, x_hi, 300)
normal_pdf = stats.norm.pdf(x_range, mean_ret, std_ret)

# Plot
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)
ax.set_facecolor(PAGE_BG)

# Histogram with density normalization
n, bins, patches = ax.hist(
    returns_pct, bins=35, density=True, color=BRAND, alpha=0.75, edgecolor=PAGE_BG, linewidth=0.8
)

# Color tail bins with distinct Okabe-Ito highlight
for i, patch in enumerate(patches):
    bin_center = (bins[i] + bins[i + 1]) / 2
    if bin_center < lower_tail or bin_center > upper_tail:
        patch.set_facecolor(TAIL_COLOR)
        patch.set_alpha(0.90)

# Subtle axvspan background tinting for tail risk zones (below histogram bars)
ax.axvspan(x_lo, lower_tail, alpha=0.07, color=TAIL_COLOR, zorder=0)
ax.axvspan(upper_tail, x_hi, alpha=0.07, color=TAIL_COLOR, zorder=0)

# Shade theoretical tail areas under the normal curve (fill_between)
x_left = np.linspace(x_lo, lower_tail, 150)
x_right = np.linspace(upper_tail, x_hi, 150)
ax.fill_between(x_left, stats.norm.pdf(x_left, mean_ret, std_ret), color=TAIL_COLOR, alpha=0.15)
ax.fill_between(x_right, stats.norm.pdf(x_right, mean_ret, std_ret), color=TAIL_COLOR, alpha=0.15)

# Normal distribution overlay curve
(normal_line,) = ax.plot(x_range, normal_pdf, color=CURVE_COLOR, linewidth=2.5, linestyle="--", label="Normal fit")

# Vertical reference lines
ax.axvline(mean_ret, color=INK, linewidth=1.8, linestyle="-", alpha=0.8, label=f"Mean ({mean_ret:.3f}%)")
ax.axvline(lower_tail, color=INK_MUTED, linewidth=1.5, linestyle=":", alpha=0.7)
ax.axvline(upper_tail, color=INK_MUTED, linewidth=1.5, linestyle=":", alpha=0.7)

# Tail annotations with observation counts — fontsize=8 for mobile legibility
ax.annotate(
    f"Left tail\n{n_left} obs ({n_left / n_days * 100:.1f}%)",
    xy=(lower_tail - 0.6, 0.02),
    fontsize=8,
    ha="center",
    color=INK_MUTED,
)
ax.annotate(
    f"Right tail\n{n_right} obs ({n_right / n_days * 100:.1f}%)",
    xy=(upper_tail + 0.7, 0.02),
    fontsize=8,
    ha="center",
    color=INK_MUTED,
)

# Statistics text box — includes actual vs expected tail % for fat-tail emphasis
stats_text = (
    f"Mean:      {mean_ret:.3f}%\n"
    f"Std Dev:   {std_ret:.3f}%\n"
    f"Skewness: {skewness:.3f}\n"
    f"Kurtosis:  {kurtosis:.3f}\n"
    f"Fat tails: {tail_pct:.1f}% (norm: {expected_tail_pct:.1f}%)"
)
ax.text(
    0.97,
    0.97,
    stats_text,
    transform=ax.transAxes,
    fontsize=8,
    verticalalignment="top",
    horizontalalignment="right",
    family="monospace",
    color=INK,
    bbox={"boxstyle": "round,pad=0.5", "facecolor": ELEVATED_BG, "edgecolor": INK_SOFT, "alpha": 0.9},
)

# Style
ax.set_xlabel("Daily Returns (%)", fontsize=10, color=INK)
ax.set_ylabel("Density", fontsize=10, color=INK)
ax.set_title(
    "histogram-returns-distribution · python · matplotlib · anyplot.ai", fontsize=12, fontweight="medium", color=INK
)
ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)

# Spines
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
for spine in ("left", "bottom"):
    ax.spines[spine].set_color(INK_SOFT)

# Grid (subtle, solid, y-axis only)
ax.set_axisbelow(True)
ax.yaxis.grid(True, alpha=0.12, linewidth=0.8, color=INK)

# Legend with correct patch colors
hist_patch = Patch(facecolor=BRAND, edgecolor=PAGE_BG, alpha=0.75, label="Returns")
tail_patch = Patch(facecolor=TAIL_COLOR, edgecolor=PAGE_BG, alpha=0.90, label="Tail Regions (>2σ)")
handles, _ = ax.get_legend_handles_labels()
leg = ax.legend(handles=[hist_patch, tail_patch] + handles, fontsize=8, loc="upper left")
leg.get_frame().set_facecolor(ELEVATED_BG)
leg.get_frame().set_edgecolor(INK_SOFT)
plt.setp(leg.get_texts(), color=INK_SOFT)

# Layout — no bbox_inches='tight' on savefig per prompts/library/matplotlib.md
fig.subplots_adjust(left=0.08, right=0.97, top=0.92, bottom=0.12)

# Save
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)
plt.close()

Part of Returns Distribution Histogram on anyplot.ai.

Other implementations