Overall Statistics
Total Trades
18722
Average Win
0.22%
Average Loss
-0.19%
Compounding Annual Return
19.682%
Drawdown
41.200%
Expectancy
0.116
Net Profit
594.118%
Sharpe Ratio
0.96
Probabilistic Sharpe Ratio
34.301%
Loss Rate
48%
Win Rate
52%
Profit-Loss Ratio
1.15
Alpha
0.1
Beta
0.48
Annual Standard Deviation
0.151
Annual Variance
0.023
Information Ratio
0.329
Tracking Error
0.154
Treynor Ratio
0.302
Total Fees
$286653.11
Estimated Strategy Capacity
$3000.00
Lowest Capacity Asset
PWOD S9QGEXER1E1X
#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(2012, 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, 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)
        
        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)

                for symbol in long:
                    price:float = self.Securities[symbol].Price
                    if price != 0:
                        q:float = portfolio_value / price
                        if q >= 1.:
                            self.MarketOnCloseOrder(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)