Quant BuffetRelax, Not Over Thinking

Timing S&P500 Using Full vs. Partial Employment

Log in to collect

Academic paper

Strategy in a nutshell

The strategy trades SPY and IEF ETFs based on employment data. Compare the annual growth rates of full-time and part-time employment. If full-time employment growth exceeds part-time, invest in SPY (equities); otherwise, invest in IEF (bonds). The portfolio is rebalanced monthly, though the rebalancing frequency and data choice (monthly or weekly) can be adjusted based on the investor's preference. This approach uses labor market trends to allocate between equities and bonds, aiming to capture economic momentum and adjust exposure based on workforce composition changes.

Economic rationale

This strategy forecasts recessionary activity rather than recessions themselves, focusing on identifying reliable patterns using diverse employment data. Analyzing large datasets, normalized to the same scale and treated as separate events, produces significant, usable results, unlike relying on single indicators like the unemployment rate. Macroeconomic data, unlike market-derived indicators, cannot be traded or arbitraged away, offering stable insights. Equity prices, by contrast, are more volatile and subject to non-economic influences. Simpler models, such as differences between two variables, often outperform complex, multi-variable conditions. Since 1956, every recession named by the National Bureau of Economic Research has been identified using this approach.

Backtest performance

Annualised return11.38%
Volatility11.05%
Beta0.326
Sharpe ratio1.03
Maximum drawdown-16.22%
Win rate53%

Full Python code

import pandas as pdclass Full_Partial_Employment(QCAlgorithm):    def Initialize(self):        self.SetStartDate(2005, 1, 1)        self.SetEndDate(2019, 7, 1)        self.SetCash(100000)                self.employment_data = pd.read_csv('https://docs.google.com/spreadsheets/d/1P0E8_ZUAm1NhqaMB1PK1Bc5uB2wL3f6W/export?format=csv', dtype={'date':str}, index_col='date') #   header=None)                symbols = ['SPY', 'IEF']        for symbol in symbols:            self.AddEquity(symbol, Resolution.Daily)        self.Schedule.On(self.DateRules.MonthStart(symbols[0]), self.TimeRules.AfterMarketOpen(symbols[0]), self.Rebalance)    def Rebalance(self):        date = str(self.Time.month) + '/' + str(self.Time.year)        current_row_index = self.employment_data.index.get_loc(date)                #one month lag due to employment data reporting        this_month = self.employment_data.iloc[current_row_index-1]        last_month = self.employment_data.iloc[current_row_index-2]        full_time_diff = this_month['full_time'] - last_month['full_time']        part_time_diff = this_month['part_time'] - last_month['part_time']        if full_time_diff > part_time_diff:            if not self.Portfolio['SPY'].Invested:                self.Liquidate('IEF')                self.SetHoldings('SPY', 1)        else:            if not self.Portfolio['IEF'].Invested:                self.Liquidate('SPY')                self.SetHoldings('IEF', 1)