English Deutsch
preview
予測による三角裁定取引

予測による三角裁定取引

MetaTrader 5トレーディング | 8 7月 2024, 11:30
21 0
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera

はじめに

この記事は三角裁定戦略に関するものです。2つの三角裁定の例があり、これはダウンロードに追加されているディープラーニングモデルと、同じくダウンロードに追加されているEAを使用しておこなわれます。三角裁定取引は、為替レートの不一致を利用して無リスクの利益を生み出します。


三角裁定取引とは何か?

裁定は非常に不思議なもので、スポーツ賭博のブックメーカーからは禁止されています。レアル・マドリードが2024年のチャンピオンになる確率が1.25で、ボルシア・ドルトムントが3.60だとすると、マドリードが勝つ確率は100/1.25=80%、ボルシアが勝つ確率は27.7%ということになります。この2つを足すと、107.7%になります。これは、ブッキーがお金を勝ちたいからであり、100%以上は彼らの手数料です。しかし、ブッキー2号がボルシアの勝率19%、オッズ5.26を提示したとしましょう。そうすれば、ブッキー1番でレアル・マドリードに、ブッキー2番でボルシアに賭けることができ、それぞれのチームに適切な量を賭ければ、どちらも100%未満しか加算されないため、試合で勝つことができます。スポーツ賭博で禁止されている理由と、裁定とは何かを簡単に説明しましょう。

あなたが「合法的」な人間で、裁定をすることでスポーツ口座を閉鎖されたくないと思っているとします。マドリードに賭けたとしても、引き分けの場合は試合開始70分を待ったり、ボルシアに勝つためにレアル・マドリードの得点を待ったりすれば、「合法的」な裁定ができることを知っているはずです。...少しリスクが高く見えますが、ここでディープラーニングを活用することができます。レアル・マドリードが得点することがわかっているので、98%の確率でこのオッズを得ることができます(予測と実際の値の間の共位相でこれがわかります)。これがディープラーニングと裁定の新しいところです。

さて、裁定とは何か、ディープラーニングの助けを借りてどのように勝率を上げるかがわかったところで、三角裁定とは何かと言えば、それは、裁定ですが、3つのペアを使用するものです。なぜかというと、FXや暗号通貨ではA/Bという記号に対してこの式が使用されるからで、これを解くには3つの方程式(A/B )*(B/C)*(C/A)が必要で、これが1以上のときは右掛け、1未満のときは左掛けになるからです。


なぜすべての口座で三角裁定取引ができるのか、できないのか?

スプレッドがゼロの口座であれば、三角裁定取引は1秒以内におこなわれます。スプレッドがあれば、このような時にスプレッドに勝つことは不可能です。しかし、前にも言ったように、このEAはどちらの方法でも本当に儲かるので心配しないでください。私の口座はスプレッドがゼロではないので、この記事ではスプレッドのある例を紹介します。


このEAには何が必要なのか?

このEAは、Pythonで作成された予測をONNXモデルに使用し、MT5のEAで使用します。このため、すべての人がこのEAを使えるように、全プロセスを確認するつもりです。ONNXモデルの作り方を知っていれば、EAにスキップできます。

初回はインストールが必要です:

- Python3.10

マイクロソフトのストアで、インストールをクリックしてください。

Python3.10

- Visual Studio Code

これはマイクロソフトのストアで見つけることができ、インストールをクリックするだけで、すべてやってくれます。

VSC

この後、ここからVisual Studio 2019またはC++をインストールする必要があります(pythonのライブラリが1つインストールされるよう求められる):

https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022

これが完了したら、python scritpsフォルダをパス変数に追加する必要があります。

また、PATHEXTに「.py」を追加する必要があります。

これが終わったら、次のようにライブラリをインストールします。

VSCを開き、端末→新規端末に移動します。

VSCがpythonに拡張機能をインストールするように聞かれたら、[0K]をクリックしてください。 

次をコピー&ペーストしてください(そしてEnterキーを押してください)。

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

エラーはないはずです。あれば、ここで質問してください。

これが全てインストールされ、エラーが無ければ、.py テストモデルに進むことができます。この例をコピー&ペーストしてみます。

# 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)
"""

この.pyは3つのONNXを作成し、グラフとデータも作成します。

データはtxtファイルで提供され、各数値はそれぞれRMSE、MSE、R2を表します。

このスクリプトを実行する前に、銘柄、サンプルサイズ、時間枠、および終了日を指定する必要があります(逆方向の期間をカウントするためにから)。

オプションの変数は文字列で、M1 Ticksやend_dateなど...onnxファイルやグラフ、データを保存したいものを追加できます。

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)

ストラテジーテスターでテストしたい場合は、日付を好きなように変更してください。取引したい場合は、このend_dateを使用すればよいです。

end_date = datetime.now()

*** もし、スプレッド以外の口座で取引している場合は、期間の代わりにティックを使用してみてください:***

eurusd_rates = mt5.copy_rates_from(symbol, timeframe ,  end_date, sample_size)

上の行を次の行で置き換えます。

eurusd_rates = mt5.copy_ticks_from(symbol, end_date, sample_size, mt5.COPY_TICKS_ALL)

ここでBidとAskのティックが表示されます。ティック数に制限があると思いますので、もっとティックが必要な場合は、これで銘柄からすべてのティックをダウンロードできます。 無料の銘柄からすべてのデータをダウンロードします。


.pyファイルを実行するには、VSCで開き、(MT5を開いた状態で)[実行] -> [デバッグなしで実行]を押すだけです。

そして終了を待ちます。

グラフ、txt、ONNXファイルの束ができあがります。ONNXファイルをMQL5/Filesフォルダに保存し、EAコードで同じパスを指定する必要があります。

この行のおかげで、まだその仕事ができます。

# 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)

しかし、多くのONNXファイルを他のフォルダの中に置きたい場合は、パスを指定することが重要です。

この.pyはこのような画像をエクスポートします。

usjpyエラー

usdjpy 1

usdjpy 2

usdjpy 3

JPY 4

このグラフは、RMSE、MSE、R2値で示されています。 

0.023019903957086384
0.0005299159781934813
0.999707563612641 

これだけで、モデルが過剰適合か過少適合かを知ることができます。 

この場合、次のようになります。

RMSEは残差(予測誤差)の標準偏差を測定します。残差は、データ点が回帰直線からどれだけ離れているかの尺度であり、RMSEは、これらの残差がどれだけ広がっているかの尺度です。言い換えれば、データが最良の適合の線の周りにどれだけ集中しているかを示しています。

RMSEの値が小さいほど、適合度が高いことを示します。RMSEの値は非常に小さく、モデルがデータセットに非常によくフィットしていることを示唆しています。

MSEはRMSEのようなものですが、平均化する前に誤差を2乗するため、より大きな誤差に高い重みを与えます。これは見積もりの​​質を測るもう一つの指標です。常に非負であり、ゼロに近いほど良い値です。

MSE値が非常に小さいことは、モデルの予測値が実際のデータ点に非常に近いことをさらに裏付けています。

R2は,回帰モデルにおいて独立変数または変数によって説明される従属変数の分散の割合を表す統計的尺度です。𝑅2値が1の場合、回帰予測がデータに完全に適合することを示します。

私たちの R2値は1に非常に近く、モデルが平均値付近の変動をほぼすべて説明していることを示しています。

全体として、これらの指標は、モデルがデータセットに対する予測またはフィッティングにおいて非常に優れた性能を発揮していることを示唆しています。

そして、過剰適合かどうかを知るために、例えばこの場合は2番目のグラフを使用します。

このグラフをもとに分析してみましょう。

  1. Train Loss(青線):

    • この線は、最初は急降下を示し、モデルが訓練データセットから素早く学習していることを示しています。反復が進むにつれて、train lossは減少し続けますが、その速度は遅く、これはモデルが最小値に向かって収束し始めるときの典型的な速度です。
  2. Validation Loss(緑線):

    • Validation Lossは極めて低く、訓練の過程を通してかなり安定しています。これは、モデルが訓練データをただ記憶しているのではなく、うまく汎化していることを示唆しています。この小さな変動は、検証セットの性能が反復によって変動することを示しているが、非常に狭い範囲にとどまっています。

全体として、このグラフは、収束と汎化が非常にうまくいっていることを示唆しています。検証セットが一般的な問題空間を代表するものであると仮定すれば、このモデルは未経験のデータに対しても良好な結果を示すはずだからです。

これがすべて完了したら、EAに移りましょう。


予測付き三角裁定用EA

//+------------------------------------------------------------------+
//|                          ONNX_Triangular EURUSD-USDJPY-EURJPY.mq5|
//|             Copyright 2024, Javier S. Gastón de Iriarte Cabrera. |
//|                      https://www.mql5.com/ja/users/jsgaston/news |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2024, Javier S. Gastón de Iriarte Cabrera."
#property link        "https://www.mql5.com/ja/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;
  }
//+------------------------------------------------------------------+



EAの説明

戦略

三角裁定が何であるかはもうお分かりだと思いますが、予測値と実際のクローズ値との差の最小値をコードに追加しました。この差は、この2つの値の間のレート変化率であり、この入力でその値を変更することができます。

input double perVar = 0.005; // Percentage of variation to make orders

*** コードには次のようなロジックがあることに注意してください。***

EUR       USD     | EUR  |^(-1)
----   x  ---  x  | ---- |
USD       JPY     | JPY  |

そのため、他のペアを使用する場合は、すべてのペアを変更しなければなりません。

このロジックを使用した他の例(EURUSD - GBPUSD - EURGBP)を添付します。

EUR      | GBP |^(-1)    | EUR  |^(-1)
----   x | --- |      x  | ---- |
USD      | USD |         | GBP  |

戦略全体は、そのロジックを掛け合わせるとき、1以上であれば右方向に掛け合わせることができ、1未満であれば左方向に掛け合わせることができるということに基づいています。

しかし、このEAの戦略は、実際の価格を使用する代わりに、予測価格を使用することです。

このロジックは、右に掛け合わせれば価格を掛け合わせ、逆に乗算すれば除算になることを意味します。そして、左側は反対です。この変更をコードに求めることができます。

ロットサイズは最高価格に応じて選択する必要があります。そのため、このEUR-USD-JPYでは最小ロットは約2ロットまたは3ロットになります。 


ロットサイズの論理はこうです。

   double lotSize2 = lotSize * predicted / predicted2;   
   double lotSize3 = lotSize * predicted / predicted3;

 ここでpredictedはEURUSDの予測価格、predicted2はUSDJPYの価格、predicted3はEURJPYの価格です。

この最後の部分は、ロットサイズをブローカーの要求に合わせて正規化することです。

lotSize2 = NormalizeDouble(lotSize2, 2); 
lotSize3 = NormalizeDouble(lotSize3, 2);



この例では、EUR-USD-JPYペアを使用します。

次のロジックで

EUR       USD     | EUR  |^(-1)
----   x  ---  x  | ---- |
USD       JPY     | JPY  |

4月3日まで、200000分の期間(サンプルサイズ)で訓練とテストをおこないます。これで約17日間の予想が可能になります。

ストラテジーテスターでのテストは4月3日から同月21日まで実行し、時間枠は1分を選択します。

最初のテストでは、この入力と設定を使用します(EA OONXに追加される銘柄を注意深く選択します)。

入力



設定


その結果がこれです。

グラフ


バックテスト


このバックテストの結果を受けてIAが再開します。

このバックテストレポートは、過去のデータを使用して、一定期間における取引戦略のパフォーマンスの詳細な分析を提供します。このストラテジーは100,000ドルの初回入金で始まり、1,674.78ドルの粗利益に対して1,279.06ドルの大幅な粗利益損失にもかかわらず、395.72ドルの合計純利益で終了しました。プロフィットファクター1.31は、売上総利益が売上総損失を31%上回ったことを示しており、この戦略が損失を上回る利益を生み出す能力を有していることを示しています。

このストラテジーは合計96回の取引をおこない、勝ち取引と負け取引の割合は、ショート取引の勝率(38.30%)とロング取引の勝率(46.94%)が示すように、ほぼ同率でした。この戦略では全体的に負け取引が多く(55件、57.29%)、取引の選択や出口戦略の改善の必要性が強調されました。

回収率0.84は中程度のリスクを示唆しており、最大ドローダウンの84%を回収しています。さらに、最大ドローダウンは358.78ドル(口座の0.36%)と比較的高く、このストラテジーが利益を上げていた一方で、回復しなければならない大幅な下落にも直面していたことを示しています。

バックテストでは、エクイティの面でも大幅なドローダウンが見られ、エクイティの最大ドローダウンは口座の0.47%でした。これは、21.21というシャープレシオと相まって、リターンが取ったリスクよりもかなり高いことを示唆しており、プラスです。しかし、平均連勝回数が少ない(3回)のに対し、平均連敗回数が多い(2回)ことから、当戦略は勝ち取引の一貫性を維持するためにアプローチを改良することで利益を得られる可能性があります。


結論

この記事では、ユーザーフレンドリーなMT5プラットフォームとPythonプログラミングを通じて、予測を使用した三角裁定のエキサイティングな概念を解説しました。ドルからユーロへ、そして円からドルへと、為替ゲームをスマートにこなし、最終的にはスタート時よりも多くのお金を手にすることができる秘密の方程式をご想像ください。これは魔法ではなく、ONNXと三角裁定取引と呼ばれる特別な予測モデルを使用することで、過去の通貨価格から学習して将来の通貨価格を予測し、取引の動きを導きます。

PythonやVisual Studio Codeのような必要なツールのインストール方法や、テストを開始するためのコンピュータの準備方法など、すべてをセットアップするのに非常に役立ちます。この記事では、取引口座が基本的なものであるか否かにかかわらず戦略を調整する方法がわかるように確認しながら、簡単な言葉で説明しました。

全体的に、この記事は、利用可能な最もスマートな技術のいくつかを使用して、外国為替取引のゲームに入ることを探しているすべての人のための素晴らしいリソースです。最新の人工知能と機械学習のおかげで、エッジの効いた取引に挑戦することができます。最新の人工知能と機械学習のおかげで、優位性のある取引に挑戦することができます。コーディングや取引の初心者であろうとなかろうと、このガイドは読者の背中を押してくれます。

私がこの記事を作るのを楽しんだように、読者がこの記事を楽しんで読んでくれることを願っています。


MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/14873

予測による統計的裁定取引 予測による統計的裁定取引
統計的裁定取引について調べ、共和分で相関する銘柄をPythonで検索し、ピアソン係数の指標を作成し、PythonとONNX モデルで予測をおこなって統計的裁定取引を行うEAを作成します。
純粋なMQL5におけるエネルギーベースの学習を用いた特徴量選択アルゴリズム 純粋なMQL5におけるエネルギーベースの学習を用いた特徴量選択アルゴリズム
この記事では、「FREL:A stable feature selection algorithm」と題された学術論文に記載された、Feature Weighting as Regularized Energy-Based Learningと呼ばれる特徴量選択アルゴリズムの実装を紹介します。
古典的戦略の再構築:原油 古典的戦略の再構築:原油
この記事では、教師あり機械学習アルゴリズムを活用することで、古典的な原油取引戦略を強化することを目的として、原油取引戦略を再検討します。ブレント原油価格とWTI原油価格のスプレッドに基づいて、将来のブレント原油価格を予測する最小二乗モデルを構築します。目標は、将来のブレント価格変動の先行指標を特定することです。
知っておくべきMQL5ウィザードのテクニック(第20回):関数同定問題 知っておくべきMQL5ウィザードのテクニック(第20回):関数同定問題
関数同定問題は、研究対象のデータセットをマッピングする基本モデルがどのようなものであるかについて、最小限の仮定から始める回帰の形式です。ベイズ法やニューラルネットワークでも実装可能ですが、ここでは遺伝的アルゴリズムによる実装が、MQL5ウィザードで使用可能なExpertSignalクラスのカスタマイズにどのように役立つかを見ていきます。