Overall Statistics |
Total Trades 651 Average Win 2.82% Average Loss -0.62% Compounding Annual Return 8.655% Drawdown 46.600% Expectancy 0.729 Net Profit 697.766% Sharpe Ratio 0.475 Probabilistic Sharpe Ratio 0.057% Loss Rate 69% Win Rate 31% Profit-Loss Ratio 4.56 Alpha 0.015 Beta 0.798 Annual Standard Deviation 0.149 Annual Variance 0.022 Information Ratio 0.006 Tracking Error 0.087 Treynor Ratio 0.089 Total Fees $1310.16 Estimated Strategy Capacity $49000000.00 Lowest Capacity Asset CCK R735QTJ8XC9X |
#region imports from AlgorithmImports import * #endregion class AlertTanMosquito(QCAlgorithm): def Initialize(self): self.SetStartDate(1997, 1, 1) self.SetEndDate(2022, 1, 1) self.SetCash(100000) self.SetBenchmark("SPY") self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin) self.SetSecurityInitializer(self.CustomSecurityInitializer) self.Settings.FreePortfolioValuePercentage = 0.05 # Set universe of stocks self.maximums = {} self.activeStocks = set() self.AddUniverse(self.MyCoarseFilter, self.MyFineFilter) self.UniverseSettings.Resolution = Resolution.Daily # Variables self.stoploss = float(self.GetParameter('Stop Loss')) self.num_coarse = int(self.GetParameter('Number of Course')) self.num_fine = int(self.GetParameter('Number of Fine')) self.lookback = int(self.GetParameter('Lookback Period')) #self.closeWindow = RollingWindow[float](self.lookback) def CustomSecurityInitializer(self, security): security.SetLeverage(1) security.SetDataNormalizationMode(DataNormalizationMode.Raw) def MyCoarseFilter(self, coarse): if len(self.activeStocks) >= 20: return self.Universe.Unchanged # Rebalance universe if a portfolio is not full # if len(self.activeStocks) < 20: # self.Log("Rebalancing as active stock count: " + str(len(self.activeStocks))) # Performs coarse selection for the QC500 constituents. # The stocks must have fundamental data # The stock must have positive previous-day close price # The stock must have positive volume on the previous trading day sortedByDollarVolume = sorted([x for x in coarse if x.HasFundamentalData and x.Volume > 0 and x.Price > 0], key = lambda x: x.DollarVolume, reverse=True) count = len(sortedByDollarVolume) # If no security has met the QC500 criteria, the universe is unchanged. if count == 0: #self.Log("No securities have met the coarse conditions.") return Universe.Unchanged # Filter for stocks above yearly high universe = sortedByDollarVolume[:self.num_coarse] for security in universe: symbol = security.Symbol if symbol not in self.maximums: self.maximums[symbol] = SelectionData(symbol, self.lookback) # Update SelectionData Object with current EOD price maxi = self.maximums[symbol] maxi.update(security.EndTime, security.AdjustedPrice) # Filter out values of the dictionary where price is below yearly high values = list(filter(lambda x: x.is_above_max, self.maximums.values())) count = len(values) if count == 0: #self.Log("No securities have met the yearly high conditions.") return Universe.Unchanged # Return the symbol objects of our sorted collection return [x.symbol for x in values[:self.num_coarse]] def MyFineFilter(self, fine): # Performs fine selection for the QC500 constituents # Company's headquarter must in the U.S. # Stock must be traded on either the NYSE or NASDAQ # At least half a year since its initial public offering # The stock's market cap must be greater than 500 million sortedByMarketCap = sorted([x for x in fine if x.CompanyReference.CountryId == "USA" and x.CompanyReference.PrimaryExchangeID in ["NYS","NAS"] and (self.Time - x.SecurityReference.IPODate).days > 180 and x.MarketCap > 5e8], key = lambda x: x.MarketCap, reverse=True) count = len(sortedByMarketCap) # If no security has met the QC500 criteria, the universe is unchanged. if count == 0: #self.Log("No securities have met the fine conditions.") return Universe.Unchanged # Return the symbol objects of our sorted collection return [x.Symbol for x in sortedByMarketCap[:self.num_fine]] def OnData(self, data): for security in self.ActiveSecurities.Values: if data.ContainsKey(security.Symbol): pass #self.closeWindow.Add(data[security.Symbol].Close) else: return for security in self.ActiveSecurities.Values: symbol = security.Symbol # Set up yearly Low indicator yearly_low = min(self.History(symbol, self.lookback, Resolution.Daily)["close"]) price = security.Price # Exit trade using market on open order when price falls below yearly low if price <= yearly_low and self.Portfolio[symbol].Invested: self.Liquidate(symbol, "Exit Conditions Met") self.activeStocks.remove(symbol) self.Log(str(symbol) + ' removed from portfolio as Exit conditions were met.') # Place market on open order when price is above yearly high if not self.Portfolio[symbol].Invested and len(self.activeStocks) < 20: self.SetHoldings(symbol, 1/self.num_fine, False, "Entry Conditions Met") self.activeStocks.add(symbol) self.Log(str(symbol) + ' added to portfolio') def OnOrderEvent(self, orderEvent): order = self.Transactions.GetOrderById(orderEvent.OrderId) # Remove invalid orders from active stocks set if orderEvent.Status == OrderStatus.Invalid and order.Type == OrderType.Market: self.activeStocks.remove(order.Symbol) # Set Stop Loss order on fill if orderEvent.Status == OrderStatus.Filled and self.Portfolio[order.Symbol].Invested: self.StopMarketOrder(order.Symbol, -order.Quantity, self.stoploss*self.Portfolio[order.Symbol].AveragePrice) # Remove sold securities from active stocks set if orderEvent.Status == OrderStatus.Filled and order.Type == OrderType.StopMarket: self.activeStocks.remove(order.Symbol) self.Log(str(order.Symbol) + ' removed from portfolio as Stop was initiated.') def OnSecuritiesChanged(self, changes): pass class SelectionData(object): def __init__(self, symbol, period): self.symbol = symbol self.high = Maximum(period) self.is_above_max = False def is_ready(self): return self.high.IsReady def update(self, time, price): if self.high.Update(time, price): high = self.high.Current.Value self.is_above_max = price >= high