from dash import Dash, html, dash_table, dcc, Input, Output, State, no_update
import dash_bootstrap_components as dbc
import pandas as pd
import plotly_express as px
import yfinance as yf
from datetime import datetime
'''
STRUCTURE OF THE APP
1. defining functions to use to retrieve and display data and create plots
2. initializing the app and app layout
3. app callback, Inputs/Outputs, and callback function
Dash inherits a certain format for code, as far as my research indicates,
so the app itself is best defined entirely in the main function at the very least.
My research indicates that global scope would be even better and more straightforward,
but using main() seems to work out okay. Function defaults are the intended
values upon app startup.
Documentation:
https://finance.yahoo.com/lookup/
https://pypi.org/project/yfinance/
https://dash.plotly.com/
https://plotly.com/python/plotly-express/
'''
#Functions for use within the app
def get_ticker_object(ticker:str='^GSPC'):
#initializes object; this object is used in two different functions so it is more efficient to have it on larger scope in its own function
= yf.Ticker(ticker)
ticker_object return ticker_object
def get_ticker_history_df(ticker_object, time:str = 'ytd'):
'''This code uses yfinance module to get historical data from the Yahoo
Finance API.
Ticker: Gets data for S&P 500 using ticker '^GSPC' by default.
Takes any valid ticker identifier.
Time: default = 'ytd';
possible: ['1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max']
'''
if time != '1d':
#gets history given time frame
= ticker_object.history(time)
history #date is current index; resets index
= pd.DataFrame(history).reset_index()
df = df.sort_values('Date', ascending = False)
dfelif time == '1d':
= ticker_object.history(time, interval = '1m')
history = pd.DataFrame(history).reset_index()
df = df.sort_values('Datetime', ascending = True)
df
#more descriptive column names
= df.columns[1:5]
old = [f'{col} (USD)' for col in df.columns[1:5]]
new = dict(zip(old, new))
old_new_dict = df.rename(columns = old_new_dict)
df
if time == '1d':
'Datetime'] = pd.to_datetime(df['Datetime'])
df['Time'] = df['Datetime'].dt.time
df[
#returns df
return df
def get_stock_name(ticker_object):
= ticker_object.info
info
if info != {'trailingPegRatio': None}: #this is the output when the query can't find the given stock
return info['longName']
else:
return 'Stock Not Found'
#makes fig when time != '1d'; '1d' takes different time formatting on x-axis
def get_ticker_history_fig(df, name):
= px.line(
fig
df,= 'Date',
x = 'Close (USD)',
y
).update_layout(= 'Date',
xaxis_title = 'Daily Closing Price (USD)',
yaxis_title = f'Closing Prices of Stocks over Time- {name}',
title = 'plotly_dark'
template
)
return fig
#makes fig when time == '1d' with hr/min on x-axis
def get_ticker_history_fig_day_price(df, name):
= px.line(
fig
df,= 'Time',
x = 'Close (USD)'
y
).update_layout(= '',
xaxis_title = 'Price',
yaxis_title = f"Today's Price- {name}",
title = 'plotly_dark'
template
).update_xaxes(='%I:%M %p'
tickformat
'''
can't figure out why this doesn't work; I want to format
the time ticks differently but it won't work properly
'''
)
return fig
#---------------------------------------------------------------------
'''APP STARTS HERE'''
#initializes Dash object 'app'; however, an initial callback is still made
= Dash(__name__, external_stylesheets=[dbc.themes.CYBORG])
app = app.server
server
= get_ticker_object('^GSPC')
ticker_object = get_stock_name(ticker_object)
name = get_ticker_history_df(ticker_object)
df = get_ticker_history_fig(df, name)
fig
#initializes layout of page
= html.Div([
app.layout
html.Header(['Stock Price Tracker', id = 'app-title', style = {'marginBottom': 5}),
html.H1(str(datetime.now().date().__format__('%B %d, %Y')), id = 'date-header', style = {'marginTop': 5})
html.H2(id = 'header'),
], "Provide a ticker and select a time frame for data display. For example, FAANG tickers are 'FB', 'AMZN', 'AAPL', 'NFLX'; S&P 500 is '^GSPC'. For more info, visit the following site: "), html.P(html.A(' Yahoo Finance Lookup', href = 'https://finance.yahoo.com/lookup/', target='_blank', id = 'yf-link', style= {'color': 'white'}))],
html.Div([html.P(={'marginBottom':'15px'}
style
),'Select stock (press Enter to update):'),
html.P(
dcc.Input(id='stock-input',
type = 'text',
= '^GSPC',
value = 'Give any valid ticker (ex. ^GSCP is default):',
placeholder= 1
n_submit
),'Select timeframe:'),
html.P(
dcc.Dropdown(id = 'time-input',
= ['1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max'],
options = 'ytd',
value = False
clearable
),
html.Br(),
html.Hr(),id='time-series-chart', style={'marginBottom': 20, 'marginTop': 20}),
dcc.Graph(= {'marginBottom': 20}),
html.Hr(style id= 'data-table', data=df.to_dict('records'), page_size = 10, style_header={'backgroundColor': 'rgb(30, 30, 30)', 'color': 'white'}, style_data={'backgroundColor': 'rgb(50, 50, 50)', 'color': 'white'})
dash_table.DataTable(
])
#callback to the app; Input tracks input changes to produce Output
@app.callback(
'data-table', 'data'), Output('time-series-chart', 'figure')],
[Output('stock-input', 'n_submit'), Input('time-input', 'value')],
[Input('stock-input', 'value')]
[State(
)
#callback function associated with new ticker or time frame
def update(n_submit, time_input, stock_input,):
if (stock_input and n_submit > 0) or time_input: #if stock changes and 'Enter', or time input changes
try:
= get_ticker_object(str(stock_input))
ticker_object = get_ticker_history_df(ticker_object, time = str(time_input))
df = get_stock_name(ticker_object)
name if time_input != '1d':
= get_ticker_history_fig(df, name)
fig elif time_input == '1d':
= get_ticker_history_fig_day_price(df, name)
fig except:
= pd.DataFrame(columns= ['Close (USD)', 'Date'])
df return df.to_dict('records'), get_ticker_history_fig(df, 'Stock Not Found')
return df.to_dict('records'), fig
return no_update, no_update #won't update without new data
'''
to_dict('records') is necessary because Dash needs a JSON object
these functions inherit the form of the callback (specifically the order);
'stock-input' becomes stock_input arg, time-input becomes time_input arg
output df.to_dict('records') becomes Output('data-table'), fig becomes Output('time-series-chart')
'''
#debug=True for debugging functionality
if __name__ == '__main__':
=False) app.run_server(debug
Stock Price Tracker Application
This project was built using Plotly Dash, a dashboard framework that is built on Flask. Through user input, a stock ticker and a time period can be specified, for which the application will:
- Extract the data from the Yahoo Finance API
- Transform the data appropriately using Pandas
- Create a time series graphical representation of the data with Plotly
- Load both the plot and the table into the dashboard.
Feel free to explore the app running with Render:
https://stock-price-dashboard.onrender.com/
It may take a minute or two to spin up in the server.
In the meantime, check out the code I wrote to create the project: