Quant BuffetRelax, Not Over Thinking

FOMC Event-Based Stock Timing Strategy

Log in to collect

Academic paper

The Pre-FOMC Announcement Drift

AuthorsDavid O. Lucca; Emanuel Moench

Institute
  • Federal Reserve Bank of New York
  • ?Federal Reserve Banks - Federal Reserve Bank of New York
  • Centre for Economic Policy Research
  • DEFrankfurt School of Finance & Management
  • DEDeutsche Bundesbank
  • DEGoethe University Frankfurt
  • ?Centre for Economic Policy Research (CEPR)
  • ?Frankfurt School of Finance and Management
  • ?Goethe University Frankfurt - Department of Money and Macroeconomics

Strategy in a nutshell

The investor is invested in stocks during FOMC meetings (going long S&P 500 ETF, fund, future, or CFD on a close one day before the meeting and closing position on close after the meeting). Otherwise, he is invested in cash during the remaining days. The strategy has very low exposure to the stock market (8 days during the average year); therefore, it can be very easily leveraged to gain very significant returns.

Economic rationale

FOMC meetings are mostly positive for the stock market. The FED’s purpose is to address banking panics, maintain the stability of the financial system, contain systemic risk in financial markets, and strengthen economic growth. Therefore it is highly unlikely that FOMC meetings’ conclusions would be highly negative for stocks. This is the main cause of a positive drift.

Backtest performance

Annualised return6.19%
Beta0.036
Sharpe ratio-0.274
Sortino ratio-0.091
Maximum drawdown11.9%
Win rate55%

Full Python code

from AlgoLib import *
from pandas.tseries.offsets import BDay
from datetime import datetime

class FederalOpenMarketCommitteeMeetingEffectinStocks(XXX):

def Initialize(self) -> None:
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)

self.market:Symbol = self.AddEquity("SPY", Resolution.Minute).Symbol

self.fed_days_symbol:Symbol = self.AddData(FedDays, 'fed_days', Resolution.Daily, TimeZones.NewYork).Symbol
self.SetWarmUp(1, Resolution.Daily)
FedDays.set_algo(self)

self.recent_day:int = -1

def OnData(self, data: Slice) -> None:
if self.IsWarmingUp: return

if self.fed_days_symbol in data and data[self.fed_days_symbol]:
    self.Log(f"New FOMC meeting data arrived: {self.Time}; submitting an MOC order...")

    # new fed day data arrived
    quantity:float = self.CalculateOrderQuantity(self.market, 1.)
    self.MarketOnCloseOrder(self.market, quantity)

    self.recent_day = self.Time.day
else:
    # other new minute resolution data arrived
    if self.Portfolio[self.market].Invested:
        if self.Time.day != self.recent_day:
            self.recent_day = self.Time.day

            self.Log(f"FOMC meeting day; submitting an MOC order to close opened position...")
        
            self.MarketOnCloseOrder(self.market, -self.Portfolio[self.market].Quantity)

class FedDays(PythonData):
algo = None

@staticmethod
def set_algo(algo):
FedDays.algo = algo

def GetSource(self, config:SubscriptionDataConfig, date:datetime, isLiveMode:bool) -> SubscriptionDataSource:
if isLiveMode:
    # FedDays.algo.Log(f"Edited GetSource date {FedDays.algo.Time}")
    return SubscriptionDataSource("https://data.quantpedia.com/backtesting_data/economic/fed_days.json", SubscriptionTransportMedium.RemoteFile, FileFormat.UnfoldingCollection)

return SubscriptionDataSource("https://data.quantpedia.com/backtesting_data/economic/fed_days.csv", SubscriptionTransportMedium.RemoteFile)

def Reader(self, config:SubscriptionDataConfig, line:str, date:datetime, isLiveMode:bool) -> BaseData:
if isLiveMode:
    try:
        # FedDays.algo.Log(f"Reader")

        objects = []
        data = json.loads(line)
        end_time = None

        for index, sample in enumerate(data):
            custom_data = FedDays()
            custom_data.Symbol = config.Symbol

            custom_data.Time = (datetime.strptime(str(sample["fed_date"]), "%Y-%m-%d") - BDay(1)).replace(hour=9, minute=31)
            # FedDays.algo.Log(f"{custom_data.Time}")

            end_time = custom_data.Time  
            objects.append(custom_data)

        return BaseDataCollection(end_time, config.Symbol, objects) 

    except ValueError:
        # FedDays.algo.Log(f"Reader Error")
        return None
else:
    if not (line.strip() and line[0].isdigit()):
        return None
    
    custom = FedDays()
    custom.Symbol = config.Symbol

    custom.Time = (datetime.strptime(line, "%Y-%m-%d") - BDay(1)).replace(hour=9, minute=31)
    custom.Value = 0.
    custom["fed_date_str"] = line

    return custom