Overall Statistics |
Total Trades 36 Average Win 0.16% Average Loss -0.39% Compounding Annual Return -29.573% Drawdown 16.300% Expectancy -0.193 Net Profit -6.287% Sharpe Ratio -1.603 Probabilistic Sharpe Ratio 8.171% Loss Rate 43% Win Rate 57% Profit-Loss Ratio 0.41 Alpha -0.315 Beta 0.418 Annual Standard Deviation 0.184 Annual Variance 0.034 Information Ratio -1.74 Tracking Error 0.198 Treynor Ratio -0.705 Total Fees $36.00 |
import numpy as np class BetaAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2020, 8, 20) # Set Start Date self.SetEndDate(2020, 10, 26) # Set End Date self.SetCash(10000) # Set Strategy Cash self.ReductionCoeff = 0.98 # Coefficient to avoid leverage self.SetWarmup(22) self.PositionsNumber = 5 # Max. number of active positions for this strategy self.spy = self.AddEquity("QQQ", Resolution.Daily) # Add equity to use for trends # Dow 30 companies. self.symbols = [self.AddEquity(ticker).Symbol for ticker in [ 'FVRR', # FIVERR 'GH', # Guardant Health 'GWRE', # Guidewire Software 'HUBS', # HubSpot 'HUYA', # HUYA 'IBKR', # Interactive Brokers Group 'INCY', # Incyte Corporation 'IONS', # Ionis Pharmaceuticals 'IOVA', # Iovance Biotherapeutics 'IRDM', # Iridium Communications 'KTOS', # Kratos Defense & Security Solutions 'LMND', # Lemonade 'LSPD', # Lightspeed POS 'MCRB', # Seres Therapeutics 'MKL', # Markel Corporation ] ] # Benchmark - Security used as reference to beat. In this case QQQ (NASDAQ index) self.benchmark = Symbol.Create('QQQ', SecurityType.Equity, Market.USA) # Set number days to trace back self.lookback = 1 # Schedule Event: trigger the event at the begining of each month. self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen(self.symbols[0]), self.Rebalance) #QQQ trend filter: only trades if benchmark trends # Very fast moving average of benchmark self.spy_ma_vfast = self.EMA("QQQ", 5) # Fast moving average of benchmark self.spy_ma_fast = self.EMA("QQQ", 13) # Slow moving average of benchmark self.spy_ma_slow = self.EMA("QQQ", 21) def Rebalance(self): # Defines benchmark in a trend (fast ma over slow ma) self.trend_up = self.spy_ma_vfast > self.spy_ma_fast and self.spy_ma_fast > self.spy_ma_slow # Fetch the historical data to perform the linear regression history = self.History( self.symbols + [self.benchmark], self.lookback, Resolution.Daily).close.unstack(level=0) symbols = self.SelectSymbols(history) # Liquidate positions that are not held by selected symbols and liquidates if benchmark is not in a trend for holdings in self.Portfolio.Values: try: symbol = holdings.Symbol if symbol not in symbols and holdings.Invested: self.Liquidate(symbol) except: self.Debug("Error in: " + str(symbol)) continue # Invest in the selected symbols is benchmark/reference is in a trend for symbol in symbols: try: if self.trend_up: # Position size is calculated based on active strategies, max positions number and some coeffs to avoid leverage self.SetHoldings(symbol, (1*self.ReductionCoeff)/self.PositionsNumber) if not self.trend_up: self.Liquidate(symbol) except: self.Debug("Error in: " + str(symbol)) continue def SelectSymbols(self, history): '''Select symbols with the highest intercept/alpha to the benchmark''' alphas = dict() # Get the benchmark returns benchmark = history[self.benchmark].pct_change().dropna() # Conducts linear regression for each symbol and save the intercept/alpha for symbol in self.symbols: try: # Get the security returns returns = history[symbol].pct_change().dropna() returns = np.vstack([returns, np.ones(len(returns))]).T # Simple linear regression function in Numpy result = np.linalg.lstsq(returns, benchmark) alphas[symbol] = result[0][1] except: self.Debug("Error in: " + str(symbol)) continue # Select symbols and number of them with the highest intercept/alpha to the benchmark selected = sorted(alphas.items(), key=lambda x: x[1], reverse=True)[:self.PositionsNumber] return [x[0] for x in selected]