Overall Statistics |
Total Orders 118 Average Win 0.49% Average Loss -0.54% Compounding Annual Return 6.241% Drawdown 10.100% Expectancy 0.286 Start Equity 100000 End Equity 108970.33 Net Profit 8.970% Sharpe Ratio 0.331 Sortino Ratio 0.315 Probabilistic Sharpe Ratio 25.107% Loss Rate 33% Win Rate 67% Profit-Loss Ratio 0.91 Alpha -0.045 Beta 0.72 Annual Standard Deviation 0.089 Annual Variance 0.008 Information Ratio -1.156 Tracking Error 0.064 Treynor Ratio 0.041 Total Fees $183.11 Estimated Strategy Capacity $50000000.00 Lowest Capacity Asset PG R735QTJ8XC9X Portfolio Turnover 3.31% |
#region imports from AlgorithmImports import * #endregion # https://quantpedia.com/Screener/Details/7 # The investment universe consists of global large cap stocks (or US large cap stocks). # At the end of the each month, the investor constructs equally weighted decile portfolios # by ranking the stocks on the past one year volatility of daily price. The investor # goes long stocks with the lowest volatility. class ShortTermReversalAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2016, 12, 31) # Set Start Date self.set_end_date(2018, 6, 1) # Set Start Date self.set_cash(100000) # Set Strategy Cash self._lookback = 252 self.universe_settings.resolution = Resolution.DAILY self.add_universe(self._coarse_selection_function, self._fine_selection_function) self._symbol_data_dict = {} self.add_equity("SPY", Resolution.DAILY) self.schedule.on(self.date_rules.month_start("SPY"),self.time_rules.after_market_open("SPY"), self._rebalance) def _coarse_selection_function(self, coarse): # drop stocks which have no fundamental data or have too low prices selected = [x for x in coarse if (x.has_fundamental_data) and (float(x.price) > 5)] # rank the stocks by dollar volume filtered = sorted(selected, key=lambda x: x.dollar_volume, reverse=True) return [x.symbol for x in filtered[:100]] def _fine_selection_function(self, fine): # filter stocks with the top market cap fine = [ x for x in fine if all([ not np.isnan(factor) for factor in [ x.earning_reports.basic_average_shares.three_months, x.earning_reports.basic_eps.twelve_months, x.valuation_ratios.pe_ratio ] ]) ] top = sorted(fine, key=lambda x: x.earning_reports.basic_average_shares.three_months * (x.earning_reports.basic_eps.twelve_months*x.valuation_ratios.pe_ratio), reverse=True) return [x.symbol for x in top[:50]] def _rebalance(self): sorted_symbol_data = sorted(self._symbol_data_dict, key=lambda x: self._symbol_data_dict[x].volatility()) # pick 5 stocks with the lowest volatility long_stocks = sorted_symbol_data[:5] stocks_invested = [x.key for x in self.portfolio if x.value.invested] # liquidate stocks not in the list for i in stocks_invested: if i not in long_stocks: self.liquidate(i) # long stocks with the lowest volatility by equal weighting for i in long_stocks: self.set_holdings(i, 1/5) def on_data(self, data): for symbol, symbol_data in self._symbol_data_dict.items(): if data.bars.contains_key(symbol): symbol_data.roc.update(self.time, self.securities[symbol].close) def on_securities_changed(self, changes): # clean up data for removed securities for removed in changes.removed_securities: symbol_data = self._symbol_data_dict.pop(removed.symbol, None) if symbol_data: symbol_data.dispose() # warm up the indicator with history price for newly added securities added_symbols = [x.symbol for x in changes.added_securities if x.symbol.value != "SPY"] history = self.history(added_symbols, self._lookback+1, Resolution.DAILY) for symbol in added_symbols: if symbol not in self._symbol_data_dict.keys(): symbol_data = SymbolData(symbol, self._lookback) self._symbol_data_dict[symbol] = symbol_data if str(symbol) in history.index: symbol_data.warm_up_indicator(history.loc[str(symbol)]) class SymbolData: '''Contains data specific to a symbol required by this model''' def __init__(self, symbol, lookback): self.symbol = symbol self.roc = RateOfChange(1) self.roc.updated += self._on_update self._roc_window = RollingWindow[IndicatorDataPoint](lookback) def _on_update(self, sender, updated): self._roc_window.add(updated) def warm_up_indicator(self, history): # warm up the RateOfChange indicator with the history request for t, row in history.iterrows(): self.roc.update(t, row.close) def volatility(self): data = [float(x.value) for x in self._roc_window] return np.std(data) def dispose(self): self.roc.updated -= self._on_update self._roc_window.reset()