Quant BuffetRelax, Not Over Thinking

The FOMC Cycle Effect

Log in to collect

Academic paper

Stock Returns Over the FOMC Cycle

AuthorsAnna Cieślak; Adair Morse; Annette Vissing‐Jørgensen

Institute
  • 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