consider this code :
import sys
import os
from PyQt5.QtWidgets import (
    QApplication, QWidget, QLabel, QLineEdit, QPushButton, QVBoxLayout, 
    QHBoxLayout, QTextEdit, QMessageBox, QFileDialog, QComboBox, QProgressBar, 
    QCheckBox, QListWidget
)
from PyQt5.QtGui import QFont, QPalette, QColor
from PyQt5.QtCore import Qt, QDate
import yfinance as yf
import pandas as pd
import numpy as np
from statsmodels.tsa.arima.model import ARIMA
from pmdarima import auto_arima
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error
from sklearn.model_selection import GridSearchCV
from xgboost import XGBRegressor
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
from textblob import TextBlob
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from tqdm import tqdm
import requests
import json
import warnings
import ta  # Technical Analysis library
warnings.filterwarnings("ignore")

class StockPredictionApp(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.all_predictions = {}
        self.analyzer = SentimentIntensityAnalyzer()
        self.prediction_data = pd.DataFrame()

    def init_ui(self):
        # Set window properties
        self.setWindowTitle('Advanced Indian Stock Price Prediction App')
        self.setGeometry(100, 100, 1400, 900)

        # Create main layout
        main_layout = QVBoxLayout()

        # Welcome Label
        welcome_label = QLabel('Advanced Indian Stock Price Prediction App')
        welcome_label.setFont(QFont('Arial', 24, QFont.Bold))
        welcome_label.setAlignment(Qt.AlignCenter)
        welcome_label.setStyleSheet("""
            QLabel {
                color: #ffffff;
                margin: 20px;
            }
        """)
        main_layout.addWidget(welcome_label)

        # Input Fields Layout
        input_layout = QVBoxLayout()

        # Stock Symbols Input
        stock_layout = QHBoxLayout()
        stock_label = QLabel('Enter Stock Symbol(s):')
        stock_label.setStyleSheet("color: #ffffff; font-size: 14px;")
        self.stock_input = QLineEdit()
        self.stock_input.setPlaceholderText('e.g., RELIANCE.NS, TCS.NS, INFY.NS')
        stock_layout.addWidget(stock_label)
        stock_layout.addWidget(self.stock_input)
        input_layout.addLayout(stock_layout)

        # Button to Load CSV or Text File
        load_file_layout = QHBoxLayout()
        load_file_label = QLabel('Or Load from File:')
        load_file_label.setStyleSheet("color: #ffffff; font-size: 14px;")
        self.load_file_button = QPushButton('Load CSV/Text')
        self.load_file_button.setFont(QFont('Arial', 10))
        self.load_file_button.setStyleSheet("""
            QPushButton {
                background-color: #FF9800; 
                color: white;
                padding: 6px;
                border: none;
                border-radius: 5px;
            }
            QPushButton:hover {
                background-color: #e68900;
            }
        """)
        self.load_file_button.clicked.connect(self.load_file)
        load_file_layout.addWidget(load_file_label)
        load_file_layout.addWidget(self.load_file_button)
        input_layout.addLayout(load_file_layout)

        # Prediction Days Input
        days_layout = QHBoxLayout()
        days_label = QLabel('Number of Days to Predict:')
        days_label.setStyleSheet("color: #ffffff; font-size: 14px;")
        self.days_input = QLineEdit()
        self.days_input.setPlaceholderText('e.g., 5')
        days_layout.addWidget(days_label)
        days_layout.addWidget(self.days_input)
        input_layout.addLayout(days_layout)

        # Historical Data Duration
        duration_layout = QHBoxLayout()
        duration_label = QLabel('Historical Data Duration:')
        duration_label.setStyleSheet("color: #ffffff; font-size: 14px;")
        self.duration_combo = QComboBox()
        self.duration_combo.addItems(['1 Year', '2 Years', '3 Years', '4 Years', '5 Years', '10 Years'])
        duration_layout.addWidget(duration_label)
        duration_layout.addWidget(self.duration_combo)
        input_layout.addLayout(duration_layout)

        # End Date Input for Backtesting
        end_date_layout = QHBoxLayout()
        end_date_label = QLabel('Data End Date:')
        end_date_label.setStyleSheet("color: #ffffff; font-size: 14px;")
        self.end_date_input = QLineEdit()
        self.end_date_input.setPlaceholderText('YYYY-MM-DD (e.g., 2024-09-25)')
        end_date_layout.addWidget(end_date_label)
        end_date_layout.addWidget(self.end_date_input)
        input_layout.addLayout(end_date_layout)

        # API Key Input
        api_key_layout = QHBoxLayout()
        api_key_label = QLabel('NewsAPI Key:')
        api_key_label.setStyleSheet("color: #ffffff; font-size: 14px;")
        self.api_key_input = QLineEdit()
        self.api_key_input.setPlaceholderText('Enter your NewsAPI.org key or leave blank to disable sentiment analysis')
        self.api_key_input.textChanged.connect(self.toggle_sentiment_checkbox)
        api_key_layout.addWidget(api_key_label)
        api_key_layout.addWidget(self.api_key_input)
        input_layout.addLayout(api_key_layout)

        # News Sentiment Analysis Checkbox
        sentiment_layout = QHBoxLayout()
        self.sentiment_checkbox = QCheckBox('Include News Sentiment Analysis')
        self.sentiment_checkbox.setStyleSheet("color: #ffffff;")
        self.sentiment_checkbox.setChecked(False)
        self.sentiment_checkbox.setEnabled(False)  # Initially disabled until API key is provided
        sentiment_layout.addWidget(self.sentiment_checkbox)
        input_layout.addLayout(sentiment_layout)

        # Choose Models to Use
        models_layout = QHBoxLayout()
        models_label = QLabel('Select Models:')
        models_label.setStyleSheet("color: #ffffff; font-size: 14px;")
        self.arima_checkbox = QCheckBox('ARIMA')
        self.arima_checkbox.setChecked(True)
        self.rf_checkbox = QCheckBox('Random Forest')
        self.rf_checkbox.setChecked(True)
        self.xgb_checkbox = QCheckBox('XGBoost')
        self.xgb_checkbox.setChecked(True)
        # Uncomment the following lines if you implement LSTM
        # self.lstm_checkbox = QCheckBox('LSTM')
        # self.lstm_checkbox.setChecked(False)
        # self.lstm_checkbox.setEnabled(False)  # Disabled by default
        models_layout.addWidget(models_label)
        models_layout.addWidget(self.arima_checkbox)
        models_layout.addWidget(self.rf_checkbox)
        models_layout.addWidget(self.xgb_checkbox)
        # models_layout.addWidget(self.lstm_checkbox)  # Uncomment if LSTM is implemented
        input_layout.addLayout(models_layout)

        main_layout.addLayout(input_layout)

        # Progress Bar
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        self.progress_bar.setStyleSheet("""
            QProgressBar {
                border: 2px solid grey;
                border-radius: 5px;
                text-align: center;
            }
            QProgressBar::chunk {
                background-color: #05B8CC;
                width: 20px;
            }
        """)
        main_layout.addWidget(self.progress_bar)

        # Predict Button
        self.predict_button = QPushButton('Predict')
        self.predict_button.setFont(QFont('Arial', 14))
        self.predict_button.setStyleSheet("""
            QPushButton {
                background-color: #4CAF50; 
                color: white;
                padding: 10px;
                border: none;
                border-radius: 5px;
                width: 150px;
                align: center;
            }
            QPushButton:hover {
                background-color: #45a049;
            }
        """)
        self.predict_button.clicked.connect(self.predict_stock)
        main_layout.addWidget(self.predict_button, alignment=Qt.AlignCenter)

        # Output Section
        self.output = QTextEdit()
        self.output.setReadOnly(True)
        self.output.setStyleSheet("background-color: #f0f0f0; padding: 10px; color: black;")
        main_layout.addWidget(self.output)

        # Stock Selection Dropdown for Individual Graphs
        graph_selection_layout = QHBoxLayout()
        graph_label = QLabel('Select Stock to View Graph:')
        graph_label.setStyleSheet("color: #ffffff; font-size: 14px;")
        self.stock_dropdown = QComboBox()
        self.stock_dropdown.currentIndexChanged.connect(self.update_graph)
        graph_selection_layout.addWidget(graph_label)
        graph_selection_layout.addWidget(self.stock_dropdown)
        main_layout.addLayout(graph_selection_layout)

        # Add Matplotlib Figure
        self.figure = Figure(figsize=(12, 8), dpi=100)
        self.canvas = FigureCanvas(self.figure)
        main_layout.addWidget(self.canvas)

        # Save Predictions Button
        self.save_button = QPushButton('Save Predictions to CSV')
        self.save_button.setFont(QFont('Arial', 12))
        self.save_button.setStyleSheet("""
            QPushButton {
                background-color: #2196F3; 
                color: white;
                padding: 8px;
                border: none;
                border-radius: 5px;
                width: 200px;
            }
            QPushButton:hover {
                background-color: #0b7dda;
            }
        """)
        self.save_button.clicked.connect(self.save_predictions)
        self.save_button.setEnabled(False)  # Disabled until predictions are made
        main_layout.addWidget(self.save_button, alignment=Qt.AlignCenter)

        # Set the main layout
        self.setLayout(main_layout)

        # Apply a background color
        palette = self.palette()
        palette.setColor(QPalette.Window, QColor(53, 53, 53))  # Dark background
        self.setPalette(palette)

    def toggle_sentiment_checkbox(self):
        api_key = self.api_key_input.text().strip()
        if api_key:
            self.sentiment_checkbox.setEnabled(True)
        else:
            self.sentiment_checkbox.setChecked(False)
            self.sentiment_checkbox.setEnabled(False)

    def load_file(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getOpenFileName(
            self, "Load Stock Symbols File", "", "CSV Files (*.csv);;Text Files (*.txt);;All Files (*)", options=options
        )
        if file_name:
            try:
                if file_name.endswith('.csv'):
                    df = pd.read_csv(file_name)
                    if 'Symbol' not in df.columns:
                        QMessageBox.warning(self, 'CSV Format Error', 'CSV must contain a "Symbol" column.')
                        return
                    symbols = df['Symbol'].astype(str).str.strip().tolist()
                elif file_name.endswith('.txt'):
                    with open(file_name, 'r') as f:
                        symbols = [line.strip() for line in f.readlines() if line.strip()]
                else:
                    QMessageBox.warning(self, 'File Format Error', 'Unsupported file format.')
                    return
                self.stock_input.setText(', '.join(symbols))
                QMessageBox.information(self, 'Success', f'Loaded {len(symbols)} stock symbols from file.')
            except Exception as e:
                QMessageBox.warning(self, 'Error', f'Failed to load file: {str(e)}')

    def predict_stock(self):
        # Reset progress bar
        self.progress_bar.setValue(0)
        QApplication.processEvents()

        # Retrieve user inputs
        stock_symbols = self.stock_input.text().upper().replace(' ', '').split(',')
        stock_symbols = [symbol.strip() for symbol in stock_symbols if symbol.strip()]
        days = self.days_input.text()
        duration_text = self.duration_combo.currentText()
        end_date_str = self.end_date_input.text()
        include_sentiment = self.sentiment_checkbox.isChecked()
        models_selected = {
            'ARIMA': self.arima_checkbox.isChecked(),
            'Random Forest': self.rf_checkbox.isChecked(),
            'XGBoost': self.xgb_checkbox.isChecked()
            # 'LSTM': self.lstm_checkbox.isChecked()  # Uncomment if implementing LSTM
        }

        if not stock_symbols:
            QMessageBox.warning(self, 'Input Error', 'Please enter at least one stock symbol or load from a file.')
            return

        if not days.isdigit():
            QMessageBox.warning(self, 'Input Error', 'Please enter a valid number of days to predict.')
            return

        # Validate End Date
        if end_date_str:
            try:
                end_date = pd.to_datetime(end_date_str)
                today = pd.to_datetime('today')
                if end_date > today:
                    QMessageBox.warning(self, 'Date Error', 'End date cannot be in the future.')
                    return
            except:
                QMessageBox.warning(self, 'Date Format Error', 'Please enter the end date in YYYY-MM-DD format.')
                return
        else:
            end_date = pd.to_datetime('today')

        days = int(days)

        # Map duration text to pandas DateOffset
        duration_mapping = {
            '1 Year': pd.DateOffset(years=1),
            '2 Years': pd.DateOffset(years=2),
            '3 Years': pd.DateOffset(years=3),
            '4 Years': pd.DateOffset(years=4),
            '5 Years': pd.DateOffset(years=5),
            '10 Years': pd.DateOffset(years=10)
        }
        duration_offset = duration_mapping.get(duration_text, pd.DateOffset(years=1))
        start_date = end_date - duration_offset

        # Display the date range in output
        self.output.clear()
        self.output.append(f"Fetching data from {start_date.date()} to {end_date.date()}...\n")

        # Initialize progress
        total_models = sum(models_selected.values())
        total_tasks = len(stock_symbols) * total_models
        current_task = 0
        self.progress_bar.setMaximum(total_tasks)
        self.progress_bar.setValue(0)
        QApplication.processEvents()

        # Clear previous plot
        self.figure.clear()
        ax = self.figure.add_subplot(111)

        # Reset predictions storage
        self.all_predictions = {}
        self.prediction_data = pd.DataFrame()

        # Populate the stock dropdown
        self.stock_dropdown.clear()
        self.stock_dropdown.addItems(stock_symbols)

        for stock in tqdm(stock_symbols, desc="Processing Stocks"):
            try:
                # Fetch historical data
                data = yf.download(stock, start=start_date, end=end_date)
                if data.empty:
                    self.output.append(f"Stock: {stock} - No data found for the specified date range.\n")
                    continue

                # Ensure the data is sorted
                data.sort_index(inplace=True)

                # Handle missing data
                data = data.asfreq('B')  # Business days
                data.fillna(method='ffill', inplace=True)

                # Include sentiment analysis if selected and API key is provided
                if include_sentiment:
                    sentiment_score = self.get_news_sentiment(stock)
                    data['Sentiment'] = sentiment_score
                else:
                    data['Sentiment'] = 0  # Neutral sentiment

                # Feature Engineering with Technical Indicators
                data = self.add_technical_indicators(data)

                # Drop rows with NaN values after technical indicators
                data.dropna(inplace=True)

                # Initialize DataFrame to store predictions for this stock
                stock_predictions = pd.DataFrame()

                ### ARIMA Model
                if models_selected['ARIMA']:
                    current_task +=1
                    self.progress_bar.setValue(current_task)
                    QApplication.processEvents()

                    try:
                        # Parameter Optimization using auto_arima
                        stepwise_fit = auto_arima(
                            data['Close'], 
                            start_p=1, 
                            start_q=1,
                            max_p=5, 
                            max_q=5, 
                            seasonal=False, 
                            trace=False,
                            error_action='ignore',
                            suppress_warnings=True
                        )

                        arima_order = stepwise_fit.order
                        model = ARIMA(data['Close'], order=arima_order)
                        model_fit = model.fit()
                        forecast = model_fit.forecast(steps=days)

                        # Calculate training RMSE
                        fitted = model_fit.fittedvalues
                        rmse = np.sqrt(mean_squared_error(data['Close'], fitted))

                        # Store predictions
                        stock_predictions['ARIMA_Predicted'] = forecast.values
                        self.all_predictions.setdefault(stock, {})['ARIMA'] = forecast.values

                        # Display Output
                        self.output.append(f"Stock: {stock} - ARIMA")
                        self.output.append(f"Model Order: {arima_order}")
                        self.output.append(f"Training RMSE: {rmse:.2f}")
                        self.output.append("Predicted Prices:")
                        for i, price in enumerate(forecast, 1):
                            pred_date = end_date + pd.Timedelta(days=i)
                            if pred_date.weekday() >=5:  # Skip weekends
                                pred_date += pd.Timedelta(days=7 - pred_date.weekday())
                            self.output.append(f"{pred_date.date()}: ₹{price:.2f}")
                        self.output.append("")

                        # Plotting
                        future_dates = pd.bdate_range(start=data.index[-1] + pd.Timedelta(days=1), periods=len(forecast))
                        ax.plot(data.index, data['Close'], label=f'{stock} Historical')
                        ax.plot(future_dates, forecast, label=f'{stock} ARIMA Predicted', linestyle='--')

                    except Exception as e:
                        self.output.append(f"ARIMA Model Error for {stock}: {str(e)}\n")
                        print(e)

                ### Random Forest Model
                if models_selected['Random Forest']:
                    current_task +=1
                    self.progress_bar.setValue(current_task)
                    QApplication.processEvents()

                    try:
                        # Prepare features and target
                        data_rf = data.copy()
                        feature_cols = ['EMA_10', 'EMA_50', 'RSI', 'MACD', 'Stochastic', 'ADX', 'Volume', 'Sentiment']
                        X = data_rf[feature_cols]
                        y = data_rf['Close']

                        # Train-Test Split
                        split = int(0.8 * len(X))
                        X_train, X_test = X.iloc[:split], X.iloc[split:]
                        y_train, y_test = y.iloc[:split], y.iloc[split:]

                        # Parameter Grid for Random Forest
                        param_grid = {
                            'n_estimators': [100, 200],
                            'max_depth': [5, 10, None],
                            'min_samples_split': [2, 5]
                        }

                        rf = RandomForestRegressor(random_state=42)
                        grid_search = GridSearchCV(estimator=rf, param_grid=param_grid, cv=3, n_jobs=-1, verbose=0)
                        grid_search.fit(X_train, y_train)
                        best_rf = grid_search.best_estimator_

                        # Prepare for iterative prediction
                        last_features = X.iloc[-1].values.reshape(1, -1)
                        rf_forecast = []

                        for _ in range(days):
                            pred_price = best_rf.predict(last_features)[0]
                            rf_forecast.append(pred_price)

                            # Update features with predicted price
                            # For simplicity, we assume technical indicators remain the same or are updated based on predicted price
                            # Ideally, you should recalculate technical indicators based on the new price
                            # Here, we'll keep them static for demonstration
                            new_features = last_features.flatten().tolist()
                            new_features[0] +=1  # Increment Day
                            # Sentiment is assumed neutral
                            new_features[-1] = 0
                            last_features = np.array(new_features).reshape(1, -1)

                        # Calculate RMSE on training data
                        rf_train_pred = best_rf.predict(X_train)
                        rf_rmse = np.sqrt(mean_squared_error(y_train, rf_train_pred))

                        # Store predictions
                        stock_predictions['RandomForest_Predicted'] = rf_forecast
                        self.all_predictions.setdefault(stock, {})['Random Forest'] = rf_forecast

                        # Display Output
                        self.output.append(f"Stock: {stock} - Random Forest")
                        self.output.append(f"Best Parameters: {grid_search.best_params_}")
                        self.output.append(f"Training RMSE: {rf_rmse:.2f}")
                        self.output.append("Predicted Prices:")
                        for i, price in enumerate(rf_forecast, 1):
                            pred_date = end_date + pd.Timedelta(days=i)
                            if pred_date.weekday() >=5:  # Skip weekends
                                pred_date += pd.Timedelta(days=7 - pred_date.weekday())
                            self.output.append(f"{pred_date.date()}: ₹{price:.2f}")
                        self.output.append("")

                        # Plotting
                        future_dates = pd.bdate_range(start=data.index[-1] + pd.Timedelta(days=1), periods=len(rf_forecast))
                        ax.plot(future_dates, rf_forecast, label=f'{stock} Random Forest Predicted', linestyle='--')

                    except Exception as e:
                        self.output.append(f"Random Forest Model Error for {stock}: {str(e)}\n")
                        print(e)

                ### XGBoost Model
                if models_selected['XGBoost']:
                    current_task +=1
                    self.progress_bar.setValue(current_task)
                    QApplication.processEvents()

                    try:
                        # Prepare features and target
                        data_xgb = data.copy()
                        feature_cols = ['EMA_10', 'EMA_50', 'RSI', 'MACD', 'Stochastic', 'ADX', 'Volume', 'Sentiment']
                        X = data_xgb[feature_cols]
                        y = data_xgb['Close']

                        # Train-Test Split
                        split = int(0.8 * len(X))
                        X_train_xgb, X_test_xgb = X.iloc[:split], X.iloc[split:]
                        y_train_xgb, y_test_xgb = y.iloc[:split], y.iloc[split:]

                        # Parameter Grid for XGBoost
                        param_grid_xgb = {
                            'n_estimators': [100, 200],
                            'max_depth': [3, 5, 7],
                            'learning_rate': [0.01, 0.1, 0.2]
                        }

                        xgb = XGBRegressor(objective='reg:squarederror', random_state=42)
                        grid_search_xgb = GridSearchCV(estimator=xgb, param_grid=param_grid_xgb, cv=3, n_jobs=-1, verbose=0)
                        grid_search_xgb.fit(X_train_xgb, y_train_xgb)
                        best_xgb = grid_search_xgb.best_estimator_

                        # Prepare for iterative prediction
                        last_features = X.iloc[-1].values.reshape(1, -1)
                        xgb_forecast = []

                        for _ in range(days):
                            pred_price = best_xgb.predict(last_features)[0]
                            xgb_forecast.append(pred_price)

                            # Update features with predicted price
                            new_features = last_features.flatten().tolist()
                            new_features[0] +=1  # Increment Day
                            # Sentiment is assumed neutral
                            new_features[-1] = 0
                            last_features = np.array(new_features).reshape(1, -1)

                        # Calculate RMSE on training data
                        xgb_train_pred = best_xgb.predict(X_train_xgb)
                        xgb_rmse = np.sqrt(mean_squared_error(y_train_xgb, xgb_train_pred))

                        # Store predictions
                        stock_predictions['XGBoost_Predicted'] = xgb_forecast
                        self.all_predictions.setdefault(stock, {})['XGBoost'] = xgb_forecast

                        # Display Output
                        self.output.append(f"Stock: {stock} - XGBoost")
                        self.output.append(f"Best Parameters: {grid_search_xgb.best_params_}")
                        self.output.append(f"Training RMSE: {xgb_rmse:.2f}")
                        self.output.append("Predicted Prices:")
                        for i, price in enumerate(xgb_forecast, 1):
                            pred_date = end_date + pd.Timedelta(days=i)
                            if pred_date.weekday() >=5:  # Skip weekends
                                pred_date += pd.Timedelta(days=7 - pred_date.weekday())
                            self.output.append(f"{pred_date.date()}: ₹{price:.2f}")
                        self.output.append("")

                        # Plotting
                        future_dates = pd.bdate_range(start=data.index[-1] + pd.Timedelta(days=1), periods=len(xgb_forecast))
                        ax.plot(future_dates, xgb_forecast, label=f'{stock} XGBoost Predicted', linestyle='--')

                    except Exception as e:
                        self.output.append(f"XGBoost Model Error for {stock}: {str(e)}\n")
                        print(e)

                # Update progress bar after each stock
                QApplication.processEvents()

            # Finalize Plot
            ax.set_title("Stock Price Prediction")
            ax.set_xlabel("Date")
            ax.set_ylabel("Price (₹)")
            ax.legend()
            self.canvas.draw()

            # Enable Save Predictions Button if predictions exist
            if self.all_predictions:
                self.save_button.setEnabled(True)
                QMessageBox.information(self, 'Success', 'Predictions generated successfully.')
            else:
                self.save_button.setEnabled(False)
                QMessageBox.warning(self, 'No Predictions', 'No predictions were generated.')

    def add_technical_indicators(self, data):
        """
        Adds various technical indicators to the DataFrame.
        """
        # Exponential Moving Averages
        data['EMA_10'] = ta.trend.EMAIndicator(close=data['Close'], window=10).ema_indicator()
        data['EMA_50'] = ta.trend.EMAIndicator(close=data['Close'], window=50).ema_indicator()

        # Relative Strength Index
        data['RSI'] = ta.momentum.RSIIndicator(close=data['Close'], window=14).rsi()

        # Moving Average Convergence Divergence
        macd = ta.trend.MACD(close=data['Close'])
        data['MACD'] = macd.macd_diff()

        # Stochastic Oscillator
        stochastic = ta.momentum.StochasticOscillator(close=data['Close'], window=14, smooth_window=3)
        data['Stochastic'] = stochastic.stoch()

        # Average Directional Index
        data['ADX'] = ta.trend.ADXIndicator(high=data['High'], low=data['Low'], close=data['Close'], window=14).adx()

        # Volume Indicators can be added similarly
        # Pivot Points, Bollinger Bands, etc., can also be added

        return data

    def get_news_sentiment(self, stock_symbol):
        try:
            api_key = self.api_key_input.text().strip()
            if not api_key:
                return 0  # Neutral sentiment if no API key is provided
            query = stock_symbol.replace('.NS', '').replace('.BO', '')
            url = f'https://newsapi.org/v2/everything?q={query}&language=en&apiKey={api_key}'
            response = requests.get(url)
            articles = response.json().get('articles', [])
            if not articles:
                return 0  # Neutral sentiment if no news

            sentiments = []
            for article in articles[:10]:  # Limit to latest 10 articles
                title = article.get('title', '')
                # Use VADER for sentiment
                vs = self.analyzer.polarity_scores(title)
                sentiments.append(vs['compound'])

            if sentiments:
                average_sentiment = np.mean(sentiments)
                return average_sentiment
            else:
                return 0
        except Exception as e:
            import traceback
            error_msg = f"Error fetching sentiment for {stock_symbol}: {str(e)}\n{traceback.format_exc()}"
            self.output.append(error_msg)
            print(error_msg)  # Print detailed error to console
            return 0

    def update_graph(self):
        selected_stock = self.stock_dropdown.currentText()
        if not selected_stock:
            return

        self.figure.clear()
        ax = self.figure.add_subplot(111)

        # Fetch historical data for plotting
        try:
            # Define a fixed historical period for plotting (e.g., 5 years)
            plot_start_date = pd.to_datetime('today') - pd.DateOffset(years=5)
            plot_end_date = pd.to_datetime('today')
            data = yf.download(selected_stock, start=plot_start_date, end=plot_end_date)
            if data.empty:
                QMessageBox.warning(self, 'Data Error', f'No data found for {selected_stock}.')
                return

            data.reset_index(inplace=True)
            data['Date'] = pd.to_datetime(data['Date'])
            data.set_index('Date', inplace=True)

            # Plot historical data
            ax.plot(data.index, data['Close'], label='Historical')

            # Plot predictions if available
            if selected_stock in self.all_predictions:
                predictions = self.all_predictions[selected_stock]
                for model_name, forecast in predictions.items():
                    # Determine the start date for predictions
                    last_date = data.index[-1]
                    future_dates = pd.bdate_range(start=last_date + pd.Timedelta(days=1), periods=len(forecast))
                    ax.plot(future_dates, forecast, label=f'{model_name} Predicted', linestyle='--')

            ax.set_title(f"{selected_stock} Price Prediction")
            ax.set_xlabel("Date")
            ax.set_ylabel("Price (₹)")
            ax.legend()
            self.canvas.draw()

        except Exception as e:
            import traceback
            error_msg = f"Failed to plot graph for {selected_stock}: {str(e)}\n{traceback.format_exc()}"
            self.output.append(error_msg)
            print(error_msg)  # Print detailed error to console

    def save_predictions(self):
        if not self.all_predictions:
            QMessageBox.warning(self, 'No Predictions', 'There are no predictions to save.')
            return

        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getSaveFileName(
            self, "Save Predictions CSV", "", "CSV Files (*.csv);;All Files (*)", options=options
        )
        if file_name:
            try:
                # Prepare DataFrame
                combined_df = pd.DataFrame()
                for stock, models in self.all_predictions.items():
                    for model, forecast in models.items():
                        # Align dates with the specified end_date
                        # Assuming that 'end_date' is the same as the one used for plotting
                        end_date_str = self.end_date_input.text().strip()
                        if end_date_str:
                            end_date = pd.to_datetime(end_date_str)
                        else:
                            end_date = pd.to_datetime('today')
                        future_dates = pd.bdate_range(start=end_date + pd.Timedelta(days=1), periods=len(forecast))
                        df = pd.DataFrame({
                            'Stock': stock,
                            'Model': model,
                            'Date': future_dates,
                            'Predicted_Price': forecast
                        })
                        combined_df = pd.concat([combined_df, df], ignore_index=True)

                # Save to CSV
                combined_df.to_csv(file_name, index=False)
                QMessageBox.information(self, 'Success', f'Predictions saved to {file_name}')
            except Exception as e:
                import traceback
                error_msg = f'Failed to save CSV: {str(e)}\n{traceback.format_exc()}'
                self.output.append(error_msg)
                QMessageBox.warning(self, 'Error', f'Failed to save CSV: {str(e)}')

def main():
    app = QApplication(sys.argv)
    app.setStyle('Fusion')  # Optional: Improve the look and feel
    window = StockPredictionApp()
    window.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main(