Overall Statistics
Total Trades
1375
Average Win
0.84%
Average Loss
-1.14%
Compounding Annual Return
-93.402%
Drawdown
95.000%
Expectancy
-0.342
Net Profit
-93.451%
Sharpe Ratio
-1.79
Probabilistic Sharpe Ratio
0.000%
Loss Rate
62%
Win Rate
38%
Profit-Loss Ratio
0.74
Alpha
0
Beta
0
Annual Standard Deviation
0.491
Annual Variance
0.241
Information Ratio
-1.79
Tracking Error
0.491
Treynor Ratio
0
Total Fees
$1482.93
Estimated Strategy Capacity
$110000.00
from Execution.ImmediateExecutionModel import ImmediateExecutionModel

from datetime import time

from collections import defaultdict

import numpy as np

from io import StringIO

import pandas as pd

import talib

class MicroShort(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2020, 1, 1)  # Set Start Date
        self.SetEndDate(2020, 12, 31)  # Set Start Date
        # self.SetEndDate(2019, 1, 1)  # Set Start Date
        
        #self.SetStartDate(2014, 1, 1)  # Set Start Date
        #self.SetEndDate(2014, 10, 1)  # Set Start Date
        
        self.SetCash(3000)  # Set Strategy Cash
        self.SetExecution(ImmediateExecutionModel())
        
        self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
        
        self.UniverseSettings.Resolution = Resolution.Minute
        
        self.UniverseSettings.ExtendedMarketHours = True
        
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        
       # self.SetSecurityInitializer(self.CustomSecurityInitializer)
        
        self.AddEquity("SPY")

        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(12, 0), self.CoverShorts)
        # self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(9, 35), self.CancelOrders)
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(9, 20), self.FireOrders)
        self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday),self.TimeRules.At(9, 35),self.RebalanceVTI)
        
        self.openOrders = []
        self.stopOrders = []
        
        self.numberOfGappers = 20
        
        self.numberOfStocks = 5
        
        self.numberOfSymbolsCoarse = 100
    
    def OnData(self, data):
        pass
    
    def CustomSecurityInitializer(self, security):
        security.SetSlippageModel(ConstantSlippageModel(0.000))
        
    def RebalanceVTI(self):
        self.Log('Rebalancing SPY')
        self.SetHoldings('SPY', 0.4)

    def FireOrders(self):
        
        self.pumpedStocks = {}
        NightChange = dict()
        
        # Gappers = dict()
        
        VolumeGappers = dict()
        
        self.openOrders = []
        self.stopOrders = []
        
        # Find Gappers
        for security in self.ActiveSecurities.Values:
            
            if 'SPY' in str(security.Symbol):
                continue
            
            if security.HasData:
                    closehist = self.History(security.Symbol, 2, Resolution.Daily)
                            
                    if str(security.Symbol) not in closehist.index or closehist.empty:
                        #algorithm.Log(f'{ticker} not found in history data frame.')
                        continue
            
                    closebars = np.asarray(closehist['close'])
                    
                    if self.LiveMode:
                        closeyest = closebars[1]
                        # self.Log('On {} comparing with the close of {} from {}'.format(str(self.Time),closeyest,np.asarray(closehist.index.values)[1]))
                    else:
                        closeyest = closebars[0]
                        # self.Log('On {} comparing with the close of {} from {}'.format(str(self.Time),closeyest,np.asarray(closehist.index.values)[0]))
                        
                    todayopen = security.Open
                    
                    sigmahist = self.History(security.Symbol, 100, Resolution.Daily)
                    sigmahist_pct = sigmahist.pct_change()
                    
                    try:
                         sigmahist_close = np.asarray(sigmahist['close'])
                         sigmahist_pct_close = np.asarray(sigmahist_pct['close'])
                    except KeyError:
                         continue
                     
                    ema = talib.EMA(sigmahist_close,timeperiod=20)[-1]
                    gap = todayopen/ closeyest - 1
                    
                    if todayopen < ema:
                        # self.Debug('Above EMA')
                        continue
                    
                    sigmahist_pct_close = sigmahist_pct_close - np.nanmean(sigmahist_pct_close)
                    sigma = np.nanstd(sigmahist_pct_close)
                    
                    
                    if gap/sigma < 2:
                        continue
                    
                    NightChange[security.Symbol] = gap
        
        Gappers = dict(sorted(NightChange.items(), key = lambda kv: (-round(kv[1], 6), kv[0]))[:self.numberOfGappers])
        
        self.Log('Scanning for Gappers ...')
        
        # Rank after Pre-Market Volume
        for s,g in Gappers.items():
            
            # self.Debug('Found {} up {:.2f}%'.format(s,g*100))
            
            premarketh = self.History(s, 300, Resolution.Minute)
            
            if premarketh.empty:
                # self.Debug('PM empty')
                continue
            
            # totalsum = premarketh.sum()
            
            # if totalsum['volume'] > 1e5:
            #     VolumeGappers[s] = totalsum['volume'] 
            
            try:
                dollarvol = (premarketh.close*premarketh.volume).sum()
            except AttributeError:
                print('Attribute Error')
                continue
            
            # if dollarvol > 1e4:
            VolumeGappers[s] = dollarvol
        
        StocksToTrade = dict(sorted(VolumeGappers.items(), key = lambda kv: (-round(kv[1], 6), kv[0]))[0:self.numberOfStocks])
    
        self.Log('Scanning for Pre-Market-Volume...')
        
        for key,value in StocksToTrade.items():

            # self.Debug('SELL')
            
            sec = self.ActiveSecurities[key]
            
            #Due to Margin requirements, take only half
            available = self.Portfolio.TotalPortfolioValue*0.5
            
            #Short Sell out of the gates
            shares = round(available/len(StocksToTrade)/sec.Price)
        
            self.Log('Shorting {} shares of {} at {:.2f} = {:.2f} with {:.2e} pre-market DollarVolume'.format(shares,str(key),sec.Price,shares*sec.Price,float(value)))
                              
            oshort = self.MarketOrder(key,-shares, asynchronous = True)
            # oshort = self.MarketOrder(key, -shares, asynchronous = True)
            self.openOrders.append(oshort)

    def OnOrderEvent(self, fill):
        # Short Order Went Through, Issue a Stop Loss
        if (fill.Status == OrderStatus.Filled or fill.Status == OrderStatus.PartiallyFilled) and fill.FillQuantity < 0 and not 'SPY' in str(fill.Symbol):
            stopo = self.StopMarketOrder(fill.Symbol, abs(fill.FillQuantity), round(fill.FillPrice*1.2,2))
            self.Log('Setting Stop Loss for {} with Stop {}'.format(fill.Symbol,round(fill.FillPrice*1.2,2)))
            self.stopOrders.append(stopo)
        
        if (fill.Status == OrderStatus.Canceled):
            self.Log('Order cancelled')
    
    # def CancelOrders(self):
    #     for o in self.openOrders:
    #         if o.Status == OrderStatus.PartiallyFilled or o.Status == OrderStatus.Submitted:
    #             o.Cancel('Short Order for {} could not be filled completely till 9:32, cancelling'.format(o.Symbol))
    #             self.openOrders.remove(o)
    
    def CoverShorts(self):
        invested = [ x.Symbol for x in self.Portfolio.Values if x.Invested ]
        
        for s in invested:
            if not str(s) == 'SPY':
                self.Liquidate(s)
            
    def CoarseSelectionFunction(self, coarse):
        
        filtered = [x for x in coarse if x.HasFundamentalData
                                      and  5000000 > x.DollarVolume > 1000000
                                      and  5 > x.Price > 0.1]

        # sort the stocks by dollar volume and take the top 500
        top = sorted(filtered, key=lambda x: x.DollarVolume, reverse=True)[:self.numberOfSymbolsCoarse]
        
        self.Log('Selecting Universe coarsely ...')

        self.symbols = [ i.Symbol for i in top ]
        
        return self.symbols
    
    def FineSelectionFunction(self, fine):
        # Tries to avoid Short Squeezes
        
        self.Log('Selecting Universe finely ...')
        
        filtered = [x for x in fine if x.EarningReports.BasicAverageShares.ThreeMonths > 100000]
        return [f.Symbol for f in filtered]