Overall Statistics
Total Trades
302
Average Win
1.19%
Average Loss
-1.66%
Compounding Annual Return
3.005%
Drawdown
14.300%
Expectancy
0.225
Net Profit
69.980%
Sharpe Ratio
0.409
Probabilistic Sharpe Ratio
0.173%
Loss Rate
28%
Win Rate
72%
Profit-Loss Ratio
0.71
Alpha
0.023
Beta
-0.008
Annual Standard Deviation
0.054
Annual Variance
0.003
Information Ratio
-0.318
Tracking Error
0.171
Treynor Ratio
-2.947
Total Fees
$1721.31
Estimated Strategy Capacity
$420000.00
Lowest Capacity Asset
SNTI XZ6355XJQ4IT
# region imports
from AlgorithmImports import *
import numpy as np

# endregion

class HyperActiveGreenFish(QCAlgorithm):

    def Initialize(self):

        qb = self 

        qb.SetStartDate(2005,1,5)
        qb.SetEndDate(2022,12,1)
        qb.SetCash(100000)
        qb.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash)
        self.SetBenchmark("SPY")    

        self.resolution = Resolution.Daily

        # self.rebalanceTime = datetime.min
        self.activeStocks = set()       # to keep track of stocks

        self.universe = {}

        self.free_positions = 10

        self.UniverseSettings.Resolution = self.resolution
        self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.SplitAdjusted
        self.AddUniverse(self.CoarseFilter)
        #, self.FineFilter)

        # self.SetWarmup(10, self.resolution)   # days = daysSMA


    def CoarseFilter(self, coarse):

        self.coarseListReturn = []
        self.coarseNames = []
        # self.coarseListReturn.clear()

        sortedByDollarVolume = sorted(coarse, key = lambda x: x.DollarVolume, reverse=True)
        # filteredByVolume = [c for c in sortedByDollarVolume if c.DollarVolume > 25000000]
        coarseUniverse = [c for c in sortedByDollarVolume if c.Price > 5]

        for c in coarseUniverse:
            symbol = c.Symbol
            #2. Check if we've created an instance of SelectionData for this symbol
            if symbol not in self.universe:
                #Create a new instance of SelectionData and save to universe[symbol]
                # First Call history to get tradebars and dataframe
                history_tb = self.History[TradeBar](symbol, 10, Resolution.Daily)
                history_df = self.History(symbol, 100, Resolution.Daily)
                
                if 'close' in history_df.columns and len(history_df) > 10 and not history_df.isnull().values.any():
                    self.universe[symbol] = SelectionData(history_tb, history_df, c.Price)
                    stock = self.universe[symbol]
                    
                    volume = stock.average_volume(history_tb, c.Price)
                    if volume > 8 :
                        #5. Check ATR and closingprices, and if ok append the symbol to selected list.
                        atr10 = stock.atr_10(history_tb, c.Price)
                        if stock.atrIsReady and atr10 > 3:   
                            # check if price increased last 2 days
                            higher = stock.each_day_higher(history_df)
                            if higher:
                                # check percent change prev 3 days
                                # if stock.percent_up_prev_days(history_df, 3) > 6:
                                # check RSI
                                rsi = stock.rsi_TV(history_df, 3)
                                if rsi > 92:
                                    self.coarseListReturn.append(symbol)
                                    self.coarseNames.append(symbol.Value)  

        self.coarseListReturn = sorted(self.coarseListReturn, key = lambda x: self.universe[x].rsi_atEntry, reverse=True)
        self.Log('COARSE: RSI: 92 - Volume avg: 8 - ATR: 2.5 - higher: 2 days - up 3 days:NaN%')
        self.Log(f'UniverseStocks amount: {len(self.coarseListReturn)}')
        self.coarseListReturn = self.coarseListReturn[:10]
        return self.coarseListReturn
    

    def OnSecuritiesChanged(self, changes):
        self.Log(f'Date: {self.Time}. (in OnSecuritiesChanged)')

        # securities taken out of universe:
        for x in changes.RemovedSecurities:
            symbol = x.Symbol
            if symbol in self.activeStocks: 
                self.activeStocks.remove(symbol)
                self.Log(f'{symbol.Value} Removed from Universe')

        # securities added to universe:
        for x in changes.AddedSecurities:
            symbol = x.Symbol
            self.activeStocks.add(symbol)
            self.Log(f'{symbol.Value} Added to Universe')

            # for symbol in self.activeStocks:
            if not self.Portfolio[symbol].IsShort:

                # checking how many positions I already chave
                # invested = [x.Key for x in self.Portfolio if x.Value.Invested]
                self.invested = [ x.Symbol.Value for x in self.Portfolio.Values if x.Invested ]
                # activePositions = len(invested)
                self.activePositions = len(self.invested)
                self.Log(f'{self.activePositions} already active positions invested')
                self.Log(f'Active positions: {self.invested}')
                
                # calculating positionsize:          
                self.free_positions = 10 - self.activePositions
                if self.free_positions == 0:
                    cashPerPosition = 0
                else:
                    cashPerPosition = 0.92*(self.Portfolio.MarginRemaining / self.free_positions)

                self.Log(f'Free positions: {self.free_positions}')
                self.Log(f'Cash pr. position: {cashPerPosition}')
                # GOING SHORT
                if self.free_positions > 0:
                
                    # getting current price
                    symbol.price = self.Securities[symbol].Price
                    if symbol.price == 0:
                        return
                    # calculate number of stocks to buy
                    amountToBuy = cashPerPosition / symbol.price

                    stock = self.universe[symbol]
                    self.orderTicket = self.MarketOrder(symbol, -amountToBuy)  
                    # save ticket to class
                    stock.order_ticket(self.orderTicket)
                    #initialising profit-max-log
                    stock.max_profit(0)
                    self.Log(f'Buy-Order sent: {symbol.Value} Cost: {cashPerPosition} RSI: {stock.rsi_atEntry}')      

        # # if empty coarseList, clear universe
        # if self.coarseListReturn == []:
        #     self.activeStocks.clear()



    def OnData(self, data):
        self.Log(f'{self.Time} - DAY START')
        # return if warming up:   
        if self.IsWarmingUp:
            return

        for symbol in self.activeStocks:
            if symbol not in data:
                return


        if self.activeStocks == [] and not self.Portfolio.Invested:
            return

        # if self.Portfolio.MarginRemaining < 500:
        #     return

        # # checking inventory in Universe
        # for universe in self.UniverseManager.Values:
        #     if universe is UserDefinedUniverse:
        #         continue
        #     symbols = universe.Members.Keys
        #     symbol_list = []
        #     symbol_names = []
            
        #     for symbol in symbols:
        #         symbol_names.append(symbol.Value)
        #         symbol_list.append(symbol)
        # # self.Log(symbol_list)
    
       
     
        # COVERING SHORT
        portfolioHoldings = list(self.Portfolio.keys())
        for symbol in portfolioHoldings:
            if self.Portfolio[symbol].IsShort:
                stock = self.universe[symbol]

                # logging new day in tradingdays since entry
                stock.tradingdays_since_entry()

                # getting current price
                self.currentPrice = self.Securities[symbol].Price

                # checking status
                self.profitSinceEntry = 100 * self.Portfolio[symbol].UnrealizedProfitPercent
                # profitSoFar = stock.profit_since_entry(stock.entryPrice, currentPrice)
            
           
                if stock.tradingDaysSinceEntry >= 6:
                    self.Liquidate(symbol)
                    self.Log(f'6-Days-sell: {symbol}')

                # exit on profit 10%
                elif self.profitSinceEntry > 10:
                    self.Liquidate(symbol)
                    self.Log(f'Profit-sell above 10%: {symbol}')

                # Stop-loss
                elif self.currentPrice > (stock.entryPrice + (3*stock.atr10_atEntry)):
                    self.Liquidate(symbol)
                    self.Log(f'Stop-loss 3ATR: {symbol}')


        # END of DAY 
        self.Log(f'EndOfDay: Invested in {len(self.invested)} stocks: {self.invested}')


    def OnOrderEvent(self, orderEvent):
        symbol = orderEvent.Symbol
        stock = self.universe[symbol]
        order_id = orderEvent.OrderId 

        ticket = self.Transactions.GetOrderTicket(order_id)

        if orderEvent.Status == OrderStatus.Submitted:
            # Update the order tag
            # updateSettings = UpdateOrderFields()
            tag = f'Submitted: RSI: {stock.rsi_atEntry:.2f} , ATR: {stock.atr10_atEntry:.2f}' #% (stock.rsi_atEntry, stock.atr10_atEntry)
            response = ticket.UpdateTag(tag)
            stock.order_date_submitted(self.Time)
            
        elif orderEvent.Status == OrderStatus.Filled:

            if orderEvent.Direction == OrderDirection.Sell:
                stock.entry_date(self.Time)
                # logging entry price
                stock.entry_price(orderEvent.FillPrice)
                # set stop-order
                stock.entry_quantity(orderEvent.FillQuantity)
                # Update the order tag
                updateSettings = UpdateOrderFields()
                updateSettings.Tag = "OnOrderEvent: Sell Filled "
                response = ticket.Update(updateSettings)

                self.free_positions -= 1

            if orderEvent.Direction == OrderDirection.Buy:
                stock.exit_date(self.Time)
                # logging exit price
                stock.exit_price(orderEvent.FillPrice)
                open_orders = self.Transactions.GetOpenOrders(symbol)
                if not len(open_orders) == 0:
                    self.Transactions.CancelOpenOrders(symbol)
                # Update the order tag
                updateSettings = UpdateOrderFields()
                updateSettings.Tag = "OnOrderEvent: Buy Filled"
                response = ticket.Update(updateSettings)
                self.universe.pop(symbol)
                self.free_positions += 1
                self.Log(f'OrderEvent (Buy Filled): {symbol} (removed from universe(dict)')

        elif orderEvent.Status == OrderStatus.Canceled:
            self.Log('Order Canceled: %s' % (symbol.Value))
            
            


# class SelectionData(object):
class SelectionData():
    def __init__(self, history_tb, history_df, currentPrice):

        # rsi_period = 3
        self.rsi_atEntry = None
        self.atr10_atEntry = None
        self.atr10_atEntry_Percent = 0
        self.atrIsReady = False
        self.atr10 = AverageTrueRange(10, movingAverageType=MovingAverageType.Exponential)
        self.orderTickets = []
        self.tradingDaysSinceEntry = 0

        # #4. Loop over the history data and update the ATR, save current value
        # for trade_bar in history_tb:
        #     self.atr10.Update(trade_bar)
        #     if self.atr_is_ready():
        #         self.atr10_atEntry = self.atr10.Current.Value
        #         self.atr10_atEntry_Percent = (100 * self.atr10_atEntry) / currentPrice

        # # calculate average volume
        # if history_tb is not None:
        #     self.volumeDollar10DayAverage = self.average_volume(history_tb, currentPrice)


        # Calculate RSI 3 (TradingView-edition) and eacDayHigher
        # if history_df is not None:
        #     self.rsi_atEntry = self.rsi_TV(history_df, 3)
        #     self.eachDayHigher = self.each_day_higher(history_df)

        # calculate percent change in price previous days
        # if history_df is not None:
        #     self.percentUpPrev3Days = self.percent_up_prev_days(history_df, 3)
          

    def atr_10(self, history_tb, currentPrice):
        if currentPrice == 0:
            return 0
        if history_tb is not None:
            for trade_bar in history_tb:
                self.atr10.Update(trade_bar)
                self.atrIsReady = self.atr10.IsReady
                if self.atrIsReady:
                    self.atr10_atEntry = self.atr10.Current.Value
                    self.atr10_atEntry_Percent = (100 * self.atr10_atEntry) / currentPrice
            
            return self.atr10_atEntry_Percent
        return 0

    def average_volume(self, history_tb, currentPrice):
        if history_tb is not None:
            volume = 0
            period = 0
            for bar in history_tb:
                volume = volume + bar.Volume
                period += 1
            if period > 0:
                self.volumeDollar10DayAverage = ((currentPrice * volume) / (period)) / 1000000
                return self.volumeDollar10DayAverage
            else:
                return 0
        return 0


    def each_day_higher(self, history_df):
        if history_df is not None:
            close = history_df["close"]
            if len(close) > 5:
                if close[-1] > close[-2] > close[-3]:  # > close[-4] > close[-5]:
                    self.eachDayHigher = True
                    return self.eachDayHigher
                else:
                    self.eachDayHigher = False
                    return self.eachDayHigher
            else:
                return None
        return None

    def percent_up_prev_days(self, history_df, period):
        if history_df is not None:
            close = history_df["close"]
            if len(close) >= period:
                lookBack = 0 - period - 1
                self.percentUpPrevDays = (100 * (close[-1] - close[lookBack])) / close[lookBack]
                return self.percentUpPrevDays

        return 0


    def rsi_TV(self, history_df, rsi_period):
        if history_df is not None:
            delta = history_df["close"].diff()

            up = delta.copy()
            up[up < 0] = 0
            up = pd.Series.ewm(up, alpha=1/rsi_period).mean()

            down = delta.copy()
            down[down > 0] = 0
            down *= -1
            down = pd.Series.ewm(down, alpha=1/rsi_period).mean()

            rsi = np.where(up == 0, 0, np.where(down == 0, 100, 100 - (100 / (1 + up / down))))
            self.rsi_atEntry = rsi[-1]

            return self.rsi_atEntry
        return None

    def tradingdays_since_entry(self):
        self.tradingDaysSinceEntry += 1
        # return self.tradingDays


    def order_date_submitted(self, submittedDate):
        self.submittedDate = submittedDate

    def entry_date(self, entryDate):
        self.entryDate = entryDate

    def exit_date(self, exitDate):
        self.exitDate = exitDate

    def max_profit(self, maxProfit):
        self.maxProfit = maxProfit  

    def entry_price(self, entryPrice):
        self.entryPrice = entryPrice

    def exit_price(self, exitPrice):
        self.exitPrice = exitPrice 

    def order_ticket(self, orderTicket):
        self.orderTickets.append(orderTicket)
        
    def entry_quantity(self, entryQuantity):
        self.entryQuantity = entryQuantity

    def profit_since_entry(self, entryPrice, currentPrice):
        self.profitSoFar = (-100 * (currentPrice - entryPrice)) / entryPrice
        return self.profitSoFar