Overall Statistics
Total Trades
50
Average Win
0.69%
Average Loss
-0.24%
Compounding Annual Return
48.942%
Drawdown
6.700%
Expectancy
1.297
Net Profit
8.176%
Sharpe Ratio
2.108
Loss Rate
40%
Win Rate
60%
Profit-Loss Ratio
2.83
Alpha
0.749
Beta
-22.963
Annual Standard Deviation
0.169
Annual Variance
0.028
Information Ratio
2.006
Tracking Error
0.169
Treynor Ratio
-0.015
Total Fees
$63.07
data = {'COUP': '12/3/2018\n', 'SMAR': '12/3/2018\n', 'RH': '12/3/2018\n', 'FNSR': '12/3/2018\n', 'ZS': '12/4/2018\n', 'GWRE': '12/4/2018\n', 'MDB': '12/4/2018\n', 'TOL': '12/4/2018\n', 'HQY': '12/4/2018\n', 'AZO': '12/4/2018\n', 'HPE': '12/4/2018\n', 'MOV': '12/4/2018\n', 'OLLI': '12/4/2018\n', 'MRVL': '12/4/2018\n', 'HDS': '12/4/2018\n', 'CONN': '12/4/2018\n', 'BMO': '12/4/2018\n', 'DG': '12/4/2018\n', 'DCI': '12/4/2018\n', 'GMS': '12/4/2018\n', 'CLDR': '12/5/2018\n', 'OKTA': '12/5/2018\n', 'FIVE': '12/5/2018\n', 'NTIOF': '12/5/2018\n', 'BF.B': '12/5/2018\n', 'SNPS': '12/5/2018\n', 'GEF': '12/5/2018\n', 'CMTL': '12/6/2018\n', 'SCWX': '12/6/2018\n', 'DOCU': '12/6/2018\n', 'AOBC': '12/6/2018\n', 'DOMO': '12/6/2018\n', 'HOME': '12/6/2018\n', 'VRNT': '12/6/2018\n', 'SAIC': '12/6/2018\n', 'ZUMZ': '12/6/2018\n', 'MEI': '12/6/2018\n', 'KR': '12/6/2018\n', 'GCO': '12/6/2018\n', 'HRB': '12/6/2018\n', 'MIK': '12/6/2018\n', 'LULU': '12/6/2018\n', 'PDCO': '12/6/2018\n', 'KFY': '12/6/2018\n', 'AVGO': '12/6/2018\n', 'GIII': '12/6/2018\n', 'SIG': '12/6/2018\n', 'ULTA': '12/6/2018\n', 'BPMX': '12/6/2018\n', 'DLTH': '12/6/2018\n', 'PLCE': '12/6/2018\n', 'JW.A': '12/6/2018\n', 'COO': '12/6/2018\n', 'FIZZ': '12/6/2018\n', 'TTC': '12/6/2018\n', 'THO': '12/6/2018\n', 'UNFI': '12/6/2018\n', 'FGP': '12/6/2018\n', 'MTN': '12/7/2018\n', 'BIG': '12/7/2018\n', 'SFIX': '12/10/2018\n', 'ASNA': '12/10/2018\n', 'CDMO': '12/10/2018\n', 'CASY': '12/10/2018\n', 'ASPU': '12/10/2018\n', 'SEAC': '12/10/2018\n', 'NX': '12/10/2018\n', 'IRET': '12/10/2018\n', 'PVTL': '12/11/2018\n', 'PLAY': '12/11/2018\n', 'DSW': '12/11/2018\n', 'ASHTY': '12/11/2018\n', 'FRAN': '12/11/2018\n', 'AEO': '12/11/2018\n', 'MMMB': '12/11/2018\n', 'SMMT': '12/11/2018\n', 'TLRD': '12/12/2018\n', 'PLAB': '12/12/2018\n', 'SKIS': '12/12/2018\n', 'NDSN': '12/12/2018\n', 'OXM': '12/12/2018\n', 'VRA': '12/12/2018\n', 'CHKE': '12/13/2018\n', 'CIEN': '12/13/2018\n', 'COST': '12/13/2018\n', 'ADBE': '12/13/2018\n', 'KALV': '12/14/2018\n', 'VIRC': '12/14/2018\n', 'UBA': '12/14/2018\n', 'RHT': '12/17/2018\n', 'ORCL': '12/17/2018\n', 'HEI': '12/17/2018\n', 'LAKE': '12/17/2018\n', 'CSBR': '12/17/2018\n', 'SCS': '12/18/2018\n', 'LOVE': '12/18/2018\n', 'NAV': '12/18/2018\n', 'JBL': '12/18/2018\n', 'AIR': '12/18/2018\n', 'FDS': '12/18/2018\n', 'ABM': '12/18/2018\n', 'MU': '12/18/2018\n', 'DRI': '12/18/2018\n', 'FDX': '12/18/2018\n', 'WOR': '12/18/2018\n', 'RAD': '12/19/2018\n', 'WGO': '12/19/2018\n', 'GIS': '12/19/2018\n', 'MLHR': '12/19/2018\n', 'PAYX': '12/19/2018\n', 'NCS': '12/19/2018\n', 'REVG': '12/19/2018\n', 'PIR': '12/19/2018\n', 'BB': '12/20/2018\n', 'CAG': '12/20/2018\n', 'NKE': '12/20/2018\n', 'ATU': '12/20/2018\n', 'ACN': '12/20/2018\n', 'CAMP': '12/20/2018\n', 'NEOG': '12/20/2018\n', 'CTAS': '12/20/2018\n', 'WBA': '12/20/2018\n', 'CCL': '12/20/2018\n', 'SCHL': '12/20/2018\n', 'APOG': '12/20/2018\n', 'SAFM': '12/20/2018\n', 'SHLO': '12/20/2018\n', 'KMX': '12/21/2018\n'}
from QuantConnect.Data.UniverseSelection import *
import math
import numpy as np
import pandas as pd
import talib

import time
from datetime import timedelta, date, datetime

from data import data
from SP500 import sp500

class FundamentalFactorAlgorithm(QCAlgorithm):

    def Initialize(self):
        #Dropbox url: https://www.dropbox.com/s/hl38drkfweosmur/earnings_dates_6months.txt?dl=0
        dates = self.Download('https://www.dropbox.com/s/5mw3jvm6wvabqo0/earnings_6month.csv?dl=1')
        dates = dates.split('\n')
        
        #self.Log(dates)
        #self.Debug(self.earnings[0])
        
        self.SetStartDate(2017, 12, 20)
        self.SetEndDate(2018, 3, 1)
        #self.SetStartDate(2018, 11, 15)
        #self.SetEndDate(2019, 1, 1)
        self.SetCash(100000)
        
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        #self.AddUniverse(self.FineSelectionFunction)
        
        '''
        # In your initialize method:
        # Chart - Master Container for the Chart:
        stockPlot = Chart("Trade Plot")
        # On the Trade Plotter Chart we want 3 series: trades and price:
        stockPlot.AddSeries(Series("Buy", SeriesType.Scatter, 0))
        stockPlot.AddSeries(Series("Sell", SeriesType.Scatter, 0))
        stockPlot.AddSeries(Series("Price", SeriesType.Line, 0))
        self.AddChart(stockPlot)
        '''
        
        self.sample_size = 10
        '''
        for ticker in list(data):
            self.AddEquity(ticker)
        '''
        
        self.AddEquity("SPY", Resolution.Daily)
        self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen("SPY", 15), Action(self.onOpen))
        self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.BeforeMarketClose("SPY", 60), Action(self.beforeClose))
        
        self.universe = []
        self.filtered = []
        self.traded = False
        
        '''
        RSI max/min
        '''
        self.rsi_max = 60
        self.rsi_min = 30

        '''
        Set the holding period in days (prior to earnings)
        '''
        self.holding_period = 10
        
        self.history_period = 30 * 2
        
        self.stop_percent = .05
        self.target_percent = 0.15
        
        #P/L
        self.pl = {}
        
        self.logger = True
        if self.logger:
            self.Debug('S&P 500 length: {0}'.format(len(sp500)))

        self.data = data
        self.data = {}
        
        for line in dates:
            #self.Debug(line.split(','))
            try:
                split = line.split(',')
                ticker = split[0]
                date = split[1]
                if ticker in list(self.data):
                    pass
                else:
                    self.data[ticker]=date
                #self.Debug('TICKER: {0} DATE: {1}'.format(ticker, date))
            except:
                pass
    
    def getEarnings(self, ticker):
        if ticker in list(self.data):
            return date(int(self.data[ticker].split('/')[2].split('\n')[0]), int(self.data[ticker].split('/')[0]), int(self.data[ticker].split('/')[1]))
        else:
            return None
    
    def CoarseSelectionFunction(self, coarse):
        return [ x.Symbol for x in coarse if x.Symbol.Value in sp500]
    
    def FineSelectionFunction(self, fine):
        #fine.OperationRatios.NetIncomeGrowth.OneMonth
        ##fine.OperationRatios.RevenueGrowth.OneMonth
        
        self.universe = [ x.Symbol for x in fine ]
        return self.universe
    
    '''
    def FineSelectionFunction(self, fine):
        #fine = [x for x in fine if self.Time > x.EarningReports.FileDate + timedelta(days=0)and x.EarningReports.FileDate != datetime.time()]
        #fine = [x for x in fine if x.EarningReports.FileDate == datetime.time()]
        self.Debug(fine[0].EarningsReports)
        fine = [x for x in fine if x.Symbol.Value in sp500]
        #today_ = datetime.time()
        for item in fine:
            self.Log('Symbole {0} Earnings date: {1} Today: {2}'.format(item.Symbol.Value, item.EarningsReports.FileDate, datetime.time()))
        return [i.Symbol for i in fine]
    '''
    
    def onOpen(self):
        for stock in list(self.pl):
            if self.pl[stock]<1:
                self.Liquidate(stock)
            else:
                self.pl[stock]-=1
        
        self.filtered = []
        current_day = date(self.Time.year, self.Time.month, self.Time.day)
        '''
        Loop through SP500 for earnings date that is within holding period and append to filtered
        '''
        #for ticker in sp500:
        for symbol in self.universe:
            next_earnings = self.getEarnings(symbol.Value)
            if next_earnings is not None:
                offset = next_earnings - current_day
                #self.Debug('ticker: {2} next_earnings_date: {0} offset: {1}'.format(next_earnings, offset, ticker))
                if int(offset.days)==self.holding_period:
                    #self.AddEquity(ticker, True)
                    self.filtered.append(symbol)

    def beforeClose(self):
        if self.filtered:
            self.Log('filtered: {0}'.format(self.filtered))
            
            for symbol in self.filtered:
                if not self.Securities.ContainsKey(symbol): continue
                if not self.Securities[symbol].IsTradable: continue
            
                #hist = self.History(self.Symbol(ticker), self.history_period)
                hist = self.History(symbol, self.history_period)
                #self.Log(hist)
                current = self.Securities[symbol].Price
                
                #close_avg = hist['close'].mean()
                #open_avg = hist['open'].mean()
                
                close = np.array(hist['close'])
                open_ = np.array(hist['open'])
                #high = np.array(hist['high'])
                #low = np.array(hist['low'])
                
                '''
                Position based on close averages
                
                if current>close_avg:
                    self.SetHoldings(ticker, .15)
                elif current<close_avg:
                    self.SetHoldings(ticker, -.15)
                '''
                
                '''
                Position based on SMA
                sma = talib.SMA(close)
                '''
                
                '''
                Position based on RSI
                '''
                rsi = talib.RSI(close, timeperiod=14)
                if rsi[-1]>self.rsi_max:
                    pass
                elif rsi[-1]<self.rsi_min:
                    pass
                else:
                    continue

                '''
                Holding period new high/low
                '''
                if current>max(close[:self.holding_period]) and current>max(open_[:self.holding_period]):
                    #self.SetHoldings(symbol, .10)
                    self.MarketOrder(symbol, 10000/current)
                    self.Plot("Trade Plot", "Buy", current)
                    #self.pl[symbol.Value]=current
                elif current<min(close[:self.holding_period]) and current<min(open_[:self.holding_period]):
                    #self.SetHoldings(symbol, -.10)
                    self.MarketOrder(symbol, -10000/current)
                    self.Plot("Trade Plot", "Sell", current)
                    #self.pl[symbol.Value]=-current

    def OnOrderEvent(self, orderEvent):
        s = orderEvent.Symbol
        if orderEvent.FillQuantity<0:
            self.pl[s.Value]=orderEvent.FillPrice
            self.Debug(orderEvent.FillPrice)
        elif orderEvent.FillQuantity>0:
            self.pl[s.Value]=orderEvent.FillPrice
            self.Debug(orderEvent.FillPrice)

    def OnData(self, data):
        pass
    '''
    def OnOrderEvent(self, orderEvent):
        s = orderEvent.Symbol
        if orderEvent.FillQuantity < 0:
            self.shorts[s.Value]=float(orderEvent.FillPrice)
            self.level[s.Value]=[False,False, 91]
        if self.log:
            self.Log(str(orderEvent))
    '''
    def OnEndOfDay(self):
       #Log the end of day prices:
       #self.Plot("Trade Plot", "Price", self.lastPrice)
       for position in list(self.pl):
           if not self.Securities.ContainsKey(position): continue
           if not self.Securities[position].IsTradable: continue
           current = self.Securities[position].Price
           entry = self.pl[position]
           if entry<0:  #and self.Portfolio[position].Invested:
               target = entry - (entry*self.target_percent)
               exit = entry + (entry*self.stop_percent)
               if current < target or current > exit:
                   self.Liquidate(position)
           elif entry>0:
               target = entry + (entry*self.target_percent)
               exit = entry - (entry*self.stop_percent)
               if target < current or current < exit:
                   self.Liquidate(position)
sp500=['MMM','AOS','ABT','ABBV','ACN','ATVI','AYI','ADBE','AAP','AMD','AES','AET','AMG','AFL','A','APD','AKAM','ALK','ALB','ARE','ALXN','ALGN','ALLE','AGN','ADS','LNT','ALL','GOOGL','GOOG','MO','AMZN','AEE','AAL','AEP','AXP','AIG','AMT','AWK','AMP','ABC','AME','AMGN','APH','APC','ADI','ANDV','ANSS','ANTM','AON','APA','AIV','AAPL','AMAT','APTV','ADM','ARNC','AJG','AIZ','T','ADSK','ADP','AZO','AVB','AVY','BHGE','BLL','BAC','BAX','BBT','BDX','BRK.B','BBY','BIIB','BLK','HRB','BA','BKNG','BWA','BXP','BSX','BHF','BMY','AVGO','BF.B','CHRW','CA','COG','CDNS','CPB','COF','CAH','KMX','CCL','CAT','CBOE','CBRE','CBS','CELG','CNC','CNP','CTL','CERN','CF','SCHW','CHTR','CVX','CMG','CB','CHD','CI','XEC','CINF','CTAS','CSCO','C','CFG','CTXS','CME','CMS','KO','CTSH','CL','CMCSA','CMA','CAG','CXO','COP','ED','STZ','GLW','COST','COTY','CCI','CSRA','CSX','CMI','CVS','DHI','DHR','DRI','DVA','DE','DAL','XRAY','DVN','DLR','DFS','DISCA','DISCK','DISH','DG','DLTR','D','DOV','DWDP','DPS','DTE','DUK','DRE','DXC','ETFC','EMN','ETN','EBAY','ECL','EIX','EW','EA','EMR','ETR','EVHC','EOG','EQT','EFX','EQIX','EQR','ESS','EL','RE','ES','EXC','EXPE','EXPD','ESRX','EXR','XOM','FFIV','FB','FAST','FRT','FDX','FIS','FITB','FE','FISV','FLIR','FLS','FLR','FMC','FL','F','FTV','FBHS','BEN','FCX','GPS','GRMN','IT','GD','GE','GGP','GIS','GM','GPC','GILD','GPN','GS','GT','GWW','HAL','HBI','HOG','HRS','HIG','HAS','HCA','HCP','HP','HSIC','HES','HPE','HLT','HOLX','HD','HON','HRL','HST','HPQ','HUM','HBAN','HII','IDXX','INFO','ITW','ILMN','INCY','IR','INTC','ICE','IBM','IP','IPG','IFF','INTU','ISRG','IVZ','IPGP','IQV','IRM','JBHT','JEC','SJM','JNJ','JCI','JPM','JNPR','KSU','K','KEY','KMB','KIM','KMI','KLAC','KSS','KHC','KR','LB','LLL','LH','LRCX','LEG','LEN','LUK','LLY','LNC','LKQ','LMT','L','LOW','LYB','MTB','MAC','M','MRO','MPC','MAR','MMC','MLM','MAS','MA','MAT','MKC','MCD','MCK','MDT','MRK','MET','MTD','MGM','KORS','MCHP','MU','MSFT','MAA','MHK','TAP','MDLZ','MON','MNST','MCO','MS','MSI','MYL','NDAQ','NOV','NAVI','NKTR','NTAP','NFLX','NWL','NFX','NEM','NWSA','NWS','NEE','NLSN','NKE','NI','NBL','JWN','NSC','NTRS','NOC','NCLH','NRG','NUE','NVDA','ORLY','OXY','OMC','OKE','ORCL','PCAR','PKG','PH','PAYX','PYPL','PNR','PBCT','PEP','PKI','PRGO','PFE','PCG','PM','PSX','PNW','PXD','PNC','RL','PPG','PPL','PX','PFG','PG','PGR','PLD','PRU','PEG','PSA','PHM','PVH','QRVO','QCOM','PWR','DGX','RRC','RJF','RTN','O','RHT','REG','REGN','RF','RSG','RMD','RHI','ROK','COL','ROP','ROST','RCL','SPGI','CRM','SBAC','SCG','SLB','STX','SEE','SRE','SHW','SPG','SWKS','SLG','SNA','SO','LUV','SWK','SBUX','STT','SRCL','SYK','STI','SIVB','SYMC','SYF','SNPS','SYY','TROW','TTWO','TPR','TGT','TEL','FTI','TXN','TXT','BK','CLX','COO','HSY','MOS','TRV','DIS','TMO','TIF','TWX','TJX','TMK','TSS','TSCO','TDG','TRIP','FOXA','FOX','TSN','USB','UDR','ULTA','UAA','UA','UNP','UAL','UNH','UPS','URI','UTX','UHS','UNM','VFC','VLO','VAR','VTR','VRSN','VRSK','VZ','VRTX','VIAB','V','VNO','VMC','WMT','WBA','WM','WAT','WEC','WFC','WELL','WDC','WU','WRK','WY','WHR','WMB','WLTW','WYN','WYNN','XEL','XRX','XLNX','XL','XYL','YUM','ZBH','ZION','ZTS']