import pandas as pd
import math
class Coin(QCAlgorithm):
def __init__(self):
# Global variables
self.initialized = False
# symbol data
self.ovd = {} # overvalued data
self.uvd = {} # undervalued data
self.ovs = [] # holds overvalued symbols
self.uvs = [] # holds undervalued symbols
# Coarse Filter
self.top = 450 # Only take the top equities from coarse raw
# Fine filter
self.hitop = 25 # Only take top hitop equities from overvalued
self.lotop = 25 # Only take top lotop equities from undervalued
self.himax = 0.99 # Upper limit for overvalued percentile
self.himin = 0.70 # lower limit for overvalued percentile
self.lomax = 0.30 # upper limit for undervalued percentile
self.lomin = 0.00 # lower limit for undervalued percentile
self.ratios = ["per", "pbr", "psr", "pgr", "tdr", "evr"] # filter through these ratios
def Initialize(self):
self.SetStartDate(2003, 1, 1)
self.SetEndDate(2003, 1, 7)
#self.SetEndDate(datetime.now().date() - timedelta(1))
self.SetCash(20000)
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.coarseFilter, self.fineFilter)
def initData(self):
aes = self.ovs + self.uvs
for symbol in aes:
if (symbol in self.ovs):
self.ovd[symbol] = UniverseSymbolData(
symbol, self.SubscriptionManager, self.RegisterIndicator)
else:
self.uvd[symbol] = UniverseSymbolData(
symbol, self.SubscriptionManager, self.RegisterIndicator)
self.SetWarmup(timedelta(1))
self.UniverseSettings.Resolution = Resolution.Minute
def coarseFilter(self, coarse):
raw = {} # raw data holding all stocks
for eq in coarse: # iterate through equitties
if (eq.HasFundamentalData): # check for fundamental data
raw[eq] = CoarseSymbolData(eq.Symbol, self.Debug) # raw: key=eq, value=CoarseSymbolData
candidate = raw[eq] # candidate = get eq in raw
candidate.update(eq.Volume, eq.DollarVolume, # give candidate eq data
eq.AdjustedPrice, eq.EndTime, self.History)
selected = list(filter(lambda sd: (sd.selected == True), raw.values())) # get only desired eq
selected.sort(key=lambda sd: sd.dvolume, reverse=True) # sort descending
if (self.top > len(selected)):
self.top = len(selected)
return [sd.symbol for sd in selected[:self.top]] # return symbols
def fineFilter(self, fine):
raw = {}
for eq in fine: # raw: key=equity, value=FineFilterData(equity)
raw[eq] = FineSymbolData(eq, eq.Symbol)
for ratio in self.ratios: # iterate through ratios
raw = self.getPercentile(ratio, raw) # filter using ratio in list of ratios
selected = list(raw.values())
selected.sort(key=lambda sd: sd.PERatio) # sort by PE ratio
if ((self.hitop + self.lotop) > len(selected)):
self.hitop = math.ceil(len(selected) / 2)
self.lotop = math.floor(len(selected) / 2)
self.ovs = selected[len(selected) - self.hitop:]
self.uvs = selected[:self.lotop]
self.uvs = [sd.symbol for sd in self.uvs]
self.ovs = [sd.symbol for sd in self.ovs]
selected = selected[:self.lotop] + selected[len(selected) - self.hitop:] # take top 25 overvalued and undervalued
#self.avs
return [sd.symbol for sd in selected]
def getPercentile(self, ratio, pool):
eqr = {} # holds symbols mapped to given ratio (in decimal)
rts = {"ratio":[]} # list of ratios (in decimal) of pool
for eq in pool:
eqr[eq] = pool[eq].getRatio(ratio)
rts["ratio"].append(pool[eq].getRatio(ratio))
daf = pd.DataFrame(rts) # dataframe to calculate percentiles below
hmin = daf.quantile(self.himin)[0]
hmax = daf.quantile(self.himax)[0]
lmin = daf.quantile(self.lomin)[0]
lmax = daf.quantile(self.lomax)[0]
for eq in eqr:
if ((eqr[eq] < hmin) | (eqr[eq] > hmax)) & ( # if ratio does not fall in overvalue range
(eqr[eq] < lmin) | (eqr[eq] > lmax)): # and ratio does not fall in undervalue range
pool.pop(eq) # remove it from pool
return pool
def OnData(self, data):
if (self.initialized != True):
self.initialized = True
self.initData()
CloseTimeString = "12:00:00" # Set up pre-market close time
CloseOutTime = datetime.strptime(CloseTimeString, "%H:%M:%S")
CloseTime = datetime.time(CloseOutTime)
OpenTimeString = "10:30:00" # Set up post-market open time
OpenTradesTime = datetime.strptime(OpenTimeString, "%H:%M:%S")
OpenTime = datetime.time(OpenTradesTime)
#long comparisons
CloseOversma = 1.02
vwmaOverema = 1.04
#short comparisons
CloseUndersma = 0.98
vwmaUnderema = 0.96
# undervalued stocks
for symbol in self.uvd: #loop through undervalued
symboldata = self.uvd[symbol]
if not self.Portfolio[symbol].Invested and data.Bars.ContainsKey(symbol): #if we do not own this symbol
# Get symbol trade time
DataSymbolTime = data[symbol].Time
DataTime = datetime.time(DataSymbolTime)
if DataTime < CloseTime: # Check to see if we are near market close
self.Debug(str(symbol) + " is long ready on " + str(DataSymbolTime))
posSize = self.CalculateOrderQuantity(symbol, 0.4) # Set Position Size
self.MarketOrder(symbol, posSize) # Market Order to buy
# overvalued stocks
for symbol in self.ovd: #loop through undervalued
symboldata = self.ovd[symbol]
if not self.Portfolio[symbol].Invested and data.Bars.ContainsKey(symbol): #if we do not own this symbol
# Get symbol trade time
DataSymbolTime = data[symbol].Time
DataTime = datetime.time(DataSymbolTime)
if DataTime < CloseTime: # Check to see if we are near market close
self.Debug(str(symbol) + " is short ready on " + str(DataSymbolTime))
posSize = self.CalculateOrderQuantity(symbol, -0.4) # Set Position Size
self.MarketOrder(symbol, posSize) # Market Order to buy
def OnEndOfDay(self): #triggered before end of day
#Liquidate all symbol
self.Liquidate()
self.Debug("Liquidating at end of day...")
class UniverseSymbolData(object):
def __init__(self, symbol, manager, register):
self.register = register
self.manager = manager
self.symbol = symbol
self.PRD = 10 # period is 10 minutes
# Periods
self.SMAP = 30 # Simple Moving Average periods
self.RSIP = 30 # Relative Strength Index periods
self.ATRP = 30 # Average True Range periods
self.EMAP = 5 # Exponential moving Average periods
self.CLSP = 1 # "close value" simple moving average
self.WMAP = 5 # Volume Weighted Moving Average periods (custom)
self.PVPD = 144 # 1 day Pivot Point (custom)
# indicators
self.sma = SimpleMovingAverage(self.SMAP)
self.rsi = RelativeStrengthIndex(self.RSIP)
self.atr = AverageTrueRange(self.ATRP)
self.ema = ExponentialMovingAverage(self.EMAP)
self.cls = SimpleMovingAverage(self.CLSP)
self.wma = WeightedMovingAverage(self.WMAP) # custom
#self.pvp = PivotPoint(self.PVPD) # custom
# create consolidator and subscribe
PRDConsolidator = TradeBarConsolidator(timedelta(minutes = self.PRD))
self.manager.AddConsolidator(self.symbol, PRDConsolidator)
# register indicators
self.register(self.symbol, self.sma, PRDConsolidator)
self.register(self.symbol, self.rsi, PRDConsolidator)
self.register(self.symbol, self.atr, PRDConsolidator)
self.register(self.symbol, self.ema, PRDConsolidator)
self.register(self.symbol, self.cls, PRDConsolidator)
self.register(self.symbol, self.wma, PRDConsolidator) #custom
class CoarseSymbolData(object): # this class for coarse filter equities (raw)
def __init__(self, symbol, debug):
self.p = debug
self.symbol = symbol
self.MINDvl = 5000000 # Minimum dollar volume
self.SMVMul = 1 # SMV 30 day multiplier
self.SMAMin = 1 # SMA min price
self.SMAVDS = 30 # simple moving average volume days
self.SMAVD = 1 # simple moving average volume day
self.SMAD = 1 # simple moving average day
self.SMAVA = SimpleMovingAverage(self.SMAVDS) # SMAV alternate indicator
self.SMAV = SimpleMovingAverage(self.SMAVD) # SMAV indicator
self.SMA = SimpleMovingAverage(self.SMAD) # SMA indicator
self.selected = False
self.dvolume = None
self.volume = None
self.price = None
self.time = None
def update(self, volume, dvolume, price, time, history):
self.dvolume = dvolume
self.volume = volume
self.price = price
self.time = time
hst = history(self.symbol, self.SMAVDS, Resolution.Daily)
for t, r in hst.iterrows():
self.SMAVA.Update(t[1], r["volume"])
self.checkReqs()
def checkReqs(self):
if ((self.SMA.Update(self.time, self.price)) & # check if indicators ready
(self.SMAV.Update(self.time, self.volume)) &
(self.SMAVA.IsReady)):
smava = self.SMAVA.Current.Value
smav = self.SMAV.Current.Value
sma = self.SMA.Current.Value
if ((sma >= self.SMAMin) & # sma has to be bigger than 10
(smav >= (self.SMVMul * smava)) & # 1 day smav >= 1.5 * 30 day smav
(self.dvolume > self.MINDvl)): # min 1 mill dollar volume
self.selected = True
class FineSymbolData(object):
def __init__(self, eq, symbol):
self.eq = eq
self.symbol = symbol
self.PERatio = self.eq.ValuationRatios.PERatio
self.PBRatio = self.eq.ValuationRatios.PBRatio
self.PSRatio = self.eq.ValuationRatios.PSRatio
self.PGRatio = self.eq.ValuationRatios.PEGRatio
self.TDRatio = self.eq.OperationRatios.TotalDebtEquityRatio.OneMonth
self.EVRatio = self.eq.ValuationRatios.EVToEBITDA1YearGrowth
def getRatio(self, ratio):
if (ratio == "per"):
return self.PERatio
elif (ratio == "pbr"):
return self.PBRatio
elif (ratio == "psr"):
return self.PSRatio
elif (ratio == "pgr"):
return self.PGRatio
elif (ratio == "tdr"):
return self.TDRatio
elif (ratio == "evr"):
return self.EVRatio
class WeightedMovingAverage:
def __init__(self, period):
self.lastdate = datetime.min
self.IsReady = False
self.period = period
self.pcount = 0
self.ptvsum = 0.0 # sum of price * volume
self.Value = 0.0 # indicator value
self.vsum = 0.0 # sum of volume
def IsReady(self):
if (self.pcount == self.period):
self.Value = (self.ptvsum / vsum)
self.pcount = 0
self.ptvsum = 0.0
self.vsum = 0.0
return True
return False
def Update(self, input): # input is a TradeBar object
success, volume, close = self.getData(input)
if not success:
return
self.pcount += 1
self.ptvsum += close * volume
self.vsum += volume
def getData(self, input):
if not input.IsFillForward:
return True, float(input.Volume), float(input.Close)
return False, 0.0, 0.0
#---------------------------------------------- END ALGORITHM ------------------------------------#