| Overall Statistics |
|
Total Trades 37552 Average Win 0.21% Average Loss -0.13% Compounding Annual Return 50285.882% Drawdown 12.200% Expectancy 0.517 Net Profit 31685676.536% Sharpe Ratio 265.58 Probabilistic Sharpe Ratio 100% Loss Rate 41% Win Rate 59% Profit-Loss Ratio 1.58 Alpha 75.228 Beta -0.109 Annual Standard Deviation 0.283 Annual Variance 0.08 Information Ratio 226.173 Tracking Error 0.332 Treynor Ratio -687.837 Total Fees $0.00 Estimated Strategy Capacity $340000.00 Lowest Capacity Asset ETHUSD XJ |
#region imports
from AlgorithmImports import *
#endregion
from sklearn.linear_model import LinearRegression
import numpy as np
class PairsTradingAlgorithm(QCAlgorithm):
closes_by_symbol = {}
def Initialize(self):
self.SetStartDate(2020,11,14)
self.SetEndDate(2025,1,1)
self.SetCash(1000000)
self.threshold = 1.
self.numdays = 369 #369 set the length of training period
#pairs: MSFT: GOOG / IWN: SPY / XLK:QQQ
#def: XLK #try: AAPL
# self.x_symbol = self.AddEquity("XLK", Resolution.Hour).Symbol #Minute
# #def: QQQ
# self.y_symbol = self.AddEquity("QQQ", Resolution.Daily).Symbol #Hour or Minute
self.x_symbol = self.AddCrypto("BTCUSD", Resolution.Minute).Symbol #Minute
self.y_symbol = self.AddCrypto("ETHUSD", Resolution.Hour).Symbol #Hour or Minute
# consolidator = TradeBarConsolidator(TimeSpan.FromMinutes(30)) ## Selected resolution is hourly, so this will create a 6-hour consolidator
# consolidator.DataConsolidated += self.OnDataConsolidated ## Tell consolidator which function to run at consolidation intervals
# self.SubscriptionManager.AddConsolidator("XLK", consolidator) ## Add consolidator to algorithm
# self.x_symbol = self.SubscriptionManager.AddConsolidator("XLK", consolidator) #hour or minute
# self.SetWarmup(250, Resolution.Minute)
for symbol in [self.x_symbol, self.y_symbol]:
history = self.History(symbol, self.numdays, Resolution.Minute)
self.closes_by_symbol[symbol] = history.loc[symbol].close.values \
if not history.empty else np.array([])
def OnData(self, data):
for symbol in self.closes_by_symbol.keys():
if not data.Bars.ContainsKey(symbol):
return
for symbol, closes in self.closes_by_symbol.items():
self.closes_by_symbol[symbol] = np.append(closes, data[symbol].Close)[-self.numdays:]
log_close_x = np.log(self.closes_by_symbol[self.x_symbol])
log_close_y = np.log(self.closes_by_symbol[self.y_symbol])
spread, beta = self.regr(log_close_x, log_close_y)
mean = np.mean(spread)
std = np.std(spread)
x_holdings = self.Portfolio[self.x_symbol]
if x_holdings.Invested:
# if x_holdings.IsShort and spread[-1] <= (3*mean) or \
# x_holdings.IsLong and spread[-1] >= (3*mean):
if x_holdings.IsShort and spread[-1] >= mean or \
x_holdings.IsLong and spread[-1] <= mean:
self.Liquidate()
else:
if beta < 1:
x_weight = 0.5
y_weight = 0.5
else:
x_weight = 0.5
y_weight = 0.5
# else:
# if beta < 1:
# x_weight = 0.5
# y_weight = 0.5 / beta
# else:
# x_weight = 0.5 / beta
# y_weight = 0.5
# if not self.Portfolio.Invested and spread[-1] < mean - self.threshold * std:
# self.SetHoldings(self.y_symbol, (y_weight))
# self.SetHoldings(self.x_symbol, (-x_weight))
# if not self.Portfolio.Invested and spread[-1] > mean + self.threshold * std:
# self.SetHoldings(self.x_symbol, (x_weight))
# self.SetHoldings(self.y_symbol, (-y_weight))
if spread[-1] < mean - self.threshold * std:
self.SetHoldings(self.y_symbol, (y_weight*.5))
self.SetHoldings(self.x_symbol, (-x_weight*.5))
if spread[-1] > mean + self.threshold * std:
self.SetHoldings(self.x_symbol, (x_weight*.5))
self.SetHoldings(self.y_symbol, (-y_weight*.5))
# if spread[-1] < mean - self.threshold * std:
# self.SetHoldings(self.y_symbol, -x_weight)
# self.SetHoldings(self.x_symbol, y_weight)
# if spread[-1] > mean + self.threshold * std:
# self.SetHoldings(self.x_symbol, y_weight)
# self.SetHoldings(self.y_symbol, -x_weight)
# if spread[-1] < mean - self.threshold * std:
# self.SetHoldings(self.y_symbol, -y_weight)
# self.SetHoldings(self.x_symbol, x_weight)
# if spread[-1] > mean + self.threshold * std:
# self.SetHoldings(self.x_symbol, -x_weight)
# self.SetHoldings(self.y_symbol, y_weight)
scale = 10000
self.Plot("Spread", "Top", (mean + self.threshold * std) * scale)
self.Plot("Spread", "Value", spread[-1] * scale)
self.Plot("Spread", "Mean", mean * scale)
self.Plot("Spread", "Bottom", (mean - self.threshold * std) * scale)
self.Plot("State", "Value", np.sign(x_holdings.Quantity))
def regr(self, x, y):
regr = LinearRegression()
x_constant = np.column_stack([np.ones(len(x)), x])
regr.fit(x_constant, y)
beta = regr.coef_[1]
alpha = regr.intercept_
spread = y - x*beta - alpha
return spread, beta