#!/usr/bin/env python3 """ lagged_oil_unrate_chart_styled.py --------------------------------- Offline version: reads UNRATE & WTI series from data/fred.db (populated by scripts/refresh_data.py) and plots them with a configurable lag. Usage ----- python scripts/lagged_oil_unrate_chart_styled.py \ --offset 18 \ --start 1973-01-01 \ --end 2025-05-31 \ --extend-years 3 """ from __future__ import annotations import argparse from datetime import datetime from pathlib import Path import matplotlib.dates as mdates import matplotlib.pyplot as plt import pandas as pd from dateutil.relativedelta import relativedelta import sqlite3 # ────────────────────────────────────────────────────────────────────────── # Data helpers # ────────────────────────────────────────────────────────────────────────── def fetch_series( start: datetime, end: datetime, db_path: str | Path = Path("data/fred.db"), ) -> tuple[pd.DataFrame, pd.DataFrame]: """ Load UNRATE and DCOILWTICO from a local SQLite DB, then: * convert UNRATE to month-end timestamps * average WTI daily prices to month-end """ db_path = Path(db_path) if not db_path.exists(): raise FileNotFoundError( f"{db_path} not found. Run scripts/refresh_data.py on a " "machine with internet, commit the new DB, then retry." ) with sqlite3.connect(db_path) as conn: unrate = ( pd.read_sql( "SELECT date, UNRATE AS value FROM UNRATE", conn, parse_dates=["date"], index_col="date" ) .loc[start:end] ) oil = ( pd.read_sql( "SELECT date, DCOILWTICO AS value FROM DCOILWTICO", conn, parse_dates=["date"], index_col="date" ) .loc[start:end] ) # Resample WTI to month-end average oil_monthly = oil.resample("M").mean() # Ensure UNRATE is at month-end, too unrate.index = unrate.index.to_period("M").to_timestamp("M") return unrate, oil_monthly # ────────────────────────────────────────────────────────────────────────── # Plotting # ────────────────────────────────────────────────────────────────────────── def plot_lagged( unrate: pd.DataFrame, oil: pd.DataFrame, offset_months: int, start_date: datetime, end_date: datetime, extend_years: int, ) -> None: """Render the dual-axis chart.""" oil_shifted = oil.shift(offset_months) fig, ax1 = plt.subplots(figsize=(14, 7)) plt.title("Unemployment Rate and US Oil Price", fontsize=20, weight="bold") plt.suptitle( "Civilian Unemployment Rate and WTI Crude Oil Price", fontsize=14, y=0.93, ) # Left axis – UNRATE ax1.plot( unrate.index, unrate["value"], label="UNRATE (LHS)", linewidth=2.5, color="#1f77b4", ) ax1.set_ylabel("Unemployment rate (%)", fontsize=12) ax1.set_ylim(0, unrate["value"].max() * 1.1) ax1.yaxis.set_major_formatter("{x:.0f}%") ax1.grid(axis="y", linestyle="--", alpha=0.4) # Right axis – shifted oil ax2 = ax1.twinx() ax2.plot( oil_shifted.index, oil_shifted["value"], label="USOIL (RHS)", linewidth=2, linestyle="-", alpha=0.8, color="#FF9900", ) ax2.set_ylabel("Oil price (USD)", fontsize=12) ax2.set_ylim(0, oil_shifted["value"].max() * 1.1) ax2.yaxis.set_major_formatter("${x:,.0f}") # X-axis ax1.set_xlim(start_date, end_date + relativedelta(years=extend_years)) ax1.xaxis.set_major_locator(mdates.YearLocator(base=5)) ax1.xaxis.set_minor_locator(mdates.YearLocator(1)) ax1.xaxis.set_major_formatter(mdates.DateFormatter("%Y")) plt.setp(ax1.get_xticklabels(), rotation=0, fontsize=11) # Legends lines, labels = ax1.get_legend_handles_labels() l2, l2l = ax2.get_legend_handles_labels() ax1.legend(lines + l2, labels + l2l, loc="upper left", frameon=False, fontsize=12) # Footnote foot = ( f"Dates: {start_date.strftime('%Y')} through " f"{unrate.index[-1].strftime('%d %b %Y')}.\n" "Source: Local FRED snapshot (offline)." ) plt.annotate( foot, (0, 0), (0, -40), xycoords="axes fraction", textcoords="offset points", va="top", fontsize=10, color="gray", ) fig.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() # ────────────────────────────────────────────────────────────────────────── # CLI # ────────────────────────────────────────────────────────────────────────── def main() -> None: p = argparse.ArgumentParser() p.add_argument( "-o", "--offset", type=int, default=18, help="lag in months (positive=lag, negative=lead)", ) p.add_argument( "--start", type=str, default="1973-01-01", help="series start date (YYYY-MM-DD)", ) p.add_argument( "--end", type=str, default=datetime.today().strftime("%Y-%m-%d"), help="series end date (YYYY-MM-DD)", ) p.add_argument( "--extend-years", type=int, default=3, help="how many years beyond end_date to show on the x-axis", ) p.add_argument( "--db", type=str, default="data/fred.db", help="path to local FRED SQLite file", ) args = p.parse_args() start_dt = datetime.fromisoformat(args.start) end_dt = datetime.fromisoformat(args.end) unrate, oil = fetch_series(start_dt, end_dt, args.db) plot_lagged(unrate, oil, args.offset, start_dt, end_dt, args.extend_years) if __name__ == "__main__": main()