| 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))