可以使用多种原油类型(如布伦特、WTI、迪拜等),结果差异不大。这项研究源于对阿拉伯轻质原油的分析。回归方程使用原油的月度收益作为自变量,股票收益作为因变量,模型每月重新估算并加入上个月的数据。投资者根据回归结果和前一个月的油价变化判断预期股票市场收益是否高于无风险利率。若预期收益较高(牛市),则全额投资于市场组合;若较低(熊市),则将资金存入现金。

策略概述

可以使用几种不同类型的原油(如布伦特、WTI、迪拜等),结果差异不大。该异常现象的研究源自阿拉伯轻质原油。回归方程使用原油的月度收益作为自变量,股票收益作为因变量。模型每月重新估算,并加入上个月的观测数据。投资者根据回归结果和前一个月的油价变化,确定某个月的预期股票市场收益是否高于或低于无风险利率。如果预期收益较高(牛市),投资者将全部投资于市场组合;如果预期收益较低(熊市),投资者将资金存入现金。

策略合理性

股票的可预测性可通过反应不足假说来解释。油价变动的信息在股票市场价格中完全反映出来需要一定时间。反应不足可能是由于投资者难以评估油价变动对股票价值的影响,或者投资者在不同时间点对信息做出反应。

论文来源

Striking Oil: Another Puzzle? [点击浏览原文]

<摘要>

油价变化可以预测全球股票市场的收益。在我们对发达市场的三十年月度收益样本中,我们发现18个国家中有12个国家以及全球市场指数的预测能力具有统计显著性。对新兴市场的较短时间序列结果相似。我们的结果不能用时变风险溢酬来解释。尽管油价冲击增加了风险,投资者似乎对油价信息反应不足:油价上涨并不会导致股票市场收益增加,反而大幅降低收益。例如,一个标准差(约10%)的油价冲击可预测性地将全球市场收益降低1%。油价变化还显著预测负的超额收益。我们的发现与投资者对油价变化反应滞后的假说一致。引入几个交易日的滞后后,股票月度收益与滞后的月度油价变化之间的关系显著增强。

回测表现

年化收益率11.9%
波动率9.8%
Beta-0.015
夏普比率-0.505
索提诺比率-0.597
最大回撤3%
胜率59%

完整python代码

from data_tools import QuantpediaFutures, QuandlValue, CustomFeeModel, InterestRate3M
from AlgoLib import *
from typing import List, Dict
import numpy as np
from scipy import stats
from collections import deque

class CrudeOilPredictsEquityReturns(XXX):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)

        self.min_period:int = 13
        self.leverage:int = 2

        self.data:Dict[Symbol, deque] = {}

        self.symbols:List[str] = [
            "CME_ES1",  # E-mini S&P 500 Futures, Continuous Contract #1
            "CME_CL1"   # Crude Oil Futures, Continuous Contract #1
        ]
        
        self.cash:Symbol = self.AddEquity('SHY', Resolution.Daily).Symbol
        self.risk_free_rate:Symbol = self.AddData(InterestRate3M, 'IR3TIB01USM156N', Resolution.Daily).Symbol
        
        for symbol in self.symbols:
            data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
            data.SetLeverage(self.leverage)
            data.SetFeeModel(CustomFeeModel())
            self.data[symbol] = deque()
        
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        self.recent_month:int = -1

    def OnData(self, data:Slice) -> None:
        rebalance_flag:bool = False
        
        for symbol in self.symbols:
            if symbol in data:
                if self.recent_month != self.Time.month:
                    rebalance_flag = True
                    
                if data[symbol]:
                    price:float = data[symbol].Value
                    self.data[symbol].append(price)

        if rebalance_flag:
            self.recent_month = self.Time.month
        
        ir_last_update_date:Dict[str, datetime.date] = InterestRate3M.get_last_update_date()
        last_update_date:Dict[str, datetime.date] = QuantpediaFutures.get_last_update_date()

        rf_rate:float = .0
        # check if data is still coming
        if self.Securities[self.risk_free_rate].GetLastData() and ir_last_update_date[self.risk_free_rate.Value] > self.Time.date():
            rf_rate = self.Securities[self.risk_free_rate].Price / 100
        else:
            return

        if not all(last_update_date[x] > self.Time.date() for x in self.symbols):
            self.Liquidate()
            return

        market_prices:np.ndarray = np.array(self.data[self.symbols[0]])
        oil_prices:np.ndarray = np.array(self.data[self.symbols[1]])
        
        # At least one year of data is ready.
        if len(market_prices) < self.min_period or len(oil_prices) < self.min_period:
            return
        
        # Trim price series lenghts.
        min_size:float = min(len(market_prices), len(oil_prices))
        market_prices = market_prices[-min_size:]
        oil_prices = oil_prices[-min_size:]
        
        market_returns = market_prices[1:] / market_prices[:-1] - 1
        oil_returns = oil_prices[1:] / oil_prices[:-1] - 1
        
        # Simple Linear Regression
        # Y = C + (M * X)
        # Y = α + (β ∗ X)

        # Y = Dependent variable (output/outcome/prediction/estimation)
        # C/α = Constant (Y-Intercept)
        # M/β = Slope of the regression line (the effect that X has on Y)
        # X = Independent variable (input variable used in the prediction of Y)
        slope, intercept, r_value, p_value, std_err = stats.linregress(oil_returns[:-1], market_returns[1:])
        expected_market_return = intercept + (slope * oil_returns[-1])
        
        if expected_market_return > rf_rate:
            if self.Portfolio[self.cash].Invested:
                self.Liquidate(self.cash)
            
            self.SetHoldings(self.symbols[0], 1)
        else:
            if self.Portfolio[self.symbols[0]].Invested:
                self.Liquidate(self.symbols[0])
            if self.cash in data and data[self.cash]:
                self.SetHoldings(self.cash, 1)

Leave a Reply

Discover more from Quant Buffet

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

Continue reading