Plotly & Dash — Dashboard of stocks from a chosen date of reference

Parag Kar
7 min readMar 19, 2023

--

A few days back I had written a note to describe how MultiIndexing can be used to compare quantities of widely different absolute values. In order to demonstrate the concept I used world currencies and plotted them on the same graph for the purpose of comparison. On this note, I want to extend this effort further and use it to compare various stock prices and plot them on a dashboard. The layout will consist of four charts from the chosen date of reference — 1) Line graph — %Return on chosen stocks; 2) Line graph — The closing price of stocks; 3) Histogram — Distribution of %Return; & 4) Coefficient of Variation.

The dashboard so created will be current, i.e will have the latest value of closing prices, and will enable the users to choose multiple stocks on the same layout for the purpose of comparison. The user will be able to use this dashboard on the internet and will be fully compatible with being viewed on the screen of mobile phones. So without wasting any more time let's begin with the process of creating this dashboard.

Stock’s Pricing Data

This the user can pull easily from yahoo finance. The code used for the same is reproduced below.

import yfinance as yf

#Choosing the set of stocks we plan to analyse (user can add more to this dict)
stock_tickers = {"Nokia": "NOK","Apple":"AAPL","Qualcomm": "QCOM","Google": "GOOG","Facebook":"META",
"Netflix": "NFLX","Reliance": "RELIANCE.NS","Nvidia":"NVDA","Amazon": "AMZN",
"Airtel":"BHARTIARTL.BO","MediaTek":"2454.TW","Cisco":"CSCO", "Tesla":"TSLA",
"Microsoft":"MSFT","TSMC":"TSM","Tencent":"0700.HK", "Samsung":"005930.KS", "Broadcom":"AVGO",
"ASML Holding":"ASML", "Oracle":"ORCL", "Alibaba":"BABA", "Salesforce":"CRM", "Texas Inst":"TXN",
"Adobe":"ADBE","SAP":"SAP","AMD":"AMD"}

#Sorting the stocks alphabatically
myKeys = list(stock_tickers.keys())
myKeys.sort()
stock_tickers = {i: stock_tickers[i] for i in myKeys}

#pulling the stock data of chosen stocks and combining them in one dataframe
def download_stocks(tickers):
df_comb=pd.DataFrame()
for ticker in tickers:
try:
stock = yf.Ticker(ticker)
df = stock.history(period="10y")
df["ticker"]=ticker
df_comb=pd.concat([df,df_comb], axis=0)

except:
pass
return df_comb

The output data frame will appear something like this.

Figure 1 — Output DataFrame

Calculating %Returns on Investment

The next step is to calculate the %Return on investments. For this purpose, the following code can be used.

def process_for_df_ret(date_value, tickers, df_comb):
#processing all stock files
# df_comb=pd.read_csv("combined.csv", index_col="Date", parse_dates=True)

df_comb.index=[dt.datetime.strftime(x, "%d-%m-%Y") for x in list(df_comb.index)]
df_comb.index = pd.to_datetime(df_comb.index, dayfirst = True)

returns ={}

for ticker in tickers:
filt = (df_comb["ticker"]==ticker)
temp_df= df_comb[filt]
closest_date_index = temp_df.index.get_loc(date_value, method="nearest")
ref_value_close = temp_df.iloc[closest_date_index,:][3]
temp_df = temp_df.iloc[closest_date_index:,:]
value_df = temp_df["Close"]/ref_value_close
returns.update({ticker: (value_df-1)*100})

df_ret = pd.DataFrame(returns)
df_ret = df_ret.reset_index()
df_ret = df_ret.melt(id_vars=['index'], value_vars=tickers, ignore_index=False)
df_ret.columns =["Date","Ticker","%Returns"]
df_ret = df_ret.dropna()
df_ret = df_ret.set_index("Date")

return df_ret

The concept is similar to what I described in my earlier note on world currencies. For this, we need to identify the data index for the chosen data of reference, and then use that to calculate %Returns. The only point to note here is that the date will be used as an index and needs to be properly formatted so as be palatable to the output “date value” from the bootstrap date picker component. The output data frame will appear something like this.

Figure 2 — The output %Return Dataframe

Calculating Coff of Variations

The coefficient of variation is nothing but the ratio between the standard deviation of the stock from the chosen date of reference and its mean value. The code used for calculating the same is reproduced below.

def process_for_df_cov(date_value, tickers, df_comb):

df_comb.index=[dt.datetime.strftime(x, "%d-%m-%Y") for x in list(df_comb.index)]
df_comb.index = pd.to_datetime(df_comb.index, dayfirst = True)

cov = {}
for ticker in tickers:
filt = (df_comb["ticker"]==ticker)
temp_df= df_comb[filt]
closest_date_index = temp_df.index.get_loc(date_value, method="nearest")
ref_value_close = temp_df.iloc[closest_date_index,:][3]
temp_df = temp_df.iloc[closest_date_index:,:]
value_df = temp_df["Close"]/ref_value_close
sd = temp_df[temp_df["ticker"]==ticker]["Close"].std()
mean = temp_df[temp_df["ticker"]==ticker]["Close"].mean()
cov.update({ticker: round(sd/mean,2)})

df_cov = pd.DataFrame.from_dict(cov, orient='index')
df_cov = df_cov.reset_index()
df_cov.columns = ["Ticker","COV"]

return df_cov

This is simple and uses stock tickers, the combined data frame, and chosen data value as input parameters. The output data frame will appear something like what is shown below.

Figure 3 — Dataframe with Cov Values

The Main Program

The main program is a dash application and consists of a “dbc.Container” with four rows. Each of the rows has multiple columns containing the dropdown, datepicker, and the dcc.Graphs. The code is shown below.

#main application

# Customizing the Layout

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
# app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP],
#the meta_tags below is for mobile viewing
meta_tags=[{'name': 'viewport',
'content': 'width=device-width, initial-scale=1.0'}]
)

#The beginning of the main container

app.layout = dbc.Container([
dbc.Row([
dbc.Col(html.H5("Performance of stocks from the date of reference",
className="text-center opacity-100 p-2 m-1 bg-primary text-light fw-bold rounded"),#width=12
xs=12, sm=12, md=12, lg=5, xl=5 #for viewing in different screen size
)
],justify="center"
),

#Dropdown for selecting multiple stocks
dbc.Row([
dbc.Col([
html.P("Choose Stocks of Interest:", style={"textDecoration":"bold"}),
dcc.Dropdown(id='stock-type', clearable=False,
value=["AAPL"],multi=True,
options=[{'label': key, 'value': value} for key, value in
stock_tickers.items()]),
], #width={'size':6},
xs=12, sm=12, md=12, lg=5, xl=5, #for viewing in different screen size
),
#Picking a reference date using the datepicker calendar

dbc.Col([
html.P("Choose a Starting Date: ", style={"textDecoration":"bold"}),
dcc.DatePickerSingle(id='date-picker-single',
min_date_allowed=date(2000,1, 1),
max_date_allowed=date(2023,3, 1),
initial_visible_month= date(2022,1,31),
date=date(2022,1, 31))
], #width ={'size':6}
xs=12, sm=12, md=12, lg=5, xl=5 #for viewing in different screen size

)
], justify="center"
),
#These are the rows hosting the dynamic graphs that will get created based on user selection

dbc.Row([
dbc.Col([dcc.Graph(id="fig1_line", className="shadow-2g",figure={}, style={"pading":10,"border":"ridge"})

],#width ={'size':12}
xs=12, sm=12, md=12, lg=5, xl=5 #for viewing in different screen size
),

dbc.Col([dcc.Graph(id="fig_hist", className="shadow-2g",figure={}, style={"pading":10,"border":"ridge"})

],#width ={'size':12}
xs=12, sm=12, md=12, lg=5, xl=5 #for viewing in different screen size
),
], justify="center"
),

dbc.Row([

dbc.Col([dcc.Graph(id="fig2_line", className="shadow-2g",figure={}, style={"pading":10,"border":"ridge"})

],#width ={'size':12}
xs=12, sm=12, md=12, lg=5, xl=5 #for viewing in different screen size
),

dbc.Col([dcc.Graph(id="fig_bar", className="shadow-2g",figure={}, style={"pading":10,"border":"ridge"})

],#width ={'size':12}
xs=12, sm=12, md=12, lg=5, xl=5 #for viewing in different screen size
),

], justify="center"
),

], fluid=False) #Choose fluid = True for killing the margins on either side of the Dashboard

The main program is fed by a set of callbacks. These are listed below.

# Callback section: connecting the components
# ************************************************************************
# Line chart 1
@app.callback(
Output('fig1_line', 'figure'),
Input(component_id="date-picker-single",component_property="date"),
Input(component_id="stock-type",component_property="value"))

def update_graph(date_value, tickers):
if date_value is not None:
date_object = date.fromisoformat(date_value)
date_string = date_object.strftime('%b %d, %y')


df_comb = download_stocks(tickers)

df_ret = process_for_df_ret(date_value,tickers, df_comb)


fig1_line = px.line(df_ret, x=df_ret.index, y="%Returns", color="Ticker", template='plotly_white',
title="% Returns on Stocks from "+date_string)

fig1_line.update_xaxes(fixedrange=True)
fig1_line.update_yaxes(fixedrange=True)

fig1_line.update_layout(uniformtext_minsize=14, uniformtext_mode='hide',
legend={'x':0,'y':1.05,"orientation":"h"})

fig1_line.update_layout(height=fig_height, width=fig_width,yaxis_title=None, xaxis_title=None)

fig1_line.update_layout(
margin=dict(
l=0,
r=10,
t=50,
b=0,
))


return fig1_line

# Line chart 2
@app.callback(
Output('fig2_line', 'figure'),
Input(component_id="date-picker-single",component_property="date"),
Input(component_id="stock-type",component_property="value"))

def update_graph(date_value, tickers):
if date_value is not None:
date_object = date.fromisoformat(date_value)
date_string = date_object.strftime('%b %d, %y')

df_comb = download_stocks(tickers)

df_stocks = process_for_df_stocks(date_value,tickers, df_comb)

df_close = df_stocks.loc[:,["Close","ticker"]]

df_close = df_close.rename(columns={'ticker': 'Ticker'})


fig2_line = px.line(df_close, x=df_close.index, y="Close", color="Ticker", log_y=True, template='plotly_white',
labels={"Close":"Closing Price","index":"Date"}, title="Closing Price of Stocks from "+date_string)

fig2_line.update_xaxes(fixedrange=True)
fig2_line.update_yaxes(fixedrange=True)

fig2_line.update_layout(uniformtext_minsize=14, uniformtext_mode='hide',legend={'x':0,'y':1.1,"orientation":"h"})

fig2_line.update_layout(height=fig_height, width=fig_width,yaxis_title=None, xaxis_title=None)

fig2_line.update_layout(
margin=dict(
l=0,
r=10,
t=50,
b=0,
))

fig2_line.update_layout(showlegend=False)

return fig2_line


# Histogram
@app.callback(
Output('fig_hist', 'figure'),
Input(component_id="date-picker-single",component_property="date"),
Input(component_id="stock-type",component_property="value"))

def update_graph(date_value, tickers):
if date_value is not None:
date_object = date.fromisoformat(date_value)
date_string = date_object.strftime('%b %d, %y')

df_comb = download_stocks(tickers)

df_ret = process_for_df_ret(date_value,tickers, df_comb)

Tickers = list(set(df_ret["Ticker"]))

fig_hist = make_subplots(rows=len(Tickers), cols=1, shared_xaxes=True)
for i, Ticker in enumerate(Tickers):
filt = df_ret["Ticker"]==Ticker
df_ret_filt = df_ret[filt]
# fig_hist.append_trace(go.Histogram(x=df_ret_filt["%Returns"], texttemplate="%{x}", textfont_size=8), row=i + 1, col=1)
fig_hist.append_trace(go.Histogram(x=df_ret_filt["%Returns"]), row=i + 1, col=1)

fig_hist.update_layout(barmode='group')
# fig_hist.update_yaxes(title_text='Count')
fig_hist.update_layout(height=fig_height, width=fig_width, title_text="Distribution of % Returns from "+date_string, template='plotly_white')
fig_hist.update_layout(showlegend=False)
fig_hist.update_xaxes(fixedrange=True)
fig_hist.update_yaxes(fixedrange=True)
fig_hist.update_traces(textposition="top center", selector=dict(type='scatter'))

fig_hist.update_layout(
margin=dict(
l=0,
r=10,
t=50,
b=0,
))


return fig_hist


# Bar Chart
@app.callback(
Output('fig_bar', 'figure'),
Input(component_id="date-picker-single",component_property="date"),
Input(component_id="stock-type",component_property="value"))

def update_graph(date_value, tickers):

if date_value is not None:
date_object = date.fromisoformat(date_value)
date_string = date_object.strftime('%b %d, %y')

df_comb = download_stocks(tickers)

df_cov = process_for_df_cov(date_value,tickers, df_comb)

fig_bar = px.bar(df_cov, x="Ticker", y="COV", color="Ticker", text_auto=True, template='plotly_white')

fig_bar.update_traces(textfont_size=7, textangle=0, textposition="inside", cliponaxis=False)

fig_bar.update_xaxes(fixedrange=True)
fig_bar.update_yaxes(fixedrange=True)

fig_bar.update_layout(uniformtext_minsize=14, uniformtext_mode='hide',
legend={'x':0,'y':1.05,"orientation":"h"}, title="Coefficient of Var (SD/Mean) from "+date_string),
fig_bar.update_layout(height=fig_height, width=fig_width, yaxis_title=None, xaxis_title=None)

fig_bar.update_layout(showlegend=False)


fig_bar.update_layout(
margin=dict(
l=0,
r=10,
t=50,
b=0,
))

return fig_bar


# Runing the application
if __name__ == '__main__':
app.run_server(debug=True,use_reloader=False)

The Output Dashboard (Desktop Version)

The final output dashboard for the desktop version will appear something like this.

Figure 4 — The Final Dashboard

The Output Dashboard (Mobile Version)

The final output dashboard for the mobile version will appear something like this.

Figure 5 — Mobile Version

Note that the effect of auto adjustment of the dashboard due to the alternation of screen size can be experienced even on the desktop. This the user can do by alternating the size of the web browser manually.

Uploading the Dashboard on the Web

The final step of this process is to upload the dashboard on the web. For that, we need to create a requirement.txt file as described in the following picture.

Figure 6 — The Requirements.txt file

The content of this file is information to the rendering server as to what set of libraries it needs to load for enabling the application to run in its environment. Then using the drag and drop option, we need to upload the following files in the GitHub repository as shown below.

Figure 7 — Uploaded files in the GitHub repository

Then using the HTTPS link of the repository in the GitHub code tab one can render the app on the web for the purpose of general viewing. The link to which I have uploaded this application can be found here. Also, you can visit my GitHub page to get access to the full code. Note while you upload the code on GitHub do not forget to add the following line (server = app.server) just below the main app code line as described below.

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP],
meta_tags=[{'name': 'viewport',
'content': 'width=device-width, initial-scale=1.0'}]
server = app.server

Hope you found this useful. Thanks for reading.

(I am aggregating all the articles on this topic here, for easy discovery and reference.)

--

--

Parag Kar
Parag Kar

Written by Parag Kar

EX Vice President, Government Affairs, India and South Asia at QUALCOMM

No responses yet