The strategy trades 11 major currency pairs using interest rate differential momentum signals, rebalancing twice daily at London and New York closes to exploit rate-driven market inefficiencies.

I. 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.

II. 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.

III. SOURCE PAPER

ANANTA: A Systematic Quantitative FX Trading Strategy [Click to Open PDF]

Georges

<Abstract>

This paper is the first of a series that aims to study in detail the ANANTA strategy, a short term systematic FX model using fixed income signals. We will focus in this part on outlining the context and an initial basic implementation of the methodology, from trading hypothesis to signal construction and results.

IV. BACKTEST PERFORMANCE

Annualised Return9.01%
Volatility5.54%
Beta-0.075
Sharpe Ratio1.63
Sortino RatioN/A
Maximum DrawdownN/A
Win Rate46%

V. 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)

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading