Chart Dropdowns

By Yeo Yong Kiat in Finanalytics

5 min read
Chances are, you're going to be looking at more than one stock or financial asset - surely you believe in diversification, even for trading? In this post, we're going to work off our previous post on basic line charts, and explore a nifty way to toggle the price trends of several stocks through a simple dropdown menu. This will prove very useful when presenting a set of charts for different stocks.

Dropdowns in Plotly

Let's take a look at how a drop-down could make it more convenient for us to analyse the prices of various stocks. As usual, I show the end product first so you get a better sense of what we're working towards. In the chart below, you have the option to choose between "All Stocks", "TSLA" or "NIO", and the price data curves update accordingly. For continuity, we work off the same chart with the slider function at the bottom, from our previous post.

Accessing the Data

Let's initiate the Python header with the important libraries and call for the stock prices of TSLA and NIO, two very popular electric vehicle companies. However, this time round, we will be using the "plotly.graph_objects" module. The key difference between both modules is that Plotly Express is a high-level wrapper for Plotly, meaning that you can do a lot of things with it with a much simpler syntax. For more complicated functions, we'll have to rely on the main Plotly modules, like "plotly.graph_objects".

## Loading Libraries
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import yfinance as yf

## Generating ticker objects
TSLA = yf.Ticker("TSLA")
NIO = yf.Ticker("NIO")

## Accessing stock price data
TSLA_df = TSLA.history(period="ytd")
NIO_df = NIO.history(period="ytd")

## Extracting only the daily high prices
## into a new dataframe
stocks_high = pd.DataFrame({
  'TSLA': TSLA_df['High'],
  'NIO': NIO_df['High']
})

## Extracting only the daily low prices
## into a new dataframe
stocks_low = pd.DataFrame({
  'TSLA': TSLA_df['Low'],
  'NIO': NIO_df['Low']
})

Check that your "stocks_high" and "stocks_low" dataframes give the following output:

Output for stocks_high.head():

                   TSLA        NIO
Date                              
2022-01-03  1201.069946  33.799999
2022-01-05  1170.339966  31.940001
2022-01-06  1088.000000  30.540001
2022-01-07  1080.930054  30.420000

Output for stocks_low.head():

                   TSLA        NIO
Date                              
2022-01-03  1136.040039  31.879999
2022-01-04  1123.050049  31.110001
2022-01-05  1081.010010  29.780001
2022-01-06  1020.500000  28.280001
2022-01-07  1010.000000  28.780001

Creating the Plotly Chart Object

With the "plotly.graph_objects" module, we can now create an empty chart Object via the "Figure()" method. After which we can then load in our data of high and low stock prices for each of the companies.

## Generating an empty Plotly chart object
fig = go.Figure()

## Adding high prices data into object
## Note: Dataset 1 = TSLA High, Dataset 2 = NIO High
for column in stocks_high.columns.to_list():
    fig.add_trace(
        go.Scatter(
            x = stocks_high.index,
            y = stocks_high[column],
            name = "Daily High"     # legend name
        )
    )

## Adding low prices data into object
## Note: Dataset 3 = TSLA Low, Dataset 4 = NIO Low
for column in stocks_low.columns.to_list():
    fig.add_trace(
        go.Scatter(
            x = stocks_low.index,
            y = stocks_low[column],
            name = "Daily Low"      # legend name
        )
    )

Next comes the fun bit - creating your dropdown! A dropdown belongs to the "updatemenus" portion of the "update_layout()" method. I've provided most of the explanation in comments below, but I want to focus on the "args" key.value pair in the dictionary. Remember how you loaded in TSLA/NIO High datasets followed by TSLA/NIO Low datasets? This means that the order of your datasets are [TSLA High, NIO High, TSLA Low, NIO Low]. The boolean list you see in the "args" key.value pair below corresponds to which dataset you want the Plotly chart to display when the corresponding label is clicked.

## Adding a dropdown list
fig.update_layout(
    updatemenus=[go.layout.Updatemenu(
        
        active=0,
        
        ## You want three buttons labelled "All Stocks", "TSLA" and "NIO"
        ## so create three dictionaries with three separate "label" key-value pairs.
        ## Also add in an "update" value under the "method" key, so that
        ## Plotly knows that you want to update the data in your charts.
        buttons=list(
              [dict(label = 'All Stocks',
                  method = 'update',
                  args = [{'visible': [True, True, True, True]},
                    {'title': '<b>YTD Price Data for Various Stocks</b>',
                     'showlegend':True}]),
              dict(label = 'TSLA',
                  method = 'update',
                  args = [{'visible': [True, False, True, False]},
                          {'title': '<b>YTD TSLA Stock Price Data</b>',
                           'showlegend':True}]),
              dict(label = 'NIO',
                  method = 'update',
                  args = [{'visible': [False, True, False, True]},
                          {'title': '<b>YTD NIO Stock Price Data</b>',
                           'showlegend':True}])
            ]),
        
        ## Positioning your dropdown along x-axis
        ## Play around with these
        x=0.01,
        xanchor="left",
        
        ## Positioning your dropdown along y-axis
        ## Play around with these
        y=1.2,
        yanchor="top"
        
      )]    
    )

fig.update_layout(
  title="YTD Price Data for Various Stocks",
  title_x=0.5
)

## Display the chart
fig.show()

If you've been following thus far, you should have gotten the following Plotly output. Play around with it first, we'll tweak it further later.

Formatting Your Chart

Let's see how else we can improve the chart. Other than the standard formatting, it'd be useful to consider the following:

  • It's often useful to display the daily highs and lows as dotted grey lines to indicate price variation boundaries.
  • I don't actually need to show the legend for the daily highs and lows - it's quite obvious which is which.
  • I find it useful to plot the daily average as a line between the price variation boundaries.

With this, we can go ahead with the formatting - here I like to tap on Plotly Express:

## Changing the daily highs and lows to
## grey dotted lines, and removing their legend
fig.update_traces(
    line_width=2,
    line_color="lightgrey",
    line_dash="dash",
    showlegend=False
)

## Formatting the chart area
fig.update_layout(
  xaxis_title="<b>Date</b>", 

  yaxis_title="<b>Price</b>",

  title="<b>YTD Price Data for Various Stocks</b>",
  title_x=0.5,
  title_font_size=30,

  paper_bgcolor="white",
  plot_bgcolor="white"
)

## Calculating the daily average prices
## into a new dataframe for loading
stocks_ave = pd.DataFrame({
  'TSLA': (TSLA_df["Low"] + TSLA_df["High"]) / 2,
  'NIO': (NIO_df["Low"] + NIO_df["High"]) / 2
})

## Adding ave prices data into object
for column in stocks_ave.columns.to_list():
    fig.add_trace(
        go.Scatter(
            x = stocks_ave.index,
            y = stocks_ave[column],
            name = column + " Daily Ave"
        )
    )

## Adding a date range selector
fig.update_layout(
  xaxis=dict(
        rangeselector=dict(
            buttons=list([
                dict(count=1,
                     label="1m",
                     step="month",
                     stepmode="backward"),
                dict(count=6,
                     label="6m",
                     step="month",
                     stepmode="backward"),
                dict(count=1,
                     label="YTD",
                     step="year",
                     stepmode="todate"),
                dict(count=1,
                     label="1y",
                     step="year",
                     stepmode="backward"),
                dict(step="all")
            ])
        ),

        rangeslider=dict(
            visible=True
        )
  )
)

fig.show()

You should end up with the chart you saw at the very beginning of this article:

Possible Improvements

One thing to consider is the possibly large number of stocks you might wish to monitor and study. It's worth considering how to implement the above procedure in the form of a loop over all the stocks. We'll come back to this in a future article. Again, the full Python implementation is below for your convenience:

## Loading Libraries
  import plotly.express as px
  import plotly.graph_objects as go
  import pandas as pd
  import yfinance as yf
  
  ## Generating ticker objects
  TSLA = yf.Ticker("TSLA")
  NIO = yf.Ticker("NIO")
  
  ## Accessing stock price data
  TSLA_df = TSLA.history(period="ytd")
  NIO_df = NIO.history(period="ytd")
  
  ## Extracting only the daily high prices
  ## into a new dataframe
  stocks_high = pd.DataFrame({
    'TSLA': TSLA_df['High'],
    'NIO': NIO_df['High']
  })
  
  ## Extracting only the daily low prices
  ## into a new dataframe
  stocks_low = pd.DataFrame({
    'TSLA': TSLA_df['Low'],
    'NIO': NIO_df['Low']
  })
)

## Generating an empty Plotly chart object
fig = go.Figure()

## Adding high prices data into object
## Note: Dataset 1 = TSLA High, Dataset 2 = NIO High
for column in stocks_high.columns.to_list():
    fig.add_trace(
        go.Scatter(
            x = stocks_high.index,
            y = stocks_high[column],
            name = "Daily High"     # legend name
        )
    )

## Adding low prices data into object
## Note: Dataset 3 = TSLA Low, Dataset 4 = NIO Low
for column in stocks_low.columns.to_list():
    fig.add_trace(
        go.Scatter(
            x = stocks_low.index,
            y = stocks_low[column],
            name = "Daily Low"      # legend name
        )
    )

## Adding a dropdown list
fig.update_layout(
    updatemenus=[go.layout.Updatemenu(
        
        active=0,
        
        ## You want three buttons labelled "All Stocks", "TSLA" and "NIO"
        ## so create three dictionaries with three separate "label" key-value pairs.
        ## Also add in an "update" value under the "method" key, so that
        ## Plotly knows that you want to update the data in your charts.
        buttons=list(
              [dict(label = 'All Stocks',
                  method = 'update',
                  args = [{'visible': [True, True, True, True]},
                    {'title': 'YTD Price Data for Various Stocks',
                     'showlegend':True}]),
              dict(label = 'TSLA',
                  method = 'update',
                  args = [{'visible': [True, False, True, False]},
                          {'title': 'YTD TSLA Stock Price Data',
                           'showlegend':True}]),
              dict(label = 'NIO',
                  method = 'update',
                  args = [{'visible': [False, True, False, True]},
                          {'title': 'YTD NIO Stock Price Data',
                           'showlegend':True}])
            ]),
        
        ## Positioning your dropdown along x-axis
        ## Play around with these
        x=0.01,
        xanchor="left",
        
        ## Positioning your dropdown along y-axis
        ## Play around with these
        y=1.2,
        yanchor="top"
        
      )]    
    )

fig.update_layout(
  title="YTD Price Data for Various Stocks",
  title_x=0.5
)

## Changing the daily highs and lows to
## grey dotted lines, and removing their legend
fig.update_traces(
    line_width=2,
    line_color="lightgrey",
    line_dash="dash",
    showlegend=False
)

## Formatting the chart area
fig.update_layout(
  xaxis_title="Date", 

  yaxis_title="Price",

  title="YTD Price Data for Various Stocks",
  title_x=0.5,
  title_font_size=30,

  paper_bgcolor="white",
  plot_bgcolor="white"
)

## Calculating the daily average prices
## into a new dataframe for loading
stocks_ave = pd.DataFrame({
  'TSLA': (TSLA_df["Low"] + TSLA_df["High"]) / 2,
  'NIO': (NIO_df["Low"] + NIO_df["High"]) / 2
})

## Adding ave prices data into object
for column in stocks_ave.columns.to_list():
    fig.add_trace(
        go.Scatter(
            x = stocks_ave.index,
            y = stocks_ave[column],
            name = column + " Daily Ave"
        )
    )

## Adding a date range selector
fig.update_layout(
  xaxis=dict(
        rangeselector=dict(
            buttons=list([
                dict(count=1,
                     label="1m",
                     step="month",
                     stepmode="backward"),
                dict(count=6,
                     label="6m",
                     step="month",
                     stepmode="backward"),
                dict(count=1,
                     label="YTD",
                     step="year",
                     stepmode="todate"),
                dict(count=1,
                     label="1y",
                     step="year",
                     stepmode="backward"),
                dict(step="all")
            ])
        ),

        rangeslider=dict(
            visible=True
        )
  )
)

fig.show()
All fine and good, but how do we produce candlestick charts, those used by the pros?

Find more Finalytics stories on my blog. Have a suggestion? Contact me at [email protected].