| Overall Statistics |
|
Total Orders 541 Average Win 0.23% Average Loss -0.16% Compounding Annual Return 50.154% Drawdown 13.300% Expectancy 0.265 Start Equity 100000 End Equity 124943.25 Net Profit 24.943% Sharpe Ratio 1.297 Sortino Ratio 1.671 Probabilistic Sharpe Ratio 60.777% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.43 Alpha 0.041 Beta 1.575 Annual Standard Deviation 0.238 Annual Variance 0.057 Information Ratio 0.697 Tracking Error 0.199 Treynor Ratio 0.196 Total Fees $546.98 Estimated Strategy Capacity $8700000.00 Lowest Capacity Asset UPS RPLRE46IQ3XH Portfolio Turnover 3.21% |
# region imports
from AlgorithmImports import *
# endregion
# -------------------------------------------------------------------
SMA = 63;
lookback = 12;
quantile = 0.1;
# -------------------------------------------------------------------
class FocusedBlackDog(QCAlgorithm):
def initialize(self):
self.SetStartDate(2024, 1, 1) # Set Start Date
self.set_security_initializer(lambda security: security.set_margin_model(SecurityMarginModel.NULL))
self.portfolio.set_positions(SecurityPositionGroupModel.NULL);
#self.aapl = self.AddEquity("AAPL", Resolution.Daily)
#self.ibm = self.AddEquity("IBM", Resolution.Daily)
self.spy = self.AddEquity("SPY", Resolution.Daily)
#self.nvda = self.AddEquity("NVDA", Resolution.Daily)
#self.tsla = self.AddEquity("TSLA", Resolution.Daily)
#self.msft = self.AddEquity("MSFT", Resolution.Daily)
#self.amzn = self.AddEquity("AMZN", Resolution.Daily)
#self.goog = self.AddEquity("GOOG", Resolution.Daily)
#self.nflx = self.AddEquity("NFLX", Resolution.Daily)
#self.fb = self.AddEquity("FB", Resolution.Daily)
self.add_universe(self.CoarseSelectionFunction)
self.symbols=[]
self.adv = {}
self.monthly_rebalance=False
self.SymbolDataBySymbol={}
def OnEndOfDay(self):
security_exchange_hours = self.spy.exchange.hours
next_day = security_exchange_hours.get_next_trading_day(self.time)
if next_day.month != self.Time.month:
self.monthly_rebalance = True
return
def CoarseSelectionFunction(self, coarse):
if self.monthly_rebalance:
for sec in coarse:
if sec not in self.adv:
# initiate a new instance of the class
self.adv[sec.Symbol] = AverageDollarVolume(SMA)
# warm up
history = self.History(sec.Symbol, SMA, Resolution.Daily)
for bar in history.iloc[:-1].itertuples(): # leave the last row
self.adv[sec.Symbol].Update(bar.close, bar.volume)
# Update with newest dollar volume
self.adv[sec.Symbol].Update(sec.AdjustedPrice, sec.Volume)
# Sort by Average Dollar Volume
sortedBySMADV = sorted(self.adv.items(), key=lambda x: x[1].Value, reverse=True)
# Return new list of symbols with ADV > $100,000
self.symbols = [x[0] for x in sortedBySMADV if x[1].Value > 100000][:500]
return self.symbols
return self.symbols
def on_securities_changed(self, changes):
added_symbols = [ x.Symbol for x in changes.AddedSecurities ]
self.log("{}: {}".format(self.time, changes))
history = self.History(
added_symbols,
lookback*25,
Resolution.Daily)
if history.empty: return
for symbol in added_symbols:
## Create SymbolData objects for any new assets
SymbolData = MonthlyData(symbol, lookback)
self.SymbolDataBySymbol[symbol] = SymbolData
SymbolData.RegisterIndicators(self)
SymbolData.WarmUpIndicators(history.loc[symbol], self)
self.Log('{}: {}'.format(symbol,self.SymbolDataBySymbol[symbol].Return))
removed_symbols = [ x.Symbol for x in changes.RemovedSecurities ]
for removed in removed_symbols:
## Unsubscribed consolidator
SymbolData = self.SymbolDataBySymbol.pop(removed, None)
if SymbolData is not None:
#SymbolData.RemoveConsolidators(self)
#self.symbolDataBySymbol=self.symbolDataBySymbol.drop(removed,axis=1)
self.liquidate(removed)
def OnData(self,data):
if not self.monthly_rebalance:
return
sortedByMOM = sorted(self.SymbolDataBySymbol.items(), key=lambda x: x[1].Return, reverse=True)
self.top_quantile = sortedByMOM[:int(len(sortedByMOM)*quantile)]
self.bottom_quantile = sortedByMOM[-int(len(sortedByMOM)*quantile):]
to_liquidate = set(sortedByMOM) ^ (set(self.top_quantile) | set(self.bottom_quantile))
to_liquidate = [x[0] for x in to_liquidate]
for i in to_liquidate:
self.liquidate(i)
self.log('LIQUIDATE: {}'.format(i))
#MOM = [x[0] for x in sortedByMOM]
#for i in MOM:
# self.log(" MOM: {}".format(i))
#top = [x[0] for x in self.top_quantile]
#for i in top:
# self.log(" TOP: {}".format(i))
#bottom = [x[0] for x in self.bottom_quantile]
#for i in bottom:
# self.log(" BOTTOM: {}".format(i))
weight = 1/len(self.top_quantile)
top = [x[0] for x in self.top_quantile]
for i in top:
self.set_holdings(i,weight)
self.log('BUY: {}'.format(i))
bottom = [x[0] for x in self.bottom_quantile]
for i in bottom:
self.set_holdings(i, -1*weight)
self.log('SELL: {}'.format(i))
self.monthly_rebalance=False
class AverageDollarVolume(PythonIndicator): # Average Dollar Volume
def __init__(self, SMA):
self.dv = RollingWindow[float](SMA)
self.Value = 0
def Update(self, price, volume):
self.dv.Add(price * volume)
if self.dv.IsReady:
self.Value = pd.Series(self.dv).mean()
return True
return False
class MonthlyData:
def __init__(self, symbol, lookback):
self.Symbol = symbol
self.Closing = CumulativeReturn(lookback)
self.Consolidator = TradeBarConsolidator(Calendar.Monthly)
self.previous = None
def RegisterIndicators(self, algorithm):
algorithm.register_indicator(self.Symbol, self.Closing, self.Consolidator)
def RemoveConsolidators(self, algorithm):
if self.Consolidator is not None:
algorithm.SubscriptionManager.RemoveConsolidator(self.Symbol, self.Consolidator)
def WarmUpIndicators(self, history, algorithm):
for index, tuple in history.iterrows():
tradeBar = TradeBar()
tradeBar.Close = tuple['close']
tradeBar.Open = tuple['open']
tradeBar.High = tuple['high']
tradeBar.Low = tuple['low']
tradeBar.Volume = tuple['volume']
tradeBar.Time = index
tradeBar.Symbol = self.Symbol
self.Consolidator.Update(tradeBar)
algorithm.Log("Closing is ready after warmup: " + str(self.Closing.IsReady))
@property
def Return(self):
return float(self.Closing.Current.Value)
@property
def CanEmit(self):
if self.previous == self.Closing.Samples:
return False
self.previous = self.Closing.Samples
return self.Closing.IsReady
class CumulativeReturn(PythonIndicator): # 12-Month Cumulative Return
def __init__(self, lookback):
self.Indicator = RollingWindow[float](lookback)
self.SimpleReturn = {}
self.Value = 0
def Update(self, bar):
self.Indicator.Add(bar.close)
if self.Indicator.IsReady:
self.SimpleReturn = pd.Series(self.Indicator)
self.SimpleReturn = self.SimpleReturn[::-1]
self.SimpleReturn = self.SimpleReturn.pct_change().dropna()
self.SimpleReturn = (self.SimpleReturn + 1).cumprod() - 1
self.Value = self.SimpleReturn.iloc[-1]
return True
return False