Overall Statistics |
Total Orders 512 Average Win 0.37% Average Loss -0.28% Compounding Annual Return 1.350% Drawdown 13.500% Expectancy -0.011 Start Equity 100000 End Equity 101356.58 Net Profit 1.357% Sharpe Ratio -0.343 Sortino Ratio -0.397 Probabilistic Sharpe Ratio 15.237% Loss Rate 57% Win Rate 43% Profit-Loss Ratio 1.31 Alpha 0 Beta 0 Annual Standard Deviation 0.109 Annual Variance 0.012 Information Ratio 0.147 Tracking Error 0.109 Treynor Ratio 0 Total Fees $567.19 Estimated Strategy Capacity $110000000.00 Lowest Capacity Asset FB V6OIPNZEM8V9 Portfolio Turnover 17.58% |
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. # Lean Algorithmic Trading Engine v2.0. Copyright 2020 QuantConnect Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #region imports from AlgorithmImports import * import tensorflow as tf from tensorflow.keras.layers import Input, Conv1D, Dense, Lambda, Flatten, Concatenate from tensorflow.keras import Model from tensorflow.keras import metrics from tensorflow.keras.losses import CategoricalCrossentropy from tensorflow.keras import utils from sklearn.preprocessing import StandardScaler import math from keras.utils import set_random_seed #endregion # varibles (aka features in ML lingo) used to make predictions input_vars = ['open', 'high', 'low', 'close', 'volume'] class Direction: '''Constants used for labeling price movements''' # labels must be integers because Keras (and most ML Libraries) # only work with numbers UP = 0 DOWN = 1 STATIONARY = 2 class MyTemporalCNN: '''Temporal Convolutional Neural Network Model built upon Keras''' # the name describes the architecture of the Neural Network model # Temporal refers to the fact the layers are separated temporally into three regions # Convolutional refers to the fact Convolutional layers are used to extract features def __init__(self, n_tsteps = 15): # n_tsteps = number of time steps in time series for one input/prediction self.n_tsteps = n_tsteps self.scaler = StandardScaler() # used for Feature Scaling self.___create_model() def ___create_model(self): '''Creates the neural network model''' set_random_seed(0) inputs = Input(shape=(self.n_tsteps, len(input_vars))) # extract our features using a Convolutional layers, hence "CNN" feature_extraction = Conv1D(30, 4, activation='relu')(inputs) # split layer into three regions based on time, hence "Temporal" long_term = Lambda( lambda x: tf.split(x, num_or_size_splits=3, axis=1)[0])(feature_extraction) mid_term = Lambda( lambda x: tf.split(x, num_or_size_splits=3, axis=1)[1])(feature_extraction) short_term = Lambda( lambda x: tf.split(x, num_or_size_splits=3, axis=1)[2])(feature_extraction) long_term_conv = Conv1D(1, 1, activation='relu')(long_term) mid_term_conv = Conv1D(1, 1, activation='relu')(mid_term) short_term_conv = Conv1D(1, 1, activation='relu')(short_term) # combine three layers back into one combined = Concatenate(axis=1)([long_term_conv, mid_term_conv, short_term_conv]) # flattening is required since our input is a 2D matrix flattened = Flatten()(combined) # 1 output neuron for each class (Up, Stationary, Down --- see Direction class) outputs = Dense(3, activation='softmax')(flattened) # specify input and output layers of our model self.model = Model(inputs=inputs, outputs=outputs) # compile our model self.model.compile(optimizer='adam', loss=CategoricalCrossentropy(from_logits=True)) def ___prepare_data(self, data, rolling_avg_window_size=5, stationary_threshold=.0001): '''Prepares the data for a format friendly for our model''' # rolling_avg_window_size = window size for the future mid prices to average, # this average is what the model wants to predict # stationary_threshold = maximum change of movement to be considered stationary # for the average mid price stated above df = data[input_vars] shift = -(rolling_avg_window_size-1) # function we will use to label our data (used in line ) def label_data(row): if row['close_avg_change_pct'] > stationary_threshold: return Direction.UP elif row['close_avg_change_pct'] < -stationary_threshold: return Direction.DOWN else: return Direction.STATIONARY # compute the % change in the average of the close of the future 5 time steps # at each time step df['close_avg'] = df['close'].rolling(window=rolling_avg_window_size).mean().shift(shift) df['close_avg_change_pct'] = (df['close_avg'] - df['close']) / df['close'] # label data based on direction, # axis=1 signifies a row-wise operation (axis=0 is col-wise) df['movement_labels'] = df.apply(label_data, axis=1) # lists to store each 2D input matrix and the corresponding label data = [] labels = [] for i in range(len(df)-self.n_tsteps+1+shift): label = df['movement_labels'].iloc[i+self.n_tsteps-1] data.append(df[input_vars].iloc[i:i+self.n_tsteps].values) labels.append(label) data = np.array(data) # temporarily reshape data to 2D, # necessary because sklearn only works wtih 2D data dim1, dim2, dim3 = data.shape data = data.reshape(dim1*dim2, dim3) # fit our scaler and transform our data in one method call data = self.scaler.fit_transform(data) # return data to original shape data = data.reshape(dim1, dim2, dim3) # Keras needs dummy matrices for classification problems, # hence the need for to_categorical() # num classes ensures our dummy matrix has 3 columns, # one for each label (Up, Down, Stationary) return data, utils.to_categorical(labels, num_classes=3) def train(self, data): '''Trains the model''' data, labels = self.___prepare_data(data) self.model.fit(data, labels, epochs=20) def predict(self, input_data): '''Makes a prediction on the direction of the future stock price''' input_data = self.scaler.transform(input_data.fillna(method='ffill').values) prediction = self.model.predict(input_data[np.newaxis, :])[0] direction = np.argmax(prediction) confidence = prediction[direction] return direction, confidence
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. # Lean Algorithmic Trading Engine v2.0. Copyright 2020 QuantConnect Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #region imports from AlgorithmImports import * from MyTemporalCNN import MyTemporalCNN, Direction, input_vars import math #endregion class CNNAlphaModel(AlphaModel): def __init__(self, algorithm): # retrain our model periodically algorithm.train( algorithm.date_rules.month_start(), algorithm.time_rules.midnight, self.train_model) self.algorithm = algorithm self.months = 0 self.day = 0 self.training_complete = False def update(self, algorithm, slice): for symbol in set(slice.splits.keys()) | set(slice.dividends.keys()): security = algorithm.securities[symbol] security['model'].train(self.get_data_frame(security)[input_vars]) if algorithm.is_warming_up or not self.training_complete or self.day == algorithm.time.day: if not self.training_complete: self.train_model() return [] self.day = algorithm.time.day insights = [] for symbol in algorithm.securities.keys(): security = algorithm.securities[symbol] symbol_df = self.get_data_frame(security).tail(15) prediction, confidence = security['model'].predict(symbol_df) if not math.isnan(confidence) and confidence > .55: if prediction == Direction.UP: insights.append(Insight.price(symbol, timedelta(days=5), InsightDirection.UP, weight=confidence)) elif prediction == Direction.DOWN: insights.append(Insight.price(symbol, timedelta(days=5), InsightDirection.DOWN, weight=confidence)) elif algorithm.live_mode: algorithm.log(f'Confidence for {symbol}: {confidence} > .55') return insights def on_securities_changed(self, algorithm, changes): for added in changes.added_securities: self.init_security_data(algorithm, added) self.training_complete = False for removed in changes.removed_securities: self.dispose_security_data(algorithm, removed) def train_model(self): '''Feed in past data to train our models''' if self.months % 3 != 0: return for symbol in self.algorithm.securities.keys(): security = self.algorithm.securities[symbol] security['model'].train(self.get_data_frame(security)[input_vars]) self.months += 1 self.training_complete = True def init_security_data(self, algorithm, security): security['window'] = RollingWindow[TradeBar](500) security['consolidator'] = TradeBarConsolidator(timedelta(1)) security['consolidator'].data_consolidated += lambda sender, bar: security['window'].add(bar) security['model'] = MyTemporalCNN() hist = algorithm.history[TradeBar](security.symbol, 500, Resolution.DAILY, data_normalization_mode=DataNormalizationMode.SCALED_RAW) for bar in hist: security['consolidator'].update(bar) algorithm.subscription_manager.add_consolidator(security.symbol, security['consolidator']) def dispose_security_data(self, algorithm, security): security['consolidator'].data_consolidated -= lambda sender, bar: security['window'].add(bar) algorithm.subscription_manager.remove_consolidator(security.symbol, security['consolidator']) security['window'].reset() def get_data_frame(self, security): return self.algorithm.pandas_converter.get_data_frame[TradeBar](security['window']).iloc[::-1]
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. # Lean Algorithmic Trading Engine v2.0. Copyright 2020 QuantConnect Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #region imports from AlgorithmImports import * from alpha import CNNAlphaModel #endregion class TransdimensionalUncoupledComputer(QCAlgorithm): '''note: This algorithm will required running on GPU node''' def initialize(self): self.set_start_date(2023, 3, 1) # Set Start Date self.set_end_date(2024, 3, 1) self.set_cash(100000) self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN) self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW self.universe_settings.resolution = Resolution.MINUTE self.set_universe_selection(ManualUniverseSelectionModel([ Symbol.create(ticker, SecurityType.EQUITY, Market.USA) for ticker in ['AAPL', 'FB', 'MSFT'] ])) self.add_alpha(CNNAlphaModel(self)) self.set_portfolio_construction(InsightWeightingPortfolioConstructionModel()) self.set_execution(ImmediateExecutionModel()) self.set_warmup(240)