
Trianguläre Arbitrage mit Vorhersagen
Einführung
In diesem Artikel geht es um die Strategie der Dreiecksarbitrage (Triangular Arbitrage). Er enthält ein Beispiel für zwei Dreiecksarbitragen, und zwar mit Deep-Learning-Modellen, die zu den Downloads hinzugefügt wurden, und einem EA, der ebenfalls zu den Downloads hinzugefügt wurde. Bei der Dreiecksarbitrage werden Wechselkursunterschiede genutzt, um risikolose Gewinne zu erzielen.
Was ist Dreiecksarbitrage?
Arbitrage ist sehr merkwürdig, sie wurde von den Buchmachern der Sportwetten verboten. Stellen Sie sich vor, Sie haben eine Gewinnwahrscheinlichkeit von 1,25 für Real Madrid, um die Meisterschaft 2024 zu gewinnen, und Borussia Dortmund hat eine Quote von 3,60, d. h. Madrid hat eine Wahrscheinlichkeit von 100/1,25 = 80 % und Borussia eine Wahrscheinlichkeit von 27,7 %, um zu gewinnen. Wenn Sie diese beiden addieren, kommen Sie auf 107,7 %, denn Buchmacher wollen Geld gewinnen, und die über 100 % sind ihre Provision. Aber stellen Sie sich vor, Sie finden Buchmacher Nummer 2 und der bietet Quoten für Borussia von 19 % Gewinnwahrscheinlichkeit, also 5,26. Dann könntest du bei Buchmacher Nummer 1 auf Real Madrid und bei Buchmacher Nummer 2 auf Borussia setzen, und wenn du die entsprechende Menge auf jede Mannschaft setzt, wirst du mit dem Spiel Geld gewinnen, weil beide sich auf weniger als 100 % addieren. Dies ist ein einfacher Weg zu erklären, warum es bei Sportwetten verboten ist und was Arbitrage ist.
Stellen Sie sich vor, Sie sind eine „legale“ Person und wollen Ihr Sportkonto nicht schließen lassen, indem Sie Arbitrage betreiben. Sie wissen, dass Sie, selbst wenn Sie auf Madrid wetten, „legale“ Arbitrage betreiben könnten, wenn Sie auf Minute 70' des Spiels warten, wenn Sie unentschieden spielen oder warten, bis Real Madrid ein Tor schießt, um diese Quoten für Borussia zu haben und einen Sieg zu erzielen.... Das scheint ein bisschen riskant zu sein, aber hier können wir den Vorteil von Deep Learning nutzen, wir wissen, dass Real Madrid ein Tor schießen wird, also werden Sie diese Quoten mit einer Wahrscheinlichkeit von 98 % haben (wir wissen dies durch Kointegration zwischen den Vorhersagen und den realen Werten). Das ist das Neue an Deep Learning und Arbitrage.
Nun, da wir wissen, was Arbitrage ist und wie wir mit Hilfe von Deep Learning mehr gewinnen können. Was ist Dreiecksarbitrage? Nun, es ist das Gleiche wie Arbitrage, aber mit drei Paaren. Weil Forex und Cryptos diese Formel für ein Symbol A / B verwenden, und wenn man das lösen muss, braucht man drei Gleichungen (A / B ) * (B/C) * (C/A). Also wenn das >1 ist, multipliziert man in die rechte Richtung, und wenn <1 kann man in die linke Richtung multiplizieren.
Warum kann oder kann nicht mit allen Konten Dreiecksarbitrage betrieben werden?
Wenn Sie ein Null-Spread-Konto haben, wäre die Dreiecksarbitrage in einer Sekunde oder weniger erledigt. Wenn Sie eine Spanne haben, ist es unmöglich, die Spanne in solchen Zeiten zu schlagen. Aber, wie ich schon sagte, keine Sorge, dieser EA ist wirklich profitabel in beide Richtungen. Mein Konto hat keinen Spread gleich Null, sodass dieser Artikel ein Beispiel mit Spreads zeigt.
Was brauchen wir für diesen EA?
Dieser EA verwendet Vorhersagen, die in Python zu ONNX Modellen gemacht werden, um sie in MT5 EA's zu verwenden. Aus diesem Grund werde ich den gesamten Prozess durchgehen, um sicherzustellen, dass jeder diesen EA verwenden kann. Wenn Sie wissen, wie man ein ONNX-Modell erstellt, können Sie mit dem EA weitermachen.
Sie müssen es zuerst installieren:
- Python 3.10
Sie finden es im Microsoft Store, klicken Sie einfach auf Installieren.
- Visual Studio-Code
Sie finden es im Microsoft Store, klicken Sie einfach auf „Installieren“, und alles wird für Sie erledigt.
Danach müssen Sie Visual Studio 2019 oder C++ von hier aus installieren (Sie werden aufgefordert, eine Python-Bibliothek mit zu installieren):
https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022
Sobald dies geschehen ist, müssen Sie den Ordner für die Python-Skripts zu den Pfadvariablen hinzufügen.
Sie müssen auch „.py“ zum PATHEXT hinzufügen.
Wenn das alles erledigt ist, können wir nun die Bibliotheken wie folgt installieren.
VSC öffnen -> Terminal -> Neues Terminal
VSC könnte Sie auffordern, Python-Erweiterungen zu installieren (klicken Sie einfach auf OK)
und fügen Sie diesen Text einfach ein (und drücken Sie die Eingabetaste):
pip install MetaTrader5==5.0.4200 pip install pandas==2.2.1 pip install scipy==1.12.0 pip install statsmodels==0.14.1 pip install numpy==1.26.4 pip install tensorflow==2.15.0 pip install tf2onnx==1.16.1 pip install scikit-learn==1.4.1.post1 pip install keras==2.15.0 pip install matplotlib==3.8.3
Es sollten keine Fehler auftreten, wenn nicht, können Sie hier fragen.
Sobald dies alles installiert ist und keine Fehler aufgetreten sind, können wir mit dem .py-Testmodell fortfahren. Ich werde dieses Beispiel kopieren und einfügen:
# python libraries import MetaTrader5 as mt5 import tensorflow as tf import numpy as np import pandas as pd import tf2onnx from datetime import timedelta, datetime # input parameters symbol1 = "EURGBP" symbol2 = "GBPUSD" symbol3 = "EURUSD" sample_size1 = 200000 optional = "_M1_test" timeframe = mt5.TIMEFRAME_M1 #end_date = datetime.now() end_date = datetime(2024, 3, 4, 0) inp_history_size = 120 sample_size = sample_size1 symbol = symbol1 optional = optional inp_model_name = str(symbol)+"_"+str(optional)+".onnx" if not mt5.initialize(): print("initialize() failed, error code =",mt5.last_error()) quit() # we will save generated onnx-file near the 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) # and save to MQL5\Files folder to use as file terminal_info=mt5.terminal_info() file_path=terminal_info.data_path+"\\MQL5\\Files\\" print("file path to save onnx model",file_path) # set start and end dates for history data #end_date = datetime.now() #end_date = datetime(2024, 5, 1, 0) start_date = end_date - timedelta(days=inp_history_size*20) # print start and end dates print("data start date =",start_date) print("data end date =",end_date) # get rates eurusd_rates = mt5.copy_rates_from(symbol, timeframe , end_date, sample_size ) # create dataframe df=pd.DataFrame() df = pd.DataFrame(eurusd_rates) print(df) # Extraer los precios de cierre directamente datas = df['close'].values """# Calcular la inversa de cada valor inverted_data = 1 / datas # Convertir los datos invertidos a un array de numpy si es necesario data = inverted_data.values""" data = datas.reshape(-1,1) # Imprimir los resultados """data = datas""" # scale data 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) train_data_initial = scaled_data[0:training_size,:] test_data_initial = scaled_data[training_size:,:1] # 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 = inp_history_size 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) # define model from keras.models import Sequential from keras.layers import Dense, Activation, Conv1D, MaxPooling1D, Dropout, Flatten, LSTM from keras.metrics import RootMeanSquaredError as rmse from tensorflow.keras import callbacks model = Sequential() model.add(Conv1D(filters=256, kernel_size=2, activation='relu',padding = 'same',input_shape=(inp_history_size,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()]) # Set up early stopping early_stopping = callbacks.EarlyStopping( monitor='val_loss', patience=5, restore_best_weights=True, ) # model training for 300 epochs history = model.fit(x_train, y_train, epochs = 300 , validation_data = (x_test,y_test), batch_size=32, callbacks=[early_stopping], verbose=2) # evaluate training data train_loss, train_rmse = model.evaluate(x_train,y_train, batch_size = 32) 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, batch_size = 32) 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}") output_path = file_path+inp_model_name onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path) print(f"saved model to {output_path}") # finish mt5.shutdown() #prediction using testing data #prediction using testing data test_predict = model.predict(x_test) print(test_predict) print("longitud total de la prediccion: ", len(test_predict)) print("longitud total del sample: ", sample_size) plot_y_test = np.array(y_test).reshape(-1, 1) # Selecciona solo el último elemento de cada muestra de prueba plot_y_train = y_train.reshape(-1,1) train_predict = model.predict(x_train) #print(plot_y_test) #calculate metrics from sklearn import metrics from sklearn.metrics import r2_score #transform data to real values value1=scaler.inverse_transform(plot_y_test) #print(value1) # Escala las predicciones inversas al transformarlas a la escala original value2 = scaler.inverse_transform(test_predict.reshape(-1, 1)) #print(value2) #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)) #sumarize model model.summary() #Print error value11=pd.DataFrame(value1) value22=pd.DataFrame(value2) #print(value11) #print(value22) value111=value11.iloc[:,:] value222=value22.iloc[:,:] print("longitud salida (tandas de 1 minuto): ",len(value111) ) #print("en horas son " + str((len(value111))*60*24)+ " minutos") print("en horas son " + str(((len(value111)))/60)+ " horas") print("en horas son " + str(((len(value111)))/60/24)+ " dias") # Calculate error error = value111 - value222 import matplotlib.pyplot as plt # Plot error plt.figure(figsize=(10, 6)) plt.scatter(range(len(error)), error, color='blue', label='Error') plt.axhline(y=0, color='red', linestyle='--', linewidth=1) # Línea horizontal en y=0 plt.title('Error de Predicción ' + str(symbol)) plt.xlabel('Índice de la muestra') plt.ylabel('Error') plt.legend() plt.grid(True) plt.savefig(str(symbol)+str(optional)+'.png') rmse_ = format(score) mse_ = metrics.mean_squared_error(value1,value2) r2_ = metrics.r2_score(value1,value2) resultados= [rmse_,mse_,r2_] # Abre un archivo en modo escritura with open(str(symbol)+str(optional)+"results.txt", "w") as archivo: # Escribe cada resultado en una línea separada for resultado in resultados: archivo.write(str(resultado) + "\n") # finish mt5.shutdown() #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" + str(symbol)) plt.legend() plt.savefig(str(symbol)+str(optional)+'1.png') #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" + str(symbol)) plt.legend() plt.savefig(str(symbol)+str(optional)+'2.png') #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" + str(symbol)) plt.xlabel("Hours") plt.ylabel("Price") plt.legend() plt.savefig(str(symbol)+str(optional)+'3.png') #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" + str(symbol)) plt.xlabel("Hours") plt.ylabel("Price") plt.legend() plt.savefig(str(symbol)+str(optional)+'4.png') ################################################################################################ EURJPY 1 # python libraries import MetaTrader5 as mt5 import tensorflow as tf import numpy as np import pandas as pd import tf2onnx # input parameters inp_history_size = 120 sample_size = sample_size1 symbol = symbol2 optional = optional inp_model_name = str(symbol)+"_"+str(optional)+".onnx" if not mt5.initialize(): print("initialize() failed, error code =",mt5.last_error()) quit() # we will save generated onnx-file near the 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) # and save to MQL5\Files folder to use as file terminal_info=mt5.terminal_info() file_path=terminal_info.data_path+"\\MQL5\\Files\\" print("file path to save onnx model",file_path) # set start and end dates for history data from datetime import timedelta, datetime #end_date = datetime.now() #end_date = datetime(2024, 5, 1, 0) start_date = end_date - timedelta(days=inp_history_size*20) # print start and end dates print("data start date =",start_date) print("data end date =",end_date) # get rates eurusd_rates2 = mt5.copy_rates_from(symbol, timeframe , end_date, sample_size) # create dataframe df=pd.DataFrame() df2 = pd.DataFrame(eurusd_rates2) print(df2) # Extraer los precios de cierre directamente datas2 = df2['close'].values """inverted_data = 1 / datas # Convertir los datos invertidos a un array de numpy si es necesario data = inverted_data.values""" data2 = datas2.reshape(-1,1) # Convertir los datos invertidos a un array de numpy si es necesario #data = datas.values # Imprimir los resultados # scale data from sklearn.preprocessing import MinMaxScaler scaler2=MinMaxScaler(feature_range=(0,1)) scaled_data2 = scaler2.fit_transform(data2) # training size is 80% of the data training_size2 = int(len(scaled_data2)*0.80) print("Training_size:",training_size2) train_data_initial2 = scaled_data2[0:training_size2,:] test_data_initial2 = scaled_data2[training_size2:,:1] # 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 = inp_history_size x_train2, y_train2 = split_sequence(train_data_initial2, time_step) x_test2, y_test2 = split_sequence(test_data_initial2, time_step) # reshape input to be [samples, time steps, features] which is required for LSTM x_train2 =x_train2.reshape(x_train2.shape[0],x_train2.shape[1],1) x_test2 = x_test2.reshape(x_test2.shape[0],x_test2.shape[1],1) # define model from keras.models import Sequential from keras.layers import Dense, Activation, Conv1D, MaxPooling1D, Dropout, Flatten, LSTM from keras.metrics import RootMeanSquaredError as rmse from tensorflow.keras import callbacks model = Sequential() model.add(Conv1D(filters=256, kernel_size=2, activation='relu',padding = 'same',input_shape=(inp_history_size,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()]) # Set up early stopping early_stopping = callbacks.EarlyStopping( monitor='val_loss', patience=5, restore_best_weights=True, ) # model training for 300 epochs history2 = model.fit(x_train2, y_train2, epochs = 300 , validation_data = (x_test2,y_test2), batch_size=32, callbacks=[early_stopping], verbose=2) # evaluate training data train_loss2, train_rmse2 = model.evaluate(x_train2,y_train2, batch_size = 32) print(f"train_loss={train_loss2:.3f}") print(f"train_rmse={train_rmse2:.3f}") # evaluate testing data test_loss2, test_rmse2 = model.evaluate(x_test2,y_test2, batch_size = 32) print(f"test_loss={test_loss2:.3f}") print(f"test_rmse={test_rmse2:.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}") output_path = file_path+inp_model_name onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path) print(f"saved model to {output_path}") # finish mt5.shutdown() #prediction using testing data #prediction using testing data test_predict2 = model.predict(x_test2) print(test_predict2) print("longitud total de la prediccion: ", len(test_predict2)) print("longitud total del sample: ", sample_size) plot_y_test2 = np.array(y_test2).reshape(-1, 1) # Selecciona solo el último elemento de cada muestra de prueba plot_y_train2 = y_train2.reshape(-1,1) train_predict2 = model.predict(x_train2) #print(plot_y_test) #calculate metrics from sklearn import metrics from sklearn.metrics import r2_score #transform data to real values value12=scaler2.inverse_transform(plot_y_test2) #print(value1) # Escala las predicciones inversas al transformarlas a la escala original value22 = scaler2.inverse_transform(test_predict2.reshape(-1, 1)) #print(value2) #calc score score2 = np.sqrt(metrics.mean_squared_error(value12,value22)) print("RMSE : {}".format(score2)) print("MSE :", metrics.mean_squared_error(value12,value22)) print("R2 score :",metrics.r2_score(value12,value22)) #sumarize model model.summary() #Print error value112=pd.DataFrame(value12) value222=pd.DataFrame(value22) #print(value11) #print(value22) value1112=value112.iloc[:,:] value2222=value222.iloc[:,:] print("longitud salida (tandas de 1 min): ",len(value1112) ) #print("en horas son " + str((len(value1112))*60*24)+ " minutos") print("en horas son " + str(((len(value1112)))/60)+ " horas") print("en horas son " + str(((len(value1112)))/60/24)+ " dias") # Calculate error error2 = value1112 - value2222 import matplotlib.pyplot as plt # Plot error plt.figure(figsize=(10, 6)) plt.scatter(range(len(error2)), error2, color='blue', label='Error') plt.axhline(y=0, color='red', linestyle='--', linewidth=1) # Línea horizontal en y=0 plt.title('Error de Predicción ' + str(symbol)) plt.xlabel('Índice de la muestra') plt.ylabel('Error') plt.legend() plt.grid(True) plt.savefig(str(symbol)+str(optional)+'.png') rmse_2 = format(score2) mse_2 = metrics.mean_squared_error(value12,value22) r2_2 = metrics.r2_score(value12,value22) resultados2= [rmse_2,mse_2,r2_2] # Abre un archivo en modo escritura with open(str(symbol)+str(optional)+"results.txt", "w") as archivo: # Escribe cada resultado en una línea separada for resultado in resultados2: archivo.write(str(resultado) + "\n") # finish mt5.shutdown() #show iteration-rmse graph for training and validation plt.figure(figsize = (18,10)) plt.plot(history2.history['root_mean_squared_error'],label='Training RMSE',color='b') plt.plot(history2.history['val_root_mean_squared_error'],label='Validation-RMSE',color='g') plt.xlabel("Iteration") plt.ylabel("RMSE") plt.title("RMSE" + str(symbol)) plt.legend() plt.savefig(str(symbol)+str(optional)+'1.png') #show iteration-loss graph for training and validation plt.figure(figsize = (18,10)) plt.plot(history2.history['loss'],label='Training Loss',color='b') plt.plot(history2.history['val_loss'],label='Validation-loss',color='g') plt.xlabel("Iteration") plt.ylabel("Loss") plt.title("LOSS" + str(symbol)) plt.legend() plt.savefig(str(symbol)+str(optional)+'2.png') #show actual vs predicted (training) graph plt.figure(figsize=(18,10)) plt.plot(scaler2.inverse_transform(plot_y_train2),color = 'b', label = 'Original') plt.plot(scaler2.inverse_transform(train_predict2),color='red', label = 'Predicted') plt.title("Prediction Graph Using Training Data" + str(symbol)) plt.xlabel("Hours") plt.ylabel("Price") plt.legend() plt.savefig(str(symbol)+str(optional)+'3.png') #show actual vs predicted (testing) graph plt.figure(figsize=(18,10)) plt.plot(scaler2.inverse_transform(plot_y_test2),color = 'b', label = 'Original') plt.plot(scaler2.inverse_transform(test_predict2),color='g', label = 'Predicted') plt.title("Prediction Graph Using Testing Data" + str(symbol)) plt.xlabel("Hours") plt.ylabel("Price") plt.legend() plt.savefig(str(symbol)+str(optional)+'4.png') ############################################################################################## JPYUSD # python libraries import MetaTrader5 as mt5 import tensorflow as tf import numpy as np import pandas as pd import tf2onnx # input parameters inp_history_size = 120 sample_size = sample_size1 symbol = symbol3 optional = optional inp_model_name = str(symbol)+"_"+str(optional)+".onnx" if not mt5.initialize(): print("initialize() failed, error code =",mt5.last_error()) quit() # we will save generated onnx-file near the 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) # and save to MQL5\Files folder to use as file terminal_info=mt5.terminal_info() file_path=terminal_info.data_path+"\\MQL5\\Files\\" print("file path to save onnx model",file_path) # set start and end dates for history data from datetime import timedelta, datetime #end_date = datetime.now() #end_date = datetime(2024, 5, 1, 0) start_date = end_date - timedelta(days=inp_history_size*20) # print start and end dates print("data start date =",start_date) print("data end date =",end_date) # get rates eurusd_rates3 = mt5.copy_rates_from(symbol, timeframe , end_date, sample_size) # create dataframe df3=pd.DataFrame() df3 = pd.DataFrame(eurusd_rates3) print(df3) # Extraer los precios de cierre directamente datas3 = df3['close'].values """# Calcular la inversa de cada valor inverted_data = 1 / datas # Convertir los datos invertidos a un array de numpy si es necesario data = inverted_data.values""" data3 = datas3.reshape(-1,1) # Imprimir los resultados """data = datas""" # scale data from sklearn.preprocessing import MinMaxScaler scaler3=MinMaxScaler(feature_range=(0,1)) scaled_data3 = scaler3.fit_transform(data3) # training size is 80% of the data training_size3 = int(len(scaled_data3)*0.80) print("Training_size:",training_size3) train_data_initial3 = scaled_data3[0:training_size3,:] test_data_initial3 = scaled_data3[training_size3:,:1] # 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 = inp_history_size x_train3, y_train3 = split_sequence(train_data_initial3, time_step) x_test3, y_test3 = split_sequence(test_data_initial3, time_step) # reshape input to be [samples, time steps, features] which is required for LSTM x_train3 =x_train3.reshape(x_train3.shape[0],x_train3.shape[1],1) x_test3 = x_test3.reshape(x_test3.shape[0],x_test3.shape[1],1) # define model from keras.models import Sequential from keras.layers import Dense, Activation, Conv1D, MaxPooling1D, Dropout, Flatten, LSTM from keras.metrics import RootMeanSquaredError as rmse from tensorflow.keras import callbacks model = Sequential() model.add(Conv1D(filters=256, kernel_size=2, activation='relu',padding = 'same',input_shape=(inp_history_size,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()]) # Set up early stopping early_stopping = callbacks.EarlyStopping( monitor='val_loss', patience=5, restore_best_weights=True, ) # model training for 300 epochs history3 = model.fit(x_train3, y_train3, epochs = 300 , validation_data = (x_test3,y_test3), batch_size=32, callbacks=[early_stopping], verbose=2) # evaluate training data train_loss3, train_rmse3 = model.evaluate(x_train3,y_train3, batch_size = 32) print(f"train_loss={train_loss3:.3f}") print(f"train_rmse={train_rmse3:.3f}") # evaluate testing data test_loss3, test_rmse3 = model.evaluate(x_test3,y_test3, batch_size = 32) print(f"test_loss={test_loss3:.3f}") print(f"test_rmse={test_rmse3:.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}") output_path = file_path+inp_model_name onnx_model = tf2onnx.convert.from_keras(model, output_path=output_path) print(f"saved model to {output_path}") # finish mt5.shutdown() #prediction using testing data #prediction using testing data test_predict3 = model.predict(x_test3) print(test_predict3) print("longitud total de la prediccion: ", len(test_predict3)) print("longitud total del sample: ", sample_size) plot_y_test3 = np.array(y_test3).reshape(-1, 1) # Selecciona solo el último elemento de cada muestra de prueba plot_y_train3 = y_train3.reshape(-1,1) train_predict3 = model.predict(x_train3) #print(plot_y_test) #calculate metrics from sklearn import metrics from sklearn.metrics import r2_score #transform data to real values value13=scaler3.inverse_transform(plot_y_test3) #print(value1) # Escala las predicciones inversas al transformarlas a la escala original value23 = scaler3.inverse_transform(test_predict3.reshape(-1, 1)) #print(value2) #calc score score3 = np.sqrt(metrics.mean_squared_error(value13,value23)) print("RMSE : {}".format(score3)) print("MSE :", metrics.mean_squared_error(value13,value23)) print("R2 score :",metrics.r2_score(value13,value23)) #sumarize model model.summary() #Print error value113=pd.DataFrame(value13) value223=pd.DataFrame(value23) #print(value11) #print(value22) value1113=value113.iloc[:,:] value2223=value223.iloc[:,:] print("longitud salida (tandas de 1 hora): ",len(value1113) ) #print("en horas son " + str((len(value1113))*60*24)+ " minutos") print("en horas son " + str(((len(value1113)))/60)+ " horas") print("en horas son " + str(((len(value1113)))/60/24)+ " dias") # Calculate error error3 = value1113 - value2223 import matplotlib.pyplot as plt # Plot error plt.figure(figsize=(10, 6)) plt.scatter(range(len(error3)), error3, color='blue', label='Error') plt.axhline(y=0, color='red', linestyle='--', linewidth=1) # Línea horizontal en y=0 plt.title('Error de Predicción ' + str(symbol)) plt.xlabel('Índice de la muestra') plt.ylabel('Error') plt.legend() plt.grid(True) plt.savefig(str(symbol)+str(optional)+'.png') rmse_3 = format(score3) mse_3 = metrics.mean_squared_error(value13,value23) r2_3 = metrics.r2_score(value13,value23) resultados3= [rmse_3,mse_3,r2_3] # Abre un archivo en modo escritura with open(str(symbol)+str(optional)+"results.txt", "w") as archivo: # Escribe cada resultado en una línea separada for resultado in resultados3: archivo.write(str(resultado) + "\n") # finish mt5.shutdown() #show iteration-rmse graph for training and validation plt.figure(figsize = (18,10)) plt.plot(history3.history['root_mean_squared_error'],label='Training RMSE',color='b') plt.plot(history3.history['val_root_mean_squared_error'],label='Validation-RMSE',color='g') plt.xlabel("Iteration") plt.ylabel("RMSE") plt.title("RMSE" + str(symbol)) plt.legend() plt.savefig(str(symbol)+str(optional)+'1.png') #show iteration-loss graph for training and validation plt.figure(figsize = (18,10)) plt.plot(history3.history['loss'],label='Training Loss',color='b') plt.plot(history3.history['val_loss'],label='Validation-loss',color='g') plt.xlabel("Iteration") plt.ylabel("Loss") plt.title("LOSS" + str(symbol)) plt.legend() plt.savefig(str(symbol)+str(optional)+'2.png') #show actual vs predicted (training) graph plt.figure(figsize=(18,10)) plt.plot(scaler3.inverse_transform(plot_y_train3),color = 'b', label = 'Original') plt.plot(scaler3.inverse_transform(train_predict3),color='red', label = 'Predicted') plt.title("Prediction Graph Using Training Data" + str(symbol)) plt.xlabel("Hours") plt.ylabel("Price") plt.legend() plt.savefig(str(symbol)+str(optional)+'3.png') #show actual vs predicted (testing) graph plt.figure(figsize=(18,10)) plt.plot(scaler3.inverse_transform(plot_y_test3),color = 'b', label = 'Original') plt.plot(scaler3.inverse_transform(test_predict3),color='g', label = 'Predicted') plt.title("Prediction Graph Using Testing Data" + str(symbol)) plt.xlabel("Hours") plt.ylabel("Price") plt.legend() plt.savefig(str(symbol)+str(optional)+'4.png') ################################################################################################ ############################################################################################## """ import onnxruntime as ort import numpy as np # Cargar el modelo ONNX sesion = ort.InferenceSession("EURUSD_M1_inverse_test.onnx") # Obtener el nombre de la entrada y la salida del modelo input_name = sesion.get_inputs()[0].name output_name = sesion.get_outputs()[0].name # Crear datos de entrada de prueba como un array de numpy # Asegúrate de que los datos de entrada coincidan con la forma y el tipo esperado por el modelo input_data = [1,120] #np.random.rand(1, 10).astype(np.float32) # Ejemplo: entrada de tamaño [1, 10] # Realizar la inferencia result = sesion.run([output_name], {input_name: input_data}) # Imprimir el resultado print(result) """
Diese .py-Datei erstellt drei ONNX sowie einige Diagramme und Daten, damit Sie überprüfen können, ob alles in Ordnung ist.
Die Daten liegen in einer txt-Datei vor, und die Zahlen stehen jeweils für RMSE, MSE und R2.
Bevor Sie dieses Skript ausführen, müssen Sie die Symbole, den Stichprobenumfang, den Zeitrahmen und das Enddatum angeben (von, um die Zeiträume rückwärts zu zählen).
Die optionale Variable ist eine Zeichenkette, in die Sie etwas wie M1 Ticks oder das Enddatum eingeben können ... was auch immer Sie wollen, um die onnx-Dateien und die Diagramme und Daten zu speichern.
symbol1 = "EURGBP" symbol2 = "GBPUSD" symbol3 = "EURUSD" sample_size1 = 200000 optional = "_M1_test" timeframe = mt5.TIMEFRAME_M1 #end_date = datetime.now() end_date = datetime(2024, 3, 4, 0)
Wenn Sie mit dem Strategietester testen wollen, ändern Sie das Datum nach Ihren Wünschen. Wenn Sie handeln wollen, müssen Sie nur dieses Enddatum verwenden.
end_date = datetime.now()
*** Wenn Sie mit einem Konto ohne Spread handeln, können Sie versuchen, Ticks anstelle von Perioden zu verwenden, Sie müssten nur das umstellen: ***
eurusd_rates = mt5.copy_rates_from(symbol, timeframe , end_date, sample_size)
mit dies hier:
eurusd_rates = mt5.copy_ticks_from(symbol, end_date, sample_size, mt5.COPY_TICKS_ALL)
um Bid und Ask Ticks zu erhalten. Ich glaube, es gibt eine Begrenzung der Anzahl der Ticks. Wenn Sie mehr Ticks benötigen, können Sie alle Ticks eines Symbols mit diesem herunterladen: Laden Sie alle Daten von einem Symbol herunter, das ist kostenlos.
Um die .py-Datei auszuführen, öffnen Sie sie einfach mit VSC und drücken Sie RUN -> Run Without Debugging (während MT5 geöffnet ist).
und warten Sie einfach, bis es fertig ist.
Am Ende erhalten Sie eine Reihe von Diagrammen, txt-Dateien und ONNX-Dateien. Sie müssen die ONNX-Datei im Ordner MQL5/Files speichern und denselben Pfad im EA-Code angeben.
Dank dieser Zeile wird er diese Aufgabe weiterhin erfüllen:
# and save to MQL5\Files folder to use as file terminal_info=mt5.terminal_info() file_path=terminal_info.data_path+"\\MQL5\\Files\\" print("file path to save onnx model",file_path)
Aber es ist wichtig, dass Sie den Pfad angeben, wenn Sie viele ONNX-Dateien haben wollen und diese in anderen Ordnern liegen.
Diese .py exportiert Bilder wie diese:
Diagramme mit den Werten RMSE, MSE und R2:
0.023019903957086384 0.0005299159781934813 0.999707563612641
Auf diese Weise können wir feststellen, ob unsere Modelle über- oder unterangepasst sind.
In diesem Fall:
Der RMSE misst die Standardabweichung der Residuen (Vorhersagefehler). Die Residuen sind ein Maß dafür, wie weit die Datenpunkte von der Regressionslinie entfernt sind; der RMSE ist ein Maß dafür, wie weit diese Residuen gestreut sind. Mit anderen Worten, sie gibt an, wie konzentriert die Daten um die Linie der besten Anpassung sind.
Ein kleinerer RMSE-Wert bedeutet eine bessere Anpassung. Ist der von Ihnen ermittelte RMSE-Wert sehr klein, deutet darauf hin, dass das Modell sehr gut auf den Datensatz passt.
MSE ist wie RMSE, quadriert aber die Fehler vor der Mittelwertbildung, wodurch größere Fehler stärker gewichtet werden. Er ist ein weiteres Maß für die Qualität eines Schätzers - er ist immer nicht-negativ, und Werte, die näher bei Null liegen, sind besser.
Der sehr kleine MSE-Wert bestätigt, dass die Modellvorhersagen sehr nahe an den tatsächlichen Datenpunkten liegen.
R2 ist ein statistisches Maß, das den Anteil der Varianz einer abhängigen Variable angibt, der durch eine unabhängige Variable oder mehrere unabhängige Variablen in einem Regressionsmodell erklärt wird. Ein 𝑅2-Wert von 1 bedeutet, dass die Regressionsvorhersagen perfekt mit den Daten übereinstimmen.
Unser R2 Wert liegt sehr nahe bei 1, was bedeutet, dass unser Modell fast die gesamte Variabilität um den Mittelwert herum erklärt, was hervorragend ist.
Insgesamt deuten diese Metriken darauf hin, dass unser Modell bei der Vorhersage oder Anpassung an Ihren Datensatz außergewöhnlich gut abschneidet.
Und um zu wissen, ob es überangepasst ist, verwenden wir die Graphen, zum Beispiel in diesem Fall den zweiten.
Hier eine Analyse auf der Grundlage des Diagramms:
-
Trainingsverlust (blaue Linie):
- Diese Linie zeigt anfangs einen steilen Abfall, was darauf hindeutet, dass das Modell schnell aus dem Trainingsdatensatz lernt. Mit fortschreitender Iteration nimmt der Trainingsverlust weiter ab, allerdings langsamer, was typisch ist, wenn das Modell beginnt, gegen ein Minimum zu konvergieren.
-
Validierungsverlust (grüne Linie):
- Der Validierungsverlust bleibt während des gesamten Trainingsprozesses äußerst gering und relativ stabil. Dies deutet darauf hin, dass das Modell gut verallgemeinert und sich nicht nur die Trainingsdaten merkt. Die geringen Schwankungen deuten auf eine Variabilität in der Leistung des Validierungssatzes über die Iterationen hinweg hin, bleiben aber innerhalb einer sehr engen Bandbreite.
Insgesamt deutet das Diagramm auf einen sehr erfolgreichen Trainingsprozess mit ausgezeichneter Konvergenz und Generalisierung hin. Der niedrige Validierungsverlust ist besonders vielversprechend, da er darauf hindeutet, dass das Modell bei ungesehenen Daten gut abschneiden sollte, vorausgesetzt, die Validierungsmenge ist repräsentativ für den allgemeinen Problemraum.
Nachdem das alles erledigt ist, gehen wir über zum EA
Expert Advisor für eine Dreiecksarbitrage mit Vorhersagen
//+------------------------------------------------------------------+ //| ONNX_Triangular EURUSD-USDJPY-EURJPY.mq5| //| Copyright 2024, Javier S. Gastón de Iriarte Cabrera. | //| https://www.mql5.com/en/users/jsgaston/news | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Javier S. Gastón de Iriarte Cabrera." #property link "https://www.mql5.com/en/users/jsgaston/news" #property version "1.04" #property strict #include <Trade\Trade.mqh> #define MAGIC (965334) #resource "/Files/art/arbitrage triangular/eurusdjpy/EURUSD__M1_test.onnx" as uchar ExtModel[] #resource "/Files/art/arbitrage triangular/eurusdjpy/USDJPY__M1_test.onnx" as uchar ExtModel2[] #resource "/Files/art/arbitrage triangular/eurusdjpy/EURJPY__M1_test.onnx" as uchar ExtModel3[] CTrade ExtTrade; #define SAMPLE_SIZE 120 input double lotSize = 3.0; //input double slippage = 3; // Add these inputs to allow dynamic control over SL and TP distances input double StopLossPips = 50.0; // Stop Loss in pips input double TakeProfitPips = 100.0; // Take Profit in pips //input double maxSpreadPoints = 10.0; input ENUM_TIMEFRAMES Periodo = PERIOD_CURRENT; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string symbol1 = Symbol(); input string symbol2 = "USDJPY"; input string symbol3 = "EURJPY"; int ticket1 = 0; int ticket2 = 0; int ticket3 = 0; int ticket11 = 0; int ticket22 = 0; int ticket33 = 0; input bool isArbitrageActive = true; double spreads[1000]; // Array para almacenar hasta 1000 spreads int spreadIndex = 0; // Índice para el próximo spread a almacenar long ExtHandle=INVALID_HANDLE; //int ExtPredictedClass=-1; datetime ExtNextBar=0; datetime ExtNextDay=0; float ExtMin=0.0; float ExtMax=0.0; long ExtHandle2=INVALID_HANDLE; //int ExtPredictedClass=-1; datetime ExtNextBar2=0; datetime ExtNextDay2=0; float ExtMin2=0.0; float ExtMax2=0.0; long ExtHandle3=INVALID_HANDLE; //int ExtPredictedClass=-1; datetime ExtNextBar3=0; datetime ExtNextDay3=0; float ExtMin3=0.0; float ExtMax3=0.0; float predicted=0.0; float predicted2=0.0; float predicted3=0.0; float predicted2i=0.0; float predicted3i=0.0; float lastPredicted1=0.0; float lastPredicted2=0.0; float lastPredicted3=0.0; float lastPredicted2i=0.0; float lastPredicted3i=0.0; int Order=0; input double targetProfit = 100.0; // Eur benefit goal input double maxLoss = -50.0; // Eur max loss input double perVar = 0.005; // Percentage of variation to make orders ulong tickets[6]; // Array para almacenar los tickets de las órdenes double sl=0.0; double tp=0.0; int Abrir = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { ExtTrade.SetExpertMagicNumber(MAGIC); Print("EA de arbitraje ONNX iniciado"); //--- create a model from static buffer ExtHandle=OnnxCreateFromBuffer(ExtModel,ONNX_DEFAULT); if(ExtHandle==INVALID_HANDLE) { Print("OnnxCreateFromBuffer error ",GetLastError()); return(INIT_FAILED); } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size, third index - number of series (only Close) const long input_shape[] = {1,SAMPLE_SIZE,1}; if(!OnnxSetInputShape(ExtHandle,ONNX_DEFAULT,input_shape)) { Print("OnnxSetInputShape error ",GetLastError()); return(INIT_FAILED); } //--- since not all sizes defined in the output tensor we must set them explicitly //--- first index - batch size, must match the batch size of the input tensor //--- second index - number of predicted prices (we only predict Close) const long output_shape[] = {1,1}; if(!OnnxSetOutputShape(ExtHandle,0,output_shape)) { Print("OnnxSetOutputShape error ",GetLastError()); return(INIT_FAILED); } //////////////////////////////////////////////////////////////////////////////////////// //--- create a model from static buffer ExtHandle2=OnnxCreateFromBuffer(ExtModel2,ONNX_DEFAULT); if(ExtHandle2==INVALID_HANDLE) { Print("OnnxCreateFromBuffer error ",GetLastError()); return(INIT_FAILED); } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size, third index - number of series (only Close) const long input_shape2[] = {1,SAMPLE_SIZE,1}; if(!OnnxSetInputShape(ExtHandle2,ONNX_DEFAULT,input_shape2)) { Print("OnnxSetInputShape error ",GetLastError()); return(INIT_FAILED); } //--- since not all sizes defined in the output tensor we must set them explicitly //--- first index - batch size, must match the batch size of the input tensor //--- second index - number of predicted prices (we only predict Close) const long output_shape2[] = {1,1}; if(!OnnxSetOutputShape(ExtHandle2,0,output_shape2)) { Print("OnnxSetOutputShape error ",GetLastError()); return(INIT_FAILED); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// //--- create a model from static buffer ExtHandle3=OnnxCreateFromBuffer(ExtModel3,ONNX_DEFAULT); if(ExtHandle3==INVALID_HANDLE) { Print("OnnxCreateFromBuffer error ",GetLastError()); return(INIT_FAILED); } //--- since not all sizes defined in the input tensor we must set them explicitly //--- first index - batch size, second index - series size, third index - number of series (only Close) const long input_shape3[] = {1,SAMPLE_SIZE,1}; if(!OnnxSetInputShape(ExtHandle3,ONNX_DEFAULT,input_shape3)) { Print("OnnxSetInputShape error ",GetLastError()); return(INIT_FAILED); } //--- since not all sizes defined in the output tensor we must set them explicitly //--- first index - batch size, must match the batch size of the input tensor //--- second index - number of predicted prices (we only predict Close) const long output_shape3[] = {1,1}; if(!OnnxSetOutputShape(ExtHandle3,0,output_shape3)) { Print("OnnxSetOutputShape error ",GetLastError()); return(INIT_FAILED); } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(ExtHandle!=INVALID_HANDLE) { OnnxRelease(ExtHandle); ExtHandle=INVALID_HANDLE; } if(ExtHandle2!=INVALID_HANDLE) { OnnxRelease(ExtHandle2); ExtHandle2=INVALID_HANDLE; } if(ExtHandle3!=INVALID_HANDLE) { OnnxRelease(ExtHandle3); ExtHandle3=INVALID_HANDLE; } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { SymbolProcessor processor; // Crear instancia de SymbolProcessor string A = processor.GetFirstThree(symbol1); string B = processor.GetLastThree(symbol1); string C = processor.GetFirstThree(symbol2); string D = processor.GetLastThree(symbol2); string E = processor.GetFirstThree(symbol3); string F = processor.GetLastThree(symbol3); if((A != E) || (B != C) || (D != F)) { Print("Wrongly selected symbols"); return; } //--- check new day if(TimeCurrent()>=ExtNextDay) { GetMinMax(); GetMinMax2(); GetMinMax3(); //--- set next day time ExtNextDay=TimeCurrent(); ExtNextDay-=ExtNextDay%PeriodSeconds(Periodo); ExtNextDay+=PeriodSeconds(Periodo); } //--- check new bar if(TimeCurrent()<ExtNextBar) { return; } //--- set next bar time ExtNextBar=TimeCurrent(); ExtNextBar-=ExtNextBar%PeriodSeconds(); ExtNextBar+=PeriodSeconds(); //--- check min and max float close=(float)iClose(symbol1,Periodo,0); if(ExtMin>close) ExtMin=close; if(ExtMax<close) ExtMax=close; float close2=(float)iClose(symbol2,Periodo,0); if(ExtMin2>close2) ExtMin2=close2; if(ExtMax2<close2) ExtMax2=close2; float close3=(float)iClose(symbol3,Periodo,0); if(ExtMin3>close3) ExtMin3=close3; if(ExtMax3<close3) ExtMax3=close3; lastPredicted1=predicted; lastPredicted2=predicted2; lastPredicted3=predicted3; lastPredicted2i=predicted2i; lastPredicted3i=predicted3i; //--- predict next price PredictPrice(); PredictPrice2(); PredictPrice3(); /* */ double price1 = SymbolInfoDouble(symbol1, SYMBOL_BID);///////////////// double price2 = SymbolInfoDouble(symbol2, SYMBOL_BID); double price2i = 1/price2; double price3 = SymbolInfoDouble(symbol3, SYMBOL_ASK); double price3i = 1/price3; double price11 = SymbolInfoDouble(symbol1, SYMBOL_ASK);///////////////// double price22 = SymbolInfoDouble(symbol2, SYMBOL_ASK); double price22i = 1/price22; double price33 = SymbolInfoDouble(symbol3, SYMBOL_BID); double price33i = 1/price33; predicted2i = 1/predicted2; predicted3i = 1/predicted3; //double lotSize = 1.0; // Lote base double lotSize2 = lotSize * predicted / predicted2; /// tengo dudas con usar el invertido o no invertido double lotSize3 = lotSize * predicted / predicted3; double lotSize22 = lotSize * predicted / predicted2; /// tengo dudas con usar el invertido o no invertido double lotSize33 = lotSize * predicted / predicted3; // Redondear lotes a un múltiplo aceptable por tu bróker lotSize2 = NormalizeDouble(lotSize2, 2); // Asume 2 decimales para lotes lotSize3 = NormalizeDouble(lotSize3, 2); lotSize22 = NormalizeDouble(lotSize22, 2); // Asume 2 decimales para lotes lotSize33 = NormalizeDouble(lotSize33, 2); int totalPositions = PositionsTotal(); if(Order==1 || Order==2) { // Verificar y cerrar órdenes si se cumplen las condiciones Print("Verificar y cerrar órdenes si se cumplen las condiciones"); CheckAndCloseOrders(); } if(!isArbitrageActive || ArePositionsOpen()) { Print("Arbitraje inactivo o ya hay posiciones abiertas."); return; } double varia11 = 100.0 - (close*100/predicted); double varia21 = 100.0 - (close2*100/predicted2); double varia31 = 100.0 - (predicted3*100/close3); double varia12 = 100.0 - (predicted*100/close); double varia22 = 100.0 - (predicted2*100/close2); double varia32 = 100.0 - (close3*100/predicted3); if((varia11 > perVar) && (varia21 > perVar) && (varia31 > perVar)) { Print("se debería proceder a apertura de ordenes de derechas"); Abrir = 1; } if((varia12 > perVar) && (varia22 > perVar) && (varia32 > perVar)) { Print("se debería proceder a apertura de ordenes de izquierdas"); Abrir = 2; } if(Abrir == 1 && (predicted*predicted2*predicted3i>1)) { Print("orden derecha"); // Inicia el arbitraje si aún no está activo if(isArbitrageActive) { if((ticket1 == 0 && ticket2 == 0 && ticket3 ==0) && (Order==0) && totalPositions ==0) { Print("Preparando para abrir órdenes"); Order = 1; MqlTradeRequest request; MqlTradeResult result; { MqlRates rates[]; ArraySetAsSeries(rates,true); int copied=CopyRates(symbol1,0,0,1,rates); CalculateSL(true,rates[0].close,symbol1); CalculateTP(true,rates[0].close,symbol1); if(ExtTrade.Buy(lotSize, symbol1, rates[0].close, sl, tp, "Arbitraje")) { tickets[0] = ExtTrade.ResultDeal(); // Getting the ticket of the last trade Print("Order placed with ticket: ", tickets[0]); } else { Print("Failed to place order: ", GetLastError()); } } { MqlRates rates[]; ArraySetAsSeries(rates,true); int copied=CopyRates(symbol2,0,0,1,rates); CalculateSL(true,rates[0].close,symbol2); CalculateTP(true,rates[0].close,symbol2); if(ExtTrade.Buy(lotSize2, symbol2, rates[0].close, sl, tp, "Arbitraje")) { tickets[1] = ExtTrade.ResultDeal(); // Getting the ticket of the last trade Print("Order placed with ticket: ", tickets[1]); } else { Print("Failed to place order: ", GetLastError()); } } { MqlRates rates[]; ArraySetAsSeries(rates,true); int copied=CopyRates(symbol3,0,0,1,rates); CalculateSL(false,rates[0].close,symbol3); CalculateTP(false,rates[0].close,symbol3); if(ExtTrade.Sell(lotSize3, symbol3, rates[0].close, sl, tp, "Arbitraje")) { tickets[2] = ExtTrade.ResultDeal(); // Getting the ticket of the last trade Print("Order placed with ticket: ", tickets[2]); } else { Print("Failed to place order: ", GetLastError()); } } ticket1=1; ticket2=1; ticket3=1; Abrir=0; return; } else { Print(" no se puede abrir ordenes"); } } } if(Abrir == 2 && (predicted*predicted2*predicted3i<1)) { Print("Orden Inversa"); // Inicia el arbitraje si aún no está activo if(isArbitrageActive) { if((ticket11 == 0 && ticket22 == 0 && ticket33 ==0) && (Order==0) && totalPositions==0) { Print("Preparando para abrir órdenes"); Order = 2; MqlTradeRequest request; MqlTradeResult result; { MqlRates rates[]; ArraySetAsSeries(rates,true); int copied=CopyRates(symbol1,0,0,1,rates); CalculateSL(false,rates[0].close,symbol1); CalculateTP(false,rates[0].close,symbol1); if(ExtTrade.Sell(lotSize, symbol1, rates[0].close, sl, tp, "Arbitraje")) { tickets[3] = ExtTrade.ResultDeal(); // Getting the ticket of the last trade Print("Order placed with ticket: ", tickets[3]); } else { Print("Failed to place order: ", GetLastError()); } } { MqlRates rates[]; ArraySetAsSeries(rates,true); int copied=CopyRates(symbol2,0,0,1,rates); CalculateSL(false,rates[0].close,symbol2); CalculateTP(false,rates[0].close,symbol2); if(ExtTrade.Sell(lotSize2, symbol2, rates[0].close, sl, tp, "Arbitraje")) { tickets[4] = ExtTrade.ResultDeal(); // Getting the ticket of the last trade Print("Order placed with ticket: ", tickets[4]); } else { Print("Failed to place order: ", GetLastError()); } } { MqlRates rates[]; ArraySetAsSeries(rates,true); int copied=CopyRates(symbol3,0,0,1,rates); CalculateSL(true,rates[0].close,symbol3); CalculateTP(true,rates[0].close,symbol3); if(ExtTrade.Buy(lotSize3, symbol3, rates[0].close, sl, tp, "Arbitraje")) { tickets[5] = ExtTrade.ResultDeal(); // Getting the ticket of the last trade Print("Order placed with ticket: ", tickets[5]); } else { Print("Failed to place order: ", GetLastError()); } } ticket11=1; ticket22=1; ticket33=1; Abrir=0; return; } else { Print(" no se puede abrir ordenes"); } } } } //+------------------------------------------------------------------+ //| Postions are open function | //+------------------------------------------------------------------+ bool ArePositionsOpen() { // Check for positions on symbol1 if(PositionSelect(symbol1) && PositionGetDouble(POSITION_VOLUME) > 0) return true; // Check for positions on symbol2 if(PositionSelect(symbol2) && PositionGetDouble(POSITION_VOLUME) > 0) return true; // Check for positions on symbol3 if(PositionSelect(symbol3) && PositionGetDouble(POSITION_VOLUME) > 0) return true; return false; } //+------------------------------------------------------------------+ //| Price prediction function | //+------------------------------------------------------------------+ void PredictPrice(void) { static vectorf output_data(1); // vector to get result static vectorf x_norm(SAMPLE_SIZE); // vector for prices normalize //--- check for normalization possibility if(ExtMin>=ExtMax) { Print("ExtMin>=ExtMax"); //ExtPredictedClass=-1; return; } //--- request last bars if(!x_norm.CopyRates(_Symbol,Periodo,COPY_RATES_CLOSE,1,SAMPLE_SIZE)) { Print("CopyRates ",x_norm.Size()); //ExtPredictedClass=-1; return; } float last_close=x_norm[SAMPLE_SIZE-1]; //--- normalize prices x_norm-=ExtMin; x_norm/=(ExtMax-ExtMin); //--- run the inference if(!OnnxRun(ExtHandle,ONNX_NO_CONVERSION,x_norm,output_data)) { Print("OnnxRun"); //ExtPredictedClass=-1; return; } //--- denormalize the price from the output value predicted=output_data[0]*(ExtMax-ExtMin)+ExtMin; //return predicted; } //+------------------------------------------------------------------+ //| Price prediction function | //+------------------------------------------------------------------+ void PredictPrice2(void) { static vectorf output_data2(1); // vector to get result static vectorf x_norm2(SAMPLE_SIZE); // vector for prices normalize //--- check for normalization possibility if(ExtMin2>=ExtMax2) { Print("ExtMin2>=ExtMax2"); //ExtPredictedClass=-1; return; } //--- request last bars if(!x_norm2.CopyRates(symbol2,Periodo,COPY_RATES_CLOSE,1,SAMPLE_SIZE)) { Print("CopyRates ",x_norm2.Size()); //ExtPredictedClass=-1; return; } float last_close2=x_norm2[SAMPLE_SIZE-1]; //--- normalize prices x_norm2-=ExtMin2; x_norm2/=(ExtMax2-ExtMin2); //--- run the inference if(!OnnxRun(ExtHandle2,ONNX_NO_CONVERSION,x_norm2,output_data2)) { Print("OnnxRun"); //ExtPredictedClass=-1; return; } //--- denormalize the price from the output value predicted2=output_data2[0]*(ExtMax2-ExtMin2)+ExtMin2; //--- classify predicted price movement //return predicted2; } //+------------------------------------------------------------------+ //| Price prediction function | //+------------------------------------------------------------------+ void PredictPrice3(void) { static vectorf output_data3(1); // vector to get result static vectorf x_norm3(SAMPLE_SIZE); // vector for prices normalize //--- check for normalization possibility if(ExtMin3>=ExtMax3) { Print("ExtMin3>=ExtMax3"); //ExtPredictedClass=-1; return; } //--- request last bars if(!x_norm3.CopyRates(symbol3,Periodo,COPY_RATES_CLOSE,1,SAMPLE_SIZE)) { Print("CopyRates ",x_norm3.Size()); //ExtPredictedClass=-1; return; } float last_close3=x_norm3[SAMPLE_SIZE-1]; //--- normalize prices x_norm3-=ExtMin3; x_norm3/=(ExtMax3-ExtMin3); //--- run the inference if(!OnnxRun(ExtHandle3,ONNX_NO_CONVERSION,x_norm3,output_data3)) { Print("OnnxRun"); //ExtPredictedClass=-1; return; } //--- denormalize the price from the output value predicted3=output_data3[0]*(ExtMax3-ExtMin3)+ExtMin3; //--- classify predicted price movement //return predicted2; } //+------------------------------------------------------------------+ //| Get minimal and maximal Close for last 120 values | //+------------------------------------------------------------------+ void GetMinMax(void) { vectorf closeMN; closeMN.CopyRates(symbol1,Periodo,COPY_RATES_CLOSE,0,SAMPLE_SIZE); ExtMin=closeMN.Min(); ExtMax=closeMN.Max(); } //+------------------------------------------------------------------+ //| Get minimal and maximal Close for last 120 values | //+------------------------------------------------------------------+ void GetMinMax2(void) { vectorf closeMN2; closeMN2.CopyRates(symbol2,Periodo,COPY_RATES_CLOSE,0,SAMPLE_SIZE); ExtMin2=closeMN2.Min(); ExtMax2=closeMN2.Max(); } //+------------------------------------------------------------------+ //| Get minimal and maximal Close for last 120 values | //+------------------------------------------------------------------+ void GetMinMax3(void) { vectorf closeMN3; closeMN3.CopyRates(symbol3,Periodo,COPY_RATES_CLOSE,0,SAMPLE_SIZE); ExtMin3=closeMN3.Min(); ExtMax3=closeMN3.Max(); } //+------------------------------------------------------------------+ //| Symbols class returns both pairs of a symbol | //+------------------------------------------------------------------+ class SymbolProcessor { public: // Método para obtener los primeros tres caracteres de un símbolo dado string GetFirstThree(string symbol) { return StringSubstr(symbol, 0, 3); } // Método para obtener los últimos tres caracteres de un símbolo dado string GetLastThree(string symbol) { if(StringLen(symbol) >= 3) return StringSubstr(symbol, StringLen(symbol) - 3, 3); else return ""; // Retorna un string vacío si el símbolo es demasiado corto } }; //+------------------------------------------------------------------+ //| Calculate total profit from all open positions for the current symbol //+------------------------------------------------------------------+ double CalculateCurrentArbitrageProfit() { double totalProfit = 0.0; int totalPositions = PositionsTotal(); // Get the total number of open positions // Loop through all open positions for(int i = 0; i < totalPositions; i++) { // Get the ticket of the position at index i ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) // Select the position by its ticket { // Add the profit of the current position to the total profit totalProfit += PositionGetDouble(POSITION_PROFIT); //Print("totalProfit ", totalProfit); } } return totalProfit; // Return the total profit of all open positions } // Función para cerrar todas las órdenes void CloseAllOrders() { string symbols[] = {symbol1, symbol2, symbol3}; for(int i = 0; i < ArraySize(symbols); i++) { if(ExtTrade.PositionClose(symbols[i], 3)) Print("Posición cerrada correctamente para ", symbols[i]); else Print("Error al cerrar posición para ", symbols[i], ": Error", GetLastError()); } // Resetea tickets y ordenes ticket1 = 0; ticket2 = 0; ticket3 = 0; ticket11 = 0; ticket22 = 0; ticket33 = 0; Order = 0; Print("Todas las órdenes están cerradas"); } //+------------------------------------------------------------------+ //| Check and close orders funcion | //+------------------------------------------------------------------+ // Función para verificar y cerrar órdenes void CheckAndCloseOrders() { double currentProfit = CalculateCurrentArbitrageProfit(); // Condiciones para cerrar las órdenes if((currentProfit >= targetProfit || currentProfit <= maxLoss)) { CloseAllOrders(); // Cierra todas las órdenes Print("Todas las órdenes cerradas. Beneficio/Pérdida actual: ", currentProfit); } } //+------------------------------------------------------------------+ //| Get order volume function | //+------------------------------------------------------------------+ double GetOrderVolume(int ticket) { if(PositionSelectByTicket(ticket)) { double volume = PositionGetDouble(POSITION_VOLUME); return volume; } else { Print("No se pudo seleccionar la posición con el ticket: ", ticket); return 0; // Retorna 0 si no se encuentra la posición } } //+------------------------------------------------------------------+ // Function to get the price and calculate SL dynamically double CalculateSL(bool isBuyOrder,double entryPrice,string simbolo) { double pointSize = SymbolInfoDouble(simbolo, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(simbolo, SYMBOL_DIGITS); double pipSize = pointSize * 10; if(isBuyOrder) { sl = NormalizeDouble(entryPrice - StopLossPips * pipSize, digits); tp = NormalizeDouble(entryPrice + TakeProfitPips * pipSize, digits); } else { sl = NormalizeDouble(entryPrice + StopLossPips * pipSize, digits); tp = NormalizeDouble(entryPrice - TakeProfitPips * pipSize, digits); } return sl; } //+------------------------------------------------------------------+ // Function to get the price and calculate TP dynamically double CalculateTP(bool isBuyOrder,double entryPrice, string simbolo) { double pointSize = SymbolInfoDouble(simbolo, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(simbolo, SYMBOL_DIGITS); double pipSize = pointSize * 10; if(isBuyOrder) { sl = NormalizeDouble(entryPrice - StopLossPips * pipSize, digits); tp = NormalizeDouble(entryPrice + TakeProfitPips * pipSize, digits); } else { sl = NormalizeDouble(entryPrice + StopLossPips * pipSize, digits); tp = NormalizeDouble(entryPrice - TakeProfitPips * pipSize, digits); } return tp; } //+------------------------------------------------------------------+ // Function to handle errors and retry bool TryOrderSend(MqlTradeRequest &request, MqlTradeResult &result) { for(int attempts = 0; attempts < 5; attempts++) { if(OrderSend(request, result)) { return true; } else { Print("Failed to send order on attempt ", attempts + 1, ": Error ", GetLastError()); Sleep(1000); // Pause before retrying to avoid 'context busy' errors } } return false; } //+------------------------------------------------------------------+
Erläuterung des Expert Advisors
Die Strategie
Wir alle wissen jetzt, was Dreiecksarbitrage ist, aber ich habe dem Code eine Mindestdifferenz zwischen der Vorhersage und dem tatsächlichen Abschlusswert hinzugefügt, diese Differenz ist die prozentuale Kursänderung zwischen diesen beiden Werten, und Sie können diesen Betrag mit dieser Eingabe ändern:
input double perVar = 0.005; // Percentage of variation to make orders
*** Beachten Sie, dass der Code diese Logik enthält: ***
EUR USD | EUR |^(-1)
---- x --- x | ---- |
USD JPY | JPY |
und alles ist an diese Logik angepasst. Wenn Sie also andere Paare verwenden, müssen alle Dinge geändert werden.
Ich werde ein weiteres Beispiel anhängen (EURUSD - GBPUSD - EURGBP), damit Sie die Änderungen sehen können, die diese Logik verwenden:
EUR | GBP |^(-1) | EUR |^(-1)
---- x | --- | x | ---- |
USD | USD | | GBP |
Die gesamte Strategie basiert darauf, dass man bei einer Multiplikation dieser Logik, wenn sie >1 ist, in die rechte Richtung multiplizieren kann, und wenn sie <1 ist, kann man in die linke Richtung multiplizieren.
Die Strategie dieses Expert Advisors besteht jedoch darin, dass wir anstelle der tatsächlichen Preise die vorhergesagten Preise verwenden werden.
Nach dieser Logik multipliziert man Preise, wenn man sie nach rechts multipliziert, und multipliziert man sie mit dem Kehrwert, wird es zu einer Division. Und auf der linken Seite geht es anders herum. Sie können diese Änderungen im Code suchen.
Die Losgrößen müssen entsprechend dem Höchstkurs gewählt werden, deshalb liegt in diesem EUR-USD-JPY das Mindestlos bei 2 oder drei Losen.
Die Logik für die Losgrößen ist folgende:
double lotSize2 = lotSize * predicted / predicted2; double lotSize3 = lotSize * predicted / predicted3;
wobei predicted der prognostizierte Preis für EURUSD, predicted2 der Preis für USDJPY und predicted3 der Preis für EURJPY ist.
Der letzte Teil dieses Vorgangs besteht darin, die Losgröße an die Anforderungen des Brokers anzupassen.
lotSize2 = NormalizeDouble(lotSize2, 2); lotSize3 = NormalizeDouble(lotSize3, 2);
Beispiel
Für dieses Beispiel werden wir die Paare von EUR-USD-JPY verwenden.
Mit dieser Logik
EUR USD | EUR |^(-1)
---- x --- x | ---- |
USD JPY | JPY |
Wir werden bis zum 3. April mit einem Zeitraum von 200000 Minuten (Stichprobengröße) trainieren und testen. Damit haben wir etwa 17 Tage Vorhersagezeit.
Der Test im Strategietester wird also vom 3. April bis zum 21. desselben Monats laufen, und wir werden den 1-Minuten-Zeitrahmen auswählen.
Für den ersten Test werden wir diese Eingaben und Einstellungen verwenden (wählen Sie die Symbole sorgfältig aus, da sie dem EA (OONX) hinzugefügt werden):
und das sind die Ergebnisse:
So lautet das Resümee des EAs zu den Ergebnissen dieses Backtests:
Dieser Backtesting-Bericht liefert eine detaillierte Analyse der Performance einer Handelsstrategie über einen bestimmten Zeitraum anhand historischer Daten. Die Strategie begann mit einer anfänglichen Einlage von $100.000 und endete mit einem Gesamtnettogewinn von $395,72, trotz eines erheblichen Bruttoverlustes von $1.279,06 im Vergleich zum Bruttogewinn von $1.674,78. Der Gewinnfaktor von 1,31 bedeutet, dass der Bruttogewinn den Bruttoverlust um 31 % übersteigt, was die Fähigkeit der Strategie unterstreicht, mehr Gewinn als Verlust zu erzielen.
Die Strategie führte insgesamt 96 Handelsgeschäfte aus, wobei sich die Gewinn- und Verlustpositionen in etwa die Waage hielten, wie die Prozentsätze der gewonnenen Verkaufspositionen (38,30%) und der gewonnenen Kaufpositionen (46,94%) zeigen. Die Strategie wies insgesamt mehr Verlustgeschäfte auf (55 bzw. 57,29 %), was darauf hindeutet, dass die Auswahl der Handelsgeschäfte oder die Ausstiegsstrategien verbessert werden müssen.
Der Erholungsfaktor von 0,84 deutet auf ein moderates Risiko hin, da die Strategie 84 % des maximalen Drawdowns zurückgewinnt. Außerdem war der maximale Drawdown mit 358,78 $ (0,36 % des Kontos) relativ hoch, was darauf hindeutet, dass die Strategie zwar profitabel war, aber auch erhebliche Rückgänge hinnehmen musste, von denen sie sich erst wieder erholen musste.
Der Backtest zeigte auch erhebliche Rückgänge des Eigenkapitals, mit einem maximalen Rückgang des Eigenkapitals von 0,47 % des Kontos. In Verbindung mit dem Sharp-Ratio von 21,21 deutet dies darauf hin, dass die Renditen deutlich höher waren als das eingegangene Risiko, was positiv ist. Die niedrigen durchschnittlichen Gewinne in Folge (3 Positionen) und die höheren durchschnittlichen Verluste in Folge (2 Positionen) deuten jedoch darauf hin, dass die Strategie von einer Verfeinerung ihres Ansatzes profitieren könnte, um die Konsistenz der Gewinnpositionen zu erhalten.
Schlussfolgerung
In diesem Artikel erläutern wir das spannende Konzept der Dreiecksarbitrage mit Vorhersagen, und zwar mit Hilfe der nutzerfreundlichen MT5-Plattform und der Python-Programmierung. Stellen Sie sich vor, Sie hätten eine Geheimformel, mit der Sie ein intelligentes Spiel des Währungsumtauschs spielen können, indem Sie Dollar in Euro, dann in Yen und wieder in Dollar umwandeln, mit dem Ziel, am Ende mehr zu haben als zu Beginn. Es handelt sich dabei nicht um Magie, sondern um spezielle Prognosemodelle namens ONNX und Dreiecksarbitrage, die aus vergangenen Devisenkursen lernen, um künftige Kurse vorherzusagen und Ihre Handelsbewegungen zu steuern.
Der Artikel ist sehr hilfreich bei der Einrichtung, denn er zeigt Ihnen, wie Sie alle benötigten Tools wie Python und Visual Studio Code installieren und wie Sie Ihren Computer für die Tests vorbereiten. Der Artikel erklärt in einfachen Worten und stellt sicher, dass Sie wissen, wie Sie die Strategie anpassen können, egal ob Ihr Handelskonto einfach oder ausgefallen ist.
Insgesamt ist dieser Artikel eine fantastische Ressource für jeden, der in den Forex-Handel einsteigen möchte und dabei einige der intelligentesten verfügbaren Technologien nutzt. Es führt Sie durch die Feinheiten der Einrichtung und des Betriebs Ihres Handelssystems, sodass Sie dank der neuesten Entwicklungen im Bereich der künstlichen Intelligenz und des maschinellen Lernens den Handel mit einem Vorsprung ausprobieren können. Egal, ob Sie ein Neuling im Programmieren oder im Handel sind, dieser Leitfaden unterstützt Sie und zeigt Ihnen Schritt für Schritt, wie Sie den digitalen Sprung in den automatisierten Handel schaffen.
Ich hoffe, Sie haben genauso viel Spaß beim Lesen dieses Artikels wie ich beim Schreiben.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/14873





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.