Quant BuffetRelax, Not Over Thinking

The Dollar Ahead of FOMC Target Rate Changes

Log in to collect

Academic paper

Strategy in a nutshell

This strategy trades the U.S. dollar against a basket of developed currencies using next-month Fed funds futures three days before FOMC announcements. The signal is the spread between expected and current target rates. If the spread ≥12.5 bps, it goes long; if ≤-12.5 bps, it goes short. No trades occur for spreads between -12.5 and 12.5 bps. Trades are executed two days before the announcement and closed on the announcement day with 5:1 leverage.

Economic rationale

Standard exchange rate models cannot explain why the dollar strengthens only two days before FOMC announcements despite gradual U.S. interest rate rises. Fed funds futures spreads and interest rate differentials align with policy changes, revealing that dollar movements are concentrated immediately prior to announcements.

Backtest performance

Annualised return7.17%
Volatility7.28%
Beta-0.002
Sharpe ratio0.98
Sortino ratio-0.434
Win rate45%

Full Python code

from AlgorithmImports import *
from pandas.tseries.offsets import BDay
from dateutil.relativedelta import relativedelta
#endregion
class TheDollarAheadOfFOMCTargetRateChanges(QCAlgorithm):
def initialize(self) -> None:
 self.set_start_date(2000, 1, 1)
 self.set_cash(100000)
 self.spread:float|None = None
 self.last_target_rate:float|None = None
 self.last_target_rate_update:datetime.date|None = None
 self.max_missing_days_target_rate:int = 5
 self.days_counter:int = 0
 self.holding_period: int = 3
 self.spread_threshold: float = 0.125
 self.leverage:int = 5
 data = self.add_data(QuantpediaFutures, 'ICE_DX1', Resolution.DAILY)
 data.set_fee_model(CustomFeeModel())
 data.set_leverage(self.leverage)
 
 self.symbol:Symbol = data.symbol
 self.fed_fund_futures:Symbol = self.add_data(NasdaqFedFuture, 'CHRIS/CME_FF3', Resolution.DAILY).symbol
 self.fed_fund_rate:Symbol = self.add_data(FedRate, 'FEDFUNDS', Resolution.DAILY).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')
 fed_dates:list[datetime.date] = [datetime.strptime(x, '%Y-%m-%d') for x in dates]
 self.three_days_before_fed:list[datetime.date] = [(x - BDay(3)).date() for x in fed_dates]
def on_data(self, data: Slice) -> None:
 curr_date:datetime.date = self.time.date()
 custom_data_last_update_date: Dict[Symbol, datetime.date] = LastDateHandler.get_last_update_date()
 if all((self.securities[symbol].get_last_data() and self.time.date() > custom_data_last_update_date[symbol]) for symbol in [self.symbol, self.fed_fund_rate]):
     self.liquidate()
     return
 # coming on daily basis
 if self.fed_fund_futures in data and data[self.fed_fund_futures]:
     fed_fund_futures_value = data[self.fed_fund_futures].value
     self.last_target_rate = 100 - fed_fund_futures_value
     self.last_target_rate_update = curr_date
 # coming on monthly basis
 if self.fed_fund_rate in data and data[self.fed_fund_rate]:
     self.last_fed_rate = data[self.fed_fund_rate].Value
 if self.last_target_rate_update != None and (curr_date - self.last_target_rate_update).days > self.max_missing_days_target_rate:
     self.last_target_rate = None
     self.last_target_rate_update = None
 # Three days before fed calculate expected target rate and spread
 if curr_date in self.three_days_before_fed and self.last_target_rate != None and self.last_fed_rate != None:
     self.spread = self.last_target_rate - self.last_fed_rate
 
 if self.symbol in data and data[self.symbol]:
     # Buy dollar future two days before fed. Emit insight and let it expire (liquidate) by itself.
     if self.spread:
         if self.spread >= self.spread_threshold:
             self.set_holdings(self.symbol, 1)
         elif self.spread <= -self.spread_threshold:
             self.set_holdings(self.symbol, -1)
     
         self.spread = None
 if self.portfolio.invested:
     self.days_counter += 1
 if self.days_counter >= self.holding_period:
     self.days_counter = 0
     self.liquidate()
class NasdaqFedFuture(NasdaqDataLink):
def __init__(self) -> None:
 self.ValueColumnName = "Value"
 
class LastDateHandler():
_last_update_date:Dict[Symbol, datetime.date] = {}

@staticmethod
def get_last_update_date() -> Dict[Symbol, datetime.date]:
return LastDateHandler._last_update_date
 
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config: SubscriptionDataConfig, date: datetime, isLiveMode: bool) -> SubscriptionDataSource:
 return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config: SubscriptionDataConfig, line: str, date: datetime, isLiveMode: bool) -> BaseData:
 data = QuantpediaFutures()
 data.Symbol = config.Symbol
 
 if not line[0].isdigit(): return None
 split = line.split(';')
 
 data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
 data['back_adjusted'] = float(split[1])
 data['spliced'] = float(split[2])
 data.Value = float(split[1])
 if config.Symbol not in LastDateHandler._last_update_date:
     LastDateHandler._last_update_date[config.Symbol] = datetime(1,1,1).date()
 if data.Time.date() > LastDateHandler._last_update_date[config.Symbol]:
     LastDateHandler._last_update_date[config.Symbol] = data.Time.date()
 return data
# Quantpedia monthly custom data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class FedRate(PythonData):
def GetSource(self, config: SubscriptionDataConfig, date: datetime, isLiveMode: bool) -> SubscriptionDataSource:
 return SubscriptionDataSource(f'data.quantpedia.com/backtesting_data/economic/{config.Symbol.Value}.csv', SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config: SubscriptionDataConfig, line: str, date: datetime, isLiveMode: bool) -> BaseData:
 data = FedRate()
 data.Symbol = config.Symbol
 
 if not line[0].isdigit(): return None
 split: str = line.split(';')
 
 data.Time = datetime.strptime(split[0], "%Y-%m-%d") + relativedelta(months=1)
 data.Value = float(split[1])
 if config.Symbol not in LastDateHandler._last_update_date:
     LastDateHandler._last_update_date[config.Symbol] = datetime(1,1,1).date()
 if data.Time.date() > LastDateHandler._last_update_date[config.Symbol]:
     LastDateHandler._last_update_date[config.Symbol] = data.Time.date()
 return data
# Custom fee model
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
 fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
 return OrderFee(CashAmount(fee, "USD"))