Overall Statistics |
Total Trades 601 Average Win 0.14% Average Loss -0.10% Compounding Annual Return -0.732% Drawdown 4.700% Expectancy -0.029 Net Profit -0.370% Sharpe Ratio -0.026 Probabilistic Sharpe Ratio 19.178% Loss Rate 60% Win Rate 40% Profit-Loss Ratio 1.42 Alpha -0.039 Beta 0.275 Annual Standard Deviation 0.079 Annual Variance 0.006 Information Ratio -1.363 Tracking Error 0.1 Treynor Ratio -0.007 Total Fees $3010.55 Estimated Strategy Capacity $1000.00 Lowest Capacity Asset FSEA X68C68WYRWPX |
from AlgorithmImports import * class CryingFluorescentYellowHamster(QCAlgorithm): def Initialize(self): self.SetStartDate(2021, 6, 1) # Set Start Date self.SetCash(100000) # Set Strategy Cash self.SetSecurityInitializer( lambda x: x.SetDataNormalizationMode(DataNormalizationMode.SplitAdjusted) \ and x.SetLeverage(1) and x.SetFillModel(CustomFillModel(self))) self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.SplitAdjusted self.UniverseSettings.Resolution = Resolution.Daily self.UniverseSettings.Leverage = 1 self.AddUniverseSelection(FineFundamentalUniverseSelectionModel(self.SelectCoarse, self.SelectFine)) ''' Universe Metadata ''' self.coarseMinPrice = 4 self.coarseMaxPrice = 10 self.coarseMaxSymbols = 30 '''Init''' self.uniData = {} self.activeStocks = {} self.maxPeriod = 250 # max history lookback def SelectCoarse(self, coarse): for c in coarse: if c.Symbol not in self.uniData: self.uniData[c.Symbol] = UniverseData(c.Symbol, c.HasFundamentalData) # c.Volume/c.SplitFactor - Adjust volume for splits self.uniData[c.Symbol].update(c.Price, c.Volume / c.SplitFactor) # Filter the values of the dict values = [x for x in self.uniData.values() if x.Price >= self.coarseMinPrice and x.hasFundamental and x.Price <= self.coarseMaxPrice] # sort by the largest in volume. values = sorted(values, key=lambda x: x.Price, reverse=True)[:self.coarseMaxSymbols] # we need to return only the symbol objects return [x.Symbol for x in values] def SelectFine(self, fine): fineSymbols = [x for x in fine if x.CompanyReference.PrimaryExchangeID in ["NYS", "NAS", "ASE"]] # we need to return only the symbol objects return [x.Symbol for x in fineSymbols] def OnSecuritiesChanged(self, changes): self.changes = changes # load history for new symbols in uni for security in self.changes.AddedSecurities: if not security.Symbol.Value in self.activeStocks: self.LoadHistory(security) for security in self.changes.RemovedSecurities: if security.Symbol.Value in self.activeStocks and not security.Invested: del self.activeStocks[security.Symbol.Value] def LoadHistory(self, security): sd = SymbolData(security) sd.avgVol = self.SMA(security.Symbol, 30, Resolution.Daily, Field.Volume) sd.close = self.Identity(security.Symbol, Resolution.Daily, Field.Close) sd.close.Updated += sd.CloseUpdated sd.closeWindow = RollingWindow[IndicatorDataPoint](self.maxPeriod) sd.emaTwentyOne = self.EMA(security.Symbol, 21, Resolution.Daily, Field.Close) sd.emaTwentyOne.Updated += sd.EmaTwentyOneUpdated sd.emaTwentyOneWindow = RollingWindow[IndicatorDataPoint](self.maxPeriod) # warmup our indicators by pushing history through the indicators # rolling window must use warmup !IMPORTANT history = self.History(security.Symbol, self.maxPeriod, Resolution.Daily) if (history.empty or history.size < self.maxPeriod): return for index, row in history.loc[security.Symbol].iterrows(): if index == self.Time: continue sd.avgVol.Update(index, row['volume']) sd.close.Update(index, row['close']) sd.emaTwentyOne.Update(index, row['close']) # add sd to active dict with all history self.activeStocks[security.Symbol.Value] = sd def IsReady(self, sd): if (sd.closeWindow.Count < self.maxPeriod or sd.emaTwentyOneWindow.Count < self.maxPeriod): return False if not (sd.closeWindow.IsReady and sd.emaTwentyOneWindow.IsReady): return False return True def OnData(self, data): '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. Arguments: data: Slice object keyed by symbol containing the stock data ''' # sort by avg volume self.activeStocks = dict( sorted(self.activeStocks.items(), key=lambda item: item[1].avgVol.Current.Value, reverse=True)) for key, sd in self.activeStocks.items(): if sd.Security.Fundamentals is None: continue if not (data.ContainsKey(sd.Symbol) and data.Bars.ContainsKey(sd.Symbol)): continue if not self.IsReady(sd): continue if not sd.Security.Invested: if sd.closeWindow[0].Value < sd.emaTwentyOneWindow[0].Value: continue self.SetHoldings(sd.Symbol, 0.1) elif sd.Security.Invested and sd.closeWindow[0].Value > sd.emaTwentyOneWindow[0].Value: self.Liquidate(sd.Symbol) class CustomFillModel(FillModel): ''' Implements a custom fill model that inherit from FillModel. Override the MarketFill method to simulate partially fill orders ''' def __init__(self, algorithm, sd=None): self.algorithm = algorithm self.absoluteRemainingByOrderId = {} self.sd = sd def MarketFill(self, asset, order): absoluteRemaining = order.AbsoluteQuantity if order.Id in self.absoluteRemainingByOrderId.keys(): absoluteRemaining = self.absoluteRemainingByOrderId[order.Id] fill = super().MarketFill(asset, order) absoluteFillQuantity = int(min(absoluteRemaining, order.AbsoluteQuantity)) fill.FillQuantity = np.sign(order.Quantity) * absoluteFillQuantity if absoluteRemaining == absoluteFillQuantity: fill.Status = OrderStatus.Filled if self.absoluteRemainingByOrderId.get(order.Id): self.absoluteRemainingByOrderId.pop(order.Id) else: absoluteRemaining = absoluteRemaining - absoluteFillQuantity self.absoluteRemainingByOrderId[order.Id] = absoluteRemaining fill.Status = OrderStatus.PartiallyFilled self.algorithm.Debug(f"CustomFillModel: {fill}") return fill class UniverseData: def __init__(self, symbol, fundamental): self.Symbol = symbol self.Volume = 0 self.Price = 0 self.hasFundamental = fundamental def update(self, price, volume): self.Volume = volume self.Price = price class SymbolData: def __init__(self, security): self.Security = security self.Symbol = security.Symbol self.avgVol = 0 ''' Candle ''' self.close = None self.closeWindow = None ''' Moving Averages ''' self.emaTwentyOne = None self.emaTwentyOneWindow = None def CloseUpdated(self, sender, updated): self.closeWindow.Add(updated) def EmaTwentyOneUpdated(self, sender, updated): self.emaTwentyOneWindow.Add(updated)