Overall Statistics |
Total Trades 71 Average Win 2.41% Average Loss -0.72% Compounding Annual Return 2.838% Drawdown 38.300% Expectancy 0.322 Net Profit 10.524% Sharpe Ratio 0.075 Sortino Ratio 0.088 Probabilistic Sharpe Ratio 3.455% Loss Rate 70% Win Rate 30% Profit-Loss Ratio 3.36 Alpha -0.031 Beta 0.462 Annual Standard Deviation 0.161 Annual Variance 0.026 Information Ratio -0.489 Tracking Error 0.166 Treynor Ratio 0.026 Total Fees $71.06 Estimated Strategy Capacity $370000000.00 Lowest Capacity Asset V U12VRGLO8PR9 Portfolio Turnover 0.54% |
#region imports from AlgorithmImports import * #endregion class PortfolioHelper(): # ========================================== # Constructor. Accepts algo Object # ========================================== def __init__(self, algo, maxOpenPositions, debugMode=False): # Algo reference # ---------------------------------------- self.algo = algo self.debugMode = debugMode self.maxOpenPositions = maxOpenPositions ## How many available spots are available in the portfolio ## ------------------------------------------------------------ @property def PortfolioCapacity(self): return int(max(0,(self.maxOpenPositions - self.NumHoldings))) ## Check if we are already holding the max # of open positions. ## ------------------------------------------------------------ @property def PortfolioAtCapacity(self): numHoldings = self.NumHoldings atCapacity = numHoldings >= self.maxOpenPositions if( self.debugMode): self.algo.Plot("Num of Positions", 'Line', numHoldings) # # Plot max held ever # # ------------------ # if( not hasattr(self,"maxHeldEver")): # self.maxHeldEver = numHoldings # else: # if(self.maxHeldEver < numHoldings): # debugstop = "here" # self.algo.Plot("Max Positions", 'Line', numHoldings) # self.maxHeldEver = max(self.maxHeldEver,numHoldings) return atCapacity # ===================================== @property def NumHoldings(self): numHoldings = len([x.Key for x in self.algo.Portfolio if x.Value.Invested]) return numHoldings # ===================================== def InvestedInSymbol (self, symbol): return (self.algo.Portfolio.ContainsKey(symbol)) and (self.algo.Portfolio[symbol].Invested) # ===================================== def ProfitLossMsgForSymbol (self, symbol): if(self.InvestedInSymbol(symbol)): profitPct = self.algo.Portfolio[symbol].UnrealizedProfitPercent winlossMsg = f"{round(profitPct*100,2)}% {( 'Win' if (profitPct > 0) else 'Loss')}" return winlossMsg else: return None
#region imports from AlgorithmImports import * #endregion ########################################################################## # The Ichimoku Breakout Testbed # ---------------------------------------------------- # # Entry: # ------- # Ichimoku cloud breakout # # # Exit: # ------- # When price dips below Tenkan # # @shock_and_awful ########################################################################## from PortfolioHelper import * class IchimokuUniverse(QCAlgorithm): # ===================================== def Initialize(self): self.SetStartDate(2020, 6, 1) # self.SetEndDate(2020, 12, 30) self.SetCash(100000) self.PortfolioHelper = PortfolioHelper(self, 5, debugMode=True) # 5 max open positions self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) self.symDataDict = { } # ===================================== def OnData(self, dataSlice): for symbol in dataSlice.Keys: if symbol in self.symDataDict: symbolData = self.symDataDict[symbol] symbolData.UpdateIndicatorsWithBars(dataSlice[symbol]) if( symbolData.ExitSignalFired() ): msg = self.PortfolioHelper.ProfitLossMsgForSymbol(symbol) self.Liquidate(symbol, tag=msg) # ===================================== def CoarseSelectionFunction(self, universe): coarseuniverse = sorted(universe, key=lambda c: c.DollarVolume, reverse=True) coarseuniverse = [c for c in coarseuniverse if c.Price > 30][:30] return [x.Symbol for x in coarseuniverse] # ===================================== def FineSelectionFunction(self, universe): # If portfolio is at capacity, skip universe selection # ---------------------------------------------------- if self.PortfolioHelper.PortfolioAtCapacity: return [] fineUniverse = [x for x in universe if x.SecurityReference.IsPrimaryShare and x.SecurityReference.SecurityType == "ST00000001" and x.SecurityReference.IsDepositaryReceipt == 0 and x.CompanyReference.IsLimitedPartnership == 0] selected = [] queuedAssetCount = 0 for element in fineUniverse: symbol = element.Symbol # Store data for this symbol, seed it with some history # ----------------------------------------------------- if symbol not in self.symDataDict: # Fetch enough historical bars with which to seed the symbol's indicators history = self.History(symbol, 201, Resolution.Daily) self.symDataDict[symbol] = SymbolData(symbol, history, self) # Stop screening If we've queued up more assets than we have capacity for # ------------------------------------------------------------------------ if(queuedAssetCount >= self.PortfolioHelper.PortfolioCapacity): del self.symDataDict[symbol] else: # If the entry signal fired for this symbol, add to universe # -------------------------------------------------------------- if self.symDataDict[symbol].IsReady() and self.symDataDict[symbol].EntrySignalFired(): selected.append(symbol) queuedAssetCount += 1 else: del self.symDataDict[symbol] return selected # # ===================================== def OnSecuritiesChanged(self, changes): # The trade actually takes place here, when # the symbol passes coarse and fine filters # and is added to our dictionary of securities # -------------------------------------------- for security in changes.AddedSecurities: self.SetHoldings(security.Symbol, 0.1) ################################## # # SymbolData Class # ################################## class SymbolData(): # ========================================== # Constructor. Accepts History array # ========================================== def __init__(self, theSymbol, history, algo): # Algo / Symbol / Price reference # ---------------------------------------- self.algo = algo self.symbol = theSymbol self.lastPrice = 0 # Initialize our Ichi and EMA indicators # ---------------------------------------- self.ichimoku = IchimokuKinkoHyo(9, 26, 26, 52, 26, 26) self.emaThirtyFour = ExponentialMovingAverage(34) self.emaTwoHundred = ExponentialMovingAverage(200) # Initialize vars for tracking indicator state # ---------------------------------------------- self.IsTKAboveCloud = False self.IsPriceBelowKJ = False self.IsBelowCloudWindow = RollingWindow[Boolean](2) self.LastPriceWindow = RollingWindow[float](2) self.ichimoku.Updated += self.UpdateIchimokuWindows # Loop over the history data and update the Ichimoku indicator # ------------------------------------------------------------- if history.empty or 'close' not in history.columns: return for index, row in history.loc[theSymbol].iterrows(): tradeBar = TradeBar() tradeBar.Close = row['close'] tradeBar.Open = row['open'] tradeBar.High = row['high'] tradeBar.Low = row['low'] tradeBar.Volume = row['volume'] tradeBar.Time = index tradeBar.Period = timedelta(1) tradeBar.Symbol = theSymbol self.UpdateIndicatorsWithBars(tradeBar) # ===================================== def UpdateIndicatorsWithBars(self, tradeBar): if((tradeBar is not None) and (self.ichimoku is not None)): self.lastPrice = tradeBar.Close self.LastPriceWindow.Add( tradeBar.Close ) self.ichimoku.Update(tradeBar) self.emaThirtyFour.Update(tradeBar.Time, tradeBar.Close) self.emaTwoHundred.Update(tradeBar.Time, tradeBar.Close) # ===================================== def UpdateIchimokuWindows(self, sender, updated): senkouA = sender.SenkouA.Current.Value senkouB = sender.SenkouB.Current.Value cloudTop = max(senkouA, senkouB) belowCloud = updated.Price <= cloudTop self.lastPrice = updated.Price self.IsPriceBelowKJ = updated.Price < sender.Kijun.Current.Value self.IsTKAboveCloud = (sender.Tenkan.Current.Value > cloudTop) self.IsBelowCloudWindow.Add( belowCloud ) # ===================================== def IsReady(self): return self.ichimoku.IsReady and \ self.emaThirtyFour.IsReady and \ self.emaTwoHundred.IsReady # ===================================== def EntrySignalFired(self): # return true if we broke out of the cloud, and TK is above the cloud # -------------------------------------------------------------------- if( ( self.IsBelowCloudWindow.Count > 1 ) and \ ( self.IsBelowCloudWindow[1] and (not self.IsBelowCloudWindow[0] )) and \ ( self.IsTKAboveCloud and (self.LastPriceWindow[0] > self.emaThirtyFour.Current.Value > self.emaTwoHundred.Current.Value))): # ( self.IsTKAboveCloud )): return True return False # ===================================== def ExitSignalFired(self): # return true if price dipped below the TK if self.IsPriceBelowKJ: return True return False