In the following code, make necessary changes to :
save the models after training them,
give an option at the start iotself, whether you wish to 'Train' the model or 'Use' the model ..
option to select which model to use, showing their respective efficiency, accuracy and available parameters,
Also tell me over how muvh number of stocks should we train the models. Does training with more number of stocks, makes the model more efficient ? should we limit  the stock data to a few or 500 or more than thousand. and  should we take data for 5 years or 10 years ? will it take longer to train the models and would it be beneficial ?
make it predict as which stocks out of the list be profitable . Also it may tell how much percent is it expected to grow in a given time horizon , or we may ask it which stocks are expected to grow this much percent in this much period of time. make it flexible to be asked any of the way, means problem may be changed in the manner it is asked. 
Also include more indicators like Bollinger bands, MACD, Stochastic , or any other you think might be suitable.
Also do not change the way we are importing models , layers and optimizers, since this is the only way we find them working with my computer.
Also there must be an audible signal or beep or something like that which marks the beginning and end of the training, or program execution. Better if we can have any visual progress bar since it takes a lot of time to train the models  but certainly  not compromising the speed of the program. 
All this can be made in the form of a beautiful interactive and colorful modern user interface using PyQT5 or any library like that ?
Here is the code :
import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt
import datetime

from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold
from sklearn.metrics import (
    classification_report, roc_auc_score, roc_curve, precision_recall_curve,
    average_precision_score, confusion_matrix, f1_score
)
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

import xgboost as xgb

from imblearn.over_sampling import SMOTE

# Import TensorFlow and Keras if using neural networks
#import tensorflow as tf
#from tensorflow import keras
#from keras.layers import Dense
#from keras.optimizers import Adam
#from keras.models import Sequential

import tensorflow as tf
from tensorflow import keras

from keras.layers import Dense
from keras.optimizers import Adam
from keras.models import Sequential


# Alternatively, if you prefer scikeras:
#from tensorflow.keras.models import Sequential
#from tensorflow.keras.layers import Dense
#from tensorflow.keras.optimizers import Adam

def transform_stock_name(name):
    # Remove the prefix 'NSE:' or 'BSE:' and add appropriate suffix
    if 'NSE:' in name:
        name = name.split('NSE:')[1]
        if '-' in name:
            name = name.split('-')[0]
        name += '.NS'
    elif 'BSE:' in name:
        name = name.split('BSE:')[1]
        name += '.BO'
    else:
        # Assume it's a ticker symbol and append '.NS' (adjust as needed)
        name += '.NS'
    return name

def fetch_stock_data(stock_list, start_date, end_date):
    stock_data = {}
    for stock in stock_list:
        try:
            df = yf.download(stock, start=start_date, end=end_date)
            if not df.empty:
                stock_data[stock] = df
            else:
                print(f"No data for {stock}")
        except Exception as e:
            print(f"Error downloading {stock}: {e}")
    return stock_data

def calculate_indicators(df):
    df = df.copy()
    # Simple Moving Averages
    df['SMA_5'] = df['Close'].rolling(window=5).mean()
    df['SMA_10'] = df['Close'].rolling(window=10).mean()
    df['SMA_20'] = df['Close'].rolling(window=20).mean()
    # Exponential Moving Averages
    df['EMA_5'] = df['Close'].ewm(span=5, adjust=False).mean()
    df['EMA_10'] = df['Close'].ewm(span=10, adjust=False).mean()
    # Relative Strength Index (RSI)
    delta = df['Close'].diff()
    gain = (delta.where(delta > 0, 0)).fillna(0)
    loss = (-delta.where(delta < 0, 0)).fillna(0)
    average_gain = gain.rolling(window=14).mean()
    average_loss = loss.rolling(window=14).mean()
    rs = average_gain / average_loss
    df['RSI'] = 100 - (100 / (1 + rs))
    # Fill any remaining NaN values
    df.fillna(method='bfill', inplace=True)
    return df

def prepare_dataset(stock_data):
    df_list = []
    for symbol, data in stock_data.items():
        data = data.copy()
        data['Symbol'] = symbol
        data.reset_index(inplace=True)
        df_list.append(data)
    dataset = pd.concat(df_list, ignore_index=True)
    return dataset

#def define_target_variable(dataset, prediction_horizon=5, return_threshold=0.02):
def define_target_variable(dataset, prediction_horizon=15, return_threshold=0.05):
    dataset = dataset.sort_values(['Symbol', 'Date'])
    dataset['Future_Close'] = dataset.groupby('Symbol')['Close'].shift(-prediction_horizon)
    dataset['Future_Return'] = (dataset['Future_Close'] - dataset['Close']) / dataset['Close']
    dataset['Target'] = np.where(dataset['Future_Return'] > return_threshold, 1, 0)
    # Drop rows with NaN values in Future_Close (the last 'n' rows for each symbol)
    dataset = dataset.dropna(subset=['Future_Close'])
    return dataset

def handle_missing_values(dataset):
    # Fill NaNs with forward/backward fill
    dataset.fillna(method='ffill', inplace=True)
    dataset.fillna(method='bfill', inplace=True)
    return dataset

def encode_categorical_variables(dataset):
    le = LabelEncoder()
    dataset['Symbol_Encoded'] = le.fit_transform(dataset['Symbol'])
    return dataset, le

def select_features(dataset):
    features = dataset.drop(columns=['Date', 'Symbol', 'Future_Close', 'Future_Return', 'Target'])
    target = dataset['Target']
    return features, target

def scale_features(features):
    scaler = StandardScaler()
    features_scaled = scaler.fit_transform(features)
    feature_names = features.columns
    return features_scaled, scaler, feature_names

def split_dataset(features, target):
    X_train, X_test, y_train, y_test = train_test_split(
        features, target, test_size=0.2, random_state=42, stratify=target
    )
    return X_train, X_test, y_train, y_test

def balance_dataset(X_train, y_train):
    smote = SMOTE(random_state=42)
    X_train_balanced, y_train_balanced = smote.fit_resample(X_train, y_train)
    return X_train_balanced, y_train_balanced

def tune_random_forest(X_train, y_train):
    param_grid = {
        'n_estimators': [100, 200],
        'max_depth': [None, 5, 10],
        'min_samples_split': [2, 5],
    }
    rf = RandomForestClassifier(random_state=42)
    grid_search = GridSearchCV(
        estimator=rf, param_grid=param_grid, cv=3, scoring='f1', n_jobs=-1
    )
    grid_search.fit(X_train, y_train)
    print("Best parameters for Random Forest:", grid_search.best_params_)
    best_rf = grid_search.best_estimator_
    return best_rf

def tune_xgboost(X_train, y_train):
    param_grid = {
        'n_estimators': [100, 200],
        'max_depth': [3, 5, 7],
        'learning_rate': [0.01, 0.1],
    }
    xgb_clf = xgb.XGBClassifier(use_label_encoder=False, eval_metric='logloss')
    grid_search = GridSearchCV(
        estimator=xgb_clf, param_grid=param_grid, cv=3, scoring='f1', n_jobs=-1
    )
    grid_search.fit(X_train, y_train)
    print("Best parameters for XGBoost:", grid_search.best_params_)
    best_xgb = grid_search.best_estimator_
    return best_xgb

def train_neural_network(X_train, y_train):
    from scikeras.wrappers import KerasClassifier

    def create_nn_model():
        model = Sequential()
        model.add(Dense(64, input_dim=X_train.shape[1], activation='relu'))
        model.add(Dense(32, activation='relu'))
        model.add(Dense(1, activation='sigmoid'))
        optimizer = Adam(learning_rate=0.001)
        model.compile(
            loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy']
        )
        return model
    nn_model = KerasClassifier(model=create_nn_model, epochs=10, batch_size=32, verbose=0)
    nn_model.fit(X_train, y_train)
    return nn_model

def train_logistic_regression(X_train, y_train):
    lr_classifier = LogisticRegression(random_state=42, max_iter=1000)
    lr_classifier.fit(X_train, y_train)
    return lr_classifier

def train_svm(X_train, y_train):
    svm_classifier = SVC(probability=True, random_state=42)
    svm_classifier.fit(X_train, y_train)
    return svm_classifier

def plot_feature_importance(model, feature_names):
    if hasattr(model, 'feature_importances_'):
        importances = model.feature_importances_
        indices = np.argsort(importances)[::-1]

        plt.figure(figsize=(12, 6))
        plt.title("Feature Importance")
        plt.bar(range(len(importances)), importances[indices], align='center')
        plt.xticks(
            range(len(importances)), [feature_names[i] for i in indices],
            rotation=90
        )
        plt.tight_layout()
        plt.show()
    else:
        print("Model does not have feature_importances_ attribute")


#####################################################################################################################################

def main_machine_learning():
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.metrics import (precision_recall_curve, average_precision_score,
                                 roc_curve, roc_auc_score, classification_report)
    from sklearn.model_selection import cross_val_score, StratifiedKFold

    # Prompt user for input stocks and prediction horizon
    user_stock_symbols = input("Enter the stock symbols separated by commas (e.g., AAPL, GOOGL, MSFT): ")
    user_stock_list = [symbol.strip() for symbol in user_stock_symbols.split(',')]

    # Transform stock names to Yahoo Finance format
    stock_list = []
    for stock in user_stock_list:
        new_name = transform_stock_name(stock)
        stock_list.append(new_name)

    # Prompt user for prediction horizon
    prediction_horizon_input = input("Enter the prediction horizon in days (e.g., 5): ")
    try:
        prediction_horizon = int(prediction_horizon_input)
    except ValueError:
        print("Invalid prediction horizon input. Using default value of 5 days.")
        prediction_horizon = 5

    # Fetch and prepare data
    end_date = datetime.datetime.now()
    start_date = end_date - datetime.timedelta(days=5*365)  # Last 5 years

    # Fetch data
    stock_data = fetch_stock_data(stock_list, start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d'))

    # Check if any data was fetched
    if not stock_data:
        print("No stock data fetched. Please check your stock symbols and internet connection.")
        return

    # Calculate indicators
    for symbol in stock_data:
        stock_data[symbol] = calculate_indicators(stock_data[symbol])

    # Prepare dataset
    dataset = prepare_dataset(stock_data)
    dataset = define_target_variable(dataset, prediction_horizon=prediction_horizon)
    dataset = handle_missing_values(dataset)

    # Encode categorical variables
    dataset, le = encode_categorical_variables(dataset)
    features, target = select_features(dataset)

    # Add feature names for plotting
    feature_names = features.columns

    # Scale features
    features_scaled, scaler, feature_names = scale_features(features)

    # Split dataset
    X_train, X_test, y_train, y_test = split_dataset(features_scaled, target)

    # Balance training data
    X_train_balanced, y_train_balanced = balance_dataset(X_train, y_train)

    # Hyperparameter tuning and train models
    rf_model = tune_random_forest(X_train_balanced, y_train_balanced)
    xgb_model = tune_xgboost(X_train_balanced, y_train_balanced)
    nn_model = train_neural_network(X_train_balanced, y_train_balanced)
    lr_model = train_logistic_regression(X_train_balanced, y_train_balanced)
    svm_model = train_svm(X_train_balanced, y_train_balanced)

    # Evaluate models
    models = {
        'Random Forest': rf_model,
        'XGBoost': xgb_model,
        'Neural Network': nn_model,
        'Logistic Regression': lr_model,
        'SVM': svm_model
    }

    optimal_thresholds = {}  # Dictionary to store optimal thresholds

    for model_name, model in models.items():
        print(f"\n{model_name} Model Evaluation:")

        # Predictions and Scores
        if model_name == 'Neural Network':
            y_scores = model.predict_proba(X_test)
            print("y_scores shape:", y_scores.shape)
            # Handle different possible shapes of y_scores
            if y_scores.ndim == 2 and y_scores.shape[1] == 2:
                # If output is (n_samples, 2), take the probability of class 1
                y_scores = y_scores[:, 1]
            elif y_scores.ndim == 2 and y_scores.shape[1] == 1:
                # If output is (n_samples, 1), flatten to (n_samples,)
                y_scores = y_scores.ravel()
            elif y_scores.ndim == 1:
                # If output is already (n_samples,), no action needed
                pass
            else:
                print("Unexpected y_scores shape:", y_scores.shape)
                continue  # Skip to next model
        else:
            if hasattr(model, 'predict_proba'):
                y_scores = model.predict_proba(X_test)[:, 1]
            else:
                y_scores = model.decision_function(X_test)

        # Default threshold at 0.5
        y_pred_default = (y_scores >= 0.5).astype(int)
        print("Classification Report with default threshold (0.5):")
        print(classification_report(y_test, y_pred_default))

        # ROC Curve and AUC
        fpr, tpr, thresholds = roc_curve(y_test, y_scores)
        auc = roc_auc_score(y_test, y_scores)
        plt.figure()
        plt.plot(fpr, tpr, label=f'{model_name} ROC Curve (AUC = {auc:.2f})')
        plt.plot([0, 1], [0, 1], linestyle='--', label='Random Chance')
        plt.title(f'{model_name} ROC Curve')
        plt.xlabel('False Positive Rate')
        plt.ylabel('True Positive Rate')
        plt.legend()
        plt.show()

        # Find Optimal Threshold
        J = tpr - fpr  # Youden's J statistic
        ix = np.argmax(J)
        optimal_threshold = thresholds[ix]
        optimal_thresholds[model_name] = optimal_threshold  # Save for later use
        print(f"Optimal Threshold for {model_name}: {optimal_threshold:.2f}")

        # Use Optimal Threshold
        y_pred_optimal = (y_scores >= optimal_threshold).astype(int)
        print(f"Classification Report with Optimal Threshold ({optimal_threshold:.2f}):")
        print(classification_report(y_test, y_pred_optimal))

        # Precision-Recall Curve
        precision, recall, pr_thresholds = precision_recall_curve(y_test, y_scores)
        ap = average_precision_score(y_test, y_scores)
        plt.figure()
        plt.plot(recall, precision, label=f'{model_name} PR Curve (AP = {ap:.2f})')
        plt.title(f'{model_name} Precision-Recall Curve')
        plt.xlabel('Recall')
        plt.ylabel('Precision')
        plt.legend()
        plt.show()

        # Cross-validation for ROC AUC
        print(f"Performing cross-validation for {model_name}...")
        cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
        if model_name == 'Neural Network':
            # Cross-validation with KerasClassifier
            auc_scores = cross_val_score(model, X_train_balanced, y_train_balanced, cv=cv, scoring='roc_auc', n_jobs=-1)
        else:
            auc_scores = cross_val_score(model, X_train_balanced, y_train_balanced, cv=cv, scoring='roc_auc')

        print(f"Cross-validated AUC scores for {model_name}: {auc_scores}")
        print(f"Mean AUC: {auc_scores.mean():.2f}")

        if model_name in ['Random Forest', 'XGBoost']:
            plot_feature_importance(model, feature_names)

    # Choose the best model (Let's assume Random Forest for this example)
    best_model = rf_model
    optimal_threshold_best_model = optimal_thresholds.get('Random Forest', 0.5)

    # Fetch recent data for prediction
    recent_end_date = datetime.datetime.now()
    recent_start_date = recent_end_date - datetime.timedelta(days=60)
    recent_stock_data = fetch_stock_data(
        stock_list, recent_start_date.strftime('%Y-%m-%d'), recent_end_date.strftime('%Y-%m-%d')
    )

    if not recent_stock_data:
        print("No recent stock data fetched. Please check your stock symbols and internet connection.")
        return

    # Prepare recent data
    recent_dataset = prepare_dataset(recent_stock_data)
    recent_dataset = handle_missing_values(recent_dataset)
    recent_dataset['Symbol_Encoded'] = le.transform(recent_dataset['Symbol'])
    recent_features = recent_dataset.drop(columns=['Date', 'Symbol'])

    # Ensure recent_features has the same columns as during training
    # Add missing columns with default values
    missing_cols = set(feature_names) - set(recent_features.columns)
    for col in missing_cols:
        recent_features[col] = 0

    # Remove any extra columns not in original features
    extra_cols = set(recent_features.columns) - set(feature_names)
    recent_features.drop(columns=extra_cols, inplace=True)

    # Reorder columns to match the original order
    recent_features = recent_features[feature_names]

    # Now transform
    recent_features_scaled = scaler.transform(recent_features)

    # Predict probabilities using the best model
    if hasattr(best_model, 'predict_proba'):
        predictions = best_model.predict_proba(recent_features_scaled)[:,1]
    else:
        predictions = best_model.decision_function(recent_features_scaled)

    recent_dataset['Prediction_Prob'] = predictions

    # Use optimal threshold
    recent_dataset['Prediction'] = (recent_dataset['Prediction_Prob'] >= optimal_threshold_best_model).astype(int)

    # Get latest date's predictions
    idx = recent_dataset.groupby('Symbol')['Date'].idxmax()
    latest_predictions = recent_dataset.loc[idx]

    # Filter stocks predicted to be profitable
    predicted_profitable_stocks = latest_predictions[latest_predictions['Prediction'] == 1]

    print("\nStocks predicted to be profitable in the next {} days:".format(prediction_horizon))
    print(predicted_profitable_stocks[['Symbol', 'Date', 'Close', 'Prediction_Prob']])






#####################################################################################################################################        


if __name__ == "__main__":
    main_machine_learning()