
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.
ASSET CLASS: CFDs, forwards, futures, swaps | REGION: Global | FREQUENCY:
Intraday | MARKET: currencies | KEYWORD: Interest Rates, Momentum, FX Rates
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 Return | 9.01% |
| Volatility | 5.54% |
| Beta | -0.075 |
| Sharpe Ratio | 1.63 |
| Sortino Ratio | N/A |
| Maximum Drawdown | N/A |
| Win Rate | 46% |
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)