Overall Statistics
Total Trades
48
Average Win
2.75%
Average Loss
-2.67%
Compounding Annual Return
5.221%
Drawdown
11.600%
Expectancy
0.255
Net Profit
13.542%
Sharpe Ratio
0.527
Loss Rate
38%
Win Rate
62%
Profit-Loss Ratio
1.03
Alpha
0.069
Beta
-0.712
Annual Standard Deviation
0.105
Annual Variance
0.011
Information Ratio
0.341
Tracking Error
0.105
Treynor Ratio
-0.078
Total Fees
$700.62
# https://quantpedia.com/Screener/Details/18
from QuantConnect.Data.UniverseSelection import *
import math
import numpy as np
import pandas as pd
import scipy as sp

class LiquidityEffectAlgorithm(QCAlgorithm):

    def Initialize(self):

        self.SetStartDate(2016, 1, 1)   
        self.SetEndDate(2018, 7, 1)         
        self.SetCash(1000000)            
        self.num_coarse = 500
        self.UniverseSettings.Resolution = Resolution.Daily
        self.long = None
        self.short = None
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        self.AddEquity("SPY", Resolution.Daily)
        self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY"), self.rebalance)
        # Count the number of months that have passed since the algorithm starts
        self.months = -1
        self.yearly_rebalance = True
    def CoarseSelectionFunction(self, coarse):
        if self.yearly_rebalance:
            # drop stocks which have no fundamental data or have low price
            selected = [x for x in coarse if (x.HasFundamentalData) and (float(x.AdjustedPrice) > 5)]
            # rank the stocks by dollar volume 
            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.yearly_rebalance:
            # Calculate the market cap and add the "MakretCap" property to fine universe object
            for i in fine:
                i.MarketCap = float(i.EarningReports.BasicAverageShares.ThreeMonths * (i.EarningReports.BasicEPS.TwelveMonths*i.ValuationRatios.PERatio))
            # The market capitalization must be no less than $10 million
            top_market_cap = list(filter(lambda x: x.MarketCap > 10000000, fine))
            # Save all market cap values
            market_caps = [i.MarketCap for i in top_market_cap]
            # Calculate the lowest market-cap quartile
            lowest_quartile = np.percentile(market_caps, 25)
            # Filter stocks in the lowest market-cap quartile
            lowest_market_cap = list(filter(lambda x: x.MarketCap <= lowest_quartile, top_market_cap))
            turnovers = []
            # Divide into quartiles based on their turnover (the number of shares traded divided by the stock’s outstanding shares) in the last 12 months
            for i in lowest_market_cap[:]:
                hist = self.History([i.Symbol], 21*12, Resolution.Daily)
                if not hist.empty:    
                    mean_volume = np.mean(hist.loc[str(i.Symbol)]['volume'])
                    i.Turnover =  mean_volume / float(i.EarningReports.BasicAverageShares.ThreeMonths)
                    turnovers.append(i.Turnover)
                else:
                    lowest_market_cap.remove(i)
            bottom_turnover = np.percentile(turnovers, 5) 
            top_turnover = np.percentile(turnovers, 95) 
            self.long = [x.Symbol for x in lowest_market_cap if x.Turnover < bottom_turnover]
            self.short = [x.Symbol for x in lowest_market_cap if x.Turnover > top_turnover]
                
            self.yearly_rebalance = False
            
            return self.long+self.short
        else:
            return self.long+self.short

    def rebalance(self):
        # yearly rebalance
        self.months += 1
        if self.months%12 == 0:
            self.yearly_rebalance = True


    def OnData(self, data):
        if not self.yearly_rebalance: return 
        if self.long and self.short:
            stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
            # liquidate stocks not in the trading list 
            for i in stocks_invested:
                if i not in self.long+self.short:
                    self.Liquidate(i)   
            # goes long on stocks with the lowest turnover    
            for short_stock in self.short:
                self.SetHoldings(short_stock, -0.5/len(self.short))            
            # short on stocks with the highest turnover        
            for long_stock in self.long:
                self.SetHoldings(long_stock, 0.5/len(self.long))