Overall Statistics
Total Trades
1564
Average Win
0.79%
Average Loss
-0.76%
Compounding Annual Return
-0.357%
Drawdown
37.100%
Expectancy
-0.004
Net Profit
-7.108%
Sharpe Ratio
-0.032
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
1.04
Alpha
-0.003
Beta
0.019
Annual Standard Deviation
0.057
Annual Variance
0.003
Information Ratio
-0.4
Tracking Error
0.189
Treynor Ratio
-0.098
Total Fees
$2650.85
#The investment universe consists of NYSE, AMEX and NASDAQ firms that have stock returns data in CRSP. Financial and utility firms with SIC codes from 6000 to 6999
#and from 4900 to 4949 are excluded.
#Firstly, the earnings acceleration is calculated as a difference of two fractions, where the first fraction is Earnings per share (EPS) of stock i at quarter t minus the EPS of
#stock i at quarter t-4 divided by the stock price price at the end of quarter t-1. The second fraction is a difference of EPS of stock i at quarter t-1 and EPS of stock i at
#quarter t-5 divided by the stock price at the end of quarter t-2.
#Long the highest earnings acceleration decile and short the lowest earnings acceleration decile. Holding period is one month
#Porfolio is value-weighted.
from datetime import datetime

class January_Effect(QCAlgorithm):
    def Initialize(self):
        
        self.SetStartDate(1999, 1, 5) # Set Start Date
        self.SetEndDate(2019, 8, 6)   # Set End Date
        self.SetCash(100000)          # Set Strategy Cash
        
        self.quarters_count = 6       # Number of quarters to calculate the earning acceleration indicator
        self.num_coarse = 100         # Number of symbols selected at Coarse Selection
        self.num_fine = 50            # Number of symbols selected at Fine Selection
        
        self.epsBySymbol = {}         # Contains RollingWindow objects for every stock
        self.longSymbols = []         # Contains the stocks we'd like to long
        self.shortSymbols = []        # Contains the stocks we'd like to short
        
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        
        self.lastTradeTime = datetime.now() # Initialize last trade time
        
        self.AddEquity("SPY", Resolution.Daily)
        self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY"), self.Rebalance) # update quarterly_rebalance
        self.quarterly_rebalance = True
        
    def CoarseSelectionFunction(self, coarse):
        '''Drop securities which have no fundamental data or have too low prices.
        Select those with highest by dollar volume'''
        
        if not self.quarterly_rebalance:
            return Universe.Unchanged

        self.quarterly_rebalance = False
        
        selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5],
            key=lambda x: x.DollarVolume, reverse=True)
            
        return [x.Symbol for x in selected[:self.num_coarse]]
        
    def FineSelectionFunction(self, fine):
        '''Select security with highest earnings acceleration'''

        eaBySymbol = dict()
        
        # pre-select
        selected = [x for x in fine if x.EarningReports.BasicEPS.ThreeMonths > 0]
        for stock in selected:
            if not stock.Symbol in self.epsBySymbol:
                self.epsBySymbol[stock.Symbol] = RollingWindow[float](self.quarters_count)
            # update rolling window for every stock
            self.epsBySymbol[stock.Symbol].Add(stock.EarningReports.BasicEPS.ThreeMonths)

            if self.epsBySymbol[stock.Symbol].IsReady:
                rw = self.epsBySymbol[stock.Symbol]
                eps_fraction1 = (rw[0] - rw[4]) / rw[1]
                eps_fraction2 = (rw[1] - rw[5]) / rw[2]
                
                eaBySymbol[stock.Symbol] = eps_fraction1 - eps_fraction2 # That's the earnings acceleration we want

        sorted_dict = sorted(eaBySymbol.items(), key = lambda x: x[1], reverse = True)

        symbols = [x[0] for x in sorted_dict[:self.num_fine]]

        decile_len = int(len(symbols) / 10)
        self.longSymbols = symbols[:decile_len]
        self.shortSymbols = symbols[-decile_len:]

        #Log for validate
        self.Log(','.join(sorted([x.Value for x in self.longSymbols + self.shortSymbols])))
        return self.longSymbols + self.shortSymbols


    def Rebalance(self):
        if self.Time.month % 3 == 0:
            self.quarterly_rebalance = True
        else:
            self.quarterly_rebalance = False
            
    def OnData(self, data):

        # 31 days after we open the positions, we liquidate.
        if (self.Time - self.lastTradeTime).days > 31:
            for holding in self.Portfolio.Values:
                if holding.Invested:
                    self.Liquidate(holding.Symbol)

        if not self.quarterly_rebalance:
            return

        count = len(self.longSymbols) + len(self.shortSymbols)

        for symbol in self.longSymbols:
            self.SetHoldings(symbol, 1/count)

        for symbol in self.shortSymbols:
            self.SetHoldings(symbol, -1/count)

        # Last Trade time
        self.lastTradeTime = self.Time