# --------------------------------------- IMPORT LIBRARIES -------------------------------------------
from datetime import datetime, timedelta
import pandas as pd
import numpy as np
import random
import math
import warnings
import quandl
import time
warnings.filterwarnings("ignore")

from feature_select import FeatureSelector

from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import Normalizer
from sklearn.preprocessing import MinMaxScaler
import talib as tb
import matplotlib.pyplot as plt
from matplotlib import gridspec

# --------------------------------------- GLOBAL PARAMETERS -------------------------------------------


# Set total fund pool
TRAIN_PORTION = 0.9
ACCOUNT_FUND = 100000
ALLOCATION_RATIO = 0.2
SINGLE_TRADING_FUND = ACCOUNT_FUND * ALLOCATION_RATIO
PRICE_IMPACT = 0.1

# Start and end period of historical data in question
START_TRAIN = datetime(2000, 1, 1)
END_TRAIN = datetime(2017, 2, 12)
START_TEST = datetime(2017, 2, 12)
END_TEST = datetime(2019, 2, 22)

# DJIA component stocks
DJI = ['MMM', 'AXP', 'AAPL', 'BA', 'CAT', 'CVX', 'CSCO', 'KO', 'DIS', 'XOM', 'GE', 'GS',
          'HD', 'IBM', 'INTC', 'JNJ', 'JPM', 'MCD', 'MRK', 'MSFT', 'NKE', 'PFE', 'PG', 'UTX',
          'UNH', 'VZ', 'WMT']
DJI_N = ['3M','American Express', 'Apple','Boeing','Caterpillar','Chevron','Cisco Systems','Coca-Cola','Disney'
         ,'ExxonMobil','General Electric','Goldman Sachs','Home Depot','IBM','Intel','Johnson & Johnson',
         'JPMorgan Chase','McDonalds','Merck','Microsoft','NIKE','Pfizer','Procter & Gamble',
         'United Technologies','UnitedHealth Group','Verizon Communications','Wal Mart']

CONTEXT_DATA = ['^GSPC', '^DJI', '^IXIC', '^RUT', 'SPY', 'QQQ', '^VIX', 'GLD', '^TYX', '^TNX' , 'SHY', 'SHV']

CONTEXT_DATA_N = ['S&P 500', 'Dow Jones Industrial Average', 'NASDAQ Composite', 'Russell 2000', 'SPDR S&P 500 ETF',
 'Invesco QQQ Trust', 'CBOE Volatility Index', 'SPDR Gold Shares', 'Treasury Yield 30 Years',
 'CBOE Interest Rate 10 Year T Note', 'iShares 1-3 Year Treasury Bond ETF', 'iShares Short Treasury Bond ETF']

random.seed(633)
RANDOM_STOCK = random.sample(DJI, 1)
ADD_STOCKS = 4

#13 WEEK TREASURY BILL (^IRX)
# https://finance.yahoo.com/quote/%5EIRX?p=^IRX&.tsrc=fin-srch
RISK_FREE_RATE = ((1+0.02383)**(1.0/252))-1 # Assuming 1.43% risk free rate divided by 360 to get the daily risk free rate.
MAR = 0.05
# ------------------------------------------------ CLASSES --------------------------------------------

class DataRetrieval:
    """
    This class prepares data by loading historical data from pre-saved data.
    """

    def __init__(self):
        # Initiate component data downloads
        self._dji_components_data()

    def _get_daily_data(self, symbol):
        """
        Load pre-saved historical data stock by stock.

        """
        daily_price = pd.read_csv("{}{}{}".format('./data/', symbol, '.csv'), index_col='Date', parse_dates=True)

        return daily_price

    def _dji_components_data(self):
        """
        This function retrieve all components data and assembles the required OHLCV data into respective data
        """

        for i in DJI + CONTEXT_DATA:
            print("Loading {}'s historical data".format((DJI + CONTEXT_DATA_N)[(DJI + CONTEXT_DATA).index(i)]))
            daily_price = self._get_daily_data(i)
            if i == (DJI + CONTEXT_DATA)[0]:
                self.components_df_o = pd.DataFrame(index=daily_price.index, columns=(DJI + CONTEXT_DATA))
                self.components_df_c = pd.DataFrame(index=daily_price.index, columns=(DJI + CONTEXT_DATA))
                self.components_df_h = pd.DataFrame(index=daily_price.index, columns=(DJI + CONTEXT_DATA))
                self.components_df_l = pd.DataFrame(index=daily_price.index, columns=(DJI + CONTEXT_DATA))
                self.components_df_v = pd.DataFrame(index=daily_price.index, columns=(DJI + CONTEXT_DATA))
                # Since this span more than 10 years of data, many corporate actions could have happened,
                # adjusted closing price is used instead
                self.components_df_o[i] = daily_price["Open"]
                self.components_df_c[i] = daily_price["Adj Close"]
                self.components_df_h[i] = daily_price["High"]
                self.components_df_l[i] = daily_price["Low"]
                self.components_df_v[i] = daily_price["Volume"]
            else:
                self.components_df_o[i] = daily_price["Open"]
                self.components_df_c[i] = daily_price["Adj Close"]
                self.components_df_h[i] = daily_price["High"]
                self.components_df_l[i] = daily_price["Low"]
                self.components_df_v[i] = daily_price["Volume"]

    def get_dailyprice_df(self):
        """
        Gets all stocks' close price and separates them into train and test set.
        """
        self.dow_stocks_test = self.components_df_c.loc[START_TEST:END_TEST][DJI]
        self.dow_stocks_train = self.components_df_c.loc[START_TRAIN:END_TRAIN][DJI]

    def get_all(self):
        """
        Response to external request to get all stock price in train and test set.
        """

        self.get_dailyprice_df()
        return self.dow_stocks_train, self.dow_stocks_test

    def technical_indicators_df(self, daily_data):
        """
        Assemble a dataframe of technical indicator series for a single stock
        """
        o = daily_data['Open'].values
        c = daily_data['Close'].values
        h = daily_data['High'].values
        l = daily_data['Low'].values
        v = daily_data['Volume'].astype(float).values
        # define the technical analysis matrix

        # Most data series are normalized by their series' mean
        ta = pd.DataFrame()
        ta['MA5'] = tb.MA(c, timeperiod=5) / tb.MA(c, timeperiod=5).mean()
        ta['MA10'] = tb.MA(c, timeperiod=10) / tb.MA(c, timeperiod=10).mean()
        ta['MA20'] = tb.MA(c, timeperiod=20) / tb.MA(c, timeperiod=20).mean()
        ta['MA60'] = tb.MA(c, timeperiod=60) / tb.MA(c, timeperiod=60).mean()
        ta['MA120'] = tb.MA(c, timeperiod=120) / tb.MA(c, timeperiod=120).mean()
        ta['MA5'] = tb.MA(v, timeperiod=5) / tb.MA(v, timeperiod=5).mean()
        ta['MA10'] = tb.MA(v, timeperiod=10) / tb.MA(v, timeperiod=10).mean()
        ta['MA20'] = tb.MA(v, timeperiod=20) / tb.MA(v, timeperiod=20).mean()
        ta['ADX'] = tb.ADX(h, l, c, timeperiod=14) / tb.ADX(h, l, c, timeperiod=14).mean()
        ta['ADXR'] = tb.ADXR(h, l, c, timeperiod=14) / tb.ADXR(h, l, c, timeperiod=14).mean()
        ta['MACD'] = tb.MACD(c, fastperiod=12, slowperiod=26, signalperiod=9)[0] / \
                     tb.MACD(c, fastperiod=12, slowperiod=26, signalperiod=9)[0].mean()
        ta['RSI'] = tb.RSI(c, timeperiod=14) / tb.RSI(c, timeperiod=14).mean()
        ta['BBANDS_U'] = tb.BBANDS(c, timeperiod=5, nbdevup=2, nbdevdn=2, matype=0)[0] / \
                         tb.BBANDS(c, timeperiod=5, nbdevup=2, nbdevdn=2, matype=0)[0].mean()
        ta['BBANDS_M'] = tb.BBANDS(c, timeperiod=5, nbdevup=2, nbdevdn=2, matype=0)[1] / \
                         tb.BBANDS(c, timeperiod=5, nbdevup=2, nbdevdn=2, matype=0)[1].mean()
        ta['BBANDS_L'] = tb.BBANDS(c, timeperiod=5, nbdevup=2, nbdevdn=2, matype=0)[2] / \
                         tb.BBANDS(c, timeperiod=5, nbdevup=2, nbdevdn=2, matype=0)[2].mean()
        ta['AD'] = tb.AD(h, l, c, v) / tb.AD(h, l, c, v).mean()
        ta['ATR'] = tb.ATR(h, l, c, timeperiod=14) / tb.ATR(h, l, c, timeperiod=14).mean()
        ta['HT_DC'] = tb.HT_DCPERIOD(c) / tb.HT_DCPERIOD(c).mean()
        ta["High/Open"] = h / o
        ta["Low/Open"] = l / o
        ta["Close/Open"] = c / o

        self.ta = ta

    def label(self, df, seq_length):
        return (df['Returns'] > 0).astype(int)

    def preprocessing(self, symbol):
        """
        Preprocess all stock data into a big dataframe of features with the help of a feature selector , also creates label data

        """
        print("\n")
        print("Preprocessing {} & its technical data".format(symbol))
        print("============================================")
        self.daily_data = pd.DataFrame()
        self.daily_data['Returns'] = pd.Series(
            (self.components_df_c[symbol] / self.components_df_c[symbol].shift(1) - 1) * 100,
            index=self.components_df_c[symbol].index)
        self.daily_data['Open'] = self.components_df_o[symbol]
        self.daily_data['Close'] = self.components_df_c[symbol]
        self.daily_data['High'] = self.components_df_h[symbol]
        self.daily_data['Low'] = self.components_df_l[symbol]
        self.daily_data['Volume'] = self.components_df_v[symbol].astype(float)
        seq_length = 3
        self.technical_indicators_df(self.daily_data)
        self.X = self.daily_data[['Open', 'Close', 'High', 'Low', 'Volume']] / self.daily_data[
            ['Open', 'Close', 'High', 'Low', 'Volume']].mean()
        self.y = self.label(self.daily_data, seq_length)
        X_shift = [self.X]

        for i in range(1, seq_length):
            shifted_df = self.daily_data[['Open', 'Close', 'High', 'Low', 'Volume']].shift(i)
            X_shift.append(shifted_df / shifted_df.mean())
        ohlc = pd.concat(X_shift, axis=1)
        ohlc.columns = sum([[c + 'T-{}'.format(i) for c in ['Open', 'Close', 'High', 'Low', 'Volume']] \
                            for i in range(seq_length)], [])
        self.ta.index = ohlc.index
        self.X = pd.concat([ohlc, self.ta], axis=1)
        self.Xy = pd.concat([self.X, self.y], axis=1)

        fs = FeatureSelector(data=self.X, labels=self.y)
        fs.identify_all(selection_params={'missing_threshold': 0.6,
                                          'correlation_threshold': 0.9,
                                          'task': 'regression',
                                          'eval_metric': 'auc',
                                          'cumulative_importance': 0.99})
        self.X_fs = fs.remove(methods='all', keep_one_hot=True)

        return self.X_fs

    def get_feature_dataframe(self, selected_stock):
        """
        Get the preprocessed dataframe and extract only the stocks in interest. Returns a smaller dataframe

        """
        self.feature_df = pd.DataFrame()
        for s in selected_stock:
            if s == selected_stock[0]:
                df = self.preprocessing(s)
                df.columns = [str(s) + '_' + str(col) for col in df.columns]
                self.feature_df = df
            else:
                df = self.preprocessing(s)
                df.columns = [str(s) + '_' + str(col) for col in df.columns]
                self.feature_df = pd.concat([self.feature_df, df], axis=1)
        return self.feature_df

    def get_adj_close(self, selected):
        """
        Get a 3D dataframe of Adjusted close price from Quandl.
        """
        # get adjusted closing prices of 5 selected companies with Quandl
        quandl.ApiConfig.api_key = 'CxU5-dDyxppBFzVgGG6z'
        data = quandl.get_table('WIKI/PRICES', ticker=selected,
                                qopts={'columns': ['date', 'ticker', 'adj_close']},
                                date={'gte': START_TRAIN, 'lte': END_TRAIN}, paginate=True)
        return data


class MathCalc:
    """
    This class performs all the mathematical calculations
    """

    @staticmethod
    def calc_return(period):
        """
        This function computes the return of a series
        """
        period_return = period / period.shift(1) - 1
        return period_return[1:len(period_return)]

    @staticmethod
    def calc_monthly_return(series):
        """
        This function computes the monthly return

        """
        return MathCalc.calc_return(series.resample('M').last())

    @staticmethod
    def positive_pct(series):
        """
        This function calculates the probably of positive values from a series of values.
        """
        return (float(len(series[series > 0])) / float(len(series)))*100

    @staticmethod
    def calc_yearly_return(series):
        """
        This function computes the yearly return
        """
        return MathCalc.calc_return(series.resample('AS').last())

    @staticmethod
    def max_drawdown(r):
        """
        This function calculates maximum drawdown occurs in a series of cummulative returns
        """
        dd = r.div(r.cummax()).sub(1)
        maxdd = dd.min()
        return round(maxdd, 2)

    @staticmethod
    def calc_lake_ratio(series):

        """
        This function computes lake ratio
        """
        water = 0
        earth = 0
        series = series.dropna()
        water_level = []
        for i, s in enumerate(series):
            if i == 0:
                peak = s
            else:
                peak = np.max(series[0:i])
            water_level.append(peak)
            if s < peak:
                water = water + peak - s
            earth = earth + s
        return water / earth

    @staticmethod
    def calc_gain_to_pain(daily_series):
        """
        This function computes the gain to pain ratio given a series of cummulative returns
        """

        try:
            monthly_returns = MathCalc.calc_monthly_return(daily_series.dropna())
            sum_returns = monthly_returns.sum()
            sum_neg_months = abs(monthly_returns[monthly_returns < 0].sum())
            gain_to_pain = sum_returns / sum_neg_months
        except:
            gain_to_pain = 1.0

        # print "Gain to Pain ratio: ", gain_to_pain
        return gain_to_pain

    @staticmethod
    def sharpe_ratio(returns):
        """
        Calculates Sharpe ratio from a series of returns.
        """
        return ((returns.mean() - RISK_FREE_RATE) / returns.std()) * np.sqrt(252)

    @staticmethod
    def downside_deviation(returns):
        """
        This method returns a lower partial moment of the returns. Create an array he same length as returns containing the minimum return threshold
        """
        #

        target = 0
        df = pd.DataFrame(data=returns, columns=["Returns"], index=returns.index)
        df["Downside Returns"] = 0
        df.loc[df["Returns"] < target, "Downside Returns"] = df["Returns"] ** 2
        expected_return = df["Returns"].mean()

        return np.sqrt(df["Downside Returns"].mean())

    @staticmethod
    def sortino_ratio(returns):
        """
        Calculates Sortino ratio from a series of returns.
        """
        return ((returns.mean() - RISK_FREE_RATE) / MathCalc.downside_deviation(returns))* np.sqrt(252)

    @staticmethod
    def calc_kpi(portfolio):
        """
        This function calculates individual portfolio KPI related its risk profile
        """

        kpi = pd.DataFrame(index=['KPI'], columns=['Avg. monthly return', 'Pos months pct', 'Avg yearly return',
                                                   'Max monthly dd', 'Max drawdown', 'Lake ratio', 'Gain to Pain',
                                                   'Sharpe ratio', 'Sortino ratio'])
        kpi['Avg. monthly return'].iloc[0] = MathCalc.calc_monthly_return(portfolio['Total asset']).mean() * 100
        kpi['Pos months pct'].iloc[0] = MathCalc.positive_pct(portfolio['Returns'])
        kpi['Avg yearly return'].iloc[0] = MathCalc.calc_yearly_return(portfolio['Total asset']).mean() * 100
        kpi['Max monthly dd'].iloc[0] = MathCalc.max_drawdown(MathCalc.calc_monthly_return(portfolio['CumReturns']))
        kpi['Max drawdown'].iloc[0] = MathCalc.max_drawdown(MathCalc.calc_return(portfolio['CumReturns']))
        kpi['Lake ratio'].iloc[0] = MathCalc.calc_lake_ratio(portfolio['Total asset'])
        kpi['Gain to Pain'].iloc[0] = MathCalc.calc_gain_to_pain(portfolio['Total asset'])
        kpi['Sharpe ratio'].iloc[0] = MathCalc.sharpe_ratio(portfolio['Returns'])
        kpi['Sortino ratio'].iloc[0] = MathCalc.sortino_ratio(portfolio['Returns'])

        return kpi

    @staticmethod
    def assemble_cum_returns(returns_buyhold, returns_sharpe_optimized_buyhold, returns_minvar_optimized_buyhold):

        """
        This function assembles cumulative returns of all portfolios.
        """
        cum_returns = pd.DataFrame()
        cum_returns['BuyHold 5 Non-corr stocks'] = returns_buyhold
        cum_returns['BuyHold Sharpe-optimized'] = returns_sharpe_optimized_buyhold
        cum_returns['BuyHold MinVar-optimized'] = returns_minvar_optimized_buyhold

        return cum_returns

    @staticmethod
    def assemble_returns(returns_buyhold, returns_sharpe_optimized_buyhold, returns_minvar_optimized_buyhold):

        """
        This function assembles returns of all portfolios.
        """
        returns = pd.DataFrame()
        returns['BuyHold 5 Non-corr stocks'] = returns_buyhold
        returns['BuyHold Sharpe-optimized'] = returns_sharpe_optimized_buyhold
        returns['BuyHold MinVar-optimized'] = returns_minvar_optimized_buyhold

        return returns

    @staticmethod
    def colrow(i):
        """
        This function calculate the row and columns index number based on the total number of subplots in the plot.

        Return:
             row: axis's row index number
             col: axis's column index number
        """

        # Do odd/even check to get col index number
        if i % 2 == 0:
            col = 0
        else:
            col = 1
        # Do floor division to get row index number
        row = i // 2

        return col, row


class Trading:
    """
    This class performs trading and all other functions related to trading
    """

    def __init__(self, dow_stocks_train, dow_stocks_test, dow_stocks_volume):
        self._dow_stocks_test = dow_stocks_test
        self.dow_stocks_train = dow_stocks_train
        self.daily_v = dow_stocks_volume
        self.remaining_stocks()

    @staticmethod
    def slippage_price(price, stock_quantity, day_volume):
        """
        This function performs slippage price calculation using Zipline's volume share model
        https://www.zipline.io/_modules/zipline/finance/slippage.html
        """

        volumeShare = stock_quantity / float(day_volume)
        impactPct = volumeShare ** 2 * PRICE_IMPACT

        if stock_quantity > 0:
            slipped_price = price * (1 + impactPct)
        else:
            slipped_price = price * (1 - impactPct)

        return slipped_price

    @staticmethod
    def commission(num_share, share_value):
        """
        This function computes commission fee of every trade
        https://www.interactivebrokers.com/en/index.php?f=1590&p=stocks1
        """
        trade_value = num_share * share_value
        max_comm_fee = 0.01 * trade_value
        comm_fee = 0.005 * num_share

        if max_comm_fee < comm_fee:
            comm_fee = max_comm_fee
        elif comm_fee <= max_comm_fee and comm_fee > 1.0:
            pass
        elif comm_fee < 1.0 and num_share > 0:
            comm_fee = 1.0
        elif num_share == 0:
            comm_fee = 0.0

        return comm_fee

    def find_efficient_frontier(self, data, selected):
        """
        Find efficient frontier of a portfolio of stocks.
        Returns the stock weights for Sharpe ratio and minimum variance optimized portfolios.
        """

        # reorganise data pulled by setting date as index with
        # columns of tickers and their corresponding adjusted prices
        clean = data.set_index('date')
        table = clean.pivot(columns='ticker')

        # calculate daily and annual returns of the stocks
        returns_daily = table.pct_change()
        returns_annual = returns_daily.mean() * 250

        # get daily and covariance of returns of the stock
        cov_daily = returns_daily.cov()
        cov_annual = cov_daily * 250

        # empty lists to store returns, volatility and weights of imiginary portfolios
        port_returns = []
        port_volatility = []
        sharpe_ratio = []
        stock_weights = []

        # set the number of combinations for imaginary portfolios
        num_assets = len(selected)
        num_portfolios = 50000

        # set random seed for reproduction's sake
        np.random.seed(36)

        # populate the empty lists with each portfolios returns,risk and weights
        for single_portfolio in range(num_portfolios):
            weights = np.random.random(num_assets)
            weights /= np.sum(weights)
            returns = np.dot(weights, returns_annual)
            volatility = np.sqrt(np.dot(weights.T, np.dot(cov_annual, weights)))
            sharpe = returns / volatility
            sharpe_ratio.append(sharpe)
            port_returns.append(returns)
            port_volatility.append(volatility)
            stock_weights.append(weights)

        # a dictionary for Returns and Risk values of each portfolio
        portfolio = {'Returns': port_returns,
                     'Volatility': port_volatility,
                     'Sharpe Ratio': sharpe_ratio}

        # extend original dictionary to accomodate each ticker and weight in the portfolio
        for counter, symbol in enumerate(selected):
            portfolio[symbol + ' Weight'] = [Weight[counter] for Weight in stock_weights]

        # make a nice dataframe of the extended dictionary
        df = pd.DataFrame(portfolio)

        # get better labels for desired arrangement of columns
        column_order = ['Returns', 'Volatility', 'Sharpe Ratio'] + [stock + ' Weight' for stock in selected]

        # reorder dataframe columns
        df = df[column_order]

        # find min Volatility & max sharpe values in the dataframe (df)
        min_volatility = df['Volatility'].min()
        max_sharpe = df['Sharpe Ratio'].max()
        sharpe_portfolio = df.loc[df['Sharpe Ratio'] == max_sharpe]
        min_variance_portfolio = df.loc[df['Volatility'] == min_volatility]

        UserDisplay().plot_efficient_frontier(df, sharpe_portfolio, min_variance_portfolio)
        # use the min, max values to locate and create the two special portfolios
        return sharpe_portfolio, min_variance_portfolio

    def remaining_stocks(self):
        """
        This function finds out the remaining Dow component stocks after the selected stocks are taken.
        """
        dow_remaining = self._dow_stocks_test.drop(RANDOM_STOCK, axis=1)
        self.dow_remaining = [i for i in dow_remaining.columns]

    def construct_book(self, dow_stocks_values, buyhold):
        """
        This function construct the trading book for the buy and hold trading strategy
        """
        portfolio = pd.DataFrame(index=dow_stocks_values.index,
                                 columns=["Total asset", "ProfitLoss", "Returns", "CumReturns"])

        if buyhold:
            portfolio["Total asset"] = dow_stocks_values.sum(axis=1) + (ACCOUNT_FUND * (1 - ALLOCATION_RATIO))
        else:
            portfolio["Total asset"] = dow_stocks_values.sum(axis=1)
        portfolio["ProfitLoss"] = portfolio["Total asset"] - portfolio["Total asset"].shift(1).fillna(
            portfolio["Total asset"][0])
        portfolio["Returns"] = portfolio["Total asset"] / portfolio["Total asset"].shift(1) - 1
        portfolio["CumReturns"] = portfolio["Returns"].add(1).cumprod().fillna(1)
        return portfolio

    def diversified_trade(self, ncs, dow_stocks):
        """
        This function create trading book for the diversifed portfolios
        """
        # Calculate equally weighted fund allocation for each stock
        single_component_fund = SINGLE_TRADING_FUND / len(ncs)
        # Randomly choose the set number of stocks from DJIA pool of component stocks
        share_distribution = single_component_fund / dow_stocks.iloc[0]
        dow_stocks_values = dow_stocks.mul(share_distribution, axis=1)
        portfolio = self.construct_book(dow_stocks_values, True)
        kpi = MathCalc.calc_kpi(portfolio)
        return dow_stocks_values, portfolio, kpi

    def optimized_diversified_trade(self, ncs, sharpe_portfolio, dow_stocks):
        """
        This function create trading book for the diversifed portfolios with asset weights that are optimized by modern portfolio theory
        """

        # Calculate equally weighted fund allocation for each stock
        single_component_fund = SINGLE_TRADING_FUND * sharpe_portfolio.T.iloc[3:].values.flatten()
        # Randomly choose the set number of stocks from DJIA pool of component stocks
        share_distribution = single_component_fund / dow_stocks[ncs].iloc[0]
        dow_stocks_values = dow_stocks[ncs].mul(share_distribution, axis=1)
        portfolio = self.construct_book(dow_stocks_values, True)
        kpi = MathCalc.calc_kpi(portfolio)
        return dow_stocks_values, portfolio, kpi

    def stocks_corr(self, portfolio_longonly_pre):
        """
        This function calculate the correlation coefficient between a portfolio returns and a stock returns
        """

        remaining_corr = pd.Series(index=self.dow_remaining)
        for stock in self.dow_remaining:
            stock_return = MathCalc.calc_return(self.dow_stocks_train[stock])
            remaining_corr[stock] = portfolio_longonly_pre['Returns'][1:].corr(stock_return)
        return remaining_corr.sort_values(ascending=True)

    def find_non_correlate_stocks(self, num_non_corr_stocks):
        """
        This function performs trade with a portfolio starting with the number of stocks specified and
        find the required number of most uncorrelated stocks.
        Only the train set data is used to perform this task to avoid look ahead bias.
        """
        add_stocks = (min(num_non_corr_stocks, len(DJI))) - 1
        # Get the returns of the long only returns of all Dow component stocks during the pre-trading period.
        single_component_fund = SINGLE_TRADING_FUND
        share_distribution = single_component_fund / self.dow_stocks_train[RANDOM_STOCK].iloc[0]
        dow_stocks_values = self.dow_stocks_train[RANDOM_STOCK].mul(share_distribution, axis=1)
        portfolio_longonly_train = self.construct_book(dow_stocks_values, True)

        # find the most uncorrelated stocks with the one randomly selected stock arranged from most
        # uncorrelated to most correlated
        remaining_corr = self.stocks_corr(portfolio_longonly_train)

        # Assemble the non-correlate stocks
        ncs = RANDOM_STOCK

        adding_stocks = [i for i in remaining_corr[0:add_stocks].index]

        # add stocks to the random portfolio stock
        ncs = ncs + adding_stocks

        # Do buy and hold trade with a simple equally weighted portfolio of the 5 non-correlate stocks
        portfolio_values, portfolio_nc_5, kpi_nc_5 = self.diversified_trade(ncs, self.dow_stocks_train[ncs])
        return portfolio_nc_5, kpi_nc_5, ncs


class UserDisplay:
    """
    The class displays plot(s) to users.
    """

    def plot_prediction(self, original, trained, train_len, nn):

        """
        Function to plot all stocks' actual price and price predicted by LSTM model.
        """
        # Set a palette so that all 14 lines can be better differentiated
        color_palette = ['#e6194b', '#3cb44b', '#4363d8']
        fig, ax = plt.subplots(5, 1, figsize=(16, 30))
        plt.subplots_adjust(hspace=0.5)
        for i, s in enumerate(original.columns):
            ax[i].plot(original.index, original[s], '-', label="Original price", linewidth=2, color=color_palette[0])
            ax[i].plot(trained.iloc[:train_len].index, trained[trained.columns[i]].iloc[:train_len], '-',
                       label="Trained price", linewidth=2,
                       color=color_palette[1], alpha=0.8)
            ax[i].plot(trained.iloc[train_len:].index, trained[trained.columns[i]].iloc[train_len:], '-',
                       label="Predicted price", linewidth=2,
                       color=color_palette[2])
            ax[i].set_title("{} trained model price prediction for {}".format(nn, s), fontsize=14)
        plt.legend()
        plt.xlabel('Date')
        plt.ylabel('Stock price')
        #plt.title('Original, trained & predicted stock price trained on {} model'.format(nn))
        plt.subplots_adjust(hspace=0.5)

        # Display and save the graph
        plt.savefig('./test_result/price_prediction_{}.png'.format(nn))
        # Inform user graph is saved and the program is ending.
        print(
            "Plot saved in ./test_result/prediction_{}.png. When done viewing, please close this plot for next plot. Thank You!".format(
                nn))
        plt.show()

    def plot_efficient_frontier(self, risk_return_dict, sharpe_portfolio, min_variance_portfolio):
        """
        Plot the efficient frontier of a portfolio of stocks.
        """

        # plot frontier, max sharpe & min Volatility values with a scatterplot
        plt.style.use('seaborn-dark')
        risk_return_dict.plot.scatter(x='Volatility', y='Returns', c='Sharpe Ratio',
                                      cmap='inferno', edgecolors='black', figsize=(10, 8), grid=True)

        plt.scatter(x=sharpe_portfolio['Volatility'], y=sharpe_portfolio['Returns'], c='red', marker='D', s=200)
        plt.scatter(x=min_variance_portfolio['Volatility'], y=min_variance_portfolio['Returns'], c='blue', marker='D',
                    s=200)
        plt.xlabel('Volatility (Std. Deviation)')
        plt.ylabel('Expected Returns')
        plt.title('Efficient Frontier')
        # Display and save the graph
        plt.savefig('./test_result/efficient_frontier.png')
        # Inform user graph is saved and the program is ending.
        print(
            "Plot saved in ./test_result/efficient_frontier.png. When done viewing, please close this plot for next plot. Thank You!")

        plt.show()

    def plot_portfolio_return(self, cum_returns):
        """
        Function to plot all portfolio cumulative returns.
        """
        # Set a palette so that all 14 lines can be better differentiated
        color_palette = ['#36C4FE', '#FF66F9', '#FF7E66', '#DE0049', '0038E7',
                         '#758CFF', '#4400E7', '#A2ED00', '#00EDC3', '#EECF00', '#EE5C00']
        fig, ax = plt.subplots(figsize=(14, 6))

        # Iterate the compared list to get correlation coefficient array for every compared index
        # Plot the correlation line on the plot canvas
        for i, d in enumerate(cum_returns):
            ax.plot(cum_returns.index, cum_returns[d], '-', label=cum_returns.columns[i], linewidth=2.5,
                    color=color_palette[i])

        plt.legend()
        plt.xlabel('Years')
        plt.ylabel('Cumulative returns')
        plt.title('Cumulative returns for portfolios with different trading models')
        # Display and save the graph
        plt.savefig('./test_result/portfolios_returns.png')
        # Inform user graph is saved and the program is ending.
        print(
            "Plot saved in ./test_result/portfolios_returns.png. When done viewing, please close this plot for next plot. Thank You!")

        plt.show()

    def plot_portfolio_risk(self, returns):
        """
        This function plot the histograms of returns for all portfolios.
        """

        plt.close('all')
        # Define axes, number of rows and columns
        f, ax = plt.subplots(3, 2, figsize=(20, 16))
        plt.subplots_adjust(hspace=0.5)

        for i, d in enumerate(returns):
            # Do odd/even check to col number for plot axes
            col, row = MathCalc.colrow(i)
            ret = returns[d].dropna()
            # plot line graph
            ax[row, col].hist(ret, bins=50, color='darkgreen')
            ax[row, col].axvline(ret.mean(), color='red',
                                 linestyle='-.', linewidth=2.5, label='Mean')
            ax[row, col].axvline(np.median(ret), color='#f1f442',
                                 linestyle='-.', linewidth=2.5, label='Median')
            ax[row, col].axvline(np.median(ret) + ret.std(), color='#b2f441', linestyle='--', linewidth=2,
                                 label='1 x sigma')
            ax[row, col].axvline(np.median(ret) - ret.std(),
                                 color='#b2f441', linestyle='--', linewidth=2)
            ax[row, col].set_title("Returns histogram for portfolio {}".format(returns.columns[i]), fontsize=14)
            ax[row, col].legend()

        plt.savefig('./test_result/portfolios_risk.png')

        print(
            "Plot saved in ./test_result/portfolios_risk.png. When done viewing, please close this plot to end program. Thank You!")

        plt.show()