Spaces:
Running
Running
Upload 9 files
#40
by
DZsoul
- opened
- STOCK-MARKET-APP/app.py +14 -0
- STOCK-MARKET-APP/config.py +58 -0
- STOCK-MARKET-APP/mode.page.txt +49 -0
- STOCK-MARKET-APP/model.py +51 -0
- STOCK-MARKET-APP/plots.py +49 -0
- STOCK-MARKET-APP/requirement.txt +9 -0
- STOCK-MARKET-APP/stock_data_loader.py +19 -0
- STOCK-MARKET-APP/utils.py +324 -0
- STOCK-MARKET-APP/view_page.py +48 -0
STOCK-MARKET-APP/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()
|
STOCK-MARKET-APP/config.py
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Configuration settings for the stock forecasting app
|
2 |
+
|
3 |
+
# Rate limiting settings
|
4 |
+
RATE_LIMIT_DELAY = 0.5 # Minimum delay between API calls (seconds)
|
5 |
+
MAX_RETRIES = 3 # Maximum number of retries for failed requests
|
6 |
+
BASE_RETRY_DELAY = 3 # Base delay for exponential backoff (seconds)
|
7 |
+
|
8 |
+
# Cache settings
|
9 |
+
DEFAULT_CACHE_TTL = 300 # Default cache time-to-live (seconds) - 5 minutes
|
10 |
+
MODEL_CACHE_TTL = 600 # Cache TTL for model data (seconds) - 10 minutes
|
11 |
+
|
12 |
+
# API settings
|
13 |
+
YAHOO_FINANCE_TIMEOUT = 10 # Timeout for yfinance requests (seconds)
|
14 |
+
|
15 |
+
# UI settings
|
16 |
+
DEFAULT_TICKERS = ['NVDA', 'AAPL', 'GOOGL', 'MSFT', 'AMZN']
|
17 |
+
PERIOD_MAP = {
|
18 |
+
'all': 'max',
|
19 |
+
'1m': '1mo',
|
20 |
+
'6m': '6mo',
|
21 |
+
'1y': '1y'
|
22 |
+
}
|
23 |
+
|
24 |
+
# Error messages
|
25 |
+
ERROR_MESSAGES = {
|
26 |
+
'rate_limit': """
|
27 |
+
π« **Rate Limit Exceeded**
|
28 |
+
|
29 |
+
Yahoo Finance has temporarily limited your requests. This happens when too many requests are made in a short time.
|
30 |
+
|
31 |
+
**What you can do:**
|
32 |
+
- Wait 5-10 minutes before trying again
|
33 |
+
- Use the cached data if available
|
34 |
+
- Try a different stock ticker
|
35 |
+
|
36 |
+
The app will automatically retry with delays between requests.
|
37 |
+
""",
|
38 |
+
'network': """
|
39 |
+
π **Network Error**
|
40 |
+
|
41 |
+
There seems to be a connectivity issue.
|
42 |
+
|
43 |
+
**What you can do:**
|
44 |
+
- Check your internet connection
|
45 |
+
- Try refreshing the page
|
46 |
+
- Wait a moment and try again
|
47 |
+
""",
|
48 |
+
'no_data': """
|
49 |
+
π **No Data Available**
|
50 |
+
|
51 |
+
No stock data was found for the selected ticker and time period.
|
52 |
+
|
53 |
+
**What you can do:**
|
54 |
+
- Try a different time period
|
55 |
+
- Check if the ticker symbol is correct
|
56 |
+
- Try a different stock ticker
|
57 |
+
"""
|
58 |
+
}
|
STOCK-MARKET-APP/mode.page.txt
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()
|
STOCK-MARKET-APP/model.py
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
from sklearn.preprocessing import MinMaxScaler
|
3 |
+
from keras.models import Sequential
|
4 |
+
from keras.layers import LSTM, Dense
|
5 |
+
import warnings
|
6 |
+
warnings.filterwarnings("ignore")
|
7 |
+
|
8 |
+
class Model:
|
9 |
+
def __init__(self, data):
|
10 |
+
self.data = data
|
11 |
+
self.scaler = MinMaxScaler(feature_range=(0, 1))
|
12 |
+
self.model = None
|
13 |
+
|
14 |
+
def prepare_data(self, look_back=1):
|
15 |
+
scaled_data = self.scaler.fit_transform(self.data['Close'].values.reshape(-1, 1))
|
16 |
+
def create_dataset(dataset):
|
17 |
+
X, Y = [], []
|
18 |
+
for i in range(len(dataset) - look_back):
|
19 |
+
a = dataset[i:(i + look_back), 0]
|
20 |
+
X.append(a)
|
21 |
+
Y.append(dataset[i + look_back, 0])
|
22 |
+
return np.array(X), np.array(Y)
|
23 |
+
|
24 |
+
X, Y = create_dataset(scaled_data)
|
25 |
+
X = np.reshape(X, (X.shape[0], 1, X.shape[1]))
|
26 |
+
return X, Y
|
27 |
+
|
28 |
+
def train_lstm(self, epochs=5, batch_size=1):
|
29 |
+
X, Y = self.prepare_data()
|
30 |
+
self.model = Sequential()
|
31 |
+
self.model.add(LSTM(50, input_shape=(1, 1)))
|
32 |
+
self.model.add(Dense(1))
|
33 |
+
self.model.compile(loss='mean_squared_error', optimizer='adam')
|
34 |
+
self.model.fit(X, Y, epochs=epochs, batch_size=batch_size, verbose=0)
|
35 |
+
|
36 |
+
def make_predictions(self):
|
37 |
+
X, _ = self.prepare_data()
|
38 |
+
predictions = self.model.predict(X)
|
39 |
+
predictions = self.scaler.inverse_transform(predictions)
|
40 |
+
return predictions
|
41 |
+
|
42 |
+
def forecast_future(self, days=5):
|
43 |
+
last_value = self.data['Close'].values[-1:].reshape(-1, 1)
|
44 |
+
last_scaled = self.scaler.transform(last_value)
|
45 |
+
future_predictions = []
|
46 |
+
for _ in range(days):
|
47 |
+
prediction = self.model.predict(last_scaled.reshape(1, 1, 1))[0]
|
48 |
+
future_predictions.append(prediction)
|
49 |
+
last_scaled = prediction
|
50 |
+
future_predictions = self.scaler.inverse_transform(future_predictions)
|
51 |
+
return future_predictions
|
STOCK-MARKET-APP/plots.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
STOCK-MARKET-APP/requirement.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-MARKET-APP/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
|
STOCK-MARKET-APP/utils.py
ADDED
@@ -0,0 +1,324 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import streamlit as st
|
3 |
+
import yfinance as yf
|
4 |
+
from functools import wraps
|
5 |
+
import pandas as pd
|
6 |
+
import numpy as np
|
7 |
+
import random
|
8 |
+
from datetime import datetime, timedelta
|
9 |
+
try:
|
10 |
+
import pandas_datareader.data as web
|
11 |
+
PANDAS_DATAREADER_AVAILABLE = True
|
12 |
+
except ImportError:
|
13 |
+
PANDAS_DATAREADER_AVAILABLE = False
|
14 |
+
st.warning("pandas_datareader not available. Install it with: pip install pandas-datareader")
|
15 |
+
|
16 |
+
class RateLimitManager:
|
17 |
+
"""Manages rate limiting for API calls"""
|
18 |
+
|
19 |
+
def __init__(self, min_delay=3.0):
|
20 |
+
self.min_delay = min_delay
|
21 |
+
self.last_call_time = 0
|
22 |
+
|
23 |
+
def wait_if_needed(self):
|
24 |
+
"""Ensure minimum delay between API calls"""
|
25 |
+
current_time = time.time()
|
26 |
+
time_since_last_call = current_time - self.last_call_time
|
27 |
+
|
28 |
+
if time_since_last_call < self.min_delay:
|
29 |
+
sleep_time = self.min_delay - time_since_last_call + random.uniform(0.5, 1.5)
|
30 |
+
time.sleep(sleep_time)
|
31 |
+
|
32 |
+
self.last_call_time = time.time()
|
33 |
+
|
34 |
+
# Global rate limit manager
|
35 |
+
rate_limiter = RateLimitManager()
|
36 |
+
|
37 |
+
def create_sample_data(ticker, period='1mo'):
|
38 |
+
"""Create sample data when API is unavailable"""
|
39 |
+
|
40 |
+
# Define sample data for common tickers
|
41 |
+
sample_data = {
|
42 |
+
'NVDA': {'base_price': 450, 'volatility': 0.03, 'trend': 0.001},
|
43 |
+
'AAPL': {'base_price': 190, 'volatility': 0.02, 'trend': 0.0005},
|
44 |
+
'GOOGL': {'base_price': 140, 'volatility': 0.025, 'trend': 0.0008},
|
45 |
+
'MSFT': {'base_price': 420, 'volatility': 0.02, 'trend': 0.0007},
|
46 |
+
'AMZN': {'base_price': 150, 'volatility': 0.025, 'trend': 0.0006}
|
47 |
+
}
|
48 |
+
|
49 |
+
# Get parameters for ticker or use defaults
|
50 |
+
params = sample_data.get(ticker, {'base_price': 100, 'volatility': 0.02, 'trend': 0.0005})
|
51 |
+
|
52 |
+
# Generate date range based on period
|
53 |
+
if period == 'max' or period == '1y':
|
54 |
+
days = 252
|
55 |
+
elif period == '6mo':
|
56 |
+
days = 126
|
57 |
+
elif period == '1mo':
|
58 |
+
days = 30
|
59 |
+
else:
|
60 |
+
days = 30
|
61 |
+
|
62 |
+
# Create date range
|
63 |
+
end_date = datetime.now()
|
64 |
+
start_date = end_date - timedelta(days=days)
|
65 |
+
dates = pd.date_range(start=start_date, end=end_date, freq='D')
|
66 |
+
|
67 |
+
# Remove weekends
|
68 |
+
dates = dates[dates.weekday < 5]
|
69 |
+
|
70 |
+
# Generate price data
|
71 |
+
np.random.seed(42) # For consistent sample data
|
72 |
+
returns = np.random.normal(params['trend'], params['volatility'], len(dates))
|
73 |
+
|
74 |
+
prices = [params['base_price']]
|
75 |
+
for ret in returns[1:]:
|
76 |
+
prices.append(prices[-1] * (1 + ret))
|
77 |
+
|
78 |
+
# Create DataFrame
|
79 |
+
df = pd.DataFrame(index=dates[:len(prices)])
|
80 |
+
df['Close'] = prices
|
81 |
+
df['Open'] = df['Close'].shift(1).fillna(df['Close'])
|
82 |
+
df['High'] = df['Close'] * (1 + np.random.uniform(0, 0.02, len(df)))
|
83 |
+
df['Low'] = df['Close'] * (1 - np.random.uniform(0, 0.02, len(df)))
|
84 |
+
df['Volume'] = np.random.randint(1000000, 10000000, len(df))
|
85 |
+
|
86 |
+
return df
|
87 |
+
|
88 |
+
def retry_with_backoff(max_retries=5, base_delay=10):
|
89 |
+
"""Decorator for retrying functions with exponential backoff"""
|
90 |
+
def decorator(func):
|
91 |
+
@wraps(func)
|
92 |
+
def wrapper(*args, **kwargs):
|
93 |
+
for attempt in range(max_retries):
|
94 |
+
try:
|
95 |
+
rate_limiter.wait_if_needed()
|
96 |
+
return func(*args, **kwargs)
|
97 |
+
except Exception as e:
|
98 |
+
error_msg = str(e).lower()
|
99 |
+
|
100 |
+
if any(keyword in error_msg for keyword in ['rate', 'limit', '429', 'too many requests']):
|
101 |
+
if attempt < max_retries - 1:
|
102 |
+
wait_time = base_delay * (2 ** attempt) + random.uniform(2, 5)
|
103 |
+
st.warning(f"π« Rate limit hit. Waiting {wait_time:.1f} seconds before retry {attempt + 2}/{max_retries}...")
|
104 |
+
time.sleep(wait_time)
|
105 |
+
continue
|
106 |
+
else:
|
107 |
+
st.error("β±οΈ Rate limit exceeded after all retries. Using sample data.")
|
108 |
+
return None
|
109 |
+
elif any(keyword in error_msg for keyword in ['expecting value', 'no timezone', 'delisted', 'json']):
|
110 |
+
if attempt < max_retries - 1:
|
111 |
+
wait_time = base_delay + random.uniform(2, 4)
|
112 |
+
st.warning(f"π Data parsing error. Retrying in {wait_time:.1f} seconds... (attempt {attempt + 2}/{max_retries})")
|
113 |
+
time.sleep(wait_time)
|
114 |
+
continue
|
115 |
+
else:
|
116 |
+
st.warning("β οΈ Unable to fetch real data. Using sample data for demonstration.")
|
117 |
+
return None
|
118 |
+
else:
|
119 |
+
if attempt < max_retries - 1:
|
120 |
+
wait_time = base_delay + random.uniform(1, 3)
|
121 |
+
st.warning(f"β Error: {str(e)[:100]}... Retrying in {wait_time:.1f} seconds...")
|
122 |
+
time.sleep(wait_time)
|
123 |
+
continue
|
124 |
+
else:
|
125 |
+
st.error(f"β Failed after {max_retries} attempts: {str(e)[:100]}...")
|
126 |
+
return None
|
127 |
+
return None
|
128 |
+
return wrapper
|
129 |
+
return decorator
|
130 |
+
|
131 |
+
def fetch_data_with_stooq(ticker_symbol, start_date=None, end_date=None, period='1mo'):
|
132 |
+
"""Fetch stock data using pandas_datareader with stooq as source"""
|
133 |
+
if not PANDAS_DATAREADER_AVAILABLE:
|
134 |
+
return None
|
135 |
+
|
136 |
+
try:
|
137 |
+
# Convert period to date range if start/end not provided
|
138 |
+
if start_date is None or end_date is None:
|
139 |
+
end_date = datetime.now()
|
140 |
+
if period == 'max' or period == '1y':
|
141 |
+
start_date = end_date - timedelta(days=365)
|
142 |
+
elif period == '6mo':
|
143 |
+
start_date = end_date - timedelta(days=180)
|
144 |
+
elif period == '1mo':
|
145 |
+
start_date = end_date - timedelta(days=30)
|
146 |
+
elif period == '5d':
|
147 |
+
start_date = end_date - timedelta(days=5)
|
148 |
+
else:
|
149 |
+
start_date = end_date - timedelta(days=30)
|
150 |
+
|
151 |
+
# Fetch data from stooq
|
152 |
+
df = web.DataReader(ticker_symbol, 'stooq', start_date, end_date)
|
153 |
+
|
154 |
+
if df.empty:
|
155 |
+
return None
|
156 |
+
|
157 |
+
# Stooq returns data in reverse chronological order, so sort it
|
158 |
+
df = df.sort_index()
|
159 |
+
|
160 |
+
# Ensure we have the required columns
|
161 |
+
required_columns = ['Open', 'High', 'Low', 'Close', 'Volume']
|
162 |
+
if all(col in df.columns for col in required_columns):
|
163 |
+
return df
|
164 |
+
else:
|
165 |
+
st.warning(f"Missing columns in stooq data: {[col for col in required_columns if col not in df.columns]}")
|
166 |
+
return None
|
167 |
+
|
168 |
+
except Exception as e:
|
169 |
+
st.error(f"Error fetching data from stooq: {str(e)}")
|
170 |
+
return None
|
171 |
+
|
172 |
+
def safe_yfinance_call(ticker_symbol, operation='history', **kwargs):
|
173 |
+
"""Safely call multiple data sources with fallback to sample data"""
|
174 |
+
|
175 |
+
# First try stooq (pandas_datareader) for historical data
|
176 |
+
if operation == 'history' and PANDAS_DATAREADER_AVAILABLE:
|
177 |
+
try:
|
178 |
+
st.sidebar.info(f"π Trying stooq API for {ticker_symbol}...")
|
179 |
+
stooq_data = fetch_data_with_stooq(
|
180 |
+
ticker_symbol,
|
181 |
+
start_date=kwargs.get('start'),
|
182 |
+
end_date=kwargs.get('end'),
|
183 |
+
period=kwargs.get('period', '1mo')
|
184 |
+
)
|
185 |
+
|
186 |
+
if stooq_data is not None and not stooq_data.empty:
|
187 |
+
st.sidebar.success(f"β
Real data from stooq for {ticker_symbol}")
|
188 |
+
return stooq_data
|
189 |
+
else:
|
190 |
+
st.sidebar.warning(f"β οΈ Stooq failed for {ticker_symbol}")
|
191 |
+
except Exception as e:
|
192 |
+
st.sidebar.warning(f"β οΈ Stooq error: {str(e)[:50]}...")
|
193 |
+
|
194 |
+
# If stooq fails or for info operation, try yfinance as backup
|
195 |
+
try:
|
196 |
+
st.sidebar.info(f"π Trying yfinance API for {ticker_symbol}...")
|
197 |
+
ticker = yf.Ticker(ticker_symbol)
|
198 |
+
|
199 |
+
if operation == 'history':
|
200 |
+
result = ticker.history(
|
201 |
+
timeout=10,
|
202 |
+
prepost=False,
|
203 |
+
auto_adjust=True,
|
204 |
+
back_adjust=False,
|
205 |
+
repair=True,
|
206 |
+
keepna=False,
|
207 |
+
actions=False,
|
208 |
+
**kwargs
|
209 |
+
)
|
210 |
+
|
211 |
+
if result is not None and not result.empty and len(result) > 0:
|
212 |
+
st.sidebar.success(f"β
Real data from yfinance for {ticker_symbol}")
|
213 |
+
return result
|
214 |
+
else:
|
215 |
+
st.sidebar.warning(f"β οΈ yfinance returned empty data for {ticker_symbol}")
|
216 |
+
|
217 |
+
elif operation == 'info':
|
218 |
+
result = ticker.info
|
219 |
+
if result and isinstance(result, dict) and len(result) > 1:
|
220 |
+
st.sidebar.success(f"β
Info from yfinance for {ticker_symbol}")
|
221 |
+
return result
|
222 |
+
else:
|
223 |
+
st.sidebar.warning(f"β οΈ yfinance info empty for {ticker_symbol}")
|
224 |
+
|
225 |
+
else:
|
226 |
+
raise ValueError(f"Unsupported operation: {operation}")
|
227 |
+
|
228 |
+
except Exception as e:
|
229 |
+
st.sidebar.warning(f"β οΈ yfinance also failed: {str(e)[:50]}...")
|
230 |
+
|
231 |
+
# Finally fallback to sample data
|
232 |
+
if operation == 'history':
|
233 |
+
st.sidebar.warning(f"π Using sample data for {ticker_symbol}")
|
234 |
+
return create_sample_data(ticker_symbol, kwargs.get('period', '1mo'))
|
235 |
+
elif operation == 'info':
|
236 |
+
sample_prices = {
|
237 |
+
'NVDA': 450, 'AAPL': 190, 'GOOGL': 140, 'MSFT': 420, 'AMZN': 150
|
238 |
+
}
|
239 |
+
base_price = sample_prices.get(ticker_symbol, 100)
|
240 |
+
return {
|
241 |
+
'symbol': ticker_symbol,
|
242 |
+
'shortName': f'{ticker_symbol} Inc.',
|
243 |
+
'currentPrice': base_price + random.uniform(-2, 2),
|
244 |
+
'previousClose': base_price
|
245 |
+
}
|
246 |
+
else:
|
247 |
+
raise Exception(f"All data sources failed for {ticker_symbol}")
|
248 |
+
|
249 |
+
def get_cached_data(cache_key, ttl_seconds=300):
|
250 |
+
"""Get cached data from session state if still valid"""
|
251 |
+
if cache_key in st.session_state:
|
252 |
+
cache_time_key = f"cache_time_{cache_key}"
|
253 |
+
if cache_time_key in st.session_state:
|
254 |
+
cache_time = st.session_state[cache_time_key]
|
255 |
+
if time.time() - cache_time < ttl_seconds:
|
256 |
+
return st.session_state[cache_key]
|
257 |
+
return None
|
258 |
+
|
259 |
+
def set_cached_data(cache_key, data):
|
260 |
+
"""Cache data in session state with timestamp"""
|
261 |
+
st.session_state[cache_key] = data
|
262 |
+
st.session_state[f"cache_time_{cache_key}"] = time.time()
|
263 |
+
|
264 |
+
def clear_cache(pattern=None):
|
265 |
+
"""Clear cached data matching pattern"""
|
266 |
+
if pattern is None:
|
267 |
+
# Clear all cache
|
268 |
+
keys_to_remove = [key for key in st.session_state.keys()
|
269 |
+
if key.startswith('cache_time_') or key.startswith('data_')]
|
270 |
+
else:
|
271 |
+
keys_to_remove = [key for key in st.session_state.keys() if pattern in key]
|
272 |
+
|
273 |
+
for key in keys_to_remove:
|
274 |
+
del st.session_state[key]
|
275 |
+
|
276 |
+
return len(keys_to_remove)
|
277 |
+
|
278 |
+
def format_error_message(error):
|
279 |
+
"""Format error messages for better user experience"""
|
280 |
+
error_str = str(error).lower()
|
281 |
+
|
282 |
+
if "rate" in error_str or "limit" in error_str:
|
283 |
+
return ("π« **Rate Limit Exceeded**\n\n"
|
284 |
+
"Yahoo Finance has temporarily limited your requests. This happens when too many requests are made in a short time.\n\n"
|
285 |
+
"**What you can do:**\n"
|
286 |
+
"- Wait 5-10 minutes before trying again\n"
|
287 |
+
"- Use the cached data if available\n"
|
288 |
+
"- Try a different stock ticker\n\n"
|
289 |
+
"The app will automatically retry with delays between requests.")
|
290 |
+
elif "network" in error_str or "connection" in error_str:
|
291 |
+
return ("π **Network Error**\n\n"
|
292 |
+
"There seems to be a connectivity issue.\n\n"
|
293 |
+
"**What you can do:**\n"
|
294 |
+
"- Check your internet connection\n"
|
295 |
+
"- Try refreshing the page\n"
|
296 |
+
"- Wait a moment and try again")
|
297 |
+
else:
|
298 |
+
return f"β **Error**: {str(error)}"
|
299 |
+
|
300 |
+
def display_cache_info():
|
301 |
+
"""Display cache information in sidebar"""
|
302 |
+
with st.sidebar:
|
303 |
+
with st.expander("Cache Information"):
|
304 |
+
cache_items = [key for key in st.session_state.keys()
|
305 |
+
if key.startswith('data_') or key.startswith('model_data_')]
|
306 |
+
|
307 |
+
if cache_items:
|
308 |
+
st.write(f"**Cached items:** {len(cache_items)}")
|
309 |
+
for item in cache_items[:5]: # Show first 5 items
|
310 |
+
cache_time_key = f"cache_time_{item}"
|
311 |
+
if cache_time_key in st.session_state:
|
312 |
+
cache_time = st.session_state[cache_time_key]
|
313 |
+
age_minutes = (time.time() - cache_time) / 60
|
314 |
+
st.write(f"β’ {item.replace('data_', '')}: {age_minutes:.1f}m ago")
|
315 |
+
|
316 |
+
if len(cache_items) > 5:
|
317 |
+
st.write(f"... and {len(cache_items) - 5} more")
|
318 |
+
|
319 |
+
if st.button("Clear All Cache"):
|
320 |
+
cleared = clear_cache()
|
321 |
+
st.success(f"Cleared {cleared} cached items")
|
322 |
+
st.experimental_rerun()
|
323 |
+
else:
|
324 |
+
st.write("No cached data")
|
STOCK-MARKET-APP/view_page.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|