该策略投资于10年期和1个月期美国国债。根据纽约联邦储备银行数据,每月计算40个月的移动平均期限溢价。通过减去移动平均值生成均值回归信号,识别期限溢价较高的时期。交易规则为:若期限溢价低于40个月移动平均值,投资于10年期国债;否则,投资1个月期国债。策略每月再平衡,投资组合完全分配于单一债券资产。

策略概述

投资标的由两种美国国债组成:10年期和1个月期美国国库券。(纽约联邦储备银行发布的数据可用于计算NSS收益率曲线。)

<策略逻辑>

  1. 每月根据回溯的扩展窗口估算期限溢价,采用40个月的移动平均值。
  2. 信号用于区分期限溢价相对较高的时期。

<计算>

通过减去移动平均值(MA)生成均值回归信号,从而识别期限溢价较高的时期。

<交易规则>

每月月底做出投资决策。如果信号显示应承担期限风险(即期限溢价低于40个月移动平均值),则在下一个月投资于10年期债券;否则,投资于1个月期债券。

该策略假定每月再平衡,投资组合完全投资于一种债券资产。

策略合理性

期限溢价符合经济理论,并在系统化债券交易策略中具有实际应用价值。首先,MA和HMM期限溢价信号可以识别出相对全样本平均值显著增加的期限溢价期。这些高期限溢价期与理论所建议的经济上显著增加的风险调整收益有关。其次,在美国和南非市场内外,两种策略均显著提高了风险调整后的收益。本文旨在研究期限溢价是否可以作为信号,以应时承担期限风险,并获得更高的风险调整收益,正如相关理论所建议的那样。尽管一些学者对期限溢价作为收益曲线形状的解释提出了异议,期限溢价信号及其相关的交易策略表明,期限溢价确实可以预测从期限风险因素中获得更高的风险调整收益。

论文来源

Trading the Term Premium [点击浏览原文]

<摘要>

近年来,因子投资策略的普及突显了这样一个观点,即通过在风险溢价较高的时期承担定时风险敞口,投资组合可以获得改进的风险调整收益。虽然在股市上已有大量关于此类风险因素和风险溢价的研究,但在固定收益市场上,相关研究较少。

其中一个已开始引起实践者和学者兴趣的固定收益风险因素是期限溢价,即与债券期限风险相关的风险溢价。Adrian等人(ACM)(2013年)引入了一个复杂的仿射期限结构模型,该模型能够使用线性回归有效估算期限溢价。虽然该模型与经济理论相符,但在债券组合中运用期限溢价以定时承担期限风险的实际应用研究较少。

本报告调查了ACM模型在南非和美国主权债券市场中的实际应用,发现该模型生成的信号能够捕捉到风险调整收益增加的时期。使用这些信号进行系统化策略同样产生了令人鼓舞的结果。

回测表现

年化收益率8.08%
波动率7.45%
Beta-0.053
夏普比率1.08
索提诺比率-0.067
最大回撤-17.8%
胜率45%

完整python代码

from AlgorithmImports import *
from pandas.core.frame import DataFrame
# endregion
class TermSpreadandTermPremiumPredictUSGovernmentBondsReturns(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2007, 6, 1) # BIL inception
        self.SetCash(100000)
        self.long_duration_bond:Symbol = self.AddEquity('IEF', Resolution.Daily).Symbol
        self.short_duration_bond:Symbol = self.AddEquity('BIL', Resolution.Daily).Symbol
        self.term_spread:Symbol = self.AddData(TermSpread, 'T10Y3M', Resolution.Daily).Symbol
        self.period:int = 40 * 21
        self.rebalance_flag:bool = False
        self.term_spread_sma = self.SMA(self.term_spread, self.period, Resolution.Daily)
        self.SetWarmup(self.period, Resolution.Daily)
        self.recent_month:int = -1
    def OnData(self, data: Slice) -> None:
        if self.IsWarmingUp:
            return
        t10y3m_last_update_date:datetime.date = TermSpread._last_update_date
        # check if custom data is still arriving
        if self.Securities[self.term_spread].GetLastData() and self.Time.date() >= t10y3m_last_update_date:
            self.Liquidate()
            return
        # rebalance monthly
        if self.Time.month == self.recent_month:
            return
        self.recent_month = self.Time.month
        # compare latest value with 40-month moving average
        traded_asset:Symbol|None = None
        risk_flag:bool = False
        if self.term_spread in data and data[self.term_spread]:
            if data[self.term_spread].Price < self.term_spread_sma.Current.Value:
                traded_asset = self.long_duration_bond
            else:
                traded_asset = self.short_duration_bond
        # trade execution
        if all(x in data and data[x] for x in [self.long_duration_bond, self.short_duration_bond]):
            if not self.Portfolio[traded_asset].Invested:
                self.Liquidate()
                self.SetHoldings(traded_asset, 1)
# Source: https://fred.stlouisfed.org/series/T10Y3M
class TermSpread(PythonData):
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource('data.quantpedia.com/backtesting_data/economic/T10Y3M.csv', SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
    _last_update_date:datetime.date = datetime(1,1,1).date()
    @staticmethod
    def get_last_update_date() -> Dict[Symbol, datetime.date]:
       return TermSpread._last_update_date
    def Reader(self, config, line, date, isLiveMode):
        data = TermSpread()
        data.Symbol = config.Symbol
        if not line[0].isdigit(): return None
        split = line.split(';')
        
        # Parse the CSV file's columns into the custom data class
        data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
        if split[1] != '.':
            data.Value = float(split[1])
        if data.Time.date() > TermSpread._last_update_date:
            TermSpread._last_update_date = data.Time.date()
        
        return data

Leave a Reply

Discover more from Quant Buffet

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

Continue reading