Overall Statistics
Total Trades
88
Average Win
1.75%
Average Loss
-1.84%
Compounding Annual Return
-6.730%
Drawdown
24.800%
Expectancy
-0.024
Net Profit
-15.961%
Sharpe Ratio
-0.356
Probabilistic Sharpe Ratio
0.450%
Loss Rate
50%
Win Rate
50%
Profit-Loss Ratio
0.95
Alpha
-0.016
Beta
-0.245
Annual Standard Deviation
0.114
Annual Variance
0.013
Information Ratio
-0.862
Tracking Error
0.166
Treynor Ratio
0.166
Total Fees
$982.66
Estimated Strategy Capacity
$9600000.00
Lowest Capacity Asset
HAE R735QTJ8XC9X
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/Screener/Details/18
import math
import scipy as sp

class LiquidityEffectAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2016, 1, 1)   
        self.SetEndDate(2018, 7, 1)         
        self.SetCash(1000000)

        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)
        
        # Count the number of months that have passed since the algorithm starts
        self.months = -1
        self.yearly_rebalance = True
        self.num_coarse = 500
        self.long = None
        self.short = None
        
    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 Universe.Unchanged

    def FineSelectionFunction(self, fine):
        if self.yearly_rebalance:
            # 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.CompanyProfile.SharesOutstanding)
                    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]
                
            return self.long + self.short
        else:
            return Universe.Unchanged

    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 
        self.yearly_rebalance = False
            
        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))