Repository Public Research preview
1"""Scheduled announcement calendar: FOMC statements (with release-time rule), CPI and Employment Situation (08:30 ET)."""2from __future__ import annotations3import json4from pathlib import Path5import pandas as pd6ET = "America/New_York"78def fomc_statement_time(d: pd.Timestamp, press_conf_dates: set[str]) -> str:9 """Release-time rule (ET): 14:15 before April 2011; April 2011–Dec 2012: 12:30 on press-conference days, 14:15 otherwise;10 from January 2013: 14:00. Unscheduled statements use recorded times (see UNSCHEDULED)."""11 if d < pd.Timestamp("2011-04-01"): return "14:15"12 if d < pd.Timestamp("2013-01-01"): return "12:30" if d.strftime("%Y-%m-%d") in press_conf_dates else "14:15"13 return "14:00"1415# Press-conference meetings 2011–2012 (statement at 12:30 ET): April, June, November 2011; January, April, June, September, December 201216PRESS_CONF_2011_2012 = {"2011-04-27", "2011-06-22", "2011-11-02", "2012-01-25", "2012-04-25", "2012-06-20", "2012-09-13", "2012-12-12"}17# Unscheduled policy statements in sample with approximate release time (ET), from the Fed press releases18UNSCHEDULED = {"2008-01-22": "08:20", "2008-10-08": "07:00", "2020-03-03": "10:00", "2020-03-15": "17:00"}19# Extra scheduled meeting end-dates missing from the minutes-link parse20EXTRA_SCHEDULED = {"2008-06-25", "2026-10-28"}21NON_POLICY_STATEMENTS = {"20080311", "20100509", "20191011", "20200319", "20200323", "20200331", "20200827", "20250822"}2223def load_fomc(ext: Path) -> pd.DataFrame:24 d = json.load(open(ext / "fomc/fomc_dates_parsed.json"))25 sched = sorted(set(d["meeting_end_dates"]) | {x.replace("-", "") for x in EXTRA_SCHEDULED})26 unsched_keys = {k.replace("-", "") for k in UNSCHEDULED}27 rows = [{"date": pd.Timestamp(s), "type": "FOMC", "scheduled": True} for s in sched if s >= "20080101" and s not in unsched_keys]28 rows += [{"date": pd.Timestamp(s), "type": "FOMC", "scheduled": False} for s in UNSCHEDULED]29 df = pd.DataFrame(rows).sort_values("date").reset_index(drop=True)30 df["time"] = [UNSCHEDULED.get(d.strftime("%Y-%m-%d")) or fomc_statement_time(d, PRESS_CONF_2011_2012) for d in df.date]31 return df3233def load_bls(ext: Path) -> pd.DataFrame:34 rows = []35 for fn, typ in (("bls/cpi_release_dates_alfred_rid10.txt", "CPI"), ("bls/empsit_release_dates_alfred_rid50.txt", "EMP")):36 ds = [l.strip() for l in open(ext / fn) if l.strip() and not l.startswith("#")]37 s = pd.Series(pd.to_datetime(ds)).sort_values()38 # keep one release per calendar month: the later one is the regular release when two occur (earlier = revision), except39 # known benchmark/revision extras which are the later date (2024-08-21, 2024-01-10, 2013-05-06, 2012-12-12, 2020-05-11).40 drop = {"2024-08-21", "2024-01-10", "2013-05-06", "2012-12-12", "2020-05-11", "2006-05-08", "2003-10-10", "2002-12-09"}41 s = s[~s.dt.strftime("%Y-%m-%d").isin(drop)]42 g = s.groupby(s.dt.to_period("M"))43 keep = g.max() if typ == "CPI" else g.min()44 rows += [{"date": d, "type": typ, "scheduled": True, "time": "08:30"} for d in keep]45 return pd.DataFrame(rows)4647def load_events(ext: Path, start="2008-01-01") -> pd.DataFrame:48 ev = pd.concat([load_fomc(ext), load_bls(ext)], ignore_index=True)49 ev = ev[ev.date >= start].sort_values(["date", "type"]).reset_index(drop=True)50 ev["ts"] = pd.to_datetime(ev.date.dt.strftime("%Y-%m-%d") + " " + ev.time).dt.tz_localize(ET)51 return ev52