Quant BuffetRelax, Not Over Thinking

Effect of Corruption on FX Markets

Log in to collect

Academic paper

Corruption, Carry Trades, and the Cross Section of Currency Returns

AuthorsKlaus Grobys; Jari‐Pekka Heinonen

Institute
  • FIUniversity of Vaasa
  • FIUniversity of Jyväskylä
  • ?University of Jyväskyla
  • ?University of Vaasa - Department of Accounting and Finance

Strategy in a nutshell

This strategy invests in 17 liquid currency pairs against the US dollar. Each year, currencies are ranked by the previous year’s Corruption Perception Index (CPI). The portfolio goes long on the five least-corrupt currencies and short on the five most-corrupt, creating a zero-cost portfolio rebalanced annually to capture potential currency performance differences linked to corruption levels.

Economic rationale

While traditional FX models focus on dollar and carry risk, corruption remains an underexplored factor. Research shows high corruption can negatively impact GDP and currency value, introducing additional risk for investors and influencing market behavior. This strategy seeks to exploit these corruption-related effects on currency returns.

Backtest performance

Annualised return6.04%
Volatility8.14%
Beta-0.013
Sharpe ratio0.74
Win rate46%

Full Python code

from AlgorithmImports import *
#endregion
class EffectCorruptionFX(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
self.symbols = {'CAD' : 'USDCAD',
                'GBP' : 'USDGBP', 
                'MXN' : 'USDMXN',
                'EUR' : 'USDEUR',
                'NOK' : 'USDNOK', 
                'CHF' : 'USDCHF',
                'SEK' : 'USDSEK',
                'AUD' : 'USDAUD',
                'NZD' : 'NZDUSD',
                'JPY' : 'USDJPY', 
                'HKD' : 'USDHKD',
                'SGD' : 'USDSGD', 
                'ZAR' : 'USDZAR',
                'INR' : 'USDINR'
                }
for symbol in self.symbols:
    data = self.AddForex(self.symbols[symbol], Resolution.Daily, Market.FXCM)

self.cpi = self.AddData(CPIData, 'CPI', Resolution.Daily).Symbol
self.quantile = 3
first_key = [x for x in self.symbols.keys()][0]
self.Schedule.On(self.DateRules.MonthStart(self.symbols[first_key]), self.TimeRules.AfterMarketOpen(self.symbols[first_key]), self.Rebalance)
def Rebalance(self):
if self.Time.month != 1: return
cpi = {}
for symbol in self.symbols:
    if self.Securities[self.cpi].GetLastData():
        cpi[symbol] = self.Securities['CPI'].GetLastData()[symbol]
    
if len(cpi) < self.quantile: 
    self.Liquidate()
    return
sorted_by_cpi = sorted(cpi.items(), key = lambda x: x[1], reverse = True)
quantile = int(len(sorted_by_cpi) / self.quantile)
long = [x[0] for x in sorted_by_cpi[:quantile]]
short = [x[0] for x in sorted_by_cpi[-quantile:]]

self.Liquidate()

long_count = len(long)
short_count = len(short)

for symbol in long:
    if symbol == 'NZD':
        self.SetHoldings(self.symbols[symbol], 1/long_count)
    else:
        self.SetHoldings(self.symbols[symbol], -1/long_count)
for symbol in short:
    if symbol == 'NZD':
        self.SetHoldings(self.symbols[symbol], -1/short_count)
    else:
        self.SetHoldings(self.symbols[symbol], 1/short_count)
# CPI data
# NOTE: IMPORTANT: Data order must be ascending (date-wise)
from dateutil.relativedelta import relativedelta
class CPIData(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/economic/CPI.csv", SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = CPIData()
data.Symbol = config.Symbol

if not line[0].isdigit(): return None

# Example File Format:
# year;CAD;GBP;MXN;EUR;NOK;CHF;SEK;AUD;NZD;JPY;HKD;SGD;ZAR;INR
# 2013;84;74;34;79;85;86;88;85;90;74;77;87;43;36
#
# YEARLY DATA
split = line.split(';')

data.Time = datetime.strptime(split[0], "%Y") + relativedelta(months=12)  # NOTE: Preventing of look ahaead bias. Add 12 months so this year we see last year's data.
data.Value = split[1]
data['CAD'] = int(split[1])
data['GBP'] = int(split[2])
data['MXN'] = int(split[3])
data['EUR'] = int(split[4])
data['NOK'] = int(split[5])
data['CHF'] = int(split[6])
data['SEK'] = int(split[7])
data['AUD'] = int(split[8])
data['NZD'] = int(split[9])
data['JPY'] = int(split[10])
data['HKD'] = int(split[11])
data['SGD'] = int(split[12])
data['ZAR'] = int(split[13])
data['INR'] = int(split[14])
return data
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))