The FOMC Cycle Effect
Log in to collectAcademic paper
Stock Returns Over the FOMC Cycle
Anna Cieślak; Adair Morse; Annette Vissing‐Jørgensen
- Duke University
- National Bureau of Economic Research
- ?Duke University - Fuqua School of Business
- ?National Bureau of Economic Research (NBER)
- University of California, Berkeley
- ?University of California, Berkeley - Haas School of Business
- Federal Reserve Board of Governors
- ?Federal Reserve Board
Strategy in a nutshell
The strategy targets the S&P 500 via ETFs, funds, futures, or CFDs. The investor goes long during FOMC cycle weeks (weeks 0, 2, 4, and 6) and holds cash during the remaining weeks. The FOMC cycle begins the day before a scheduled announcement and resets with each of the eight annual FOMC meetings.
Economic rationale
Stock returns exhibit a biweekly cyclical pattern influenced by the Federal Reserve Board of Governors. While there are eight scheduled FOMC meetings annually, the board holds approximately 30 meetings per year, creating predictable even-week return patterns that this strategy exploits.
Backtest performance
Annualised return12.15%
Volatility13.15%
Beta0.566
Sharpe ratio0.92
Sortino ratio0.25
Win rate58%
Full Python code
from AlgorithmImports import *
class TheFOMCCycleEffect(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2000, 1, 1)
self.set_cash(100_000)
self.symbol: Symbol = self.add_equity("SPY", Resolution.MINUTE).symbol
csv_string_file: str = self.download('data.quantpedia.com/backtesting_data/economic/fed_days.csv')
dates: List[str] = csv_string_file.split('\r\n')
dates_before_fed: List[datetime.date] = [(datetime.strptime(x, "%Y-%m-%d") - BDay(1)).date() for x in dates]
self.trade_flag: bool = False
self.days_to_switch_positions: bool = 5
self.schedule.on(self.date_rules.on(dates_before_fed), self.time_rules.after_market_open(self.symbol, 1), self.day_before_FED)
self.schedule.on(self.date_rules.every_day(self.symbol), self.time_rules.after_market_open(self.symbol, 1), self.rebalance)
def day_before_FED(self) -> None:
self.set_holdings(self.symbol, 1)
self.days_to_switch_positions = 5
self.trade_flag = True
def rebalance(self) -> None:
if self.trade_flag:
if self.days_to_switch_positions == 0:
if self.portfolio[self.symbol].is_long:
self.liquidate(self.symbol)
else:
self.set_holdings(self.symbol, 1)
self.days_to_switch_positions = 5
self.days_to_switch_positions -= 1