“该投资策略涉及做多腐败感知指数(CPI)得分高的国家的货币,做空腐败感知指数得分低的国家的货币,每年重新平衡。”

I. 策略概要

投资范围包括17种兑美元的流动性货币对。在每年年初,货币根据前一年的腐败感知指数(CPI)分为三分位数。低CPI表示高腐败,高CPI表示低腐败。投资组合做多CPI得分最高的五种货币,做空得分最低的五种货币。这个零成本投资组合每年重新平衡,旨在利用腐败认知与货币表现之间的关系。

II. 策略合理性

金融文献对外汇市场的关注是最近才出现的。外汇市场的第一个实证资产定价模型包括美元和利差风险因素。然而,腐败尚未被广泛认为是风险因素。一些研究表明,腐败遵循周期,并可能影响GDP增长。高腐败水平对国家及其货币都是有害的,可能通过引入额外的风险层、影响投资者行为和影响货币价值来影响金融市场。

III. 来源论文

Corruption, carry trades, and the cross section of currency returns [点击查看论文]

<摘要>

这是第一篇探讨感知腐败对外汇市场影响的论文。它发现,被认为遭受高腐败水平的国家货币产生的回报,在统计学上显著低于被认为腐败水平低的国家货币。此外,投资组合利差与美国全国经济研究所(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"))

发表评论

了解 Quant Buffet 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读