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'