Overall Statistics |
Total Orders 76 Average Win 1.68% Average Loss -1.10% Compounding Annual Return 4.836% Drawdown 13.200% Expectancy 0.380 Start Equity 1000000 End Equity 1125103.02 Net Profit 12.510% Sharpe Ratio 0.249 Sortino Ratio 0.314 Probabilistic Sharpe Ratio 12.842% Loss Rate 45% Win Rate 55% Profit-Loss Ratio 1.53 Alpha 0.034 Beta -0.132 Annual Standard Deviation 0.092 Annual Variance 0.008 Information Ratio -0.451 Tracking Error 0.144 Treynor Ratio -0.172 Total Fees $1237.37 Estimated Strategy Capacity $8900000.00 Lowest Capacity Asset HAE R735QTJ8XC9X Portfolio Turnover 0.65% |
#region imports from AlgorithmImports import * #endregion # https://quantpedia.com/Screener/Details/18 class LiquidityEffectAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2016, 1, 1) self.set_end_date(2018, 7, 1) self.set_cash(1000000) self.universe_settings.resolution = Resolution.DAILY self.add_universe(self._coarse_selection_function, self._fine_selection_function) self.add_equity("SPY", Resolution.DAILY) self.schedule.on(self.date_rules.month_start("SPY"), self.time_rules.after_market_open("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 _coarse_selection_function(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.has_fundamental_data) and (float(x.adjusted_price) > 5)] # rank the stocks by dollar volume filtered = sorted(selected, key=lambda x: x.dollar_volume, reverse=True) self.filtered_coarse = [ x.symbol for x in filtered[:self._num_coarse]] return self.filtered_coarse else: return Universe.UNCHANGED def _fine_selection_function(self, fine): if self._yearly_rebalance: # The market capitalization must be no less than $10 million top_market_cap = list(filter(lambda x: x.market_cap > 10000000, fine)) # Save all market cap values market_caps = [i.market_cap 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.market_cap <= 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.company_profile.shares_outstanding) 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 on_data(self, data): if not self._yearly_rebalance: return self._yearly_rebalance = False if self._long and self._short: stocks_invested = [symbol for symbol, security_holding in self.portfolio.items() if security_holding.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.set_holdings(short_stock, -0.5/len(self._short)) # short on stocks with the highest turnover for long_stock in self._long: self.set_holdings(long_stock, 0.5/len(self._long))