“通过国民净资产与GDP比率和外币债务每月交易55种货币,做多国民净资产与GDP比率低/外币债务高的货币,做空国民净资产与GDP比率高/外币债务低的货币,使用等权重投资组合。”

I. 策略概要

投资范围包括55个国家的货币。在月末,货币根据其国民净资产(NFA)与GDP的比率分为两组。在低NFA组中,做多外币计价债务份额最高的40%国家。在高NFA组中,做空此类债务份额最低的40%国家。投资组合等权重,每月重新平衡,利用国民净资产头寸与外币债务敞口之间的关系来指导交易决策。

II. 策略合理性

研究表明,在金融困境时期(即金融家吸收能力非常低,风险厌恶情绪非常高),净债务国面临货币贬值。因此,投资者要求持有债务国货币的溢价,因为它们风险更高。此外,Eichengreen和Hausmann(2005)认为,货币风险溢价的程度也与外债的货币计价有关,因为面临政治不稳定或高通胀的风险较高国家可能被迫发行以外币计价的债务。

III. 来源论文

Currency Premia and Global Imbalances [点击查看论文]

<摘要>

我们表明,一个全球失衡风险因子,它捕捉了国家外部失衡的利差及其以外币发行外部负债的倾向,解释了货币超额回报的横截面变异。经济直觉很简单:净债务国提供货币风险溢价,以补偿愿意为负外部失衡提供资金的投资者,因为它们的货币在经济不景气时会贬值。这种机制与基于不完善金融市场资本流动的汇率理论一致。我们还发现,全球失衡因子在其他主要资产市场的横截面中得到了定价。

IV. 回测表现

年化回报4.4%
波动率6.43%
β值-0.048
夏普比率0.68
索提诺比率-0.365
最大回撤-20%
胜率35%

V. 完整的 Python 代码

from AlgorithmImports import *
import data_tools
#endregion
class GlobalImbalanceCurrencyFactor(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        self.quantpedia_futures:bool = False
        
        self.countries:Dict[str, List[str]] = {
            'Australia': ['AUDUSD', 'AUS_GDP'],
            'United Kingdom': ['GBPUSD', 'GBR_GDP'],
            'New Zealand': ['NZDUSD', 'NZL_GDP'],
            'Canada': ['USDCAD', 'CAN_GDP'],
            'Switzerland': ['USDCHF', 'CHE_GDP'],
            'China': ['USDCNH', 'CHN_GDP'],
            'Czech Republic': ['USDCZK', 'CZE_GDP'],
            'Denmark': ['USDDKK', 'DNK_GDP'],
            'Hungary': ['USDHUF', 'HUN_GDP'],
            'India': ['USDINR', 'IND_GDP'],
            'Japan': ['USDJPY' ,'JPN_GDP'],
            'Norway': ['USDNOK', 'NOR_GDP'],
            'Poland': ['USDPLN', 'POL_GDP'],
            'Saudi Arabia': ['USDSAR', 'SAU_GDP'],
            'Sweden': ['USDSEK', 'SWE_GDP'],
            'Thailand': ['USDTHB', 'THA_GDP'],
            'Turkey': ['USDTRY', 'TUR_GDP']
        }
        
        if self.quantpedia_futures:
            self.countries:Dict[str, List[str]] = {
                'Australia': ['CME_AD1', 'AUS_GDP'],
                'United Kingdom': ['CME_BP1', 'GBR_GDP'],
                'New Zealand': ['CME_NE1', 'NZL_GDP'],
                'Canada': ['CME_CD1', 'CAN_GDP'],
                'Switzerland': ['CME_SF1', 'CHE_GDP'],
                'Japan': ['CME_JY1' ,'JPN_GDP']
            }
        
        self.quantile:int = 2
        self.leverage:int = 5
        
        self.net_foreign_assets:Symbol = self.AddData(data_tools.QuantpediaNetForeignAssets, 'NET_FOREIGN_ASSETS', Resolution.Daily).Symbol
        self.debt_all_currencies:Symbol = self.AddData(data_tools.QuantpediaCurrenciesDebt ,'DEBT_ALL_CURRENCIES', Resolution.Daily).Symbol
        self.debt_foreign_currencies:Symbol = self.AddData(data_tools.QuantpediaCurrenciesDebt ,'DEBT_FOREIGN_CURRENCIES', Resolution.Daily).Symbol
        
        # store SymbolData object under country 
        self.data:Dict[str, SymbolData] = { x : SymbolData() for x in self.countries }
        
        for country in self.countries:
            ticker:str = self.countries[country][0]
            gdp:str = self.countries[country][1]
            
            if self.quantpedia_futures:
                data = self.AddData(data_tools.QuantpediaFutures, ticker, Resolution.Daily)
            else:
                # subscribe to currency
                data = self.AddForex(ticker, Resolution.Daily, Market.Oanda)
            
            data.SetLeverage(self.leverage)
            
            # subscribe to quandl gdp
            self.AddData(data_tools.GDPData, gdp, Resolution.Daily)
        
        self.symbol:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
        
        self.selection_flag:bool = False
        self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection)
    def OnData(self, data: Slice) -> None:
        if self.debt_all_currencies in data and data[self.debt_all_currencies]:
            for country in self.countries:
                # get country debt based on index from data object
                debt = data[self.debt_all_currencies].GetProperty(country)
                # store debt in SymbolData object under country name in dictionary
                self.data[country].all_currencies_debt_value = float(debt)
        
        if self.net_foreign_assets in data and data[self.net_foreign_assets]:
            for country in self.countries:
                # get country net foreign asset based on index from data object
                asset = data[self.net_foreign_assets].GetProperty(country)
                # store net foreign asset in SymbolData object under country name in dictionary
                self.data[country].nfa_value = float(asset)
        
        if self.debt_foreign_currencies in data and data[self.debt_foreign_currencies]:
            for country in self.countries:
                # get country debt based on index from data object
                debt = data[self.debt_foreign_currencies].GetProperty(country)
                # store debt in SymbolData object under country name in dictionary
                self.data[country].foreign_currencies_debt_value = float(debt)
        debt_last_update_date:Dict[str, datetime.date] = data_tools.QuantpediaCurrenciesDebt.get_last_update_date()
        nfa_last_update_date:Dict[str, datetime.date] = data_tools.QuantpediaNetForeignAssets.get_last_update_date()
        gdp_last_update_date:Dict[str, datetime.date] = data_tools.GDPData.get_last_update_date()
        if self.quantpedia_futures:
            futures_last_update_date:Dict[str, datetime.date] = data_tools.QuantpediaFutures.get_last_update_date()
        # custom data is still comming in
        if not all(self.Securities[x].GetLastData() and self.Time.date() < debt_last_update_date[x.Value] for x in [self.debt_foreign_currencies, self.debt_all_currencies]):
            self.Liquidate()
            return
        if not (self.Securities[self.net_foreign_assets].GetLastData() and self.Time.date() < nfa_last_update_date):
            self.Liquidate()
            return
        for country in self.countries:
            ticker:str = self.countries[country][0]
            gdp:str = self.countries[country][1]
         
            if gdp in data and data[gdp]:
                value = data[gdp].Value
                # update gdp value
                self.data[country].gdp_value = value
                self.data[country].gdp_ready = False
                    
        # rebalance at the end of the month
        if not self.selection_flag:
            return
        self.selection_flag = False
        
        # storing nfa/gdp values under countries
        nfa:Dict[str, float] = {}
        
        # storing foreign/all gross debts under countries
        share_of_debt:Dict[str, float] = {}
        
        for country, symbol_data in self.data.items():
            # data is still comming in
            if self.quantpedia_futures:
                if self.Securities[self.countries[country][0]].GetLastData() and self.Time.date() > futures_last_update_date[self.countries[country][0]]:
                    continue
            else:
                if not self.Securities[self.countries[country][0]].GetLastData():
                    continue
            if self.Securities[self.countries[country][1]].GetLastData() and self.Time.date() > gdp_last_update_date[self.countries[country][1]]:
                continue
                        
            if not symbol_data.is_ready():
                continue
            
            # store value under country
            nfa[country] = symbol_data.nfa_value / symbol_data.gdp_value
            
            # get country debts
            foreign_debt:float = symbol_data.foreign_currencies_debt_value
            all_debt:float = symbol_data.all_currencies_debt_value
            
            # can't perform division by zero
            if all_debt == 0:
                share_of_debt[country] = 0
                continue
            
            # store share of debt under country
            share_of_debt[country] = foreign_debt / all_debt
            
        # performing one month lag
        for _, symbol_data in self.data.items():
            symbol_data.gdp_ready = True
            
        if len(nfa) < self.quantile:
            self.Liquidate()
            return
        
        quantile:int = int(len(nfa) / self.quantile)
        sorted_nfa:List[str] = [x[0] for x in sorted(nfa.items(), key=lambda item: item[1])]
        sorted_share_of_debt:List[str] = [x[0] for x in sorted(share_of_debt.items(), key=lambda item: item[1])]
        
        low_nfa:List[str] = sorted_nfa[:quantile]
        high_nfa:List[str] = sorted_nfa[-quantile:]
        
        low_nfa_by_share_of_debt:List[str] = sorted(low_nfa, key = lambda x: share_of_debt[x])
        high_nfa_by_share_of_debt:List[str] = sorted(high_nfa, key = lambda x: share_of_debt[x])
        
        long:List[str] = []
        short:List[str] = []
        
        if len(low_nfa_by_share_of_debt) >= self.quantile and len(high_nfa_by_share_of_debt) >= self.quantile:
            quantile = int(len(low_nfa_by_share_of_debt) / self.quantile)    
            long = [self.countries[x][0] for x in low_nfa_by_share_of_debt[-quantile:]]
            short = [self.countries[x][0] for x in high_nfa_by_share_of_debt[:quantile]]
        
        # trade execution
        long_length:int = len(long)
        short_length:int = len(short)
        
        invested:List[str] = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
        for ticker in invested:
            if ticker not in long + short:
                self.Liquidate(ticker)
        
        for ticker in long:
            weight = 1 / long_length
            
            if ticker[:3] == 'USD':
                weight = weight * -1
            
            self.SetHoldings(ticker, weight)
            
        for ticker in short:
            weight = -1 / short_length
            
            if ticker[:3] == 'USD':
                weight = weight * -1
            
            self.SetHoldings(ticker, weight)
                
    def Selection(self) -> None:
        self.selection_flag = True
        
class SymbolData():
    def __init__(self):
        self.nfa_value = -1
        self.all_currencies_debt_value = -1
        self.foreign_currencies_debt_value = -1
        self.gdp_value = -1
        self.gdp_ready = False
        
    def is_ready(self):
        return self.gdp_ready and self.nfa_value != -1 and \
                self.all_currencies_debt_value != -1 and self.foreign_currencies_debt_value != -1

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读