| 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