English Русский 中文 Deutsch 日本語 Português
preview
Reimaginando las estrategias clásicas (Parte V): Análisis de múltiples símbolos en USDZAR

Reimaginando las estrategias clásicas (Parte V): Análisis de múltiples símbolos en USDZAR

MetaTrader 5Ejemplos |
235 4
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

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:

  1. Cotizaciones ordinarias de OHLC en el USDZAR.
  2. 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

USDZAR Oil

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

Niveles de correlación en nuestro conjunto de datos.

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.

Diagrama de dispersión de los precios del oro frente a los precios del petróleo.

Figura 3: Diagrama de dispersión de los precios del oro frente a los precios del petróleo.

Diagrama de dispersión de los precios del petróleo frente al precio de cierre del USDZAR.

Figura 4: Diagrama de dispersión de los precios del petróleo frente al precio de cierre del USDZAR.

Un gráfico de dispersión de los precios del oro frente al 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

Nuestros niveles de error al pronosticar utilizando predictores OHLC.

Figura 6: Nuestros niveles de error al realizar pronósticos utilizando predictores OHLC.

Nuestros niveles de error al pronosticar utilizando predictores OHLC II.

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

Nuestros niveles de precisión al realizar pronósticos utilizando los precios del petróleo y el oro.

Figura 8: Nuestros niveles de precisión al realizar pronósticos utilizando los precios del petróleo y el oro.

Nuestros niveles de precisión al realizar pronósticos utilizando los precios del petróleo y el oro II.

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()))}")
LinearRegression() normal error 0.01136361865358375
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()))}")

LinearRegression() normal error 0.13404065973045615
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()}%")
LinearRegression() changed by -10.795596439535894%
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_
('Open', 'Low', 'Close')


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)

Los resultados de ajustar nuestro mejor modelo.

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_

{'weights': 'distance',
 '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))
0.06633226373900612

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

¡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

Nuestros factores de escala.

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

Nuestros datos escalados.

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)

Los metadetalles de nuestro modelo ONNX.

Figura 12: Las especificaciones de nuestro modelo ONNX.

La estructura 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. 

Nuestro asesor experto.

Figura 17: Nuestro asesor experto.

Nuestro sistema en acción.

Figura 14: Las entradas para nuestro asesor experto.

Nuestro sistema en acción.

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

Archivos adjuntos |
USDZAR_FLOAT_M1.onnx (524.58 KB)
USDZAR.ipynb (694.01 KB)
FetchData.mq5 (2.05 KB)
USDZAR.mq5 (10.54 KB)
linfo2
linfo2 | 19 ago 2024 en 07:12
Impresionante, otro excelente recorrido. Gracias por el cuaderno de trabajo, ahora tenemos una plantilla para probar nuestras propias correlaciones. Muchas gracias
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 21 ago 2024 en 15:32
linfo2 #:
Impresionante, otro excelente recorrido. Gracias por el cuaderno de trabajo, ahora tenemos una plantilla para probar nuestras propias correlaciones. Muchas gracias
El placer es mío Neil, recuerdo que una vez me dijo que tenía una idea que implica la búsqueda de correlación entre los indicadores, no dude en compartir cómo sus hallazgos en ese proyecto puede ayudarnos aquí, y espero que podamos cocinar 💯🔥
Carl Schreiber
Carl Schreiber | 21 oct 2024 en 13:48
En el script no estás cerrando el archivo abierto para escribir la fecha, tanto en el artículo como en fetchData.mq5
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 21 oct 2024 en 18:14
Carl Schreiber archivo abierto para escribir la fecha, tanto en el artículo como en fetchData.mq5
Gracias por señalarlo, lo corregiré en el futuro.
Redes neuronales en el trading: Resultados prácticos del método TEMPO Redes neuronales en el trading: Resultados prácticos del método TEMPO
Continuamos familiarizándonos con el método TEMPO. En este artículo, analizaremos la efectividad de los enfoques propuestos con datos históricos reales.
Aprendiendo MQL5 de principiante a profesional (Parte IV): Sobre arrays, funciones y variables globales del terminal Aprendiendo MQL5 de principiante a profesional (Parte IV): Sobre arrays, funciones y variables globales del terminal
El artículo es una continuación de la serie para principiantes. En él proporcionamos información detallada sobre los arrays de datos y la interacción de datos y funciones, así como de las variables globales del terminal que permiten el intercambio de datos entre diferentes programas MQL5.
Reimaginando las estrategias clásicas (Parte VI): Análisis de múltiples marcos temporales Reimaginando las estrategias clásicas (Parte VI): Análisis de múltiples marcos temporales
En esta serie de artículos, revisamos las estrategias clásicas para ver si podemos mejorarlas utilizando IA. En el artículo de hoy, examinaremos la popular estrategia de análisis de múltiples marcos temporales para juzgar si la estrategia se podría mejorar con IA.
Características del Wizard MQL5 que debe conocer (Parte 32): Regularización Características del Wizard MQL5 que debe conocer (Parte 32): Regularización
La regularización es una forma de penalizar la función de pérdida en proporción a la ponderación discreta aplicada a lo largo de las distintas capas de una red neuronal. Observamos la importancia que esto puede tener, para algunas de las diversas formas de regularización, en ejecuciones de prueba con un Asesor Experto ensamblado mediante el asistente.