| Overall Statistics |
|
Total Trades 7585 Average Win 0.44% Average Loss -0.46% Compounding Annual Return 4.051% Drawdown 43.400% Expectancy 0.042 Net Profit 110.862% Sharpe Ratio 0.33 Probabilistic Sharpe Ratio 0.071% Loss Rate 47% Win Rate 53% Profit-Loss Ratio 0.96 Alpha 0.041 Beta -0.001 Annual Standard Deviation 0.125 Annual Variance 0.016 Information Ratio -0.193 Tracking Error 0.216 Treynor Ratio -38.093 Total Fees $71154.34 |
import numpy as np
import pandas as pd
import statsmodels.api as sm
from Selection.QC500UniverseSelectionModel import QC500UniverseSelectionModel
from datetime import datetime
class Oilsensibiltiy(QCAlgorithm):
def Initialize(self):
self.SetStartDate( 2002 , 1, 1) # Set Start Date
self.SetEndDate( 2020 , 10, 10)
self.SetCash(100000) # Set Strategy Cash
self.lookback = 61 # Length(days) of historical data
self.weights_long,self.weights_short = pd.DataFrame(),pd.DataFrame() # Pandas data frame (index: symbol) that stores the weight
self.Portfolio.MarginModel = PatternDayTradingMarginModel()
self.AGG = self.AddEquity("AGG", Resolution.Daily).Symbol
self.nextLiquidate = self.Time # Initialize last trade time
self.rebalance_days = 30
self.UniverseSettings.Resolution = Resolution.Daily # Use hour resolution for speed
self.oil = self.AddData(QuandlOil, 'FRED/DCOILBRENTEU', Resolution.Daily).Symbol
self.AddUniverse(self.CoarseSelection, self.SelectFine)
self.selectedequity = 500
self.numberOfSymbolsFine = 50
self.Symbols_long = []
self.Symbols_short = []
self.zscore_keep_buy = []
self.zscore_keep_short = []
self.weights_long = []
self.weights_short = []
def CoarseSelection(self, coarse):
if self.Time < self.nextLiquidate:
return Universe.Unchanged
selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5],
key=lambda x: x.DollarVolume, reverse=True)
symbols = [x.Symbol for x in selected[:self.selectedequity ] ]
return symbols
def SelectFine(self, fine):
filtered = [x.Symbol for x in fine if x.AssetClassification.MorningstarSectorCode == 309]
self.Symbols_long = filtered[:self.numberOfSymbolsFine]
self.Symbols_short = filtered[-self.numberOfSymbolsFine:]
return self.Symbols_long + self.Symbols_short
def GetWeights_Buy(self, history , crudeoil_history):
crudeoil_history = np.log(crudeoil_history/crudeoil_history.shift(1)).dropna()
history = history.dropna(axis=1)
sample = np.log(history/history.shift(1)).dropna()
crudeoil_history.index = sample.index
zscore = self.ZscoreGrade(sample,crudeoil_history)
zscore_buy = zscore[zscore>1.25].dropna(axis=1)
zscore_keep = zscore[zscore>0.50].dropna(axis=1)
L = len(zscore_buy.columns)
try :
weights = (zscore_buy * (1 / L)/zscore_buy).iloc[0,:].sort_values()
except:
weights = pd.DataFrame()
return weights,zscore_keep,L
def GetWeights_Sell(self, history , crudeoil_history,L):
crudeoil_history = np.log(crudeoil_history/crudeoil_history.shift(1)).dropna()
history = history.dropna(axis=1)
sample = np.log(history/history.shift(1)).dropna()
crudeoil_history.index = sample.index
zscore = self.ZscoreGrade(sample,crudeoil_history)
zscore_short = zscore[zscore<-1.25].dropna(axis=1)
zscore_keep = zscore[zscore<-0.50].dropna(axis=1)
try :
weights = (zscore_short * (-1 / L)/zscore_short).iloc[0,:][:L]
except:
weights = pd.DataFrame()
return weights,zscore_keep
def ZscoreGrade(self,sample, factors) :
factors = sm.add_constant(factors)
# Train Ordinary Least Squares linear model for each stock
OLSmodels = {ticker: sm.OLS(sample[ticker], factors).fit() for ticker in sample.columns}
# Get the residuals from the linear regression after PCA for each stoc
resids = pd.DataFrame({ticker: model.resid for ticker, model in OLSmodels.items()})
#Get the OU parameters
shifted_residuals = resids.cumsum().iloc[1:,:]
resids = resids.cumsum().iloc[:-1,:]
resids.index = shifted_residuals.index
OLSmodels2 = {ticker: sm.OLS(resids[ticker],sm.add_constant(shifted_residuals[ticker])).fit() for ticker in resids.columns}
# Get the new residuals
resids2 = pd.DataFrame({ticker: model.resid for ticker, model in OLSmodels2.items()})
# Get the mean reversion parameters
a = pd.DataFrame({ticker : model.params[0] for ticker , model in OLSmodels2.items()},index=["a"])
b = pd.DataFrame({ticker: model.params[1] for ticker , model in OLSmodels2.items()},index=["a"])
e = (resids2.std())/(252**(-1/2))
k = -np.log(b) * 252
#Get the z-score
var = (e**2 /(2 * k) )*(1 - np.exp(-2 * k * 252))
num = -a * np.sqrt(1 - b**2)
den = ( 1-b ) * np.sqrt( var )
m = ( a / ( 1 - b ) )
zscores= num / den # zscores of the most recent day
return zscores
def OnData(self, data):
history_long = self.History(self.Symbols_long, self.lookback, Resolution.Daily).close.unstack(level=0)
new_look_back = len(history_long)
crudeoil_history = self.History(QuandlOil,self.oil , 300, Resolution.Daily).droplevel(level=0)
crudeoil_history = crudeoil_history[~crudeoil_history.index.duplicated(keep='last')].iloc[-new_look_back:]
self.weights_long,self.zscore_keep_buy,L = self.GetWeights_Buy(history_long,crudeoil_history)
#history_short = self.History(self.Symbols_long, self.lookback, Resolution.Daily).close.unstack(level=0)
self.weights_short,self.zscore_keep_short = self.GetWeights_Sell(history_long,crudeoil_history,L)
self.Debug(self.weights_short)
for holding in self.Portfolio.Values:
if holding.Symbol in self.zscore_keep_short.index or holding.Symbol in self.zscore_keep_buy.index or holding.Symbol == self.AGG :
continue
if holding.Invested:
self.Liquidate(holding.Symbol)
for symbol, weight in self.weights_short.items():
self.Debug(symbol)
self.SetHoldings(symbol,0.75*weight)
for symbol, weight in self.weights_long.items():
self.SetHoldings(symbol,0.75*weight)
if self.Time < self.nextLiquidate:
return
self.SetHoldings('AGG', 0.70 )
self.nextLiquidate = self.Time + timedelta(self.rebalance_days)
def OnSecuritiesChanged(self, changes):
for security in changes.RemovedSecurities:
if security.Invested:
self.Liquidate(security.Symbol, 'Removed from Universe')
class QuandlOil(PythonQuandl):
def __init__(self):
self.ValueColumnName = 'Value'