Overall Statistics |
Total Trades 6072 Average Win 0.33% Average Loss -0.32% Compounding Annual Return -41.158% Drawdown 79.500% Expectancy -0.146 Net Profit -78.780% Sharpe Ratio -0.998 Probabilistic Sharpe Ratio 0.000% Loss Rate 58% Win Rate 42% Profit-Loss Ratio 1.03 Alpha -0.34 Beta 0.668 Annual Standard Deviation 0.279 Annual Variance 0.078 Information Ratio -1.47 Tracking Error 0.252 Treynor Ratio -0.418 Total Fees $19271.46 Estimated Strategy Capacity $4000.00 Lowest Capacity Asset TRST R735QTJ8XC9X |
#region imports from AlgorithmImports import * #endregion # ----------------------- # Parameters # ----------------------- IS_LIVE_MODE:bool = False N_DAYS_BEFORE_PAYDAY:int = 1 # open trade n days before dividend payday M_SELECTION_PERIOD:int = 3 # new universe selection monthly period DAILY_HOLDING_PERIOD:int = 1 # number of days to hold position opened; (1/self.daily_holding_period) of portfolio is used for every new position tranche POSITION_DIRECTION:int = 1 # 1:long; -1:short LEVERAGE:int = 1 # portfolio leverage MARKET_SMA_FILTER:bool = False # market SMA filter (trade only if market > SMA) MARKET_SMA_PERIOD:int = 120 # market SMA filter period # int(self.GetParameter('MARKET_SMA_PERIOD')) STOCK_SMA_FILTER:bool = False # individual stock SMA filter (trade only if stock price > SMA) STOCK_SMA_PERIOD:int = 120 # individual stock SMA filter period # NOTE Do not change D_WARMUP_PERIOD = 30 # number of days to store dividend data for before start of the algorithm
#region imports from AlgorithmImports import * #endregion class DividendDataLive(PythonData): def GetSource(self, config:SubscriptionDataConfig, date:datetime, isLiveMode:bool) -> SubscriptionDataSource: return SubscriptionDataSource("http://ec2-52-53-178-85.us-west-1.compute.amazonaws.com:8081/dividend_dates/recent?fields=symbol,payment_date", SubscriptionTransportMedium.RemoteFile) # return SubscriptionDataSource("data.quantpedia.com/backtesting_data/data.json", SubscriptionTransportMedium.RemoteFile) def Reader(self, config:SubscriptionDataConfig, line:str, date:datetime, isLiveMode:bool) -> BaseData: try: custom_data = DividendDataLive() custom_data.Symbol = config.Symbol line = json.loads(line) custom_data.EndTime = datetime.strptime(str(line["dividend_ex_date"]), "%Y-%m-%d") # custom_data.EndTime = custom_data.Time + timedelta(days=1) custom_data['stocks'] = line["stocks"] return custom_data except ValueError: return None class DividendDataHistory(PythonData): # _tickers:Set[str] = set() _from_date:str = None _end_date:str = None def GetSource(self, config:SubscriptionDataConfig, date:datetime, isLiveMode:bool) -> SubscriptionDataSource: return SubscriptionDataSource(f"http://ec2-52-53-178-85.us-west-1.compute.amazonaws.com:8081/dividend_dates?from_date={DividendDataHistory._from_date}&end_date={DividendDataHistory._end_date}&fields=symbol,payment_date", SubscriptionTransportMedium.RemoteFile, FileFormat.UnfoldingCollection) @staticmethod def set_dates(from_date:str, end_date:str) -> None: DividendDataHistory._from_date = from_date DividendDataHistory._end_date = end_date def Reader(self, config:SubscriptionDataConfig, line:str, date:datetime, isLiveMode:bool) -> BaseData: try: objects = [] data = json.loads(line) end_time = None for index, sample in enumerate(data): custom_data = DividendDataHistory() custom_data.Symbol = config.Symbol custom_data.EndTime = datetime.strptime(str(sample["dividend_ex_date"]), "%Y-%m-%d") # custom_data.EndTime = custom_data.Time + timedelta(days=1) end_time = custom_data.EndTime custom_data['stocks'] = sample["stocks"] objects.append(custom_data) # for stock in sample["stocks"]: # ticker = stock["symbol"] # DividendData._tickers.add(ticker) return BaseDataCollection(end_time, config.Symbol, objects) except ValueError: return None
from AlgorithmImports import * drip_tickers = [ 'FIFG', 'MMM', 'SSRX', 'JOBS', 'AIR', 'ABT', 'FAX', 'XIAFX', 'XFCOX', 'ABBCD', 'ABN', 'ABNYY', 'AKR', 'ANCX', 'ACE', 'ACTS', 'AYI', 'ADX', 'ADCT', 'ATE', 'AEG', 'AONNY', 'AET', 'AFL', 'ARBJY', 'AGYS', 'ATG', 'AEM', 'ADC', 'AIFLY', 'AIRYY', 'AIQUY', 'APD', 'AMCN', 'AIXG', 'AJINY', 'AKS', 'VOLVY', 'AKZA', 'ALAB', 'ALSK', 'AIN', 'ALB', 'ALU', 'AA', 'ALEX', 'ALFA', 'ATI', 'AGN', 'ALE', 'ALNC', 'AOI', 'ACG', 'LNT', 'AZ', 'ALD', 'AIB', 'ATHLY', 'ALL', 'AT', 'ALAXY', 'ALBKY', 'APELY', 'APSA', 'MO', 'AWC', 'ACH', 'ACO', 'AMCRY', 'AMFI', 'AEE', 'AMX', 'ACAS', 'ACBA', 'AEP', 'AXP', 'AFG', 'AFR', 'AM', 'AMNB', 'AMSWA', 'AWR', 'AMT', 'AMP', 'ABCB', 'ASRV', 'AME', 'AMLTY', 'AMR', 'AMY', 'APC', 'ABCW', 'ANDE', 'AGL', 'AAUK', 'AGPPY', 'AU', 'BUD', 'NLY', 'AHR', 'ANH', 'AOC', 'APA', 'AINV', 'CRA', 'AIT', 'ATS', 'WTR', 'ARA', 'ARB', 'ARJ', 'ACI', 'ADM', 'ARCC', 'ARMHY', 'ACKH', 'AROW', 'ARTNA', 'ARVNF', 'ARZMY', 'ARM', 'TRMD', 'ASA', 'ASH', 'ASML', 'ASBC', 'AEC', 'AF', 'AZN', 'T', 'ATO', 'AUBN', 'AVY', 'CAR', 'AVA', 'AVP', 'AVX', 'AXA', 'BAANF', 'IBA', 'BMI', 'BAESY', 'BHI', 'BEZ', 'BLD', 'BLL', 'BNSTY', 'ITU', 'STD', 'CIB', 'BXS', 'BCV', 'BTFG', 'BDG', 'BKMU', 'BAC', 'BCH', 'GRAN', 'BOH', 'IRE', 'BMO', 'BK', 'BKSC', 'BKJAY', 'BFIN', 'BANR', 'BHB', 'BCS', 'BCR', 'BRRAY', 'B', 'BOL', 'BAX', 'BAYK.OB', 'SHRGY', 'HVMGY', 'BBT', 'BFR', 'BCE', 'BCSB', 'BESIY', 'BZH', 'BEC', 'BDX', 'BMS', 'BNGPY', 'BHLB', 'BBY', 'BRGYY', 'BHP', 'BBL', 'BMR', 'BDK', 'BKH', 'BHWB', 'BLU', 'BSI', 'BTH', 'BNPQY', 'BORD', 'BA', 'BOKF', 'BGP', 'BWA', 'SAM', 'BPFH', 'BXP', 'BOCOY', 'BNE', 'BP', 'BRMLL', 'BRC', 'BDN', 'BAK', 'CBD', 'PBR', 'BRE', 'BRDCY', 'BGG', 'BMY', 'BAIRY', 'BSY', 'BXXX', 'BRKL', 'BWS', 'BF.B', 'BRT', 'BC', 'BW', 'BT', 'BPL', 'BLG', 'BKC', 'BNI', 'CFFI', 'CA', 'CBT', 'CBY', 'CSG', 'CADE', 'CLMS', 'CCC', 'CWT', 'ELY', 'CAFI', 'CAC', 'CPT', 'CPB', 'CAJ', 'CGEMY', 'CBKN', 'CCBG', 'COF', 'CSWC', 'CSE', 'CBC', 'LSE', 'CSAR', 'WPC', 'CSL', 'CCL', 'CUK', 'CRS', 'CRRB', 'TAST', 'CARV', 'CASB', 'CASY', 'CSH', 'CAS', 'CAT', 'CATY', 'CAV', 'CBL', 'CBRL', 'CBS', 'CCFN', 'CECB', 'FUN', 'CDR', 'CX', 'CNBC', 'CHC', 'CNP', 'CAIFY', 'CEBK', 'CEE', 'CJPRY', 'CPF', 'CPUBN.OB', 'CPUBO.OB', 'CV', 'CVBK', 'CNBKA', 'CTL', 'CEN', 'CHG', 'CMNGY', 'CCF', 'CHE', 'CXSP', 'SQM', 'CHFC', 'CEM', 'CHMG', 'CSK', 'CPK', 'CHEUY', 'CVX', 'CMOPY', 'CBI', 'CH', 'CEA', 'JRJC', 'LFC', 'CHL', 'NPD', 'ZNH', 'CSUN', 'CNTF', 'CHA', 'CHU', 'CHZ', 'COFS', 'CB', 'CHT', 'CHD', 'CGS', 'CI', 'CINF', 'C', 'CZNC', 'CZN', 'CTZN', 'CIZN', 'CIA', 'CRBC', 'CSBC', 'CHCO', 'LIZ', 'CLC', 'CNL', 'CLF', 'CLX', 'CMS', 'CCNE', 'CNH', 'CISG', 'CEO', 'COA', 'COKE', 'KO', 'CCE', 'KOF', 'CCH', 'CVLY', 'CL', 'CNB', 'CLP', 'CBAN', 'CMCO', 'CMA', 'CCBP', 'CWBS', 'CMTV', 'CBIN', 'CBU', 'CMTY', 'SCB', 'CPBK', 'CCBD', 'CDMHY', 'CDDMY', 'ELP', 'SID', 'RIO', 'RIODF', 'CU', 'BVN', 'CAG', 'VCO', 'CTWS', 'COP', 'ED', 'CWCO', 'CEG', 'CNW', 'CBE', 'CTB', 'CRGI', 'CMNFY', 'CPO', 'CLM', 'CRF', 'CXP', 'OFC', 'CPSVY', 'COST', 'CFC', 'CUMD', 'CUZ', 'CPY', 'CR', 'CS', 'CSNT', 'CRESY', 'CRH', 'CSBB', 'CSX', 'CTCI', 'CTRP', 'CMI', 'CW', 'CVS', 'DECC', 'DAI', 'DFIHY', 'DWAHY', 'DWASY', 'DMSQ', 'DCNAQ', 'DNSKY', 'DRI', 'DASTY', 'DBSDY', 'DCT', 'DBHMY', 'DE', 'DLM', 'DDIAX', 'VMM', 'DEG', 'DELL', 'DELT', 'DGAS', 'DT', 'DDR', 'DEO', 'DAMRY', 'DBD', 'DCOM', 'DIMC', 'DYS', 'DCA', 'DNBF', 'DNBHY', 'D', 'DPZ', 'DCI', 'DGICA', 'DOV', 'DVD', 'DOW', 'DJ', 'DPL', 'RDY', 'DROOY', 'XDCGX', 'DHF', 'DMF', 'XDNMX', 'DTE', 'DTF', 'DUK', 'DRE', 'DX', 'DD', 'EONGY', 'EGBN', 'EGLE', 'EFSI', 'EML', 'EVBS', 'EGP', 'EMN', 'EK', 'ESIC', 'ETN', 'ETJ', 'EXG', 'ECL', 'EIX', 'EDR', 'EJ', 'ESALY', 'EP', 'ELN', 'EDS', 'EPUMI', 'ECF', 'ESBK', 'LONG', 'AKO-A', 'ERJ', 'EMCI', 'EMR', 'EBSH', 'EDE', 'EDERL', 'ENB', 'ELE', 'EN', 'EGN', 'EAS', 'TXU', 'ENSI', 'E', 'ETR', 'EBTC', 'EPD', 'EPR', 'EPCYY', 'EPCYY', 'EFX', 'EQT', 'ELS', 'EQR', 'ERIAF', 'ESBF', 'ESS', 'EL', 'ERASL', 'EEA', 'EVBN', 'BOBE', 'EXC', 'XOM', 'FMAO', 'FMFG', 'FFKT', 'FBSS', 'FFG', 'FCIC', 'FRE', 'FDMLQ', 'FNM', 'FRT', 'FDX', 'FGP', 'FOE', 'FIATY', 'FSBI', 'FDBC', 'LION', 'FITB', 'DHFT', 'FAF', 'FBNC', 'FCAP', 'FCBS', 'FCTR', 'FCEC', 'FCF', 'FCCO', 'FDEF', 'FFSX', 'FFBC', 'FFCH', 'FHN', 'ISL', 'FKFS', 'FMFC', 'FRME', 'FMBH', 'FMBI', 'FNCB', 'FXNC', 'FNFG', 'FNFI', 'FPFC', 'FPO', 'FSGI', 'FSNM', 'FUNC', 'FBMI', 'FE', 'FMER', 'FBC', 'FLML', 'FPU', 'FLO', 'FNB', 'FNBP', 'FNBF', 'FNBN', 'FCCG', 'FL', 'F', 'FORTY', 'FDI', 'FORSY', 'FO', 'FWLT', 'FOTXY', 'FOFN', 'FXX', 'FPL', 'FTE', 'BEN', 'FSP', 'FWRLY', 'FMS', 'FBR', 'FRDPY', 'FTBK', 'FUJI', 'FJTNY', 'FUL', 'FULT', 'FUAIY', 'GDL', 'GGN', 'AJG', 'GCI', 'GBTS', 'GMT', 'GET', 'GBTB', 'GY', 'GAM', 'GE', 'GGP', 'GIS', 'GM', 'GENE', 'GPC', 'GNW', 'GABC', 'GTY', 'GVHR', 'GBCI', 'GSK', 'GLBZ', 'GRT', 'GIF', 'GPN', 'GMOYY', 'GOL', 'GFI', 'GR', 'GT', 'GRC', 'GGG', 'GRIN', 'GVA', 'GTN', 'GFR', 'GAP', 'GXP', 'GSBC', 'GFLS', 'GBX', 'GCBC', 'GDNNY', 'SAB', 'GRCNL', 'GGAL', 'TV', 'GNV', 'GSH', 'GFED', 'GUARL', 'HLUKY', 'HRB', 'HABC', 'HMPR', 'HANAY', 'HBHC', 'HBI', 'HCM', 'HSPCY', 'HDNG', 'HOG', 'HGIC', 'HNBC', 'HARL', 'HMY', 'HET', 'HRS', 'HSC', 'HMX', 'HAS', 'HE', 'HWKN', 'HCP', 'HDB', 'HEDYY', 'HCN', 'HR', 'HCSG', 'HTLF', 'HINKY', 'HNZ', 'OTE', 'JKHY', 'HPC', 'HTGC', 'HT', 'HSY', 'HPQ', 'HXWRF', 'HTCO', 'HIW', 'HB', 'HNDNF', 'HMNF', 'HOC', 'HOMB', 'HD', 'HOME', 'HMIN', 'HME', 'HXM', 'HMC', 'HON', 'HOKCY', 'HH', 'HBNC', 'HRZB', 'HRL', 'HPT', 'HRP', 'HBC', 'HRVAL', 'HNP', 'HUB.A', 'HCBK', 'HBAN', 'HSM', 'HTR', 'HREHY', 'HYTM', 'HYMLY', 'IBDRY', 'IBKC', 'IRW', 'ICA', 'IBN', 'ICON', 'IDA', 'IIBK', 'IAR', 'IDKOY', 'IEX', 'IKN', 'ITW', 'ILOG', 'ILX', 'IMN', 'IMH', 'IMP', 'IMPDF', 'IMO', 'ITY', 'INDB', 'IBCP', 'IF', 'IMB', 'IFX', 'IPCC', 'INFY', 'ING', 'IR', 'IRC', 'INKPP', 'ISIG', 'IIIN', 'IBNK', 'TEG', 'INTC', 'IHG', 'IBM', 'IFF', 'IP', 'IIJI', 'IPG', 'ISIL', 'IVC', 'IVZ', 'IOND', 'IRS', 'SFI', 'ITT', 'JCP', 'JPM', 'JNS', 'JEQ', 'JMHLY', 'JSHLY', 'JEF', 'JFBC', 'BTO', 'JHS', 'JHI', 'HPF', 'PDT', 'XDIVX', 'HPI', 'HPI', 'HPS', 'HTD', 'JNJ', 'JCI', 'JELCY', 'CRMUY', 'JLL', 'JPST', 'JUVF', 'KAMN', 'KCRPY', 'KYE', 'KED', 'KYN', 'KCCPY', 'KRNY', 'KEI', 'K', 'KWD', 'KELYA', 'KMT', 'KEY', 'KRC', 'KMB', 'KCDMY', 'KIM', 'KNBWY', 'KRG', 'KMGB', 'KNBT', 'KCAP', 'KNM', 'KPN', 'KKPN', 'PHG', 'KJWNY', 'KB', 'KEP', 'KFT', 'KUB', 'KYO', 'LG', 'LFRGY', 'LBAI', 'LKFN', 'LFL', 'LANC', 'LNCE', 'LHO', 'LZB', 'LCNB', 'LDK', 'LEA', 'FLPB', 'LEH', 'LXP', 'LBY', 'LRY', 'LIHR', 'LLY', 'LTD', 'LNCB', 'LNC', 'LTON', 'LINN.OB', 'LYG', 'SCD', 'TLI', 'RIT', 'LNBB', 'LMT', 'LFVGY', 'LDG', 'LRLCY', 'LPX', 'LOW', 'LYTS', 'LTC', 'LZ', 'LUB', 'LUM', 'LUX', 'LVMUY', 'LYO', 'MTB', 'MCBC', 'MAC', 'CLI', 'M', 'MSP', 'MPET', 'MTA', 'MAM', 'MSFG', 'MKTAY', 'MNOIY', 'MTW', 'MAN', 'MFC', 'MRO', 'MCS', 'MAR', 'MMC', 'MI', 'MARUY', 'MAURY', 'MAS', 'MYS', 'MASB', 'MEE', 'MCI', 'MAUSY', 'MC', 'MSEWY', 'MAT', 'MRTI', 'MFLR', 'MBTF', 'MKC', 'MDR', 'MCD', 'MCGC', 'MHP', 'MCK', 'MDU', 'MIG', 'MWV', 'TAXI', 'MEG', 'MDIUY', 'MDT', 'MLGGY', 'MSFZY', 'MSFJY', 'MBWM', 'MMBI', 'MBVT', 'MRK', 'MDP', 'VIVO', 'MER', 'MERB', 'MPR', 'MCBI', 'MGS', 'FMX', 'MXE', 'MXF', 'MFA', 'MGEE', 'MDH', 'MSFT', 'MAA', 'MBP', 'MBRG', 'MBCN', 'MSEX', 'DOLL', 'MSL', 'MBHI', 'OSKY', 'MWFS', 'MZ', 'MLEAY', 'MLHR', 'MIL', 'MRAE', 'MITEY', 'MTU', 'MT', 'MBT', 'MOD', 'MOLX', 'MNC', 'MNXBY', 'MNRTA', 'MORE', 'MON', 'MS', 'MSF', 'IIF', 'MOT', 'MTBGY', 'MTRJY', 'MTSC', 'MMAB', 'MYL', 'GRF', 'NAFC', 'NSHA', 'NBOH', 'NBG', 'NCC', 'NFP', 'NFG', 'NGG', 'NHI', 'NPBC', 'NNN', 'NWPC', 'NFS', 'NHP', 'NATR', 'NTZ', 'NCR', 'NELTY', 'NESN', 'NETC', 'NTES', 'HYB', 'GF', 'NHTB', 'IRL', 'NJR', 'NYB', 'NYT', 'NAL', 'NBBC', 'NWL', 'NEU', 'NEWP', 'NGPC', 'NICE', 'GAS', 'NJ', 'NKE', 'NIKOY', 'NINE', 'NTDOY', 'NISTY', 'NTT', 'NIS', 'NI', 'NSANY', 'NOK', 'NMR', 'NAT', 'NDSN', 'NSC', 'NHYDY', 'NTL', 'NU', 'NTHN.PK', 'NSFC', 'NOC', 'NRF', 'NWSB', 'NWN', 'NVS', 'NVO', 'NVGN', 'NST', 'DCM', 'NUE', 'OXY', 'OCENY', 'OCFC', 'ODP', 'OMX', 'OGE', 'OH1', 'OVBC', 'FEEOY', 'OCRNL', 'NWTEY', 'SBTLY', 'UVYPY', 'ODMTY', 'ONB', 'OPOF', 'ORI', 'OLN', 'OMEF', 'OHI', 'OGPJY', 'OCR', 'OMC', 'OMN', 'OMRNY', 'OMVKY', 'OMVZY', 'OLP', 'OB', 'NEI.P', 'OKE', 'RCG', 'OKASY', 'ROS', 'VIP', 'ORPB', 'OFG', 'ORRF', 'UVYZY', 'OSK', 'OTTR', 'OMI', 'PABK', 'PHF', 'PCBC', 'PFLC', 'PLL', 'PHHM', 'PBCI', 'PPXLF', 'PH', 'PKY', 'PTNR', 'PRTR', 'PCAP', 'PVLN', 'PAYX', 'BTU', 'PGC', 'PSO', 'PNNT', 'PFSB', 'PNNW', 'PWOD', 'COBH', 'PEI', 'PNR', 'PFDC', 'PEBO', 'PEBK', 'PBTC', 'PBCT', 'PFIS.OB', 'PBY', 'POM', 'PBG', 'PAS', 'PEP', 'PDA', 'PKI', 'PBT', 'PFOH', 'TLK', 'PTR', 'PEO', 'PEUGY', 'PFV', 'PFB', 'PFE', 'PCG', 'PXSL', 'PHI', 'PNY', 'PIR', 'PPBN', 'PNW', 'HNW', 'PHD', 'PHT', 'MUO', 'MAV', 'MHI', 'PBF', 'PBI', 'PXPLD', 'PXPL', 'NVSKL', 'STJSY', 'VLGAY', 'PCL', 'PCC', 'PMI', 'PNC', 'PNM', 'PFSL', 'PII', 'POL', 'BPOP', 'PBIB', 'PT', 'PKX', 'PPS', 'PCH', 'PPG', 'PPL', 'PX', 'PVLY', 'PDLA', 'PDL-B', 'PDE', 'PG', 'PGN', 'PLD', 'PMSEY', 'PSEC', 'PL', 'PTIL', 'PVD', 'PBKS', 'PCBS', 'PFS', 'PBNY', 'PVMIY', 'PBIP', 'PUK', 'PSBG', 'PMD', 'PIIAF', 'PEG', 'PSD', 'PULB', 'PUM', 'QADI', 'KWR', 'QCOM', 'NX', 'STR', 'LQU', 'Q', 'RGFC', 'RSH', 'RAS', 'RPT', 'RANGY', 'GOLD', 'RAVN', 'RYN', 'RTN', 'RWT', 'ENL', 'RUK', 'REG', 'RF', 'RGS', 'RELV', 'RNST', 'REP', 'RBCAA', 'RSO', 'RTRSY', 'REXMY', 'RAI', 'RGCO', 'RHA', 'RICOY', 'RTP', 'RIVR', 'RLI', 'RBN', 'ROK', 'COL', 'ROH', 'ROL', 'ROME', 'RBS', 'FUND', 'RMT', 'RVT', 'RPM', 'RRD', 'RDK', 'RBNF', 'RYAAY', 'R', 'RTULP', 'SYBT', 'STBA', 'SBSP3', 'SDA', 'SWY', 'SAFRF', 'SGPYY', 'SAPFF', 'SMGBY', 'SAFM', 'SASR', 'SNY', 'SBP', 'STOSY', 'SANYY', 'SAP', 'SPP', 'SLE', 'SSL', 'BFS', 'SCG', 'SCBT', 'SGK', 'SGP', 'SCHN', 'SCHW', 'SCO', 'SSP', 'SCRB', 'SEE', 'SOMLY', 'SBKC', 'SEKEY', 'SIGI', 'SMI', 'SRE', 'SNH', 'SXT', 'SGL', 'SHALY', 'SHCAY', 'SHW', 'SHPGY', 'SHBI', 'SHBK', 'SI', 'SRP', 'SIG', 'SIMO', 'SVRTY', 'SPG', 'SGF', 'SHI', 'SKIL', 'SWKS', 'SLG', 'SFBC', 'SLM', 'SPHZF', 'SPHXY', 'SNN', 'AOS', 'SMTB', 'SJM', 'SNA', 'SOLF', 'SOLUQ', 'SVYSY', 'SON', 'SBNK', 'SNE', 'BID', 'SOR', 'TSFG', 'SJI', 'ASR', 'SO', 'SCMF', 'PCU', 'SUG', 'LUV', 'OKSB', 'SWX', 'SGB', 'SWWC', 'SWN', 'SOV', 'SSS', 'SPAR', 'SEH', 'SE', 'SPGLL', 'S', 'SCBFF', 'SR', 'SXI', 'SWK', 'SPLS', 'STBC', 'STT', 'STO', 'STL', 'SLFI', 'STSA', 'SBHO', 'SSFN', 'STC', 'STM', 'SEOAY', 'STRA', 'SRR', 'SUBK', 'SUOPY', 'SMTOY', 'SMFJY', 'SUI', 'SU', 'SUN', 'STP', 'STI', 'SUP', 'SVU', 'SUSQ', 'SBBX', 'SWDBY', 'SWZ', 'SWCEY', 'SNV', 'SYY', 'TAEWF', 'TWN', 'TFC', 'TYOYY', 'TKPHY', 'TAM', 'SKT', 'TGT', 'TSTY', 'TCO', 'TCB', 'TDK', 'TSH', 'TKPPY', 'TE', 'TEK', 'TNE', 'TEO', 'NZT', 'TI', 'TFX', 'TMX', 'TMB', 'TELNY', 'TCN', 'TDS', 'TSP', 'TKG', 'TU', 'TIN', 'TDF', 'EMF', 'TEI', 'GIM', 'TRF', 'TMPOL', 'TS', 'TNC', 'TEVA', 'TXN', 'TXT', 'TF', 'THLEY', 'TNB', 'TMA', 'TIBB', 'TDW', 'TIF', 'TSU', 'TKR', 'TISM', 'TKS', 'TMP', 'TMK', 'TTC', 'TYY', 'TYG', 'TOT', 'TSS', 'TOTDY', 'TOWN', 'TSUKY', 'TM', 'TSCO', 'TAI', 'TRP', 'RIG', 'TGS', 'TRV', 'TG', 'TRCY', 'TSEO', 'TRB', 'TCBK', 'TSL', 'TRIB', 'TTPA', 'TRST', 'TUWLY', 'TKC', 'TUXS', 'TWB', 'TWIN', 'TYC', 'TSN', 'UDR', 'UGI', 'UIL', 'UGP', 'UMBF', 'UMH', 'UMPQ', 'UN', 'UL', 'UNBH', 'UBSH', 'UNNF', 'UNP', 'UB', 'UNS', 'UBCP', 'UBAB.OB', 'UBOH', 'UBSI', 'UCBI', 'UCFC', 'UFCS', 'UIC', 'GBNIY', 'UPS', 'UBFO', 'X', 'UTX', 'UUPLY', 'UTL', 'UTR', 'UNTY', 'UVV', 'UHT', 'UVSP', 'UNM', 'UBP', 'USB', 'USU', 'USG', 'UST', 'VFC', 'VRX', 'VLEEY', 'VLY', 'VAL', 'VDM', 'VIT', 'VVC', 'VTR', 'VE', 'VZ', 'VJAPY', 'VIMEL', 'VIMC', 'VCISY', 'VFGI', 'VC', 'VIV', 'VOD', 'VLKAY', 'VNO', 'VCP', 'VMC', 'WHI', 'GRA', 'WB', 'WACLY', 'WDR', 'WMT', 'WAG', 'DIS', 'WM', 'WRE', 'WASH', 'WMI', 'WPP', 'WVCM', 'WAYN', 'WBS', 'WZEN', 'WRI', 'WMK', 'WFC', 'WEN', 'WSBC', 'WCBO', 'WMBC', 'WST', 'WABC', 'WR', 'SBG', 'EFL', 'EMD', 'EDF', 'ESD', 'EHI', 'GDF', 'HIX', 'HIF', 'HIO', 'IMF', 'SBI', 'MHY', 'MMU', 'MHF', 'MNP', 'GFY', 'SBW', 'ZIF', 'WBK', 'WY', 'WGL', 'WHR', 'WTNY', 'WMB', 'WSH', 'WFBC', 'WL', 'WIN', 'WIT', 'WEC', 'WNS', 'WOSLY', 'WGOV', 'WCCLY', 'WWE', 'WOR', 'WWY', 'WSFS', 'WH', 'WX', 'WYE', 'XEL', 'XRM', 'XRX', 'XIN', 'XL', 'XMSR', 'XTO', 'YHOO', 'YZC', 'YARIY', 'YGE', 'YORW', 'YPF', 'YUSA', 'YUM', 'ZION', ]
# 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. #region imports from AlgorithmImports import * from datetime import datetime, timedelta from pandas.tseries.offsets import BDay from drip_tickers import drip_tickers import config from data_load import DividendDataHistory, DividendDataLive import json #endregion class TradingDividendPaydate(QCAlgorithm): def Initialize(self): self.SetStartDate(2020, 1, 1) # NOTE dividend data begin in 2012 # self.SetEndDate(2022, 9, 6) self.SetCash(100000) # PARAMETERS -> please see config.py # dividend data self.dividend_data:dict[datetime.date, List[str]] = {} # dict of lists indexed by paydate date # historical and live dividend payday data if config.IS_LIVE_MODE: # custom dividend data from_date:str = (self.Time - timedelta(days=config.D_WARMUP_PERIOD)).strftime("%Y-%m-%d") end_date:str = self.EndDate.date().strftime("%Y-%m-%d") self.Log(f'Requesting live dividend data...') self.live_dividend_symbol:Symbol = self.AddData(DividendDataLive, 'DividendDataLive', Resolution.Daily).Symbol self.Log(f'Requesting historical dividend data for dates from {from_date} to {end_date}...') # DividendDataHistory.set_dates(from_date, end_date) # self.historical_dividend_symbol:Symbol = self.AddData(DividendDataHistory, 'DividendDataHistory', Resolution.Daily).Symbol historical_data_file = self.Download(f"http://ec2-52-53-178-85.us-west-1.compute.amazonaws.com:8081/dividend_dates?from_date={from_date}&end_date={end_date}&fields=symbol,payment_date") historical_data = json.loads(historical_data_file) else: self.Log(f'Requesting historical dividend data for the whole history...') # TODO move json file to our FTP server? dividend_data_str:str = self.Download("data.quantpedia.com/backtesting_data/economic/dividend_data.json") historical_data = json.loads(dividend_data_str) # store historical dividend data in dictionary structure for ex_date_entry in historical_data: for stock_data in ex_date_entry['stocks']: payday:datetime.date = datetime.strptime(stock_data['payment_date'], "%m/%d/%Y").date() if stock_data['payment_date'] != 'N/A' else None if payday: ticker:str = stock_data['symbol'] if payday not in self.dividend_data: self.dividend_data[payday] = [] if ticker not in self.dividend_data[payday]: self.dividend_data[payday].append(ticker) self.Log(f'Historical dividend data loaded with {len(self.dividend_data)} date entries.') self.SMA_by_symbol:dict[Symbol, SimpleMovingAverage] = {} self.market = self.AddEquity('SPY', Resolution.Minute).Symbol self.SetWarmup(timedelta(days=max(config.MARKET_SMA_PERIOD, config.D_WARMUP_PERIOD) if config.MARKET_SMA_FILTER else config.D_WARMUP_PERIOD), Resolution.Daily) # storage of opened positions with trade open date self.opened_position_by_date:dict[datetime.date, list[Symbol]] = {} # store drip tickers # Source: http://www.dripdatabase.com/DRIP_Directory_AtoZ.aspx self.drip_tickers:list[str] = drip_tickers self.active_universe:list[Symbol] = [] # quarterly selected stock universe self.selection_flag:bool = True self.UniverseSettings.Resolution = Resolution.Minute self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) self.Schedule.On(self.DateRules.MonthEnd(self.market), self.TimeRules.AfterMarketOpen(self.market), self.Selection) self.Schedule.On(self.DateRules.EveryDay(self.market), self.TimeRules.BeforeMarketClose(self.market, 1), self.Rebalance) # self.Schedule.On(self.DateRules.EveryDay(self.market), self.TimeRules.BeforeMarketClose(self.market, 16), self.Rebalance) def OnSecuritiesChanged(self, changes) -> None: for security in changes.AddedSecurities: symbol:Symbol = security.Symbol security.SetLeverage(config.LEVERAGE * 2) # assign stock SMA indicators self.Log(f'Warming up SMA indicator for {symbol} ...') self.warmup_SMA(symbol) def CoarseSelectionFunction(self, coarse:List[CoarseFundamental]) -> List[Symbol]: if not self.selection_flag: return Universe.Unchanged self.selection_flag = False selected:List[Symbol] = [x.Symbol for x in coarse if x.Symbol.Value in self.drip_tickers] return selected def FineSelectionFunction(self, fine:List[FineFundamental]) -> List[Symbol]: fine:List[FineFundamental] = [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:List[FineFundamental] = sorted(fine, key = lambda x: x.MarketCap, reverse = True) half:int = int(len(sorted_by_market_cap) / 2) # pick lower half self.active_universe:List[Symbol] = [x.Symbol for x in sorted_by_market_cap[-half:]] self.Log(f'New universe selection done with {len(self.active_universe)} active universe symbols.') return self.active_universe def Rebalance(self) -> None: if self.IsWarmingUp: return # close opened positions lookup_date:datetime.date = (self.Time - BDay(config.DAILY_HOLDING_PERIOD)).date() dates_to_remove:list[datetime.date] = [] for open_date, holdings in self.opened_position_by_date.items(): if open_date <= lookup_date: dates_to_remove.append(open_date) for symbol in holdings: if self.Portfolio[symbol].Invested: q_invested:int = self.Portfolio[symbol].Quantity # self.MarketOnCloseOrder(symbol, -q_invested) self.MarketOrder(symbol, -q_invested) # self.Liquidate(symbol) for date in dates_to_remove: del self.opened_position_by_date[date] # do not trade if market is trading below its SMA if config.MARKET_SMA_FILTER: if self.SMA_by_symbol[self.market].IsReady: if self.Securities[self.market].Price < self.SMA_by_symbol[self.market].Current.Value: return else: return day_to_check:datetime.date = (self.Time.date() + BDay(config.N_DAYS_BEFORE_PAYDAY)).date() # there are stocks with payday next business day if day_to_check in self.dividend_data: # payday_tickers:list[str] = list(self.dividend_data[day_to_check].keys()) payday_tickers:list[str] = self.dividend_data[day_to_check] self.Log(f'{len(payday_tickers)} of tickers found for requested payday lookup date {day_to_check}.') long:list[Symbol] = [] for symbol in self.active_universe: if symbol.Value in payday_tickers: # consider only stocks with price > SMA if config.STOCK_SMA_FILTER: if symbol in self.SMA_by_symbol and self.SMA_by_symbol[symbol].IsReady and self.Securities[symbol].Price > self.SMA_by_symbol[symbol].Current.Value: long.append(symbol) else: long.append(symbol) if len(long) != 0: # portfolio_value:float = self.Portfolio.MarginRemaining / config.DAILY_HOLDING_PERIOD / len(long) portfolio_value:float = self.Portfolio.TotalPortfolioValue / config.DAILY_HOLDING_PERIOD / len(long) for symbol in long: price:float = self.Securities[symbol].Price if price != 0: # self.SetHoldings(symbol, 1 / config.DAILY_HOLDING_PERIOD / len(long)) q:float = portfolio_value / price * 0.9 if q >= 1.: # self.MarketOnCloseOrder(symbol, config.POSITION_DIRECTION * q * config.LEVERAGE) self.MarketOrder(symbol, config.POSITION_DIRECTION * q * config.LEVERAGE) self.opened_position_by_date[self.Time.date()] = long # remove already processed paydates from dividend data paydates_to_remove:List[datetime.date] = [x for x in list(self.dividend_data.keys()) if x <= day_to_check] self.Log(f"Removing {len(paydates_to_remove)} dividend payday entries older than {day_to_check}.") for paydate_to_remove in paydates_to_remove: del self.dividend_data[paydate_to_remove] else: self.Log(f"There're no tickers found for requested payday lookup date {day_to_check}") def OnData(self, data:Slice) -> None: # adjust SMA indicators when split/dividend event occurs for symbol in list(data.Splits.keys()) + list(data.Dividends.keys()): if symbol in self.SMA_by_symbol: self.Log(f'Split of dividend event occured for {symbol}. Recalculating SMA indicator...') self.SMA_by_symbol[symbol].Reset() self.warmup_SMA(symbol) # store live dividend data if config.IS_LIVE_MODE: if data.ContainsKey(self.live_dividend_symbol): self.Log(f'New live dividend data at {self.Time}.') for stock in data[self.live_dividend_symbol].GetProperty('stocks'): payday:datetime.date = datetime.strptime(stock['payment_date'], "%m/%d/%Y").date() if stock['payment_date'] != 'N/A' else None if payday: ticker:str = stock['symbol'] if payday not in self.dividend_data: self.dividend_data[payday] = [] if ticker not in self.dividend_data[payday]: self.dividend_data[payday].append(ticker) def Selection(self) -> None: if self.IsWarmingUp: return if self.Time.month % config.M_SELECTION_PERIOD == 0: self.selection_flag = True def warmup_SMA(self, symbol:Symbol) -> None: if symbol not in self.SMA_by_symbol: # init SMA if symbol == self.market: if config.MARKET_SMA_FILTER: self.SMA_by_symbol[symbol] = self.SMA(symbol, config.MARKET_SMA_PERIOD, Resolution.Daily) else: return else: if config.STOCK_SMA_FILTER: self.SMA_by_symbol[symbol] = self.SMA(symbol, config.STOCK_SMA_PERIOD, Resolution.Daily) else: return # warmup SMA data history:pd.DataFrame = self.History(symbol, config.STOCK_SMA_PERIOD, Resolution.Daily) if history.empty: self.Debug(f"No history for {symbol} yet") else: closes:pd.Series = history.loc[symbol].close for time, close in closes.iteritems(): self.SMA_by_symbol[symbol].Update(time, close)