
Reimaginando las estrategias clásicas (Parte II): Ruptura de las Bandas de Bollinger
Introducción
Las Bandas de Bollinger son herramientas versátiles en las estrategias comerciales, efectivas tanto para seguir tendencias como para identificar posibles puntos de giro o reversión. Técnicamente, el indicador se compone de una media móvil exponencial (Exponential Moving Average, EMA) que suaviza el precio de cierre de un valor. Esta línea central está envuelta por dos líneas adicionales, ubicadas por encima y por debajo de la EMA con una diferencia de típicamente 2 desviaciones estándar.
En este artículo, nuestro objetivo es analizar empíricamente los beneficios de la estrategia desde la base. Nuestro objetivo es ayudar a los lectores que pueden estar considerando el uso de las Bandas de Bollinger para decidir si la estrategia puede ser más adecuado para ellos. Además, mostraremos cómo pueden utilizarse los indicadores técnicos para guiar los modelos de IA y, con un poco de suerte, desarrollar estrategias de trading más estables.
Para ello, entrenamos dos modelos de IA equivalentes mediante el algoritmo de análisis discriminante lineal y comparamos los modelos mediante validación cruzada de series temporales, basándonos únicamente en la biblioteca scikit-learn para las pruebas. El primer modelo fue entrenado para predecir simplemente si el precio se apreciaría o depreciaría, mientras que el segundo aprendió a pronosticar cómo se mueve el precio entre las cuatro zonas delineadas por la Banda de Bollinger. Desafortunadamente para los fanáticos de las Bandas de Bollinger, nuestras observaciones empíricas nos llevaron a concluir que predecir el precio directamente puede ser más efectivo que pronosticar la transición entre las cuatro zonas creadas por las Bandas de Bollinger. Sin embargo, cabe señalar que no se emplearon técnicas de optimización para establecer los parámetros del indicador.
Este artículo pretende demostrar:
- Cómo comparar analíticamente dos posibles estrategias comerciales.
- Cómo implementar el análisis discriminante lineal desde cero en MQL5.
- Cómo crear estrategias comerciales estables que incorporen IA.
Panorama de la estrategia y nuestras motivaciones
El término “Inteligencia Artificial” (IA) es posiblemente una de las convenciones de nomenclatura más engañosas de la historia. Después de leer este artículo, usted podrá estar de acuerdo en que el término «IA» es un término inapropiado. Como autor, mi problema radica en la palabra “inteligencia”. Los modelos de IA no son inteligentes en el sentido humano. Más bien, son aplicaciones inteligentes de algoritmos de optimización.
Los modelos de IA tienen como objetivo principal minimizar los errores o maximizar las recompensas dentro de un sistema. Sin embargo, las soluciones derivadas de estos modelos pueden no ser siempre prácticas. Por ejemplo, un sistema de IA diseñado para minimizar las pérdidas en una cuenta de operaciones podría concluir que no realizar operaciones es la mejor solución, ya que garantiza que no habrá pérdidas. Si bien esta solución satisface matemáticamente el problema en cuestión, no es práctica para el comercio.
Como profesionales de IA inteligente, debemos guiar nuestros modelos con restricciones cuidadosamente planificadas. En este artículo, dirigiremos nuestros modelos de IA utilizando Bandas de Bollinger. Identificaremos cuatro posibles zonas donde podría estar el precio en cualquier momento. Tenga en cuenta que el precio solo puede estar en una de estas cuatro zonas en un momento dado:
- Zona 1: El precio está completamente por encima de las Bandas de Bollinger
- Zona 2: El precio está por encima de la banda media pero por debajo de la banda alta.
- Zona 3: El precio está por encima de la banda baja pero por debajo de la banda media.
- Zona 4: El precio está por debajo de la banda baja.
Entrenaremos un modelo para comprender cómo transiciona el precio entre estas cuatro zonas y predecir la próxima zona a la que se moverá el precio. Las señales comerciales se generan siempre que el precio cambia de una zona a otra. Por ejemplo, si nuestro modelo predice que el precio se moverá de la Zona 2 a la Zona 1, lo interpretamos como un movimiento ascendente e iniciamos una orden de compra. Nuestro modelo y Asesor Experto se implementarán completamente en MQL5 nativo.
Las Bandas de Bollinger se pueden utilizar en una variedad de estrategias comerciales, desde el seguimiento de tendencias hasta la identificación de puntos de giro o reversión. Técnicamente, este indicador consiste en una media móvil exponencial (EMA) que normalmente suaviza el precio de cierre de un valor. Está flanqueado por dos Bandas adicionales: una ubicada encima y otra debajo de la EMA, cada una normalmente establecida en 2 desviaciones estándar.
Tradicionalmente, las Bandas de Bollinger se utilizan para identificar niveles de precios de sobrecompra y sobreventa. Cuando los precios alcanzan la banda superior de Bollinger, tienden a caer hasta el valor medio, y este comportamiento suele ser válido también para la banda inferior. Esto puede interpretarse como que el valor se descuenta en 2 desviaciones estándar cuando alcanza la banda inferior, lo que potencialmente atrae a los inversores a comprar el activo con un descuento atractivo. Sin embargo, hay momentos en que los precios pueden romper violentamente fuera de las Bandas de Bollinger y continuar en una tendencia fuerte. Lamentablemente, nuestro análisis estadístico muestra que puede ser más difícil pronosticar rupturas de las Bandas de Bollinger que pronosticar cambios en los precios.Obtención de datos desde nuestro terminal MetaTrader 5
Para comenzar, abra su terminal MetaTrader 5 y haga clic en el ícono Símbolo en el menú contextual; debería ver una lista de símbolos disponibles en su terminal.
Fig. 1: Preparación para obtener datos de nuestra terminal MetaTrader 5.
Luego haga clic en la ventana Barras y busque el símbolo que desea modelar, seleccione el marco de tiempo que desea utilizar. Para nuestro ejemplo, modelaré el tipo de cambio diario GBPUSD.
Fig. 2: Preparándonos para exportar nuestros datos.
Luego haga clic en el botón "Exportar barras" y continuaremos nuestro análisis en Python.
Análisis exploratorio de datos
Visualicemos las interacciones entre las Bandas de Bollinger y los cambios en los niveles de precios.
Comenzaremos importando las bibliotecas que necesitamos.
#Import libraries import pandas as pd import numpy as np import seaborn as sns import pandas_ta as taLuego leeremos el archivo CSV que generamos para nuestra prueba empírica. Tenga en cuenta que pasamos el parámetro sep="\t" para indicar que nuestro archivo CSV está delimitado por tabulaciones. Esta es la salida estándar del terminal MetaTrader 5.
#Read in the csv file csv = pd.read_csv("/home/volatily/market_data/GBPUSD_Daily_20160103_20240131.csv",sep="\t")Definamos ahora nuestro horizonte de pronóstico.
#Define how far into the future we should forecast look_ahead = 20Ahora calcularemos las Bandas de Bollinger para los datos que tenemos usando la biblioteca pandas ta.
#Add the Bollinger bands csv.ta.bbands(length=30,std=2,append=True)A continuación necesitamos una columna para almacenar el precio de cierre futuro.
#Add a column to show the future price csv["Future Close"] = csv["Close"].shift(-look_ahead)
Ahora etiquetaremos nuestros datos. Tendremos dos etiquetas, una que denota el cambio de precio y la otra que denota el cambio de precio entre las zonas de las Bandas de Bollinger. Los cambios de precio se etiquetarán como 1 para arriba y 0 para abajo. Las etiquetas de las Bandas de Bollinger se definieron anteriormente.
#Add the normal target, predicting changes in the close price csv["Price Target"] = 0 csv["Price State"] = 0 #Label the data our conditions #If price depreciated, our label is 0 csv.loc[csv["Close"] < csv["Close"].shift(look_ahead),"Price State"] = 0 csv.loc[csv["Close"] > csv["Future Close"], "Price Target"] = 0 #If price appreciated, our label is 1 csv.loc[csv["Close"] > csv["Close"].shift(look_ahead),"Price State"] = 1 csv.loc[csv["Close"] < csv["Future Close"], "Price Target"] = 1 #Label the Bollinger bands #The label to store the current state of the market csv["Current State"] = -1 #If price is above the upper-band, our label is 1 csv.loc[csv["Close"] > csv["BBU_30_2.0"], "Current State"] = 1 #If price is below the upper-band and still above the mid-band,our label is 2 csv.loc[(csv["Close"] < csv["BBU_30_2.0"]) & (csv["Close"] > csv["BBM_30_2.0"]),"Current State"] = 2 #If price is below the mid-band and still above the low-band,our label is 3 csv.loc[(csv["Close"] < csv["BBM_30_2.0"]) & (csv["Close"] > csv["BBL_30_2.0"]),"Current State"] = 3 #Finally, if price is beneath the low-band our label is 4 csv.loc[csv["Close"] < csv["BBL_30_2.0"], "Current State"] = 4 #Now we can add a column to denote the future state the market will be in csv["State Target"] = csv["Current State"].shift(-look_ahead)
Eliminemos cualquier entrada nula.
#Let's drop any NaN values csv.dropna(inplace=True)
Ahora estamos listos para comenzar a visualizar nuestros datos, comenzando con los cambios en los niveles de precios utilizando diagramas de caja. En el eje y, mostraremos los precios de cierre, y en el eje x, tendremos dos valores. El primer valor en el eje x representa instancias en nuestros datos donde el precio estaba cayendo, marcado como 0. Dentro del valor 0, observará dos diagramas de caja. El primer diagrama de caja, que se muestra en azul, representa instancias en las que el precio cayó durante 20 velas y continuó cayendo durante otras 20 velas. El diagrama de caja naranja representa instancias en las que el precio cayó durante 20 velas pero luego se apreció durante las siguientes 20 velas. Observe que en los datos que recopilamos, parece que siempre que los niveles de precios cayeron por debajo de 1.1, rebotaron. Por el contrario, el valor 1 en el eje x también tiene dos gráficos de caja encima. El primer gráfico de caja azul resume los casos en los que el precio se apreció y luego se depreció, mientras que el segundo gráfico de caja naranja resume los casos en los que el precio subió y continuó subiendo.
Tenga en cuenta que para el valor 1, o en otras palabras, cuando el precio aumenta durante 20 velas, la cola del gráfico de caja azul es mayor que la del gráfico de caja naranja. Esto puede indicar que siempre que el tipo de cambio GBPUSD sube hacia el nivel 1,5, tiende a caer, mientras que en la columna 0, cuando el tipo de cambio cae alrededor del nivel 1.1, parece que el precio tiene una tendencia a revertirse y comenzar a subir.
#Notice that the tails of the box plots have regions where they stop overlapping these zones may guide us as boundaries sns.boxplot(data=csv,x="Price State",y="Close",hue="Price Target")
Fig. 3: Visualización de los cambios en los niveles de precios.
También podemos realizar visualizaciones similares utilizando los estados definidos por las Bandas de Bollinger. Como antes, el precio de cierre estará en el eje y, y la ubicación actual del precio dentro de las Bandas de Bollinger estará marcada por los cuatro valores en el eje x. Observe que las colas de los diagramas de caja tienen regiones en las que naturalmente no se superponen. Estas regiones podrían potencialmente servir como límites de clasificación. Por ejemplo, observe que siempre que el precio está en el estado 4, o completamente debajo de las Bandas de Bollinger, y se acerca al nivel 1.1, parece siempre rebotar.
#Notice that the tails of the box plots have regions where they stop overlapping these zones may guide us as boundaries sns.boxplot(data=csv,x="Current State",y="Close",hue="Price Target")
Fig. 4: Visualización del comportamiento del precio dentro de las 4 zonas de las Bandas de Bollinger.
Además, también podemos visualizar cómo evoluciona el precio entre los cuatro estados de las Bandas de Bollinger utilizando diagramas de caja. Por ejemplo, el diagrama de caja a continuación tiene el precio de cierre en el eje y y cuatro valores en el eje x que indican las cuatro zonas creadas por las Bandas de Bollinger. Cada diagrama de caja resume hacia dónde se dirigió el precio después de aparecer en esa zona. Interpretemos los datos juntos. Tenga en cuenta que el primer valor, estado 1, solo tiene tres diagramas de caja. Esto significa que desde el estado 1, el precio solo pasa a tres estados posibles: permanece en el estado 1 o pasa a los estados 2 o 3.
#Notice that the tails of the box plots have regions where they stop overlapping these zones may guide us as boundaries sns.boxplot(data=csv,x="Current State",y="Close",hue="State Target")
Fig. 5: Visualización del comportamiento del precio dentro de las 4 zonas.
#we have very poor separation in the data sns.catplot(data=csv,x="Price State",y="Close",hue="Price Target")
Fig. 6: Visualización de la separación en el conjunto de datos.
Podemos realizar las mismas visualizaciones utilizando los cuatro estados definidos por las Bandas de Bollinger. Una vez más, observamos que los puntos azules y naranjas se separan mejor en los niveles de precios extremos.
#Visualizing the separation of data in the Bollinger band zones sns.catplot(data=csv,x="Current State",y="Close",hue="Price Target")
Fig. 7: Visualización de la separación de los datos en las zonas Bollinger Band.
Ahora podemos crear un gráfico de dispersión con el valor de cierre en el eje x y el precio de cierre futuro en el eje y. Colorearemos los puntos en naranja o azul dependiendo de si el precio subió o bajó durante las 20 velas anteriores. Imagínese colocar una línea dorada desde la esquina inferior izquierda hasta la esquina superior derecha del gráfico. Todos los puntos por encima de esta línea dorada representan instancias en las que el precio terminó subiendo durante las siguientes 20 velas, independientemente de si cayó (azul) o subió (naranja) durante las 20 velas anteriores. Observe que hay una mezcla de puntos azules y naranjas a ambos lados de la línea dorada.
Además, observe que si colocamos una línea roja imaginaria en el valor de cierre de 1,3, habría muchos puntos azules y naranjas tocando esta línea. Esto implica que otras variables afectan el precio de cierre futuro además del precio de cierre actual. Otra forma de interpretar estas observaciones es que el mismo valor de entrada puede generar diferentes valores de salida, lo que indica que nuestro conjunto de datos es ruidoso.
#Nótese que utilizando el objetivo de precio nos da una bonita separación en el conjunto de datos. sns.scatterplot(data=csv,x=«Close»,y=«Future Close»,hue=«Price Target»)
Fig. 8: Nuestro conjunto de datos tiene muy poca separación natural.
Ahora realizaremos la misma visualización utilizando el estado objetivo de las Bandas de Bollinger para colorear el gráfico de dispersión. Tenga en cuenta que tenemos una separación muy pobre dentro de nuestro conjunto de datos cuando usamos las Bandas de Bollinger. Visualmente, parece incluso peor que la separación que obtuvimos cuando simplemente usamos el precio en sí.
#Using the Bollinger bands to define states, however, gives us rather mixed separation sns.scatterplot(data=csv,x="Close",y="Future Close",hue="Current State")
Fig. 9: Visualización de la separación en el conjunto de datos creado por las zonas de Bandas de Bollinger.
Realicemos ahora nuestras pruebas analíticas para determinar si logramos una mayor precisión al predecir cambios en los niveles de precios o cambios en los estados de las Bandas de Bollinger. Primero, importamos las librerías necesarias.
#Now let us compare our accuracy forecasting the original price target and the new Bollinger bands target from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import accuracy_score
A continuación, definiremos nuestros parámetros de validación cruzada de series de tiempo. El primer parámetro, divisiones, especifica el número de particiones que se crearán a partir de nuestros datos. El segundo parámetro, "gap", determina el tamaño del espacio entre cada partición. Esta brecha (gap) debería ser al menos tan grande como nuestro horizonte de pronóstico.
#Now let us define the cross validation parameters splits = 10 gap = look_ahead
Ahora podemos crear nuestro objeto de serie temporal, que nos proporcionará los índices apropiados para nuestro conjunto de entrenamiento y conjunto de prueba. En nuestro ejemplo, generará 10 pares de índices para entrenar y evaluar nuestro modelo.
#Now create the cross validation object
tscv = TimeSeriesSplit(n_splits=splits,gap=gap)
A continuación, crearemos un "DataFrame" para almacenar la precisión de nuestro modelo para pronosticar cada objetivo.
#We need a dataframe to store the accuracy associated with each target target_accuracy = pd.DataFrame(index=np.arange(0,splits),columns=["Price Target Accuracy","New Target Accuracy"])
Ahora definiremos las entradas de nuestro modelo.
#Define the inputs predictors = ["Open","High","Low","Close"] target = "Price Target"
Ahora realizaremos la prueba de validación cruzada.
#Now let us perform the cross validation for i,(train,test) in enumerate(tscv.split(csv)): #First initialize the model model = LinearDiscriminantAnalysis() #Now train the model model.fit(csv.loc[train[0]:train[-1],predictors],csv.loc[train[0]:train[-1],target]) #Now record the accuracy target_accuracy.iloc[i,0] = accuracy_score(csv.loc[test[0]:test[-1],target],model.predict(csv.loc[test[0]:test[-1],predictors]))
Ahora finalmente podemos analizar el resultado de las pruebas.
target_accuracy
Fig. 10: Nuestro modelo funcionó mejor al pronosticar cambios en el precio directamente.
Como se mencionó anteriormente, nuestras pruebas demostraron que nuestro modelo es más efectivo para predecir los niveles de precios que las transiciones de las Bandas de Bollinger. Sin embargo, cabe señalar que, en promedio, las dos estrategias no son significativamente diferentes.
A continuación, implementaremos la estrategia en el código MQL5 para probarla y ver cómo funciona con datos reales del mercado.
Implementando la estrategia
Para comenzar, primero importaremos las bibliotecas necesarias que usaremos en todo nuestro programa.
//+------------------------------------------------------------------+ //| Target Engineering.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Libraries we need | //+------------------------------------------------------------------+ /* This Expert Advisor will implement the Linear Discriminant Anlysis algorithm to help us successfully trade Bollinger Band Breakouts. Gamuchirai Zororo Ndawana Selebi Phikwe Botswana Wednesday 10 July 2024 15:42 */ #include <Trade/Trade.mqh>//Trade class CTrade Trade;
A continuación, definiremos entradas configurables por el usuario, como el período de las Bandas de Bollinger y la desviación estándar.
//+------------------------------------------------------------------+ //| Input variables | //+------------------------------------------------------------------+ input double bband_deviation = 2.0;//Bollinger Bands standard deviation input int bband_period = 60; //Bollinger Bands Period input int look_ahead = 10; //How far into the future should we forecast? int input lot_multiple = 1; //How many times bigger than minimum lot? int input fetch = 200;//How much data should we fetch? input double stop_loss_values = 1;//Stop loss values
Posteriormente definiremos las variables globales que se utilizarán en nuestra aplicación.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int bband_handler;//Technical Indicator Handlers vector bband_high_reading = vector::Ones(fetch);//Bollinger band high reading vector bband_mid_reading = vector::Ones(fetch);//Bollinger band mid reading vector bband_low_reading = vector::Ones(fetch);//Bollinger band low reading double minimum_volume;//The smallest contract size allowed double ask_price;//Ask double bid_price;//Bid vector input_data = vector::Zeros(fetch);//All our input data will be kept in vectors int training_output_array[];//Our output data will be stored in a vector vector output_data = vector::Zeros(fetch); double variance;//This is the variance of our input data int classes = 4;//The total number of output classes we have vector mean_values = vector::Zeros(classes);//This vector will store the mean value for each class vector probability_values = vector::Zeros(classes);//This vector will store the prior probability the target will belong each class vector total_class_count = vector::Zeros(classes);//This vector will count the number of times each class was the target bool model_trained = false;//Has our model been trained? bool training_procedure_running = false;//Have we started the training process? int forecast = 0;//Our model's forecast double discriminant_values[4];//The discriminant function int current_state = 0;//The current state of the system
A continuación, necesitamos definir la función de inicialización de nuestro Asesor Experto. En esta función, inicializaremos el indicador de Bandas de Bollinger y obtendremos datos importantes del mercado.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the bollinger bands bband_handler = iBands(_Symbol,PERIOD_CURRENT,bband_period,0,bband_deviation,PRICE_CLOSE); //--- Market data minimum_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); //--- End of initilization return(INIT_SUCCEEDED); }
A continuación, definiremos funciones auxiliares esenciales para dividir nuestro código en segmentos más pequeños y manejables. La primera función que crearemos será la encargada de actualizar nuestros datos de mercado.
//+------------------------------------------------------------------+ //|This function will update the price and other technical data | //+------------------------------------------------------------------+ void update_technical_data(void) { //--- Update the bid and ask prices ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK); bid_price = SymbolInfoDouble(_Symbol,SYMBOL_BID); }
Posteriormente, necesitamos implementar una función que organice el procedimiento de inicialización. Esta función garantizará que obtengamos datos de entrenamiento, ajustemos nuestro modelo y comencemos a realizar pronósticos en la secuencia correcta.
//+------------------------------------------------------------------+ //|This function will start training our model | //+------------------------------------------------------------------+ void model_initialize(void) { //--- First we have to fetch the input and output data Print("Initializing the model"); int input_start = 1 + (look_ahead * 2); int output_start = 1+ look_ahead; fetch_input_data(input_start,fetch); fetch_output_data(output_start,fetch); //--- Fit the model fit_lda_model(); }
A continuación, definiremos la función encargada de obtener los datos de entrada para entrenar nuestro modelo. Es importante tener en cuenta que la entrada del modelo consistirá en el estado actual del mercado, específicamente, qué zona ocupa actualmente el mercado. El modelo luego pronosticará a qué zona se moverá el mercado a continuación.
//+------------------------------------------------------------------+ //|This function will fetch the inputs for our model | //+------------------------------------------------------------------+ void fetch_input_data(int f_start,int f_fetch) { //--- This function will fetch input data for our model Print("Fetching input data"); //--- The input for our model will be the current state of the market //--- To know the current state of the market, we have to first update our indicator readings bband_mid_reading.CopyIndicatorBuffer(bband_handler,0,f_start,f_fetch); bband_high_reading.CopyIndicatorBuffer(bband_handler,1,f_start,f_fetch); bband_low_reading.CopyIndicatorBuffer(bband_handler,2,f_start,f_fetch); vector historical_prices; historical_prices.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,f_start,f_fetch); //--- Reshape the input data input_data.Resize(f_fetch); //--- Now we will input the state of the market for(int i = 0; i < f_fetch;i++) { //--- Are we above the bollinger bands entirely? if(historical_prices[i] > bband_high_reading[i]) { input_data[i] = 1; } //--- Are we between the upper and mid band? else if((historical_prices[i] < bband_high_reading[i]) && (historical_prices[i] > bband_mid_reading[i])) { input_data[i] = 2; } //--- Are we between the mid and lower band? else if((historical_prices[i] < bband_mid_reading[i]) && (historical_prices[i] > bband_low_reading[i])) { input_data[i] = 3; } //--- Are we below the bollinger bands entirely? else if(historical_prices[i] < bband_low_reading[i]) { input_data[i] = 4; } } //--- Show the input data Print(input_data); }
De ahora en adelante, necesitaremos una función para recuperar los datos de salida de nuestro modelo. Esta tarea es más compleja que obtener los datos de entrada. No solo debemos registrar la zona final en la que terminó el precio, sino también rastrear cuántas veces cada zona fue la salida. Este recuento es crucial para estimar los parámetros de nuestro modelo LDA en una etapa posterior.
A partir de este momento estamos preparados para implementar nuestro modelo LDA. Hay varios métodos disponibles para ajustar el modelo; hoy nos centraremos en un enfoque específico.
//+---------------------------------------------------------------------+ //|Fetch the output data for our model | //+---------------------------------------------------------------------+ void fetch_output_data(int f_start,int f_fetch) { //--- The output for our model will be the state of the market //--- To know the state of the market, we have to first update our indicator readings Print("Fetching output data"); bband_mid_reading.CopyIndicatorBuffer(bband_handler,0,f_start,(f_fetch)); bband_high_reading.CopyIndicatorBuffer(bband_handler,1,f_start,(f_fetch)); bband_low_reading.CopyIndicatorBuffer(bband_handler,2,f_start,(f_fetch)); vector historical_prices; historical_prices.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,f_start,f_fetch); //--- First we have to ensure that the class count has been reset total_class_count[0] = 0; total_class_count[1] = 0; total_class_count[2] = 0; total_class_count[3] = 0; //--- Now we need to resize the matrix ArrayResize(training_output_array,f_fetch); //--- Now we will input the state of the market to our output vector for(int i =0 ; i < f_fetch;i++) { //--- Are we above the bollinger bands entirely? if(historical_prices[i] > bband_high_reading[i]) { training_output_array[i] = 1; total_class_count[0] += 1; } //--- Are we between the upper and mid band? else if((historical_prices[i] < bband_high_reading[i]) && (historical_prices[i] > bband_mid_reading[i])) { training_output_array[i] = 2; total_class_count[1] += 1; } //--- Are we between the mid and lower band? else if((historical_prices[i] < bband_mid_reading[i]) && (historical_prices[i] > bband_low_reading[i])) { training_output_array[i] = 3; total_class_count[2] += 1; } //--- Are we below the bollinger bands entirely? else if(historical_prices[i] < bband_low_reading[i]) { training_output_array[i] = 4; total_class_count[3] += 1; } } //--- Show the output data Print("Final state of output vector"); ArrayPrint(training_output_array); //--- Show the total number of times each class appeared as the target. Print(total_class_count); }
El proceso es un poco complejo y requiere una explicación detallada. Inicialmente, calculamos la suma total de todos los valores de entrada correspondientes a cada clase en la salida. Por ejemplo, para cada caso en el que el objetivo era 1, calculamos la suma de todos los valores de entrada asignados a una salida de 1, y así sucesivamente para cada clase de salida. Posteriormente, calculamos el valor medio de X para cada clase. Si hubiera múltiples entradas, calcularíamos el valor medio para cada entrada. Siguiendo adelante, procedemos a determinar la probabilidad de que cada clase aparezca como el objetivo real, en función de los datos del conjunto de entrenamiento. A continuación, calculamos la varianza de X para cada clase de y. Por último, actualizamos nuestros indicadores para indicar la finalización del procedimiento de entrenamiento.
//+------------------------------------------------------------------+ //|Fit the LDA model | //+------------------------------------------------------------------+ void fit_lda_model(void) { //--- To fit the LDA model, we first need to know the mean value for each our inputs for each of our 4 classes double sum_class_one = 0; double sum_class_two = 0; double sum_class_three = 0; double sum_class_four = 0; //--- In this case we only have 1 input for(int i = 0; i < fetch;i++) { //--- Class 1 if(training_output_array[i] == 1) { sum_class_one += input_data[i]; } //--- Class 2 else if(training_output_array[i] == 2) { sum_class_two += input_data[i]; } //--- Class 3 else if(training_output_array[i] == 3) { sum_class_three += input_data[i]; } //--- Class 4 else if(training_output_array[i] == 4) { sum_class_four += input_data[i]; } } //--- Show the sums Print("Class 1: ",sum_class_one," Class 2: ",sum_class_two," Class 3: ",sum_class_three," Class 4: ",sum_class_four); //--- Calculate the mean value for each class mean_values[0] = sum_class_one / fetch; mean_values[1] = sum_class_two / fetch; mean_values[2] = sum_class_three / fetch; mean_values[3] = sum_class_four / fetch; Print("Mean values"); Print(mean_values); //--- Now we need to calculate class probabilities for(int i=0;i<classes;i++) { probability_values[i] = total_class_count[i] / fetch; } Print("Class probability values"); Print(probability_values); //--- Calculating the variance Print("Calculating the variance"); //--- Next we need to calculate the variance of the inputs within each class of y. //--- This process can be simplified into 2 steps //--- First we calculate the difference of each instance of x from the group mean. double squared_difference[4]; for(int i =0; i < fetch;i++) { //--- If the output value was 1, find the input value that created the output //--- Calculate how far that value is from it's group mean and square the difference if(training_output_array[i] == 1) { squared_difference[0] = MathPow((input_data[i]-mean_values[0]),2); } else if(training_output_array[i] == 2) { squared_difference[1] = MathPow((input_data[i]-mean_values[1]),2); } else if(training_output_array[i] == 3) { squared_difference[2] = MathPow((input_data[i]-mean_values[2]),2); } else if(training_output_array[i] == 4) { squared_difference[3] = MathPow((input_data[i]-mean_values[3]),2); } } //--- Show the squared difference values Print("Squared difference value for each output value of y"); ArrayPrint(squared_difference); //--- Next we calculate the variance as the average squared difference from the mean variance = (1.0/(fetch - 4.0)) * (squared_difference[0] + squared_difference[1] + squared_difference[2] + squared_difference[3]); Print("Variance: ",variance); //--- Update our flags to denote the model has been trained model_trained = true; training_procedure_running = false; } //+------------------------------------------------------------------+
Para realizar un pronóstico con nuestro modelo, comenzamos obteniendo los últimos datos de entrada del mercado. Utilizando estos datos de entrada, calculamos la función discriminante para cada clase posible. La clase con el valor de función discriminante más alto será nuestra clase predicha.
En MQL5, las matrices ofrecen una función útil llamada ArrayMaximum() que devuelve el índice del valor más grande en una matriz 1D. Dado que las matrices tienen índice cero, agregamos 1 al resultado de ArrayMaximum() para obtener la clase prevista.
//+------------------------------------------------------------------+ //|This function will obtain forecasts from our model | //+------------------------------------------------------------------+ int model_forecast(void) { //--- First we need to fetch the most recent input data fetch_input_data(0,1); //--- Update the current state of the system current_state = input_data[0]; //--- We need to calculate the discriminant function for each class //--- The predicted class is the one with the largest discriminant function Print("Calculating discriminant values."); for(int i = 0; i < classes; i++) { discriminant_values[i] = (input_data[0] * (mean_values[i]/variance) - (MathPow(mean_values[i],2)/(2*variance)) + (MathLog(probability_values[i]))); } ArrayPrint(discriminant_values); return(ArrayMaximum(discriminant_values) + 1); }
Después de obtener un pronóstico de nuestro modelo, el siguiente paso es interpretarlo y decidir en consecuencia. Como se mencionó anteriormente, nuestras señales comerciales se generan cuando el modelo predice que el precio se moverá a una zona diferente:
- Si el pronóstico indica un movimiento de la zona 1 a la zona 2, esto activa una señal de venta.
- Por el contrario, una previsión de movimiento de la zona 4 a la zona 3 indica una señal de compra.
- Sin embargo, si el pronóstico sugiere que el precio se mantendrá en la misma zona (por ejemplo, de zona 1 a zona 1), esto no genera una señal de entrada.
//+--------------------------------------------------------------------+ //|This function will interpret out model's forecast and execute trades| //+--------------------------------------------------------------------+ void find_entry(void) { //--- If the model's forecast is not equal to the current state then we are interested //--- Otherwise whenever the model forecasts that the state will remain the same //--- We are uncertain whether price levels will rise or fall if(forecast != current_state) { //--- If the model forecasts that we will move from a small state to a greater state //--- That is from 1 to 2 or from 2 to 4 then that is a down move if(forecast > current_state) { Trade.Sell(minimum_volume * lot_multiple,_Symbol,bid_price,(bid_price + stop_loss_values),(bid_price - stop_loss_values)); } //--- Otherwise we have a buy setup else { Trade.Buy(minimum_volume * lot_multiple,_Symbol,ask_price,(ask_price - stop_loss_values),(ask_price +stop_loss_values)); } } //--- Otherwise we do not have an entry signal from our model }
Por último, nuestro controlador de eventos OnTick() es responsable de gestionar el flujo de eventos y garantizar que solo operemos cuando nuestro modelo haya sido entrenado, además de satisfacer nuestras otras condiciones comerciales.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- We must always update market data update_technical_data(); //--- First we must ensure our model has been trained switch(model_trained) { //--- Our model has been trained case(true): //--- If we have no open positions, let's obtain a forecast from our model if(PositionsTotal() == 0) { //--- Obtaining a forecast forecast = model_forecast(); Comment("Model forecast: ",forecast); //--- Find an entry setup find_entry(); } break; //--- End of case 1 //--- Our model has not been trained default: //--- We haven't started the training procedure! if(!training_procedure_running) { Print("Our model has not been trained, starting the training procedure now."); //--- Initialize the model model_initialize(); } break; //--- End of default case } } //+------------------------------------------------------------------+
Fig. 11: Nuestro sistema de trading en acción.
Limitaciones
Hasta este punto, nuestra estrategia se enfrenta a una limitación importante: puede ser difícil de interpretar. Cuando nuestro modelo predice que el precio se mantendrá en la misma zona, no tenemos claridad sobre si los precios subirán o bajarán. Esta compensación se debe a nuestra decisión de categorizar los estados del mercado en cuatro zonas distintas, lo que mejora la precisión pero sacrifica la transparencia en comparación con la previsión directa de los movimientos de precios. Además, este enfoque genera menos señales comerciales porque debemos esperar a que el modelo prediga un cambio de zona antes de tomar medidas.
Conclusión
En conclusión, nuestra estrategia aprovecha el poder del aprendizaje automático, específicamente el análisis discriminante lineal (LDA), integrado con las Bandas de Bollinger para las señales comerciales. Si bien brindamos mayor precisión, nuestro enfoque sacrifica cierta transparencia. En general, los traders pueden obtener mejores resultados pronosticando cambios en los precios que pronosticando rupturas de las Bandas de Bollinger.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/15336
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
Echa un vistazo al nuevo artículo: Reimaginando las Estrategias Clásicas (Parte II): Bollinger Bands Breakouts.
Autor: Gamuchirai Zororo Ndawana
Hola Ndawana
En primer lugar gracias por el artículo y simplificar el mito de la IA :) Estoy tratando de utilizar la señal generada a partir de esto en mi código con algunas modificaciones.
¿Puedes explicarme por qué has utilizado vectores en lugar de simples matrices en tu código?
Hola Ndawana
En primer lugar gracias por el artículo y simplificar el mito de la IA :) Estoy tratando de utilizar la señal generada a partir de esto en mi código con algunas modificaciones.
¿Podrías explicarme las razones por las que has utilizado vectores en lugar de simples matrices en tu código?
Mi preferencia por los vectores viene de las funciones especializadas que están disponibles sólo para vectores, además de esas funciones especiales los vectores también nos permiten realizar cálculos en todos los elementos a la vez. He aquí un ejemplo sencillo.
Así que imagina si en el futuro pensamos en un cálculo que pueda ser útil, será mucho más fácil modificar el código base ya que estamos usando vectores.