# 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
# input parameters
inp_model_name = "model.test.onnx"
symbol="EURUSD"
inp_history_size =120
window_size = 120
learning_rate = 0.001
dropout_rate = 0.5
batch_size = 1024
layer_1 = 256
epochs = 1000
k_reg = 0.0001
patience = 20

if not mt5.initialize():
    print("initialize() failed, error code =",mt5.last_error())
    quit()

# we will save generated onnx-file near our script to use as resource
from sys import argv
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)

eurusd_rates = mt5.copy_rates_range(symbol, mt5.TIMEFRAME_H1, start_date, end_date)
print(eurusd_rates)

# create dataframe
df = pd.DataFrame(eurusd_rates)

# get close prices only
data = df.filter(['close']).values
data = pd.DataFrame(data)
print(data)
# 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']

# 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 = MinMaxScaler()
X_train_scaled = scaler.fit_transform(x_train)
X_test_scaled = scaler.transform(x_test)

scaler_y = MinMaxScaler()
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))

# define model
from keras.models import Sequential
from keras.layers import Dense, Activation, Conv1D, MaxPooling1D, Dropout, Flatten, LSTM, BatchNormalization
from keras.metrics import RootMeanSquaredError as rmse
from tensorflow.keras.regularizers import l2
from tensorflow.keras.optimizers import Adam

custom_optimizer = Adam(learning_rate=learning_rate)

model = Sequential()
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()])

# Set up early stopping
"""early_stopping = callbacks.EarlyStopping(
    min_delta=0.0001,
    patience=75,
    restore_best_weights=True,
)"""
# model training for 300 epochs
history = model.fit(x_train, y_train, epochs =epochs , validation_data = (x_test,y_test_scaled), batch_size=batch_size)#, callbacks=[early_stopping], verbose=2)

# evaluate training data
train_loss, train_rmse = model.evaluate(x_train,y_train, batch_size =batch_size)
print(f"train_loss={train_loss:.3f}")
print(f"train_rmse={train_rmse:.3f}")

# evaluate testing data
test_loss, test_rmse = model.evaluate(x_test,y_test_scaled, batch_size = batch_size)
print(f"test_loss={test_loss:.3f}")
print(f"test_rmse={test_rmse:.3f}")

# save model to ONNX
output_path = data_path+inp_model_name
onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path)
print(f"saved model to {output_path}")

X_predict = data.tail(len(x_features*0.2))[[0]]
X_predict_scaled = scaler.transform(X_predict)
predictions = model.predict(X_predict_scaled)

print("X_train shape: ", x_train.shape)
print("X_test shape: ", x_test.shape)
print("X_test scaled: ", X_test_scaled.shape)
print("y_test_scaled: ", y_test_scaled.shape)

# Calculate mean squared error
predictions = model.predict(X_test_scaled)

# Check the shape of predictions
print("Predictions shape:", predictions.shape)

# Reshape predictions if necessary
predictions = predictions.reshape(-1, 1)

# Verify input shapes
print("y_test_scaled shape:", y_test_scaled.shape)

# Calculate mean squared error
mse = mean_squared_error(y_test_scaled, predictions)
print("Mean Squared Error:", mse)

# Calculate and print mean absolute error
mae = mean_absolute_error(y_test_scaled, predictions)
print(f"\nMean Absolute Error: {mae}")

# Calculate and print R2 Score
r2 = r2_score(y_test_scaled, predictions)
print(f"\nR2 Score: {r2}")

# Calculate baseline predictions (e.g., mean of y_test_scaled)
baseline_predictions = np.full_like(y_test_scaled, np.mean(y_test_scaled))

# Calculate metrics for the baseline
baseline_mse = mean_squared_error(y_test_scaled, baseline_predictions)
baseline_mae = mean_absolute_error(y_test_scaled, baseline_predictions)
baseline_r2 = r2_score(y_test_scaled, baseline_predictions)

# Compare metrics
print("Baseline MSE:", baseline_mse)
print("Baseline MAE:", baseline_mae)
print("Baseline R2 Score:", baseline_r2)

mt5.shutdown()