Overall Statistics |
Total Trades 2 Average Win 4.53% Average Loss 0% Compounding Annual Return 545.418% Drawdown 1.500% Expectancy 0 Net Profit 4.527% Sharpe Ratio 19.249 Probabilistic Sharpe Ratio 95.462% Loss Rate 0% Win Rate 100% Profit-Loss Ratio 0 Alpha 5.814 Beta 0.287 Annual Standard Deviation 0.293 Annual Variance 0.086 Information Ratio 20.589 Tracking Error 0.302 Treynor Ratio 19.655 Total Fees $301.81 Estimated Strategy Capacity $670000.00 Lowest Capacity Asset DMC SY1SU1LT23QD |
class SymbolData: def __init__(self, algorithm, symbol): self.algorithm = algorithm self.symbol = symbol # Daily consolidator definition and subscription to data self.daily_consolidator = TradeBarConsolidator(timedelta(days=1)) self.algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.daily_consolidator) self.daily_consolidator.DataConsolidated += self.OnDailyBar # signal resolution consolidator definition and subscription to data self.consolidator = TradeBarConsolidator(timedelta(minutes=10)) self.algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator) self.consolidator.DataConsolidated += self.OnBar # self.algorithm.ResolveConsolidator # minute consolidator definition and subscription to data self.minute_consolidator = TradeBarConsolidator(timedelta(minutes=1)) self.algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.minute_consolidator) self.minute_consolidator.DataConsolidated += self.OnMinuteBar # Volume daily SMA self.vol_sma = SimpleMovingAverage(20) # 30 min resolution - 10 period SMA for price self.sma = SimpleMovingAverage(10) self.algorithm.RegisterIndicator(self.symbol, self.sma, self.consolidator) self.minute_bars = RollingWindow[TradeBar](10) self.bars = RollingWindow[TradeBar](3) self.WarmUpIndicators() def WarmUpIndicators(self): # returns a dataframe - to warmup daily volume sma history = self.algorithm.History(self.symbol, 20, Resolution.Daily) # to warm up 30 minute price sma and minute bar window # minute_history = self.algorithm.History(self.symbol, 330, Resolution.Minute) # # gets rid of symbol/ticker index in df # minute_history = minute_history.droplevel(0, 0) # # creates 30 min bars from minute bars # _30_minute_history = minute_history.resample('30T').ohlc() # # picks out required columns # _30_minute_history = _30_minute_history.drop([c for c in _30_minute_history.columns if c not in ['close', 'volume']], 1) # for bar in minute_history.itertuples(): # time = bar.Index # close = bar.close # open = bar.open # low = bar.low # high = bar.high # volume = bar.volume # min_bar = TradeBar(time, self.symbol, open, high, low, close, volume) # self.minute_bars.Add(min_bar) # m # for row in _30_minute_history.itertuples(): # time = row[0] # open = row[1] # high = row[2] # low = row[3] # close = row[4] # volume = row[8] # _30_min_bar = TradeBar(time, self.symbol, open, high, low, close, volume) # self.bars.Add(_30_min_bar) # self.sma.Update(time, close) for bar in history.itertuples(): time = bar.Index[1] open = bar.open high = bar.high low = bar.low close = bar.close volume = bar.volume daily_bar = TradeBar(time, self.symbol, open, high, low, close, volume) self.vol_sma.Update(time, volume) def OnDailyBar(self, sender, bar): # Updates volume sma with latest daily bar data self.vol_sma.Update(bar.EndTime, bar.Volume) def OnBar(self, sender, bar): # Saves signal resolution bars self.bars.Add(bar) def OnMinuteBar(self, sender, bar): # Saves minute bars self.minute_bars.Add(bar) #Find the $ Volume of the signal bar @property def RecentDollarVolume(self): dollar_volume = 0 for bar in list(self.minute_bars): dollar_volume += bar.Volume * bar.Close return dollar_volume # Find Volume of the signal bar @property def RecentVolume(self): actual_volume = 0 for bar in list(self.minute_bars): actual_volume += bar.Volume return actual_volume @property def CandlePatternSignal(self): # Close > Open # Wick < .5*Body # Body > 1.02*Open most_recent_bar = self.bars[0] close = most_recent_bar.Close open = most_recent_bar.Open high = most_recent_bar.High wick = high - close body = close - open return close > open and wick < 0.5 * body and close > 1.02 * open @property def UnusualVolume(self): vol_sma = self.vol_sma.Current.Value recent_volume = self.bars[0].Volume return recent_volume > 3 * vol_sma @property def UnusualVolumeSignal(self): dolvolLim = bool(self.RecentDollarVolume > 150000) #dollars minimum actualVolume = bool(self.RecentVolume > 150000) #shares minimum if dolvolLim and actualVolume and self.CandlePatternSignal and self.UnusualVolume: return True return False #return self.RecentDollarVolume > 500000 and self.CandlePatternSignal and self.UnusualVolume @property def findGapUp(self): #Get the Signal Candle Open price to compare to yesterdays high most_recent_bar = self.bars[0] open = most_recent_bar.Open #Find Yesterdays high to compare to for a gap up or down history = self.algorithm.History(self.symbol, 1, Resolution.Daily) for bar in history.itertuples(): time = bar.Index[1] #open = bar.open high = bar.high #low = bar.low #close = bar.close #volume = bar.volume #daily_bar = TradeBar(time, self.symbol, open, high, low, close, volume) if open > high: self.algorithm.Debug(f"Gap Up on {self.symbol} -- 10m Open is {open} > yesterday high of {high}") return True if open <= high: self.algorithm.Debug(f"NO Gap Up on {self.symbol} -- 10m Open is {open} <= yesterday high of {high}") return False @property def IsReady(self): return self.vol_sma.IsReady and self.sma.IsReady and self.minute_bars.IsReady and \ self.bars.IsReady def KillConsolidators(self): self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.consolidator) self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.minute_consolidator) self.algorithm.SubscriptionManager.RemoveConsolidator(self.symbol, self.daily_consolidator)
class TradeManagement: def __init__(self, algorithm, symbol): self.algorithm = algorithm self.symbol = symbol self.entry_ticket = None self.stop_loss = None self.take_profit = None # Trailing stop functionality self.trailing_stop_percent = None self.trailing_stop_reference = None def TrailingStopWithTakeProfitOrder(self, quantity, stop_percent=0.10, take_profit_percent=0.30): self.entry_ticket = self.algorithm.MarketOrder(self.symbol, quantity) market_price = self.algorithm.Securities[self.symbol].Price if quantity > 0: self.take_profit = market_price * (1 + take_profit_percent) self.stop_loss = market_price * (1 - stop_percent) else: self.take_profit = market_price * (1 - take_profit_percent) self.stop_loss = market_price * (1 + stop_percent) self.trailing_stop_reference = market_price self.trailing_stop_percent = stop_percent self.algorithm.Debug(f"Entering Position For {self.symbol}, quantity: {quantity}, entry_price:{market_price}, stop_loss:{self.stop_loss}, take_profit:{self.take_profit}") def CheckAndUpdate(self): current_market_price = self.algorithm.Securities[self.symbol].Price if not self.ActivePosition: return # updating trailing stop and checking stop loss/take profit crossovers if self.IsLong: if current_market_price > self.trailing_stop_reference: self.trailing_stop_reference = current_market_price self.stop_loss = (1 - self.trailing_stop_percent) * self.trailing_stop_reference # Checking for crossovers for stop loss and take profit if current_market_price < self.stop_loss: self.algorithm.Debug(f"{self.symbol} - Stop loss hit! market_price:{current_market_price} stop_loss:{self.stop_loss}") self.Liquidate() elif current_market_price > self.take_profit: self.algorithm.Debug(f"{self.symbol} - Take loss hit! market_price:{current_market_price} take_profit:{self.take_profit}") self.Liquidate() else: if current_market_price < self.trailing_stop_reference: self.trailing_stop_reference = current_market_price self.stop_loss = (1 + self.trailing_stop_percent) * self.trailing_stop_reference if current_market_price > self.stop_loss: self.algorithm.Debug(f"{self.symbol} - Stop loss hit! market_price:{current_market_price} stop_loss:{self.stop_loss}") self.Liquidate() elif current_market_price < self.take_profit: self.algorithm.Debug(f"{self.symbol} - Take loss hit! market_price:{current_market_price} take_profit:{self.take_profit}") self.Liquidate() def CancelEntryOrder(self): if self.entry_ticket is not None: self.entry_ticket.Cancel() self.stop_loss = None self.take_profit = None @property def ActivePosition(self): return self.algorithm.Portfolio[self.symbol].Invested def GetPositionSize(self): portfolio_value = self.algorithm.Portfolio.TotalPortfolioValue #... def Liquidate(self): self.algorithm.Debug(f"Liquidating.. {self.symbol}....{self.algorithm.Securities[self.symbol].Price}") self.algorithm.Liquidate(self.symbol) self.entry_ticket = None self.stop_loss = None self.take_profit = None self.trailing_stop_percent = None self.trailing_stop_reference = None @property def IsLong(self): if not self.ActivePosition: self.Debug("Error: Can Check If Long for Active Positions") return None return self.algorithm.Portfolio[self.symbol].IsLong
from SymbolData import * from TradeManagement import * class LogicalRedOrangeGoshawk(QCAlgorithm): def Initialize(self): self.SetStartDate(2021, 6, 10) self.SetEndDate(2021, 6, 20) self.SetCash(100000) self.benchmark = "SPY" self.AddEquity("SPY", Resolution.Minute) self.AddEquity("DSS", Resolution.Minute) # self.AddUniverse(self.SelectCoarse) self.UniverseSettings.Resolution = Resolution.Minute self.symbols = {} self.trade_managers = {} self.Schedule.On(self.DateRules.EveryDay(self.benchmark), self.TimeRules.AfterMarketOpen(self.benchmark, 11), self.Rebalance) self.Schedule.On(self.DateRules.EveryDay(self.benchmark), self.TimeRules.BeforeMarketClose(self.benchmark, 1), self.EoDClose) def Rebalance(self): #self.Debug(f"Universe Size... {len(self.symbols)}") for symbol, symbol_data in self.symbols.items(): if not symbol_data.IsReady: continue #trade_manager = self.trade_managers[symbol] #Unusual Volume Signal if symbol_data.UnusualVolumeSignal: #self.SetHoldings(symbol, -.15) #self.Debug(f"shorting {symbol}") #Go Short trade_manager = self.trade_managers[symbol] if symbol_data.findGapUp == True: # self.SetHoldings(symbol, -.15) quantity = -1 * self.CalculateOrderQuantity(symbol, 0.15) stop_percent = 0.20 take_profit_percent = 0.30 trade_manager.TrailingStopWithTakeProfitOrder(quantity, stop_percent, take_profit_percent) self.Debug(f"short {symbol}") #Go Long if symbol_data.findGapUp == False: quantity = self.CalculateOrderQuantity(symbol, 0.50) stop_percent = 0.20 take_profit_percent = 0.30 trade_manager.TrailingStopWithTakeProfitOrder(quantity, stop_percent, take_profit_percent) # self.SetHoldings(symbol, .5) self.Debug(f"Long {symbol}") # self.Debug(f"{symbol} - $V {symbol_data.RecentDollarVolume}, bar signal {symbol}") # self.Debug(f"~~~~{symbol} - {self.Time}~~~~~") # self.Debug(f"Unusual Volume Signal: {symbol_data.UnusualVolumeSignal}") # self.Debug(f"30 Minute $ Volume: {symbol_data.RecentDollarVolume}") # self.Debug(f"Candle Pattern: {symbol_data.CandlePatternSignal}") # self.Debug(f"20 Day VOL SMA: {symbol_data.vol_sma.Current.Value}") # latest_bar = symbol_data.bars[0] # self.Debug(f"latest 30 min bar: {latest_bar.EndTime} --- {latest_bar}") # self.Debug(f"Unusual Volume: {symbol_data.UnusualVolume}") # self.Debug(f"{self.Time} -- {symbol} has a signal!") def EoDClose(self): for symbol, symbol_data in self.symbols.items(): if self.Portfolio[symbol].Invested: self.Debug(f"End of Day Close... {symbol}") self.trade_managers[symbol].Liquidate() def SelectCoarse(self, coarse): filtered_by_price = [c for c in coarse if c.AdjustedPrice >= 1 and c.AdjustedPrice <= 7] return [c.Symbol for c in filtered_by_price] def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: symbol = security.Symbol if symbol not in self.symbols and symbol.Value != self.benchmark: self.symbols[symbol] = SymbolData(self, symbol) self.trade_managers[symbol] = TradeManagement(self, symbol) for security in changes.RemovedSecurities: symbol = security.Symbol if symbol in self.symbols: symbol_data = self.symbols.pop(symbol, None) symbol_data.KillConsolidators() self.trade_managers.pop(symbol, None) def OnData(self, data): for symbol, trade_manager in self.trade_managers.items(): # dont care for symbols with no open positions if not trade_manager.ActivePosition: continue # update stop losses, check for breaches and handle with liquidates # if necessary trade_manager.CheckAndUpdate()