# Author:Zhang Yuan
from MyPackage import *
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import seaborn as sns
import statsmodels.api as sm
from scipy import stats

# ------------------------------------------------------------
__mypath__ = MyPath.MyClass_Path("")  # 路径类
mylogging = MyDefault.MyClass_Default_Logging(activate=False)  # 日志记录类，需要放在上面才行
myfile = MyFile.MyClass_File()  # 文件操作类
myword = MyFile.MyClass_Word()  # word生成类
myexcel = MyFile.MyClass_Excel()  # excel生成类
myini = MyFile.MyClass_INI()  # ini文件操作类
mytime = MyTime.MyClass_Time()  # 时间类
myparallel = MyTools.MyClass_ParallelCal()  # 并行运算类
myplt = MyPlot.MyClass_Plot()  # 直接绘图类(单个图窗)
mypltpro = MyPlot.MyClass_PlotPro()  # Plot高级图系列
myfig = MyPlot.MyClass_Figure(AddFigure=False)  # 对象式绘图类(可多个图窗)
myfigpro = MyPlot.MyClass_FigurePro(AddFigure=False)  # Figure高级图系列
myplthtml = MyPlot.MyClass_PlotHTML()  # 画可以交互的html格式的图
mypltly = MyPlot.MyClass_Plotly()  # plotly画图相关
mynp = MyArray.MyClass_NumPy()  # 多维数组类(整合Numpy)
mypd = MyArray.MyClass_Pandas()  # 矩阵数组类(整合Pandas)
mypdpro = MyArray.MyClass_PandasPro()  # 高级矩阵数组类
myDA = MyDataAnalysis.MyClass_DataAnalysis()  # 数据分析类
myDefault = MyDefault.MyClass_Default_Matplotlib()  # 画图恢复默认设置类
# myMql = MyMql.MyClass_MqlBackups() # Mql备份类
# myBaidu = MyWebCrawler.MyClass_BaiduPan() # Baidu网盘交互类
# myImage = MyImage.MyClass_ImageProcess()  # 图片处理类
myBT = MyBackTest.MyClass_BackTestEvent()  # 事件驱动型回测类
myBTV = MyBackTest.MyClass_BackTestVector()  # 向量型回测类
myML = MyMachineLearning.MyClass_MachineLearning()  # 机器学习综合类
mySQL = MyDataBase.MyClass_MySQL(connect=False)  # MySQL类
mySQLAPP = MyDataBase.MyClass_SQL_APPIntegration()  # 数据库应用整合
myWebQD = MyWebCrawler.MyClass_QuotesDownload(tushare=False)  # 金融行情下载类
myWebR = MyWebCrawler.MyClass_Requests()  # Requests爬虫类
myWebS = MyWebCrawler.MyClass_Selenium(openChrome=False)  # Selenium模拟浏览器类
myWebAPP = MyWebCrawler.MyClass_Web_APPIntegration()  # 爬虫整合应用类
myEmail = MyWebCrawler.MyClass_Email()  # 邮箱交互类
myReportA = MyQuant.MyClass_ReportAnalysis()  # 研报分析类
myFactorD = MyQuant.MyClass_Factor_Detection()  # 因子检测类
myKeras = MyDeepLearning.MyClass_tfKeras()  # tfKeras综合类
myTensor = MyDeepLearning.MyClass_TensorFlow()  # Tensorflow综合类
myMT5 = MyMql.MyClass_ConnectMT5(connect=False)  # Python链接MetaTrader5客户端类
myMT5Pro = MyMql.MyClass_ConnectMT5Pro(connect=False)  # Python链接MT5高级类
myMT5Indi = MyMql.MyClass_MT5Indicator()  # MT5指标Python版
myMT5Report = MyMT5Report.MyClass_StratTestReport(AddFigure=False)  # MT5策略报告类
myMT5Analy = MyMT5Analysis.MyClass_ForwardAnalysis()  # MT5分析类
myMT5Lots_Fix = MyMql.MyClass_Lots_FixedLever(connect=False)  # 固定杠杆仓位类
myMT5Lots_Dy = MyMql.MyClass_Lots_DyLever(connect=False)  # 浮动杠杆仓位类
myMT5run = MyMql.MyClass_RunningMT5()  # Python运行MT5
myMT5code = MyMql.MyClass_CodeMql5()  # Python生成MT5代码
myMoneyM = MyTrade.MyClass_MoneyManage()  # 资金管理类
myDefault.set_backend_default("Pycharm")  # Pycharm下需要plt.show()才显示图
# ------------------------------------------------------------
# Jupyter Notebook 控制台显示必须加上：%matplotlib inline ，弹出窗显示必须加上：%matplotlib auto
# %matplotlib inline
# import warnings
# warnings.filterwarnings('ignore')

# %%
# 简介
# A CNN-LSTM-Based Model to Forecast Stock Prices (Wenjie Lu, Jiazheng Li, Yifan Li, Aijun Sun, Jingyang Wang, Complexity magazine, vol. 2020, Article ID 6622927, 10 pages, 2020) 一文的作者比较了各种股票价格预测模型：

# 股票价格数据具有时间序列的特点。
# 同时，基于机器学习长短期记忆（LSTM）具有通过记忆功能分析时间序列数据之间关系的优势，我们提出了一种基于CNN-LSTM的股票价格预测方法。
# 同时，我们使用MLP、CNN、RNN、LSTM、CNN-RNN等预测模型逐一对股票价格进行了预测。此外，还对这些模型的预测结果进行了分析和比较。
# 本研究利用的数据涉及1991年7月1日至2020年8月31日的每日股票价格，包括7127个交易日。
# 在历史数据方面，我们选择了八个特征，包括开盘价、最高价、最低价、收盘价、成交量、成交额、涨跌幅和变化。
# 首先，我们采用CNN从数据中有效提取特征，即前10天的项目。然后，我们采用LSTM，用提取的特征数据来预测股票价格。
# 根据实验结果，CNN-LSTM可以提供可靠的股票价格预测，并且预测精度最高。
# 这种预测方法不仅为股票价格预测提供了一种新的研究思路，也为学者们研究金融时间序列数据提供了实践经验。

# 在所有考虑的模型中，CNN-LSTM模型在实验中产生了最好的结果。在这篇文章中，我们将考虑如何创建这样一个模型来预测金融时间序列，以及如何在MQL5专家顾问中使用创建的ONNX模型。

#%%
#Python libraries
import matplotlib.pyplot as plt
import MetaTrader5 as mt5
import tensorflow as tf
import numpy as np
import pandas as pd
import tf2onnx
from sklearn.model_selection import train_test_split
from sys import argv

#check tensorflow version
print(tf.__version__)
#check GPU support
print(len(tf.config.list_physical_devices('GPU'))>0)
#initialize MetaTrader5 for history data
if not mt5.initialize():
    print("initialize() failed, error code =",mt5.last_error())
    quit()

#show terminal info
terminal_info=mt5.terminal_info()
print(terminal_info)
#show file path
file_path=terminal_info.data_path+"\\MQL5\\Files\\"
print(file_path)
#data path to save the model
# data_path=argv[0]
# last_index=data_path.rfind("\\")+1
# data_path=data_path[0:last_index]
data_path = __mypath__.get_desktop_path() + "\\"
print("data path to save onnx model",data_path)

#set start and end dates for history data
from datetime import timedelta,datetime
end_date = datetime.now()
start_date = end_date - timedelta(days=120)

#print start and end dates
print("data start date=",start_date)
print("data end date=",end_date)
#get EURUSD rates (H1) from start_date to end_date
eurusd_rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, start_date, end_date)
#create dataframe
df = pd.DataFrame(eurusd_rates)
df.head()
df.shape

#prepare close prices only
data = df.filter(['close']).values

#show close prices
plt.figure(figsize = (18,10))
plt.plot(data,'b',label = 'Original')
plt.xlabel("Hours")
plt.ylabel("Price")
plt.title("EURUSD_H1")
plt.legend()
plt.show()

#%%
#scale data using MinMaxScaler
from sklearn.preprocessing import MinMaxScaler
scaler=MinMaxScaler(feature_range=(0,1))
scaled_data = scaler.fit_transform(data)

#training size is 80% of the data
training_size = int(len(scaled_data)*0.80)
print("training size:",training_size)

#create train data and check size
train_data_initial = scaled_data[0:training_size,:]
print(len(train_data_initial))

#create test data and check size
test_data_initial= scaled_data[training_size:,:1]
print(len(test_data_initial))

#split a univariate sequence into samples
def split_sequence(sequence, n_steps):
    X, y = list(), list()
    for i in range(len(sequence)):
       #find the end of this pattern
       end_ix = i + n_steps
       #check if we are beyond the sequence
       if end_ix > len(sequence)-1:
          break
       #gather input and output parts of the pattern
       seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
       X.append(seq_x)
       y.append(seq_y)
    return np.array(X), np.array(y)

#split into samples
time_step = 120
x_train, y_train = split_sequence(train_data_initial, time_step)
x_test, y_test = split_sequence(test_data_initial, time_step)
#reshape input to be [samples, time steps, features] which is required for LSTM
x_train =x_train.reshape(x_train.shape[0],x_train.shape[1],1)
x_test = x_test.reshape(x_test.shape[0],x_test.shape[1],1)

#%%
#import keras libraries for the model
import math
from keras.models import Sequential
from keras.layers import Dense,Activation,Conv1D,MaxPooling1D,Dropout
from keras.layers import LSTM
from keras.utils.vis_utils import plot_model
from keras.metrics import RootMeanSquaredError as rmse
from keras import optimizers

#define the model
model = Sequential()
model.add(Conv1D(filters=256, kernel_size=2,activation='relu',padding = 'same',input_shape=(120,1)))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(100, return_sequences = True))
model.add(Dropout(0.3))
model.add(LSTM(100, return_sequences = False))
model.add(Dropout(0.3))
model.add(Dense(units=1, activation = 'sigmoid'))
model.compile(optimizer='adam', loss= 'mse' , metrics = [rmse()])

#show model
model.summary()

#measure time
import time
time_calc_start = time.time()

#fit model with 300 epochs
history=model.fit(x_train,y_train,epochs=300,validation_data=(x_test,y_test),batch_size=32,verbose=1)

#calculate time
fit_time_seconds = time.time() - time_calc_start
print("fit time =",fit_time_seconds," seconds.")

#show training history keys
history.history.keys()

#show iteration-loss graph for training and validation
plt.figure(figsize = (18,10))
plt.plot(history.history['loss'],label='Training Loss',color='b')
plt.plot(history.history['val_loss'],label='Validation-loss',color='g')
plt.xlabel("Iteration")
plt.ylabel("Loss")
plt.title("LOSS")
plt.legend()
plt.show()

#show iteration-rmse graph for training and validation
plt.figure(figsize = (18,10))
plt.plot(history.history['root_mean_squared_error'],label='Training RMSE',color='b')
plt.plot(history.history['val_root_mean_squared_error'],label='Validation-RMSE',color='g')
plt.xlabel("Iteration")
plt.ylabel("RMSE")
plt.title("RMSE")
plt.legend()
plt.show()

#evaluate training data
model.evaluate(x_train, y_train, batch_size = 32)
#evaluate testing data
model.evaluate(x_test, y_test, batch_size = 32)
#prediction using training data
train_predict = model.predict(x_train)
plot_y_train = y_train.reshape(-1,1)

#show actual vs predicted (training) graph
plt.figure(figsize=(18,10))
plt.plot(scaler.inverse_transform(plot_y_train),color = 'b', label = 'Original')
plt.plot(scaler.inverse_transform(train_predict),color='red', label = 'Predicted')
plt.title("Prediction Graph Using Training Data")
plt.xlabel("Hours")
plt.ylabel("Price")
plt.legend()
plt.show()

#prediction using testing data
test_predict = model.predict(x_test)
plot_y_test = y_test.reshape(-1,1)

#%%
# 为了计算度量，我们需要将数据从区间[0,1]转换过来。同样，我们使用MinMaxScaler。
#calculate metrics
from sklearn import metrics
from sklearn.metrics import r2_score
#transform data to real values
value1=scaler.inverse_transform(plot_y_test)
value2=scaler.inverse_transform(test_predict)
#calc score
score = np.sqrt(metrics.mean_squared_error(value1,value2))
print("RMSE         : {}".format(score))
print("MSE          :", metrics.mean_squared_error(value1,value2))
print("R2 score     :",metrics.r2_score(value1,value2))

#show actual vs predicted (testing) graph
plt.figure(figsize=(18,10))
plt.plot(scaler.inverse_transform(plot_y_test),color = 'b',  label = 'Original')
plt.plot(scaler.inverse_transform(test_predict),color='g', label = 'Predicted')
plt.title("Prediction Graph Using Testing Data")
plt.xlabel("Hours")
plt.ylabel("Price")
plt.legend()
plt.show()

# save model to ONNX
output_path = data_path+"model.eurusd.H1.120.onnx"
onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path)
print(f"model saved to {output_path}")
# 保存到MQL5的Files中
output_path = file_path+"model.eurusd.H1.120.onnx"
onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path)
print(f"saved model to {output_path}")

# finish
mt5.shutdown()
# Python脚本的完整代码附在文章的Jupyter笔记本中。
# 在《基于CNN-LSTM的模型预测股票价格》一文中，采用CNN-LSTM架构的模型获得了R^2=0.9646的最佳结果。在我们的例子中，CNN-LSTM网络产生的最佳结果是R^2=0.9684。根据这些结果，这种类型的模型在解决预测问题时可以很有效率。
# 我们考虑了一个Python脚本的例子，它建立和训练CNN-LSTM模型来预测金融时间序列。

#%% Using the Model in MetaTrader 5
# 2.1. 在你开始之前要知道的好事
# 有两种方法来创建一个模型： 你可以使用OnnxCreate从onnx文件创建模型，或者使用OnnxCreateFromBuffer从数据阵列创建模型。
# 如果ONNX模型被用作EA中的资源，你需要在每次改变模型时重新编译EA。
# 并非所有模型都有完全定义的尺寸输入和/或输出张量。这通常是负责包尺寸的第一个维度。在运行一个模型之前，你必须使用OnnxSetInputShape和OnnxSetOutputShape函数明确指定尺寸。模型的输入数据应该以训练模型时的相同方式准备。
# 对于输入和输出数据，我们建议使用模型中使用的相同类型的数组、矩阵和/或向量。在这种情况下，你将不必在运行模型时转换数据。如果数据不能以所需类型表示，数据将被自动转换。
# 使用OnnxRun来推理（运行）你的模型。请注意，一个模型可以被多次运行。使用模型后，使用 OnnxRelease 函数释放它。

# 2.2. 读取onnx文件并获得输入和输出的信息
# 为了使用我们的模型，我们需要知道模型的位置、输入数据类型和形状，以及输出数据类型和形状。根据之前创建的脚本，model.eurusd.H1.120.onnx与生成onnx文件的Python脚本位于同一个文件夹中。输入是float32，120个归一化的收盘价（用于在批量大小等于1的情况下工作）；输出是float32，这是一个由模型预测的归一化价格。
# 我们还在MQL5\Files文件夹中创建了onnx文件，以便使用MQL5脚本获得模型输入和输出数据。



# 参见MT5











