Quant BuffetRelax, Not Over Thinking

Interest Rates Momentum Predicts FX Rates

Log in to collect

Academic paper

Strategy in a nutshell

This strategy trades cross-pairs of 11 major currencies (AUD, CAD, CHF, DKK, EUR, GBP, JPY, NOK, NZD, SEK, USD) using interest rate differential momentum signals. Sub-signals for each pair are combined to determine net positions, and the portfolio is rebalanced twice daily at London and New York close times.

Economic rationale

It exploits differences in monetary policy expectations, buying currencies with rising rate expectations and selling those with falling ones. Momentum arises from investors’ slow reactions followed by overreactions, allowing predictable patterns in currency movements to be captured.

Backtest performance

Annualised return9.01%
Volatility5.54%
Beta-0.075
Sharpe ratio1.63
Win rate46%

Full Python code

import data_tools
from AlgorithmImports import *
import numpy as np
class InterestRatesMomentumPredictsFXRates(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)

# Country symbol and currency future symbol.
self.symbols = {
                "USD" : "not_traded", # US Dollar index Futures, Continuous Contract #1
                "EUR" : "CME_EC1", # Euro FX Futures, Continuous Contract #1
                "GBP" : "CME_BP1", # British Pound Futures, Continuous Contract #1
                "CHF" : "CME_SF1", # Swiss Franc Futures, Continuous Contract #1
                "JPY" : "CME_JY1", # Japanese Yen Futures, Continuous Contract #1
                }
# Interest rate data.
self.interest_rate = self.AddData(data_tools.InterestRate, 'InterestRate', Resolution.Daily).Symbol

self.period = 15
self.yield_difference = {}

countries = [x[0] for x in self.symbols.items()]
for i, country1 in enumerate(countries):
    for j, country2 in enumerate(countries):
        if i <= j: continue
        self.yield_difference[country1 + country2] = RollingWindow[float](self.period)
for country, currency_future in self.symbols.items():
    # Currency futures data.
    data = self.AddData(data_tools.QuantpediaFutures, currency_future, Resolution.Daily)
    data.SetLeverage(10)
    data.SetFeeModel(data_tools.CustomFeeModel())

def OnData(self, data):
if not self.Securities.ContainsKey(self.interest_rate): return

interest_rate_date = self.Securities[self.interest_rate].GetLastData()
if not interest_rate_date: return

# make sure interest data is still comming in
if (self.Time.date() - interest_rate_date.Time.date()).days >= 5:
    self.Liquidate()
    return

countries = [x[0] for x in self.symbols.items()]
signal = {}
for i, country1 in enumerate(countries):
    sub_signal = {}
    for j, country2 in enumerate(countries):
        if i <= j: continue
    
        index = country1 + country2
        yield1 = interest_rate_date[country1]
        yield2 = interest_rate_date[country2]
        diff = yield1 - yield2
        
        self.yield_difference[index].Add(diff)
        
        if self.yield_difference[index].IsReady:
            avg_diff = np.mean([x for x in self.yield_difference[index]])
            sub_signal[index] = (diff - avg_diff) / abs(avg_diff)
    
    if len(sub_signal) > 0:
        abs_percentile = np.percentile([abs(x[1]) for x in sub_signal.items()], 50)
        
        for signal_index, sig in sub_signal.items():
            iter_country1 = signal_index[:3]
            iter_country2 = signal_index[-3:]
            if abs(sig) > abs_percentile:
                if iter_country1 != 'USD':
                    iter_future1 = self.symbols[iter_country1]
                    if iter_future1 not in signal:
                        signal[iter_future1] = 0
                    signal[iter_future1] += np.sign(sig)
                    
                if iter_country2 != 'USD':
                    iter_future2 = self.symbols[iter_country2]
                    if iter_future2 not in signal:
                        signal[iter_future2] = 0
                    signal[iter_future2] -= np.sign(sig)

if len(signal) != 0:
    futures_invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
    for currency_future in futures_invested:
        if currency_future not in signal:
            self.Liquidate(currency_future)
    
    foo = 3
    
    for currency_future, country_signal in signal.items():
        self.SetHoldings(currency_future, country_signal)