| Overall Statistics |
|
Total Trades 27 Average Win 1.23% Average Loss -1.70% Compounding Annual Return -46.339% Drawdown 5.900% Expectancy -0.139 Net Profit -1.803% Sharpe Ratio -2.047 Probabilistic Sharpe Ratio 26.797% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 0.72 Alpha -0.274 Beta 0.423 Annual Standard Deviation 0.208 Annual Variance 0.043 Information Ratio -0.262 Tracking Error 0.261 Treynor Ratio -1.006 Total Fees $27.00 |
from sklearn import linear_model
import numpy as np
import pandas as pd
from scipy import stats
from math import floor
from datetime import timedelta
from datetime import time
class PairsTradingAlgo(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020,6,1)
self.SetEndDate(self.StartDate + timedelta(10))
self.SetCash(10000)
# self.Settings.FreePortfolioValuePercentage = 0.05
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
tickers = ['XOM', 'CVX', 'SPY']
for t in tickers:
self.AddEquity(t, Resolution.Hour).Symbol
self.SetBenchmark('SPY')
self.SetAlpha(PairsAlpha())
self.SetExecution(ImmediateExecutionModel())
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
self.SetRiskManagement(TrailingStop())
class PairsAlpha(AlphaModel):
def __init__(self):
self.window_length = 13
self.threshold = 1.5
self.symbol_data = {}
def Update(self, algo, data):
insights = []
for security in data.Keys:
symbol = security.Value
if symbol == 'SPY': continue
if symbol not in self.symbol_data.keys():
self.symbol_data[symbol] = RollingWindow[TradeBar](self.window_length)
if data.Bars.ContainsKey(symbol):
self.symbol_data[symbol].Add(data[security])
symbols = [x for x in self.symbol_data.keys()]
if not (self.symbol_data[symbols[0]].IsReady and self.symbol_data[symbols[1]].IsReady):
return insights
price_x = pd.Series([float(i.Close) for i in self.symbol_data[symbols[0]]],
index = [i.Time for i in self.symbol_data[symbols[0]]])
price_y = pd.Series([float(i.Close) for i in self.symbol_data[symbols[1]]],
index = [i.Time for i in self.symbol_data[symbols[1]]])
spread_regr = self.regr(np.log(price_x), np.log(price_y))
self.spread_mean = np.mean(spread_regr)
self.spread_std = np.std(spread_regr)
algo.Log('%s: %s' % (str(symbols[0]), str(algo.Portfolio[symbols[0]].Invested)))
algo.Log('%s: %s' % (str(symbols[1]), str(algo.Portfolio[symbols[1]].Invested)))
if not (algo.Portfolio[symbols[0]].Invested or algo.Portfolio[symbols[1]].Invested):
if spread_regr[-1] > self.spread_mean + self.threshold * self.spread_std:
insights.append(Insight.Price(symbols[0], timedelta(days=30), InsightDirection.Up))
insights.append(Insight.Price(symbols[1], timedelta(days=30), InsightDirection.Down))
algo.Log('insights emitted')
elif spread_regr[-1] < self.spread_mean - self.threshold * self.spread_std:
insights.append(Insight.Price(symbols[1], timedelta(days=30), InsightDirection.Up))
insights.append(Insight.Price(symbols[0], timedelta(days=30), InsightDirection.Down))
algo.Log('insights emitted')
return insights
def regr(self, x, y):
regr = linear_model.LinearRegression()
x_constant = np.column_stack([np.ones(len(x)), x])
regr.fit(x_constant, y)
beta = regr.coef_[0]
alpha = regr.intercept_
spread = y - x*beta - alpha
return spread
class TrailingStop(RiskManagementModel):
def __init__(self, drawdown_threshold = 0.01):
self.drawdown_threshold = -abs(drawdown_threshold)
self.max_profits = {}
def ManageRisk(self, algo, targets):
targets = []
for kvp in algo.Securities:
security = kvp.Value
symbol = security.Symbol.Value
current_profit = security.Holdings.UnrealizedProfitPercent
if not security.Invested:
continue
if symbol not in self.max_profits.keys():
self.max_profits[symbol] = current_profit
self.max_profits[symbol] = np.maximum(self.max_profits[symbol], current_profit)
drawdown = current_profit - self.max_profits[symbol]
# algo.Log('%s - Current profit: %.3f, Max profit: %.3f, Current drawdown: %.3f' % (str(symbol), current_profit, self.max_profits[symbol], drawdown))
if drawdown < self.drawdown_threshold:
targets.append(PortfolioTarget(symbol, 0))
algo.Log('%s position to be liquidated' % str(symbol))
# Reset dictionary
del self.max_profits[symbol]
return targets