该策略从美国投资者的角度,涵盖28个货币市场。每个当地货币的回报通过股票和债券预测模型回归,使用最新120个月的月度观察数据。回报计算为以自然对数表示的汇率变化。每月生成下一期的回报预测:若预计回报为正,则买入该货币;若为负,则卖出。最终构建等权重的多空组合,并每月重新平衡。文中还讨论了递归回归预测策略的变体,相关结果见表2A。策略为多空组合,尽管某些符号可能暗示纯多头策略。

策略概述

从美国投资者的角度,投资范围包括28个货币市场。对于每个当地货币,使用股票和债券预测模型来回归第t+1期的当地货币回报,基于t期的股票和债券预测因子集,回归使用最新的120个月的月度观察数据(见文献中方程4)。计算当地货币在第t+1期的回报为以自然对数表示的汇率变化(即t+1期与t期时以美元表示的当地货币汇率的差异)。每月基于滚动窗口的120个月的观察数据,为每个当地货币生成下一个月的回报预测。交易规则如下:如果预计某个货币在t+1期的回报为正,则在t期买入该货币;如果预计回报为负,则卖出该货币。最终构建等权重的多空组合,并每月重新平衡。文中还提供了使用递归回归预测下一个月回报的变体策略,相关结果见表2A。需注意,策略为多空组合,尽管某些符号暗示了纯多头策略。

策略合理性

该策略基于两个理论:追逐回报假设和组合再平衡假设,但两者对汇率变化的预测方向不同。一方面,追逐回报假设预测当地股市回报的增加将吸引外国投资者投资当地股市,从而导致当地货币升值;另一方面,组合再平衡假设预测投资者将减少对表现良好的国家的外汇敞口,因此股市回报增加可能导致货币贬值。此外,债券预测因子的加入灵感来源于未覆盖利率平价理论(UIP),即汇率变化等于两国之间的利率差。结合股市和债市信息的模型能够为汇率变化提供重要信息。

论文来源

Foreign Currency Forecasting: What Can Stock and Bond Markets Tell Us? [点击浏览原文]

<摘要>

本文提供了统计和经济证据,证明当地货币的股票和债券回报对预测汇率回报具有显著作用。我们首先构建了股票和债券回报的样本外(OOS)预测模型,并评估其相对于随机游走(RW)模型的统计准确性。接着,我们设计了基于这些预测信号的交易策略。使用28个国家的数据,得出了三个关键结果:(1)样本内的回归结果表明所有预测模型的斜率估计显著,表明RW模型存在误差;(2)统计准确性评估显示我们的OOS预测优于RW模型;(3)基于股票和债券回报的交易策略产生了高且显著的夏普比率,且与传统风险因子和其他货币回报决定因素无关。

回测表现

年化收益率8.47%
波动率3.88%
Beta-0.009
夏普比率2.11
索提诺比率-0.3
最大回撤N/A
胜率49%

完整python代码

import statsmodels.api as sm
from AlgorithmImports import *
import data_tools

class TheSerialDependenceoftheCommodityFuturesReturns(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)

        # equity etf and 10Y bond yield symbols by currency symbol
        self.symbols = {
            "AUDUSD" : ("EWA", "AU10YT"),
            "CADUSD" : ("EWC", "CA10YT"),
            "EURUSD" : ("EWG", "DE10YT"),
            "JPYUSD" : ("EWJ", "JP10YT"),
            "NZDUSD" : ("ENZL", "NZ10YT"),
            "USDNOK" : ("ENOR", "NO10YT"),
            "CHFUSD" : ("EWL", "CH10YT"),
            "GBPUSD" : ("EWU", "GB10YT"),
            "USDINR" : ("INDA", "IN10YT"),
            "USDMXN" : ("EWW", "MX10YT"),
            "USDPLN" : ("EPOL", "PL10YT"),
            "USDSGD" : ("EWS", "SG10YT"),
            "USDTRY" : ("TUR", "TR10YT"),
            }
        
        self.us_bonds = self.AddData(data_tools.QuantpediaBondYield, "US10YT", Resolution.Daily).Symbol
        self.us_equities = self.AddEquity("VTI", Resolution.Daily).Symbol

        self.daily_period = 21
        self.period = 120 + 1
        self.SetWarmUp(self.period * self.daily_period, Resolution.Daily)
        
        self.perf_data = {}
        self.last_month_price = {}
        self.bond_yield_values = {}
        
        for currency_symbol, (equity_symbol, bond_symbol) in self.symbols.items():
            # currency
            data = self.AddForex(currency_symbol, Resolution.Daily, Market.Oanda)
            data.SetFeeModel(data_tools.CustomFeeModel())
            data.SetLeverage(5)
            
            # equity
            self.AddEquity(equity_symbol, Resolution.Daily)
            
            # bond
            self.AddData(data_tools.QuantpediaBondYield, bond_symbol, Resolution.Daily)
            self.bond_yield_values[bond_symbol] = []
            
            # price and performance data needed
            symbols_needed = [currency_symbol, equity_symbol, bond_symbol]
            for symbol in symbols_needed:
                self.last_month_price[symbol] = None    
                self.perf_data[symbol] = RollingWindow[float](self.period)

        self.last_month_price[self.us_equities] = None    
        self.perf_data[self.us_equities] = RollingWindow[float](self.period)
        
        self.perf_data[self.us_bonds] = RollingWindow[float](self.period)
        self.bond_yield_values[self.us_bonds] = []

        self.recent_month = -1

    def OnData(self, data):
        # store daily bond yields
        bond_yield_symbols = [x[1][1] for x in self.symbols.items()]
        for bond_symbol in bond_yield_symbols + [self.us_bonds]:
            if bond_symbol in data and data[bond_symbol]:
                self.bond_yield_values[bond_symbol].append(data[bond_symbol].Value)
        
        # rebalance once a month
        if self.recent_month == self.Time.month:
            return
        self.recent_month = self.Time.month
        
        if len(self.bond_yield_values[self.us_bonds]) == 0:
            return

        us_data_available = False
        
        # store monthly performance for us bonds and equities
        if self.Securities[self.us_equities].GetLastData() and (self.Time.date() - self.Securities[self.us_equities].GetLastData().Time.date()).days < 5 and    \
            self.Securities[self.us_bonds].GetLastData() and (self.Time.date() - self.Securities[self.us_bonds].GetLastData().Time.date()).days < 5:
            
            us_eq_price = self.Securities[self.us_equities].Price

            # symbol has last month's price stored
            if self.last_month_price[self.us_equities] is not None:
                self.perf_data[self.us_equities].Add(us_eq_price - self.last_month_price[self.us_equities])
            
            # store monthly price
            self.last_month_price[self.us_equities] = us_eq_price
            
            # calculate monthly bond yield equity performance
            monthly_us_bond_perf = self.bond_yield_equity_performance(np.array(self.bond_yield_values[self.us_bonds]))     # take any count there is for a given month of daily bond yield values
            self.perf_data[self.us_bonds].Add(monthly_us_bond_perf)

            us_data_available = True
        
        long = []
        short = []
        for currency_symbol, (equity_symbol, bond_symbol) in self.symbols.items():
            # bond and equity data is still comming in
            if self.Securities[currency_symbol].GetLastData() and (self.Time.date() - self.Securities[currency_symbol].GetLastData().Time.date()).days < 5 and \
                self.Securities[bond_symbol].GetLastData() and (self.Time.date() - self.Securities[bond_symbol].GetLastData().Time.date()).days < 5 and \
                self.Securities[equity_symbol].GetLastData() and (self.Time.date() - self.Securities[equity_symbol].GetLastData().Time.date()).days < 5:
                
                symbols_needed = [currency_symbol, equity_symbol]
                
                # store monthly prices and performance
                for symbol in symbols_needed:
                    price = self.Securities[symbol].Price
                    
                    # symbol has last month's price stored
                    if self.last_month_price[symbol] is not None:
                        self.perf_data[symbol].Add(price - self.last_month_price[symbol])
                    
                    # store monthly price
                    self.last_month_price[symbol] = price
                
                # bond yield value collection is not empty and it's size is equall to size of us bond yield values
                if len(self.bond_yield_values[bond_symbol]) != 0:
                    # compute monthly bond yield equity performance
                    monthly_bond_perf = self.bond_yield_equity_performance(np.array(self.bond_yield_values[bond_symbol]))   # take any count there is for a given month of daily bond yield values
                    self.perf_data[bond_symbol].Add(monthly_bond_perf)
                
                symbols_needed += [bond_symbol]
                
                # regression data is ready
                if all(self.perf_data[symbol].IsReady for symbol in symbols_needed + [self.us_bonds, self.us_equities]) and us_data_available:
                    # linear regression
                    y = np.array([x for x in self.perf_data[currency_symbol]][:-1])
                    x = np.array([
                        [x for x in self.perf_data[bond_symbol]][1:],
                        [x for x in self.perf_data[self.us_bonds]][1:],
                        [x for x in self.perf_data[equity_symbol]][1:],
                        [x for x in self.perf_data[self.us_equities]][1:]
                        ])
                
                    # prediction
                    model_result = self.multiple_linear_regression(x, y)
                    x_pred = np.array([
                        1,
                        self.perf_data[bond_symbol][0],
                        self.perf_data[self.us_bonds][0],
                        self.perf_data[equity_symbol][0],
                        self.perf_data[self.us_equities][0]
                        ])
                    
                    y_pred = model_result.predict(x_pred)[0]
                    if y_pred > 0:
                        # reverse position if needed
                        if currency_symbol[-3:] == 'USD':
                            long.append(currency_symbol)
                        else:
                            short.append(currency_symbol)
                    else:
                        # reverse position if needed
                        if currency_symbol[-3:] == 'USD':
                            short.append(currency_symbol)
                        else:
                            long.append(currency_symbol)
            
            self.bond_yield_values[bond_symbol] = []
        
        self.bond_yield_values[self.us_bonds] = []
        
        if self.IsWarmingUp:
            return
        
        # trade execution
        invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
        for symbol in invested:
            if symbol not in long + short:
                self.Liquidate(symbol)
                
        for symbol in long:
            self.SetHoldings(symbol, 1 / len(long))
        for symbol in short:
            self.SetHoldings(symbol, -1 / len(short))

    def multiple_linear_regression(self, x:np.array, y:np.array):
        x = x.T
        x = sm.add_constant(x)
        result = sm.OLS(endog=y, exog=x).fit()
        return result
    
    def bond_yield_equity_performance(self, bond_yield_values:np.array) -> float:
        x:np.array = 1+bond_yield_values[:-1]/100/250
        y:np.array = 7.7 * (bond_yield_values[:-1] - bond_yield_values[1:]) / 100
        z:np.array = x+y
        
        previous_equity:float = 1.0
        eq:list = [previous_equity]
        
        # calculate next equity value from the previous one
        for value in z:
            new_eq = value*previous_equity
            eq += [new_eq]
            previous_equity = new_eq
            
        performance:float = (eq[-1] / eq[0] - 1)
        return performance

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading