We investigate whether momentum or reversal is the dominant phenomenon in short horizon (one- to four-week) foreign exchange rate returns. We find, based on a broad sample of 63 emerging and developed market currencies, evidence of momentum rather than reversal. Momentum returns are as large as 9% p.a. The short-term momentum effect appears to be robust. Returns are larger in the earlier sub-period but still exist in the more recent period. The strategies are also profitable in US recessions and expansions, and in up and down currency markets.
Keywords: currency, momentum, reversal
- Use a set of 63 currencies paired with USD.
- 4-week holding and look-back periods
- Identify the top quintile of currencies with the largest lagged excess returns.
- Identify the bottom quintile of currencies with the smallest lagged excess returns.
- Take long positions in currencies that have seen the most appreciation relative to USD.
- Initiate short positions in currencies that have seen the most depreciation against USD.
import backtrader as bt class OneMonthCurrencyMomentum(bt.Strategy): params = ( ("lookback_period", 4 * 5), # 4 weeks, assuming 5 trading days per week ("holding_period", 4 * 5), ("portfolio_size", 0.2), # Top and bottom 20% of currencies ) def __init__(self): self.currency_data = self.datas[1:] # Ignore the first data feed (USD) self.order_dict = dict() def next(self): if self.datetime.weekday() == 0: # Execute strategy on Mondays if len(self.data) < self.p.lookback_period: return # Calculate returns for each currency currency_returns = [ (data, (data.close - data.close[-self.p.lookback_period]) / data.close[-self.p.lookback_period]) for data in self.currency_data ] # Sort currencies by returns sorted_currencies = sorted(currency_returns, key=lambda x: x, reverse=True) # Determine the number of currencies to trade num_currencies = int(len(sorted_currencies) * self.p.portfolio_size) # Close existing positions for data, order in self.order_dict.items(): if order and order.status in [order.Accepted, order.Submitted]: self.cancel(order) elif data in self.positions: self.close(data) # Enter long positions for top currencies for data, return_ in sorted_currencies[:num_currencies]: self.order_dict[data] = self.buy(data) # Enter short positions for bottom currencies for data, return_ in sorted_currencies[-num_currencies:]: self.order_dict[data] = self.sell(data) # Example usage with sample data feeds if __name__ == "__main__": cerebro = bt.Cerebro() cerebro.addstrategy(OneMonthCurrencyMomentum) # Add data feeds (assuming they are in a list called 'datafeeds') for data in datafeeds: cerebro.adddata(data) cerebro.broker.set_cash(10000) cerebro.run()
Please note that this code assumes you have already prepared a list of data feeds (
datafeeds) for the 63 currencies against USD. You can use Backtrader’s
GenericCSVData or any other supported data format to load the data. The code provided is just the implementation of the strategy itself.