Quant BuffetRelax, Not Over Thinking

Carry Trade Strategy: Long Highest-Rate Currencies, Short Lowest-Rate

Log in to collect

Academic paper

Strategy in a nutshell

Establish a diverse currency investment universe, including 10-20 currencies. Long the top three currencies with the highest central bank prime rates and short the lowest three. Unutilized cash, designated as margin, is invested in overnight rates. Monthly rebalancing ensures strategy adherence and optimization for sustained performance.

Economic rationale

In academic circles, consensus affirms the efficacy of the foreign exchange carry trade anomaly. Acemoglu, Rogoff, and Woodford's work "Carry Trades and Currency Crashes" highlights the success of a simple strategy pursuing high yields globally. Despite theoretical expectations under uncovered interest rate parity, where profits from carry trades are deemed unpredictable, empirical evidence suggests consistent profitability over three decades. High-interest-rate currencies often fail to depreciate sufficiently to negate yield differentials due to lower-than-expected inflation. Moreover, carry trade activities tend to weaken the borrowed currency as investors shift funds into higher-yielding options, inducing further price drift. Systematic portfolio rebalancing facilitates the capture of these gains, underscoring the practicality and potential profitability of the carry trade strategy in foreign exchange markets.

Backtest performance

Annualised return7.30%
Volatility9.60%
Beta0.241
Sharpe ratio0.57
Sortino ratio0.61
Maximum drawdown32.02%
Win rate68%

Full Python code

from AlgoLib import *

class SectorMomentumAlgorithm(XXX):
"""
A Sector Momentum Algorithm that selects and trades sectors based on their momentum.
It initializes with a set starting cash, selects a subset of sectors to trade, and
rebalances the portfolio based on momentum indicators.
"""

def Initialize(self):
"""
Initializes the algorithm settings, including start date, initial cash, and the momentum calculation period.
Sets up the sectors to monitor and applies leverage.
"""

self.SetStartDate(2015, 1, 1)  # Set the start date of the algorithm.
self.SetCash(100000)  # Set the starting cash for the portfolio.

# Initializes a dictionary to hold momentum data for each sector.
self.data:Dict[str, Momentum] = {}

# The period over which momentum is calculated.
self.momentum_period:int = 252

# Set the algorithm to warm up with the specified period to allow indicators to initialize.
self.SetWarmUp(self.momentum_period, Resolution.Daily)

# The number of sectors to select based on momentum.
self.selected_symbol_count:int = 3 

# A list of selected sectors to monitor for momentum.
self.selected_sectors:List[str] = [
    "XLK",  # Technology
    "XLE",  # Energy
    "XLV",  # Health Care
    "XLF",  # Financial
    "XLI",  # Industrial
    "XLB",  # Materials
    "XLY",  # Consumer Discretionary
    "XLP",  # Consumer Staples
    "XLU"   # Utilities
]

# Add equity data for each sector, set leverage, and initialize momentum indicators.
for sector in self.selected_sectors:
    data = self.AddEquity(sector, Resolution.Daily)
    data.SetLeverage(2)
    
    # Initializes momentum object for each sector with the specified period.
    self.data[sector] = self.MOMP(sector, self.momentum_period, Resolution.Daily)

# Subscribe to the momentum indicator's updated event for the first sector.
self.data[self.selected_sectors[0]].Updated += self.OnMomentumUpdated

# Variable to track the most recent month processed to ensure monthly rebalance.
self.recent_month:int = -1

# A flag to trigger portfolio rebalance.
self.rebalance_flag:bool = False

def OnMomentumUpdated(self, sender, updated) -> None:
"""
Event handler for momentum indicator updates. It sets a flag to trigger portfolio rebalancing.
"""

# Check if the month has changed since the last update and set the rebalance flag.
if self.recent_month != self.Time.month:
    self.recent_month = self.Time.month
    self.rebalance_flag = True

def OnData(self, data: Slice) -> None:
"""
Event handler for new data. This function rebalances the portfolio based on sector momentum, if necessary.
"""

if self.IsWarmingUp: return  # Skip if the algorithm is still warming up.

# Check if it's time to rebalance the portfolio.
if self.rebalance_flag:
    self.rebalance_flag = False  # Reset the flag
    
    # Sort sectors by momentum and filter for readiness and data availability.
    sorted_by_momentum:List = sorted([x for x in self.data.items() if x[1].IsReady and \
        x[0] in self.selected_sectors and \
        x[0] in data and data[x[0]]], \
        key = lambda x: x[1].Current.Value, reverse = True)

    # If there are fewer sectors than the target count, liquidate the portfolio.
    if len(sorted_by_momentum) < self.selected_symbol_count:
        self.Liquidate()
        return

    # Select top sectors based on momentum for investment.
    selected_sectors:List[str] = [x[0] for x in sorted_by_momentum[:self.selected_symbol_count]]
    
    # Liquidate sectors not in the selected list.
    invested_sectors:List[str] = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
    for sector in invested_sectors:
        if sector not in selected_sectors:
            self.Liquidate(sector)
    
    # Allocate equal weights to selected sectors.
    for sector in selected_sectors:
        self.SetHoldings(sector, 1 / len(selected_sectors))

# Custom fee model class definition.
class CustomFeeModel(FeeModel):
"""
A custom fee model that calculates order fees.
"""

def GetOrderFee(self, parameters):
"""
Calculates the fee for an order based on the security price and order quantity.
"""

# Calculate the fee as a percentage of the trade amount.
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.0  # Assumes no fee.
return OrderFee(CashAmount(fee, "USD"))