
Reimaginando las estrategias clásicas (Parte V): Análisis de múltiples símbolos en USDZAR
Introducción
Existen innumerables formas de integrar la IA en nuestras estrategias de negociación, pero, por desgracia, no podemos evaluar cada una de ellas antes de decidir a cuál confiar nuestro capital. Hoy volvemos a examinar una estrategia de negociación popular de análisis de símbolos múltiples para determinar si podemos mejorar la estrategia utilizando IA. Le proporcionaremos la información que necesita para llegar a una decisión informada sobre si esta estrategia es adecuada para su perfil de inversor.
Visión general de la estrategia de negociación
Las estrategias comerciales que emplean el análisis de múltiples símbolos se basan principalmente en la correlación que se observa entre la canasta de símbolos. La correlación es una medida de dependencia lineal entre dos variables. Sin embargo, la correlación a menudo se confunde con una indicación de una relación entre dos variables, lo que no siempre es el caso.
Los comerciantes de todo el mundo han aprovechado su comprensión fundamental de los activos correlacionados para guiar sus decisiones de inversión, medir los niveles de riesgo e incluso como señal de salida. Por ejemplo, consideremos el par de divisas USDZAR. El gobierno estadounidense es uno de los principales exportadores de petróleo del mundo, mientras que, por otro lado, el gobierno sudafricano es el mayor exportador de oro del mundo.
Dado que estos productos básicos contribuyen con una proporción significativa del Producto Interno Bruto de estos dos países, uno podría esperar naturalmente que los niveles de precios de estos productos básicos puedan explicar parte de la variación en el par de divisas USDZAR. Así que, si el petróleo tiene un mejor desempeño que el oro en el mercado al contado, podemos esperar que el dólar sea más fuerte que el Rand y viceversa.
Descripción general de la metodología
Para evaluar la relación, exportamos todos nuestros datos de mercado desde nuestra terminal MetaTrader 5 utilizando un script escrito en MQL5. Entrenamos varios modelos utilizando 2 grupos de posibles entradas para los modelos:
- Cotizaciones ordinarias de OHLC en el USDZAR.
- Una combinación de precios del petróleo y el oro.
De los datos recopilados se desprende que el petróleo tiene niveles de correlación más fuertes con el par de divisas UDZAR que el oro.
Como nuestros datos estaban en escalas diferentes, estandarizamos y normalizamos los datos antes del entrenamiento. Realizamos una validación cruzada de 10 pasos sin mezcla aleatoria para comparar nuestra precisión en los diferentes conjuntos de entradas.
Nuestros hallazgos sugieren que el primer grupo puede producir el menor error. El modelo con mejores resultados fue la regresión lineal con datos de OHLC ordinarios. Sin embargo, en el último grupo, 2, el modelo con mejor rendimiento fue el algoritmo KNeigborsRegressor.
Realizamos con éxito el ajuste de hiperparámetros utilizando 500 iteraciones de una búsqueda aleatoria sobre 5 parámetros del modelo. Realizamos pruebas de sobreajuste comparando los niveles de error de nuestro modelo personalizado con un modelo predeterminado en un conjunto de validación que se mantuvo durante la optimización; después de entrenar ambos modelos en conjuntos de entrenamiento equivalentes, superamos al modelo predeterminado en el conjunto de validación.
Finalmente, exportamos nuestro modelo personalizado al formato ONNX y lo integramos en nuestro Asesor Experto en MQL5.
Extracción de datos
He creado un script útil para ayudarle a extraer los datos necesarios de su terminal MetaTrader 5, simplemente arrastre y suelte el script en el símbolo deseado y extraerá los datos para usted y los colocará en la ruta: “\MetaTrader 5\MQL5\Files\..”
//+------------------------------------------------------------------+ //| ProjectName | //| Copyright 2020, CompanyName | //| http://www.companyname.net | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" #property script_show_inputs //---Amount of data requested input int size = 100000; //How much data should we fetch? //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { //---File name string file_name = "Market Data " + Symbol() + ".csv"; //---Write to file int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(int i= size;i>=0;i--) { if(i == size) { FileWrite(file_handle,"Time","Open","High","Low","Close"); } else { FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i), iOpen(Symbol(),PERIOD_CURRENT,i), iHigh(Symbol(),PERIOD_CURRENT,i), iLow(Symbol(),PERIOD_CURRENT,i), iClose(Symbol(),PERIOD_CURRENT,i)); } } } //+------------------------------------------------------------------+
Análisis exploratorio de datos en Python
Comenzamos importando las bibliotecas estándar.
#Libraries import pandas as pd import numpy as np import seaborn as sns
Ahora leamos los datos que extrajimos anteriormente.
#Dollar VS Rand USDZAR = pd.read_csv("/home/volatily/market_data/Market Data USDZAR.csv") #US Oil USOIL = pd.read_csv("/home/volatily/market_data/Market Data US Oil.csv") #SA Gold SAGOLD = pd.read_csv("/home/volatily/market_data/Market Data XAUUSD.csv")
Inspeccionando los datos.
USOIL
Figura 1: Nuestros datos se están ejecutando hacia atrás en el tiempo.
Tenga en cuenta que nuestras marcas de tiempo se ejecutan desde cerca del presente y más atrás en el pasado, esto no es deseable para tareas de aprendizaje automático. Invirtamos el orden de los datos para que podamos hacer pronósticos hacia el futuro y no hacia el pasado.
#Format the data USDZAR = USDZAR[::-1] USOIL = USOIL[::-1] SAGOLD = SAGOLD[::-1]
Antes de poder fusionar nuestros conjuntos de datos, primero asegurémonos de que todos utilicen la columna de fecha como índice. Al hacerlo, podemos estar seguros de que solo seleccionamos los días compartidos por todos los conjuntos de datos, en el orden cronológico correcto.
#Set the indexes USOIL = USOIL.set_index("Time") SAGOLD = SAGOLD.set_index("Time") USDZAR = USDZAR.set_index("Time")
Fusionando los conjuntos de datos.
#Merge the dataframes merged_df = pd.merge(USOIL,SAGOLD,how="inner",left_index=True,right_index=True,suffixes=(" US OIL"," SA GOLD")) merged_df = pd.merge(merged_df,USDZAR,how="inner",left_index=True,right_index=True)
Definición del horizonte de previsión.
#Define the forecast horizon look_ahead = 10
El objetivo será el precio de cierre futuro del par USDZAR, también incluiremos un objetivo binario para fines de visualización.
#Label the data merged_df["Target"] = merged_df["Close"].shift(-look_ahead) merged_df["Binary Target"] = 0 merged_df.loc[merged_df["Close"] < merged_df["Target"],"Binary Target"] = 1
Descartamos todas las filas vacías.
#Drop empty rows
merged_df.dropna(inplace=True)
Observe los niveles de correlación.
#Let's observe the correlation levels merged_df.corr()
Figura 2: Niveles de correlación en nuestro conjunto de datos.
El petróleo parece estar demostrando niveles de correlación relativamente más fuertes con el par USDZAR, aproximadamente -0,4, mientras que el oro tiene niveles de correlación relativamente más débiles con el par de divisas, aproximadamente 0,1. Es importante recordar que la correlación no siempre implica que haya una relación entre las variables, a veces la correlación resulta de una causa común que afecta a ambas variables.
Por ejemplo, históricamente, la relación entre el oro y el dólar estaba invertida. Cada vez que el dólar se depreciaba, los comerciantes retiraban su dinero del dólar y lo invertían en oro. Históricamente, esto provocó que los precios del oro subieran cada vez que el dólar tenía un desempeño pobre. Entonces, la causa común, en este ejemplo simple, serían los comerciantes que participaban en ambos mercados.
Los diagramas de dispersión nos ayudan a visualizar la relación entre dos variables, por lo que creamos un diagrama de dispersión de los precios del petróleo frente a los precios del oro y coloreamos los puntos dependiendo de si los niveles de precios del USDZAR se apreciaron (rojo) o se depreciaron (verde). Como se puede ver, no hay un nivel claro de separación en los datos. De hecho, ninguno de los gráficos de dispersión que creamos sugiere una relación fuerte.
Figura 3: Diagrama de dispersión de los precios del oro frente a los precios del petróleo.
Figura 4: Diagrama de dispersión de los precios del petróleo frente al precio de cierre del USDZAR.
Figura 5: Diagrama de dispersión de los precios del oro frente al cierre del USDZAR.
Modelando la relación
Restablezcamos el índice de nuestro conjunto de datos para que podamos realizar una validación cruzada.
#Reset the index
merged_df.reset_index(inplace=True)
Ahora importaremos las bibliotecas que necesitamos para modelar la relación en los datos.
#Import the libraries we need from sklearn.linear_model import LinearRegression from sklearn.linear_model import Lasso from sklearn.ensemble import GradientBoostingRegressor from sklearn.ensemble import RandomForestRegressor from sklearn.ensemble import AdaBoostRegressor from sklearn.ensemble import BaggingRegressor from sklearn.neighbors import KNeighborsRegressor from sklearn.svm import LinearSVR from sklearn.neural_network import MLPRegressor from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import root_mean_squared_error from sklearn.preprocessing import RobustScaler
Definir los predictores y el objetivo.
#Define the predictors normal_predictors = ["Open","High","Low","Close"] oil_gold_predictors = ["Open US OIL","High US OIL","Low US OIL","Close US OIL","Open SA GOLD","High SA GOLD","Low SA GOLD","Close SA GOLD"] target = "Target"
Escalando los datos.
#Scale the data all_predictors = normal_predictors + oil_gold_predictors scaler = RobustScaler() scaled_data = pd.DataFrame(scaler.fit_transform(merged_df.loc[:,all_predictors]),columns=all_predictors,index=np.arange(0,merged_df.shape[0]))
Inicializando los modelos.
#Now prepare the models models = [ LinearRegression(), Lasso(), GradientBoostingRegressor(), RandomForestRegressor(), AdaBoostRegressor(), BaggingRegressor(), KNeighborsRegressor(), LinearSVR(), MLPRegressor(hidden_layer_sizes=(10,5),early_stopping=True), MLPRegressor(hidden_layer_sizes=(50,15),early_stopping=True) ] columns = [ "Linear Regression", "Lasso", "Gradient Boosting Regressor", "Random Forest Regressor", "AdaBoost Regressor", "Bagging Regressor", "KNeighbors Regressor", "Linear SVR", "Small Neural Network", "Large Neural Network" ]
Crear una instancia del objeto de validación cruzada de series de tiempo.
#Prepare the time-series split object splits = 10 tscv = TimeSeriesSplit(n_splits=splits,gap=look_ahead)
Creando un marco de datos para almacenar nuestros niveles de error.
#Prepare the dataframes to store the error levels normal_error = pd.DataFrame(columns=columns,index=np.arange(0,splits)) new_error = pd.DataFrame(columns=columns,index=np.arange(0,splits))
Ahora realizaremos una validación cruzada utilizando un bucle for anidado. El primer bucle itera sobre nuestra lista de modelos, mientras que el segundo bucle valida cada modelo y almacena los niveles de error.
#First we iterate over all the models we have available for j in np.arange(0,len(models)): #Now we have to perform cross validation with each model for i,(train,test) in enumerate(tscv.split(scaled_data)): #Get the data X_train = scaled_data.loc[train[0]:train[-1],oil_gold_predictors] X_test = scaled_data.loc[test[0]:test[-1],oil_gold_predictors] y_train = merged_df.loc[train[0]:train[-1],target] y_test = merged_df.loc[test[0]:test[-1],target] #Fit the model models[j].fit(X_train,y_train) #Measure the error new_error.iloc[i,j] = root_mean_squared_error(y_test,models[j].predict(X_test))
Nuestros niveles de error utilizando las entradas del modelo ordinario.
normal_error
Figura 6: Nuestros niveles de error al realizar pronósticos utilizando predictores OHLC.
Figura 7: Nuestros niveles de error al pronosticar utilizando predictores OHLC II.
Ahora echemos un vistazo a nuestros niveles de error utilizando sólo los precios del petróleo y el oro.
new_error
Figura 8: Nuestros niveles de precisión al realizar pronósticos utilizando los precios del petróleo y el oro.
Figura 9: Nuestros niveles de precisión al realizar pronósticos utilizando los precios del petróleo y el oro II.
Veamos nuestro rendimiento promedio de cada modelo usando predictores ordinarios.
#Let's see our average performance on the normal dataset for i in (np.arange(0,normal_error.shape[0])): print(f"{models[i]} normal error {((normal_error.iloc[:,i].mean()))}")
Lasso() normal error 0.11138143304314707
GradientBoostingRegressor() normal error 0.03472997520534606
RandomForestRegressor() normal error 0.03616484012058101
AdaBoostRegressor() normal error 0.037484107657877755
BaggingRegressor() normal error 0.03670486223028821
KNeighborsRegressor() normal error 0.035113189373409175
LinearSVR() normal error 0.01085610361276552
MLPRegressor(early_stopping=True, hidden_layer_sizes=(10, 5)) normal error 2.558754334716706
MLPRegressor(early_stopping=True, hidden_layer_sizes=(50, 15)) normal error 1.0544369296125597
Ahora evaluaremos nuestro desempeño promedio usando los nuevos predictores.
#Let's see our average performance on the new dataset for i in (np.arange(0,normal_error.shape[0])): print(f"{models[i]} normal error {((new_error.iloc[:,i].mean()))}")
Lasso() normal error 0.11138143304314707
GradientBoostingRegressor() normal error 0.0893855335909897
RandomForestRegressor() normal error 0.08957454602573789
AdaBoostRegressor() normal error 0.08658796789785872
BaggingRegressor() normal error 0.08887059320664067
KNeighborsRegressor() normal error 0.07696901077705855
LinearSVR() normal error 0.15463529064256165
MLPRegressor(early_stopping=True, hidden_layer_sizes=(10, 5)) normal error 3.8970873719426784
MLPRegressor(early_stopping=True, hidden_layer_sizes=(50, 15)) normal error 0.6958177634524169
Observemos los cambios en la precisión.
#Let's see our average performance on the normal dataset for i in (np.arange(0,normal_error.shape[0])): print(f"{models[i]} changed by {((normal_error.iloc[:,i].mean()-new_error.iloc[:,i].mean()))/normal_error.iloc[:,i].mean()}%")
Lasso() changed by 0.0%
GradientBoostingRegressor() changed by -1.573728690057642%
RandomForestRegressor() changed by -1.4768406476311784%
AdaBoostRegressor() changed by -1.3099914419240863%
BaggingRegressor() changed by -1.421221271695885%
KNeighborsRegressor() changed by -1.1920256220116057%
LinearSVR() changed by -13.244087580439862%
MLPRegressor(early_stopping=True, hidden_layer_sizes=(10, 5)) changed by -0.5230408480672479%
MLPRegressor(early_stopping=True, hidden_layer_sizes=(50, 15)) changed by 0.34010489967561475%
Selección de funciones
Nuestro modelo con mejor desempeño en los predictores de petróleo y oro es el regresor KNeighbors, veamos qué características son las más importantes para él.
#Our best performing model was the KNeighbors Regressor #Let us perform feature selection to test how stable the relationship is from mlxtend.feature_selection import SequentialFeatureSelector as SFS
Crea una nueva instancia del modelo.
#Let us select our best model
model = KNeighborsRegressor()
Utilizaremos la selección avanzada para identificar las características más importantes para nuestro modelo. Ahora le daremos a nuestro modelo acceso a todos los predictores a la vez.
#Create the sequential selector object sfs1 = SFS( model, k_features=(1,len(all_predictors)), forward=True, scoring="neg_mean_squared_error", cv=10, n_jobs=-1 )
Ajuste del selector de características secuenciales.
#Fit the sequential selector sfs1 = sfs1.fit(scaled_data.loc[:,all_predictors],merged_df.loc[:,"Target"])Observar las mejores características seleccionadas por el algoritmo puede llevarnos a concluir que ni los precios del petróleo ni los del oro son de mucha utilidad al pronosticar el USDZAR porque nuestro algoritmo solo seleccionó 3 características que eran cotizaciones de precio de apertura, mínimo y cierre del USDZAR.
#Now let us see which predictors were selected
sfs1.k_feature_names_
Ajuste de hiperparámetros
Intentemos realizar un ajuste de hiperparámetros utilizando el módulo RandomizedSearchCV de Scikit-learn. El algoritmo nos ayuda a muestrear una superficie de respuesta que puede ser demasiado grande para muestrearla en su totalidad. Cuando utilizamos modelos con numerosos parámetros, la combinación total de entradas crece a un ritmo significativamente rápido. Por lo tanto, preferimos el algoritmo de búsqueda aleatoria cuando tratamos con muchos parámetros que tienen muchos valores posibles.
El algoritmo proporciona un equilibrio entre la precisión de los resultados y el tiempo de cálculo. Este equilibrio se controla ajustando el número de iteraciones que permitimos. Tenga en cuenta que debido a la naturaleza aleatoria del algoritmo, puede resultar difícil reproducir exactamente los resultados demostrados en este artículo.
Importar el módulo Scikit-learn.
#Now we will load the libraries we need
from sklearn.model_selection import RandomizedSearchCV
Preparar conjuntos de pruebas.
#Let us see if we can tune the model #First we will create train test splits train_X = scaled_data.loc[:(scaled_data.shape[0]//2),:] train_y = merged_df.loc[:(merged_df.shape[0]//2),"Target"] test_X = scaled_data.loc[(scaled_data.shape[0]//2):,:] test_y = merged_df.loc[(merged_df.shape[0]//2):,"Target"]
Para realizar el ajuste de parámetros, necesitamos pasar un estimador que implemente la interfaz Scikit-learn, seguido de un diccionario que contenga claves que correspondan a los parámetros del estimador y valores que correspondan al rango de entradas permitidas para cada parámetro, desde allí especificamos que nos gustaría realizar una validación cruzada de 5 veces, y luego tenemos que especificar la métrica de puntuación como error cuadrático medio negativo.
#Create the tuning object rs = RandomizedSearchCV(KNeighborsRegressor(n_jobs=-1),{ "n_neighbors": [1,2,3,4,5,8,10,16,20,30,60,100], "weights":["uniform","distance"], "leaf_size":[1,2,3,4,5,10,15,20,40,60,90], "algorithm":["ball_tree","kd_tree"], "p":[1,2,3,4,5,6,7,8] },cv=5,n_iter=500,return_train_score=False,scoring="neg_mean_squared_error")
Realizar el ajuste de parámetros en el conjunto de entrenamiento.
#Let's perform the hyperparameter tuning rs.fit(train_X,train_y)
Mirando los resultados que obtuvimos, de mejor a peor.
#Let's store the results from our hyperparameter tuning tuning_results = pd.DataFrame(rs.cv_results_) tuning_results.loc[:,["param_n_neighbors","param_weights","param_leaf_size","param_algorithm","param_p","mean_test_score"]].sort_values(by="mean_test_score",ascending=False)
Figura 10: Los resultados de ajustar nuestro mejor modelo.
Estos son los mejores parámetros que encontramos.
#The best parameters we came across
rs.best_params_
'p': 1,
'n_neighbors': 4,
'leaf_size': 15,
'algorithm': 'ball_tree'}
Comprobación del sobreajuste
Preparémonos para comparar nuestros modelos personalizados y predeterminados. Ambos modelos se entrenarán en conjuntos de entrenamiento idénticos. Si el modelo predeterminado supera a nuestro modelo personalizado en el conjunto de validación, entonces puede ser una señal de que sobreajustamos los datos de entrenamiento. Sin embargo, si nuestro modelo personalizado funciona mejor, puede sugerir que hemos ajustado con éxito los parámetros del modelo sin sobreajustarlo.
#Create instances of the default model and the custmoized model default_model = KNeighborsRegressor() customized_model = KNeighborsRegressor(p=rs.best_params_["p"],weights=rs.best_params_["weights"],n_neighbors=rs.best_params_["n_neighbors"],leaf_size=rs.best_params_["leaf_size"],algorithm=rs.best_params_["algorithm"])
Midamos la precisión del modelo predeterminado.
#Measure the accuracy of the default model default_model.fit(train_X,train_y) root_mean_squared_error(test_y,default_model.predict(test_X))
Ahora la precisión del modelo personalizado.
#Measure the accuracy of the customized model
customized_model.fit(train_X,train_y)
root_mean_squared_error(test_y,customized_model.predict(test_X))
¡Parece que hemos ajustado bien el modelo sin sobreajustarlo! Ahora preparémonos para exportar nuestro modelo personalizado al formato ONNX.
Exportación al formato ONNX
Open Neural Network Exchange (ONNX) es un marco interoperable para construir e implementar modelos de aprendizaje automático de manera independiente del lenguaje. Al utilizar ONNX, nuestros modelos de aprendizaje automático se pueden usar fácilmente en cualquier lenguaje de programación siempre que dicho lenguaje admita la API de ONNX. Al momento de escribir este artículo, la API de ONNX está siendo desarrollada y mantenida por un consorcio de las empresas más grandes del mundo.
Import the libraries we need #Let's prepare to export the customized model to ONNX format import onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType
Necesitamos asegurarnos de que nuestros datos estén escalados y normalizados de manera que podamos reproducirlos en la terminal MetaTrader 5. Por lo tanto, realizaremos una transformación estándar que siempre podremos realizar en nuestra terminal más adelante. Restaremos el valor medio de cada columna, esto centrará nuestros datos. Y luego dividiremos cada valor por la desviación estándar de su columna particular, esto ayudará a nuestro modelo a apreciar mejor los cambios en las variables en diferentes escalas.
#Train the model on all the data we have #But before doing that we need to first scale the data in a way we can repeat in MQL5 scale_factors = pd.DataFrame(columns=all_predictors,index=["mean","standard deviation"]) for i in np.arange(0,len(all_predictors)): scale_factors.iloc[0,i] = merged_df.loc[:,all_predictors[i]].mean() scale_factors.iloc[1,i] = merged_df.loc[:,all_predictors[i]].std() scale_factors
Figura 12: Algunos de los valores que usaremos para escalar y estandarizar nuestros datos, no se muestran todas las columnas
Ahora realicemos la normalización y estandarización.
for i in all_predictors:
merged_df.loc[:,i] = (merged_df.loc[:,i] - merged_df.loc[:,i].mean()) / merged_df.loc[:,i].std()
Veamos ahora nuestros datos.
merged_df
Figura 11: Cómo se ven nuestros datos después de escalarlos, no se muestran todas las columnas.
Inicializa nuestro modelo personalizado.
customized_model = KNeighborsRegressor(p=rs.best_params_["p"],weights=rs.best_params_["weights"],n_neighbors=rs.best_params_["n_neighbors"],leaf_size=rs.best_params_["leaf_size"],algorithm=rs.best_params_["algorithm"]) customized_model.fit(merged_df.loc[:,all_predictors],merged_df.loc[:,"Target"])
Define la forma de entrada de nuestro modelo.
#Define the input shape and type initial_type = [("float_tensor_type",FloatTensorType([1,train_X.shape[1]]))]
Crear la representación ONNX.
#Create an ONNX representation
onnx_model = convert_sklearn(customized_model,initial_types=initial_type)
Guarde el modelo ONNX.
#Store the ONNX model onnx_model_name = "USDZAR_FLOAT_M1.onnx" onnx.save_model(onnx_model,onnx_model_name)
Visualización del modelo ONNX
Netron es un visualizador de código abierto para modelos de aprendizaje automático. Netron extiende el soporte a muchos marcos diferentes además de ONNX, como Keras. Usaremos Netron para garantizar que nuestro modelo ONNX tenga la forma de entrada y salida que esperábamos.
Importar el módulo Netron.
#Let's visualize the model in netron import netron
Ahora podemos visualizar el modelo usando Netron.
#Run netron
netron.start(onnx_model_name)
Figura 12: Las especificaciones de nuestro modelo ONNX.
Figura 13: La estructura de nuestro modelo ONNX.
Nuestro modelo ONNX está cumpliendo con nuestras expectativas, la forma de entrada y salida está exactamente donde esperábamos que estuvieran. Ahora podemos pasar a construir un Asesor Experto sobre nuestro modelo ONNX.
Implementación en MQL5
Ahora podemos comenzar a construir nuestro Asesor Experto, comencemos integrando primero nuestro modelo ONNX en nuestra aplicación. Al especificar el archivo ONNX como recurso, el archivo ONNX se incluirá en el programa compilado con la extensión .ex5.
//+------------------------------------------------------------------+ //| USDZAR.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/en/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/gamuchiraindawa" #property version "1.00" //+-----------------------------------------------------------------+ //| Require the ONNX file | //+-----------------------------------------------------------------+ #resource "\\Files\\USDZAR_FLOAT_M1.onnx" as const uchar onnx_model_buffer[];
Ahora importaremos la biblioteca comercial.
//+-----------------------------------------------------------------+ //| Libraries we need | //+-----------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade Trade;
Definamos las entradas que el usuario final puede controlar.
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ double input sl_width = 0.4; //How tight should our stop loss be? int input lot_multiple = 10; //How many times bigger than minimum lot should we enter? double input max_risk = 10; //After how much profit/loss should we close?
Ahora necesitamos algunas variables globales.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ long onnx_model; //Our onnx model double mean_values[12],std_values[12]; //The scaling factors we used for our data vector model_inputs = vector::Zeros(12); //Our model's inputs vector model_forecast = vector::Zeros(1); //Our model's output double bid,ask; //Market prices double minimum_volume; //Smallest lot size double state = 0; //0 means we have no positions, 1 means we have buy position, 2 means we have sell position.
Definamos ahora funciones auxiliares para tareas que podamos necesitar realizar repetidamente. Primero, controlemos nuestros niveles de riesgo, si la ganancia/pérdida total excede nuestros niveles de riesgo definidos, cerraremos automáticamente la posición.
//+------------------------------------------------------------------+ //| Check if we have reached our risk level | //+------------------------------------------------------------------+ void check_risk_level(void) { //--- Check if we have surpassed our maximum risk level if(MathAbs(PositionGetDouble(POSITION_PROFIT)) > max_risk) { //--- We should close our positions Trade.PositionClose("USDZAR"); } }
Dado que contamos con un sistema de IA integrado, usémoslo para detectar reversiones. Si nuestro sistema predice que el precio se moverá en nuestra contra, cerraremos la posición y alertaremos al usuario final de que se ha detectado una posible reversión.
//+------------------------------------------------------------------+ //| Check if there is a reversal may be coming | //+------------------------------------------------------------------+ void check_reversal(void) { if(((state == 1) && (model_forecast[0] < iClose("USDZAR",PERIOD_M1,0))) ||((state == 2) && (model_forecast[0] > iClose("USDZAR",PERIOD_M1,0)))) { //--- There may be a reversal coming Trade.PositionClose("USDZAR"); //--- Give the user feedback Alert("Potential reversal detected"); } }
Ahora necesitamos una función que encuentre oportunidades de entrada para nosotros. Solo consideraremos que una entrada es válida si la predicción de nuestro modelo se alinea con los cambios en los niveles de precios en períodos de tiempo más altos.
//+------------------------------------------------------------------+ //| Find an entry opportunity | //+------------------------------------------------------------------+ void find_entry(void) { //---Check for the change in price on higher timeframes if(iClose("USDZAR",PERIOD_D1,0) > iClose("USDZAR",PERIOD_D1,21)) { //--- We're looking for buy oppurtunities if(model_forecast[0] > iClose("USDZAR",PERIOD_M1,0)) { //--- Open the position Trade.Buy(minimum_volume,"USDZAR",ask,(ask - sl_width),(ask + sl_width),"USDZAR AI"); //--- Update the system state state = 1; } } //---Check for the change in price on higher timeframes else if(iClose("USDZAR",PERIOD_D1,0) < iClose("USDZAR",PERIOD_D1,21)) { //--- We're looking for sell oppurtunities if(model_forecast[0] < iClose("USDZAR",PERIOD_M1,0)) { //--- Open sell position Trade.Sell(minimum_volume,"USDZAR",bid,(bid + sl_width),(bid - sl_width),"USDZAR AI"); //--- Update the system state state = 2; } } }
Ahora necesitamos una función para obtener una predicción de nuestro modelo. Para ello, primero debemos obtener los precios actuales del mercado y luego transformarlos restando la media y dividiéndolos por la desviación estándar.
//+------------------------------------------------------------------+ //| Obtain a forecast from our model | //+------------------------------------------------------------------+ void model_predict(void) { //Let's fetch our model's inputs //--- USDZAR model_inputs[0] = ((iOpen("USDZAR",PERIOD_M1,0) - mean_values[0]) / std_values[0]); model_inputs[1] = ((iHigh("USDZAR",PERIOD_M1,0) - mean_values[1]) / std_values[1]); model_inputs[2] = ((iLow("USDZAR",PERIOD_M1,0) - mean_values[2]) / std_values[2]); model_inputs[3] = ((iClose("USDZAR",PERIOD_M1,0) - mean_values[3]) / std_values[3]); //--- XTI OIL US model_inputs[4] = ((iOpen("XTIUSD",PERIOD_M1,0) - mean_values[4]) / std_values[4]); model_inputs[5] = ((iHigh("XTIUSD",PERIOD_M1,0) - mean_values[5]) / std_values[5]); model_inputs[6] = ((iLow("XTIUSD",PERIOD_M1,0) - mean_values[6]) / std_values[6]); model_inputs[7] = ((iClose("XTIUSD",PERIOD_M1,0) - mean_values[7]) / std_values[7]); //--- GOLD SA model_inputs[8] = ((iOpen("XAUUSD",PERIOD_M1,0) - mean_values[8]) / std_values[8]); model_inputs[9] = ((iHigh("XAUUSD",PERIOD_M1,0) - mean_values[9]) / std_values[9]); model_inputs[10] = ((iLow("XAUUSD",PERIOD_M1,0) - mean_values[10]) / std_values[10]); model_inputs[11] = ((iClose("XAUUSD",PERIOD_M1,0) - mean_values[11]) / std_values[11]); //--- Get a prediction OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast); }
Dado que estamos analizando múltiples símbolos, necesitamos agregarlos al seguimiento del mercado.
//+------------------------------------------------------------------+ //| Load the symbols we need and add them to the market watch | //+------------------------------------------------------------------+ void load_symbols(void) { SymbolSelect("XAUUSD",true); SymbolSelect("XTIUSD",true); SymbolSelect("USDZAR",true); }
Necesitamos una función responsable de cargar nuestros factores de escala, la media y la desviación estándar de cada columna.
//+------------------------------------------------------------------+ //| Load the scale values | //+------------------------------------------------------------------+ void load_scale_values(void) { //--- Mean //--- USDZAR mean_values[0] = 18.14360511919699; mean_values[1] = 18.145737421580925; mean_values[2] = 18.141568574864074; mean_values[3] = 18.14362306984525; //--- XTI US OIL mean_values[4] = 80.76956702216644; mean_values[5] = 80.7864452112087; mean_values[6] = 80.75236177331661; mean_values[7] = 80.76923546633206; //--- GOLD SA mean_values[8] = 2430.5180384776245; mean_values[9] = 2430.878959640318; mean_values[10] = 2430.1509598494354; mean_values[11] = 2430.5204140526976; //--- Standard Deviation //--- USDZAR std_values[0] = 0.11301636249300206; std_values[1] = 0.11318116432297631; std_values[2] = 0.11288670156099372; std_values[3] = 0.11301994613848391; //--- XTI US OIL std_values[4] = 0.9802409859148413; std_values[5] = 0.9807944310705999; std_values[6] = 0.9802449355481064; std_values[7] = 0.9805961626626833; //--- GOLD SA std_values[8] = 26.397404261230328; std_values[9] = 26.414599597905003; std_values[10] = 26.377605644853944; std_values[11] = 26.395208330942864; }
Por último, necesitamos una función responsable de cargar nuestro archivo ONNX.
//+------------------------------------------------------------------+ //| Load the onnx file from buffer | //+------------------------------------------------------------------+ bool load_onnx_file(void) { //--- Create the model from the buffer onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DEFAULT); //--- The input size for our onnx model ulong input_shape [] = {1,12}; //--- Check if we have the right input size if(!OnnxSetInputShape(onnx_model,0,input_shape)) { Comment("Incorrect input shape, the model has input shape ",OnnxGetInputCount(onnx_model)); return(false); } //--- The output size for our onnx model ulong output_shape [] = {1,1}; //--- Check if we have the right output size if(!OnnxSetOutputShape(onnx_model,0,output_shape)) { Comment("Incorrect output shape, the model has output shape ",OnnxGetOutputCount(onnx_model)); return(false); } //--- Everything went fine return(true); } //+------------------------------------------------------------------+
Ahora que hemos definido estas funciones auxiliares, podemos comenzar a utilizarlas en nuestro Asesor Experto. Primero, definamos el comportamiento de nuestra aplicación cuando se carga por primera vez. Comenzaremos cargando nuestro modelo ONNX, preparando los valores de escala y luego buscaremos datos del mercado.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Load the onnx file if(!load_onnx_file()) { return(INIT_FAILED); } //--- Load our scaling values load_scale_values(); //--- Add the symbols we need to the market watch load_symbols(); //--- The smallest lotsize we can use minimum_volume = SymbolInfoDouble("USDZAR",SYMBOL_VOLUME_MIN) * lot_multiple; //--- Everything went fine return(INIT_SUCCEEDED); }
Cada vez que nuestro programa ha sido desactivado, necesitamos liberar los recursos que ya no estamos utilizando.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release the resources we used for our onnx model OnnxRelease(onnx_model); //--- Remove the expert advisor ExpertRemove(); }
Finalmente, cada vez que el precio cambia, necesitamos obtener un nuevo pronóstico de nuestro modelo, obtener precios de mercado actualizados y luego abrir una nueva posición o administrar las posiciones que tenemos abiertas actualmente.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- We always need a forecast from our model model_predict(); //--- Fetch market prices bid = SymbolInfoDouble("USDZAR",SYMBOL_BID); ask = SymbolInfoDouble("USDZAR",SYMBOL_ASK); //--- If we have no open positions, find an entry if(PositionsTotal() == 0) { //--- Find an entry find_entry(); //--- Reset the system state state = 0; } //--- If we have open postitions, manage them else { //--- Check for a reveral warning from our AI check_reversal(); //--- Check if we have not reached our max risk levels check_risk_level(); } }
Juntando todo esto ahora podemos observar nuestro programa en acción.
Figura 17: Nuestro asesor experto.
Figura 14: Las entradas para nuestro asesor experto.
Figura 15: Nuestro programa en acción.
Conclusión
En este artículo demostramos cómo se puede crear un asesor experto de símbolos múltiples impulsado con IA. Aunque logramos niveles de error más bajos con los datos OHLC estándar, esto no significa necesariamente que ocurra lo mismo con todos los símbolos en tu terminal MetaTrader 5. Podría haber un conjunto de símbolos que genere errores aún menores que las cotizaciones OHLC de USDZAR.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/15570
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Impresionante, otro excelente recorrido. Gracias por el cuaderno de trabajo, ahora tenemos una plantilla para probar nuestras propias correlaciones. Muchas gracias