
“该投资策略涉及做多腐败感知指数(CPI)得分高的国家的货币,做空腐败感知指数得分低的国家的货币,每年重新平衡。”
资产类别: 差价合约、远期、期货、互换 | 地区: 美国 | 周期: 每年 | 市场: 外汇 | 关键词: 腐败
I. 策略概要
投资范围包括17种兑美元的流动性货币对。在每年年初,货币根据前一年的腐败感知指数(CPI)分为三分位数。低CPI表示高腐败,高CPI表示低腐败。投资组合做多CPI得分最高的五种货币,做空得分最低的五种货币。这个零成本投资组合每年重新平衡,旨在利用腐败认知与货币表现之间的关系。
II. 策略合理性
金融文献对外汇市场的关注是最近才出现的。外汇市场的第一个实证资产定价模型包括美元和利差风险因素。然而,腐败尚未被广泛认为是风险因素。一些研究表明,腐败遵循周期,并可能影响GDP增长。高腐败水平对国家及其货币都是有害的,可能通过引入额外的风险层、影响投资者行为和影响货币价值来影响金融市场。
III. 来源论文
Corruption, carry trades, and the cross section of currency returns [点击查看论文]
- Grobys、Heinonen,瓦萨大学;于韦斯屈莱大学,瓦萨大学会计与金融系
<摘要>
这是第一篇探讨感知腐败对外汇市场影响的论文。它发现,被认为遭受高腐败水平的国家货币产生的回报,在统计学上显著低于被认为腐败水平低的国家货币。此外,投资组合利差与美国全国经济研究所(NBER)的衰退和美国非耐用品消费增长高度相关。有趣的是,随机贴现因子模型分析显示,即使在控制标准外汇风险因素的情况下,投资组合利差对于定价货币回报的横截面也是有用的。


IV. 回测表现
| 年化回报 | 6.04% |
| 波动率 | 8.14% |
| β值 | -0.013 |
| 夏普比率 | 0.74 |
| 索提诺比率 | N/A |
| 最大回撤 | N/A |
| 胜率 | 46% |
V. 完整的 Python 代码
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"))