“该策略涉及根据12个月股票回报的z分数交易10年期债券。头寸根据z分数的符号和大小做多或做空,每月重新平衡。”

I. 策略概要

该投资范围包括来自美国、英国、德国、日本、加拿大和澳大利亚的10年期债券。头寸基于过去12个月股票回报的z分数,计算方法是:12个月回报减去10年平均回报,然后除以标准差。z分数上限为1(-1)以避免极端情况。正z分数导致债券空头头寸,而负z分数导致多头头寸。头寸规模由z分数的绝对值决定。投资组合等权重,每月重新平衡。还提出了仅使用z分数符号或无上限分数的变体。

II. 策略合理性

股票和债券在市场上充当替代品:当股票表现良好时,债券需求下降,导致其价格下跌;反之,在股市崩盘期间则相反。研究证实,这种可预测性在不同的经济条件下都表现稳健,包括衰退、扩张和不同通胀时期。研究结果表明,债券市场择时策略并非由于结构性债券风险,而是反映了真实的市场行为,在市场波动较大时回报更为显著。这突出了根据股市表现趋势择时进入和退出债券市场的能力。

III. 来源论文

Predicting Bond Returns: 70 years of International Evidence [点击查看论文]

<摘要>

我们通过对主要债券市场70年国际数据的深入研究,考察了政府债券回报的可预测性。利用基于经济交易的测试框架,我们发现了债券回报可预测性的强有力经济和统计证据,自1950年以来夏普比率为0.87。这一发现对市场和时间段都具有稳健性,包括30年的国际债券市场样本外数据和另外九个国家的数据。此外,结果在各种经济环境中保持一致,包括利率长期上升或下降的时期,并且在扣除交易成本后仍可利用。可预测性与通胀和经济增长的可预测性相关。总的来说,政府债券溢价显示出可预测的动态,国际债券市场回报的择时为投资者提供了可利用的机会。

IV. 回测表现

年化回报3.8%
波动率10%
β值-0.068
夏普比率0.38
索提诺比率-0.101
最大回撤N/A
胜率69%

V. 完整的 Python 代码

import numpy as np
from collections import deque
from AlgorithmImports import *
class PredictingBondReturnswithEquityReturn(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        # Bond future and equity etf.
        self.symbols = [
                        ("ASX_XT1", 'EWA'),       # 10 Year Commonwealth Treasury Bond Futures, Continuous Contract #1 (Australia)
                        ("MX_CGB1",  'EWC'),      # Ten-Year Government of Canada Bond Futures, Continuous Contract #1 (Canada)
                        ("EUREX_FGBL1", 'EWG'),   # Euro-Bund (10Y) Futures, Continuous Contract #1 (Germany)
                        ("LIFFE_R1", 'EWU'),      # Long Gilt Futures, Continuous Contract #1 (U.K.)
                        ("SGX_JB1", 'EWJ'),       # SGX 10-Year Mini Japanese Government Bond Futures, Continuous Contract #1 (Japan)
                        ("CME_TY1",  'SPY')       # 10 Yr Note Futures, Continuous Contract #1 (USA)
                        ]
                    
        # Monthly price data.
        self.data = {}
        
        self.month_period = 5
        self.period = self.month_period * 12 + 1
        
        self.SetWarmUp(self.period * 21)
        
        for bond_future, equity_etf in self.symbols:
            data = self.AddData(QuantpediaFutures, bond_future, Resolution.Daily)
            data.SetFeeModel(CustomFeeModel())
            data.SetLeverage(10)
            
            # Equity data.
            self.AddEquity(equity_etf, Resolution.Daily)
            self.data[equity_etf] = deque(maxlen = self.period)
        
        self.last_month = -1
        self.Schedule.On(self.DateRules.MonthStart(self.symbols[0][1]), self.TimeRules.At(0, 0), self.Rebalance)
        
    def OnData(self, data):
        # Update only on new month start.
        if self.Time.month == self.last_month:
            return
        self.last_month = self.Time.month
    
        # Store monthly data.
        for bond_future, equity_etf in self.symbols:
            if equity_etf in data and data[equity_etf]:
                price = data[equity_etf].Value
                self.data[equity_etf].append(price)
        
    def Rebalance(self):
        # Z score calc.
        weight = {}
        for bond_future, equity_etf in self.symbols:
            if self.Securities[bond_future].GetLastData() and self.time.date() < QuantpediaFutures.get_last_update_date()[bond_future]:
                # At least 3 years of data is ready.
                minimum_data_count = ((self.period-1) / self.month_period) * 3
                if len(self.data[equity_etf]) >= minimum_data_count:
                    closes = [x for x in self.data[equity_etf]]
                    separete_yearly_returns = [Return(closes[x:x+13]) for x in range(0, len(closes),1)]
                    return_mean = np.mean(separete_yearly_returns)
                    return_std = np.std(separete_yearly_returns)
                    z_score = (separete_yearly_returns[-1] - return_mean) / return_std
                    
                    if z_score > 1: z_score = 1
                    elif z_score < -1: z_score = -1
                    
                    weight[bond_future] = -1 * z_score
                    
        # Trade execution    
        invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
        for symbol in invested:
            if symbol not in weight:
                self.Liquidate(symbol)
        for symbol, weight in weight.items():
            self.SetHoldings(symbol, weight)
            
# Custom fee model
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
    _last_update_date:Dict[Symbol, datetime.date] = {}
    @staticmethod
    def get_last_update_date() -> Dict[Symbol, datetime.date]:
       return QuantpediaFutures._last_update_date
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
    def Reader(self, config, line, date, isLiveMode):
        data = QuantpediaFutures()
        data.Symbol = config.Symbol
        
        if not line[0].isdigit(): return None
        split = line.split(';')
        
        data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
        data['back_adjusted'] = float(split[1])
        data['spliced'] = float(split[2])
        data.Value = float(split[1])
        if config.Symbol.Value not in QuantpediaFutures._last_update_date:
            QuantpediaFutures._last_update_date[config.Symbol.Value] = datetime(1,1,1).date()
        if data.Time.date() > QuantpediaFutures._last_update_date[config.Symbol.Value]:
            QuantpediaFutures._last_update_date[config.Symbol.Value] = data.Time.date()
        return data
def Return(values):
    return (values[-1] - values[0]) / values[0]

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读