| 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 Nonefrom 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)