
Ingeniería de características con Python y MQL5 (Parte I): Predicción de medias móviles para modelos de IA de largo plazo
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()
¿Cuántos símbolos tenemos disponibles?
#The total number of symbols we have print(f"Total Symbols Available: ",mt5.symbols_total())
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")
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()
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"]))
#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"]))
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:
- Más de 70.
- Menos de 30.
- Entre 70 y 30.
#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_
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))
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_
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))
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
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
El resultado parece prometedor; lo probaré.
Más de este tipo, por favor.
Gracias.
Se ha publicado el artículo Trait Engineering with Python and MQL5 (Part I): Modelos de IA para previsiones a largo plazo sobre medias móviles:
Autor: Gamuchirai Zororo Ndawana
Algunas de las imágenes no se muestran...
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.
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.