投资范围包括两个美国ETF:SPY和BIL。我们构建了一种基于50%门槛的MRI衰退信号的市场时机策略,以在股票和现金之间切换。MRI Raw_t指标通过新闻媒体中“衰退”一词的出现次数计算,并用于回归分析,估计NBER衰退概率。当未来6个月的衰退概率低于50%时,持有股票市场指数;高于50%时,持有3个月期国债。投资组合根据信号动态再平衡,完全投资于其中一种资产。

策略概述

投资范围包括两个美国ETF:SPY和BIL。我们构建了一种基于50%门槛的MRI衰退信号的市场时机策略,用以在股票和现金之间切换。MRI Raw_t指标变量按照方程(1)计算,基本上是新闻媒体中“衰退”一词的出现次数的总和;该变量加入方程(3)的回归公式中,用于计算方程(9)和(10)中的衰退概率。该回归的因变量为NBER的衰退概率,而自变量是MRI Raw_t的估计系数。当未来6个月内的衰退概率低于50%时,持有股票市场指数;当衰退概率高于50%时,持有美国3个月期国债。投资组合根据信号动态再平衡,二元化操作:完全投资于其中一种资产。

策略合理性

基于计算语言学(或自然语言处理[NLP])技术,并应用2000年代的研究成果,目标是在媒体指标上升到某一数值后预测进入衰退的几个月前景。关键思路很简单:作者通过金融和经济专刊(如FT和WSJ)中“衰退”一词的出现频率来衡量衰退信号。科学家发现,MRI可以提前6个月预测NBER衰退,其表现优于传统预测衰退的其他变量。由于这一任务的复杂性,采用了非线性模型来捕捉经济活动和资产价格迅速变化带来的潜在非线性。文章还对其媒体衰退指标进行了多项稳健性检查。

论文来源

What is the Value of Financial News? [点击浏览原文]

<摘要>

我们构建了基于金融报刊中提到‘衰退’一词的出现次数的美国商业周期活动的实证衡量指标。MRI(媒体衰退指标)是预测美国经济活动和股票回报的有效工具,无论是在样本内还是样本外。并且,与现有的商业周期预测指标(期限溢价、违约利差、不确定性和大数据指标)相比,MRI具有更好的表现。MRI还能够提前六个月预测美国经济衰退的概率。基于此信息,我们展示了简单的市场时机投资策略能够大幅超过股票市场指数(如S&P500)。结论表明,阅读金融新闻可以产生金融价值。

回测表现

年化收益率8.78%
波动率13.52%
Beta0.45
夏普比率0.65
索提诺比率0.338
最大回撤-18.15%
胜率61%

完整python代码

from AlgorithmImports import *
from typing import List
from data_tools import FREDData, Data, IndexMRI, IndexNBER, MultipleLinearRegression
# endregion

class TextBasedRecessionDetectionStrategy(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100_000)
        
        self.leverage: int = 5
        self.recent_month: int = -1
        self.regression_period: int = 6
        self.max_missing_days: int = 35
        self.min_values: int = 15
        self.threshold: float = 0.5

        self.data: Data = Data(self.regression_period)

        security: Equity = self.AddEquity('SPY', Resolution.Daily)
        security.SetLeverage(self.leverage)

        self.spy: Symbol = security.Symbol

        security: Equity = self.AddEquity('BIL', Resolution.Daily)
        security.SetLeverage(self.leverage)

        self.bil: Symbol = security.Symbol

        self.baa10ym: Symbol = self.AddData(FREDData, 'BAA10YM', Resolution.Daily).Symbol
        self.t10y3m: Symbol = self.AddData(FREDData, 'T10Y3M', Resolution.Daily).Symbol

        self.mri: Symbol = self.AddData(IndexMRI, 'MRI', Resolution.Daily).Symbol
        self.nber: Symbol = self.AddData(IndexNBER, 'NBER', Resolution.Daily).Symbol

    def OnData(self, slice: Slice) -> None:
        if self.baa10ym in slice and slice[self.baa10ym]:
            # monthly data
            curr_date:datetime.date = self.Time.date()

            # make sure data still coming
            if not self.data.baa10ym_data_still_coming(curr_date, self.max_missing_days):
                self.data.reset_baa10ym()

            self.data.update_baa10ym(curr_date, slice[self.baa10ym].Value)
    
        if self.t10y3m in slice and slice[self.t10y3m]:
            # daily data
            self.data.update_t10y3m(slice[self.t10y3m].Value)

        if self.mri in slice and slice[self.mri]:
            # daily data
            self.data.update_mri(slice[self.mri].Value)

        if self.nber in slice and slice[self.nber]:
            # monthly data
            curr_date:datetime.date = self.Time.date()

            # make sure data still coming
            if not self.data.nber_data_still_coming(curr_date, self.max_missing_days):
                self.data.reset_nber()

            self.data.update_nber(self.Time.date(), slice[self.nber].Value)

        # rebalance monthly
        if self.Time.month == self.recent_month:
            return
        self.recent_month = self.Time.month

        # if there aren't enough daily data, monthly data for regresion will be reset
        self.data.update_monthly_values(self.min_values)
        self.data.reset_daily_values()

        if not self.data.regression_data_ready():
            self.Liquidate()
        else:
            train_y: List[float] = self.data.get_train_regression_y()
            train_x: List[List[float]] = self.data.get_train_regression_x()

            regression_model = MultipleLinearRegression(train_x, train_y)
            
            test_x: List[List[float]] = self.data.get_test_regression_x()
            predicted_value: float = regression_model.predict(test_x)[0]

            if predicted_value > self.threshold:
                self.Liquidate(self.spy)
                if self.bil in slice and slice[self.bil]:
                    self.SetHoldings(self.bil, 1)

            else:
                self.Liquidate(self.bil)
                if self.spy in slice and slice[self.spy]:
                    self.SetHoldings(self.spy, 1)

Leave a Reply

Discover more from Quant Buffet

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

Continue reading