Oil Beta Uncertainty and Global Stock Returns
Log in to collectAcademic paper
Oil Beta Uncertainty and Global Stock Returns
Chun‐Da Chen; Rıza Demirer
- Lamar University
- Southern Illinois University Edwardsville
- EGEconomic Research Forum
- ?Economic Research Forum (ERF)
- ?Southern Illinois University Edwardsville - Department of Economics & Finance
Strategy in a nutshell
The strategy examines 79 MSCI-classified stock markets worldwide, focusing on their sensitivity to oil price fluctuations. Each month, stock markets are ranked by the degree of oil beta uncertainty, calculated from rolling regressions of excess returns on global market and oil returns. Investors go long the quintile of markets with the highest oil beta uncertainty and short the quintile with the lowest, forming an equally weighted portfolio that is rebalanced monthly.
Economic rationale
Oil price changes significantly affect global stock returns, but the direction and magnitude of these effects vary, creating uncertainty. This “oil beta uncertainty” represents an undiversifiable risk factor. According to information uncertainty theory, ambiguity-averse investors demand higher risk premiums for holding assets with greater response uncertainty to oil price movements. Thus, markets with higher oil beta uncertainty are expected to deliver superior returns as compensation for bearing this ambiguity-related risk.
Backtest performance
Full Python code
from AlgorithmImports import *
import statsmodels.api as sm
import data_tools
# endregion
class CasualApricotSalamander(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
tickers:list[str] = [
'EWA', # iShares MSCI Australia Index ETF
'EWO', # iShares MSCI Austria Investable Mkt Index ETF
'EWK', # iShares MSCI Belgium Investable Market Index ETF
'EWZ', # iShares MSCI Brazil Index ETF
'EWC', # iShares MSCI Canada Index ETF
'FXI', # iShares China Large-Cap ETF
'EWQ', # iShares MSCI France Index ETF
'EWG', # iShares MSCI Germany ETF
'EWH', # iShares MSCI Hong Kong Index ETF
'EWI', # iShares MSCI Italy Index ETF
'EWJ', # iShares MSCI Japan Index ETF
'EWM', # iShares MSCI Malaysia Index ETF
'EWW', # iShares MSCI Mexico Inv. Mt. Idx
'EWN', # iShares MSCI Netherlands Index ETF
'EWS', # iShares MSCI Singapore Index ETF
'EZA', # iShares MSCI South Africe Index ETF
'EWY', # iShares MSCI South Korea ETF
'EWP', # iShares MSCI Spain Index ETF
'EWD', # iShares MSCI Sweden Index ETF
'EWL', # iShares MSCI Switzerland Index ETF
'EWT', # iShares MSCI Taiwan Index ETF
'THD', # iShares MSCI Thailand Index ETF
'EWU', # iShares MSCI United Kingdom Index ETF
'SPY', # SPDR S&P 500 ETF
]
self.min_prices:int = 15
self.regression_period:int = 24
self.beta_index:int = 2 # relevant regression beta position
self.t:float = 2.074 # https://www.sjsu.edu/faculty/gerstman/StatPrimer/t-table.pdf
self.quantile:int = 5
self.data:dict[Symbol, data_tools.SymbolData] = {}
for ticker in tickers:
security = self.AddEquity(ticker, Resolution.Daily)
security.SetLeverage(5)
self.data[security.Symbol] = data_tools.SymbolData(self.regression_period)
self.msci_world:Symbol = self.AddEquity('URTH', Resolution.Daily).Symbol
self.data[self.msci_world] = data_tools.SymbolData(self.regression_period)
self.oil:Symbol = self.AddData(data_tools.QuantpediaFutures, 'CME_CL1', Resolution.Daily).Symbol
self.data[self.oil] = data_tools.SymbolData(self.regression_period)
self.recent_month:int = -1
def OnData(self, data: Slice):
# update prices
for symbol, symbol_obj in self.data.items():
if symbol in data and data[symbol] and data[symbol].Value != 0:
symbol_obj.update_prices(data[symbol].Value)
# monthly rebalance
if self.recent_month != self.Time.month:
self.recent_month = self.Time.month
curr_date:datetime.date = self.Time.date()
# update all regression data
for symbol, symbol_obj in self.data.items():
if symbol_obj.prices_ready(self.min_prices):
symbol_obj.update_regression_data(curr_date)
else:
# reset regression data in case of missing month -> makes sure data are consecutive
symbol_obj.reset_regression_data()
# clear space for next month prices
symbol_obj.reset_prices()
# get regression_x if possible
regression_x:list[list[float]] = [
self.data[self.msci_world].get_regression_data(),
self.data[self.oil].get_regression_data()
] if self.data[self.msci_world].regression_data_ready() and \
self.data[self.oil].regression_data_ready() else None
if regression_x == None:
# liquidate when regression x isn't ready
self.Liquidate()
return
oil_beta_uncertainty:dict[Symbol, float] = {}
for symbol, symbol_obj in self.data.items():
if symbol_obj.regression_data_ready() and symbol not in [self.msci_world, self.oil]:
# calculate the measure of the oil beta uncertainty for stock market i in month t as a difference between the highest and lowest 95% confidence interval
regression_y:list[float] = symbol_obj.get_regression_data()
regression_model = self.MultipleLinearRegression(regression_x, regression_y)
oil_beta:float = regression_model.params[self.beta_index]
SE:float = regression_model.bse[self.beta_index]
top_conf_interval:float = oil_beta + (self.t * SE)
bottom_conf_interval:float = oil_beta - (self.t * SE)
intervals_diff:float = top_conf_interval - bottom_conf_interval
oil_beta_uncertainty[symbol] = intervals_diff
# perform selection and trade
if len(oil_beta_uncertainty) >= self.quantile:
quantile:int = int(len(oil_beta_uncertainty) / self.quantile)
sorted_by_uncertainty:list[Symbol] = [x[0] for x in sorted(oil_beta_uncertainty.items(), key=lambda item: item[1])]
# buy quantile with highest values
long_leg:list[Symbol] = sorted_by_uncertainty[-quantile:]
# short quantile with lowest values
short_leg:list[Symbol] = sorted_by_uncertainty[:quantile]
# trade execution
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long_leg + short_leg:
self.Liquidate(symbol)
long_length:int = len(long_leg)
for symbol in long_leg:
if symbol in data and data[symbol]:
self.SetHoldings(symbol, 1 / long_length)
short_length:int = len(short_leg)
for symbol in short_leg:
if symbol in data and data[symbol]:
self.SetHoldings(symbol, -1 / short_length)
else:
self.Liquidate()
def MultipleLinearRegression(self, x:list, y:list):
x:np.ndarray = np.array(x).T
x = sm.add_constant(x)
result = sm.OLS(endog=y, exog=x).fit()
return result