| Overall Statistics |
|
Total Trades 601 Average Win 0.14% Average Loss -0.10% Compounding Annual Return -0.732% Drawdown 4.700% Expectancy -0.029 Net Profit -0.370% Sharpe Ratio -0.026 Probabilistic Sharpe Ratio 19.178% Loss Rate 60% Win Rate 40% Profit-Loss Ratio 1.42 Alpha -0.039 Beta 0.275 Annual Standard Deviation 0.079 Annual Variance 0.006 Information Ratio -1.363 Tracking Error 0.1 Treynor Ratio -0.007 Total Fees $3010.55 Estimated Strategy Capacity $1000.00 Lowest Capacity Asset FSEA X68C68WYRWPX |
from AlgorithmImports import *
class CryingFluorescentYellowHamster(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2021, 6, 1) # Set Start Date
self.SetCash(100000) # Set Strategy Cash
self.SetSecurityInitializer(
lambda x: x.SetDataNormalizationMode(DataNormalizationMode.SplitAdjusted) \
and x.SetLeverage(1) and x.SetFillModel(CustomFillModel(self)))
self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.SplitAdjusted
self.UniverseSettings.Resolution = Resolution.Daily
self.UniverseSettings.Leverage = 1
self.AddUniverseSelection(FineFundamentalUniverseSelectionModel(self.SelectCoarse, self.SelectFine))
''' Universe Metadata '''
self.coarseMinPrice = 4
self.coarseMaxPrice = 10
self.coarseMaxSymbols = 30
'''Init'''
self.uniData = {}
self.activeStocks = {}
self.maxPeriod = 250 # max history lookback
def SelectCoarse(self, coarse):
for c in coarse:
if c.Symbol not in self.uniData:
self.uniData[c.Symbol] = UniverseData(c.Symbol, c.HasFundamentalData)
# c.Volume/c.SplitFactor - Adjust volume for splits
self.uniData[c.Symbol].update(c.Price, c.Volume / c.SplitFactor)
# Filter the values of the dict
values = [x for x in self.uniData.values() if x.Price >= self.coarseMinPrice
and x.hasFundamental
and x.Price <= self.coarseMaxPrice]
# sort by the largest in volume.
values = sorted(values, key=lambda x: x.Price, reverse=True)[:self.coarseMaxSymbols]
# we need to return only the symbol objects
return [x.Symbol for x in values]
def SelectFine(self, fine):
fineSymbols = [x for x in fine if x.CompanyReference.PrimaryExchangeID in ["NYS", "NAS", "ASE"]]
# we need to return only the symbol objects
return [x.Symbol for x in fineSymbols]
def OnSecuritiesChanged(self, changes):
self.changes = changes
# load history for new symbols in uni
for security in self.changes.AddedSecurities:
if not security.Symbol.Value in self.activeStocks:
self.LoadHistory(security)
for security in self.changes.RemovedSecurities:
if security.Symbol.Value in self.activeStocks and not security.Invested:
del self.activeStocks[security.Symbol.Value]
def LoadHistory(self, security):
sd = SymbolData(security)
sd.avgVol = self.SMA(security.Symbol, 30, Resolution.Daily, Field.Volume)
sd.close = self.Identity(security.Symbol, Resolution.Daily, Field.Close)
sd.close.Updated += sd.CloseUpdated
sd.closeWindow = RollingWindow[IndicatorDataPoint](self.maxPeriod)
sd.emaTwentyOne = self.EMA(security.Symbol, 21, Resolution.Daily, Field.Close)
sd.emaTwentyOne.Updated += sd.EmaTwentyOneUpdated
sd.emaTwentyOneWindow = RollingWindow[IndicatorDataPoint](self.maxPeriod)
# warmup our indicators by pushing history through the indicators
# rolling window must use warmup !IMPORTANT
history = self.History(security.Symbol, self.maxPeriod, Resolution.Daily)
if (history.empty or history.size < self.maxPeriod):
return
for index, row in history.loc[security.Symbol].iterrows():
if index == self.Time:
continue
sd.avgVol.Update(index, row['volume'])
sd.close.Update(index, row['close'])
sd.emaTwentyOne.Update(index, row['close'])
# add sd to active dict with all history
self.activeStocks[security.Symbol.Value] = sd
def IsReady(self, sd):
if (sd.closeWindow.Count < self.maxPeriod or
sd.emaTwentyOneWindow.Count < self.maxPeriod):
return False
if not (sd.closeWindow.IsReady and
sd.emaTwentyOneWindow.IsReady):
return False
return True
def OnData(self, data):
'''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
Arguments:
data: Slice object keyed by symbol containing the stock data
'''
# sort by avg volume
self.activeStocks = dict(
sorted(self.activeStocks.items(), key=lambda item: item[1].avgVol.Current.Value, reverse=True))
for key, sd in self.activeStocks.items():
if sd.Security.Fundamentals is None:
continue
if not (data.ContainsKey(sd.Symbol) and data.Bars.ContainsKey(sd.Symbol)):
continue
if not self.IsReady(sd):
continue
if not sd.Security.Invested:
if sd.closeWindow[0].Value < sd.emaTwentyOneWindow[0].Value:
continue
self.SetHoldings(sd.Symbol, 0.1)
elif sd.Security.Invested and sd.closeWindow[0].Value > sd.emaTwentyOneWindow[0].Value:
self.Liquidate(sd.Symbol)
class CustomFillModel(FillModel):
'''
Implements a custom fill model that inherit from FillModel.
Override the MarketFill method to simulate partially fill orders
'''
def __init__(self, algorithm, sd=None):
self.algorithm = algorithm
self.absoluteRemainingByOrderId = {}
self.sd = sd
def MarketFill(self, asset, order):
absoluteRemaining = order.AbsoluteQuantity
if order.Id in self.absoluteRemainingByOrderId.keys():
absoluteRemaining = self.absoluteRemainingByOrderId[order.Id]
fill = super().MarketFill(asset, order)
absoluteFillQuantity = int(min(absoluteRemaining, order.AbsoluteQuantity))
fill.FillQuantity = np.sign(order.Quantity) * absoluteFillQuantity
if absoluteRemaining == absoluteFillQuantity:
fill.Status = OrderStatus.Filled
if self.absoluteRemainingByOrderId.get(order.Id):
self.absoluteRemainingByOrderId.pop(order.Id)
else:
absoluteRemaining = absoluteRemaining - absoluteFillQuantity
self.absoluteRemainingByOrderId[order.Id] = absoluteRemaining
fill.Status = OrderStatus.PartiallyFilled
self.algorithm.Debug(f"CustomFillModel: {fill}")
return fill
class UniverseData:
def __init__(self, symbol, fundamental):
self.Symbol = symbol
self.Volume = 0
self.Price = 0
self.hasFundamental = fundamental
def update(self, price, volume):
self.Volume = volume
self.Price = price
class SymbolData:
def __init__(self, security):
self.Security = security
self.Symbol = security.Symbol
self.avgVol = 0
''' Candle '''
self.close = None
self.closeWindow = None
''' Moving Averages '''
self.emaTwentyOne = None
self.emaTwentyOneWindow = None
def CloseUpdated(self, sender, updated):
self.closeWindow.Add(updated)
def EmaTwentyOneUpdated(self, sender, updated):
self.emaTwentyOneWindow.Add(updated)