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()