Stock Market Participation Predicts P/E Ratios
Revised June 2026: the original data had weak sourcing and couldn't be fully replicated. With cleaner sources (HelloSafe, worldperatio.com, n=17), the relationship holds but is weaker: r=0.63, p=0.007, vs. r=0.89 originally. The revised chart is at the bottom, below the original.
Across developed markets, the share of the population that owns stocks predicts how expensive that market is. More buyers who aren’t purely return-driven bids up prices relative to earnings.
The EU is actively trying to boost retail participation through the Capital Markets Union and the Pan-European Pension Product (PEPP), both designed to push European retail participation upward over the coming decades. If they succeed, the model suggests European P/E ratios will follow. The continent is currently clustered at the low-participation, low-valuation end of this chart. Invest? 🚀 Nah, is priced in right? Worth keeping in mind before calling elevated multiples a bubble.

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import linregress
# Participation rate (%) vs P/E ratio, developed markets, approx 2023-2024
# Sources: participation from national surveys / ECB / Fed;
# P/E from MSCI / StarCapital country estimates
data = {
"United States": (55, 26.2),
"Australia": (38, 21.1),
"Canada": (49, 19.2),
"United Kingdom": (30, 17.5),
"Japan": (14, 15.4),
"Germany": (12, 12.9),
"Sweden": (22, 19.5),
"Netherlands": (27, 16.8),
"France": (15, 15.2),
"Italy": (10, 12.5),
"Switzerland": (30, 20.1),
"South Korea": (15, 11.8),
"Norway": (25, 16.2),
"Spain": (13, 13.1),
}
countries = list(data.keys())
x = np.array([data[c][0] for c in countries])
y = np.array([data[c][1] for c in countries])
slope, intercept, r, _, _ = linregress(x, y)
r2 = r**2
trendline_x = np.linspace(x.min()-2, x.max()+2, 100)
trendline_y = slope * trendline_x + intercept
fig, ax = plt.subplots(figsize=(12, 7))
ax.scatter(x, y, marker='x', color='blue', s=100, zorder=5)
ax.plot(trendline_x, trendline_y, 'r--', linewidth=2, label=f'Fitted line (r={r:.2f}, r²={r2:.2f})')
for c in countries:
ax.annotate(c, (data[c][0], data[c][1]), textcoords="offset points", xytext=(6, 4), fontsize=9)
ax.set_xlabel('Stock Market Participation Rate (%)', fontsize=12)
ax.set_ylabel('P/E Ratio', fontsize=12)
ax.set_title('Stock Market Participation Rate vs. P/E Ratio (Developed Markets)', fontsize=14)
ax.grid(True, linestyle='--', alpha=0.5)
ax.legend(fontsize=11)
plt.tight_layout()
plt.savefig('participation-vs-pe.png', dpi=150, bbox_inches='tight')
Edit (June 30, 2026): Revisited this post. Only weak source info was provided originally and results could not be fully replicated. Results get weaker but remain significant (r=0.63, p=0.007 vs original r=0.89).
Participation from HelloSafe’s 2025 cross-country study (household direct + indirect equity ownership, 2023-2024, 5-10% margin of error). Trailing P/E from worldperatio.com via country ETF proxies, snapshot June 26, 2026. Norway, Denmark, and South Korea excluded: no HelloSafe participation figure available using a consistent definition.

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import linregress
# Participation: HelloSafe, "Stock ownership by country" (2023-2024)
# https://hellosafe.ca/en/investing/broker/stock-ownership
# Valuation: worldperatio.com, trailing P/E via country-ETF proxies
# https://worldperatio.com/, snapshot 26 June 2026
data = {
"United States": (55.0, 26.06),
"Canada": (49.0, 18.94),
"Australia": (37.0, 19.95),
"United Kingdom": (33.0, 17.54),
"Sweden": (22.0, 14.78),
"Switzerland": (17.6, 23.20),
"Ireland": (17.0, 15.14),
"Japan": (15.2, 18.84),
"France": (15.1, 17.30),
"Germany": (14.2, 17.05),
"Netherlands": (14.0, 18.92),
"Hong Kong": (13.8, 17.33),
"Spain": (12.5, 15.06),
"Singapore": (8.3, 16.32),
"Italy": (7.0, 13.92),
"Austria": (5.6, 14.18),
"Belgium": (5.0, 18.46),
}
countries = list(data.keys())
x = np.array([data[c][0] for c in countries])
y = np.array([data[c][1] for c in countries])
slope, intercept, r, p, _ = linregress(x, y)
r2 = r**2
trendline_x = np.linspace(x.min()-3, x.max()+3, 100)
trendline_y = slope * trendline_x + intercept
fig, ax = plt.subplots(figsize=(12, 7))
ax.scatter(x, y, marker='x', color='blue', s=100, zorder=5)
ax.plot(trendline_x, trendline_y, 'r--', linewidth=2,
label=f'Fitted line (r={r:.2f}, r²={r2:.2f}, p={p:.3f}, n={len(countries)})')
for c in countries:
ax.annotate(c, (data[c][0], data[c][1]), textcoords="offset points", xytext=(6, 4), fontsize=9)
ax.set_xlabel('Household Equity Participation Rate (%) — HelloSafe, 2023-2024', fontsize=11)
ax.set_ylabel('Trailing P/E Ratio — worldperatio.com, 26 Jun 2026', fontsize=11)
ax.set_title('Stock Market Participation vs. Trailing P/E (17 Developed Markets)', fontsize=13)
ax.grid(True, linestyle='--', alpha=0.5)
ax.legend(fontsize=10)
plt.tight_layout()
plt.savefig('participation-vs-pe-revisited.png', dpi=150, bbox_inches='tight')