| 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)