Overall Statistics |
Total Orders 6811 Average Win 0.08% Average Loss -0.08% Compounding Annual Return 1.711% Drawdown 16.200% Expectancy 0.009 Start Equity 1000000 End Equity 1089274.11 Net Profit 8.927% Sharpe Ratio -0.052 Sortino Ratio -0.051 Probabilistic Sharpe Ratio 1.696% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.04 Alpha -0.018 Beta 0.153 Annual Standard Deviation 0.066 Annual Variance 0.004 Information Ratio -0.688 Tracking Error 0.146 Treynor Ratio -0.022 Total Fees $50615.93 Estimated Strategy Capacity $220000000.00 Lowest Capacity Asset CSCO R735QTJ8XC9X Portfolio Turnover 29.04% |
#region imports from AlgorithmImports import * from sklearn.naive_bayes import GaussianNB from dateutil.relativedelta import relativedelta from symbol_data import SymbolData #endregion class GaussianNaiveBayesAlphaModel(AlphaModel): """ Emits insights in the direction of the prediction made by the SymbolData objects. """ _symbol_data_by_symbol = {} _new_securities = False def update(self, algorithm, data): """ Called each time the alpha model receives a new data slice. Input: - algorithm Algorithm instance running the backtest - data A data structure for all of an algorithm's data at a single time step Returns a list of Insights to the portfolio construction model. """ if self._new_securities: self._train() self._new_securities = False tradable_symbols = {} features = [[]] for symbol, symbol_data in self._symbol_data_by_symbol.items(): if data.contains_key(symbol) and data[symbol] is not None and symbol_data.is_ready: tradable_symbols[symbol] = symbol_data features[0].extend(symbol_data.features_by_day.iloc[-1].values) insights = [] if len(tradable_symbols) == 0: return [] weight = 0.5 / len(tradable_symbols) for symbol, symbol_data in tradable_symbols.items(): direction = symbol_data.model.predict(features) if direction: insights.append(Insight.price(symbol, data.time + timedelta(days=1, seconds=-1), direction, None, None, None, weight)) return insights def on_securities_changed(self, algorithm, changes): """ Called each time the universe has changed. Input: - algorithm Algorithm instance running the backtest - changes The additions and removals of the algorithm's security subscriptions """ for security in changes.added_securities: self._symbol_data_by_symbol[security.symbol] = SymbolData(security, algorithm) for security in changes.removed_securities: symbol_data = self._symbol_data_by_symbol.pop(security.symbol, None) if symbol_data: symbol_data.dispose() self._new_securities = True def _train(self): """ Trains the Gaussian Naive Bayes classifier model. """ features = pd.DataFrame() labels_by_symbol = {} for symbol, symbol_data in self._symbol_data_by_symbol.items(): if symbol_data.is_ready: features = pd.concat([features, symbol_data.features_by_day], axis=1) labels_by_symbol[symbol] = symbol_data.labels_by_day for symbol, symbol_data in self._symbol_data_by_symbol.items(): if symbol_data.is_ready: symbol_data.model = GaussianNB().fit(features.iloc[:-2], labels_by_symbol[symbol])
#region imports from AlgorithmImports import * from universe import BigTechUniverseSelectionModel from alpha import GaussianNaiveBayesAlphaModel #endregion class GaussianNaiveBayesClassificationAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2015, 10, 1) self.set_end_date(2020, 10, 13) self.set_cash(1000000) self.set_universe_selection(BigTechUniverseSelectionModel()) self.universe_settings.resolution = Resolution.DAILY self.set_alpha(GaussianNaiveBayesAlphaModel()) self.set_portfolio_construction(InsightWeightingPortfolioConstructionModel()) self.set_execution(ImmediateExecutionModel()) self.set_brokerage_model(AlphaStreamsBrokerageModel())
#region imports from AlgorithmImports import * #endregion class SymbolData: """ This class stores data unique to each security in the universe. """ def __init__(self, security, algorithm, num_days_per_sample=4, num_samples=100): """ Input: - security Security object for the security - algorithm The algorithm instance running the backtest - num_days_per_sample The number of open-close intraday returns for each sample - num_samples The number of samples to train the model """ self.model = None self._symbol = security.symbol self._algorithm = algorithm self._num_days_per_sample = num_days_per_sample self._num_samples = num_samples self._previous_open = 0 # Setup consolidators self._consolidator = TradeBarConsolidator(timedelta(days=1)) self._consolidator.data_consolidated += self._custom_daily_handler algorithm.subscription_manager.add_consolidator(self._symbol, self._consolidator) # Warm up training set self._roc_window = np.array([]) self.labels_by_day = pd.Series() data = {f'{self._symbol.id}_(t-{i})' : [] for i in range(1, num_days_per_sample + 1)} self.features_by_day = pd.DataFrame(data) lookback = num_days_per_sample + num_samples + 1 history = algorithm.history(self._symbol, lookback, Resolution.DAILY) if history.empty or 'close' not in history: algorithm.log(f"Not enough history for {self._symbol} yet") return history = history.loc[self._symbol] history['open_close_return'] = (history.close - history.open) / history.open start = history.shift(-1).open end = history.shift(-2).open history['future_return'] = (end - start) / start for day, row in history.iterrows(): self._previous_open = row.open if self._update_features(day, row.open_close_return) and not pd.isnull(row.future_return): row = pd.Series([np.sign(row.future_return)], index=[day]) self.labels_by_day = pd.concat([self.labels_by_day, row]).iloc[-self._num_samples:] def _update_features(self, day, open_close_return): """ Updates the training data features. Inputs - day Timestamp of when we're aware of the open_close_return - open_close_return Open to close intraday return Returns T/F, showing if the features are in place to start updating the training labels. """ self._roc_window = np.append(open_close_return, self._roc_window)[:self._num_days_per_sample] if len(self._roc_window) < self._num_days_per_sample: return False self.features_by_day.loc[day] = self._roc_window self.features_by_day = self.features_by_day[-(self._num_samples+2):] return True def _custom_daily_handler(self, sender, consolidated): """ Updates the rolling lookback of training data. Inputs - sender Function calling the consolidator - consolidated Tradebar representing the latest completed trading day """ time = consolidated.end_time if time in self.features_by_day.index: return _open = consolidated.open close = consolidated.close open_close_return = (close - _open) / _open if self._update_features(time, open_close_return) and self._previous_open: day = self.features_by_day.index[-3] open_open_return = (_open - self._previous_open) / self._previous_open self.labels_by_day[day] = np.sign(open_open_return) self.labels_by_day = self.labels_by_day[-self._num_samples:] self._previous_open = _open def dispose(self): """ Removes the consolidator subscription. """ self._algorithm.subscription_manager.remove_consolidator(self._symbol, self._consolidator) @property def is_ready(self): return self.features_by_day.shape[0] == self._num_samples + 2
#region imports from AlgorithmImports import * from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel #endregion class BigTechUniverseSelectionModel(FundamentalUniverseSelectionModel): """ This universe selection model contain the 10 largest securities in the technology sector. """ def __init__(self, fine_size=10): """ Input: - fine_size Maximum number of securities in the universe """ self._fine_size = fine_size self._month = -1 super().__init__(True) def select_coarse(self, algorithm, coarse): """ Coarse universe selection is called each day at midnight. Input: - algorithm Algorithm instance running the backtest - coarse List of CoarseFundamental objects Returns the symbols that have fundamental data. """ if algorithm.time.month == self._month: return Universe.UNCHANGED return [ x.symbol for x in coarse if x.has_fundamental_data ] def select_fine(self, algorithm, fine): """ Fine universe selection is performed each day at midnight after `SelectCoarse`. Input: - algorithm Algorithm instance running the backtest - fine List of FineFundamental objects that result from `SelectCoarse` processing Returns a list of symbols that are in the energy sector and have the largest market caps. """ self._month = algorithm.time.month tech_stocks = [ f for f in fine if f.asset_classification.morningstar_sector_code == MorningstarSectorCode.TECHNOLOGY ] sorted_by_market_cap = sorted(tech_stocks, key=lambda x: x.market_cap, reverse=True) return [ x.symbol for x in sorted_by_market_cap[:self._fine_size] ]