# Copyright 2023, MetaQuotes Ltd.
# https://www.mql5.com

# python libraries
import MetaTrader5 as mt5
import tensorflow as tf
import numpy as np
import pandas as pd
import tf2onnx
from tensorflow.keras import layers, callbacks
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score 
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import matplotlib.pyplot as plt
from sklearn.model_selection import GridSearchCV
from openpyxl import *
from sys import argv
# input parameters

symbol="EURUSD"
days = 120
inp_history_size =days

if not mt5.initialize():
    print("initialize() failed, error code =",mt5.last_error())
    quit()

# we will save generated onnx-file near the our script
data_path=argv[0]
last_index=data_path.rfind("\\")+1
data_path=data_path[0:last_index]
print("data path to save onnx model",data_path)

# set start and end dates for history dat
from datetime import timedelta, datetime
#end_date = datetime.now()
end_date = datetime(2024, 1, 1, 0)
start_date = end_date - timedelta(days=inp_history_size)

# print start and end dates
print("data start date = ",start_date)
print("data end date = ",end_date)

# get rates
eurusd_rates = mt5.copy_rates_range(symbol, mt5.TIMEFRAME_H1, start_date, end_date)

# create dataframe
df = pd.DataFrame(eurusd_rates)

# get close prices only
data = df.filter(['close']).values
data = pd.DataFrame(data)
print(data)
inp_history_size = len(data)
print("inp_history_size",inp_history_size)
# Check columns in 'data'
print(data.columns)

# If 'Close' exists in columns, proceed with assignment
if 'Close' in data.columns:
    data['target'] = data['Close']
else:
    data['target'] = data.iloc[:, 0]


#####################################################################################################
    # Extract OHLC columns
x_features = data[[0]]
# Target variable
y_target = data['target']

######################################################################################################


#######################################################################################################################
from sklearn.base import BaseEstimator, RegressorMixin
from keras.models import Sequential
from keras.layers import Dense, BatchNormalization, Activation, Dropout
from keras.optimizers import Adam
from keras.regularizers import l2
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import cross_val_score
import numpy as np
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import uniform, randint
from sklearn.metrics import make_scorer, mean_squared_error, mean_absolute_error, r2_score
from sklearn.model_selection import train_test_split
from sklearn.base import BaseEstimator, RegressorMixin
from keras.models import Sequential
from keras.layers import Dense, BatchNormalization, Activation, Dropout
from keras.optimizers import Adam
from keras.regularizers import l2
from keras.callbacks import ReduceLROnPlateau, EarlyStopping
import numpy as np
import pandas as pd
from keras.metrics import RootMeanSquaredError as rmse

window_size = 120
learning_rate = 0.001
dropout_rate = 0.5
batch_size = 1024
layer_1 = 256
epochs = 1000
k_reg = 0.0001


def create_keras_model2(dropout_rate=0.0, k_reg=0.0, layer_1=0, learning_rate=0.0, window_size=0):
    model = Sequential()

    """window_size = 120
    learning_rate = 0.001
    dropout_rate = 0.3
    batch_size = 1024
    layer_1 = 256
    epochs = 1000
    k_reg = 0.001"""

    custom_optimizer = Adam(learning_rate=learning_rate)
    if dropout_rate > 0.0:

            model.add(Dense(layer_1, kernel_regularizer=l2(k_reg), input_shape=(window_size, 1)))
            model.add(BatchNormalization())
            model.add(Activation('relu'))
            model.add(Dropout(dropout_rate))

            model.add(Dense(512, kernel_regularizer=l2(k_reg)))
            model.add(BatchNormalization())
            model.add(Activation('relu'))
            model.add(Dropout(dropout_rate))

            model.add(Dense(1024, kernel_regularizer=l2(k_reg)))
            model.add(BatchNormalization())
            model.add(Activation('relu'))
            model.add(Dropout(dropout_rate))

            model.add(Dense(512, kernel_regularizer=l2(k_reg)))
            model.add(BatchNormalization())
            model.add(Activation('relu'))
            model.add(Dropout(dropout_rate))

            model.add(Dense(256, kernel_regularizer=l2(k_reg)))
            model.add(BatchNormalization())
            model.add(Activation('relu'))
            model.add(Dropout(dropout_rate))

            model.add(Dense(128, kernel_regularizer=l2(k_reg)))
            model.add(BatchNormalization())
            model.add(Activation('relu'))
            model.add(Dropout(dropout_rate))

            model.add(Dense(64, kernel_regularizer=l2(k_reg)))
            model.add(BatchNormalization())
            model.add(Activation('relu'))
            model.add(Dropout(dropout_rate))

            model.add(Dense(1, activation='linear'))
            model.add(BatchNormalization())
            model.compile(optimizer=custom_optimizer, loss='mse', metrics=[rmse()])
    return model

class KerasRegressorWrapper(BaseEstimator, RegressorMixin):
    def __init__(self, build_fn=create_keras_model2, batch_size=None, dropout_rate=None, epochs=1000, k_reg=0.99, learning_rate=0.99, layer_1=256, window_size=120):
        self.build_fn = build_fn
        self.batch_size = batch_size
        self.dropout_rate = dropout_rate
        self.epochs = epochs
        self.k_reg = k_reg
        self.learning_rate = learning_rate
        self.layer_1 = layer_1
        self.window_size = window_size
        self.model = self.build_fn(dropout_rate=self.dropout_rate, k_reg=self.k_reg, learning_rate=self.learning_rate, layer_1=self.layer_1, window_size=self.window_size)

    def fit(self, X, y, **kwargs):
        self.model.fit(X, y, epochs=self.epochs, **kwargs)
        return self

    def predict(self, X):
        # Ensure that the model is compiled before making predictions
        if not hasattr(self.model, '_is_compiled') or not self.model._is_compiled:
            raise RuntimeError("The model must be compiled before making predictions.")
        
        # Modify this part to handle 3D predictions correctly
        y_pred = self.model.predict(X)
        if len(y_pred.shape) == 3:
            y_pred = y_pred[:, -1, :]  # Assuming you want the last time step

        return y_pred


# Define the parameter grid for Random Search
param_dist = {
    'learning_rate': uniform(0.01, 0.1),#(0.0001, 0.1)
    'dropout_rate': uniform(0.3, 0.7),#(0.1, 0.8)
    'batch_size': randint(32, 1024),#(32, 1024)
    'layer_1': randint(200, 300),#(64, 500)
    'k_reg': uniform(0.002, 0.005),#(0.0001, 0.01)
    'epochs': randint(600, 900),#(50, 1000)
    'window_size': randint(120, 121)#(120, 121)
}


# Use the wrapper class in cross_val_score
keras_regressor = KerasRegressorWrapper(build_fn=create_keras_model2, batch_size=batch_size, dropout_rate=dropout_rate, epochs=epochs, k_reg=k_reg, learning_rate=learning_rate, layer_1=layer_1, window_size=window_size)



# Define R2 scorer
r2_scorer = make_scorer(r2_score)

# Perform Random Search
random_search = RandomizedSearchCV(
    keras_regressor,
    param_distributions=param_dist,
    n_iter=10,
    cv=5,
    scoring={'neg_mean_squared_error': 'neg_mean_squared_error', 'neg_mean_absolute_error': 'neg_mean_absolute_error', 'r2': r2_scorer},
    refit='neg_mean_squared_error',
    random_state=42,
    verbose=3
)

# Split the data into training and testing sets
x_train, x_test, y_train, y_test = train_test_split(x_features, y_target, test_size=0.2, shuffle=False)

# Standardize the features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(x_train)
X_test_scaled = scaler.transform(x_test)

scaler_y = StandardScaler()
y_train_scaled = scaler_y.fit_transform(np.array(y_train).reshape(-1, 1))
y_test_scaled = scaler_y.transform(np.array(y_test).reshape(-1, 1))


# Fit the Random Search to your data with batch_size
random_search.fit(X_train_scaled, y_train_scaled, batch_size=batch_size)  # Adjust batch_size as needed

# Print the best hyperparameters
print("Best Hyperparameters:", random_search.best_params_)

# Get the best model
best_model = random_search.best_estimator_

# Evaluate the best model
best_model.fit(X_train_scaled, y_train_scaled)
# Predict on the test set
y_pred_scaled = best_model.predict(X_test_scaled)

# Inverse transform the scaled predictions to the original scale
y_pred = scaler_y.inverse_transform(y_pred_scaled)

# Inverse transform the true labels to the original scale
y_true = scaler_y.inverse_transform(y_test_scaled)

# Calculate metrics
test_loss = mean_squared_error(y_true, y_pred)
test_rmse = mean_absolute_error(y_true, y_pred)
test_r2 = r2_score(y_true, y_pred)

print(f"Test Loss: {test_loss:.3f}")
print(f"Test RMSE: {test_rmse:.3f}")
print(f"Test R2: {test_r2:.3f}")
print(f"Best Model Test Loss: {test_loss:.3f}")
print(f"Best Model Test RMSE: {test_rmse:.3f}")

# Access other scores from the best model
best_model_scores = random_search.best_score_
print("Best Model Scores:", best_model_scores)

# Access other scores from the best model
best_model_scores = random_search.best_score_

print(type(best_model_scores))
print(best_model_scores)




# Extract the results
results_df = pd.DataFrame(random_search.cv_results_)
resultados_df = pd.DataFrame(random_search.cv_results_)
# Display the relevant columns in the results
relevant_columns = ['params', 'mean_test_neg_mean_squared_error', 'std_test_neg_mean_squared_error', 'rank_test_neg_mean_squared_error']
results_df = results_df[relevant_columns]

# Extract the results
results_df2 = pd.DataFrame(random_search.cv_results_)

# Convert 'params' column to string representation
results_df2['params'] = results_df2['params'].astype(str)

print(results_df2)
# Ruta al archivo Excel donde deseas guardar el DataFrame
ruta_excel = 'C:/Users/jsgas/OneDrive/Trading/Articulos/2. py a onnx/GRU vs LSTM/para el articulo/Resultados finales y finetuning/results_file.xlsx'
ruta_excel2 = 'C:/Users/jsgas/OneDrive/Trading/Articulos/2. py a onnx/GRU vs LSTM/para el articulo/Resultados finales y finetuning/resultados_file.xlsx'
# Guarda el DataFrame en un archivo Excel
results_df.to_excel(ruta_excel, index=False)
resultados_df.to_excel(ruta_excel2, index=False)
print(f'DataFrame guardado en {ruta_excel}')

cv_scores = -cross_val_score(keras_regressor, X_train_scaled, y_train_scaled, cv=5, scoring='neg_mean_squared_error')
print("cv scores: ", cv_scores)

print("Mean CV Score: ", np.mean(cv_scores))

