Overall Statistics |
Total Trades 12 Average Win 0.66% Average Loss -0.19% Compounding Annual Return 210.012% Drawdown 1.100% Expectancy 1.722 Net Profit 1.877% Sharpe Ratio 14.989 Probabilistic Sharpe Ratio 98.577% Loss Rate 40% Win Rate 60% Profit-Loss Ratio 3.54 Alpha 0.945 Beta -0.82 Annual Standard Deviation 0.104 Annual Variance 0.011 Information Ratio 10.151 Tracking Error 0.229 Treynor Ratio -1.908 Total Fees $98.15 |
from datetime import timedelta, datetime import statsmodels.api as sm import numpy as np import pandas as pd from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel from sklearn.decomposition import PCA class SMAPairsTrading(QCAlgorithm): def Initialize(self): self.SetStartDate(2015, 1 , 1 ) self.SetEndDate(2015, 1 , 6 ) self.SetCash(100000) self.UniverseSettings.Resolution = Resolution.Hour self.AddUniverse(self.Universe.Index.QC500) self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw self.AddAlpha(PairsTradingAlphaModel()) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) self.SetExecution(ImmediateExecutionModel()) self.SetRiskManagement(MaximumDrawdownPercentPerSecurity(0.03)) self.SetBenchmark("SPY") self.SetSecurityInitializer(self.CustomSecurityInitializer) self.buy = pd.DataFrame() self.sell = pd.DataFrame() self.liquidate = pd.DataFrame() def OnEndOfDay(self, symbol): self.Log("Taking a position of " + str(self.Portfolio[symbol].Quantity) + " units of symbol " + str(symbol)) def CustomSecurityInitializer(self, security): security.SetLeverage(1) class PairsTradingAlphaModel(AlphaModel): def __init__(self): self.pair = [] self.period = timedelta(days=1) self.curr_day = -1 def Update(self, algorithm, data): if self.curr_day == algorithm.Time.day: return [] self.curr_day = algorithm.Time.day List=[x.Symbol for x in self.pair] history = algorithm.History(List, 61, Resolution.Daily ).close.unstack(level=0) self.buy,self.sell,self.liquidate = self.GetIndexes( history) Appd = [] for i in self.buy: Appd.append(Insight.Price(i,self.period, InsightDirection.Up,None,None,None))#,None, None, None,0.02)) for i in self.sell: Appd.append(Insight.Price(i,self.period, InsightDirection.Down,None,None,None)) for i in self.liquidate: Appd.append(Insight.Price(i,self.period, InsightDirection.Flat,None,None,None)) return Insight.Group([ x for x in Appd]) def GetIndexes(self, history): # Sample data for PCA sample = history.dropna(axis=1).pct_change().dropna() sample_mean = sample.mean() sample_std = sample.std() sample = ((sample-sample_mean)/(sample_std)) #Normalizing # Fit the PCA model for sample data model = PCA().fit(sample) #Distributing eigenportfolios EigenPortfolio = pd.DataFrame(model.components_) EigenPortfolio.columns = sample.columns # EigenPortfolio = EigenPortfolio/sample_std EigenPortfolio = ( EigenPortfolio.T / EigenPortfolio.sum(axis=1) ) # Get the first n_components factors factors = np.dot(sample, EigenPortfolio)[:,:1] # we want to replicate the market # Add 1's to fit the linear regression (intercept) 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 stock 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 ).iloc[0,:]# zscores of the most recent day # Get the stocks far from mean (for mean reversion) selected_buy = zscores[zscores < -1.5].dropna().sort_values()[:1] selected_sell = zscores[zscores > 1.5].dropna().sort_values()[-1:] selected_liquidate = zscores[abs(zscores) < 0.50 ] # Return each selected stock weights_buy = selected_buy.index weights_sell = selected_sell.index weights_liquidate = selected_liquidate.index return weights_buy, weights_sell, weights_liquidate def OnSecuritiesChanged(self, algorithm, changes): self.pair = [x for x in changes.AddedSecurities]