| Overall Statistics |
|
Total Trades 494 Average Win 0.32% Average Loss -0.34% Compounding Annual Return 2.153% Drawdown 3.600% Expectancy 0.130 Net Profit 12.436% Sharpe Ratio 0.724 Loss Rate 42% Win Rate 58% Profit-Loss Ratio 0.94 Alpha 0.038 Beta -0.962 Annual Standard Deviation 0.028 Annual Variance 0.001 Information Ratio 0.058 Tracking Error 0.028 Treynor Ratio -0.021 Total Fees $567.69 |
#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.
class January_Effect(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2014, 1, 1)
self.SetEndDate(2019, 7, 1)
self.SetCash(100000)
self.quarters_count = 6
self.num_coarse = 100
self.num_fine = 50
self.symbol_ea = {} # contain the earnings acceleration for every stock
self.symbol_rw = {} # contain RollingWindow objects for every stock
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
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):
if self.quarterly_rebalance:
selected = [x for x in coarse if (x.HasFundamentalData) and (float(x.Price) > 5)]
filtered = sorted(selected, key=lambda x: x.DollarVolume, reverse=True)
self.filtered_coarse = [ x.Symbol for x in filtered[:self.num_coarse]]
return self.filtered_coarse
else:
return self.filtered_coarse
def FineSelectionFunction(self, fine):
if self.quarterly_rebalance:
# pre-select
selected = [x for x in fine if x.EarningReports.BasicEPS.ThreeMonths > 0]
for stock in selected:
if not stock.Symbol in self.symbol_rw.keys():
self.symbol_rw[stock.Symbol] = RollingWindow[float](self.quarters_count)
# update rolling window for every stock
self.symbol_rw[stock.Symbol].Add(stock.EarningReports.BasicEPS.ThreeMonths)
if self.symbol_rw[stock.Symbol].IsReady:
rw = self.symbol_rw[stock.Symbol]
eps_fraction1 = (rw[0] - rw[4]) / rw[1]
eps_fraction2 = (rw[1] - rw[5]) / rw[2]
self.symbol_ea[stock.Symbol] = eps_fraction1 - eps_fraction2 # That's the earnings acceleration we want
sorted_dict = sorted(self.symbol_ea.items(), key = lambda x: x[1], reverse = True)
self.filtered_fine = [x[0] for x in sorted_dict[:self.num_fine]]
self.quarterly_rebalance = False
#Log for validate
self.Log([x.Value for x in self.filtered_fine])
return self.filtered_fine
else:
return self.filtered_fine
def Rebalance(self):
if self.Time.month % 3 == 0:
self.quarterly_rebalance = True
else:
self.quarterly_rebalance = False
def OnData(self, data):
if self.quarterly_rebalance:
if len(self.symbol_ea) == 0:
return
decile_len = int(len(self.symbol_ea) / 10)
long_stocks = self.filtered_fine[:decile_len]
short_stocks = self.filtered_fine[-decile_len:]
# Close positions first
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for stock in stocks_invested:
if (stock not in long_stocks) and (stock not in short_stocks):
self.Liquidate(stock)
for long_stock in long_stocks:
if self.Portfolio[long_stock].IsShort:
self.Liquidate(long_stock)
if not self.Portfolio[long_stock].IsLong:
self.SetHoldings(long_stock, 1/(2*decile_len))
for short_stock in short_stocks:
if self.Portfolio[short_stock].IsLong:
self.Liquidate(short_stock)
if not self.Portfolio[short_stock].IsShort:
self.SetHoldings(short_stock, -1/(2*decile_len))
self.StartTime = self.Time
stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
# holding period is 1 month
if len(stocks_invested) > 0 and (self.Time - self.StartTime).days > 30:
for stock in stocks_invested:
self.Liquidate(stock)