| Overall Statistics |
|
Total Trades
121
Average Win
1.61%
Average Loss
-0.26%
Compounding Annual Return
1.905%
Drawdown
5.100%
Expectancy
3.313
Net Profit
56.620%
Sharpe Ratio
-0.494
Probabilistic Sharpe Ratio
30.479%
Loss Rate
40%
Win Rate
60%
Profit-Loss Ratio
6.19
Alpha
-0.006
Beta
-0.015
Annual Standard Deviation
0.014
Annual Variance
0
Information Ratio
-0.275
Tracking Error
0.164
Treynor Ratio
0.459
Total Fees
$985.02
Estimated Strategy Capacity
$6900000.00
Lowest Capacity Asset
SHY SGNKIKYGE9NP
Portfolio Turnover
1.38%
|
from AlgorithmImports import *
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
# Quandl "value" data
class QuandlValue(PythonQuandl):
def __init__(self):
self.ValueColumnName = 'Value'
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
_last_update_date:Dict[str, datetime.date] = {}
@staticmethod
def get_last_update_date() -> Dict[str, datetime.date]:
return QuantpediaFutures._last_update_date
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
data['back_adjusted'] = float(split[1])
data['spliced'] = float(split[2])
data.Value = float(split[1])
# store last update date
if config.Symbol.Value not in QuantpediaFutures._last_update_date:
QuantpediaFutures._last_update_date[config.Symbol.Value] = datetime(1,1,1).date()
if data.Time.date() > QuantpediaFutures._last_update_date[config.Symbol.Value]:
QuantpediaFutures._last_update_date[config.Symbol.Value] = data.Time.date()
return data
# https://quantpedia.com/strategies/crude-oil-predicts-equity-returns/
#
# Several types of oil can be used (Brent, WTI, Dubai etc.) without big differences in results. The source paper for
# this anomaly uses Arab Light crude oil. Monthly oil returns are used in the regression equation as an independent
# variable and equity returns are used as a dependent variable. The model is re-estimated every month and
# observations of the last month are added. The investor determines whether the expected stock market return in
# a specific month (based on regression results and conditional on the oil price change in the previous month) is higher
# or lower than the risk-free rate. The investor is fully invested in the market portfolio if the expected
# return is higher (bull market); he invests in cash if the expected return is lower (bear market).
from data_tools import QuantpediaFutures, QuandlValue, CustomFeeModel
from AlgorithmImports import *
from typing import List, Dict
import numpy as np
from scipy import stats
from collections import deque
class CrudeOilPredictsEquityReturns(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.rf_max_missing_days:int = 31
self.min_period:int = 13
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(QuandlValue, 'OECD/KEI_IR3TIB01_USA_ST_M', Resolution.Daily).Symbol
for symbol in self.symbols:
data = self.AddData(QuantpediaFutures, symbol, Resolution.Daily)
data.SetLeverage(2)
data.SetFeeModel(CustomFeeModel())
self.data[symbol] = deque()
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
rf_rate:float = .0
if self.Securities[self.risk_free_rate].GetLastData() and (self.Time.date() - self.Securities[self.risk_free_rate].GetLastData().Time.date()).days < self.rf_max_missing_days:
rf_rate = self.Securities[self.risk_free_rate].Price / 100
else:
return
last_update_date:Dict[str, datetime.date] = QuantpediaFutures.get_last_update_date()
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])
self.SetHoldings(self.cash, 1)