
Create an investment pool of 10-20 currencies, evaluate using OECD PPP, monthly CPI, and exchange rates. Go long on three undervalued and short three overvalued currencies. Rebalance quarterly.
ASSET CLASS: CFDs, forwards, futures, swaps | REGION: Global | FREQUENCY: Quarterly | MARKET: Currency | KEYWORD: Value Factor
STRATEGY IN A NUTSHELL
Build a portfolio with 10-20 currencies, using the latest OECD Purchasing Power Parity (PPP) for initial fair value against the USD. Adjust these values monthly based on CPI and exchange rate shifts to determine previous month’s fair PPP value. Invest in the three most undervalued currencies and short the three most overvalued ones, based on PPP calculations. Allocate unused cash to overnight interest accounts. Rebalance the portfolio monthly or quarterly to adapt to changing market conditions. This strategy aims to capitalize on currency valuation disparities, leveraging PPP adjustments to guide investment decisions and maximize returns.
ECONOMIC RATIONALE
Menkhoff, Sarno, Schmeling, and Schrimpf explored the predictive power of currency valuation based on real exchange rates. They found that such measures are informative for FX excess returns and spot exchange rate changes across currencies. This predictability primarily arises from persistent macroeconomic disparities among countries, indicating that currency value largely reflects varying risk premia, relatively stable over time. Contrary to conventional belief, trading based on simplistic currency valuation metrics isn’t inherently profitable due to reversion to fundamental values. Deeper analysis reveals that refined valuation measures align more closely with original currency value concepts, predicting both excess returns and exchange rate reversals. Moreover, differing consumption baskets across nations allow for relative price level assessment, indicating “cheap” and “expensive” countries. Price differentials evolve slowly, enabling gains through exchange rate convergence with fair value in a rebalanced portfolio comprising undervalued and overvalued currencies.
SOURCE PAPER
db Currency Returns [Click to Open PDF]
Deutsche Bank
<Abstract>
The “db Currency Returns” (DBCR) document details strategies and performances of the DBCR index, capturing systematic returns in global currency markets through three main strategies: Carry, exploiting forward-rate bias by trading currencies based on interest rate differentials; Momentum, capitalizing on long-term currency trends; and Valuation, buying undervalued and selling overvalued currencies based on their move towards “fair value” over time. The DBCR index offers benchmark exposure to these strategies, demonstrating low correlation with traditional asset classes, highlighting its diversification benefits. It combines these strategies into a single, non-discretionary index with daily liquidity. Performance data from 1989 to 2009 shows each strategy’s return, volatility, and Sharpe ratio, underscoring the index’s utility in investment portfolios. DBCR variants like DBCR+ and DBCR Dynamic introduce adjustments for potentially enhanced returns through expanded currency universe or dynamic allocation based on past performance. The document emphasizes DBCR’s role in offering systematic, diversified exposure to currency markets, based on decades of academic research and market performance data.

BACKTEST PERFORMANCE
| Annualised Return | 7.8% |
| Volatility | 9.3% |
| Beta | 0.045 |
| Sharpe Ratio | -0.18 |
| Sortino Rato | -0.21 |
| Maximum Drawdown | 30.4% |
| Win Rate | 52% |
FULL PYTHON CODE
# Import the algorithm base and dictionary collection from external libraries.
from AlgoLib import algorithm
from System.Collections.Generic import Dictionary
# Define the class PPPBasedCurrencyStrategy which inherits from an unspecified class XXX.
class PPPBasedCurrencyStrategy(XXX):
def Initialize(self):
"""Initializes the trading algorithm with starting conditions and data subscriptions."""
# Set the start date of the algorithm to January 1st, 2000.
self.SetStartDate(2000, 1, 1)
# Set the initial cash to $100,000.
self.SetCash(100000)
# Define leverage and the number of symbols to trade.
self.leverage = 3
self.symbols_to_trade = 3
# Initialize an empty dictionary to store purchasing power parity data.
self.purchasing_power_parity = {}
# Define a mapping between future symbols and their corresponding PPP symbols.
self.future_to_ppp_mapping = {
"CME_AD1": "AUS_PPP",
"CME_BP1": "GBR_PPP",
"CME_CD1": "CAD_PPP",
"CME_EC1": "DEU_PPP",
"CME_JY1": "JPN_PPP",
"CME_NE1": "NZL_PPP",
"CME_SF1": "CHE_PPP"
}
# Subscribe to futures data and purchasing power parity data for each mapped symbol.
for future_symbol, ppp_symbol in self.future_to_ppp_mapping.items():
self.AddFuture(future_symbol, Resolution.Daily, self.leverage)
self.AddData(custom.PurchasingPowerParity, ppp_symbol, Resolution.Daily)
# Set the minimum order margin as a percentage of the portfolio to 0.0, allowing for higher leverage.
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
# Initialize a variable to keep track of the last month processed.
self.last_month_processed = None
def OnData(self, data):
"""Handles new data events and decides when to rebalance the portfolio."""
# If the current month is the same as the last processed month, do nothing.
if self.Time.month == self.last_month_processed:
return
# Update the PPP data with the latest values.
self.UpdatePPPData(data)
# Update the last processed month to the current month.
self.last_month_processed = self.Time.month
# If it's January, rebalance the portfolio.
if self.Time.month == 1:
self.RebalancePortfolio(data)
def AddFuture(self, symbol, resolution, leverage):
"""Adds a future to the data subscription with specified leverage."""
# Add the future data to the algorithm with specified resolution.
future = self.AddData(custom.QuantpediaFutures, symbol, resolution)
# Set the leverage for the future.
future.SetLeverage(leverage)
# Return the future object for further use (if needed).
return future
def UpdatePPPData(self, data):
"""Updates the purchasing power parity data for each symbol in the mapping."""
# Iterate through each symbol and its corresponding PPP symbol.
for symbol, ppp_symbol in self.future_to_ppp_mapping.items():
# If the PPP symbol data is available in the new data, update the PPP data dictionary.
if ppp_symbol in data:
self.purchasing_power_parity[symbol] = data[ppp_symbol].Value
def RebalancePortfolio(self, data):
"""Rebalances the portfolio based on the purchasing power parity data."""
# Check if there's enough PPP data to rebalance the portfolio.
if len(self.purchasing_power_parity) >= self.symbols_to_trade * 2:
# Sort the symbols by their PPP value.
sorted_symbols = sorted(self.purchasing_power_parity.items(), key=lambda x: x[1])
# Select symbols for long positions (the ones with highest PPP).
long_symbols = [symbol for symbol, _ in sorted_symbols[-self.symbols_to_trade:]]
# Select symbols for short positions (the ones with lowest PPP).
short_symbols = [symbol for symbol, _ in sorted_symbols[:self.symbols_to_trade]]
# Clear the PPP data for the next rebalance.
self.purchasing_power_parity.clear()
# Execute the trades based on the selected long and short positions.
self.ExecuteTrades(long_symbols, short_symbols, data)
def ExecuteTrades(self, long_symbols, short_symbols, data):
"""Executes the trading strategy by setting holdings based on desired long and short positions."""
# Liquidate positions that are no longer in the selected long or short symbols.
for symbol in self.Portfolio.keys():
if symbol not in long_symbols + short_symbols and self.Portfolio[symbol].Invested:
self.Liquidate(symbol)
# Set holdings for long positions, equally distributing the investment.
for symbol in long_symbols:
if symbol in data:
self.SetHoldings(symbol, 1 / len(long_symbols))
# Set holdings for short positions, equally distributing the investment.
for symbol in short_symbols:
if symbol in data:
self.SetHoldings(symbol, -1 / len(short_symbols))