Overall Statistics |
Total Trades 512 Average Win 0.11% Average Loss -0.09% Compounding Annual Return -18.033% Drawdown 5.700% Expectancy -0.175 Net Profit -3.532% Sharpe Ratio -1.509 Probabilistic Sharpe Ratio 10.915% Loss Rate 63% Win Rate 37% Profit-Loss Ratio 1.21 Alpha -0.144 Beta 0.006 Annual Standard Deviation 0.097 Annual Variance 0.009 Information Ratio 0.563 Tracking Error 0.254 Treynor Ratio -25.037 Total Fees $512.79 Estimated Strategy Capacity $190000000.00 Lowest Capacity Asset DDOG X7ZCS8BRO6ZP |
from datetime import date, timedelta, datetime from decimal import Decimal import numpy as np import pandas as pd from scipy.stats import linregress import decimal as d class MomentumandStateofMarketFiltersAlgorithm(QCAlgorithm): def Initialize(self): #QC setup self.SetStartDate(2020, 1, 1) self.SetCash(100000) self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) self.SetBenchmark('SPY') self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol self.AddUniverse(self.Coarse, self.Fine) self.UniverseSettings.Resolution = Resolution.Daily #User input area: self.ema_fast_span = 2 #Fast_ema_period self.ema_slow_span = 5 #Slow ema period self.spy_fast_span = 1 #SPY ema period self.spy_slow_span = 3 #SPY ema period self.manual_list = [] self.portfolio_sizing = True #If set to false, algorithm will not run buy signal if invested in any equities long or short #Variables self.symbols = [] self.long_list = [] self.short_list = [] self.holdings = {} self.ema_fast = {} self.ema_slow = {} self.prices = {} self.spy_ema_fast = {} self.spy_ema_slow = {} self.back_period = 10 # 3 months self.vol_period = 2 # days for calc vol self.target_vol = 0.2 self.lev = 1 # max lev from ratio targ_vol / real_vol self.delta = 0.05 # min rebalancing self.x = np.asarray(range(self.vol_period)) self.spy_fast_or_slow = False self.lookback = 1 self.num_coarse = 100 self.long = [] self.short = [] self.mom = {} def Coarse(self, coarse): filtered = [x for x in coarse if x.HasFundamentalData and x.DollarVolume > 1000000 and (float(x.Price) > 5) or x.Symbol.Value in self.manual_list or x.Symbol.Value in self.holdings] sortedStocks = sorted(filtered, key = lambda x: x.DollarVolume, reverse = True) return [x.Symbol for x in sortedStocks][:self.num_coarse] def Fine(self, fine): return [x.Symbol for x in fine if x.CompanyReference.PrimaryExchangeID == "NAS" and x.CompanyReference.CountryId == "USA" or x.Symbol.Value in self.manual_list or x.Symbol.Value in self.holdings] def OnSecuritiesChanged(self, changes): for stock in changes.RemovedSecurities: symbol = stock.Symbol self.Liquidate(symbol) if symbol in self.symbols: self.symbols.remove(symbol) self.ema_fast.pop(symbol, None) self.ema_slow.pop(symbol, None) self.prices.pop(symbol, None) if symbol in self.long: self.long.remove(symbol) if symbol in self.short: self.short.remove(symbol) if symbol in self.mom: self.mom.pop(symbol, None) self.history = self.History([stock.Symbol for stock in changes.AddedSecurities], 100, Resolution.Daily) for stock in changes.AddedSecurities: symbol = stock.Symbol if symbol in self.history.index: if symbol not in self.symbols: close = self.history.loc[symbol]["close"].to_list() self.symbols.append(symbol) self.prices[symbol] = close self.mom[symbol] = Momentum(self.lookback) close_prices = {} close_prices["close"] = close ema = pd.DataFrame(close_prices , columns = ["close"]) ema["EMA_fast"] = ema["close"].ewm(span=self.ema_fast_span,min_periods=0,adjust=False,ignore_na=False).mean() self.ema_fast[symbol] = ema["EMA_fast"].to_list() ema["EMA_slow"] = ema["close"].ewm(span=self.ema_slow_span,min_periods=0,adjust=False,ignore_na=False).mean() self.ema_slow[symbol] = ema["EMA_slow"].to_list() if symbol not in self.history.index: if symbol in self.long: self.long.remove(symbol) if symbol in self.short: self.short.remove(symbol) addedSymbols = [k for k,v in self.mom.items() if not v.IsReady] history = self.History(addedSymbols, 1 + self.lookback, Resolution.Daily) history = history.close.unstack(level=0) for symbol in addedSymbols: ticker = str(symbol) if ticker in history: for time, value in history[ticker].items(): item = IndicatorDataPoint(symbol, time, value) self.mom[symbol].Update(item) def OnData(self, data): self.UpdateData(data) self.CheckEMA() self.CheckMom() self.CheckLiquidate() self.CheckSPY() self.Signal() self.JustBeforeMarketClose() self.Reset() def UpdateData(self, data): self.data = data self.tradebars = data.Bars for symbol in self.symbols: if not self.data.ContainsKey(symbol): continue if not self.data.Bars.ContainsKey(symbol): continue self.prices[symbol].append(self.tradebars[symbol].Close) close = self.prices[symbol] close_prices = {} close_prices["close"] = close ema = pd.DataFrame(close_prices , columns = ["close"]) ema["EMA_fast"] = ema["close"].ewm(span=self.ema_fast_span,min_periods=0,adjust=False,ignore_na=False).mean() ema_fast = ema["EMA_fast"].to_list() self.ema_fast[symbol] = ema_fast ema["EMA_slow"] = ema["close"].ewm(span=self.ema_slow_span,min_periods=0,adjust=False,ignore_na=False).mean() ema_slow = ema["EMA_slow"].to_list() self.ema_slow[symbol] = ema_slow temp_list = self.prices[self.spy] close_prices = {} close_prices["close"] = temp_list ema = pd.DataFrame(close_prices , columns = ["close"]) ema["EMA_fast"] = ema["close"].ewm(span=self.spy_fast_span,min_periods=0,adjust=False,ignore_na=False).mean() self.spy_ema_fast[self.spy] = ema["EMA_fast"].to_list() ema["EMA_slow"] = ema["close"].ewm(span=self.spy_slow_span,min_periods=0,adjust=False,ignore_na=False).mean() self.spy_ema_slow[self.spy] = ema["EMA_slow"].to_list() for symbol, mom in self.mom.items(): if not self.data.Bars.ContainsKey(symbol): continue mom.Update(self.Time, self.tradebars[symbol].Close) def CheckEMA(self): for i in self.symbols: if i in self.ema_fast: if self.ema_fast[i][-1] > self.ema_slow[i][-1]: self.long.append(i) elif self.ema_fast[i][-1] < self.ema_slow[i][-1]: self.short.append(i) def CheckMom(self): sorted_mom = sorted([k for k,v in self.mom.items() if v.IsReady], key=lambda x: self.mom[x].Current.Value, reverse=True) for i in sorted_mom: if i in self.long: self.long_list.append(i) for i in sorted_mom: if i in self.short: self.short_list.append(i) self.long_list = self.long_list[:20] self.short_list = self.short_list[-20:] def CheckLiquidate(self): self.holdings = {} for kvp in self.Portfolio: security_holding = kvp.Value if security_holding.Invested: symbol = security_holding.Symbol quantity = security_holding.Quantity self.holdings[symbol] = quantity if len(self.holdings) > 0: for i in self.holdings: if self.holdings[i] > 0: if i in self.ema_slow: if self.ema_slow[i][-1] > self.ema_fast[i][-1]: self.Liquidate(i) else: 0 if self.holdings[i] < 0: if i in self.ema_slow: if self.ema_fast[i][-1] > self.ema_slow[i][-1]: self.Liquidate(i) else: 0 def CheckSPY(self): if self.spy_ema_fast[self.spy][-1] > self.spy_ema_slow[self.spy][-1]: self.spy_fast_or_slow = True if self.spy_ema_fast[self.spy][-1] < self.spy_ema_slow[self.spy][-1]: self.spy_fast_or_slow = False def Signal(self): if self.long is None or self.short is None: return if len(self.holdings) > 0 and self.portfolio_sizing == True: if self.spy_fast_or_slow == True: if len(self.long_list) > 0: self.rebalance(self.long_list, 1, 0.8) if len(self.short_list) > 0: self.rebalance(self.short_list, -1, 0.2) elif self.spy_fast_or_slow == False: if len(self.short_list) > 0: self.rebalance(self.short_list, -1, 0.8) if len(self.long_list) > 0: self.rebalance(self.long_list, 1, 0.2) elif len(self.holdings) == 0 and self.portfolio_sizing == True: if self.spy_fast_or_slow == True: if len(self.long_list) > 0: self.rebalance(self.long_list, 1, 0.8) if len(self.short_list) > 0: self.rebalance(self.short_list, -1, 0.2) elif self.spy_fast_or_slow == False: if len(self.short_list) > 0: self.rebalance(self.short_list, -1, 0.8) if len(self.long_list) > 0: self.rebalance(self.long_list, 1, 0.2) elif len(self.holdings) > 0 and self.portfolio_sizing == False: 0 elif len(self.holdings) == 0 and self.portfolio_sizing == False: if self.spy_fast_or_slow == True: if len(self.long_list) > 0: self.rebalance(self.long_list, 1, 0.8) if len(self.short_list) > 0: self.rebalance(self.short_list, -1, 0.2) elif self.spy_fast_or_slow == False: if len(self.short_list) > 0: self.rebalance(self.short_list, -1, 0.8) if len(self.long_list) > 0: self.rebalance(self.long_list, 1, 0.2) else: 0 def rebalance(self, lists, sign, portfolio_weight): self.w = 1 / len(lists) try: pos_sizing = self.pos_sizing(lists, sign) except Exception as e: msg = f'Exception: {e}' self.Log(msg) return tot_port = self.Portfolio.TotalPortfolioValue for symbol, info in pos_sizing.items(): new_weight = info[0] yesterdayClose = info[1] security = self.Securities[symbol] quantity = security.Holdings.Quantity price = security.Price if price == 0: price = yesterdayClose curr_weight = quantity * price / tot_port shall_trade = abs(new_weight - curr_weight) > self.delta if shall_trade: delta_shares = (sign * (int(new_weight * (tot_port * portfolio_weight) / price))) - quantity self.MarketOnOpenOrder(symbol, delta_shares) msg = f"{symbol} -- weight: {new_weight:.2f} (old weight was: {curr_weight:.2f}) -- last price: {price}" def pos_sizing(self, lists, sign): allPrices = self.History(lists, self.back_period, Resolution.Daily).close.unstack(level=0) pos = {} for symbol in lists: try: prices = allPrices[symbol] change = prices.pct_change().dropna() last = np.float(prices[-1]) rsq = self.rsquared(self.x, prices[-self.vol_period:]) alpha = min(0.6, np.exp(-10. * (1. - rsq))) vol = change.ewm(alpha=alpha).std() ann_vol = np.float(vol.tail(1)) * np.sqrt(100) weight = (self.target_vol / ann_vol).clip(0.0, self.lev) * self.w pos[symbol] = (weight, last) msg = f"{symbol}: {pos[symbol][0]}, rsqr: {rsq}, alpha: {alpha}, ann_vol = {ann_vol}" except KeyError: pass return pos def Reset(self): self.long.clear() self.short.clear() self.long_list.clear() self.short_list.clear() def rsquared(self, x, y): _, _, r_value, _, _ = linregress(x, y) return r_value**2 def OnMarginCallWarning(self): msg = f"{self.Time} : check warning margin call! Fast" self.Log(msg) def JustBeforeMarketClose(self): msg = f"End of day: {self.Time} \nPortfolio value is {self.Portfolio.TotalPortfolioValue:.2f} and Margin Remaining is: {self.Portfolio.MarginRemaining:.2f} (Total Holdings Value: {self.Portfolio.TotalHoldingsValue:.2f})" self.Log(msg) def OnOrderEvent(self, orderEvent): order = self.Transactions.GetOrderById(orderEvent.OrderId) self.Log(f"{self.Time}: {order.Type}: {orderEvent}") def TimeIs(self, day, hour, minute): return self.Time.day == day and self.Time.hour == hour and self.Time.minute == minute