-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathSimpleDashboard.py
More file actions
298 lines (275 loc) · 11.1 KB
/
Copy pathSimpleDashboard.py
File metadata and controls
298 lines (275 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
import pandas as pd
import re
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import yfinance
from Layout import *
##### Dashboard layout #####
# Dash Set up
app = dash.Dash()
# Base Layout
app.layout = html.Div([
dcc.Tabs(id='dashboard-tabs', value='price-tab',children=[
dcc.Tab(label='Stock Price', value='price-tab',children=[
html.Div([html.H2(id='tab1-stock-name',
style={'width':'30%','display':'inline-block'}),
html.H4(id='tab1-ticker',
style={'width':'10%','display':'inline-block'})],
style={'width':'90%','margin':'auto'}),
# Position 0, Title
html.Div(get_tab1_info_box(),
style={'width':'90%','margin':'auto'}),
# Position 1, Info and dropdown
html.Div(get_stats_graph_layout('tab1'),
style={'width':'90%','margin':'auto'})
# Position 2, Table of stats and graph
]), # Tab 1, End price-tab
dcc.Tab(label='Stock/Index Growth', value='change-tab',children=[
html.Div([html.H2('Stock Price Growth vs. Index Growth')],
style={'width':'90%','margin':'auto',
'text-align':'center'}),
# Position 0, Title
html.Div(get_tab2_info_box(),
style={'width':'90%','margin':'auto'}),
# Position 1, Info and dropdown
html.Div(get_stats_graph_layout('tab2'),
style={'width':'90%','margin':'auto'})
# Position 2, Table of stats and graph
]) # Tab 2, End change-tab
]) # End Tabs
]) # End base Div
##### Callbacks for Tab 1 ######
### Helper Function for Tab 1 Callbacks ###
"""
Verify ticker, return (Valid Ticker is True, Ticker).
This function only verify the format, it does not verify existence.
"""
def verify_ticker(ticker, mkt):
# Identify which ticker is that
# For Hong Kong Stocks
if mkt == 'hk':
tick = re.findall('^\d{1,5}$', ticker)
if len(tick)>0:
tick = str(tick[0])[::-1]
while(len(tick)<4):
tick += '0'
tick = tick[::-1]
tick += '.HK'
return True, tick
# If entered ticker invalid
else:
return False, None
#
elif mkt == 'us':
tick = re.findall('^[A-Za-z]{1,4}$', ticker)
if len(tick)>0:
return True, tick[0].upper()
# If entered ticker invalid
else:
return False, None
return False, None
# Obtain 50- ,100- and 200-day moving average
def getMA(stock, time, date_list):
if 'mo' in time or time=='ytd' or time=='1y':
df = stock.history(period='2y')
elif time=='2y' or time=='3y' or time=='4y':
df = stock.history(period='5y')
else:
df = stock.history(period='10y')
df = df.reset_index()[['Date','Open','Low','High','Close']]
df['MA50'] = df.Close.rolling(50).mean()
df['MA100'] = df.Close.rolling(100).mean()
df['MA200'] = df.Close.rolling(200).mean()
df = df.loc[(df['Date']>=date_list.min()) & (df['Date']<=date_list.max())]
return df
# Generate stock price and graph on Tab 1
@app.callback([Output('tab1-stock-name','children'), # Stock Name
Output('tab1-ticker','children'), # Ticker
Output('tab1-stock-price','children'), # Current Stock Price
Output('tab1-stock-price-change','children'), # Price Change
Output('tab1-stock-price-change','style'),
# Price Change font colour
Output('tab1-stock-price-percentchange','children'),
# Price Percent Change
Output('tab1-stock-price-percentchange','style'),
# Price Precent Change font colour
Output('tab1-error-message','children'), # Error Message
Output('tab1-vis','figure'), # Stock Price Chart
Output('tab1-table','children')], # Table of Stock Stats
[Input('tab1-submit','n_clicks'), # Button
Input('tab1-time-interval','value')], # Time interval
[State('tab1-ticker-input','value'), # Ticker textbox input
State('tab1-market-dropdown', 'value')]) # Market Dropdown input
def get_ticker(n_clicks, time, ticker, mkt):
# For default setting
if ticker == '':
return 'Please Enter a Stock Ticker', \
'','','',{'width':'20%', 'display':'inline-block'},'', \
{'width':'20%', 'display':'inline-block'},'', \
{'data':None}, None
# Verify ticker format in respective to stock market
stockFormat, ticker = verify_ticker(ticker, mkt)
# Catch incorrect
if stockFormat is False:
return 'Wrong Ticker', '#######', '$##.##', '##.##', \
{'width':'20%', 'display':'inline-block'}, '##.##%', \
{'width':'20%', 'display':'inline-block'}, \
'Error! Please try again.', {'data':None}, None
# Obtain stock price and stats
stock = yfinance.Ticker(ticker)
# Catch if stock exists
if stock.history(period='ytd').shape[0] == 0:
return 'Wrong Ticker', '#######', '$##.##', '##.##', \
{'width':'20%', 'display':'inline-block'}, '##.##%', \
{'width':'20%', 'display':'inline-block'}, \
'Error! Please try again.', {'data':None}, None
### Stock Stats for Info Box ###
try:
# Name and price
stock_name = stock.info['longName']
price_list = stock.history(period=time)['Close'].tolist()
price = f'${price_list[-1]:.2f}'
# Price Change
price_change = price_list[-1] - price_list[-2]
price_percent_change = (price_list[-1]/price_list[-2])-1
if price_change > 0:
price_change_colour = {'color':'green'}
else:
price_change_colour = {'color':'red'}
price_change_colour['display']= 'inline-block'
price_change_colour['width']= '20%'
price_change_colour['font-size'] = '150%'
price_change = f'{price_change:.2f}'
price_percent_change = f'{price_percent_change*100:,.2f}%'
df = getMA(stock, time,
stock.history(period=time).reset_index()['Date'])
fig = getCandlestick(df)
table = getTab1Table(stock.history(period=time).reset_index(),
stock.info)
except:
return 'Sorry! Company Not Available', '#######', '$##.##', '##.##', \
{'width':'20%', 'display':'inline-block'}, '##.##%', \
{'width':'20%', 'display':'inline-block'}, \
'Error! Please try again another Company.', {'data':None}, None
return stock_name, ticker, price, price_change, price_change_colour, \
price_percent_change, price_change_colour, '', fig, table
##### Callbacks for Tab 2 ######
"""
To generate the options to display on second dropdown for users to include
stock(s) addition to index.
Tab 2 only relies on data from Yahoo Finance
"""
"""
This callback return the list of stocks of corresponsed market. It requires
user to input which market to look and return 2 outputs.
1- List of stocks in the multi-dropdown list
2- The default value of the multi-dropdown list, in order to unselect previous
stocks.
"""
@app.callback([Output('tab2-stock-include','options'),
Output('tab2-stock-include','value')],
[Input('tab2-index-choice','value')])
def generate_dropdown_stocknames(mkt):
if mkt == 'hsi':
stock_list = pd.read_csv('../IndexComponents/HengSengStockList.csv',
dtype=str)
stock_list = stock_list.sort_values('Ticker')
stock_list['label'] = stock_list['Ticker'].astype(str) + '\t' + \
stock_list['Company']
opts = [{'label': label, 'value': ticker} for label, ticker in
zip(stock_list['label'].tolist(),
stock_list['Ticker'].tolist())]
return opts, []
elif mkt == 'sp500':
stock_list = pd.read_csv('../IndexComponents/SP500StockList.csv',
engine='python')
stock_list = stock_list.sort_values('Ticker')
stock_list['label'] = stock_list['Ticker']
opts = [{'label': label, 'value': ticker} for label, ticker in
zip(stock_list['label'].tolist(),
stock_list['Ticker'].tolist())]
return opts, []
return [], []
"""
This callback generates Graph and the summary table of statistic.
The steps of this completing this functions:
1- Obtain the inputs, first get a list of stocks along with which index
2- If there is at least one stock selected other than the index, obtain
the data frame of stock price. Since there is no stock selected for
default setting, this step will be skipped if there is no stock
selected
3- Take the price of the first day of the time interval, calculate the
price change in percentage for each day relatively to the first day.
4- Generate a line chart from the data prepared in step 3
5- Generate a html table for the summary of index statistics
"""
@app.callback([Output('tab2-table','children'),
Output('tab2-vis','figure')],
[Input('tab2-index-choice','value'),
Input('tab2-stock-include','value'),
Input('tab2-time-interval','value')])
def generate_tab2_graph(mkt,stocks,time):
# Function to calculate price change relative to the first day
def get_price_change(price_list):
base_price = price_list[0]
return [(price/base_price)-1 for price in price_list]
# Obtain the histoical stock price of selected stocks
df_stocks = []
if len(stocks) > 0:
for stock in stocks:
if mkt=='hsi':
stock = stock[1:] + '.HK'
stock_df = yfinance.Ticker(stock).history(period=time)
stock_df = stock_df.reset_index()[['Date','Close']]
stock_df.columns = ['Date',stock]
df_stocks.append(stock_df)
"""
Obtain the historical index and with stock price dataframe.
Then, convert to percent change relative to first day in the data frame.
"""
y_ticker = None
index_col = None
if mkt == 'hsi':
y_ticker = '^HSI'
index_col = 'Heng Seng Index'
elif mkt == 'sp500':
# Note that Yahoo use ^GSPC as SP 500 ticker, SPX only has 1 entry
y_ticker = '^GSPC'
index_col = 'S&P 500'
# Prevent error if nothing selected
else:
return html.Table(), {'data': None}
# Prepare the data set to plot the line chart
index = yfinance.Ticker(y_ticker)
df_index = index.history(period=time).reset_index()[['Date','Close']]
df_index.columns = ['Date', index_col]
# To take out duplicated columns, ie, Date, while concat
if len(stocks) > 0:
for df_temp in df_stocks:
df_index = pd.merge(df_index, df_temp, how='left',on='Date')
# In case there are NA's, fill with last observation
df_index = df_index.fillna(method='backfill', axis=1)
# Drop duplicated columns in case there are
df_index = df_index.loc[:,~df_index.columns.duplicated()]
# Convert from price to percent change relative to Day 1 of the period
for col in df_index.columns:
if col != 'Date':
df_index[col] = get_price_change(df_index[col].tolist())
fig = getLinePlot(df_index, 2)
# Prepare the data set to list the summary on the table
last_close = df_index[index_col].tolist()[-1]*100
df_index_period = index.history(period=time).reset_index()
df_index_52weeks = index.history(period='1y').reset_index()
range_period = (round(df_index_period['Close'].min(),2),
round(df_index_period['Close'].max(),2))
range_52weeks = (round(df_index_52weeks['Close'].min(),2),
round(df_index_52weeks['Close'].max(),2))
volume = (index.history(period=time).reset_index()['Volume'].tolist()[-1],
index.history(period=time).reset_index()['Volume'].mean())
# Generate a table of summary
table = getTab2Table(index_col, last_close, range_period, range_52weeks)
return table, fig
if __name__ == '__main__':
app.run_server(debug=True, port=8000)