| 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]