Overall Statistics
Total Orders
355
Average Win
0.15%
Average Loss
-0.03%
Compounding Annual Return
12.867%
Drawdown
27.800%
Expectancy
4.382
Start Equity
100000
End Equity
190780.68
Net Profit
90.781%
Sharpe Ratio
0.543
Sortino Ratio
0.548
Probabilistic Sharpe Ratio
19.361%
Loss Rate
2%
Win Rate
98%
Profit-Loss Ratio
4.48
Alpha
-0.005
Beta
0.797
Annual Standard Deviation
0.136
Annual Variance
0.018
Information Ratio
-0.717
Tracking Error
0.035
Treynor Ratio
0.093
Total Fees
$355.74
Estimated Strategy Capacity
$1500000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
0.11%
# region imports
from AlgorithmImports import *
import tensorflow.compat.v1 as tf
from google.protobuf import json_format
import json5

tf.disable_v2_behavior()
# endregion

class TensorflowExampleAlgorithm(QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2019, 1, 1)
        self.SetEndDate(2024,5,1)
        self.SetCash(100000)
        self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
        self._step=int(self.GetParameter('_step'))

        training_length = 252*2
        self.training_data = RollingWindow[float](training_length)
        history = self.History[TradeBar](self.symbol, training_length, Resolution.Daily)
        for trade_bar in history:
            self.training_data.Add(trade_bar.Close)

        if self.ObjectStore.ContainsKey('graph') and self.ObjectStore.ContainsKey('weights'):
            json_graph = self.ObjectStore.Read('graph')
            json_weights = self.ObjectStore.Read('weights')
        
            # Restore the tensorflow graph from JSON objects
            tf.reset_default_graph()
            graph_definition = json_format.Parse(json_graph, tf.MetaGraphDef())
            self.model = tf.Session()
            tf.train.import_meta_graph(graph_definition)
        
            # Select the input, output tensors and optimizer
            self.X = tf.get_default_graph().get_tensor_by_name('X:0')
            self.Y = tf.get_default_graph().get_tensor_by_name('Y:0')
            self.output = tf.get_default_graph().get_tensor_by_name('outer:0')
            self.optimizer = tf.get_default_graph().get_collection('Variable/Adam')

            # Restore the model weights from the JSON object.
            weights = [np.asarray(x) for x in json5.loads(json_weights)]
            assign_ops = []
            feed_dict = {}
            vs = tf.trainable_variables()
            zipped_values = zip(vs, weights)
            for var, value in zipped_values:
                value = np.asarray(value)
                assign_placeholder = tf.placeholder(var.dtype, shape=value.shape)
                assign_op = var.assign(assign_placeholder)
                assign_ops.append(assign_op)
                feed_dict[assign_placeholder] = value
            self.model.run(assign_ops, feed_dict=feed_dict)

        else:
            self.model, self.X, self.Y, self.output, self.optimizer = self.BuildModel()
            self.model.run(tf.global_variables_initializer())

        self.Train(self.my_training_method)
        # Train the model every Sunday at 8:00 AM
        self.Train(self.DateRules.Every(DayOfWeek.Sunday), self.TimeRules.At(8, 0), self.my_training_method)
    
    def BuildModel(self):    
        # Instantiate a tensorflow session
        sess = tf.Session()

        # Declare the number of factors and then create placeholders for the input and output layers.
        num_factors = 5
        X = tf.placeholder(dtype=tf.float32, shape=[None, num_factors], name='X')
        Y = tf.placeholder(dtype=tf.float32, shape=[None], name='Y')
        
        # Set up the weights and bias initializers for each layer.
        weight_initializer = tf.variance_scaling_initializer(mode="fan_avg", distribution="uniform", scale=1)
        bias_initializer = tf.zeros_initializer()
        
        # Create hidden layers that use the Relu activator.
        num_neurons_1 = 32
        num_neurons_2 = 16
        num_neurons_3 = 8
        
        W_hidden_1 = tf.Variable(weight_initializer([num_factors, num_neurons_1]))
        bias_hidden_1 = tf.Variable(bias_initializer([num_neurons_1]))
        hidden_1 = tf.nn.relu(tf.add(tf.matmul(X, W_hidden_1), bias_hidden_1))
        
        W_hidden_2 = tf.Variable(weight_initializer([num_neurons_1, num_neurons_2]))
        bias_hidden_2 = tf.Variable(bias_initializer([num_neurons_2]))
        hidden_2 = tf.nn.relu(tf.add(tf.matmul(hidden_1, W_hidden_2), bias_hidden_2))
        
        W_hidden_3 = tf.Variable(weight_initializer([num_neurons_2, num_neurons_3]))
        bias_hidden_3 = tf.Variable(bias_initializer([num_neurons_3]))
        hidden_3 = tf.nn.relu(tf.add(tf.matmul(hidden_2, W_hidden_3), bias_hidden_3))
        
        # Create the output layer and give it a name, so it is accessible after saving and loading the model.
        W_out = tf.Variable(weight_initializer([num_neurons_3, 1]))
        bias_out = tf.Variable(bias_initializer([1]))
        output = tf.transpose(tf.add(tf.matmul(hidden_3, W_out), bias_out), name='outer')
        
        # Set up the loss function and optimizers for gradient descent optimization and backpropagation.
        # This example uses mean-square error as the loss function because the close price is a continuous data and uses Adam as the optimizer because of its adaptive step size.
        loss = tf.reduce_mean(tf.squared_difference(output, Y))
        optimizer = tf.train.AdamOptimizer().minimize(loss)
        
        return sess, X, Y, output, optimizer

    def get_features_and_labels(self, n_steps=5):
        close_prices = list(self.training_data)[::-1]
        
        features = []
        labels = []
        for i in range(len(close_prices)-n_steps):
            features.append(close_prices[i:i+n_steps])
            labels.append(close_prices[i+n_steps])
        features = np.array(features)
        labels = np.array(labels)

        return features, labels

    def my_training_method(self):
        features, labels = self.get_features_and_labels()
        self.model.run(self.optimizer, feed_dict={self.X: features, self.Y: labels})

    def OnData(self, slice: Slice) -> None:
        if self.symbol in slice.Bars:
            self.training_data.Add(slice.Bars[self.symbol].Close)

        new_features, __ = self.get_features_and_labels()
        prediction = self.model.run(self.output, feed_dict={self.X: new_features[-1].reshape(1, -1)})
        prediction = float(prediction.flatten()[-1])

        if prediction > slice[self.symbol].Price:
            self.SetHoldings(self.symbol, -1+(self._step)/10)
        else:            
            self.SetHoldings(self.symbol, 1-(self._step)/10)

    def OnEndOfAlgorithm(self):
        #graph_definition = tf.train.export_meta_graph()
        #json_graph = json_format.MessageToJson(graph_definition)

        #weights = self.model.run(tf.trainable_variables())
        #weights = [w.tolist() for w in weights]
        #json_weights = json5.dumps(weights)

        #self.ObjectStore.Save('graph', json_graph)
        #self.ObjectStore.Save('weights', json_weights)
        pass