Overall Statistics
Total Trades
163
Average Win
1.38%
Average Loss
-0.87%
Compounding Annual Return
-3.041%
Drawdown
14.200%
Expectancy
-0.011
Net Profit
-1.944%
Sharpe Ratio
0.004
Probabilistic Sharpe Ratio
19.205%
Loss Rate
62%
Win Rate
38%
Profit-Loss Ratio
1.58
Alpha
0.018
Beta
-0.064
Annual Standard Deviation
0.239
Annual Variance
0.057
Information Ratio
-0.996
Tracking Error
0.269
Treynor Ratio
-0.014
Total Fees
$1086.41
Estimated Strategy Capacity
$68000.00
Lowest Capacity Asset
SEED TDJ3UYNCD4KL
import pandas as pd
import numpy as np
from io import StringIO
import json
import pickle
from datetime import datetime
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel

'''
Would it be beneficial to run a universe selection every day based on which stocks 
have a trading halt? Yes, it increases spead 10 fold
'''

class TradingHalt(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2021, 1, 1)
        self.SetEndDate(2021, 8, 21)
        self.SetCash(100000)
        csv = self.Download("https://www.dropbox.com/s/nsirge1gachgjg2/HaltData.csv?dl=1")
        self.HaltData = pd.read_csv(StringIO(csv))
        
        if self.ObjectStore.ContainsKey("TradingHaltObject"):
            self.ObjectStore.Delete("TradingHaltObject")
        
        result = self.HaltData.to_json(orient="index")
        parsed = json.loads(result)
        dump = json.dumps(parsed)#, indent=4)  
        
        # dump = json.dumps(self.dict)
        
        self.ObjectStore.Save("TradingHaltObject", dump)
        
        serializedModel = bytes( self.ObjectStore.ReadBytes("HaltModel") )
        self.haltModel = pickle.loads(serializedModel)

        self.priorityTypes = ["T1", "T2", "T5", "T6", "LUDP", "LUDS"] #, "MWC1", "MWC2", "MWC3", "MWCO"]
        self.ConvertTimes()
        
        self.activeSymbols = {}
        self.symbolDataBySymbol = {}
        self.tickersT = []
        self.tickers = []
        
        self.Resolution = Resolution.Hour
        self.UniverseSettings.Resolution = self.Resolution
        self.SetUniverseSelection(FineFundamentalUniverseSelectionModel(self.CoarseSelectionFunction, self.FineSelectionFunction))
        
        self.spy = self.AddEquity("SPY", self.Resolution).Symbol
                 
        self.Schedule.On(self.DateRules.EveryDay("SPY"),
                 self.TimeRules.BeforeMarketClose("SPY", 60),
                 self.SellAll)
                 
        self.todaysHalts = pd.DataFrame()
        
        self.prepareTrading = []
        self.maxPositions = 10
        
    def TakeProfit(self):
        activePositions = [x.Key for x in self.Portfolio if x.Value.Invested]
        for symbol in activePositions:
            if self.Portfolio[symbol].UnrealizedProfitPercent >= 20:
                self.Liquidate(symbol)
        
    def OnData(self, data):
        # self.TakeProfit()
        if len(self.todaysHalts.index) == 0:
            return
        for tuple in self.todaysHalts.itertuples():
            if tuple.Tickers not in list(self.activeSymbols.keys()):
                continue
            
            if tuple.Tickers == "UNAM" or tuple.Tickers == "AGBAU":
                continue
            
            symbol = self.activeSymbols[tuple.Tickers]
            curHaltDate = str(tuple.StringHaltDate)
            curHaltTime = str(tuple.HaltTime)
            curResumeDate = str(tuple.StringResumedDate)
            curResumeTime = str(tuple.ResumedTime)
            
            try:
                curHalt = curHaltDate + " " + curHaltTime
                curHaltTime = datetime.strptime(curHalt, '%m/%d/%Y %H:%M:%S')
                curResume = curResumeDate + " " + curResumeTime
                curResumeTime = datetime.strptime(curResume, '%m/%d/%Y %H:%M:%S')
            except:
                continue
            haltDelta = self.Time - curHaltTime
            resumeDelta = self.Time - curResumeTime
            if haltDelta.total_seconds() >= 0 and resumeDelta.total_seconds() < 0 and tuple.HaltType in self.priorityTypes:# and data.ContainsKey(self.activeSymbols[tuple.Tickers]):
                self.HaltOccuring(symbol)
                self.symbolDataBySymbol[symbol].gatheredData = True
                
            elif resumeDelta.total_seconds() >= (60*60)*2 and self.symbolDataBySymbol[symbol].gatheredData == True and tuple.HaltType in self.priorityTypes and not self.Portfolio[self.activeSymbols[tuple.Tickers]].Invested:# and data.ContainsKey(self.activeSymbols[tuple.Tickers]):
                self.AfterHaltTrades(symbol, resumeDelta)
                
    def HaltOccuring(self, symbol):
        history = self.History(symbol, 20, Resolution.Hour)
        mompClose = MomentumPercent(20)
        standardDeviation = history.loc[symbol]["close"].std()
        for historyTuple in history.loc[symbol].itertuples():
            mompClose.Update(historyTuple.Index, historyTuple.close)
        self.symbolDataBySymbol[symbol].beforeHaltPriceMOMP = mompClose.Current.Value
        self.symbolDataBySymbol[symbol].beforeHaltSD = standardDeviation

    def AfterHaltTrades(self, symbol, delta):
        history = self.History(symbol, 3, Resolution.Hour)
        closes = history.loc[symbol]["close"]
        self.symbolDataBySymbol[symbol].afterHaltPriceMOMP = (closes[-1] - closes[-3]) / closes[-3]
        if self.symbolDataBySymbol[symbol].beforeHaltPriceMOMP == None:
            return
        if (self.symbolDataBySymbol[symbol].beforeHaltPriceMOMP >= 0 and self.symbolDataBySymbol[symbol].afterHaltPriceMOMP) >= 0 or (self.symbolDataBySymbol[symbol].beforeHaltPriceMOMP < 0 and self.symbolDataBySymbol[symbol].afterHaltPriceMOMP < 0):
            self.symbolDataBySymbol[symbol].earlyIndicator = True
        else:
            self.symbolDataBySymbol[symbol].earlyIndicator = False
            
        direction = -1
        if self.symbolDataBySymbol[symbol].beforeHaltPriceMOMP >= 0:
            direction = 1
        
        tempDict = {}
        tempDict[symbol] = [self.symbolDataBySymbol[symbol].beforeHaltSD, self.symbolDataBySymbol[symbol].earlyIndicator]
        testDf = pd.DataFrame.from_dict(tempDict)

        prediction = self.haltModel.predict(testDf.T)
        positionSize = (1)/len(self.activeSymbols.keys())
        if prediction == 1:
            self.SetHoldings(symbol, positionSize)
        else:
            self.SetHoldings(symbol, -1/positionSize)

    def ConvertTimes(self):
        self.HaltData["StringResumedDate"] = self.HaltData["ResumedDate"]
        self.HaltData["ResumedDate"] = [datetime.strptime(x, '%m/%d/%Y') for x in self.HaltData["ResumedDate"]]
        self.HaltData["StringHaltDate"] = self.HaltData["HaltDate"]
        self.HaltData["HaltDate"] = [datetime.strptime(x, '%m/%d/%Y') for x in self.HaltData["HaltDate"]]
        
    def CurrentHalts(self):
        self.todaysHalts = self.HaltData.loc[self.HaltData['HaltDate'] == self.Time.date()]
        
    def SellAll(self):
        self.Liquidate()
        
    def CoarseSelectionFunction(self, coarse):
        self.CurrentHalts()
        tickers = []
        symbols = []
        for tuple in self.todaysHalts.itertuples():
            tickers.append(tuple.Tickers)
        
        for x in coarse:
            symbolTicker = x.Symbol.Value
            if symbolTicker in tickers:
                symbols.append(x.Symbol)
        
        return symbols
    
    def FineSelectionFunction(self, fine):
        return [x.Symbol for x in fine]
        
    def OnSecuritiesChanged(self, changes):
        for x in changes.AddedSecurities:
            symbol = x.Symbol
            self.Securities[symbol].SetSlippageModel(CustomSlippageModel(self))
            self.Securities[symbol].SetDataNormalizationMode(DataNormalizationMode.Raw)
            self.activeSymbols[symbol.Value] = symbol
            symbolData = SymbolData(symbol)
            self.symbolDataBySymbol[symbol] = symbolData
            
        for x in changes.RemovedSecurities:
            symbol = x.Symbol
            self.Liquidate(symbol)
            self.activeSymbols.pop(symbol.Value, None)
            if symbol in self.symbolDataBySymbol:
                self.symbolDataBySymbol.pop(symbol, None)
            
class CustomSlippageModel:
    def __init__(self, algorithm):
        self.algorithm = algorithm

    def GetSlippageApproximation(self, asset, order):
        slippage = asset.Price * float(0.0001 * np.log10(2*float(order.AbsoluteQuantity)))
        return slippage
        
class SymbolData:
    def __init__(self, symbol):
        self.Symbol = symbol
        self.gatheredData = False
        self.beforeHaltPriceMOMP = None
        self.afterHaltPriceMOMP = None
        self.beforeHaltSD = None
        self.earlyIndicator = None