Returns Distribution Histogram — Makie.jl

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 Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# histogram-returns-distribution: Returns Distribution Histogram
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 92/100 | Created: 2026-09-02

using CairoMakie
using Colors
using Random
using Statistics
using Printf

Random.seed!(42)

# --- Theme tokens -------------------------------------------------------------
const THEME       = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG     = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const ELEVATED_BG = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420"
const INK         = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT    = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"
const IMPRINT_PALETTE = [
    colorant"#009E73", colorant"#C475FD", colorant"#4467A3", colorant"#BD8233",
    colorant"#AE3030", colorant"#2ABCCD", colorant"#954477", colorant"#99B314",
]
const ANYPLOT_AMBER = colorant"#DDCC77"  # warning — tail events beyond 2 std dev
const ANYPLOT_NEUTRAL = INK             # reference line — fitted normal curve

# --- Data -----------------------------------------------------------------
# One trading year of daily ETF returns (%). A minority of days carry a larger
# negative shock, producing the fat left tail and mild negative skew typical
# of real equity return series.
n_days = 252
base_returns = 1.1 .* randn(n_days) .+ 0.05
is_shock_day = rand(n_days) .< 0.07
crash_shocks = -abs.(2.6 .* randn(n_days))
daily_returns = ifelse.(is_shock_day, base_returns .+ crash_shocks, base_returns)

mean_return = mean(daily_returns)
std_return = std(daily_returns)
z_scores = (daily_returns .- mean_return) ./ std_return
skewness = mean(z_scores .^ 3)
excess_kurtosis = mean(z_scores .^ 4) - 3

# --- Histogram binning (manual, so tail bins can be recolored) ------------
n_bins = 22
lo, hi = minimum(daily_returns), maximum(daily_returns)
edges = range(lo, hi; length = n_bins + 1)
bin_width = step(edges)
counts = zeros(Int, n_bins)
for r in daily_returns
    idx = clamp(Int(floor((r - lo) / bin_width)) + 1, 1, n_bins)
    counts[idx] += 1
end
bin_centers = [edges[i] + bin_width / 2 for i in 1:n_bins]
densities = counts ./ (n_days * bin_width)  # density normalization

tail_threshold = 2 * std_return
is_tail_bin = [abs(c - mean_return) > tail_threshold for c in bin_centers]

# Normal distribution fitted to the sample, for comparison
normal_xs = range(lo, hi; length = 200)
normal_pdf(x) = exp(-0.5 * ((x - mean_return) / std_return)^2) / (std_return * sqrt(2π))
normal_ys = normal_pdf.(normal_xs)

# --- Plot -------------------------------------------------------------------
fig = Figure(
    resolution      = (1600, 900),
    fontsize        = 14,
    backgroundcolor = PAGE_BG,
)

ax = Axis(
    fig[1, 1];
    title              = "histogram-returns-distribution · julia · makie · anyplot.ai",
    titlesize          = 25,
    titlecolor         = INK,
    xlabel             = "Daily Return (%)",
    ylabel             = "Density",
    xlabelsize         = 14,
    ylabelsize         = 14,
    xlabelcolor        = INK,
    ylabelcolor        = INK,
    xticklabelsize     = 12,
    yticklabelsize     = 12,
    xticklabelcolor    = INK_SOFT,
    yticklabelcolor    = INK_SOFT,
    backgroundcolor    = PAGE_BG,
    topspinevisible    = false,
    rightspinevisible  = false,
    leftspinecolor     = INK_SOFT,
    bottomspinecolor   = INK_SOFT,
    xgridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),
    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),
    xminorgridvisible  = false,
    yminorgridvisible  = false,
)

barplot!(
    ax, bin_centers[.!is_tail_bin], densities[.!is_tail_bin];
    color = IMPRINT_PALETTE[1], width = bin_width, gap = 0.0,
    label = "Daily returns (|z| ≤ 2σ)",
)
barplot!(
    ax, bin_centers[is_tail_bin], densities[is_tail_bin];
    color = ANYPLOT_AMBER, width = bin_width, gap = 0.0,
    label = "Tail events (|z| > 2σ)",
)
lines!(
    ax, normal_xs, normal_ys;
    color = ANYPLOT_NEUTRAL, linewidth = 3, linestyle = :dash, label = "Normal fit",
)

# Legend built declaratively from the axis's own labeled plot objects, rather
# than hand-assembled proxy elements — Makie collects the legend entries for us.
Legend(
    fig[1, 2], ax;
    framevisible = false,
    labelcolor   = INK_SOFT,
    labelsize    = 12,
    backgroundcolor = PAGE_BG,
)

stats_text = @sprintf(
    "Mean: %.2f%%\nStd Dev: %.2f%%\nSkewness: %.2f\nExcess Kurtosis: %.2f",
    mean_return, std_return, skewness, excess_kurtosis,
)
poly!(
    ax, Rect2f(0.02, 0.79, 0.34, 0.17);
    color = (ELEVATED_BG, 0.92), strokecolor = INK_SOFT, strokewidth = 1,
    space = :relative,
)
text!(
    ax, 0.045, 0.935;
    text = stats_text, space = :relative, align = (:left, :top),
    color = INK, fontsize = 13, font = :bold,
)

# --- Save -------------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit = 2)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/histogram-returns-distribution/makie/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "histogram-returns-distribution",
  "language": "julia",
  "library": "makie",
  "page": "https://anyplot.ai/histogram-returns-distribution/julia/makie",
  "hub": "https://anyplot.ai/histogram-returns-distribution",
  "code_json": "https://api.anyplot.ai/specs/histogram-returns-distribution/makie/code",
  "spec_json": "https://api.anyplot.ai/specs/histogram-returns-distribution",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/histogram-returns-distribution/julia/makie/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/histogram-returns-distribution/julia/makie/plot-dark.png",
  "quality_score": 92.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Returns Distribution Histogram on anyplot.ai.

Other implementations