Overall Statistics |
Total Orders 540 Average Win 2.02% Average Loss -2.24% Compounding Annual Return -18.301% Drawdown 81.600% Expectancy -0.075 Start Equity 1000000 End Equity 445280.02 Net Profit -55.472% Sharpe Ratio -0.155 Sortino Ratio -0.196 Probabilistic Sharpe Ratio 0.388% Loss Rate 51% Win Rate 49% Profit-Loss Ratio 0.90 Alpha -0.059 Beta -0.061 Annual Standard Deviation 0.427 Annual Variance 0.183 Information Ratio -0.4 Tracking Error 0.465 Treynor Ratio 1.095 Total Fees $13309.16 Estimated Strategy Capacity $25000.00 Lowest Capacity Asset MYOS VS3HQVEKIZJ9 Portfolio Turnover 2.60% |
#region imports from AlgorithmImports import * #endregion # https://quantpedia.com/Screener/Details/77 class BetaFactorInStocks(QCAlgorithm): def initialize(self): self.set_start_date(2018, 1, 1) self.set_end_date(2022, 1, 1) self.set_cash(1000000) self.set_security_initializer(BrokerageModelSecurityInitializer( self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))) self.universe_settings.resolution = Resolution.DAILY self.add_universe(self._coarse_selection_function) # Add Wilshire 5000 Total Market Index data from Dropbox self._price5000 = self.add_data(Fred, Fred.Wilshire.PRICE_5000, Resolution.DAILY).symbol # Setup a RollingWindow to hold market return self._market_return = RollingWindow[float](252) # Use a ROC indicator to convert market price index into return, and save it to the RollingWindow self._roc = self.roc(self._price5000, 1) self._roc.updated += lambda _, updated: self._market_return.add(updated.value) # Warm up hist = self.history(self._price5000, 253, Resolution.DAILY) for t, value in hist.loc[self._price5000]['value'].items(): self._roc.update(t, value) self._data = {} self._monthly_rebalance = False self._long = None self._short = None spy = Symbol.create("SPY", SecurityType.EQUITY, Market.USA) self.schedule.on(self.date_rules.month_start(spy), self.time_rules.after_market_open(spy), self._rebalance) self.set_warm_up(timedelta(365)) def _coarse_selection_function(self, coarse): for c in coarse: if c.symbol not in self._data: self._data[c.symbol] = SymbolData(c.symbol) self._data[c.symbol].update(c.end_time, c.adjusted_price) if self._monthly_rebalance: filtered_data = {symbol: data for symbol, data in self._data.items() if data.last_price > 5 and data.is_ready()} if len(filtered_data) > 10: # sort the dictionary in ascending order by beta value sorted_beta = sorted(filtered_data, key=lambda x: filtered_data[x].beta(self._market_return)) self._long = sorted_beta[:5] self._short = sorted_beta[-5:] return self._long + self._short else: self._monthly_rebalance = False return [] def _rebalance(self): self._monthly_rebalance = True def on_data(self, data): if not self._monthly_rebalance or self.is_warming_up: return # Liquidate symbols not in the universe anymore for symbol, security_holding in self.portfolio.items(): if security_holding.invested and symbol not in self._long + self._short: self.liquidate(symbol) if self._long is None or self._short is None: return longs = [symbol for symbol in self._long if self.securities[symbol].price] long_scale_factor = 0.4/sum(range(1,len(longs)+1)) for rank, symbol in enumerate(longs): self.set_holdings(symbol, (len(longs)-rank+1)*long_scale_factor) shorts = [symbol for symbol in self._short if self.securities[symbol].price] short_scale_factor = 0.4/sum(range(1,len(shorts)+1)) for rank, symbol in enumerate(shorts): self.set_holdings(symbol, -(rank+1)*short_scale_factor) self._monthly_rebalance = False self._long = None self._short = None class SymbolData: def __init__(self, symbol): self.symbol = symbol self.last_price = 0 self._returns = RollingWindow[float](252) self._roc = RateOfChange(1) self._roc.updated += lambda _, updated: self._returns.add(updated.value) def update(self, time, price): if price != 0: self.last_price = price self._roc.update(time, price) def is_ready(self): return self._roc.is_ready and self._returns.is_ready def beta(self, market_ret): asset_return = np.array(list(self._returns), dtype=np.float32) market_return = np.array(list(market_ret), dtype=np.float32) return np.cov(asset_return, market_return)[0][1] / np.var(market_return)