Overall Statistics
Total Trades
349
Average Win
1.67%
Average Loss
-3.23%
Compounding Annual Return
12.986%
Drawdown
43.200%
Expectancy
0.081
Net Profit
43.562%
Sharpe Ratio
0.471
Probabilistic Sharpe Ratio
13.825%
Loss Rate
29%
Win Rate
71%
Profit-Loss Ratio
0.52
Alpha
0.155
Beta
-0.099
Annual Standard Deviation
0.314
Annual Variance
0.098
Information Ratio
0.183
Tracking Error
0.388
Treynor Ratio
-1.498
Total Fees
$2437.36
Estimated Strategy Capacity
$9700000.00
Lowest Capacity Asset
CJJDD ULZ5AW2MP4DH
# region imports
from AlgorithmImports import *
import numpy as np

# endregion

class HyperActiveGreenFish(QCAlgorithm):

    def Initialize(self):

        qb = self 

        qb.SetStartDate(2020,1,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 = Resolution.Daily
        self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.SplitAdjusted
        self.AddUniverse(self.CoarseFilter)
        #, self.FineFilter)

        # qb.SetWarmup(5, self.resolution)   # days = daysSMA


    def CoarseFilter(self, coarse):

        self.coarseListReturn = []
        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:
                #3. Create a new instance of SelectionData and save to averages[symbol]
                # 1. Call history to get tradebars
                history_tb = self.History[TradeBar](symbol, 10, Resolution.Daily)
                
                self.universe[symbol] = SelectionData(history_tb, None, c.Price)
                stock = self.universe[symbol]
        
      
                if stock.volumeDollar10DayAverage > 8 :
                    #5. Check ATR and closingprices, and if ok append the symbol to selected list.
                    if stock.atr_is_ready() and stock.atr10_atEntry_Percent > 2.5:
                        history_df = self.History(symbol, 100, Resolution.Daily)
                        if 'close' in history_df.columns and not history_df.isnull().values.any():  #history_df.index[-1] == self.Time
                            self.universe[symbol] = SelectionData(history_tb, history_df, c.Price)
                            stock = self.universe[symbol]
                            # check if price increased last 2 days
                            if stock.eachDayHigher:
                                # check percent change prev 3 days
                                # if stock.percentUpPrev3Days > 6:
                                # check RSI
                                if stock.rsi_atEntry > 92:
                                    self.coarseListReturn.append(symbol)  

        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%')
        return self.coarseListReturn[:10]
    

    def OnSecuritiesChanged(self, changes):
        for x in changes.RemovedSecurities:
            if x.Symbol in self.activeStocks: 
                self.activeStocks.remove(x.Symbol)

        for x in changes.AddedSecurities:
            if x.Symbol not in self.activeStocks:
                self.activeStocks.add(x.Symbol)


    def OnData(self, data):

        # return if warming up:   
        if self.IsWarmingUp:
            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)
    
       
        for symbol in self.activeStocks:
            if symbol not in data:
                return

 
        # checking how many positions I already chave
        invested = [x.Key for x in self.Portfolio if x.Value.Invested]
        activePositions = len(invested)

        
         # calculating positionsize:          
        self.free_positions = 10 - activePositions

        if self.free_positions == 0:
            cashPerPosition = 0
        else:
            cashPerPosition = 0.92*(self.Portfolio.MarginRemaining / self.free_positions)

        # GOING SHORT

        for symbol in self.activeStocks:
            stock = self.universe[symbol]
            # getting current price
            symbol.price = self.Securities[symbol].Price

            if not self.Portfolio[symbol].IsShort and self.free_positions > 0:

               # calculate number of stocks to buy
                amountToBuy = cashPerPosition / symbol.price

                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('BuyOrder: %s Cost: %f' % (symbol.Value, cashPerPosition))      

        # 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
                currentPrice = self.Securities[symbol].Price

                # COVER:
                daysSinceEntry = (self.Time - stock.entryDate).days
                profitSinceEntry = 100 * self.Portfolio[symbol].UnrealizedProfitPercent
                profitSoFar = stock.profit_since_entry(stock.entryPrice, currentPrice)
            
           
                if stock.tradingDaysSinceEntry >= 6:
                    # Update the order tag
                    # updateSettings = UpdateOrderFields()
                    # tag = 'Days-Exit. Profit:{:.2f}, Days since entry: {:.2f} '.format(profitSinceEntry, daysSinceEntry)
                    # response = ticket.UpdateTag(updateSettings)
                    self.Liquidate(symbol)
                    self.Log('6-Days-sell')

                # exit on profit 10%
                elif profitSinceEntry > 10:
                    # Update the order tag
                    # updateSettings = UpdateOrderFields()
                    # tag = 'Profit-Exit. Profit: %f , Days since entry: %f ' % (profitSinceEntry, daysSinceEntry)
                    # response = ticket.UpdateTag(tag)
                    self.Liquidate(symbol)
                    self.Log('Profit-sell above 10%')

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


        # END of DAY 


    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.free_positions += 1

        elif orderEvent.Status == OrderStatus.Canceled:
            pass
            


# 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 = 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_is_ready(self):
        return self.atr10.IsReady

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


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

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


    def rsi_TV(self, history_df, rsi_period):
    
        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))))
        rsi = rsi[-1]

        return rsi

    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