English Русский 中文 Deutsch 日本語
preview
Ingeniería de características con Python y MQL5 (Parte I): Predicción de medias móviles para modelos de IA de largo plazo

Ingeniería de características con Python y MQL5 (Parte I): Predicción de medias móviles para modelos de IA de largo plazo

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

Al aplicar la IA a cualquier tarea, debemos esforzarnos al máximo por proporcionar al modelo tanta información útil sobre el mundo real como sea posible. Para describir las diferentes propiedades del mercado a nuestros modelos de IA, debemos manipular y transformar los datos de entrada, un proceso que se conoce como ingeniería de características. Esta serie de artículos le enseñará cómo transformar sus datos de mercado para reducir los niveles de error de sus modelos. Hoy me centraré en cómo utilizar las medias móviles para aumentar el rango de predicción de sus modelos de IA de una manera que proporcione un control total y una comprensión razonable de la eficacia global de la estrategia.


Resumen de la estrategia

La última vez que hablamos sobre la predicción de medias móviles con IA, proporcioné pruebas que sugerían que los valores de la media móvil son más fáciles de predecir para nuestros modelos de IA que los niveles de precios futuros. El enlace a ese artículo se encuentra aquí. Sin embargo, para estar seguros de que nuestros hallazgos son significativos, entrené dos modelos de IA idénticos con más de 200 símbolos bursátiles diferentes y comparé la precisión de la previsión de precios con la precisión de la previsión de la media móvil, y los resultados parecen indicar que nuestros niveles de precisión disminuyen en promedio un 34% si predecimos precios por encima de las medias móviles.

En promedio, podemos esperar una precisión del 70% al pronosticar las medias móviles, en comparación con una precisión esperada del 52% al pronosticar el precio. Todos sabemos que, dependiendo del periodo, el indicador de media móvil no sigue muy de cerca los niveles de precios; por ejemplo, el precio puede caer más de 20 velas mientras que las medias móviles suben durante el mismo intervalo. Esta divergencia no nos conviene porque, aunque podemos predecir correctamente la dirección futura de la media móvil, el precio puede divergir. Sorprendentemente, observamos que la tasa de divergencia se mantiene fija en torno al 31% en todos los mercados, y nuestra capacidad para pronosticar divergencias fue, en promedio, del 68%.

Además, la varianza de nuestra capacidad para pronosticar la divergencia y la ocurrencia de la divergencia fue de 0,000041 y 0,000386, respectivamente. Esto demuestra que nuestro modelo es capaz de corregirse a sí mismo con un nivel de precisión fiable. Los miembros de la comunidad que deseen aplicar la IA a estrategias de trading a largo plazo deberían considerar este enfoque alternativo en marcos temporales más amplios. Por ahora, nuestro análisis se limita al M1, ya que este periodo de tiempo nos garantiza que dispondremos de datos suficientes en los 297 mercados para poder realizar comparaciones justas.

Hay muchas razones posibles por las que las medias móviles son más fáciles de predecir que el precio en sí. Esto puede ser cierto porque predecir las medias móviles se ajusta más a la idea de la regresión lineal que predecir el precio, es decir, la regresión lineal asume que los datos son una combinación lineal (suma) de varias entradas: las medias móviles son una suma de los valores de precios anteriores, lo que significa que la suposición lineal es cierta. El precio en sí mismo no es una simple suma de variables del mundo real, sino una relación compleja entre muchas variables.


Primeros pasos

Primero tendremos que importar nuestras bibliotecas estándar para la computación científica en Python.

#Load the libraries we need
import pandas as pd
import numpy as np
import MetaTrader5 as mt5
from sklearn.model_selection import TimeSeriesSplit,cross_val_score
from sklearn.linear_model import LogisticRegression,LinearRegression
import matplotlib.pyplot as plt

Inicialicemos nuestro terminal MetaTrader 5.

#Initialize the terminal
mt5.initialize()
True

¿Cuántos símbolos tenemos disponibles?

#The total number of symbols we have
print(f"Total Symbols Available: ",mt5.symbols_total())
Total Symbols Available:  297

Obtener los nombres de todos los símbolos.

#Get the names of all pairs
symbols = mt5.symbols_get()
idx = [s.name for s in symbols]

Crea un marco de datos para almacenar nuestros niveles de precisión en todos los símbolos.

global_params = pd.DataFrame(index=idx,columns=["OHLC Error","MAR Error","Noise Levels","Divergence Error"])
global_params

Figura 1: Nuestro marco de datos que almacenará nuestros niveles de precisión en todos los mercados de nuestra terminal.

Defina nuestro objeto de división de series temporales.

#Define the time series split object
tscv = TimeSeriesSplit(n_splits=5,gap=10)

Medir nuestra precisión en todos los símbolos.

#Iterate over all symbols
for i in np.arange(global_params.dropna().shape[0],len(idx)):
    #Fetch M1 Data
    data = pd.DataFrame(mt5.copy_rates_from_pos(cols[i],mt5.TIMEFRAME_M1,0,50000))
    data.rename(columns={"open":"Open","high":"High","low":"Low","close":"Close"},inplace=True)
    #Define our period
    period = 10
    #Add the classical target
    data.loc[data["Close"].shift(-period) > data["Close"],"OHLC Target"] = 1
    #Calculate the returns
    data.loc[:,["Open","High","Low","Close"]] = data.loc[:,["Open","High","Low","Close"]].diff(period)
    data["RMA"] = data["Close"].rolling(period).mean()
    #Calculate our new target
    data.dropna(inplace=True)
    data.reset_index(inplace=True,drop=True)
    data.loc[data["RMA"].shift(-period) > data["RMA"],"New Target"] = 1
    data = data.iloc[0:-period,:]
    #Calculate the divergence target
    data.loc[data["OHLC Target"] != data["New Target"],"Divergence Target"] = 1
    #Noise ratio
    global_params.iloc[i,2] = data.loc[data["New Target"] != data["OHLC Target"]].shape[0] / data.shape[0]
    #Test our accuracy predicting the future close price
    score = cross_val_score(LogisticRegression(),data.loc[:,["Open","High","Low","Close"]],data["OHLC Target"],cv=tscv)
    global_params.iloc[i,0] = score.mean()
    #Test our accuracy predicting the moving average of future returns
    score = cross_val_score(LogisticRegression(),data.loc[:,["Open","Close","RMA"]],data["New Target"],cv=tscv)
    global_params.iloc[i,1] = score.mean()
    #Test our accuracy predicting the future divergence between price and its moving average
    score = cross_val_score(LogisticRegression(),data.loc[:,["Open","Close","RMA"]],data["Divergence Target"],cv=tscv)
    global_params.iloc[i,3] = score.mean()
    print(f"{((i/len(idx)) * 100)}% complete")

#We are done
print("Done")
99.66329966329967% complete
Done


Análisis de los resultados

Ahora que hemos obtenido nuestros datos de mercado y evaluado nuestro modelo en los 2 objetivos, resumamos los resultados de nuestra prueba en todos los mercados. Comenzaremos resumiendo nuestra precisión al predecir el cambio en el precio de cierre futuro. La figura 2 a continuación muestra el resumen de la predicción del cambio en el precio de cierre futuro. La línea horizontal roja representa el umbral de precisión del 50%. Nuestra precisión promedio usando esta técnica está indicada por la línea horizontal azul. Como podemos ver, nuestra precisión promedio no está significativamente lejos del umbral del 50%, esto no es una información alentadora.

Sin embargo, para ser justos, también podemos observar que algunos mercados en particular están muy por encima del promedio y pueden predecirse con más del 65% de precisión. Esto es impresionante, pero también justifica más investigación para determinar si los resultados son significativos o podrían haber ocurrido por casualidad.

global_params.iloc[:,0].plot()
plt.title("OHLC Accuracy")
plt.xlabel("Market")
plt.ylabel("5-fold Accuracy %")
plt.axhline(global_params.iloc[:,0].mean(),linestyle='--')
plt.axhline(0.5,linestyle='--',color='red')

Figura 2: Nuestra precisión promedio al predecir el precio.

Centrémonos ahora en nuestra precisión a la hora de predecir el cambio en las medias móviles. La figura 3 a continuación resume los datos para nosotros. Una vez más, la línea roja representa el umbral del 50%, la línea dorada representa nuestra precisión media a la hora de predecir cambios en los niveles de precios y la línea azul es nuestra precisión media a la hora de predecir cambios en la media móvil. Decir simplemente que nuestro modelo es mejor para predecir las medias móviles es quedarse corto. Creo que ya no es objeto de debate, sino simplemente un hecho, que nuestros modelos son mejores a la hora de predecir ciertos indicadores que a la hora de predecir el precio.

global_params.iloc[:,1].plot()
plt.title("Moving Average Returns Accuracy")
plt.xlabel("Market")
plt.ylabel("5-fold Accuracy %")
plt.axhline(global_params.iloc[:,1].mean(),linestyle='--')
plt.axhline(global_params.iloc[:,0].mean(),linestyle='--',color='orange')
plt.axhline(0.5,linestyle='--',color='red')

Figura 3: Nuestra precisión al predecir el cambio en la media móvil.

Observemos ahora la velocidad a la que divergen el precio y la media móvil. Los niveles de divergencia cercanos al 50% son negativos, ya que eso significa que no podemos estar razonablemente seguros de si el precio y la media móvil se moverán en la misma dirección o en direcciones opuestas. Afortunadamente para nosotros, los niveles de ruido se mantuvieron constantes en todos los mercados que evaluamos. Los niveles de ruido variaron entre el 35% y el 30%.

global_params.iloc[:,2].plot()
plt.title("Noise Level")
plt.xlabel("Market")
plt.ylabel("Percentage of Divergence:Price And Moving Average")
plt.axhline(global_params.iloc[:,2].mean(),linestyle='--')

Figura 4: Visualización de nuestros niveles de ruido en todos los mercados.

Si dos variables tienen una relación casi constante, entonces puede ser una señal de que existe una relación que podemos modelar. Observemos qué tan bien podemos pronosticar la divergencia entre el precio y la media móvil. Nuestro razonamiento es simple: si nuestro modelo pronostica que la media móvil caerá, ¿podemos predecir razonablemente si el precio se moverá en la misma dirección o si el precio se desviará de la media móvil? Resulta que podemos predecir la divergencia con un nivel confiable de precisión, casi el 70% en promedio.

global_params.iloc[:,3].plot()
plt.title("Divergence Accuracy")
plt.xlabel("Market")
plt.ylabel("5-fold Accuracy %")
plt.axhline(global_params.iloc[:,3].mean(),linestyle='--')

Figura 5: Nuestra precisión al predecir la divergencia entre el precio y la media móvil.

También podemos resumir nuestros hallazgos en forma de tabla. De esta forma podemos comparar fácilmente nuestros niveles de precisión entre los rendimientos del mercado y un promedio móvil de los rendimientos. Tenga en cuenta que, aunque nuestra media móvil pueda «ir con retraso» respecto al precio, nuestra precisión a la hora de predecir reversiones sigue siendo significativamente mayor que nuestra precisión a la hora de predecir el precio en sí.

Métrica
Precisión
Devuelve un error
0.525353
Error de media móvil de retorno
0.705468
Niveles de ruido
0.317187
Error de divergencia
0.682069

Veamos en qué mercados funcionó mejor nuestro modelo.

global_params.sort_values("MAR Error",ascending=False)


Figura 6: Nuestros mercados con mejor desempeño.


Optimizando para nuestro mercado con mejor rendimiento

Ahora diseñemos y adaptemos nuestro indicador de media móvil para uno de los mercados en los que nuestro rendimiento sea mayor. También compararemos visualmente nuestra nueva función diseñada con las funciones clásicas. Comenzaremos especificando nuestro mercado seleccionado.

symbol = "AUDJPY"

Asegúrese de que podamos llegar a la terminal.

#Reach the terminal
mt5.initialize()
True

Ahora, busque datos del mercado.

data = pd.DataFrame(mt5.copy_rates_from_pos(symbol,mt5.TIMEFRAME_D1,365*2,5000))

Importando las librerias que necesitamos.

#Standard libraries
import seaborn                 as     sns
from   mpl_toolkits.mplot3d    import Axes3D
from   sklearn.linear_model    import LinearRegression
from   sklearn.neural_network  import MLPRegressor
from   sklearn.metrics         import mean_squared_error
from   sklearn.model_selection import cross_val_score,TimeSeriesSplit

Defina el punto de inicio y final para nuestro cálculo del período y nuestro horizonte de pronóstico. Asegúrese de que ambas entradas tengan la misma dimensión, de lo contrario nuestro código fallará.

#Define the input range
x_min , x_max = 2,100 #Look ahead
y_min , y_max = 2,100 #Period

Muestreemos nuestro dominio de entrada en pasos de 5 para que nuestros cálculos sean detallados y económicos de obtener.

#Sample input range uniformly
x_axis = np.arange(x_min,x_max,2) #Look ahead
y_axis = np.arange(y_min,y_max,2) #Period

Crea una cuadrícula utilizando nuestros ejes "x" e "y". La red de cuadrícula está compuesta por dos matrices bidimensionales que definen todas las combinaciones posibles de períodos de horizontes de previsión que deseamos evaluar.

#Create a meshgrid
x , y = np.meshgrid(x_axis,y_axis)

A continuación, necesitamos una función que recupere nuestros datos de mercado y los etiquete para que podamos evaluarlos.

def clean_data(look_ahead,period):
    #Fetch the data from our terminal and clean it up 
    data = pd.DataFrame(mt5.copy_rates_from_pos('AUDJPY',mt5.TIMEFRAME_D1,365*2,5000))
    data['time'] = pd.to_datetime(data['time'],unit='s')
    data['MA'] = data['close'].rolling(period).mean()
    #Transform the data
    #Target
    data['Target'] = data['MA'].shift(-look_ahead) - data['MA']
    #Change in price
    data['close']  = data['close'] - data['close'].shift(period)
    #Change in MA
    data['MA']  = data['MA'] - data['MA'].shift(period)
    data.dropna(inplace=True)
    data.reset_index(drop=True,inplace=True)
    return(data)

La siguiente función realizará una validación cruzada de cinco veces en nuestro modelo de IA.

#Evaluate the objective function
def evaluate(look_ahead,period):
    #Define the model
    model = LinearRegression()
    #Define our time series split
    tscv = TimeSeriesSplit(n_splits=5,gap=look_ahead)
    temp = clean_data(look_ahead,period)
    score = np.mean(cross_val_score(model,temp.loc[:,["Open","High","Low","Close"]],temp["Target"],cv=tscv))
    return(score)

Finalmente, nuestra función objetivo. Nuestra función objetivo es simplemente el error de validación de 5 veces de nuestro modelo bajo las nuevas configuraciones que deseamos evaluar. Recordemos que estamos tratando de encontrar la distancia óptima hacia el futuro que nuestro modelo debe pronosticar y, además, estamos tratando de localizar el período con el que debemos calcular los cambios en el precio.

#Define the objective
def objective(x,y):
    #Define the output matrix
    results = np.zeros([x.shape[0],y.shape[0]])
    #Fill in the output matrix
    for i in np.arange(0,x.shape[0]):
        #Select the rows
        look_ahead = x[i]
        period     = y[i]
        for j in np.arange(0,y.shape[0]):
            results[i,j] = evaluate(look_ahead[j],period[j])
    return(results)

Evaluaremos la relación de nuestro modelo con el mercado mientras intentamos predecir directamente el cambio en los niveles de precios. La figura 7 muestra la relación entre nuestro modelo y el cambio en el precio, mientras que la figura 8 muestra la relación entre nuestro modelo y el cambio en el promedio móvil. El punto blanco en ambos gráficos simboliza la combinación de entradas que resultó en el menor error para nosotros. 

res = objective(x,y)
res = np.abs(res)

Trazando el mejor desempeño de nuestro modelo prediciendo el retorno diario del par AUDJPY. Los datos revelan que cuando pronosticamos cambios futuros en los precios, lo mejor que podemos hacer es pronosticar un paso hacia el futuro. Los traders humanos no se limitan a mirar un paso adelante cuando realizan sus operaciones. Por lo tanto, los resultados que hemos obtenido al pronosticar directamente los retornos del mercado limitan nuestro enfoque y hacen que nuestros modelos se fijen en la siguiente vela.

plt.contourf(x,y,res,100,cmap="jet")
plt.plot(x_axis[res.min(axis=0).argmin()],y_axis[res.min(axis=1).argmin()],'.',color='white')
plt.ylabel("Differencing Period")
plt.xlabel("Forecast Horizon")
plt.title("Linear Regression Accuracy Forecasting AUDJPY Daily Return")

Figura 7: Visualización de la capacidad de nuestro modelo para pronosticar niveles de precios futuros.

Una vez que comenzamos a pronosticar el cambio en la media móvil en lugar del cambio en el precio, podemos observar que nuestro horizonte de pronóstico óptimo se desplaza hacia la derecha. La figura 8 muestra que, al pronosticar el cambio en las medias móviles, ahora podemos pronosticar de forma fiable 22 pasos hacia el futuro, en lugar de un solo paso hacia el futuro cuando se pronostica el cambio en el precio.

plt.contourf(x,y,res,100,cmap="jet")
plt.plot(x_axis[res.min(axis=0).argmin()],y_axis[res.min(axis=1).argmin()],'.',color='white')
plt.ylabel("Differencing Period")
plt.xlabel("Forecast Horizon")
plt.title("Linear Regression Accuracy Forecasting AUDJPY Daily Moving Average Return")


Figura 8: Visualización de la capacidad de nuestro modelo para pronosticar los niveles futuros de la media móvil.

Lo que es aún más impresionante es que, en los puntos óptimos, nuestros niveles de error en los dos objetivos fueron idénticos. En otras palabras, a nuestro modelo le resulta tan fácil predecir el cambio en la media móvil 40 pasos hacia el futuro como predecir el cambio en el precio 1 paso hacia el futuro. Por lo tanto, la predicción de la media móvil nos proporciona un mayor rango, sin aumentar el error de nuestras predicciones.

Cuando visualizamos los resultados de nuestra prueba en 3D, la diferencia entre los 2 objetivos queda clara. La figura 9 a continuación, nos muestra la relación entre los cambios en los niveles de precios y los parámetros de pronóstico para nuestro modelo. Hay una tendencia clara que podemos ver en los datos: a medida que pronosticamos más hacia el futuro, nuestros resultados empeoran. Por lo tanto, cuando diseñamos nuestros modelos de IA de esta manera, son algo «miopes» y no pueden pronosticar de manera razonable intervalos superiores a 20.

La figura 10 fue creada a partir de la relación entre nuestro modelo y su error al pronosticar los cambios en las medias móviles. El gráfico de superficie muestra propiedades deseables, podemos ver claramente que a medida que avanzamos en el pronóstico hacia el futuro y aumentamos el período de cálculo del cambio en la media móvil, nuestras tasas de error caen suavemente hasta un mínimo y luego comienzan a subir de nuevo. Esta demostración visual muestra lo fácil que es para nuestro modelo predecir la media móvil en comparación con predecir el precio.

#Create a surface plot
fig , ax = plt.subplots(subplot_kw={"projection":"3d"})
fig.set_size_inches(8,8)
ax.plot_surface(x,y,optimal_nn_res,cmap="jet")


Figura 9: Visualización de la relación entre nuestro modelo y los cambios en el precio diario del AUDJPY.


Figura 10: La relación entre nuestro modelo y los cambios en el valor de la media móvil del par AUDJPY.


Transformaciones no lineales: Eliminación de ruido con wavelets

Hasta ahora, solo hemos aplicado transformaciones lineales a nuestros datos. Podemos profundizar aún más y aplicar transformaciones no lineales a los datos de entrada del modelo. La ingeniería de características es, en ocasiones, simplemente un proceso de prueba y error. No siempre tenemos la garantía de obtener mejores resultados. Por lo tanto, aplicamos estas transformaciones de manera ad-hoc, no tenemos una fórmula precisa que nos muestre la “mejor” transformación que debemos aplicar en cada momento.

La transformación wavelet es una herramienta matemática utilizada para crear una representación de los datos en función de la frecuencia y el tiempo. Se utiliza habitualmente en tareas de procesamiento de señales e imágenes para separar el ruido de la señal que se desea procesar. Una vez aplicada la transformación, nuestros datos estarán en el dominio de la frecuencia. La idea es que el ruido en nuestros datos proviene de los pequeños valores de frecuencia identificados por la transformación. Todos los valores, por debajo de un umbral determinado, se reducen a 0 de una de las dos formas posibles. El resultado es una representación dispersa de los datos originales.

La eliminación de ruido mediante wavelets tiene varias ventajas sobre otras técnicas populares, como la Transformación Rápida de Fourier (Fast Fourier Transformation, FFT). Para los lectores que no estén familiarizados, la Transformación de Fourier representa cualquier señal como una suma de ondas seno y coseno. Desafortunadamente, la Transformación de Fourier filtra nuestros valores de alta frecuencia. Esto puede no ser siempre deseable, especialmente para datos donde la señal está en el dominio de alta frecuencia. Dado que no estamos seguros de si nuestra señal está en el dominio de alta o baja frecuencia, necesitamos una transformación que sea lo suficientemente flexible para realizar esta tarea de forma no supervisada. La transformada wavelet preservará la señal en los datos, mientras filtra la mayor cantidad de ruido posible.

Para seguirnos, asegúrese de tener instalada la imagen de scikit learn y su dependencia PyWavelets. Para los lectores que deseen crear una aplicación comercial completa en MQL5, implementar y depurar la transformación desde cero puede resultar demasiado complicado. Es más fácil para nosotros progresar sin ella. Y para los lectores que deseen interactuar con la terminal usando la biblioteca Python, la transformación es una herramienta que vale la pena incluir en su arsenal.

Podemos comparar el cambio en la precisión de la validación para ver si la transformación está ayudando a nuestro modelo, y lo está. Tenga en cuenta que solo aplicamos la transformación a las entradas del modelo y no al objetivo; preservamos el objetivo. Observe que nuestra precisión de validación efectivamente disminuyó. Estamos utilizando la transformación wavelet con un umbral estricto, por lo que establece todos los coeficientes de ruido en 0. Como alternativa, podríamos utilizar un umbral suave, que dirigiría nuestros coeficientes de ruido a 0, pero podría no establecerlos exactamente en 0.

#Benchmark Score
np.mean(cross_val_score(LinearRegression(),data.loc[:,["MA"]],data["Target"]))
0.9935846835797412

#Wavelet denoising
data["Denoised"] = denoise_wavelet(
    data["MA"],
    method='BayesShrink',
    mode='hard',
    rescale_sigma=True,
    wavelet_levels = 3,
    wavelet='sym5'
    )
np.mean(cross_val_score(LinearRegression(),np.sqrt(np.log(data.loc[:,["Denoised"]])),data["Target"]))
0.9082244556297641


Creación de modelos de IA personalizados

Ahora que conocemos los parámetros ideales sobre hasta dónde debemos pronosticar en el futuro y nuestro período promedio móvil óptimo. Obtengamos nuestros datos de mercado directamente desde el terminal MetaTrader 5 para garantizar que nuestros modelos de IA se estén entrenando utilizando los mismos valores de indicador que observarán durante el trading real. Queremos emular la experiencia del trading real tanto como sea posible.

El período de la media móvil, en el script, coincidirá con el período ideal de la media móvil que hemos calculado anteriormente. Además, también obtendremos lecturas de RSI de nuestra terminal para estabilizar el comportamiento de nuestro robot comercial de IA. Al confiar en las predicciones realizadas por dos indicadores separados en lugar de uno, nuestro modelo de IA puede ser más estable a lo largo del tiempo.

//+------------------------------------------------------------------+
//|                                                      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

//+------------------------------------------------------------------+
//| Script Inputs                                                    |
//+------------------------------------------------------------------+
input int size = 100000; //How much data should we fetch?

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_handler,rsi_handler;
double ma_reading[],rsi_reading[];

//+------------------------------------------------------------------+
//| On start function                                                |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Load indicator
ma_handler  = iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_CLOSE);
rsi_handler = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE);
   
//--- Load the indicator values
CopyBuffer(ma_handler,0,0,size,ma_reading);
CopyBuffer(rsi_handler,0,0,size,rsi_reading);

ArraySetAsSeries(ma_reading,true);
ArraySetAsSeries(rsi_reading,true);

//--- File name
   string file_name = "Market Data " + Symbol() +" MA RSI " +  " As Series.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","MA","RSI");
        }

      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),
                   ma_reading[i],
                   rsi_reading[i]
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+

Ahora que hemos creado nuestro script, podemos simplemente arrastrarlo y soltarlo en el mercado deseado, y luego podemos comenzar a trabajar con los datos del mercado. Para que nuestras pruebas retrospectivas sean significativas, hemos eliminado los últimos 2 años de datos de mercado del archivo CSV que hemos generado. De esa manera, cuando probemos nuestra estrategia de 2023 a 2024, los resultados que observaremos serán un reflejo fiel del rendimiento de nuestro modelo en datos que no ha visto antes.

#Read in the data
data = pd.read_csv("Market Data AUDJPY MA RSI As Series.csv")
#Let's drop the last two years of data. We'll use that to validate our model in the back test
data = data.iloc[365:-(365 * 2),:]
data

Figura 11: Entrenamiento de nuestro modelo a lo largo de 22 años de datos de mercado, excluyendo el período 2023-2024.

Ahora etiquetemos nuestros datos para el aprendizaje automático. Queremos ayudar a nuestro modelo a aprender los cambios en el precio dados los cambios en nuestros indicadores técnicos. Para ayudar a nuestro modelo a aprender esta relación, transformaremos nuestras entradas para denotar el estado actual del indicador. Por ejemplo, nuestro indicador RSI tendrá 3 estados posibles:

  1. Más de 70.
  2. Menos de 30.
  3. Entre 70 y 30.
Si la lectura actual del RSI es superior a 70, la primera columna RSI 1 se establecerá en 1 y las columnas restantes se establecerán en 0. Haremos lo mismo con nuestras medias móviles, pero estas solo tendrán dos estados, para indicar si han estado subiendo o bajando. Al aplicar estas transformaciones, ayudamos a nuestro modelo a centrarse en los cambios críticos en el indicador como lo hace un operador humano.
#MA States
data["MA 1"] = 0
data["MA 2"] = 0
data.loc[data["MA"] > data["MA"].shift(40),"MA 1"] = 1
data.loc[data["MA"] <= data["MA"].shift(40),"MA 2"] = 1

#RSI States
data["RSI 1"] = 0
data["RSI 2"] = 0
data["RSI 3"] = 0
data.loc[data["RSI"] < 30,"RSI 1"] = 1
data.loc[data["RSI"] > 70,"RSI 2"] = 1
data.loc[(data["RSI"] >= 30) & (data["RSI"] <= 70),"RSI 3"] = 1

#Target
data["Target"]    = data["Close"].shift(-22) - data["Close"]
data["MA Target"] = data["MA"].shift(-22) - data["MA"]

#Clean up the data
data = data.dropna()
data = data.iloc[40:,:]
data = data.reset_index(drop=True)

Ahora podemos empezar a medir nuestra precisión.

from sklearn.linear_model import Ridge
from sklearn.model_selection import TimeSeriesSplit,cross_val_score

Aplicando estas transformaciones, podemos observar el cambio promedio en el precio a medida que el RSI pasa por las 3 zonas que hemos especificado. Los coeficientes de nuestro modelo lineal se pueden interpretar como el cambio promedio en el precio asociado con cada una de las 3 zonas RSI. Estos hallazgos a veces pueden contradecir las enseñanzas clásicas sobre cómo utilizar el indicador. Por ejemplo, nuestro modelo Ridge ha aprendido que cuando la lectura del RSI supera 70, los niveles de precios tienden a caer; de lo contrario, cuando la lectura del RSI es inferior a 70, los niveles de precios futuros tienden a subir.

#Our model can suggest optimal ways of using the RSI indicator
#Our model has learned that on average price tends to fall the RSI reading is less than 30 and increases otherwises
model = Ridge()

model.fit(data.loc[:,["RSI 1","RSI 2","RSI 3"]] , data["Target"])

model.coef_
array([-0.19297857,  0.14816216,  0.04481641])

Nuestro modelo Ridge puede predecir bien los precios futuros sólo dado el estado actual del RSI.

#RSI state
np.mean(cross_val_score(Ridge(),data.loc[:,["RSI 1","RSI 2","RSI 3"]] , data["Target"],cv=tscv))
-0.025569914370219736

Asimismo, nuestro modelo también ha aprendido sus propias reglas comerciales a partir de los cambios en los indicadores de media móvil. El primer coeficiente del modelo es negativo, lo que significa que cuando la media móvil supera las 40 velas, tiende a caer. Y el segundo coeficiente es positivo. Por lo tanto, según los datos históricos que hemos obtenido en nuestra terminal, cuando la media móvil diaria del AUDJPY de 40 períodos cae por debajo de 40 velas, tiende a subir posteriormente. Nuestro modelo ha aprendido una estrategia de reversión a la media a partir de los datos.

#Our model can suggest optimal ways of using the RSI indicator
#Our model has learned that on average price tends to fall the RSI reading is less than 30 and increases otherwises
model = Ridge()

model.fit(data.loc[:,["MA 1","MA 2"]] , data["Target"])

model.coef_
array([-0.15572796,  0.15572796])

Nuestro modelo funciona aún mejor cuando le proporcionamos el estado actual de nuestro indicador de media móvil.

#MA state
np.mean(cross_val_score(Ridge(),data.loc[:,["MA 1","MA 2"]] , data["Target"],cv=tscv))
-0.009645886983465935


Conversión a ONNX

Ahora que hemos encontrado los parámetros de entrada ideales para nuestra previsión de media móvil, preparémonos para convertir nuestro modelo al formato ONNX. Open Neural Network Exchange (ONNX) nos permite crear modelos de aprendizaje automático en un marco independiente del lenguaje. El protocolo ONNX es una iniciativa de código abierto cuyo objetivo es crear una representación estándar universal para los modelos de aprendizaje automático, lo que nos permite crear e implementar modelos de aprendizaje automático en cualquier lenguaje, siempre y cuando este adopte plenamente la API de ONNX.

En primer lugar, recopilemos los datos que necesitamos, según la mejor información que hayamos encontrado.

#Fetch clean data
new_data = clean_data(140,130)

Importa las bibliotecas que necesitamos.

import onnx
from   skl2onnx import convert_sklearn
from   skl2onnx.common.data_types import FloatTensorType

Ajustar el modelo RSI a todos los datos que tenemos.

#First we will export the RSI model
rsi_model = Ridge()
rsi_model.fit(data.loc[:,['RSI 1','RSI 2','RSI 3']],data.loc[:,'Target'])

Ajuste el modelo de media móvil a todos los datos que tenemos.

#Finally we will export the MA model
ma_model = Ridge()
ma_model.fit(data.loc[:,['MA 1','MA 2']],data.loc[:,'MA Target'])

Define la forma de entrada de nuestro modelo y guárdala en el disco.

initial_types = [('float_input', FloatTensorType([1, 3]))]
onnx.save(convert_sklearn(rsi_model,initial_types=initial_types,target_opset=12),"AUDJPY D1 RSI AI F22 P40.onnx")

initial_types = [('float_input', FloatTensorType([1, 2]))]
onnx.save(convert_sklearn(ma_model,initial_types=initial_types,target_opset=12),"AUDJPY D1 MA AI F22 P40.onnx")


Implementación en MQL5

Ahora estamos listos para comenzar a construir nuestra aplicación comercial en MQL5. Queremos crear una aplicación de trading capaz de entrar y salir de sus posiciones utilizando nuestros nuevos conocimientos sobre las medias móviles. No solo eso, sino que, por lo general, guiaremos nuestro modelo utilizando indicadores más lentos que generan señales de una manera que no es demasiado agresiva. Intentemos imitar la forma en que los operadores humanos no siempre fuerzan una posición en el mercado.

Además, también implementaremos órdenes stop loss dinámicas para garantizar una gestión de riesgos adecuada. Utilizaremos el indicador Average True Range (ATR) para establecer dinámicamente nuestros niveles de stop loss y take profit. Nuestra estrategia se basa principalmente en canales de medias móviles.

Nuestra estrategia prevé las medias móviles 40 pasos hacia el futuro para confirmarnos antes de que podamos abrir cualquier posición. Probamos esta estrategia durante un año de datos históricos que no se mostraron al modelo durante el entrenamiento.

Primero, comenzaremos importando el archivo ONNX que acabamos de crear.

//+------------------------------------------------------------------+
//|                                                    GBPUSD AI.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"

//+------------------------------------------------------------------+
//| Load our resources                                               |
//+------------------------------------------------------------------+
#resource  "\\Files\\AUDJPY D1 MA AI F22 P40.onnx" as const uchar onnx_buffer[];
#resource  "\\Files\\AUDJPY D1 RSI AI F22 P40.onnx" as const uchar rsi_onnx_buffer[];

Luego importaremos las librerías que necesitemos.

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;
#include <Trade\OrderInfo.mqh>
class COrderInfo;

Ahora definiremos las variables globales que necesitamos.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long     onnx_model;
int      ma_handler,state;
double   bid,ask,vol;
vectorf  model_forecast   = vectorf::Zeros(1);
vectorf  rsi_model_output = vectorf::Zeros(1);
double   min_volume,max_volume_increase, volume_step, buy_stop_loss, sell_stop_loss,atr_stop,risk_equity;
double   take_profit = 0;
double   close_price[3],atr_reading[],ma_buffer[];
long     min_distance,login;
int      atr,close_average,ticket_1,ticket_2;
bool     authorized = false;
double   margin,lot_step;
string   currency,server;
bool     all_closed =true;
int      rsi_handler;
long     rsi_onnx_model;
double   indicator_reading[];
ENUM_ACCOUNT_TRADE_MODE account_type;
const double  stop_percent = 1;

Definamos nuestras variables de entrada.

//+------------------------------------------------------------------+
//| Technical indicators                                             |
//+------------------------------------------------------------------+
input group "Money Management"
input int    lot_multiple     = 10; // How big should the lot size be?
input double profit_target = 0;     // Profit Target
input double loss_target   = 0;     // Max Loss Allowed

input group "Money Management"
const int    atr_period = 200;      //ATR Period
input double atr_multiple =2.5;     //ATR Multiple

Ahora debemos definir exactamente cómo debe inicializarse nuestra aplicación comercial. Primero verificaremos que el usuario haya otorgado permiso a la aplicación para operar. Si tenemos permiso para continuar, cargaremos nuestros indicadores técnicos y modelos ONNX.

int OnInit()
  {
//Authorization
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
     {
      Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program");
      return(INIT_FAILED);
     }

   else
      if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
        {
         Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading");
         return(INIT_FAILED);
        }

      else
        {
         Comment("This License is Genuine");
         setup();
        }
//Everything was okay
   return(INIT_SUCCEEDED);
  }

Siempre que nuestra aplicación comercial ya no esté en uso, debemos liberar los recursos que ya no utilizamos, para garantizar que nuestro usuario final tenga una buena experiencia con nuestra aplicación.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   OnnxRelease(onnx_model);
   OnnxRelease(rsi_onnx_model);
   IndicatorRelease(atr)
  }

Cada vez que recibamos cotizaciones de precios actualizadas, actualizaremos nuestras variables y buscaremos nuevas oportunidades comerciales. De lo contrario, si ya tenemos operaciones abiertas, entonces actualizaremos nuestro stop loss dinámico.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//Update technical data
   update();

   if(PositionsTotal() == 0)
     {
      check_setup();
     }

   if(PositionsTotal() > 0)
     {
      check_atr_stop();
     }
  }

Para obtener una predicción de nuestro modelo, tenemos que definir los estados actuales del indicador RSI y de media móvil.

//+------------------------------------------------------------------+
//| Get a prediction from our model                                  |
//+------------------------------------------------------------------+
int model_predict(void)
  {
//MA Forecast
   vectorf  model_inputs = vectorf::Zeros(2);
   vectorf  rsi_model_inputs = vectorf::Zeros(3);
   CopyBuffer(ma_handler,0,0,40,ma_buffer);

   if(ma_buffer[0] > ma_buffer[39])
     {
      model_inputs[0] = 1;
      model_inputs[1] = 0;
     }

   else
      if(ma_buffer[0] < ma_buffer[39])
        {
         model_inputs[1] = 1;
         model_inputs[0] = 0;
        }

//RSI Forecast
   CopyBuffer(rsi_handler,0,0,1,indicator_reading);

   if(indicator_reading[0] < 30)
     {
      rsi_model_inputs[0] = 1;
      rsi_model_inputs[1] = 0;
      rsi_model_inputs[2] = 0;
     }


   else
      if(indicator_reading[0] >70)
        {
         rsi_model_inputs[0] = 0;
         rsi_model_inputs[1] = 1;
         rsi_model_inputs[2] = 0;
        }

      else
        {
         rsi_model_inputs[0] = 0;
         rsi_model_inputs[1] = 0;
         rsi_model_inputs[2] = 1;
        }

//Model predictions
   OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast);
   OnnxRun(rsi_onnx_model,ONNX_DEFAULT,rsi_model_inputs,rsi_model_output);


//Evaluate model output for buy setup
   if(((rsi_model_output[0] > 0)  && (model_forecast[0] > 0)))
     {
      //AI Models forecast
      Comment("AI Forecast: UP");
      return(1);
     }

//Evaluate model output for a sell setup
   if((rsi_model_output[0] < 0) && (model_forecast[0] < 0))
     {
      Comment("AI Forecast: DOWN");
      return(-1);
     }

//Otherwise no position was found
   return(0);
  }

Actualice nuestras variables globales. Para nosotros es más limpio realizar estas actualizaciones en una sola llamada de función, en lugar de tener todo el código ejecutándose directamente en el controlador OnTick().

//+------------------------------------------------------------------+
//| Update our market data                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   buy_stop_loss = 0;
   sell_stop_loss = 0;
   static datetime time_stamp;
   datetime time = iTime(_Symbol,PERIOD_CURRENT,0);
   check_price(3);
   CopyBuffer(atr,0,0,1,atr_reading);
   CopyBuffer(ma_handler,0,0,1,ma_buffer);
   ArraySetAsSeries(atr_reading,true);
   atr_stop = ((min_volume + atr_reading[0]) * atr_multiple);
//On Every Candle
   if(time_stamp != time)
     {

      //Mark the candle
      time_stamp = time;
      OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,min_volume,ask,margin);
     }
  }

Cargar los recursos que necesitamos como nuestros indicadores técnicos, información de cuenta y mercado y otros datos de esa naturaleza.

//+------------------------------------------------------------------+
//| Load resources                                                   |
//+------------------------------------------------------------------+
bool setup(void)
  {
//Account Info
   currency = AccountInfoString(ACCOUNT_CURRENCY);
   server = AccountInfoString(ACCOUNT_SERVER);
   login = AccountInfoInteger(ACCOUNT_LOGIN);

//Indicators
   atr = iATR(_Symbol,PERIOD_CURRENT,atr_period);

//Setup technical indicators
   ma_handler   =iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_LOW);
   vol          = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN) * lot_multiple;
   rsi_handler  = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE);

//Market Information
   min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   max_volume_increase = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX) / SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);

//Define our ONNX model
   ulong ma_input_shape [] = {1,2};
   ulong rsi_input_shape [] = {1,3};
   ulong output_shape [] = {1,1};

//Create the model
   onnx_model     = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);
   rsi_onnx_model = OnnxCreateFromBuffer(rsi_onnx_buffer,ONNX_DEFAULT);

   if((onnx_model == INVALID_HANDLE) || (rsi_onnx_model == INVALID_HANDLE))
     {
      Comment("[ERROR] Failed to load AI module correctly");
      return(false);
     }

//Validate I/O
   if((!OnnxSetInputShape(onnx_model,0,ma_input_shape)) || (!OnnxSetInputShape(rsi_onnx_model,0,rsi_input_shape)))
     {
      Comment("[ERROR] Failed to set input shape correctly: ",GetLastError());
      return(false);
     }

   if((!OnnxSetOutputShape(onnx_model,0,output_shape)) || (!OnnxSetOutputShape(rsi_onnx_model,0,output_shape)))
     {
      Comment("[ERROR] Failed to load AI module correctly: ",GetLastError());
      return(false);
     }
//Everything went fine
   return(true);
  }

Poniendo todo junto, así es como se ve nuestra aplicación comercial.

//+------------------------------------------------------------------+
//|                                                    GBPUSD AI.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"

//+------------------------------------------------------------------+
//| Load our resources                                               |
//+------------------------------------------------------------------+
#resource  "\\Files\\AUDJPY D1 MA AI F22 P40.onnx" as const uchar onnx_buffer[];
#resource  "\\Files\\AUDJPY D1 RSI AI F22 P40.onnx" as const uchar rsi_onnx_buffer[];

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;
#include <Trade\OrderInfo.mqh>
class COrderInfo;

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long     onnx_model;
int      ma_handler,state;
double   bid,ask,vol;
vectorf  model_forecast   = vectorf::Zeros(1);
vectorf  rsi_model_output = vectorf::Zeros(1);
double   min_volume,max_volume_increase, volume_step, buy_stop_loss, sell_stop_loss,atr_stop,risk_equity;
double   take_profit = 0;
double   close_price[3],atr_reading[],ma_buffer[];
long     min_distance,login;
int      atr,close_average,ticket_1,ticket_2;
bool     authorized = false;
double   margin,lot_step;
string   currency,server;
bool     all_closed =true;
int      rsi_handler;
long     rsi_onnx_model;
double   indicator_reading[];
ENUM_ACCOUNT_TRADE_MODE account_type;
const double  stop_percent = 1;

//+------------------------------------------------------------------+
//| Technical indicators                                             |
//+------------------------------------------------------------------+
input group "Money Management"
input int    lot_multiple     = 10; // How big should the lot size be?
input double profit_target = 0;     // Profit Target
input double loss_target   = 0;     // Max Loss Allowed

input group "Money Management"
input int    bb_period = 36;        //Bollinger band period
input int    ma_period = 4;         //Moving average period
const int    atr_period = 200;      //ATR Period
input double atr_multiple =2.5;      //ATR Multiple

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//Authorization
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
     {
      Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program");
      return(INIT_FAILED);
     }

   else
      if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
        {
         Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading");
         return(INIT_FAILED);
        }

      else
        {
         Comment("This License is Genuine");
         setup();
        }
//--- Everything was okay
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   OnnxRelease(onnx_model);
   OnnxRelease(rsi_onnx_model);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Update technical data
   update();

   if(PositionsTotal() == 0)
     {
      check_setup();
     }

   if(PositionsTotal() > 0)
     {
      check_atr_stop();
     }
  }
//+------------------------------------------------------------------+
//| Get a prediction from our model                                  |
//+------------------------------------------------------------------+
int model_predict(void)
  {
//MA Forecast
   vectorf  model_inputs = vectorf::Zeros(2);
   vectorf  rsi_model_inputs = vectorf::Zeros(3);
   CopyBuffer(ma_handler,0,0,40,ma_buffer);

   if(ma_buffer[0] > ma_buffer[39])
     {
      model_inputs[0] = 1;
      model_inputs[1] = 0;
     }

   else
      if(ma_buffer[0] < ma_buffer[39])
        {
         model_inputs[1] = 1;
         model_inputs[0] = 0;
        }

//RSI Forecast
   CopyBuffer(rsi_handler,0,0,1,indicator_reading);

   if(indicator_reading[0] < 30)
     {
      rsi_model_inputs[0] = 1;
      rsi_model_inputs[1] = 0;
      rsi_model_inputs[2] = 0;
     }


   else
      if(indicator_reading[0] >70)
        {
         rsi_model_inputs[0] = 0;
         rsi_model_inputs[1] = 1;
         rsi_model_inputs[2] = 0;
        }

      else
        {
         rsi_model_inputs[0] = 0;
         rsi_model_inputs[1] = 0;
         rsi_model_inputs[2] = 1;
        }

//Model predictions
   OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast);
   OnnxRun(rsi_onnx_model,ONNX_DEFAULT,rsi_model_inputs,rsi_model_output);


//Evaluate model output for buy setup
   if(((rsi_model_output[0] > 0)  && (model_forecast[0] > 0)))
     {
      //AI Models forecast
      Comment("AI Forecast: UP");
      return(1);
     }

//Evaluate model output for a sell setup
   if((rsi_model_output[0] < 0) && (model_forecast[0] < 0))
     {
      Comment("AI Forecast: DOWN");
      return(-1);
     }

//Otherwise no position was found
   return(0);
  }

//+------------------------------------------------------------------+
//| Check for valid trade setups                                     |
//+------------------------------------------------------------------+
void check_setup(void)
  {
   int res = model_predict();

   if(res == -1)
     {
      Trade.Sell(vol,Symbol(),bid,0,0,"VD V75 AI");
      state = -1;
     }

   else
      if(res == 1)
        {
         Trade.Buy(vol,Symbol(),ask,0,0,"VD V75 AI");
         state = 1;
        }
  }

//+------------------------------------------------------------------+
//| Update our market data                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   buy_stop_loss = 0;
   sell_stop_loss = 0;
   static datetime time_stamp;
   datetime time = iTime(_Symbol,PERIOD_CURRENT,0);
   check_price(3);
   CopyBuffer(atr,0,0,1,atr_reading);
   CopyBuffer(ma_handler,0,0,1,ma_buffer);
   ArraySetAsSeries(atr_reading,true);
   atr_stop = ((min_volume + atr_reading[0]) * atr_multiple);
//On Every Candle
   if(time_stamp != time)
     {

      //Mark the candle
      time_stamp = time;
      OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,min_volume,ask,margin);
     }
  }

//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Load resources                                                   |
//+------------------------------------------------------------------+
bool setup(void)
  {
//Account Info
   currency = AccountInfoString(ACCOUNT_CURRENCY);
   server = AccountInfoString(ACCOUNT_SERVER);
   login = AccountInfoInteger(ACCOUNT_LOGIN);

//Indicators
   atr = iATR(_Symbol,PERIOD_CURRENT,atr_period);

//Setup technical indicators
   ma_handler   =iMA(Symbol(),PERIOD_CURRENT,40,0,MODE_SMA,PRICE_LOW);
   vol          = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN) * lot_multiple;
   rsi_handler  = iRSI(Symbol(),PERIOD_CURRENT,30,PRICE_CLOSE);

//Market Information
   min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   max_volume_increase = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX) / SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);

//Define our ONNX model
   ulong ma_input_shape [] = {1,2};
   ulong rsi_input_shape [] = {1,3};
   ulong output_shape [] = {1,1};

//Create the model
   onnx_model     = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);
   rsi_onnx_model = OnnxCreateFromBuffer(rsi_onnx_buffer,ONNX_DEFAULT);

   if((onnx_model == INVALID_HANDLE) || (rsi_onnx_model == INVALID_HANDLE))
     {
      Comment("[ERROR] Failed to load AI module correctly");
      return(false);
     }

//--- Validate I/O
   if((!OnnxSetInputShape(onnx_model,0,ma_input_shape)) || (!OnnxSetInputShape(rsi_onnx_model,0,rsi_input_shape)))
     {
      Comment("[ERROR] Failed to set input shape correctly: ",GetLastError());
      return(false);
     }

   if((!OnnxSetOutputShape(onnx_model,0,output_shape)) || (!OnnxSetOutputShape(rsi_onnx_model,0,output_shape)))
     {
      Comment("[ERROR] Failed to load AI module correctly: ",GetLastError());
      return(false);
     }
//--- Everything went fine
   return(true);
  }

//+------------------------------------------------------------------+
//| Close all our open positions                                     |
//+------------------------------------------------------------------+
void close_all()
  {
   if(PositionsTotal() > 0)
     {
      ulong ticket;
      for(int i =0;i < PositionsTotal();i++)
        {
         ticket = PositionGetTicket(i);
         Trade.PositionClose(ticket);
        }
     }
  }

//+------------------------------------------------------------------+
//| Update our trailing ATR stop                                     |
//+------------------------------------------------------------------+
void check_atr_stop()
  {

   for(int i = PositionsTotal() -1; i >= 0; i--)
     {

      string symbol = PositionGetSymbol(i);
      if(_Symbol == symbol)
        {

         ulong ticket = PositionGetInteger(POSITION_TICKET);
         double position_price = PositionGetDouble(POSITION_PRICE_OPEN);
         double type = PositionGetInteger(POSITION_TYPE);
         double current_stop_loss = PositionGetDouble(POSITION_SL);

         if(type == POSITION_TYPE_BUY)
           {
            double atr_stop_loss = (ask - (atr_stop));
            double atr_take_profit = (ask + (atr_stop));

            if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0))
              {
               Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
              }
           }

         else
            if(type == POSITION_TYPE_SELL)
              {
               double atr_stop_loss = (bid + (atr_stop));
               double atr_take_profit = (bid - (atr_stop));
               if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0))
                 {
                  Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
                 }
              }
        }
     }
  }

//+------------------------------------------------------------------+
//| Close our open buy positions                                     |
//+------------------------------------------------------------------+
void close_buy()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal();i++)
        {
         if(PositionGetSymbol(i) == _Symbol)
           {
            ticket = PositionGetTicket(i);
            type = (int)PositionGetInteger(POSITION_TYPE);
            if(type == POSITION_TYPE_BUY)
              {
               Trade.PositionClose(ticket);
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Close our open sell positions                                    |
//+------------------------------------------------------------------+
void close_sell()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal();i++)
        {
         if(PositionGetSymbol(i) == _Symbol)
           {
            ticket = PositionGetTicket(i);
            type = (int)PositionGetInteger(POSITION_TYPE);
            if(type == POSITION_TYPE_SELL)
              {
               Trade.PositionClose(ticket);
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Get the most recent price values                                 |
//+------------------------------------------------------------------+
void check_price(int candles)
  {
   for(int i = 0; i < candles;i++)
     {
      close_price[i] = iClose(_Symbol,PERIOD_CURRENT,i);
     }
  }
//+------------------------------------------------------------------+

Ahora vamos a realizar una prueba retrospectiva de nuestro algoritmo comercial utilizando datos que no le mostramos al algoritmo cuando lo estábamos entrenando. El período que hemos seleccionado se extiende desde principios de enero de 2023 hasta el 28 de junio de 2024, en las cotizaciones diarias del mercado en el par AUDJPY. Estableceremos el parámetro "Período Forward" en "No" porque nos hemos asegurado de que las fechas que seleccionamos no se observaran cuando entrenamos el modelo.


Figura 12: El símbolo y marco temporal que utilizaremos para evaluar nuestra estrategia de trading.

Además, imitaremos las condiciones comerciales reales configurando primero el parámetro "Retrasos" en "Retraso aleatorio". Este parámetro controla la latencia entre el momento en que se realizan nuestras órdenes y el momento en que se ejecutan. Configurarlo para que sea aleatorio es similar a lo que ocurre en el comercio real, ya que nuestra latencia no es constante en todo momento. Además, le indicaremos a nuestra terminal que modele el mercado utilizando ticks reales. Esta configuración ralentizará ligeramente nuestra prueba retrospectiva, ya que el terminal primero tendrá que obtener datos detallados del mercado de nuestro bróker a través de Internet.

Los últimos parámetros, que controlan el depósito de la cuenta y el apalancamiento, deben ajustarse para adaptarse a su configuración de trading. Suponiendo que conseguimos recuperar todos los datos que solicitamos, comenzará nuestra prueba retrospectiva.

Figura 13: Los parámetros que utilizaremos para nuestra prueba retrospectiva.

Figura 14: El desempeño de nuestra estrategia en datos con los que nuestro modelo no fue entrenado.

Figura 12: Más detalles de nuestra prueba retrospectiva sobre datos de mercado no vistos.


Conclusión

Los amplios datos de mercado que hemos analizado hoy nos dicen claramente que si desea pronosticar intervalos menores a 40 pasos en el futuro, probablemente sea mejor predecir el precio directamente. Sin embargo, si desea pronosticar más de 40 pasos en el futuro, probablemente sea mejor predecir el cambio en la media móvil en lugar del cambio en el precio. Siempre hay más mejoras esperando a que las observemos y veamos la diferencia que marcan. Es evidente que cualquier tiempo dedicado a transformar los datos de entrada merece la pena, ya que nos permite exponer las relaciones subyacentes a nuestros modelos de una forma más significativa.


Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16230

Too Chee Ng
Too Chee Ng | 27 mar 2025 en 10:43

El resultado parece prometedor; lo probaré.

Más de este tipo, por favor.

Gracias.

npats2007
npats2007 | 13 jun 2025 en 15:03
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 10 jul 2025 en 09:31
Too Chee Ng # :

Los resultados parecen prometedores.

Más cosas como ésta, por favor.

Más cosas como ésta, por favor. Gracias.

De nada Too Che Ng.

Definitivamente todavía se puede decir mucho más dado un comienzo tan fuerte.


Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 10 jul 2025 en 09:33
npats2007 # :

Algunas imágenes no se muestran...

Lamento oír eso, estoy seguro de que los moderadores se pondrán a arreglar eso, dado que ya tienen mucho que hacer tal y como están las cosas.

Robot comercial multimodular en Python y MQL5 (Parte I): Creamos la arquitectura básica y los primeros módulos Robot comercial multimodular en Python y MQL5 (Parte I): Creamos la arquitectura básica y los primeros módulos
Hoy desarrollaremos un sistema comercial modular que combina Python para el análisis de datos con MQL5 para la ejecución de transacciones. Sus cuatro módulos independientes supervisan en paralelo distintos aspectos del mercado: volúmenes, arbitraje, economía y riesgo, y utilizan RandomForest con 400 árboles para el análisis. Se hace especial hincapié en la gestión del riesgo, porque sin una gestión eficaz del riesgo, ni siquiera los algoritmos comerciales más avanzados sirven de mucho.
Criterios de tendencia en el trading Criterios de tendencia en el trading
Las tendencias son una parte importante de muchas estrategias comerciales. En este artículo analizaremos algunas de las herramientas utilizadas para identificar tendencias y sus características. Comprender e interpretar correctamente las tendencias puede mejorar sustancialmente los resultados comerciales y minimizar los riesgos.
Asesores Expertos Auto-Optimizables con MQL5 y Python (Parte VI): Cómo aprovechar el doble descenso profundo Asesores Expertos Auto-Optimizables con MQL5 y Python (Parte VI): Cómo aprovechar el doble descenso profundo
El aprendizaje automático tradicional enseña a los profesionales a estar atentos para no sobreajustar sus modelos. Sin embargo, esta ideología está siendo cuestionada por nuevos hallazgos publicados por diligentes investigadores de Harvard, quienes han descubierto que lo que parece ser un sobreajuste puede, en algunas circunstancias, ser el resultado de finalizar prematuramente los procedimientos de entrenamiento. Demostraremos cómo podemos utilizar las ideas publicadas en el artículo de investigación para mejorar nuestro uso de la IA en la previsión de retornos del mercado.
Algoritmo de Tribu Artificial (Artificial Tribe Algorithm, ATA) Algoritmo de Tribu Artificial (Artificial Tribe Algorithm, ATA)
Este artículo detalla los componentes clave y las innovaciones del algoritmo de optimización ATA, un método evolutivo con un sistema de comportamiento dual único que se adapta según la situación. Usando el cruce para la exploración en profundidad y la migración para la búsqueda cuando se dan atascos en óptimos locales, el ATA combina el aprendizaje individual y el social.