Initial commit

This commit is contained in:
Dennis Thiessen
2020-12-12 15:23:59 +01:00
commit 8e391a2597
6 changed files with 4879 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# IntelliJ project files
.idea
*.iml
out
gen
data
venv

2717
assets/base.css Normal file

File diff suppressed because one or more lines are too long

1118
assets/dash-logo-new.png Normal file

File diff suppressed because one or more lines are too long

633
assets/style.css Normal file
View File

@@ -0,0 +1,633 @@
/* Table of contents
Taken from https://codepen.io/chriddyp/pen/bWLwgP.css
- Grid
- Base Styles
- Typography
- Links
- Buttons
- Forms
- Lists
- Code
- Tables
- Spacing
- Utilities
- Clearing
- Media Queries
- Custom App CSS */
.VirtualizedSelectOption {
background-color: #1E1E1E;
color: #c3c3c3;
}
.VirtualizedSelectFocusedOption {
background-color: #5E5E5E;
color: #c3c3c3;
}
/* Grid */
.container {
position: relative;
width: 100%;
max-width: 960px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box;
}
.column, .columns {
width: 100%;
float: left;
box-sizing: border-box;
}
/* For devices larger than 400px */
@media (min-width: 400px) {
.container {
width: 85%;
padding: 0;
}
}
/* For devices larger than 550px */
@media (min-width: 550px) {
.container {
width: 80%;
}
.column, .columns {
margin-left: 4%;
}
.column:first-child, .columns:first-child {
margin-left: 0;
}
.one.column, .one.columns {
width: 4.66666666667%;
}
.two.columns {
width: 13.3333333333%;
}
.three.columns {
width: 22%;
}
.four.columns {
width: 30.6666666667%;
}
.five.columns {
width: 39.3333333333%;
}
.six.columns {
width: 48%;
}
.seven.columns {
width: 56.6666666667%;
}
.eight.columns {
width: 65.3333333333%;
}
.nine.columns {
width: 74.0%;
}
.ten.columns {
width: 82.6666666667%;
}
.eleven.columns {
width: 91.3333333333%;
}
.twelve.columns {
width: 100%;
margin-left: 0;
}
.one-third.column {
width: 30.6666666667%;
}
.two-thirds.column {
width: 65.3333333333%;
}
.one-half.column {
width: 48%;
}
/* Offsets */
.offset-by-one.column, .offset-by-one.columns {
margin-left: 8.66666666667%;
}
.offset-by-two.column, .offset-by-two.columns {
margin-left: 17.3333333333%;
}
.offset-by-three.column, .offset-by-three.columns {
margin-left: 26%;
}
.offset-by-four.column, .offset-by-four.columns {
margin-left: 34.6666666667%;
}
.offset-by-five.column, .offset-by-five.columns {
margin-left: 43.3333333333%;
}
.offset-by-six.column, .offset-by-six.columns {
margin-left: 52%;
}
.offset-by-seven.column, .offset-by-seven.columns {
margin-left: 60.6666666667%;
}
.offset-by-eight.column, .offset-by-eight.columns {
margin-left: 69.3333333333%;
}
.offset-by-nine.column, .offset-by-nine.columns {
margin-left: 78.0%;
}
.offset-by-ten.column, .offset-by-ten.columns {
margin-left: 86.6666666667%;
}
.offset-by-eleven.column, .offset-by-eleven.columns {
margin-left: 95.3333333333%;
}
.offset-by-one-third.column, .offset-by-one-third.columns {
margin-left: 34.6666666667%;
}
.offset-by-two-thirds.column, .offset-by-two-thirds.columns {
margin-left: 69.3333333333%;
}
.offset-by-one-half.column, .offset-by-one-half.columns {
margin-left: 52%;
}
}
/* Base Styles */
/* NOTE html is set to 62.5% so that all the REM measurements throughout Skeleton are based on 10px sizing. So basically 1.5rem = 15px :) */
html {
font-size: 62.5%;
font-family: Verdana;
}
body {
font-size: 1.5em;
/* currently ems cause chrome bug misinterpreting rems on body element */
line-height: 1.6;
font-weight: 400;
color: rgb(50, 50, 50);
}
/* Typography */
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0;
font-weight: 300;
}
h1 {
font-size: 4.5rem;
line-height: 1.2;
letter-spacing: -.1rem;
margin-bottom: 2rem;
}
h2 {
font-size: 3.6rem;
line-height: 1.25;
letter-spacing: -.1rem;
margin-bottom: 1.8rem;
margin-top: 1.8rem;
}
h3 {
font-size: 3.0rem;
line-height: 1.3;
letter-spacing: -.1rem;
margin-bottom: 1.5rem;
margin-top: 1.5rem;
}
h4 {
font-size: 2.6rem;
line-height: 1.35;
letter-spacing: -.08rem;
margin-bottom: 1.2rem;
margin-top: 1.2rem;
}
h5 {
font-size: 2.2rem;
line-height: 1.5;
letter-spacing: -.05rem;
margin-bottom: 0.6rem;
margin-top: 0.6rem;
}
h6 {
font-size: 2.0rem;
line-height: 1.6;
letter-spacing: 0;
margin-bottom: 0.75rem;
margin-top: 0.75rem;
}
p {
margin-top: 0;
}
/* Blockquotes */
blockquote {
border-left: 4px lightgrey solid;
padding-left: 1rem;
margin-top: 2rem;
margin-bottom: 2rem;
margin-left: 0rem;
}
/* Links */
a {
color: #1EAEDB;
text-decoration: underline;
cursor: pointer;
}
a:hover {
color: #0FA0CE;
}
/* Buttons */
.rc-slider-track{
background-color: #007eff
}
.rc-slider-rail{
background-color: #505050
}
.main-svg{
font-family: "Verdana"
}
.gtitle{
font-family: "Verdana" !important
}
.g-title{
font-family: "Verdana"
}
.button, button, input[type="submit"], input[type="reset"], input[type="button"] {
display: inline-block;
height: 38px;
padding: 0 30px;
color: #555;
text-align: center;
font-size: 11px;
font-weight: 600;
line-height: 38px;
letter-spacing: .1rem;
text-transform: uppercase;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border-radius: 4px;
border: 1px solid #bbb;
cursor: pointer;
box-sizing: border-box;
}
.button:hover, button:hover, input[type="submit"]:hover, input[type="reset"]:hover, input[type="button"]:hover, .button:focus, button:focus, input[type="submit"]:focus, input[type="reset"]:focus, input[type="button"]:focus {
color: #333;
border-color: #888;
outline: 0;
}
.button.button-primary, button.button-primary, input[type="submit"].button-primary, input[type="reset"].button-primary, input[type="button"].button-primary {
color: #FFF;
background-color: #33C3F0;
border-color: #33C3F0;
}
.button.button-primary:hover, button.button-primary:hover, input[type="submit"].button-primary:hover, input[type="reset"].button-primary:hover, input[type="button"].button-primary:hover, .button.button-primary:focus, button.button-primary:focus, input[type="submit"].button-primary:focus, input[type="reset"].button-primary:focus, input[type="button"].button-primary:focus {
color: #FFF;
background-color: #1EAEDB;
border-color: #1EAEDB;
}
/* Forms */
input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea, select {
height: 38px;
padding: 6px 10px;
/* The 6px vertically centers text on FF, ignored by Webkit */
background-color: #fff;
border: 1px solid #D1D1D1;
border-radius: 4px;
box-shadow: none;
box-sizing: border-box;
font-family: inherit;
font-size: inherit;
/*https://stackoverflow.com/questions/6080413/why-doesnt-input-inherit-the-font-from-body*/
}
/* Removes awkward default styles on some inputs for iOS */
input[type="email"], input[type="number"], input[type="search"], input[type="text"], input[type="tel"], input[type="url"], input[type="password"], textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
textarea {
min-height: 65px;
padding-top: 6px;
padding-bottom: 6px;
}
input[type="email"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="text"]:focus, input[type="tel"]:focus, input[type="url"]:focus, input[type="password"]:focus, textarea:focus, select:focus {
border: 1px solid #33C3F0;
outline: 0;
}
label, legend {
display: block;
margin-bottom: 0px;
}
fieldset {
padding: 0;
border-width: 0;
}
input[type="checkbox"], input[type="radio"] {
display: inline;
}
label > .label-body {
display: inline-block;
margin-left: .5rem;
font-weight: normal;
}
/* Lists */
ul {
list-style: circle inside;
}
ol {
list-style: decimal inside;
}
ol, ul {
padding-left: 0;
margin-top: 0;
}
ul ul, ul ol, ol ol, ol ul {
margin: 1.5rem 0 1.5rem 3rem;
font-size: 90%;
}
li {
margin-bottom: 1rem;
}
/* Tables */
table {
border-collapse: collapse;
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #E1E1E1;
}
th:first-child, td:first-child {
padding-left: 0;
}
th:last-child, td:last-child {
padding-right: 0;
}
/* Spacing */
button, .button {
margin-bottom: 0rem;
}
input, textarea, select, fieldset {
margin-bottom: 0rem;
}
pre, dl, figure, table, form {
margin-bottom: 0rem;
}
p, ul, ol {
margin-bottom: 0.75rem;
}
/* Utilities */
.u-full-width {
width: 100%;
box-sizing: border-box;
}
.u-max-full-width {
max-width: 100%;
box-sizing: border-box;
}
.u-pull-right {
float: right;
}
.u-pull-left {
float: left;
}
/* Misc */
hr {
margin-top: 3rem;
margin-bottom: 3.5rem;
border-width: 0;
border-top: 1px solid #E1E1E1;
}
/* Clearing */
/* Self Clearing Goodness */
.container:after, .row:after, .u-cf {
content: "";
display: table;
clear: both;
}
/* Media Queries */
/* Note: The best way to structure the use of media queries is to create the queries near the relevant code. For example, if you wanted to change the styles for buttons on small devices, paste the mobile query code up in the buttons section and style it there. */
/* Larger than mobile */
@media (min-width: 400px) {
}
/* Larger than phablet (also point when grid becomes active) */
@media (min-width: 550px) {
}
/* Larger than tablet */
@media (min-width: 750px) {
}
/* Larger than desktop */
@media (min-width: 1000px) {
}
/* Larger than Desktop HD */
@media (min-width: 1200px) {
}
/* Custom App CSS Below --------------------------------- */
/* Main Layout */
html, body{
height: 100%;
}
body {
background-color: #1E1E1E;
color: #d8d8d8;
height: 100%;
margin: 0;
padding: 0;
}
.react-entry-point{
height: 100%;
}
h1, h2, h3, h4, h5 {
font-family: "Open Sans Semi Bold";
letter-spacing: 2.1px;
font-size: 21px;
padding-left: 12px;
}
p {
font-family: "Open Sans Light";
font-weight: 400;
font-size: 14px;
padding-left: 12px;
}
a {
text-decoration: none;
}
.bg-grey{
background-color: #31302F;
}
.text-padding{
padding: 5px;
}
/* Graph Layout */
.div-for-charts{
display: flex;
flex-direction: column;
height: 100vh;
width: 100%;
}
#histogram {
flex-grow: 1
}
#map-graph {
flex-grow: 2
}
.mapboxgl-canvas, .mapboxgl-map {
min-width: 100%;
}
/* Graph Control Objects */
.div-user-controls {
padding-left: 55px;
padding-top: 64px;
}
.div-for-dropdown {
padding-top: 12px;
padding-bottom: 12px;
}
.div-for-slider {
width: 97%;
text-align: center;
}
.logo {
height: 50px;
padding-bottom: 12px;
}
.Select-control, .Select-menu-outer, .Select-multi-value-wrapper, .select-up, .is-open .Select-control {
background-color: #1E1E1E;
color: white;
}
#modelselector .Select-control{
max-height: 82px;
overflow-y: auto;
margin: 0px 0px -3px 0px;
border: 0.5px solid #dbdbdb44;
}
.Select-control{
border: 0.5px solid #dbdbdb44;
}
.has-value.Select--single>.Select-control .Select-value .Select-value-label, .has-value.is-pseudo-focused.Select--single>.Select-control .Select-value .Select-value-label {
color: white;
background: #1E1E1E;
background-color: #1E1E1E;
}
._dash-app-content {
overflow-x: hidden;
overflow-y: hidden;
position: relative;
}
.has-value.Select--single>.Select-control .Select-value .Select-value-label, .has-value.is-pseudo-focused.Select--single>.Select-control .Select-value .Select-value-labeln {
color: #d8d8d8;
font-weight: 400;
font-size: 14px;
}
.Input.DateInput_1 {
background-color: #1e1e1e;
}
#date {
background-color: #1e1e1e;
background-color: #1e1e1e;
font-family: "Open Sans Light";
font-weight: 400;
color: #dbdbdb;
border: 0.5px solid #dbdbdb44;
}
#date-picker{
width: 100%;
}
._dash-undo-redo {
display: none;
}
.DateInput.DateInput_1{
width: 100%;
}
.SingleDatePickerInput__withBorder{
border:none;
}
.DateInput.DateInput_1 {
background-color: #1e1e1e;
}
.SingleDatePickerInput__withBorder {
border-radius: 3px;
color: #dbdbdb;
background: #1e1e1e;
background-color: #1e1e1e;
}
.SingleDatePickerInput.SingleDatePickerInput_1.SingleDatePickerInput__withBorder.SingleDatePickerInput__withBorder_2 {
border: 0.0px solid #dbdbdb;
display: block;
}
.SingleDatePicker_1 {
border: 0.0px solid #dbdbdb;
display: block;
}
.SingleDatePicker_picker, .SingleDatePicker_picker_1, .SingleDatePicker_picker__directionLeft,
.SingleDatePicker_picker__directionLeft_2{
filter:invert(100%);
}
/* For Mobile Phones and small screens */
@media only screen and (max-width: 768px) {
.four, .eight {
min-width: 100%;
}
h1, h2, h3, p {
text-align: center;
}
body {
display: block;
margin: 0px;
overflow-y: scroll;
}
.div-for-charts {
padding: 0px;
width: 100%;
text-align: center;
}
.div-user-controls {
padding: 32px;
}
.side-by-side{
display: inline-block;
width: 48%;
}
.side-by-side-right{
display: inline-block;
width: 48%;
float:right;
}
.div-for-charts{
margin: 0px;
}
}
/* width */
::-webkit-scrollbar {
width: 10px !important;
display: block !important;
}
/* Track */
::-webkit-scrollbar-track {
background: #1e1e1e !important;
border-radius: 10px !important;
display: block !important;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: transparent;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #d8d8d870 !important;
}

115
stockdash_main.py Normal file
View File

@@ -0,0 +1,115 @@
import os
import pandas as pd
import json
from datetime import datetime
import time
import stockdash_render
import yfinance as yf
API_KEY = '44ced5e44c50543745b1d89fce8cd93a'
api_key = "?apikey=" + API_KEY
api_kpi_url = "https://financialmodelingprep.com/api/v3/key-metrics/"
api_batch_stock_price_url = "https://financialmodelingprep.com/api/v3/quote/"
data_dir = 'data'
file_symbol = os.path.join(data_dir, 'symbols.json')
file_kpi = os.path.join(data_dir, 'comp_kpi.csv')
file_div = os.path.join(data_dir, 'comp_div.csv')
file_rec = os.path.join(data_dir, 'comp_rec.csv')
file_his = os.path.join(data_dir, 'comp_his.csv')
sym_exclude = []
used_columns = ['symbol', 'shortName', 'sector', 'industry', 'country', 'marketCap', 'enterpriseValue', 'dividendRate',
'trailingPE', 'forwardPE', 'enterpriseToEbitda', 'shortRatio']
def load_symbols():
symbols = []
with open(file_symbol) as json_file:
data = json.load(json_file)
for sym in data:
if 'exchange' in sym and sym['exchange'] == 'Nasdaq Global Select' and sym['symbol'] not in sym_exclude:
symbols.append(sym['symbol'])
return symbols
def get_data(symbols):
tickers = yf.Tickers(' '.join(symbols))
if os.path.exists(file_kpi) and os.path.exists(file_div) and os.path.exists(file_rec) and os.path.exists(file_his):
print("Found cached files. Loading cache...")
kpi_data = pd.read_csv(file_kpi)
print("%s KPIs loaded..." % len(kpi_data))
div_data = pd.read_csv(file_div)
print("%s Dividends loaded..." % len(div_data))
rec_data = pd.read_csv(file_rec)
print("%s Recommendations loaded..." % len(rec_data))
his_data = pd.read_csv(file_his)
print("%s Price History loaded..." % len(his_data))
else:
kpi_data, div_data, rec_data, his_data = pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), pd.DataFrame()
i = 1
for ticker in tickers.tickers:
try:
info = ticker.info
print("%s/%s Querying data for ticker %s" % (i, len(symbols), info['symbol']))
kpi_data = kpi_data.append(info, ignore_index=True)
div = ticker.dividends.to_frame().reset_index()
div.insert(0, 'Symbol', info['symbol'])
div_data = div_data.append(div, ignore_index=True)
rec = ticker.recommendations.reset_index()
rec.insert(0, 'Symbol', info['symbol'])
rec_data = rec_data.append(rec, ignore_index=True)
his = ticker.history(period='5y').reset_index()
his.insert(0, 'Symbol', info['symbol'])
his_data = his_data.append(his, ignore_index=True)
i += 1
except Exception:
print("Error occured when quering %s - skipping this entry")
continue
kpi_data = kpi_data[['symbol'] + [col for col in kpi_data.columns if col != 'symbol']]
rec_data['Date'] = pd.to_datetime(rec_data['Date'])
his_data['priceMA50'] = his_data['Close'].rolling(window=50).mean()
his_data['priceMA200'] = his_data['Close'].rolling(window=200).mean()
his_data['diffMA50_200'] = his_data['priceMA50'] - his_data['priceMA200']
kpi_data.to_csv(file_kpi)
div_data.to_csv(file_div)
rec_data.to_csv(file_rec)
his_data.to_csv(file_his)
return kpi_data, div_data, rec_data, his_data
if __name__ == '__main__':
start_time = time.time()
print("----- Starting STOCKDASH @ %s -----" % datetime.fromtimestamp(start_time))
used_symbols = load_symbols()
print("%s symbols loaded from file" % len(used_symbols))
kpi_data, div_data, rec_data, his_data = get_data(used_symbols[:100])
# Modify Recommendation Data
rec_data_mod = pd.concat([rec_data, pd.get_dummies(rec_data['To Grade'], prefix='grade')], axis=1)
rec_data_mod.drop(['To Grade', 'From Grade', 'Action'], axis=1, inplace=True)
rec_data_mod['Date'] = pd.to_datetime(rec_data_mod['Date'])
df2 = rec_data_mod.groupby([pd.Grouper(key='Date', freq='Y'), pd.Grouper('Symbol')]).agg(['sum'])
df2['Positive'] = df2['grade_Buy'] + df2['grade_Outperform'] + df2['grade_Market Outperform'] + df2[
'grade_Overweight'] + df2['grade_Positive'] + df2['grade_Strong Buy']
df2['Neutral'] = df2['grade_Equal-Weight'] + df2['grade_Hold'] + df2['grade_Neutral']
df2['Negative'] = df2['grade_Market Underperform'] + df2['grade_Reduce'] + df2['grade_Sell'] + df2[
'grade_Underweight']
columns = ['Positive', 'Neutral', 'Negative']
rec_data_mod = df2[columns]
print("Data loaded after %ss" % (time.time()-start_time))
stockdash_render.load_dash(kpi_data[used_columns], rec_data_mod, div_data, his_data)

288
stockdash_render.py Normal file
View File

@@ -0,0 +1,288 @@
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)