Overall Statistics |
Total Orders 757 Average Win 0.86% Average Loss -1.08% Compounding Annual Return 15.758% Drawdown 27.600% Expectancy 0.327 Start Equity 100000 End Equity 432546.45 Net Profit 332.546% Sharpe Ratio 0.638 Sortino Ratio 0.7 Probabilistic Sharpe Ratio 10.097% Loss Rate 26% Win Rate 74% Profit-Loss Ratio 0.79 Alpha 0.002 Beta 1.164 Annual Standard Deviation 0.177 Annual Variance 0.031 Information Ratio 0.165 Tracking Error 0.105 Treynor Ratio 0.097 Total Fees $3051.03 Estimated Strategy Capacity $86000000.00 Lowest Capacity Asset AMT RBASL7V8PIZP Portfolio Turnover 2.50% |
#region imports from AlgorithmImports import * #endregion # https://quantpedia.com/Screener/Details/14 class MomentumEffectAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2009, 7, 1) # Set Start Date self.set_end_date(2019, 7, 1) # Set Start Date self.set_cash(100000) # Set Strategy Cash self.universe_settings.resolution = Resolution.DAILY self._momp = {} # Dict of Momentum indicator keyed by Symbol self._lookback = 252 # Momentum indicator lookback period self._num_coarse = 100 # Number of symbols selected at Coarse Selection self._num_fine = 50 # Number of symbols selected at Fine Selection self._num_long = 5 # Number of symbols with open positions self._month = -1 self._rebalance = False self.add_universe(self._coarse_selection_function, self._fine_selection_function) def _coarse_selection_function(self, coarse): '''Drop securities which have no fundamental data or have too low prices. Select those with highest by dollar volume''' if self._month == self.time.month: return Universe.UNCHANGED self._rebalance = True self._month = self.time.month selected = sorted([x for x in coarse if x.has_fundamental_data and x.price > 5], key=lambda x: x.dollar_volume, reverse=True) return [x.symbol for x in selected[:self._num_coarse]] def _fine_selection_function(self, fine): '''Select security with highest market cap''' selected = sorted(fine, key=lambda f: f.market_cap, reverse=True) return [x.symbol for x in selected[:self._num_fine]] def on_data(self, data): # Update the indicator for symbol, mom in self._momp.items(): mom.update(self.time, self.securities[symbol].close) if not self._rebalance: return # Selects the securities with highest momentum sorted_mom = sorted([k for k,v in self._momp.items() if v.is_ready], key=lambda x: self._momp[x].current.value, reverse=True) selected = sorted_mom[:self._num_long] # Liquidate securities that are not in the list for symbol, mom in self._momp.items(): if symbol not in selected: self.liquidate(symbol, 'Not selected') # Buy selected securities for symbol in selected: self.set_holdings(symbol, 1/self._num_long) self._rebalance = False def on_securities_changed(self, changes): # Clean up data for removed securities and Liquidate for security in changes.removed_securities: symbol = security.symbol if self._momp.pop(symbol, None) is not None: self.liquidate(symbol, 'Removed from universe') for security in changes.added_securities: if security.symbol not in self._momp: self._momp[security.symbol] = MomentumPercent(self._lookback) # Warm up the indicator with history price if it is not ready added_symbols = [k for k,v in self._momp.items() if not v.is_ready] history = self.history(added_symbols, 1 + self._lookback, Resolution.DAILY) history = history.close.unstack(level=0) for symbol in added_symbols: ticker = symbol.id.to_string() if ticker in history: for time, value in history[ticker].dropna().items(): item = IndicatorDataPoint(symbol, time.date(), value) self._momp[symbol].update(item)