289 lines
13 KiB
Python
289 lines
13 KiB
Python
from datetime import datetime
|
|
import time
|
|
|
|
import dash_table
|
|
import plotly.graph_objects as go
|
|
import dash_core_components as dcc
|
|
import dash_html_components as html
|
|
from dash import Dash
|
|
from dash_table.Format import Format
|
|
from dash.dependencies import Input, Output
|
|
|
|
colorway = ["#9a58cc", '#FF4F00', '#375CB1', '#FF7400', '#FFF400', '#FF0056']
|
|
PAGE_SIZE = 20
|
|
|
|
|
|
def load_dash(comp_kpi, rec_data_mod, div_data, his_data):
|
|
start_time = time.time()
|
|
print(" -- Rendering STOCKDASH @ %s -----" % datetime.fromtimestamp(start_time))
|
|
|
|
app = Dash(__name__)
|
|
|
|
app.layout = html.Div(children=[
|
|
html.Div(className='row',
|
|
children=[html.Div(className='three columns div-user-controls',
|
|
children=[
|
|
html.H2('STOCKDASH'),
|
|
html.P('''Visualising data with Plotly - Dash'''),
|
|
html.P('''Pick one or more KPIs from the dropdown below.'''),
|
|
html.Div(
|
|
className='div-for-dropdown',
|
|
children=[
|
|
dcc.Dropdown(id='stockselector',
|
|
options=[{'label': i, 'value': i} for i in
|
|
comp_kpi._get_numeric_data().columns],
|
|
multi=True,
|
|
value=[comp_kpi._get_numeric_data().columns[0]],
|
|
style={'backgroundColor': '#1E1E1E'},
|
|
className='stockselector')
|
|
],
|
|
style={'color': '#1E1E1E'})
|
|
]),
|
|
html.Div(className='nine columns div-for-charts bg-grey',
|
|
style={'padding': 0},
|
|
children=[
|
|
dash_table.DataTable(
|
|
id='company-kpi-data',
|
|
columns=
|
|
[{"name": i, "id": i, 'deletable': True, 'type': 'numeric',
|
|
'format': Format(group=',')} if i in comp_kpi._get_numeric_data().columns
|
|
else {"name": i, "id": i, 'deletable': True} for i in comp_kpi.columns],
|
|
style_as_list_view=True,
|
|
style_data_conditional=[{
|
|
'if': {'column_editable': False},
|
|
'backgroundColor': 'rgba(50, 50, 50, 0.5)',
|
|
'textAlign': 'left',
|
|
'color': 'white',
|
|
'padding': 7
|
|
}],
|
|
style_filter_conditional=[{
|
|
'if': {'column_editable': False},
|
|
'backgroundColor': 'rgba(40, 40, 40,0.5)',
|
|
'textAlign': 'left',
|
|
'color': 'white'
|
|
}],
|
|
style_header_conditional=[{
|
|
'if': {'column_editable': False},
|
|
'backgroundColor': 'rgba(30, 30, 30,0.5)',
|
|
'textAlign': 'left',
|
|
'fontWeight': 'bold',
|
|
'color': 'white'
|
|
}],
|
|
page_current=0,
|
|
page_size=PAGE_SIZE,
|
|
page_action='custom',
|
|
|
|
filter_action='custom',
|
|
filter_query='',
|
|
|
|
sort_action='custom',
|
|
sort_mode='multi',
|
|
sort_by=[]
|
|
),
|
|
dcc.Graph(
|
|
id='bar-chart-marketcap',
|
|
className='bg-grey',
|
|
hoverData={'points': [{'x': 'AAPL'}]},
|
|
animate=True),
|
|
dcc.Graph(
|
|
id='timeseries-chart-price',
|
|
className='bg-grey',
|
|
config={'displayModeBar': False},
|
|
animate=False),
|
|
dcc.Graph(
|
|
id='recom-bar-chart',
|
|
className='bg-grey',
|
|
config={'displayModeBar': False},
|
|
animate=True)
|
|
])
|
|
])
|
|
])
|
|
|
|
@app.callback(Output('bar-chart-marketcap', 'figure'),
|
|
[Input('company-kpi-data', 'data'),
|
|
Input('stockselector', 'value')])
|
|
def update_graph(data, selected_columns):
|
|
used_symbols = [x['symbol'] for x in data]
|
|
|
|
figure = go.Figure(
|
|
layout=go.Layout(
|
|
colorway=colorway,
|
|
template='plotly_dark',
|
|
paper_bgcolor='rgba(0, 0, 0, 0)',
|
|
plot_bgcolor='rgba(0, 0, 0, 0)',
|
|
margin={'b': 15},
|
|
hovermode='x',
|
|
autosize=True,
|
|
title={'text': 'Market Data', 'font': {'color': 'white'}, 'x': 0.5}
|
|
))
|
|
|
|
val = dict()
|
|
val["xaxis"] = dict(domain=[0.15, 0.85])
|
|
for i, column in enumerate(selected_columns):
|
|
i += 1
|
|
figure.add_trace(go.Bar(name=column,
|
|
x=used_symbols,
|
|
y=[x[column] for x in data],
|
|
yaxis='y' + str(i), offsetgroup=i))
|
|
|
|
val["yaxis%s" % i] = dict(
|
|
title=column,
|
|
titlefont=dict(color=colorway[i - 1]),
|
|
tickfont=dict(color=colorway[i - 1]),
|
|
)
|
|
|
|
if i == 2:
|
|
val["yaxis2"].update(dict(
|
|
anchor="x",
|
|
overlaying="y",
|
|
side="right"
|
|
))
|
|
elif i == 3:
|
|
val["yaxis3"].update(dict(
|
|
anchor="free",
|
|
overlaying="y",
|
|
side="left",
|
|
position=0.05
|
|
))
|
|
elif i == 4:
|
|
val["yaxis4"].update(dict(
|
|
anchor="free",
|
|
overlaying="y",
|
|
side="right",
|
|
position=0.95
|
|
))
|
|
|
|
figure.update_layout(val)
|
|
figure.update_yaxes(
|
|
showgrid=True, zeroline=True, zerolinewidth=1, zerolinecolor='White',
|
|
)
|
|
|
|
return figure
|
|
|
|
@app.callback(Output('recom-bar-chart', 'figure'),
|
|
[Input('company-kpi-data', 'data')])
|
|
def update_graph(data):
|
|
used_symbols = [x['symbol'] for x in data]
|
|
|
|
df = rec_data_mod.loc['2020-12-31'].reset_index()
|
|
df_tmp = df.loc[df['Symbol'].isin(used_symbols)]
|
|
|
|
figure = go.Figure(layout=go.Layout(
|
|
colorway=colorway,
|
|
template='plotly_dark',
|
|
paper_bgcolor='rgba(0, 0, 0, 0)',
|
|
plot_bgcolor='rgba(0, 0, 0, 0)',
|
|
margin={'b': 15},
|
|
hovermode='x',
|
|
autosize=True,
|
|
title={'text': 'Recommendation Data', 'font': {'color': 'white'}, 'x': 0.5},
|
|
barmode='stack'
|
|
))
|
|
|
|
figure.add_trace(go.Bar(x=used_symbols, y=df_tmp['Positive'].tolist(), name='Positive Outlook', marker_color='#41B3A3'))
|
|
figure.add_trace(go.Bar(x=used_symbols, y=df_tmp['Neutral'].tolist(), name='Neutral Outlook', marker_color='#E8A87C'))
|
|
figure.add_trace(go.Bar(x=used_symbols, y=df_tmp['Negative'].tolist(), name='Negative Outlook', marker_color='#E27D60'))
|
|
|
|
return figure
|
|
|
|
@app.callback(Output('timeseries-chart-price', 'figure'),
|
|
[Input('bar-chart-marketcap', 'hoverData')])
|
|
def update_graph(hoverData):
|
|
trace1 = []
|
|
columns = ['Close', 'priceMA50', 'priceMA200']
|
|
df_sub = his_data[his_data['Symbol'] == hoverData['points'][0]['x']]
|
|
|
|
for column in columns:
|
|
trace1.append(go.Scatter(x=df_sub['Date'],
|
|
y=df_sub[column],
|
|
mode='lines',
|
|
opacity=0.7,
|
|
name=hoverData['points'][0]['x'] + "-" + column,
|
|
textposition='bottom center'))
|
|
|
|
traces = [trace1]
|
|
data = [val for sublist in traces for val in sublist]
|
|
figure = {'data': data,
|
|
'layout': go.Layout(
|
|
colorway=colorway,
|
|
template='plotly_dark',
|
|
paper_bgcolor='rgba(0, 0, 0, 0)',
|
|
plot_bgcolor='rgba(0, 0, 0, 0)',
|
|
margin={'b': 15},
|
|
hovermode='x',
|
|
autosize=True,
|
|
|
|
title={'text': 'Stock Prices', 'font': {'color': 'white'}, 'x': 0.5},
|
|
xaxis={'range': [df_sub['Date'].min(), df_sub['Date'].max()]},
|
|
yaxis={'range': [0, df_sub['Close'].max() + df_sub['Close'].max() / 10]}
|
|
),
|
|
}
|
|
|
|
return figure
|
|
|
|
def split_filter_part(filter_part):
|
|
operators = [['ge ', '>='],
|
|
['le ', '<='],
|
|
['lt ', '<'],
|
|
['gt ', '>'],
|
|
['ne ', '!='],
|
|
['eq ', '='],
|
|
['contains '],
|
|
['datestartswith ']]
|
|
|
|
for operator_type in operators:
|
|
for operator in operator_type:
|
|
if operator in filter_part:
|
|
name_part, value_part = filter_part.split(operator, 1)
|
|
name = name_part[name_part.find('{') + 1: name_part.rfind('}')]
|
|
|
|
value_part = value_part.strip()
|
|
v0 = value_part[0]
|
|
|
|
if v0 == value_part[-1] and v0 in ("'", '"', '`'):
|
|
value = value_part[1: -1].replace('\\' + v0, v0)
|
|
else:
|
|
try:
|
|
value = float(value_part)
|
|
except ValueError:
|
|
value = value_part
|
|
|
|
return name, operator_type[0].strip(), value
|
|
return [None] * 3
|
|
|
|
@app.callback(
|
|
Output('company-kpi-data', "data"),
|
|
Input('company-kpi-data', "page_current"),
|
|
Input('company-kpi-data', "page_size"),
|
|
Input('company-kpi-data', "sort_by"),
|
|
Input('company-kpi-data', 'filter_query'))
|
|
def update_table(page_current, page_size, sort_by, filter):
|
|
filtering_expressions = filter.split(' && ')
|
|
dff = comp_kpi
|
|
for filter_part in filtering_expressions:
|
|
col_name, operator, filter_value = split_filter_part(filter_part)
|
|
|
|
if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
|
|
dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
|
|
elif operator == 'contains':
|
|
dff = dff.loc[dff[col_name].str.contains(filter_value)]
|
|
elif operator == 'datestartswith':
|
|
dff = dff.loc[dff[col_name].str.startswith(filter_value)]
|
|
|
|
if len(sort_by):
|
|
dff = dff.sort_values(
|
|
[col['column_id'] for col in sort_by],
|
|
ascending=[
|
|
col['direction'] == 'asc'
|
|
for col in sort_by
|
|
],
|
|
inplace=False
|
|
)
|
|
|
|
page = page_current
|
|
size = page_size
|
|
return dff.iloc[page * size: (page + 1) * size].to_dict('records')
|
|
|
|
print("Rendering loaded after %ss" % (time.time()-start_time))
|
|
app.run_server(debug=True)
|