Overall Statistics |
Total Trades
757
Average Win
0.69%
Average Loss
-0.71%
Compounding Annual Return
-2.694%
Drawdown
39.800%
Expectancy
-0.169
Net Profit
-18.721%
Sharpe Ratio
-0.135
Loss Rate
58%
Win Rate
42%
Profit-Loss Ratio
0.98
Alpha
-0.201
Beta
9.228
Annual Standard Deviation
0.134
Annual Variance
0.018
Information Ratio
-0.283
Tracking Error
0.134
Treynor Ratio
-0.002
Total Fees
$2413.12
|
# https://quantpedia.com/Screener/Details/55 import numpy as np import pandas as pd from scipy import stats from math import floor from datetime import timedelta from collections import deque import itertools as it from decimal import Decimal class PairsTradingwithCountryETFsAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2011, 1, 1) self.SetEndDate(2018, 8, 1) self.SetCash(100000) # choose ten sector ETFs tickers = ["GAF", # SPDR S&P Emerging Middle East & Africa ETF 2007.4 "ENZL", # iShares MSCI New Zealand Investable Market Index Fund 2010.9 "NORW", # Global X FTSE Norway 30 ETF 2011 "EWY", # iShares MSCI South Korea Index ETF 2000.6 "EWP", # iShares MSCI Spain Index ETF 1996 "EWD", # iShares MSCI Sweden Index ETF 1996 "EWL", # iShares MSCI Switzerland Index ETF 1996 "GXC", # SPDR S&P China ETF 2007.4 "EWC", # iShares MSCI Canada Index ETF 1996 "EWZ", # iShares MSCI Brazil Index ETF 2000.8 # "AND", # Global X FTSE Andean 40 ETF 2011.3 "AIA", # iShares S&P Asia 50 Index ETF 1996 "EWO", # iShares MSCI Austria Investable Mkt Index ETF 1996 "EWK", # iShares MSCI Belgium Investable Market Index ETF 1996 "ECH", # iShares MSCI Chile Investable Market Index ETF 2018 2008 # "EGPT", # Market Vectors Egypt Index ETF 2011 "EWJ", # iShares MSCI Japan Index ETF 1999 "EZU", # iShares MSCI Eurozone ETF 2000 "EWW", # iShares MSCI Mexico Inv. Mt. Idx 2000 # "ERUS", # iShares MSCI Russia ETF 2011 "IVV", # iShares S&P 500 Index 2001 "AAXJ", # iShares MSCI All Country Asia ex Japan Index ETF 2008.8 "EWQ", # iShares MSCI France Index ETF 2000 "EWH", # iShares MSCI Hong Kong Index ETF 1999 # "EPI", # WisdomTree India Earnings ETF 2008.3 "EIDO", # iShares MSCI Indonesia Investable Market Index ETF 2008.3 "EWI", # iShares MSCI Italy Index ETF 1996 "ADRU"] # BLDRS Europe 100 ADR Index ETF 2003 self.threshold = 0.5 self.symbols = [] for i in tickers: self.symbols.append(self.AddEquity(i, Resolution.Daily).Symbol) self.pairs = {} self.formation_period = 121 self.history_price = {} for symbol in self.symbols: hist = self.History([symbol.Value], self.formation_period+1, Resolution.Daily) if hist.empty: self.symbols.remove(symbol) else: self.history_price[symbol.Value] = deque(maxlen=self.formation_period) for tuple in hist.loc[str(symbol)].itertuples(): self.history_price[symbol.Value].append(float(tuple.close)) if len(self.history_price[symbol.Value]) < self.formation_period: self.symbols.remove(symbol) self.history_price.pop(symbol.Value) self.symbol_pairs = list(it.combinations(self.symbols, 2)) # Add the benchmark self.AddEquity("SPY", Resolution.Daily) self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY"), self.Rebalance) self.sorted_pairs = None def OnData(self, data): # Update the price series everyday for symbol in self.symbols: if data.Bars.ContainsKey(symbol) and symbol.Value in self.history_price: self.history_price[symbol.Value].append(float(data[symbol].Close)) if self.sorted_pairs is None: return for i in self.sorted_pairs: pair = Pair(i[0], i[1], self.history_price[i[0].Value], self.history_price[i[1].Value]) index_a = pair.index_a[-1] index_b = pair.index_b[-1] delta = pair.distance() if index_a - index_b > self.threshold*delta: if not self.Portfolio[pair.symbol_a].Invested and not self.Portfolio[pair.symbol_b].Invested: ratio = self.Portfolio[pair.symbol_a].Price / self.Portfolio[pair.symbol_b].Price quantity = int(self.CalculateOrderQuantity(pair.symbol_a, 0.2)) self.Sell(pair.symbol_a, quantity) self.Buy(pair.symbol_b, floor(ratio*quantity)) elif index_a - index_b < -self.threshold*delta: if not self.Portfolio[pair.symbol_a].Invested and not self.Portfolio[pair.symbol_b].Invested: ratio = self.Portfolio[pair.symbol_b].Price / self.Portfolio[pair.symbol_a].Price quantity = int(self.CalculateOrderQuantity(pair.symbol_b, 0.2)) self.Sell(pair.symbol_b, quantity) self.Buy(pair.symbol_a, floor(ratio*quantity)) # the position is closed when prices revert back elif self.Portfolio[i[0]].Invested and self.Portfolio[i[1]].Invested: self.Liquidate(pair.symbol_a) self.Liquidate(pair.symbol_b) def Rebalance(self): # schedule the event to fire every half year to select pairs with the smallest historical distance distances = {} for i in self.symbol_pairs: if i[0].Value in self.history_price and i[1].Value in self.history_price: distances[i] = Pair(i[0], i[1], self.history_price[i[0].Value], self.history_price[i[1].Value]).distance() self.sorted_pairs = sorted(distances, key = lambda x: distances[x])[:5] class Pair: def __init__(self, symbol_a, symbol_b, price_a, price_b): self.symbol_a = symbol_a self.symbol_b = symbol_b self.price_a = np.array(price_a) self.price_b = np.array(price_b) # compute normalized cumulative price indices self.index_a = np.cumprod(self.price_a[1:]/self.price_a[:-1]) self.index_b = np.cumprod(self.price_b[1:]/self.price_b[:-1]) def distance(self): return 1/120*sum(abs(self.index_a -self.index_b))