Overall Statistics |
Total Orders 10101 Average Win 0.42% Average Loss -0.39% Compounding Annual Return 20.793% Drawdown 51.900% Expectancy 0.076 Start Equity 100000 End Equity 369627.53 Net Profit 269.628% Sharpe Ratio 0.609 Sortino Ratio 0.637 Probabilistic Sharpe Ratio 13.689% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.06 Alpha 0.089 Beta 0.702 Annual Standard Deviation 0.244 Annual Variance 0.06 Information Ratio 0.285 Tracking Error 0.222 Treynor Ratio 0.212 Total Fees $29605.75 Estimated Strategy Capacity $8000.00 Lowest Capacity Asset PNM R735QTJ8XC9X Portfolio Turnover 100.15% |
#region imports from AlgorithmImports import * #endregion # https://quantpedia.com/strategies/trading-on-the-dividend-paydate/ # # The investment universe consists of stocks from NYSE, AMEX and NASDAQ that offer company-sponsored DRIPs. # Each day at close investors buy stocks which have dividend payday on the next working day and hold these stocks # for one day. Stocks are weighted equally. # # QC implementation: # Can scrape the below websites for info # Source of drip tickers: http://www.dripdatabase.com/DRIP_Directory_AtoZ.aspx # Source of dividend data: https://www.nasdaq.com/market-activity/dividends from datetime import datetime from pandas.tseries.offsets import BDay class TradingDividendPaydate(QCAlgorithm): def Initialize(self): # self.SetStartDate(2010, 1, 1) self.SetStartDate(2017, 10, 1) # self.SetStartDate(2021, 12, 1) # self.SetStartDate(2022, 9, 20) # self.SetEndDate(2022, 8, 31) # Set End Date self.SetEndDate(2024, 8, 31) # Set End Date self.SetCash(100000) self.symbol = self.AddEquity('SPY', Resolution.Minute).Symbol # Store drip tickers. # csv_string_file = self.Download('data.quantpedia.com/backtesting_data/economic/drip_tickers.csv') csv_string_file = self.Download('https://www.dropbox.com/s/id5bvqweevq3cja/drip_tickers_1.csv?dl=1') lines = csv_string_file.split('\r\n') self.drip_tickers = [x for x in lines[1:]] # dividend data self.dividend_data = {} # dict of dicts indexed by paydate date self.dividend_tickers = [] # csv_string_file = self.Download('data.quantpedia.com/backtesting_data/economic/dividend_dates.csv') # csv_string_file = self.Download('https://www.dropbox.com/s/lgxdzccpsq3yvl8/nasdaq_dividend_dates_4.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/tlq1wty1a7twuhz/nasdaq_dividend_dates_5.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/5puro7dshiu5t2i/nasdaq_dividend_dates_5_short_copy.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/thqtz9br5zhxfvu/nasdaq_dividend_dates_6_short_copy.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/ydby6dber7i4347/dividend_dates_1.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/59frn1ixdzdbzaa/nasdaq_dividend_dates_7.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/8kq73i9qhuc0lvb/dividend_dates_8_short.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/5uuelwq20o46ruc/nasdaq_dividend_dates_9_copy.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/p0y1n9p4cl4opww/nasdaq_dividend_dates_11_copy.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/sdyfvyqymndud3w/nasdaq_dividend_dates_12_copy.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/7urf9v9pc9al4ri/nasdaq_dividend_dates_13_copy.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/dj68d3225os97ee/nasdaq_dividend_dates_14_copy.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/42rlzc3ho76ypz9/nasdaq_dividend_dates_15_copy.csv?dl=1') # csv_string_file = self.Download('https://www.dropbox.com/s/3u51mnzipod4e0x/nasdaq_dividend_dates_16_copy.csv?dl=1') csv_string_file = self.Download('https://www.dropbox.com/s/svftrjdzxhukyw2/nasdaq_dividend_dates_18_copy.csv?dl=1') lines = csv_string_file.split('\r\n') # lines = csv_string_file.split('\n') for line in lines[3:]: # skip first three comment lines if line == '': continue line_split = line.split(';') # self.Debug(str(line_split)) # self.Debug("Before datetime: " + str(line_split[0])) ex_div_date = datetime.strptime(line_split[0], "%Y-%m-%d").date() # ex_div_date = datetime.strptime(line_split[0], "%Y-%m-%d %H:%M").date() # self.Debug("After datetime: " + str(ex_div_date)) # 1/0 # N stocks -> n*6 properties for i in range(1, len(line_split), 6): # parse dividend info ticker = str(line_split[i]) # self.Debug("Before datetime: " + str(line_split[i+1])) payday = datetime.strptime(line_split[i+1], "%m/%d/%Y").date() if line_split[i+1] != '' else None # payday = datetime.strptime(line_split[i+1], "%m/%d/%Y").date() if line_split[i+1] != '' or line_split[i+1] != 'N/A' else None # self.Debug(str(payday)) # self.Debug("After datetime: " + str(payday)) # 1/0 if payday not in self.dividend_data: self.dividend_data[payday] = {} # self.Debug("Before datetime: " + str(line_split[i+2])) record_date = datetime.strptime(line_split[i+2], "%m/%d/%Y").date() if line_split[i+2] != '' else None # record_date = datetime.strptime(line_split[i+2], "%m/%d/%Y").date() if line_split[i+2] != '' or line_split[i+2] != 'N/A' else None # self.Debug(str(record_date)) # self.Debug("After datetime: " + str(record_date)) # self.Debug("Before datetime: " + str(line_split[i+3])) dividend_value = float(line_split[i+3]) if line_split[i+3] != '' else None # dividend_value = float(line_split[i+3]) if line_split[i+3] != '' or line_split[i+3] != 'N/A' else None # self.Debug(str(dividend_value)) # self.Debug("After datetime: " + str(dividend_value)) # self.Debug("Before datetime: " + str(line_split[i+4])) ann_dividend_value = float(line_split[i+4]) if line_split[i+4] != '' else None # ann_dividend_value = float(line_split[i+4]) if line_split[i+4] != '' or line_split[i+4] != 'N/A' else None # self.Debug(str(ann_dividend_value)) # self.Debug("After datetime: " + str(ann_dividend_value)) # self.Debug("Before datetime: " + str(line_split[i+5])) try: announcement_date = datetime.strptime(line_split[i+5], "%m/%d/%Y").date() if line_split[i+5] != '' else None except: "05/02/2022" # announcement_date = datetime.strptime(line_split[i+5], "%m/%d/%Y").date() if line_split[i+5] != '' or line_split[i+5] != 'N/A' else None # self.Debug(str(announcement_date)) # self.Debug("After datetime: " + str(announcement_date)) # 1/0 # store ticker dividend info to current ex-div date self.dividend_data[payday][ticker] = DividendInfo(ticker, ex_div_date, payday, record_date, dividend_value, ann_dividend_value, announcement_date) # self.Debug("A") # store dividend info ticker universe self.dividend_tickers.append(ticker) # self.Debug("Dividend Tickers: " + str(self.dividend_tickers)) self.opened_positions = {} self.active_universe = [] # selected stock universe self.selection_flag = False self.UniverseSettings.Resolution = Resolution.Minute self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) self.Schedule.On(self.DateRules.MonthEnd(self.symbol), self.TimeRules.AfterMarketOpen(self.symbol), self.Selection) self.Schedule.On(self.DateRules.EveryDay(self.symbol), self.TimeRules.BeforeMarketClose(self.symbol, 16), self.Rebalance) def OnSecuritiesChanged(self, changes): for security in changes.AddedSecurities: # security.SetFeeModel(CustomFeeModel(self)) security.SetFeeModel(CustomFeeModel()) security.SetLeverage(10) def CoarseSelectionFunction(self, coarse): if not self.selection_flag: return Universe.Unchanged self.selection_flag = False # For Scalable Version # sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True) # filtered = [ x.Symbol for x in sortedByDollarVolume if x.Price > 10 and x.DollarVolume > 10000000 ] # selected = [x.Symbol for x in sortedByDollarVolume if x.Symbol in self.drip_tickers #x.HasFundamentalData # and x.Market == 'usa' and x.Price > 5 # and x.DollarVolume > 10000000 # My addition to help with scale # and x.Symbol in self.drip_tickers # and x.Symbol in self.dividend_tickers] # selected = selected[:2000] # selected = [x.Symbol for x in sortedByDollarVolume if x.Price > 5 # and x.DollarVolume > 10000000 # and x.Symbol.Value in self.drip_tickers # and x.Symbol.Value in self.dividend_tickers] # Original Version selected = [x.Symbol for x in coarse if x.Symbol.Value in self.drip_tickers and x.Symbol.Value in self.dividend_tickers] return selected def FineSelectionFunction(self, fine): fine = [x for x in fine if x.MarketCap != 0 and \ ((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE"))] # sorting by market cap sorted_by_market_cap = sorted(fine, key = lambda x: x.MarketCap, reverse = True) half = int(len(sorted_by_market_cap) / 2) # pick lower half self.active_universe = [x.Symbol for x in sorted_by_market_cap[-half:]] # pick upper half # self.active_universe = [x.Symbol for x in sorted_by_market_cap[:half]] return self.active_universe def Rebalance(self): # close opened positions if len(self.opened_positions) != 0: for symbol, q in self.opened_positions.items(): self.MarketOnCloseOrder(symbol, -q) self.opened_positions.clear() day_to_check = (self.Time.date() + BDay(1)).date() # there are stocks with payday next business day if day_to_check in self.dividend_data: payday_tickers = list(self.dividend_data[day_to_check].keys()) long = [] for symbol in self.active_universe: if symbol.Value in payday_tickers: long.append(symbol) if len(long) != 0: portfolio_value = self.Portfolio.TotalPortfolioValue / len(long) for symbol in long: price = self.Securities[symbol].Price if price != 0: q = portfolio_value / price self.MarketOnCloseOrder(symbol, q) self.opened_positions[symbol] = q def Selection(self): if self.Time.month % 3 == 0: self.selection_flag = True # custom fee model # class CustomFeeModel(FeeModel): class CustomFeeModel: def GetOrderFee(self, parameters): fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005 return OrderFee(CashAmount(fee, "USD")) class DividendInfo(): def __init__( self, ticker:str, ex_div_date:datetime, payday:datetime, record_date:datetime, dividend_value:float, ann_dividend_value:float, announcement_date:datetime ): self.ticker:str = ticker self.ex_div_date:datetime = ex_div_date self.payday:datetime = payday self.record_date:datetime = record_date self.dividend_value:float = dividend_value self.ann_dividend_value:float = ann_dividend_value self.announcement_date:datetime = announcement_date