Overall Statistics
Total Trades
41
Average Win
2.84%
Average Loss
-0.89%
Compounding Annual Return
0.917%
Drawdown
8.500%
Expectancy
0.051
Net Profit
0.866%
Sharpe Ratio
0.153
Probabilistic Sharpe Ratio
17.844%
Loss Rate
75%
Win Rate
25%
Profit-Loss Ratio
3.20
Alpha
0.014
Beta
-0.027
Annual Standard Deviation
0.06
Annual Variance
0.004
Information Ratio
-0.517
Tracking Error
0.321
Treynor Ratio
-0.334
Total Fees
$0.00
import tensorflow as tf

import numpy as np
import random

from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import TimeSeriesSplit
from sklearn.model_selection import train_test_split
from keras.models import Sequential
from keras.layers import Dense, Activation, LSTM, Dropout
from keras.utils import to_categorical
from keras import optimizers
from keras import metrics
from keras import backend as K

from datetime import datetime, timedelta

import pandas as pd

### <summary>
### Basic template algorithm simply initializes the date range and cash. This is a skeleton
### framework you can use for designing an algorithm.
### </summary>

seed = 123
random.seed(seed)
np.random.seed(seed)

class ForexTrade(QCAlgorithm):
    '''Deep Learning Model'''

    def Initialize(self):
        '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''

        self.SetStartDate(2020,1, 2)  #Set Start Date
        self.SetEndDate(2020,12,11)   #Set End Date
        self.SetCash(10000)           #Set Strategy Cash
        # Find more symbols here: http://quantconnect.com/data
        self.currency = "EURUSD"
        self.AddForex(self.currency, Resolution.Daily)
        
        ## define a long list, short list and portfolio
        self.long_list = []
        self.short_list = []

        # Initialise indicators
        self.rsi = RelativeStrengthIndex(9)
        self.bb = BollingerBands(30,2,2)
        self.macd = MovingAverageConvergenceDivergence(12, 26, 9)
        self.stochastic = Stochastic(14, 3, 3)
        self.ema = ExponentialMovingAverage(9)

        prev_rsi = []
        prev_bb = []
        prev_macd = []
        lower_bb = []
        upper_bb = []
        sd_bb = []
        prev_stochastic = []
        prev_ema = []

        # Historical Currencty Data
        self.currency_data = self.History([self.currency,], 150, Resolution.Daily) # Drop the first 20 for indicators to warm up
        
        ytd_open = self.currency_data["open"][-1]
        ytd_close = self.currency_data["close"][-1]
        
        self.currency_data = self.currency_data[:-1]

        for tup in self.currency_data.loc[self.currency].itertuples():        
            # making Ibasedatabar for stochastic
            bar = QuoteBar(
                tup.Index, 
                "EURUSD",
                Bar(tup.bidclose, tup.bidhigh, tup.bidlow, tup.bidopen),
                0,
                Bar(tup.askclose, tup.askhigh, tup.asklow, tup.askopen),
                0,
                timedelta(days=1)
                )
        
    
            self.stochastic.Update(bar)
            prev_stochastic.append(float(self.stochastic.ToString()))
    
            self.rsi.Update(tup.Index, tup.close)
            prev_rsi.append(float(self.rsi.ToString()))
    
            self.bb.Update(tup.Index, tup.close)
            prev_bb.append(float(self.bb.ToString()))
            lower_bb.append(float(self.bb.LowerBand.ToString()))
            upper_bb.append(float(self.bb.UpperBand.ToString()))
            sd_bb.append(float(self.bb.StandardDeviation.ToString()))
    
            self.macd.Update(tup.Index, tup.close)
            prev_macd.append(float(self.macd.ToString()))
            
            self.ema.Update(tup.Index, tup.close)
            prev_ema.append(float(self.ema.ToString()))
        
        rsi_df = pd.DataFrame(prev_rsi, columns = ["rsi"])
        macd_df = pd.DataFrame(prev_macd, columns = ["macd"])
        upper_bb_df = pd.DataFrame(upper_bb, columns = ["upper_bb"])
        lower_bb_df = pd.DataFrame(lower_bb, columns = ["lower_bb"])
        sd_bb_df = pd.DataFrame(sd_bb, columns = ["sd_bb"])
        stochastic_df = pd.DataFrame(prev_stochastic, columns = ["stochastic"])
        ema_df = pd.DataFrame(prev_ema, columns = ["ema"])


        self.indicators_df = pd.concat([rsi_df, macd_df, upper_bb_df, lower_bb_df, sd_bb_df, stochastic_df, ema_df], axis=1)
        self.indicators_df = self.indicators_df.iloc[20:]
        self.indicators_df.reset_index(inplace=True, drop=True)


        self.currency_data.reset_index(level = [0, 1], drop = True, inplace = True)
        
        self.currency_data.drop(columns=["askopen", "askhigh", "asklow", "askclose", "bidopen", "bidhigh", "bidhigh", "bidlow", "bidclose"], inplace=True)
        self.currency_data = self.currency_data.iloc[20:]
        self.currency_data.reset_index(inplace=True, drop=True)


        close_prev_prices = self.previous_prices("close", self.currency_data["close"], 6)
        open_prev_prices = self.previous_prices("open", self.currency_data["open"], 6)
        high_prev_prices = self.previous_prices("high", self.currency_data["high"], 6)
        low_prev_prices = self.previous_prices("low", self.currency_data["low"], 6)
        
        all_prev_prices = pd.concat([close_prev_prices, open_prev_prices, high_prev_prices, low_prev_prices], axis=1)
        
        final_table = self.currency_data.join(all_prev_prices, how="outer")
        final_table = final_table.join(self.indicators_df, how="outer")
        
        # Drop NaN from feature table
        self.features = final_table.dropna()
        
        self.features.reset_index(inplace=True, drop=True)
        
        # Make labels
        self.labels = self.features["close"]
        self.labels = pd.DataFrame(self.labels)
        self.labels.index -= 1
        self.labels = self.labels[1:]
        new_row = pd.DataFrame({"close": [ytd_close]})
        self.labels = self.labels.append(new_row)
        self.labels.reset_index(inplace=True, drop=True)
        
        
        self.Debug(len(self.labels))
        self.Debug(len(self.features))
        
        
        ## Define scaler for this class
        self.scaler_X = MinMaxScaler()
        self.scaler_X.fit(self.features)
        self.scaled_features = self.scaler_X.transform(self.features)
        
        self.scaler_Y = MinMaxScaler()
        self.scaler_Y.fit(self.labels)
        self.scaled_labels = self.scaler_Y.transform(self.labels)
        
        ## fine tune the model to determine hyperparameters
        ## only done once (upon inititialize)
        
        tscv = TimeSeriesSplit(n_splits=2)
        cells = [100, 200]
        epochs = [100, 200]
        
        ## create dataframee to store optimal hyperparams
        params_df = pd.DataFrame(columns = ["cells", "epoch", "mse"])
        
        # # ## loop thru all combinations of cells and epochs
        # for i in cells:
        #     for j in epochs:
                
        #         print("CELL", i, "EPOCH", j)
                
        #         # list to store the mean square errors
        #         cvscores = []
                
        #         for train_index, test_index in tscv.split(self.scaled_features):
        #             #print(train_index, test_index)
        #             X_train, X_test = self.scaled_features[train_index], self.scaled_features[test_index]
        #             Y_train, Y_test = self.scaled_labels[train_index], self.scaled_labels[test_index]
                    
        #             X_train = np.reshape(X_train, (X_train.shape[0], 1, X_train.shape[1]))
        #             X_test = np.reshape(X_test, (X_test.shape[0], 1, X_test.shape[1]))
                    
        #             model = Sequential()
        #             model.add(LSTM(i, input_shape = (1, X_train.shape[2]), return_sequences = True))
        #             model.add(Dropout(0.10))
        #             model.add(LSTM(i,return_sequences = True))
        #             model.add(LSTM(i))
        #             model.add(Dropout(0.10))
        #             model.add(Dense(1))
        #             model.compile(loss= 'mean_squared_error',optimizer = 'rmsprop', metrics = ['mean_squared_error'])
        #             model.fit(X_train,Y_train,epochs=j,verbose=0)
                    
        #             scores = model.evaluate(X_test, Y_test)
        #             cvscores.append(scores[1])
                    
        #         ## get average value of mean sq error    
        #         MSE = np.mean(cvscores)
                
        #         ## make this df for cells, epoch and mse and append to params_df
        #         this_df = pd.DataFrame({"cells": [i], "epoch":[j], "mse": [MSE]})
        #         # self.Debug(this_df)
        #         # params_df = params_df.append(this_df)
                
        #         params_df = params_df.append(this_df)
        #         self.Debug(params_df)
                    
        
        
        # # Check the optimised values (O_values) obtained from cross validation
        # # This code gives the row which has minimum mse and store the values to O_values
        # O_values = params_df[params_df['mse'] == params_df['mse'].min()]
                
        # # Extract the optimised values of cells and epochcs from abbove row (having min mse)
        self.opt_cells = 200
        self.opt_epochs = 100
        # self.opt_cells = O_values["cells"][0]
        # self.opt_epochs = O_values["epoch"][0]
        
        
        X_train = np.reshape(self.scaled_features, (self.scaled_features.shape[0], 1, self.scaled_features.shape[1]))
        y_train = self.scaled_labels
        
        
        self.session = K.get_session()
        self.graph = tf.get_default_graph()

        # Intialise the model with optimised parameters
        self.model = Sequential()
        self.model.add(LSTM(self.opt_cells, input_shape = (1, X_train.shape[2]), return_sequences = True))
        self.model.add(Dropout(0.20))
        self.model.add(LSTM(self.opt_cells,return_sequences = True))
        self.model.add(Dropout(0.20))
        self.model.add(LSTM(self.opt_cells, return_sequences = True))
        self.model.add(LSTM(self.opt_cells))
        self.model.add(Dropout(0.20))
        self.model.add(Dense(1))
        
        # self.model.add(Activation("softmax"))
        self.model.compile(loss= 'mean_squared_error',optimizer = 'adam', metrics = ['mean_squared_error'])
        
        
        # self.model.fit(X_train, y_train, epochs=opt_epochs, verbose=0)


    def OnData(self, data):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
        Arguments:
            data: Slice object keyed by symbol containing the stock data
        '''
        current_price = data[self.currency].Price
        
        ## call in previous 1 day data
        ytd_data = self.History([self.currency,], 1, Resolution.Daily)
        
        if ytd_data.empty:
            ytd_data = self.History([self.currency,], 2, Resolution.Daily)

        ### Update the Features and Labels for Fitting ###
        
        # ## pop first row from features and labels
        # self.features = self.features[1:]
        # self.labels = self.labels[1:]
        
        # ## Append new row to features
        # self.features = self.features.append(self.t_minus_2)

        # ## APPEND TODAYS PRICE TO LABELS
        # new_val = pd.DataFrame({"open":[current_price]})
        # self.labels = pd.concat([self.labels, new_val], ignore_index=True)
        
        ## isolate last 6 rows of df for lookback
        features_t_minus_1 = self.features[-6:]
        
        ## generate prev 6 datapoints (as features)
        close_prev_prices = self.previous_prices("close", features_t_minus_1["close"], 6)
        open_prev_prices = self.previous_prices("open", features_t_minus_1["open"], 6)
        high_prev_prices = self.previous_prices("high", features_t_minus_1["high"], 6)
        low_prev_prices = self.previous_prices("low", features_t_minus_1["low"], 6)
        
        ## join all
        all_prev_prices = pd.concat([close_prev_prices, open_prev_prices, high_prev_prices, low_prev_prices], axis=1)
        all_prev_prices.reset_index(drop=True, inplace=True)
        
        
        ## get the indicators
        prev_stochastic, prev_rsi, prev_bb, lower_bb, upper_bb, prev_macd, sd_bb, prev_ema = [],[],[],[],[],[],[],[]
        
        for tup in ytd_data.loc[self.currency].itertuples():        
            # making Ibasedatabar for stochastic
            bar = QuoteBar(tup.Index, 
                           self.currency,
                           Bar(tup.bidclose, tup.bidhigh, tup.bidlow, tup.bidopen),
                           0,
                           Bar(tup.askclose, tup.askhigh, tup.asklow, tup.askopen),
                           0,
                           timedelta(days=1)
                          )
            
            self.stochastic.Update(bar)
            prev_stochastic.append(float(self.stochastic.ToString()))
            
            self.rsi.Update(tup.Index, tup.close)
            prev_rsi.append(float(self.rsi.ToString()))
            
            self.bb.Update(tup.Index, tup.close)
            prev_bb.append(float(self.bb.ToString()))
            lower_bb.append(float(self.bb.LowerBand.ToString()))
            upper_bb.append(float(self.bb.UpperBand.ToString()))
            sd_bb.append(float(self.bb.StandardDeviation.ToString()))
            
            self.macd.Update(tup.Index, tup.close)
            prev_macd.append(float(self.macd.ToString()))
            
            self.ema.Update(tup.Index, tup.close)
            prev_ema.append(float(self.ema.ToString()))
            
        
        rsi_df = pd.DataFrame(prev_rsi, columns = ["rsi"])
        macd_df = pd.DataFrame(prev_macd, columns = ["macd"])
        upper_bb_df = pd.DataFrame(upper_bb, columns = ["upper_bb"])
        lower_bb_df = pd.DataFrame(lower_bb, columns = ["lower_bb"])
        sd_bb_df = pd.DataFrame(sd_bb, columns = ["sd_bb"])
        stochastic_df = pd.DataFrame(prev_stochastic, columns = ["stochastic"])
        ema_df = pd.DataFrame(prev_ema, columns = ["ema"])
        
        indicators_df = pd.concat([rsi_df, macd_df, upper_bb_df, lower_bb_df, sd_bb_df, stochastic_df, ema_df], axis=1)
        indicators_df.reset_index(inplace=True, drop=True)
        
        ytd_data.drop(columns=["askopen", "askhigh", "asklow", "askclose", "bidopen", "bidhigh", "bidhigh", "bidlow", "bidclose"], inplace=True)
        ytd_data.reset_index(drop=True, inplace=True)
        
        ytd_data = ytd_data.join(all_prev_prices, how="outer")
        ytd_data = ytd_data.join(indicators_df, how="outer")
        
        # # Set Next day's T-2 as ytd_data
        # self.t_minus_2 = ytd_data
        
        # Scaling
        # scaled_features = self.scaler_X.transform(self.features)
        
        # scaler_Y = MinMaxScaler()
        # scaler_Y.fit(self.labels)
        # scaled_labels = scaler_Y.transform(self.labels)
        
        # Reshape Features
        # X_train = np.reshape(scaled_features, (scaled_features.shape[0], 1, scaled_features.shape[1]))
        # y_train = scaled_labels
        
        # Fit the model at every onData
        with self.session.as_default():
            with self.graph.as_default():
                # self.model.fit(X_train, y_train, epochs=self.opt_epochs, verbose=0)
        
        
                # Get prediction for ytd_data instance to predict T+1
                # scaler_X.fit(ytd_data)
                self.Debug(str(ytd_data))
                scaled_ytd_data = self.scaler_X.transform(ytd_data)
                X_predict = np.reshape(scaled_ytd_data, (scaled_ytd_data.shape[0], 1, scaled_ytd_data.shape[1]))
                
                close_price = self.model.predict_on_batch(X_predict)
                                
        
                close_price_prediction = self.scaler_Y.inverse_transform(close_price)
                close_price_prediction = close_price_prediction[0][0]
                self.Debug(close_price_prediction)
        
        
        ## BUY/SELL STRATEGY BASED ON PREDICTED PRICE
        #Make decision for trading based on the output from LSTM and the current price.
        #If output ( forecast) is greater than current price , we will buy the currency; else, do nothing.
        # Only one trade at a time and therefore made a list " self.long_list". 
        #As long as the currency is in that list, no further buying can be done.
        # Risk and Reward are defined: Ext the trade at 1% loss or 1 % profit.
        # Generally the LSTM model can predict above/below the current price and hence a random value is used
        #to scale it down/up. Here the number is 1.1 but can be backtested and optimised.
        
        # If RSI is below 30, shouldn't 
        if close_price_prediction > current_price and self.currency not in self.long_list and self.currency not in self.short_list:
            
            self.Debug("output is greater")
            # Buy the currency with X% of holding in this case 90%
            self.SetHoldings(self.currency, 0.9)
            self.long_list.append(self.currency)
            self.Debug("long")
            
        if self.currency in self.long_list:
            cost_basis = self.Portfolio[self.currency].AveragePrice
            #self.Debug("cost basis is " +str(cost_basis))
            if  ((current_price <= float(0.99) * float(cost_basis)) or (current_price >= float(1.03) * float(cost_basis))):
                self.Debug("SL-TP reached")
                #self.Debug("price is" + str(price))
                #If true then sell
                self.SetHoldings(self.currency, 0)
                self.long_list.remove(self.currency)
                self.Debug("squared")
        #self.Debug("END: Ondata")
        
        # Short
        if close_price_prediction < current_price and self.currency not in self.short_list and self.currency not in self.long_list:
                
            self.SetHoldings(self.currency, -0.9)
            self.short_list.append(self.currency)
            self.Debug("short")
            
                
        if self.currency in self.short_list:
            cost_basis = self.Portfolio[self.currency].AveragePrice
            #self.Debug("cost basis is " +str(cost_basis))
            if  ((current_price <= float(0.97) * float(cost_basis)) or (current_price >=float(1.01) * float(cost_basis))):
                self.Debug("SL-TP reached")
                #self.Debug("price is" + str(price))
                #If true then sell
                self.SetHoldings(self.currency, 0)
                self.short_list.remove(self.currency)
                self.Debug("squared")
        
                
    

    def previous_prices(self, raw_type, data, num_lookback):
        
            '''
            num_lookback is the number of previous prices
            Data is open, high, low or close
            Data is a series
            Returns a dataframe of previous prices
            '''
            
            prices = []
            length = len(data)
        
            for i in range(num_lookback, length+1):
                this_data = np.array(data[i-num_lookback : i])
                prices.append(this_data)
        
            prices_df = pd.DataFrame(prices)
                
            columns = {}
            
            for index in prices_df.columns:
                columns[index] = "{0}_shifted_by_{1}".format(raw_type, num_lookback - index)
            
            prices_df.rename(columns = columns, inplace=True)
            prices_df.index += num_lookback
            
            return prices_df