jonathan-cristovao
commited on
Commit
•
ef94dec
1
Parent(s):
b2ed964
Upload 10 files
Browse files- .gitattributes +35 -35
- .vscode/settings.json +9 -0
- README.md +13 -13
- app.py +14 -0
- model.py +52 -0
- model_page.py +49 -0
- plots.py +53 -0
- requirements.txt +9 -0
- stock_data_loader.py +19 -0
- view_page.py +50 -0
.gitattributes
CHANGED
@@ -1,35 +1,35 @@
|
|
1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.vscode/settings.json
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"python.linting.enabled": true,
|
3 |
+
"python.linting.pylintEnabled": true,
|
4 |
+
"files.exclude": {
|
5 |
+
"**/*.pyc": {"when": "$(basename).py"},
|
6 |
+
"**/__pycache__": true,
|
7 |
+
"**/*.pytest_cache": true,
|
8 |
+
}
|
9 |
+
}
|
README.md
CHANGED
@@ -1,13 +1,13 @@
|
|
1 |
-
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
-
sdk: streamlit
|
7 |
-
sdk_version: 1.36.0
|
8 |
-
app_file: app.py
|
9 |
-
pinned: false
|
10 |
-
license: mit
|
11 |
-
---
|
12 |
-
|
13 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
+
---
|
2 |
+
title: Stock Predict Lstm
|
3 |
+
emoji: 👁
|
4 |
+
colorFrom: blue
|
5 |
+
colorTo: gray
|
6 |
+
sdk: streamlit
|
7 |
+
sdk_version: 1.36.0
|
8 |
+
app_file: app.py
|
9 |
+
pinned: false
|
10 |
+
license: mit
|
11 |
+
---
|
12 |
+
|
13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from view_page import StockDashboard
|
3 |
+
from model_page import StockModelPage
|
4 |
+
|
5 |
+
def main():
|
6 |
+
st.set_page_config(layout='wide', page_title='Stock Analysis', page_icon=':dollar:')
|
7 |
+
page = st.sidebar.radio('Pages', ['View Page', 'Model Page'])
|
8 |
+
if page == 'View Page':
|
9 |
+
StockDashboard().run()
|
10 |
+
elif page == 'Model Page':
|
11 |
+
StockModelPage().run()
|
12 |
+
|
13 |
+
if __name__ == '__main__':
|
14 |
+
main()
|
model.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import numpy as np
|
3 |
+
from sklearn.preprocessing import MinMaxScaler
|
4 |
+
from keras.models import Sequential
|
5 |
+
from keras.layers import LSTM, Dense
|
6 |
+
import warnings
|
7 |
+
warnings.filterwarnings("ignore")
|
8 |
+
|
9 |
+
class Model:
|
10 |
+
def __init__(self, data):
|
11 |
+
self.data = data
|
12 |
+
self.scaler = MinMaxScaler(feature_range=(0, 1))
|
13 |
+
self.model = None
|
14 |
+
|
15 |
+
def prepare_data(self, look_back=1):
|
16 |
+
scaled_data = self.scaler.fit_transform(self.data['Close'].values.reshape(-1, 1))
|
17 |
+
def create_dataset(dataset):
|
18 |
+
X, Y = [], []
|
19 |
+
for i in range(len(dataset) - look_back):
|
20 |
+
a = dataset[i:(i + look_back), 0]
|
21 |
+
X.append(a)
|
22 |
+
Y.append(dataset[i + look_back, 0])
|
23 |
+
return np.array(X), np.array(Y)
|
24 |
+
|
25 |
+
X, Y = create_dataset(scaled_data)
|
26 |
+
X = np.reshape(X, (X.shape[0], 1, X.shape[1]))
|
27 |
+
return X, Y
|
28 |
+
|
29 |
+
def train_lstm(self, epochs=5, batch_size=1):
|
30 |
+
X, Y = self.prepare_data()
|
31 |
+
self.model = Sequential()
|
32 |
+
self.model.add(LSTM(50, input_shape=(1, 1)))
|
33 |
+
self.model.add(Dense(1))
|
34 |
+
self.model.compile(loss='mean_squared_error', optimizer='adam')
|
35 |
+
self.model.fit(X, Y, epochs=epochs, batch_size=batch_size, verbose=0)
|
36 |
+
|
37 |
+
def make_predictions(self):
|
38 |
+
X, _ = self.prepare_data()
|
39 |
+
predictions = self.model.predict(X)
|
40 |
+
predictions = self.scaler.inverse_transform(predictions)
|
41 |
+
return predictions
|
42 |
+
|
43 |
+
def forecast_future(self, days=5):
|
44 |
+
last_value = self.data['Close'].values[-1:].reshape(-1, 1)
|
45 |
+
last_scaled = self.scaler.transform(last_value)
|
46 |
+
future_predictions = []
|
47 |
+
for _ in range(days):
|
48 |
+
prediction = self.model.predict(last_scaled.reshape(1, 1, 1))[0]
|
49 |
+
future_predictions.append(prediction)
|
50 |
+
last_scaled = prediction
|
51 |
+
future_predictions = self.scaler.inverse_transform(future_predictions)
|
52 |
+
return future_predictions
|
model_page.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import streamlit as st
|
3 |
+
from model import Model
|
4 |
+
from plots import Plots
|
5 |
+
from stock_data_loader import StockDataLoader
|
6 |
+
|
7 |
+
class StockModelPage:
|
8 |
+
def __init__(self):
|
9 |
+
self.tickers = ['NVDA', 'AAPL', 'GOOGL', 'MSFT', 'AMZN']
|
10 |
+
self.setup_sidebar()
|
11 |
+
|
12 |
+
def setup_sidebar(self):
|
13 |
+
self.ticker = st.sidebar.selectbox('Choose Stock Ticker', self.tickers)
|
14 |
+
self.start_date = st.sidebar.date_input('Start Date', value=pd.to_datetime('2010-01-01'))
|
15 |
+
self.end_date = st.sidebar.date_input('End Date', value=pd.to_datetime('today'))
|
16 |
+
self.load_button_clicked = st.sidebar.button('Load Data')
|
17 |
+
|
18 |
+
def load_data(self):
|
19 |
+
if self.load_button_clicked:
|
20 |
+
loader = StockDataLoader(self.ticker, self.start_date, self.end_date)
|
21 |
+
st.session_state['stock_data'] = loader.get_stock_data()
|
22 |
+
st.write("--------------------------------------------")
|
23 |
+
st.write(f"Data for {self.ticker} from {self.start_date} to {self.end_date} loaded successfully!")
|
24 |
+
|
25 |
+
def handle_model_training(self):
|
26 |
+
if 'stock_data' in st.session_state:
|
27 |
+
stock_data = st.session_state['stock_data']
|
28 |
+
if st.button('Train Model'):
|
29 |
+
st.write("Training Model...")
|
30 |
+
model = Model(stock_data)
|
31 |
+
model.train_lstm()
|
32 |
+
predictions = model.make_predictions()
|
33 |
+
future_predictions = model.forecast_future(days=5)
|
34 |
+
self.plot_predictions(stock_data, predictions, future_predictions)
|
35 |
+
else:
|
36 |
+
st.write("Click the button above to train the model.")
|
37 |
+
else:
|
38 |
+
st.write("--------------------------------------------")
|
39 |
+
st.write("Please load data before training the model.")
|
40 |
+
|
41 |
+
def plot_predictions(self, stock_data, predictions, future_predictions):
|
42 |
+
plot_instance = Plots(stock_data)
|
43 |
+
plot_instance.plot_predictions(predictions, future_predictions)
|
44 |
+
|
45 |
+
def run(self):
|
46 |
+
st.write("--------------------------------------------")
|
47 |
+
st.write(f'<div style="font-size:50px">🤖 Real-Time Stock Prediction', unsafe_allow_html=True)
|
48 |
+
self.load_data()
|
49 |
+
self.handle_model_training()
|
plots.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import streamlit as st
|
3 |
+
import plotly.graph_objects as go
|
4 |
+
from plotly.subplots import make_subplots
|
5 |
+
|
6 |
+
|
7 |
+
class StockChart:
|
8 |
+
def __init__(self, data):
|
9 |
+
self.data = data
|
10 |
+
self.fig = make_subplots(rows=2, cols=1, vertical_spacing=0.01, shared_xaxes=True)
|
11 |
+
|
12 |
+
def add_price_chart(self):
|
13 |
+
self.fig.add_trace(go.Scatter(x=self.data.index, y=self.data['Open'], name='Open Price', marker_color='#1F77B4'), row=1, col=1)
|
14 |
+
self.fig.add_trace(go.Scatter(x=self.data.index, y=self.data['High'], name='High Price', marker_color='#9467BD'), row=1, col=1)
|
15 |
+
self.fig.add_trace(go.Scatter(x=self.data.index, y=self.data['Low'], name='Low Price', marker_color='#D62728'), row=1, col=1)
|
16 |
+
self.fig.add_trace(go.Scatter(x=self.data.index, y=self.data['Close'], name='Close Price', marker_color='#76B900'), row=1, col=1)
|
17 |
+
|
18 |
+
|
19 |
+
def add_oversold_overbought_lines(self):
|
20 |
+
self.fig.add_hline(y=30, line_dash='dash', line_color='limegreen', line_width=1, row=1, col=1)
|
21 |
+
self.fig.add_hline(y=70, line_dash='dash', line_color='red', line_width=1, row=1, col=1)
|
22 |
+
self.fig.update_yaxes(title_text='RSI Score', row=1, col=1)
|
23 |
+
|
24 |
+
def add_volume_chart(self):
|
25 |
+
colors = ['#9C1F0B' if row['Open'] - row['Close'] >= 0 else '#2B8308' for index, row in self.data.iterrows()]
|
26 |
+
self.fig.add_trace(go.Bar(x=self.data.index, y=self.data['Volume'], showlegend=False, marker_color=colors), row=2, col=1)
|
27 |
+
|
28 |
+
def render_chart(self):
|
29 |
+
self.fig.update_layout(title='Historical Price and Volume', height=500, margin=dict(l=0, r=10, b=10, t=25))
|
30 |
+
st.plotly_chart(self.fig, use_container_width=True)
|
31 |
+
|
32 |
+
class Plots:
|
33 |
+
def __init__(self, data):
|
34 |
+
self.data = data
|
35 |
+
|
36 |
+
def plot_predictions(self, predictions, future_predictions):
|
37 |
+
|
38 |
+
predicted_dates = self.data.index[-len(predictions):]
|
39 |
+
future_dates = pd.date_range(start=self.data.index[-1] + pd.Timedelta(days=1), periods=len(future_predictions), freq='B')
|
40 |
+
predictions = [float(val) for val in predictions if pd.notna(val)]
|
41 |
+
future_predictions = [float(val) for val in future_predictions if pd.notna(val)]
|
42 |
+
|
43 |
+
fig = make_subplots(rows=1, cols=1)
|
44 |
+
fig.add_trace(go.Scatter(x=self.data.index, y=self.data['Close'], mode='lines', name='Actual Stock Prices', marker_color='blue'))
|
45 |
+
fig.add_trace(go.Scatter(x=predicted_dates, y=predictions, mode='lines', name='LSTM Predicted Prices', marker_color='red', line=dict(dash='dash')))
|
46 |
+
fig.add_trace(go.Scatter(x=future_dates, y=future_predictions, mode='lines', name='Future Predictions', marker_color='green', line=dict(dash='dot')))
|
47 |
+
|
48 |
+
fig.update_layout(title='Comparison of Actual, Predicted, and Future Stock Prices', xaxis_title='Date', yaxis_title='Price', legend_title='Legend', height=500)
|
49 |
+
st.plotly_chart(fig, use_container_width=True)
|
50 |
+
|
51 |
+
|
52 |
+
|
53 |
+
|
requirements.txt
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
numpy
|
2 |
+
pandas
|
3 |
+
seaborn
|
4 |
+
matplotlib
|
5 |
+
keras
|
6 |
+
tensorflow
|
7 |
+
scikit-learn
|
8 |
+
yfinance
|
9 |
+
plotly
|
stock_data_loader.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import yfinance as yf
|
3 |
+
|
4 |
+
import warnings
|
5 |
+
warnings.filterwarnings("ignore")
|
6 |
+
|
7 |
+
class StockDataLoader:
|
8 |
+
def __init__(self, ticker, start_date, end_date):
|
9 |
+
self.ticker = ticker
|
10 |
+
self.start_date = start_date
|
11 |
+
self.end_date = end_date
|
12 |
+
|
13 |
+
def get_stock_data(self):
|
14 |
+
stock = yf.Ticker(self.ticker)
|
15 |
+
stock_data = stock.history(start=self.start_date, end=self.end_date)
|
16 |
+
stock_data.reset_index(inplace=True)
|
17 |
+
stock_data['Date'] = pd.to_datetime(stock_data['Date'])
|
18 |
+
stock_data.set_index('Date', inplace=True)
|
19 |
+
return stock_data
|
view_page.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from stock_data_loader import StockDataLoader
|
2 |
+
import streamlit as st
|
3 |
+
import pandas as pd
|
4 |
+
import yfinance as yf
|
5 |
+
from datetime import datetime
|
6 |
+
from plots import Plots, StockChart
|
7 |
+
|
8 |
+
class StockDashboard:
|
9 |
+
def __init__(self):
|
10 |
+
self.tickers = ['NVDA', 'AAPL', 'GOOGL', 'MSFT', 'AMZN']
|
11 |
+
self.period_map = {'all': 'max','1m': '1mo', '6m': '6mo', '1y': '1y'}
|
12 |
+
|
13 |
+
def render_sidebar(self):
|
14 |
+
st.sidebar.header("Choose your filter:")
|
15 |
+
self.ticker = st.sidebar.selectbox('Choose Ticker', options=self.tickers, help='Select a ticker')
|
16 |
+
self.selected_range = st.sidebar.selectbox('Select Period', options=list(self.period_map.keys()))
|
17 |
+
|
18 |
+
def load_data(self):
|
19 |
+
self.yf_data = yf.Ticker(self.ticker)
|
20 |
+
self.df_history = self.yf_data.history(period=self.period_map[self.selected_range])
|
21 |
+
self.current_price = self.yf_data.info.get('currentPrice', 'N/A')
|
22 |
+
self.previous_close = self.yf_data.info.get('previousClose', 'N/A')
|
23 |
+
|
24 |
+
def display_header(self):
|
25 |
+
company_name = self.yf_data.info['shortName']
|
26 |
+
symbol = self.yf_data.info['symbol']
|
27 |
+
st.subheader(f'{company_name} ({symbol}) 💰')
|
28 |
+
st.divider()
|
29 |
+
if self.current_price != 'N/A' and self.previous_close != 'N/A':
|
30 |
+
price_change = self.current_price - self.previous_close
|
31 |
+
price_change_ratio = (abs(price_change) / self.previous_close * 100)
|
32 |
+
price_change_direction = "+" if price_change > 0 else "-"
|
33 |
+
st.metric(label='Current Price', value=f"{self.current_price:.2f}",
|
34 |
+
delta=f"{price_change:.2f} ({price_change_direction}{price_change_ratio:.2f}%)")
|
35 |
+
|
36 |
+
def plot_data(self):
|
37 |
+
chart = StockChart(self.df_history)
|
38 |
+
chart.add_price_chart()
|
39 |
+
chart.add_oversold_overbought_lines()
|
40 |
+
chart.add_volume_chart()
|
41 |
+
chart.render_chart()
|
42 |
+
|
43 |
+
def run(self):
|
44 |
+
st.write("--------------------------------------------")
|
45 |
+
self.render_sidebar()
|
46 |
+
self.load_data()
|
47 |
+
self.display_header()
|
48 |
+
self.plot_data()
|
49 |
+
|
50 |
+
|