"""S&P 500 P/E and Dividend Yield Charts""" import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from src.data.market_bubbles import sp500_pe, sp500_dividend_yield from src.utils.styling import get_theme, EXPORT_DPI, BUBBLE_ZONE, WARNING_ZONE, NORMAL_ZONE, GRAY_DARK from src.utils.export import save_chart_tight def plot_pe_dividend() -> str: """Generate 2-panel S&P 500 P/E Ratio and Dividend Yield chart.""" plt.rcParams.update(get_theme()) fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 10), sharex=True) # ── Panel 1: P/E ─────────────────────────────────────────────────────── years_pe = [d["year"] for d in sp500_pe] values_pe = [d["value"] for d in sp500_pe] ax1.plot(years_pe, values_pe, color=GRAY_DARK, linewidth=1.5) ax1.axhspan(0, 15, alpha=0.15, color=NORMAL_ZONE) ax1.axhspan(15, 25, alpha=0.15, color=WARNING_ZONE) ax1.axhspan(25, 80, alpha=0.15, color=BUBBLE_ZONE) ax1.axhline(y=18.2, color="#333", linestyle="--", linewidth=1, alpha=0.7) ax1.text(1960, 18.7, "Mean: 18.2", fontsize=10) ax1.set_ylabel("P/E Ratio", fontsize=12) ax1.set_ylim(0, 80) pe_events = [ (1999, 32.92, "1999 Peak"), (2009, 70.91, "2009 Anomaly"), (2021, 35.96, "2021"), (2026, 29.60, "2026"), ] for year, val, label in pe_events: ax1.annotate(label, xy=(year, val), xytext=(year + 3, val + 3), arrowprops=dict(arrowstyle="->", color="gray", lw=0.8), fontsize=8) # ── Panel 2: Dividend Yield ──────────────────────────────────────────── years_dy = [d["year"] for d in sp500_dividend_yield] values_dy = [d["value"] for d in sp500_dividend_yield] ax2.plot(years_dy, values_dy, color="#e74c3c", linewidth=1.5) ax2.axhline(y=3.2, color="#333", linestyle="--", linewidth=1, alpha=0.7) ax2.text(1960, 3.5, "Mean: 3.2%", fontsize=10) ax2.set_ylabel("Dividend Yield (%)", fontsize=12) ax2.set_xlabel("Year", fontsize=12) ax2.set_ylim(0, 8) dy_events = [ (1950, 7.44, "1950: 7.44%"), (2000, 1.22, "2000: 1.22%"), (2026, 1.04, "2026: 1.04%"), ] for year, val, label in dy_events: ax2.annotate(label, xy=(year, val), xytext=(year + 3, val + 0.5), arrowprops=dict(arrowstyle="->", color="gray", lw=0.8), fontsize=8) fig.suptitle( "S&P 500 Valuation Metrics — P/E Ratio and Dividend Yield", fontsize=16, fontweight="bold", ) for ax in (ax1, ax2): ax.grid(True, alpha=0.3) # Save — avoid tight_layout() and bbox_inches="tight" to bypass # Python 3.14 + matplotlib deepcopy RecursionError import os output_dir = "output/charts" os.makedirs(output_dir, exist_ok=True) path = os.path.join(output_dir, "03_pe_dividend.png") fig.subplots_adjust(top=0.90, bottom=0.06, left=0.08, right=0.95, hspace=0.28) plt.savefig(path, dpi=EXPORT_DPI, facecolor=fig.get_facecolor(), edgecolor="none") plt.close(fig) return path def main(): path = plot_pe_dividend() print(f"Chart saved: {path}") if __name__ == "__main__": main()