English Русский 中文 Deutsch 日本語 Português
preview
Reimaginando las estrategias clásicas (Parte III): Predicción de máximos crecientes y mínimos decrecientes

Reimaginando las estrategias clásicas (Parte III): Predicción de máximos crecientes y mínimos decrecientes

MetaTrader 5Ejemplos |
553 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Introducción

La Inteligencia Artificial (IA) ofrece oportunidades posiblemente infinitas para mejorar nuestras capacidades comerciales. Sin embargo, es poco probable que todas las estrategias sean efectivas. Nuestro objetivo es ayudar a los comerciantes a identificar las estrategias que mejor se adaptan a sus necesidades proporcionándoles la información necesaria para una toma de decisiones informada.

Dada la gran cantidad de posibles combinaciones de estrategias, ningún operador puede evaluarlas todas exhaustivamente antes de tomar decisiones importantes. Este problema lo comparten todos en nuestra comunidad. Por lo tanto, a lo largo de esta serie de artículos, exploraremos la búsqueda de estrategias comerciales comparando la precisión de las estrategias populares con los modelos más simples.

En este artículo, analizamos en profundidad la clásica estrategia de trading de acción del precio, que se basa en “máximos más altos” o “mínimos más bajos”. Entrenamos varios modelos para predecir dos objetivos distintos. El primer objetivo es predecir la evolución de los precios, el objetivo más sencillo posible. El segundo objetivo es determinar si el precio de cierre futuro es superior al máximo actual, inferior al mínimo actual o si se situaría entre ambos.

Para comparar modelos de distinta complejidad, empleamos la validación cruzada de series temporales sin barajado aleatorio. Analizamos los cambios en las puntuaciones de precisión en ambos objetivos. Nuestros resultados sugieren que los modelos más sencillos, que prevén cambios en los niveles de precios, pueden ser más eficaces.


Visión general de la estrategia de negociación

Normalmente, cuando los operadores que siguen estrategias de acción del precio analizan un valor, buscan señales de tendencias fuertes que se estén formando o que estén desapareciendo. Una señal bien conocida de que se está formando una tendencia fuerte es cuando los niveles de precios se cierran por encima de los valores extremos anteriores y continúan alejándose en pasos progresivamente mayores. Esto se conoce coloquialmente como "máximos más altos" o "mínimos más bajos", dependiendo de la dirección en la que se mueva el precio.

Durante incontables generaciones, los operadores han utilizado esta sencilla estrategia para identificar los puntos de entrada y salida. Por lo general, los puntos de salida se determinan cuando el precio no logra superar sus valores extremos, lo que indica que la tendencia está perdiendo fuerza y podría invertirse. A lo largo de los años, se han añadido varias extensiones menores a esta estrategia, pero su plantilla fundamental sigue siendo la misma.

Uno de los mayores inconvenientes de esta estrategia es cuando el precio retrocede inesperadamente por debajo de su extremo. Estos cambios adversos en los precios se conocen como "retrocesos" y son difíciles de predecir. Como resultado, la mayoría de los operadores no entran inmediatamente en una posición cuando el precio alcanza un nuevo extremo. En lugar de ello, esperan a ver cuánto tiempo puede mantener el precio esos niveles antes de comprometerse, permitiendo esencialmente que la tendencia se demuestre a sí misma. Sin embargo, este enfoque plantea varios interrogantes: ¿Cuánto tiempo hay que esperar antes de concluir que la tendencia se ha demostrado? A la inversa, ¿cuánto tiempo es demasiado antes de que se invierta la tendencia? Este es el dilema al que se enfrentan los analistas de la acción del precio.


Resumen de la metodología

Ahora que conocemos los puntos débiles de la estrategia de negociación, comprendemos la motivación de emplear la IA para superar estas limitaciones del pasado. Como ya se ha mencionado, entrenamos a un grupo diverso de clasificadores para predecir si el precio se cerraría más allá de sus valores extremos actuales o se mantendría dentro del rango. Para esta tarea seleccionamos varios clasificadores, como AdaBoost, árboles de decisión y redes neuronales. No se realizó ningún ajuste de hiperparámetros en ninguno de los modelos antes de la comparación.

Asignamos los tres resultados potenciales a tres niveles categóricos: 1, 2 y 3, respectivamente:

  • 1 representa que el precio de cierre en el futuro será mayor que el precio máximo actual.
  • 2 representa que el precio de cierre en el futuro será menor que el precio bajo actual.
  • 3 representa que 1 y 2 eran falsos, el precio futuro estará entre el máximo y el mínimo actuales.

Vale la pena señalar que el nuevo objetivo que hemos creado es más difícil de interpretar. Si nuestro modelo pronostica una transición al estado 3, puede seguir siendo incierto si los niveles de precios se apreciarán o depreciarán, dependiendo del precio en ese momento. Esto hace que nuestro modelo no sólo sea menos transparente sino también aparentemente menos preciso que el modelo más simple.

Normalmente otorgamos mérito a cualquier estrategia que pueda superar al modelo más simple. Una estrategia compleja que no logre esto quizá no justifique el compromiso adicional de recursos limitados, como el tiempo.


Análisis exploratorio de datos en Python

Para empezar, primero tenemos que exportar datos del mercado desde nuestro terminal MetaTrader 5. Comenzamos abriendo el terminal y seleccionando el icono Símbolo. Luego seleccione barras en el menú contextual, busque el símbolo que le interese y presione exportar.

Exportando datos

Figura 1: Preparación para exportar datos de mercado.

Ahora que nuestros datos están listos, comencemos a visualizar si puede haber alguna relación entre las variables en cuestión.

Comenzamos importando las bibliotecas que necesitamos.

#import libraries
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

Luego leemos los datos que preparamos anteriormente y observamos que la terminal MetaTrader 5 nos entrega archivos CSV separados por pestañas. Por lo tanto, pasamos el parámetro “\t” al leer el archivo.

gbpusd = pd.read_csv("/home/volatily/market_data/GBPUSD_Daily_20160103_20240131.csv",sep="\t")

Cambiemos el nombre de las columnas en nuestro marco de datos.

#Rename the columns
gbpusd.rename(columns={"<DATE>":"Date","<OPEN>":"Open","<HIGH>":"High","<LOW>":"Low","<CLOSE>":"Close","<TICKVOL>":"TickVol","<VOL>":"Vol","<SPREAD>":"Spread"},inplace=True)

Definamos hasta qué punto en el futuro queremos pronosticar.

#Define how far into the future we want to forecast
look_ahead = 20

Ahora definiremos nuestras etiquetas de la misma manera que lo discutimos.

#This column will help us with our plots
gbpusd["Future Close"] = gbpusd["Close"].shift(-look_ahead)
#Let's mark the normal target
#If price rises, our target will be 1
#If price falls, our target will be 0
gbpusd["Price Target"] = 0
#Let's mark the new target
#If price makes a higher high, we will label 1
#If price makes a lower low, we will label 2
#If price fails to make either, we will label 3
gbpusd["New Target"] = 0

Etiquetado de los datos.

#Labeling the data
#If the future close was less than the current close, price depreciated, label 0
gbpusd.loc[gbpusd["Close"] > gbpusd["Close"].shift(-look_ahead),"Price Target"] = 0
#If the future close was greater than the current close, price depreciated, label 1
gbpusd.loc[gbpusd["Close"] < gbpusd["Close"].shift(-look_ahead),"Price Target"] = 1
#If price makes a higher high our label will be 1
gbpusd.loc[gbpusd["Close"].shift(-look_ahead) > gbpusd["High"],"New Target"] = 1
#If price makes a lower low our label will be 2
gbpusd.loc[gbpusd["Close"].shift(-look_ahead) < gbpusd["Low"],"New Target"] = 2
#Otherwise our label will be 3 
gbpusd.loc[gbpusd["Close"].shift(-look_ahead) < gbpusd["Low"],"New Target"] = 3

Podemos eliminar todas las filas con valores faltantes.

#Drop the last look ahead rows
gbpusd = gbpusd[:-look_ahead]

La estrategia comercial implica que existe una relación entre el cierre y el máximo, visualicemos si existe alguna relación entre el cierre y el máximo.

#Plot a scattor plot so we can see if there may be any relationship between the close and the high
sns.scatterplot(data=gbpusd,x="Close",y="High",hue="Price Target")

Cierre y máximo
Figura 2: Visualización del cierre graficado contra el máximo.

Los diagramas de dispersión son útiles porque nos permiten visualizar las relaciones entre cualquier par de variables de estado en el sistema que estamos modelando.

Al observar los datos, podemos ver inmediatamente lo que parece ser una relación fuerte, casi lineal, entre el precio de cierre y el precio máximo. Agregamos color al gráfico para distinguir los casos en que el precio se apreció de aquellos en que se depreció. Como se observa, no existe una separación clara entre ambas instancias. Los únicos puntos de separación notables aparecen en valores extremos; por ejemplo, cuando el precio cierra por debajo del nivel 1.1, siempre parece rebotar.

Podemos realizar el mismo diagrama de dispersión, sin embargo esta vez colocaremos el precio bajo en el eje Y.

#Plot a scattor plot so we can see if there may be any relationship between the close and the low
sns.scatterplot(data=gbpusd,x="Close",y="Low",hue="Price Target")

Cierre y mínimo

Figura 3: Visualización de la relación entre el cierre y el mínimo.

Como era de esperar, no conseguimos mucha separación natural. Esta separación natural es deseada porque ayuda a nuestros modelos a aprender los límites de decisión más rápidamente. Veamos si nuestro nuevo objetivo nos ayuda a separar mejor nuestro conjunto de datos.

#Plot a scattor plot so we can see if there may be any relationship between the close and the low
sns.scatterplot(data=gbpusd,x="Close",y="Low",hue="New Target")

Nuevo objetivo

Figura 4: Nuestro nuevo objetivo no proporciona más separación.

Como podemos observar la separación sigue siendo pobre. Los puntos más oscuros, que representan el estado 3, recorren casi toda la longitud de nuestro gráfico. Esto es problemático porque indica visualmente que hay instancias en nuestros datos donde la misma entrada resultó en diferentes salidas.

Para demostrar cómo se ve una buena separación, visualicemos el precio actual en el eje X y el precio futuro en el eje Y. Colorearemos los puntos de la trama en naranja para los aumentos de precios y en azul para las disminuciones de precios.

#Plot a scattor plot so we can see if there may be any relationship between the close and the low
sns.scatterplot(data=gbpusd,x="Close",y="Future Close",hue="Price Target")

Buena separación

Figura 5: Un ejemplo de cómo se ve una buena separación.

Como se puede ver, hay una clara separación entre las dos clases en este gráfico. Esto es de esperarse porque estamos usando el objetivo en sí mismo en el gráfico; nuestro objetivo como profesionales del aprendizaje automático es descubrir una característica o un objetivo que nos brinde un nivel de separación cercano a lo que vemos en la Figura 4.

Si realizamos la misma trama utilizando nuestro nuevo objetivo, observamos algo peculiar.

#Plot a scattor plot so we can see if there may be any relationship between the close and the low
sns.scatterplot(data=gbpusd,x="Close",y="Future Close",hue="New Target")

Visualizando la nueva separación de objetivos
Figura 6: Visualización de la separación provocada por el nuevo objetivo.

Observe que los puntos oscuros y claros están bien separados entre las mitades superior e inferior del gráfico, lo que representa instancias en las que el precio subió y bajó, respectivamente. Entre estas dos clases, tenemos puntos clasificados como estado 3, indicados por los puntos oscuros a lo largo del centro, que muestran instancias en las que el precio estaba oscilando.


Entrenando a los modelos

Ahora importaremos los modelos que necesitamos y otras herramientas de preprocesamiento.

#Let's get a group of different models
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.neural_network import MLPClassifier
from sklearn.svm import LinearSVC
#Import cross validation libraries
from sklearn.model_selection import TimeSeriesSplit
#Import accuracy metrics
from sklearn.metrics import accuracy_score
#Import preprocessors
from sklearn.preprocessing import RobustScaler

Definiremos los parámetros para nuestra validación cruzada de series de tiempo. Recuerde que la brecha debe ser al menos igual a nuestro horizonte de pronóstico.

#Splits
splits = 10
gap = look_ahead

Almacenaremos cada uno de los modelos que necesitamos en una lista para que podamos ajustar programáticamente todos los modelos que tenemos.

#Store each of the models we need
cols = ["AdaBoostClassifier","Linear DiscriminantAnalysis","Bagging Classifier","Random Forest Classifier","KNeighborsClassifier","Neural Network Small","Neural Network Large"]
models = [AdaBoostClassifier(),LinearDiscriminantAnalysis(),BaggingClassifier(n_jobs=-1),RandomForestClassifier(n_jobs=-1),KNeighborsClassifier(n_jobs=-1),MLPClassifier(hidden_layer_sizes=(5,2),early_stopping=True,max_iter=1000),MLPClassifier(hidden_layer_sizes=(20,10),early_stopping=True,max_iter=1000)]
#Create data frames to store our accuracy with different models on different targets
index = np.arange(0,splits)
price_target = pd.DataFrame(columns=cols,index=index)
new_target = pd.DataFrame(columns=cols,index=index)

Crearemos nuestro objeto de división de series de tiempo para nuestra prueba de validación cruzada.

#Create the tscv splits
tscv = TimeSeriesSplit(n_splits=splits,gap=gap)

Definamos los predictores y objetivos de nuestros modelos.

#Define the predictors and target
predictors = ["Open","High","Low","Close"]
target = "New Target"

Realizar validación cruzada.

#Now we perform cross validation
for j in (np.arange(len(models))):
    #We need to train each model
    model = models[j]
    for i,(train,test) in enumerate(tscv.split(gbpusd)):
        #Scale the data
        scaler = RobustScaler()
        X_train_scaled = scaler.fit_transform(gbpusd.loc[train[0]:train[-1],predictors])
        scaler = RobustScaler()
        X_test_scaled = scaler.fit_transform(gbpusd.loc[test[0]:test[-1],predictors])
        #Train the model
        model.fit(X_train_scaled,gbpusd.loc[train[0]:train[-1],target])
        #Measure the accuracy
        new_target.iloc[i,j] = accuracy_score(gbpusd.loc[test[0]:test[-1],target],model.predict(X_test_scaled))

Observemos el rendimiento de cada uno de nuestros modelos en el objetivo más simple posible.

#Calculate the mean for each column when predicting price 
for i in np.arange(0,len(models)):
    print(f"{cols[i]} achieved accuracy: {price_target.iloc[:,i].mean()}")
AdaBoostClassifier alcanzó una precisión de: 0,5190265486725664
El análisis discriminante lineal logró una precisión de: 0,5579646017699115
El clasificador de bolsas alcanzó una precisión de: 0,5075221238938052
El clasificador de bosque aleatorio alcanzó una precisión de: 0,5349557522123894
KNeighborsClassifier alcanzó una precisión de: 0,536283185840708
Red neuronal pequeña con precisión alcanzada: 0,45309734513274336
Red neuronal grande con precisión alcanzada: 0,5446902654867257

El análisis discriminante lineal funcionó mejor en este conjunto de datos en particular, alcanzando casi el 56% de precisión. Pero veamos ahora nuestro desempeño en el nuevo objetivo.

#Calculate the mean for each column when predicting price 
for i in np.arange(0,len(models)):
    print(f"{cols[i]} achieved accuracy: {new_target.iloc[:,i].mean()}")
AdaBoostClassifier alcanzó una precisión de: 0,45929203539823016
El análisis discriminante lineal logró una precisión de: 0,4668141592920355
El clasificador de bolsas alcanzó una precisión de: 0,4393805309734514
El clasificador de bosque aleatorio alcanzó una precisión de: 0,45929203539823016
KNeighborsClassifier logró una precisión de: 0,465929203539823
Red neuronal pequeña con precisión alcanzada: 0,3920353982300885
Red neuronal grande con precisión alcanzada: 0,4606194690265487

El análisis discriminante lineal siguió estando en lo más alto de nuestra lista en ambos casos. Todos los modelos demostraron una habilidad menor en el nuevo objetivo, pero la red neuronal pequeña sufrió la mayor caída en el rendimiento.

Modelo
Cambio en el rendimiento
AdaBoostClassifier
 -14.32748538011695%
Análisis discriminante lineal
-19.526066350710863%
Clasificador de bolsas
-22.09660842754366%
Clasificador de bosque aleatorio
-16.730769230769248%
KNeighborsClassifier
-15.099715099715114%
Red neuronal pequeña
-41.04193138500632%
Red neuronal grande
-21.1502782931354%

Analicemos la matriz de confusión de nuestro modelo con mejor rendimiento.

#Let's continue analysing the performance of our best model Linear Discriminant Analysis
from mlxtend.evaluate import confusion_matrix
from mlxtend.plotting import plot_confusion_matrix
model = LinearDiscriminantAnalysis()
model.fit(gbpusd.loc[0:1000,predictors],gbpusd.loc[0:1000,"New Target"])
cm = confusion_matrix(y_target=gbpusd.loc[1000:,"New Target"],y_predicted=model.predict(gbpusd.loc[1000:,predictors]),binary=True)
fig , ax = plot_confusion_matrix(cm)

Matriz de confusión

Figura 7: La matriz de confusión para nuestro modelo de análisis discriminante lineal.

La matriz de confusión nos ayuda a identificar qué clases son desafiantes para nuestro modelo. Como se muestra en el gráfico anterior, nuestro modelo tuvo el peor desempeño al pronosticar la clase 3. Sin embargo, esta clase tiene un conjunto pequeño de observaciones. Para abordar esto, tal vez necesitemos utilizar datos que representen mejor a toda la población. Podemos lograr esto obteniendo más datos históricos o analizando períodos de tiempo más cortos.


Selección de funciones

A veces, podemos mejorar el rendimiento eliminando características innecesarias de nuestros modelos. Centrémonos en nuestro modelo de mejor rendimiento e identifiquemos sus características más importantes para ver si podemos mejorar aún más el rendimiento.
#Now let us perform feature selection
from mlxtend.feature_selection import SequentialFeatureSelector

Hay muchos algoritmos de selección de características disponibles y en este artículo utilizamos el algoritmo de selección de características directa. Si bien existen varias versiones de este algoritmo, el proceso general comienza con un modelo nulo que sirve como referencia. Luego, el algoritmo evalúa cada una de las p características disponibles una por una, seleccionando la que produce la mayor mejora en el rendimiento como primera característica. Este proceso se repite para los predictores p-1 restantes. Gracias a los recientes avances en computación paralela, estos algoritmos se han vuelto más factibles.

Si reducimos nuestros predictores de "p" a "k", donde "k<p", y elegimos bien las "k" características, podremos superar nuestro modelo original o conseguir un modelo igual de fiable pero más rápido de entrenar. Además, reducir el número de predictores utilizados en el modelo puede reducir la varianza de los coeficientes de nuestro modelo.

Sin embargo, este algoritmo tiene dos limitaciones importantes que vale la pena discutir. En primer lugar, el nuevo modelo puede estar ligeramente más sesgado debido a la información limitada que utilizamos para entrenarlo. Además, la elección de la primera característica influye en las selecciones posteriores. Si la característica inicial elegida tiene poca relación con el objetivo, las características posteriores pueden parecer poco informativas debido a esta mala elección inicial.

En nuestro análisis, permitimos que el selector de características eligiera tantas variables como considerara importantes, pero seleccionó solo una: el precio de apertura.

#Forward feature selection 
forward_feature_selection = SequentialFeatureSelector(LinearDiscriminantAnalysis(),
                                                     k_features =(1,4),
                                                     forward=True,
                                                     verbose=2,
                                                     scoring="accuracy",
                                                     cv=5,
                                                     n_jobs=-1).fit(gbpusd.loc[:,predictors],gbpusd.loc[:,"New Target"])

Ahora queremos ver la mejor característica.

#Best feature
forward_feature_selection.k_feature_names_
(Open)

Observemos nuestros nuevos niveles de precisión.

#Update the predictors and target
predictors = ["Open"]
target = "New Target"
best_features_for_new_target = pd.DataFrame(columns=["Linear Discriminant Analysis"],index=index)

Realice una validación cruzada utilizando la mejor característica que identificamos.

#Now we perform cross validation
for i,(train,test) in enumerate(tscv.split(gbpusd)):
    #First initialize the model
    model = LogisticRegression()
    #Train the model
    model.fit(gbpusd.loc[train[0]:train[-1],predictors],gbpusd.loc[train[0]:train[-1],target])
    #Measure the accuracy
    best_features_for_new_target.iloc[i,0] = accuracy_score(gbpusd.loc[test[0]:test[-1],target],model.predict(gbpusd.loc[test[0]:test[-1],predictors]))

Observemos nuestros nuevos niveles de precisión.

#New accuracy only using the open price
best_features_for_new_target.iloc[:,0].mean()
0.46548672566371685

Y finalmente, observemos el cambio en el rendimiento entre el modelo que utilizó todos los predictores y el modelo que solo utilizó uno.

-0.0013274336283186638

Como podemos ver, el cambio en el rendimiento es de aproximadamente -0.2%. Lo que significa que perdimos muy poca información al dejar de lado los otros tres predictores.


Implementando la estrategia en MQL5

Comenzamos importando las bibliotecas que necesitamos.

//+------------------------------------------------------------------+
//|                                   Forecasting Highs And Lows.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                                                 |
//+------------------------------------------------------------------+
#include <Trade/Trade.mqh> //Trade class
CTrade Trade;             //Initialize the class

Luego definimos variables de entrada, para que nuestro usuario final pueda personalizar su experiencia.

//+------------------------------------------------------------------+
//| Input variables                                                  |
//+------------------------------------------------------------------+
input int fetch = 10; //How much data should we fetch?
input int look_ahead = 2; //Forecst horizon.
input int rsi_period = 20; //Forecst horizon.
int input  lot_multiple = 1; //How many times bigger than minimum lot?
input double stop_loss_values = 1; //How large should our stop loss be?

Necesitamos algunas variables globales que se utilizarán en toda nuestra aplicación.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
vector state = vector::Zeros(3);//This vector will store the state of the system using binary mapping
double minimum_volume;//The smallest contract size allowed
vector input_data;//Input data
vector output_data;//Output data
vector rsi_data;//RSI output data
double variance;//This is the variance of our input data
int classes = 3;//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
int rsi_handler;//This will store our RSI handler
int forecast = 0;//Our model's forecast
double discriminant_values[3];//The discriminant function

A continuación definiremos el procedimiento de inicialización para nuestro Asesor Experto. Nuestro procedimiento primero garantiza que el usuario haya ingresado entradas válidas y luego procede a configurar nuestro indicador técnico e inicializa el estado de nuestro sistema comercial.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Validate inputs
   if(!valid_inputs())
     {
      //User passed invalid inputs
      Print("Invalid inputs were received!");
      return(INIT_FAILED);
     }
//--- Load input data
   rsi_handler = iRSI(_Symbol,PERIOD_CURRENT,rsi_period,PRICE_CLOSE);
//--- Market data
   minimum_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
//--- Update system state
   update_system_state(0);
//--- End of initialization
   return(INIT_SUCCEEDED);
  }

También necesitamos definir un procedimiento de desinicialización para nuestra aplicación.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Remove indicators
   IndicatorRelease(rsi_handler);
//--- Detach the Expert Advisor
   ExpertRemove();
//--- End of deinitialization
  }

Desarrollaremos una función que nos ayude a analizar si tenemos señales de entrada; nuestras señales de entrada se consideran válidas si el pronóstico del modelo se alinea con la tendencia en períodos de tiempo más altos, como el semanal. Si ambos se alinean, cronometraremos nuestras entradas utilizando el indicador RSI.

//+------------------------------------------------------------------+
//| This function will analyse our entry signals                     |
//+------------------------------------------------------------------+
void analyse_entry(void)
  {
   Print("Higher Time Frame Trend");
   Print(iClose(_Symbol,PERIOD_W1,12) - iClose(_Symbol,PERIOD_CURRENT,0));
   if(iClose(_Symbol,PERIOD_W1,12) < iClose(_Symbol,PERIOD_CURRENT,0))
     {

      if(forecast == 1)
        {
         bullish_sentiment();
        }
     }
   if(iClose(_Symbol,PERIOD_W1,12) > iClose(_Symbol,PERIOD_CURRENT,0))
     {

      if(forecast == 2)
        {
         bearish_sentiment();
        }
     }
  }

Necesitamos dos funciones dedicadas para interpretar nuestro indicador RSI, una función analizará el indicador en busca de oportunidades de venta y la otra en busca de oportunidades de compra.

//+------------------------------------------------------------------+
//| This function will analyze our RSI for sell signals              |
//+------------------------------------------------------------------+
void bearish_sentiment(void)
  {
   rsi_data.CopyIndicatorBuffer(rsi_handler,0,0,1);
   if(rsi_data[0] < 50)
     {
      Trade.Sell(minimum_volume * lot_multiple,_Symbol,SymbolInfoDouble(_Symbol,SYMBOL_BID),(SymbolInfoDouble(_Symbol,SYMBOL_BID) + stop_loss_values),(SymbolInfoDouble(_Symbol,SYMBOL_BID) - stop_loss_values));
      update_system_state(2);
     }
  }
//+------------------------------------------------------------------+
//| This function will analyze our RSI for buy signals               |
//+------------------------------------------------------------------+
void bullish_sentiment(void)
  {
   rsi_data.CopyIndicatorBuffer(rsi_handler,0,0,1);
   if(rsi_data[0] > 50)
     {
      Trade.Buy(minimum_volume * lot_multiple,_Symbol,SymbolInfoDouble(_Symbol,SYMBOL_ASK),(SymbolInfoDouble(_Symbol,SYMBOL_ASK) - stop_loss_values),(SymbolInfoDouble(_Symbol,SYMBOL_ASK) +stop_loss_values));
      update_system_state(2);
     }
  }

Ahora definiremos una función que validará las entradas que nuestro usuario pasó durante la inicialización.

//+------------------------------------------------------------------+
//|This function will check the inputs the user passed               |
//+------------------------------------------------------------------+
bool valid_inputs(void)
  {
//--- For the inputs to be valid:
//--- The forecast horizon must be less than the data fetched
   return((fetch > look_ahead));
  }

Diseñemos ahora una función que inicializará nuestro modelo LDA (Linear Discriminant Analysis).

//+------------------------------------------------------------------+
//| This function will initialize our model                          |
//+------------------------------------------------------------------+
void initialize_model(void)
  {
//--- First fetch the input data
   fetch_input_data(look_ahead,fetch);
   fetch_output_data(0,fetch);
//--- Update the system state
   update_system_state(1);
//--- Fit the model
   fit_model();
  }

Para inicializar el modelo, primero debemos obtener los datos de entrada para el modelo, esa es responsabilidad de esta función. Tenga en cuenta que la función simplemente obtiene el precio de apertura porque nuestro análisis sugirió que era la característica más importante que teníamos.

//+------------------------------------------------------------------+
//| This function will fetch our input data                          |
//+------------------------------------------------------------------+
void fetch_input_data(int start,int size)
  {
//--- Fetching input data
   Print("Fetching input data");
   input_data = vector::Zeros(fetch);
   input_data.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_OPEN,start,size);
   input_data.Resize(size);
   Print("Input data fetched");
  }

Luego debemos obtener nuestros datos de salida y etiquetarlos; también tenga en cuenta que mantenemos un registro de cuántas veces apareció cada clase como objetivo; esta información se utilizará más adelante cuando ajustemos el modelo LDA.

//+------------------------------------------------------------------+
//| This function will fetch our output data                         |
//+------------------------------------------------------------------+
void fetch_output_data(int start,int size)
  {
//--- Fetching output data
   vector historic_high = vector::Zeros(size);
   vector historic_low = vector::Zeros(size);
   vector historic_close = vector::Zeros(size);
   historic_close.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,start,size+look_ahead);
   historic_low.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_HIGH,start,size+look_ahead);
   historic_high.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_LOW,start,size+look_ahead);
   output_data = vector::Zeros(size);
   output_data.Resize(size);
//--- Reset class counts
   total_class_count[0] = 0;
   total_class_count[1] = 0;
   total_class_count[2] = 0;
//--- Label the data
   for(int i = 0; i < size; i++)
     {
      //--- Price broke into a higher high
      if(historic_close[i + look_ahead] > historic_high[i])
        {
         output_data[i] = 1;
         total_class_count[0] += 1;
        }
      //--- Price broke into a lower low
      else
         if(historic_close[i + look_ahead] < historic_low[i])
           {
            output_data[i] = 2;
            total_class_count[1] += 1;
           }
         //--- Price was stuck in a range
         else
            if((historic_close[i + look_ahead] > historic_low[i]) && (historic_close[i + look_ahead] < historic_high[i]))
              {
               output_data[i] = 3;
               total_class_count[2] += 1;
              }
     }
//--- We fetched output data succesfully
   Print("Output data fetched");
   Print("Total class counts");
   Print(total_class_count);
  }

Ahora definimos el procedimiento para ajustar el modelo LDA. El primer paso implica calcular el valor promedio del precio de apertura en cada una de las 3 clases. Los segundos pasos requieren que calculemos la distribución de probabilidad previa de cada clase como clase objetivo; podemos simplemente estimar este valor utilizando los recuentos de clases que realizamos en el paso anterior. Por último, necesitamos calcular la varianza del precio de apertura para cada una de las 3 clases.

//+------------------------------------------------------------------+
//| This function will fit the LDA algorithm                         |
//+------------------------------------------------------------------+
//--- Fit the model
void fit_model(void)
  {
//--- To fit the LDA model, we first need to know the mean value of X for each of our 3 classes
   double sum_class_one = 0;
   double sum_class_two = 0;
   double sum_class_three = 0;

//--- In this case we only have 1 input
   for(int i = 0; i < fetch;i++)
     {
      //--- Class 1
      if(output_data[i] == 1)
        {
         sum_class_one += input_data[i];
        }
      //--- Class 2
      else
         if(output_data[i] == 2)
           {
            sum_class_two += input_data[i];
           }
         //--- Class 3
         else
            if(output_data[i] == 3)
              {
               sum_class_three += input_data[i];
              }
     }
//--- Calculate the mean value for each class
   mean_values[0] = sum_class_one / total_class_count[0];
   mean_values[1] = sum_class_two / total_class_count[1];
   mean_values[2] = sum_class_three / total_class_count[2];
   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[3];
   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(output_data[i] == 1)
        {
         squared_difference[0] = MathPow((input_data[i]-mean_values[0]),2);
        }

      else
         if(output_data[i] == 2)
           {
            squared_difference[1] = MathPow((input_data[i]-mean_values[1]),2);
           }

         else
            if(output_data[i] == 3)
              {
               squared_difference[2] = MathPow((input_data[i]-mean_values[2]),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 - 3.0)) * (squared_difference[0] + squared_difference[1] + squared_difference[2]);
   Print("Variance: ",variance);
  }

Ahora necesitamos una función para obtener pronósticos de nuestro modelo, nuestro modelo pronosticará un valor discriminante para cada una de las 3 clases posibles. La clase con el mayor valor discriminante es la clase predicha.

//+-------------------------------------------------------------------+
//| This model will fetch our model's prediction                      |
//+-------------------------------------------------------------------+
void model_forecast(void)
  {
//--- Obtain a forecast from our model
//--- First we need to fetch the most recent input data
   fetch_input_data(0,1);
//--- 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])));
     }
//--- Show the LDA prediction
   forecast = (ArrayMaximum(discriminant_values) +1);
   Print("LDA Forecast: ",forecast);
   ArrayPrint(discriminant_values);
  }

Necesitamos una función para actualizar el estado de nuestro sistema para que nuestra función OnTick siempre sepa qué hacer a continuación.

//+-------------------------------------------------------------------+
//| This function will be used to update the state of the system      |
//+-------------------------------------------------------------------+
void update_system_state(int index)
  {
//--- Each column vector is set to 0 except column 0, the first column.
//--- If the first column is set to 1, then our model has not been trained
//--- If the second column is set to 1, then our model has been trained but we have no positions
//--- If the third column is set to 1, then we have a position we need to manage
//--- Update the system state
   state = vector::Zeros(3);
   state[index] = 1;
   Print("Updating system state");
   Print(state);
  }

Definamos ahora la función OnTick, que garantizará que todas nuestras funciones se llamen en el momento apropiado.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- The model has not been trained
   if(state.ArgMax() == 0)
     {
      Print("Training the model.");
      initialize_model();
     }
//--- The model has been trained, but we have no positions
   else
      if(state.ArgMax() == 1)
        {
         Print("Finding An Entry.");
         model_forecast();
         analyse_entry();
        }
  }
//+------------------------------------------------------------------+

Nuestro Asesor Experto

Figura 8: Nuestro Asesor Experto.

Nuestro Asesor Experto II

Figura 9: Nuestro Asesor Experto de LDA.

 

Figura 10: Datos históricos de operaciones de nuestro Asesor Experto.


Conclusión

En este artículo, hemos demostrado por qué parece que los traders pueden obtener mejores resultados pronosticando cambios en los precios que pronosticando máximos más altos y mínimos más bajos. Esperamos que después de leer este artículo tenga más confianza para decidir si esta estrategia comercial es adecuada para usted, dados sus niveles personales de tolerancia al riesgo y sus objetivos financieros.

El algoritmo de análisis discriminante lineal (LDA) modela la distribución de las variables de entrada dentro de cada clase, utilizando el teorema de Bayes para estimar probabilidades y asumiendo una distribución normal con medias específicas de cada clase y varianza común. Esto ayuda a LDA a distinguir eficazmente las características de clase calculando valores discriminantes que maximizan la separación de clases y minimizan la varianza dentro de la clase. Sin embargo, los supuestos de LDA pueden limitar la transparencia y la interpretabilidad, y su rendimiento puede ser inferior al de modelos más simples sin un ajuste exhaustivo de los parámetros. Nuestras pruebas utilizando configuraciones predeterminadas en datos diarios revelaron posibles problemas de rendimiento, lo que sugiere que se podrían lograr mejores resultados con más datos y recursos computacionales.


Repetir el análisis con conjuntos de datos más grandes podría proporcionar más información, aunque este enfoque solo es factible con suficiente potencia computacional. Utilizamos una validación cruzada de series de tiempo de 10 veces, lo que significa que cada modelo se entrenó 10 veces. A medida que aumenta el tamaño del conjunto de datos, se podría esperar que los tiempos de entrenamiento del modelo crezcan exponencialmente.

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

Aprendizaje automático y Data Science (Parte 28): Predicción de múltiples futuros para el EURUSD mediante IA Aprendizaje automático y Data Science (Parte 28): Predicción de múltiples futuros para el EURUSD mediante IA
Es una práctica común que muchos modelos de Inteligencia Artificial predigan un único valor futuro. Sin embargo, en este artículo profundizaremos en la poderosa técnica de utilizar modelos de aprendizaje automático para predecir múltiples valores futuros. Este enfoque, conocido como pronóstico de múltiples pasos, nos permite predecir no sólo el precio de cierre de mañana, sino también el de pasado mañana y más allá. Al dominar la previsión en varios pasos, los operadores y los científicos de datos pueden obtener conocimientos más profundos y tomar decisiones más informadas, mejorando significativamente sus capacidades de predicción y planificación estratégica.
MetaTrader 5 en macOS MetaTrader 5 en macOS
Hemos preparado un instalador especial de la plataforma comercial MetaTrader 5 para macOS: se trata de un asistente completo que permite instalar la aplicación como nativa, y que realiza todas las acciones necesarias: detecta su sistema, descarga e instala la última versión de Wine para él, lo configura, y luego instala MetaTrader dentro del mismo. Todo sucede en modo automático, solo hay que esperar a que se complete la instalación, después de lo cual se podrá empezar a trabajar inmediatamente con la plataforma.
Añadimos un LLM personalizado a un robot comercial (Parte 5): Desarrollar y probar la estrategia de negociación con LLMs (I) Ajuste fino Añadimos un LLM personalizado a un robot comercial (Parte 5): Desarrollar y probar la estrategia de negociación con LLMs (I) Ajuste fino
Con el rápido desarrollo de la inteligencia artificial en la actualidad, los modelos lingüísticos (LLM) son una parte importante de la inteligencia artificial, por lo que deberíamos pensar en cómo integrar potentes LLM en nuestras operaciones algorítmicas. Para la mayoría de la gente, es difícil ajustar estos potentes modelos a sus necesidades, desplegarlos localmente y luego aplicarlos a la negociación algorítmica. Esta serie de artículos abordará paso a paso la consecución de este objetivo.
Implementación de una estrategia de trading con Bandas de Bollinger en MQL5: Guía paso a paso Implementación de una estrategia de trading con Bandas de Bollinger en MQL5: Guía paso a paso
Una guía paso a paso para implementar un algoritmo de trading automatizado en MQL5 basado en la estrategia de trading de las Bandas de Bollinger. Un tutorial detallado basado en la creación de un Asesor Experto que puede ser útil para los traders.