English Русский 中文 Deutsch 日本語 Português
preview
Red neuronal en la práctica: La primera neurona

Red neuronal en la práctica: La primera neurona

MetaTrader 5Aprendizaje automático |
380 4
Daniel Jose
Daniel Jose

Introducción

Hola a todos, y bienvenidos a otro artículo sobre redes neuronales. En el artículo anterior Red neuronal en la práctica: Esbozando una neurona, creamos un esquema básico e inicial para la construcción de una simple neurona. Sin embargo, esa simple neurona que construimos no es exactamente lo que necesitamos. Esto se debe a que tiene una pequeña falla, por así decirlo. Pero quiero que tú, mi querido y estimado lector, comprendas el paso a paso de cómo se construye una red neuronal. Esa falla no afecta mucho el funcionamiento de esa sencilla neurona.

Quizás estés pensando: "¿Pero qué falla es esa de la que hablas? No noté nada mal. La neurona funcionó perfectamente en las pruebas que realicé". Bueno, retrocedamos un poco en esta misma serie sobre redes neuronales, para que puedas entender de qué estoy hablando.

En los primeros artículos sobre redes neuronales, mostré cómo podríamos forzar a la máquina a crear una ecuación de línea recta. Inicialmente, la ecuación estaba limitada al origen en los ejes cartesianos, es decir, la línea pasaba obligatoriamente por el punto (0, 0). Esto se debía a que el valor de la constante < b > en la ecuación vista a continuación era cero.

A pesar de que utilizamos el método de mínimos cuadrados para intentar encontrar una ecuación adecuada, de modo que el conjunto de datos o conocimiento previo contenido en la base de datos pudiera ser representado adecuadamente en forma de una ecuación matemática, ese modelado no lograba forzar la búsqueda de una ecuación realmente adecuada. Esto se debe a que, dependiendo de los datos presentes en la base de conocimiento, necesitaríamos que el valor de la constante < b > fuera diferente de cero.

Si estudias con calma esos artículos anteriores, notarás que fue necesario realizar ciertos malabarismos matemáticos para definir el mejor valor posible tanto para la constante < a >, que es el coeficiente angular, como para la constante < b >, que es el punto de intersección. Estas maniobras permitían encontrar la ecuación de línea recta más adecuada, mostrándose dos formas de hacerlo: una mediante cálculos de derivadas y otra mediante cálculos matriciales.

Sin embargo, para lo que necesitamos hacer a partir de este momento, tales cálculos no nos serán útiles, ya que necesitamos modelar otra forma de encontrar las constantes de la ecuación de línea recta. En el artículo anterior, mostré cómo podríamos encontrar la constante que representa el coeficiente angular. Espero que te hayas divertido y hayas experimentado bastante con ese código, porque ahora haremos algo un poco más complicado. Sin embargo, aunque es solo un poco más complicado, de hecho abrirá las puertas a muchas otras cosas. Literalmente, este quizás sea el artículo más interesante que verás en esta serie sobre redes neuronales, ya que después de él, todo será mucho más sencillo y práctico.


Por qué les gusta tanto complicar las cosas

Muy bien, mi querido lector, antes de que veamos la parte del código en sí, me gustaría intentar ayudarte a entender algunas cosas. Normalmente, cuando buscas estudiar sobre redes neuronales, te encontrarás con un montón de términos. Literalmente es una avalancha de conceptos. No sé por qué a las personas que explican sobre redes neuronales les gusta tanto complicar algo que es simple. A mi modo de ver, no hay motivo para ello. Pero no estoy aquí para juzgar ni minimizar. Estoy aquí para explicarte cómo funcionan las cosas detrás de escena.

Para simplificar al máximo, me centraré en algunos términos que suelen aparecer frecuentemente cuando se habla de redes neuronales. Vamos con el primero: Pesos. Este término, pesos, no es más que el coeficiente angular de una ecuación de línea recta. No importa cómo quieran llamarlo, el término peso simplemente se refiere al coeficiente angular. Otro término muy divulgado es: Sesgo. Bueno, este término, que también puedes escuchar como: Bias, no es nada del otro mundo, ni está restringido únicamente a redes neuronales o inteligencia artificial. Nada de eso. Este término no es más que el punto de intersección. Recuerda que estamos lidiando con una recta secante. Por favor, no confundas las cosas.

A partir de estos términos, que aparentemente parecen cosas de otro mundo, surgen un montón de otros conceptos y expresiones. Pero todos ellos, absolutamente todos, no son novedades. De hecho, son solo nombres diferentes para cosas que ya existían. No entraré en el porqué de poner nombres distintos a conceptos conocidos. Sin embargo, si al intentar estudiar redes neuronales o algo relacionado con ellas ves aparecer términos extraños, busca entender cuál es la fórmula matemática detrás de todo eso, y verás que básicamente es mucho más simple de lo que aparenta ser.

Te digo esto porque hay mucha gente que adora complicar las cosas.

Toman algo sencillo y comienzan a inventar una serie de elementos adicionales, simplemente para complicar algo que cualquiera puede entender. Cuando se trata de programación o ciencias exactas, cuanto más simple, mejor. Cuando las cosas empiezan a llenarse de adornos o elementos que desvían nuestra atención, es el momento de detenerse, quitar toda esa parafernalia y maquillaje, para así poder ver la verdadera realidad. Muchos dirán que es complicado, que es necesario ser un experto en el área para entender o implementar una red neuronal, que solo se puede hacer usando tal o cual lenguaje, o con tal o cual recurso. Pero hasta el momento, tú, mi querido y estimado lector, habrás notado que una red neuronal no es complicada. Es simple y no hay razón para el pánico ni la histeria que muchos buscan provocar en las redes sociales.

Muy bien, ahora basta de rodeos y pasemos a un nuevo tema. Veamos cómo podemos añadir el valor de la constante < b >, que es el punto de intersección, o como algunos prefieren llamarlo: Sesgo o Bias. Esto para que podamos forzar la ecuación de la recta a representar de manera más precisa la base de datos que estamos utilizando para entrenar nuestra neurona.


El nacimiento de la primera neurona

Para que nuestra primera neurona cobre vida, y una vez que lo haga, ya no necesitaremos modificarla, como verás más adelante. Primero debemos entender lo que tenemos entre manos. Nuestra neurona actual se comporta como la animación de abajo.



Esta es la misma animación vista en el artículo "Red neuronal en la práctica: Línea Secante". Es decir, acabamos de dar el primer paso para construir una neurona que pueda realizar algo que antes hacíamos manualmente con las teclas de dirección. Sin embargo, habrás notado que esto no es suficiente. De hecho, necesitamos incluir la constante de intersección para que la ecuación obtenida sea aún mejor. Quizás estés pensando que hacer esto será algo extremadamente complicado, pero no lo es. De hecho, hacerlo es tan simple que resulta casi aburrido. Observa cómo añadimos la constante de intersección a la neurona. Esto puede apreciarse en el siguiente fragmento de código.

01. //+------------------------------------------------------------------+
02. double Cost(const double w, const double b)
03. {
04.     double err, fx, x;
05. 
06.     err = 0;
07.     for (uint c = 0; c < nTrain; c++)
08.     {
09.         x = Train[c][0];
10.         fx =  a * w + b;
11.         err += MathPow(fx - Train[c][1], 2);
12.     }
13. 
14.     return err / nTrain;
15. }
16. //+------------------------------------------------------------------+

Me estoy asegurando de dividir el código en fragmentos para que tú, mi querido lector, comprendas en detalle lo que se está haciendo. Y luego dime: ¿Es complicado o no? ¿Realmente necesita toda esa complicación que muchos gustan de añadir cuando hablan de redes neuronales?

Presta mucha atención, porque el nivel de complejidad es casi ridículo. (RISAS). En la línea nueve tomamos nuestro valor de entrenamiento y lo colocamos en la variable X. Luego, en la línea diez, hacemos la factorización. Vaya, qué cuenta más complicada. Pero espera un momento. ¿No es esta justamente la ecuación mostrada al principio? La famosa ecuación de la línea recta. ¡Estás de broma! Esto no va a funcionar como una neurona utilizada en programas de inteligencia artificial.

Calma, mi querido lector. Verás que esto sí funcionará, igual que cualquier otro programa de inteligencia artificial o red neuronal. No importa cuán complicado quieran explicarte el tema. Verás que esto es exactamente lo mismo que se implementa en cualquier tipo de red neuronal. Lo que cambia es el próximo paso, que veremos en breve. Pero el cambio no es tan grande como quizás estés imaginando. Tranquilo, lo veremos paso a paso.

Una vez que el cálculo de error, o coste, se haya actualizado, podemos actualizar el fragmento que ajustará estos dos parámetros en la función de coste. Esto se hará de manera gradual para que puedas entender algunos detalles involucrados. Así que, lo primero que haremos es modificar el código original, presentado en el artículo anterior, por el nuevo código que se muestra a continuación.

01. //+------------------------------------------------------------------+
02. void OnStart()
03. {
04.     double weight, ew, eb, e1, bias;
05.     int f = FileOpen("Cost.csv", FILE_COMMON | FILE_WRITE | FILE_CSV);
06. 
07.     Print("The first neuron...");
08.     MathSrand(512);
09.     weight = (double)macroRandom;
10.     bias = (double)macroRandom;
11. 
12.     for(ulong c = 0; (c < ULONG_MAX) && ((e1 = Cost(weight, bias)) > eps); c++)
13.     {
14.         ew = (Cost(weight + eps, bias) - e1) / eps;
15.         eb = (Cost(weight, bias + eps) - e1) / eps;
16.         weight -= (ew * eps);
17.         bias -= (eb * eps);
18.         if (f != INVALID_HANDLE)
19.             FileWriteString(f, StringFormat("%I64u;%f;%f;%f;%f;%f\n", c, weight, ew, bias, eb, e1));
20.     }
21.     if (f != INVALID_HANDLE)
22.         FileClose(f);
23.     Print("Weight: ", weight, "  Bias: ", bias);
24.     Print("Error Weight: ", ew);
25.     Print("Error Bias: ", eb);
26.     Print("Error: ", e1);
27. }
28. //+------------------------------------------------------------------+

Al ejecutar el script después de estas modificaciones, verás algo parecido a la imagen de abajo.


Ahora observemos únicamente este segundo fragmento de código. En la línea cuatro, añadimos y modificamos algunas variables. Algo bastante simple. Ya en la línea diez, le indicamos a la aplicación que defina un valor aleatorio para el sesgo, o nuestra constante de intersección. Ahora, observa que también necesitaremos pasar este valor a la función Cost. Esto se hace en las líneas 12, 14 y 15. Sin embargo, la parte interesante es que estaremos generando dos tipos de errores acumulados: uno para el valor del peso y otro para el valor del sesgo. Debes entender que, aunque ambos forman parte de la misma ecuación, deberán ajustarse de manera diferente. Por lo tanto, necesitamos saber qué error representa cada uno dentro del sistema general.

Sabiendo esto, en las líneas 16 y 17 podemos ajustar adecuadamente los valores para la siguiente iteración del bucle for. Ahora, de la misma manera que se hizo en el artículo anterior, también lanzamos los valores a un archivo CSV. Esto nos permitirá generar un gráfico para estudiar cómo se están ajustando estos valores.

Bien, en este punto, nuestra primera neurona está completamente construida. Pero hay algunos detalles que podrás entender si observas el código completo de esta neurona. El código completo se muestra justo a continuación.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define macroRandom (rand() / (double)SHORT_MAX)
05. //+------------------------------------------------------------------+
06. double Train[][2] {
07.                     {0, 0},
08.                     {1, 2},
09.                     {2, 4},
10.                     {3, 6},
11.                     {4, 8},
12.                   };
13. //+------------------------------------------------------------------+
14. const uint nTrain = Train.Size() / 2;
15. const double eps = 1e-3;
16. //+------------------------------------------------------------------+
17. double Cost(const double w, const double b)
18. {
19.     double err, fx, a;
20. 
21.     err = 0;
22.     for (uint c = 0; c < nTrain; c++)
23.     {
24.         a = Train[c][0];
25.         fx =  a * w + b;
26.         err += MathPow(fx - Train[c][1], 2);
27.     }
28. 
29.     return err / nTrain;
30. }
31. //+------------------------------------------------------------------+
32. void OnStart()
33. {
34.     double weight, ew, eb, e1, bias;
35.     int f = FileOpen("Cost.csv", FILE_COMMON | FILE_WRITE | FILE_CSV);
36. 
37.     Print("The first neuron...");
38.     MathSrand(512);
39.     weight = (double)macroRandom;
40.     bias = (double)macroRandom;
41. 
42.     for(ulong c = 0; (c < ULONG_MAX) && ((e1 = Cost(weight, bias)) > eps); c++)
43.     {
44.         ew = (Cost(weight + eps, bias) - e1) / eps;
45.         eb = (Cost(weight, bias + eps) - e1) / eps;
46.         weight -= (ew * eps);
47.         bias -= (eb * eps);
48.         if (f != INVALID_HANDLE)
49.             FileWriteString(f, StringFormat("%I64u;%f;%f;%f;%f;%f\n", c, weight, ew, bias, eb, e1));
50.     }
51.     if (f != INVALID_HANDLE)
52.         FileClose(f);
53.     Print("Weight: ", weight, "  Bias: ", bias);
54.     Print("Error Weight: ", ew);
55.     Print("Error Bias: ", eb);
56.     Print("Error: ", e1);
57. }
58. //+------------------------------------------------------------------+

Observa algo interesante tanto en el código como en el resultado visto en la imagen anterior. En la línea seis están los valores utilizados para entrenar la neurona. Claramente, notarás que el valor utilizado en la multiplicación es dos. Sin embargo, la neurona nos indica que el valor es: 1.9804357049081742. Y también podemos notar que el punto de intersección debería ser cero, pero la neurona nos dice que es: 0.054422740828113325. Ok, considerando el hecho de que en la línea 15 aceptamos que el error puede ser de: 0.001, no está tan mal. Esto se debe a que el error final reportado por la neurona fue de: 0.0009994343288155726, es decir, por debajo del margen de error que consideramos aceptable.

Estas diferencias que puedes notar claramente son el índice de probabilidad de que la información sea correcta. Normalmente, esto se representa en términos de porcentaje. Sin embargo, nunca verás un 100%. El número puede ser muy cercano al 100%, pero nunca será exacto debido a esta ligera desviación con respecto al resultado correcto.

A pesar de esto, este índice de probabilidad no es un índice de certeza sobre una información. Aún no estamos trabajando en la generación de este segundo índice. Por ahora, solo estamos entrenando y verificando si la neurona es capaz de establecer una correlación entre los datos de entrenamiento. Pero es posible que ya estés pensando lo siguiente: "Este tema de la neurona es inútil, ya que de la forma en que lo estás creando, no sirve para nada más. Solo sirve para buscar un número que ya sabemos cuál es. Lo que realmente quiero es un sistema que pueda decirme cosas, escribir un texto, o incluso un código. Quién sabe, tal vez hasta un programa que pueda operar en el mercado financiero y darme dinero siempre que lo necesite.

Ciertamente, tienes grandes intereses en mente. Pero si tú, mi querido lector, piensas y estás buscando aprender sobre redes neuronales o inteligencia artificial con la intención de ganar dinero, siento decirte que no lo lograrás. Las únicas personas que realmente van a ganar dinero con esto son aquellas que vendan los sistemas de redes neuronales o inteligencia artificial. Esto ocurre cuando logran convencer a los demás de que la inteligencia artificial o las redes neuronales pueden superar a un buen profesional. Aparte de estas personas, que van a sacar todo el dinero de los demás vendiendo tales productos, nadie más ganará dinero con esto. Si así fuera, ¿por qué escribiría estos artículos explicando cómo funciona? O incluso, ¿por qué algunas personas igualmente conocedoras del funcionamiento de estos mecanismos explicarían cómo trabajan? No tiene sentido. Podrían simplemente guardar silencio, ganar dinero con una red neuronal bien entrenada y listo. Pero no es así como funcionan las cosas en la práctica. Por esta razón, olvídate de la idea de que vas a crear una red neuronal y, sin ningún conocimiento, lograrás ganar dinero solo recogiendo fragmentos de código de aquí y de allá.

Sin embargo, nada te impide, mi estimado lector, crear una pequeña red neuronal cuyo objetivo sea ayudarte en la toma de decisiones, ya sea para comprar, vender o incluso para ayudarte a visualizar ciertos aspectos en el mercado de capitales. ¿Es posible hacerlo? Sí. De hecho, estudiando todo lo que sea necesario, tú, aunque lentamente y con mucho esfuerzo y dedicación, podrás entrenar una red neuronal para ese fin. Pero, como acabo de decir, tendrás que esforzarte para conseguirlo. Sin embargo, es perfectamente posible.

Muy bien, ya tenemos nuestra primera neurona. Pero antes de que te emociones y empieces a pensar en formas de usarla, veamos un poco mejor cómo está esquematizada. Para facilitar la visualización, observa la imagen a continuación.

En esta imagen, podemos ver lo que está implementado en nuestra neurona. Fíjate que tiene una entrada y una salida. Esta única entrada recibe un peso. Tal vez esto no sea tan útil después de todo, ya que ¿cuál es el sentido de tener una entrada y una salida? Está bien, comprendo tu escepticismo ante lo que acabamos de crear. Pero tal vez no sepas, ya que esto depende de cuánto conoces sobre diversos temas, que en la electrónica digital existe un circuito que tiene una entrada y una salida. En realidad, hay dos. Uno es el inversor y el otro es un buffer. Ambos son partes integrales de componentes aún más complejos. Y esta neurona puede aprender cómo funcionan ambos circuitos. Solo necesitas entrenarla para eso. Para entrenarla, lo único que sería necesario es cambiar la matriz de entrenamiento por una de las que se muestran a continuación.

//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define macroRandom (rand() / (double)SHORT_MAX)
//+------------------------------------------------------------------+
double Train[][2] {
                    {0, 1},
                    {1, 0},
                  };
//+------------------------------------------------------------------+
const uint nTrain = Train.Size() / 2;
const double eps = 1e-3;
//+------------------------------------------------------------------+

Al usar este fragmento de código, obtendrás algo parecido a lo que se muestra en la imagen de abajo:


Observa que el valor de weight es negativo. Esto implica que estaremos invirtiendo el valor que entra. Es decir, tenemos un inversor. Usando el fragmento que se ve a continuación, tendríamos otra salida.

//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define macroRandom (rand() / (double)SHORT_MAX)
//+------------------------------------------------------------------+
double Train[][2] {
                    {0, 0},
                    {1, 1},
                  };
//+------------------------------------------------------------------+
const uint nTrain = Train.Size() / 2;
const double eps = 1e-3;
//+------------------------------------------------------------------+

La salida, en este caso, se muestra en la imagen a continuación.


Observa que ahora el valor de weight es positivo. Esto implica que estamos almacenando la entrada directamente en la salida. Es decir, tenemos un buffer. Nota lo divertido que es jugar con esto.

El simple hecho de cambiar la información en la base de conocimiento, o base de datos, que en nuestro caso es un array de dos dimensiones, ya permite que el mismo código cree una ecuación para representar las cosas. Y por esta razón, todos, absolutamente todos los que se interesan por la programación, disfrutan trabajando con redes neuronales. Son muy entretenidas para trabajar.

Sin embargo, esta primera neurona es bastante limitada, no por su diseño, sino porque solo nos permite una entrada. En la práctica, las neuronas de redes neuronales pueden tener tantas entradas como sea necesario. Del mismo modo, también pueden tener tantas salidas como sean necesarias para realizar sus tareas. Estoy presentando las cosas poco a poco para no abrumarte. Quiero que comprendas cómo y por qué este mecanismo funciona. Claro, por mucho que yo, o cualquier otra persona, intentemos, jamás lograremos mostrar todo lo que se puede hacer con este mecanismo. Esto se debe a que las posibilidades dependen únicamente de tu creatividad. Pero en este momento lo haremos un poco más interesante y, al mismo tiempo, más entretenido.


Función sigmoidea

A partir de ahora, cualquier cosa que te muestre será solo la punta del iceberg. No importa lo genial, divertido, complicado o emocionante que pueda parecer programar esto. Todo, absolutamente todo, de aquí en adelante, será solo un pequeño vistazo de lo que podemos hacer. Entonces, mi querido lector, desde este momento debes comenzar a estudiar de manera un poco más independiente. Solo quiero guiarte por un camino que te inspire para nuevos descubrimientos. Siéntete completamente libre de experimentar y divertirte con lo que se te mostrará. Porque, como se mencionó antes, el único factor limitante será tu imaginación.

Para que nuestra única neurona pueda aprender con más entradas disponibles, solo necesitarás entender un pequeño y simple detalle. Esto se puede ver en la imagen a continuación.

O de manera más resumida, la misma ecuación se muestra justo a continuación.

El valor < k > es el número de entradas que nuestra neurona podrá tener. Así que, no importa cuántas entradas sean necesarias, lo único que necesitamos hacer es añadir la cantidad de entradas necesaria para que la neurona aprenda a manejar cada nueva situación. Sin embargo, a partir de la segunda entrada, la función deja de ser una ecuación de línea recta y pasa a ser una ecuación con cualquier forma posible. Esto para que la neurona pueda encontrar la mejor forma de manejar diferentes tipos de entrenamiento.

Ahora la cosa se pone realmente seria, ya que podemos hacer que una única neurona aprenda diversas cosas diferentes. Sin embargo, existe un pequeño problema al dejar de manejar una ecuación de línea recta. Para entender esto, vamos a modificar el programa de forma que quede como se muestra a continuación:

//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define macroRandom (rand() / (double)SHORT_MAX)
//+------------------------------------------------------------------+
double Train[][3] {
                    {0, 0, 0},
                    {0, 1, 1},
                    {1, 0, 1},
                    {1, 1, 1},
                  };
//+------------------------------------------------------------------+
const uint nTrain = Train.Size() / 3;
const double eps = 1e-3;
//+------------------------------------------------------------------+
double Cost(const double w0, const double w1, const double b)
{
    double err;

    err = 0;
    for (uint c = 0; c < nTrain; c++)
        err += MathPow(((Train[c][0] * w0) + (Train[c][1] * w1) + b) - Train[c][2], 2);

    return err / nTrain;
}
//+------------------------------------------------------------------+
void OnStart()
{
    double  w0, w1, err, ew0, ew1, eb, bias;

    Print("The Mini Neuron...");
    MathSrand(512);
    w0 = (double)macroRandom;
    w1 = (double)macroRandom;
    bias = (double)macroRandom;

    for (ulong c = 0; (c < 3000) && ((err = Cost(w0, w1, bias)) > eps); c++)
    {
        ew0 = (Cost(w0 + eps, w1, bias) - err) / eps;
        ew1 = (Cost(w0, w1 + eps, bias) - err) / eps;
        eb  = (Cost(w0, w1, bias + eps) - err) / eps;
        w0 -= (ew0 * eps);
        w1 -= (ew1 * eps);
        bias -= (eb * eps);
        PrintFormat("%I64u > w0: %.4f %.4f || w1: %.4f %.4f || b: %.4f %.4f || %.4f", c, w0, ew0, w1, ew1, bias, eb, err);
    }
    Print("w0 = ", w0, " || w1 = ", w1, " || Bias = ", bias);
    Print("Error Weight 0: ", ew0);
    Print("Error Weight 1: ", ew1);
    Print("Error Bias: ", eb);
    Print("Error: ", err);
}
//+------------------------------------------------------------------+

Cuando ejecutes este código, obtendrás un resultado similar al de la imagen a continuación:


Bueno, ¿qué está mal aquí? Observa que en el código simplemente añadimos la posibilidad de nuevas entradas, lo cual está bien hecho. Pero presta atención a algo: cerca de las diez mil iteraciones, el costo simplemente dejó de disminuir, o si está disminuyendo, lo hace muy lentamente. ¿Por qué sucede esto? El motivo es que falta algo en la neurona. Algo que no era necesario con una única entrada, pero que se vuelve crucial cuando queremos añadir nuevas entradas. Además, es algo utilizado cuando trabajamos con capas de neuronas, lo cual es común en el aprendizaje profundo. Pero eso lo veremos más adelante. Por ahora, concentrémonos en el punto principal. Observa que la neurona está llegando a un punto de estancamiento, donde no puede reducir más el costo. Este problema se resuelve añadiendo una función de activación justo en la salida. La función y cómo suceden las cosas aquí dependen mucho del tipo de tarea que queramos realizar. No existe una única manera de resolver esta parte, ya que se pueden usar diversas funciones de activación. Sin embargo, normalmente se utiliza una sigmoidea, y el motivo es simple. Esta función nos permite llevar valores que van desde más infinito hasta menos infinito a un rango que va de 0 a 1. En algunos casos, la modificamos para que este rango esté entre 1 y -1, pero aquí utilizaremos la versión básica. La función sigmoidea se presenta con la siguiente fórmula:

Bien, pero ¿cómo aplicamos esto en nuestro código? Podría parecer algo muy complicado. En realidad, querido lector, es más sencillo de lo que parece. En el mismo código mostrado anteriormente, será necesario cambiar muy poco, como puedes ver a continuación:

//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define macroRandom (rand() / (double)SHORT_MAX)
#define macroSigmoid(a) (1.0 / (1 + MathExp(-a)))
//+------------------------------------------------------------------+
double Train[][3] {
                    {0, 0, 0},
                    {0, 1, 1},
                    {1, 0, 1},
                    {1, 1, 1},
                  };
//+------------------------------------------------------------------+
const uint nTrain = Train.Size() / 3;
const double eps = 1e-3;
//+------------------------------------------------------------------+
double Cost(const double w0, const double w1, const double b)
{
    double err;

    err = 0;
    for (uint c = 0; c < nTrain; c++)
        err += MathPow((macroSigmoid((Train[c][0] * w0) + (Train[c][1] * w1) + b) - Train[c][2]), 2);

    return err / nTrain;
}
//+------------------------------------------------------------------+
void OnStart()
{
    double  w0, w1, err, ew0, ew1, eb, bias;

    Print("The Mini Neuron...");
    MathSrand(512);
    w0 = (double)macroRandom;
    w1 = (double)macroRandom;
    bias = (double)macroRandom;

    for (ulong c = 0; (c < ULONG_MAX) && ((err = Cost(w0, w1, bias)) > eps); c++)
    {
        ew0 = (Cost(w0 + eps, w1, bias) - err) / eps;
        ew1 = (Cost(w0, w1 + eps, bias) - err) / eps;
        eb  = (Cost(w0, w1, bias + eps) - err) / eps;
        w0 -= (ew0 * eps);
        w1 -= (ew1 * eps);
        bias -= (eb * eps);
        PrintFormat("%I64u > w0: %.4f %.4f || w1: %.4f %.4f || b: %.4f %.4f || %.4f", c, w0, ew0, w1, ew1, bias, eb, err);
    }
    Print("w0 = ", w0, " || w1 = ", w1, " || Bias = ", bias);
    Print("Error Weight 0: ", ew0);
    Print("Error Weight 1: ", ew1);
    Print("Error Bias: ", eb);
    Print("Error: ", err);
}
//+------------------------------------------------------------------+

Y al ejecutar el código mostrado arriba, el resultado será algo similar al que ves en la imagen a continuación.


Nota que fueron necesarias 2.630.936 iteraciones para alcanzar el resultado esperado dentro del margen de error. Lo cual no está nada mal. Puede que tengas la impresión de que el programa empieza a volverse un poco lento, ya que estamos usando solo la CPU. Pero esta impresión se debe precisamente al hecho de que estamos imprimiendo un mensaje en cada iteración del código. Podemos hacer que el código sea un poco más rápido si cambiamos esta forma de mostrar las cosas por un método nuevo. Al mismo tiempo, también añadiremos un pequeño código para probar la capacidad de la neurona. Así que el código final es el que puedes observar justo a continuación.

//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define macroRandom (rand() / (double)SHORT_MAX)
#define macroSigmoid(a) (1.0 / (1 + MathExp(-a)))
//+------------------------------------------------------------------+
double Train[][3] {
                    {0, 0, 0},
                    {0, 1, 1},
                    {1, 0, 1},
                    {1, 1, 1},
                  };
//+------------------------------------------------------------------+
const uint nTrain = Train.Size() / 3;
const double eps = 1e-3;
//+------------------------------------------------------------------+
double Cost(const double w0, const double w1, const double b)
{
    double err;

    err = 0;
    for (uint c = 0; c < nTrain; c++)
        err += MathPow((macroSigmoid((Train[c][0] * w0) + (Train[c][1] * w1) + b) - Train[c][2]), 2);

    return err / nTrain;
}
//+------------------------------------------------------------------+
void OnStart()
{
    double  w0, w1, err, ew0, ew1, eb, bias;
    ulong count;

    Print("The Mini Neuron...");
    MathSrand(512);
    w0 = (double)macroRandom;
    w1 = (double)macroRandom;
    bias = (double)macroRandom;

    for (count = 0; (count < ULONG_MAX) && ((err = Cost(w0, w1, bias)) > eps); count++)
    {
        ew0 = (Cost(w0 + eps, w1, bias) - err) / eps;
        ew1 = (Cost(w0, w1 + eps, bias) - err) / eps;
        eb  = (Cost(w0, w1, bias + eps) - err) / eps;
        w0 -= (ew0 * eps);
        w1 -= (ew1 * eps);
        bias -= (eb * eps);
    }
    PrintFormat("%I64u > w0: %.4f %.4f || w1: %.4f %.4f || b: %.4f %.4f || %.4f", count, w0, ew0, w1, ew1, bias, eb, err);
    Print("w0 = ", w0, " || w1 = ", w1, " || Bias = ", bias);
    Print("Error Weight 0: ", ew0);
    Print("Error Weight 1: ", ew1);
    Print("Error Bias: ", eb);
    Print("Error: ", err);

    Print("Testing the neuron...");
    for (uchar p0 = 0; p0 < 2; p0++)
        for (uchar p1 = 0; p1 < 2; p1++)
            PrintFormat("%d OR %d IS %f", p0, p1, macroSigmoid((p0 * w0) + (p1 * w1) + bias));
}
//+------------------------------------------------------------------+

Al ejecutar este código, podrías ver un mensaje como el que se muestra en la imagen a continuación, apareciendo en el terminal.


Es decir, logramos que nuestra sencilla neurona entendiera cómo una PUERTA OR funciona. Ahora estamos entrando en un camino sin retorno, ya que nuestra única neurona ya comienza a aprender cosas un poco más complejas que simplemente saber si algo está o no relacionado entre sí.


Consideraciones finales

En este artículo, comenzamos a crear algo que a muchos les sorprende ver funcionando. Pues esta simple y modesta neurona, que logramos programar con muy poco código en MQL5, ha demostrado su capacidad. Muchos dicen que se necesitan mil y un recursos para hacer lo que hemos hecho aquí. Pero espero que tú, querido lector, hayas entendido cómo va evolucionando el proceso. En pocos artículos, he resumido un largo tiempo de trabajo realizado por varios investigadores. Aunque sea algo relativamente sencillo, diseñar cómo se deben implementar las cosas llevó su tiempo. Tanto es así que aún hoy en día hay investigaciones que buscan hacer que todos estos cálculos se ejecuten de manera más fluida, o mejor dicho, más rápida. Aquí estamos usando solo una neurona con dos entradas, cinco parámetros y una salida. Y aun así, nota que el sistema toma algo de tiempo para encontrar la ecuación correcta.

Claro, podríamos usar OpenCL para comenzar a acelerar las cosas utilizando la GPU. Pero, en mi opinión, aún es temprano para pensar en esa solución. Podemos avanzar un poco más antes de que sea necesario realmente usar la GPU para los cálculos. Sin embargo, si deseas profundizar en el tema de redes neuronales, te sugiero encarecidamente que pienses en adquirir una GPU, ya que acelerará significativamente ciertos tipos de actividades en la red neuronal.

En el anexo, puedes acceder al último código presentado en este artículo. Úsalo a tu gusto, experimenta y diviértete. Observa cómo esta neurona es capaz de manejar diversas situaciones y trata de encontrar sus limitaciones, porque las tiene. Por esta razón, todavía tendremos que explorar más temas.

Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/13745

Archivos adjuntos |
Anexo.mq5 (2.22 KB)
Multi Dead
Multi Dead | 4 abr 2025 en 17:50

Sí, correcto, acabo de ver un video en YouTube, entrenar a una IA para jugar Flappy Bird y otros juegos con el método N.E.A.T, tengo una idea para entrenar a una IA para el comercio con N.E.A.T, acabo de aprender los fundamentos de la red neuronal, y creó un modelo con la ayuda de chat GPT, porque yo no sé de código. me tomó dos semanas para normalizar los datos y dos días para crear el modelo, pero tomó sólo una hora para entrenar, 1300 generación, 20 genomas por generación, mi portátil de segunda mano de 5 años estaba ardiendo, y cuando conecté el modelo con MT5, el modelo era tan agresivo y era tan preciso en la predicción de las próximas velas, pero no rentable. porque los datos no fueron normalizados correctamente, y todavía no entiendo el código de los modelos. pero fue divertido aprender y ver el modelo de predicción, y es por eso que vine aquí para aprender más acerca de la IA, y este es el código de NEAT AI


import os
import csv
import math
import random
import json
from datetime import datetime

# -----------------------------------------
# UTILS: Activación y pase simple hacia adelante
# -----------------------------------------
def tanh(x):
    return math.tanh(x)

def forward_pass(genome, inputs):
    """
    genome = {
       'hidden_weights': 2D list,
       'hidden_bias': 1D list,
       'output_weights': list,
       'output_bias': float,
       'fitness': float
    }
    inputs: list of 5 floats
    Returns: single float in (-1, 1)
    """
    hidden_activations = []
    for h in range(len(genome['hidden_bias'])):
        z = sum(inputs[i] * genome['hidden_weights'][h][i] for i in range(len(inputs)))
        z += genome['hidden_bias'][h]
        hidden_activations.append(tanh(z))
    
    z_out = sum(hidden_activations[h] * genome['output_weights'][h] for h in range(len(hidden_activations)))
    z_out += genome['output_bias']
    return tanh(z_out)

def interpret_output(output):
    """
    Convert the Tanh output to discrete values:
      if output >= 0.5  =>  1  (Buy)
      if output <= -0.5 => -1  (Sell)
      otherwise         =>  0  (Hold)
    """
    if output >= 0.5:
        return 1
    elif output <= -0.5:
        return -1
    else:
        return 0

# ------------------------------------------------------
# ALGORITMO GENÉTICO: Inicialización, selección, mutación
# ------------------------------------------------------
def create_random_genome(input_size=5, hidden_size=8):
    return {
        'hidden_weights': [[random.uniform(-1, 1) for _ in range(input_size)]
                           for _ in range(hidden_size)],
        'hidden_bias': [random.uniform(-1, 1) for _ in range(hidden_size)],
        'output_weights': [random.uniform(-1, 1) for _ in range(hidden_size)],
        'output_bias': random.uniform(-1, 1),
        'fitness': 0.0
    }

def mutate(genome, mutation_rate=0.1, mutation_strength=0.5):
    for h in range(len(genome['hidden_weights'])):
        for i in range(len(genome['hidden_weights'][h])):
            if random.random() < mutation_rate:
                genome['hidden_weights'][h][i] += random.uniform(-mutation_strength, mutation_strength)
    for h in range(len(genome['hidden_bias'])):
        if random.random() < mutation_rate:
            genome['hidden_bias'][h] += random.uniform(-mutation_strength, mutation_strength)
    for h in range(len(genome['output_weights'])):
        if random.random() < mutation_rate:
            genome['output_weights'][h] += random.uniform(-mutation_strength, mutation_strength)
    if random.random() < mutation_rate:
        genome['output_bias'] += random.uniform(-mutation_strength, mutation_strength)

def crossover(genome1, genome2):
    child = create_random_genome()  # random init, will be overwritten
    for h in range(len(genome1['hidden_weights'])):
        for i in range(len(genome1['hidden_weights'][h])):
            child['hidden_weights'][h][i] = (genome1['hidden_weights'][h][i]
                                             if random.random() < 0.5
                                             else genome2['hidden_weights'][h][i])
    for h in range(len(genome1['hidden_bias'])):
        child['hidden_bias'][h] = (genome1['hidden_bias'][h]
                                   if random.random() < 0.5
                                   else genome2['hidden_bias'][h])
    for h in range(len(genome1['output_weights'])):
        child['output_weights'][h] = (genome1['output_weights'][h]
                                      if random.random() < 0.5
                                      else genome2['output_weights'][h])
    child['output_bias'] = (genome1['output_bias']
                            if random.random() < 0.5
                            else genome2['output_bias'])
    child['fitness'] = 0.0
    return child

def evolve_population(population, keep_best=5):
    population.sort(key=lambda g: g['fitness'], reverse=True)
    new_population = population[:keep_best]  # Conservar los mejores genomas
    while len(new_population) < len(population):
        parent1 = random.choice(new_population)
        parent2 = random.choice(new_population)
        child = crossover(parent1, parent2)
        mutate(child)
        new_population.append(child)
    return new_population

# -------------------------------------------
# SAVE & LOAD best model to avoid re-training
# -------------------------------------------
def save_best_model(genome, filename="best_model.json"):
    data = {
        'hidden_weights': genome['hidden_weights'],
        'hidden_bias': genome['hidden_bias'],
        'output_weights': genome['output_weights'],
        'output_bias': genome['output_bias'],
        'fitness': genome['fitness']
    }
    with open(filename, 'w') as f:
        json.dump(data, f, indent=2)

def load_best_model(filename="best_model.json"):
    if not os.path.exists(filename):
        return None
    with open(filename, 'r') as f:
        data = json.load(f)
    return data

# ----------------
# MAIN TRAINING LOGIC
# ----------------
def train_neural_network_data(folder_path):
    # Hiperparámetros
    POP_SIZE = 20
    KEEP_BEST = 5
    input_size = 5
    hidden_size = 8

    # Crear población inicial o cargar el mejor modelo si se dispone de él.
    population = [create_random_genome(input_size, hidden_size) for _ in range(POP_SIZE)]
    best_saved = load_best_model()
    if best_saved is not None:
        population[0]['hidden_weights'] = best_saved['hidden_weights']
        population[0]['hidden_bias']    = best_saved['hidden_bias']
        population[0]['output_weights'] = best_saved['output_weights']
        population[0]['output_bias']    = best_saved['output_bias']
        population[0]['fitness']        = 0.0

    # Helper to parse ISO8601 date/time strings with timezone info
    def get_date_from_time_str(t_str):
        dt = datetime.strptime(t_str, "%Y-%m-%dT%H:%M:%S%z")
        return dt.date()

    csv_files = sorted([f for f in os.listdir(folder_path) if f.endswith('.csv')])

    # Para cada genoma, almacena la última señal y el precio correspondiente (si lo hay)
    genome_states = [{'last_signal': None, 'last_price': None} for _ in range(POP_SIZE)]
    current_date = None
    generation_count = 0

    # Procesar cada fichero CSV de uno en uno (streaming fila a fila)
    for csv_file in csv_files:
        file_path = os.path.join(folder_path, csv_file)
        with open(file_path, 'r', newline='') as f:
            reader = csv.DictReader(f)
            for row in reader:
                row_time_str = row['Time']
                row_date = get_date_from_time_str(row_time_str)

                # Comprueba si ha empezado un nuevo día (fin de generación)
                if current_date is None:
                    current_date = row_date
                elif row_date != current_date:
                    # Generación finalizada; imprimir estadísticas para monitorización
                    generation_count += 1
                    fitnesses = [g['fitness'] for g in population]
                    avg_fitness = sum(fitnesses) / len(fitnesses)
                    best_fitness = max(fitnesses)
                    print(f"Generation {generation_count} | Date: {current_date} | Avg Fitness: {avg_fitness:.2 f} | Best Fitness: {best_fitness:.2 f}")

                    # Evoluciona la población para la nueva generación
                    population = evolve_population(population, keep_best=KEEP_BEST)

                    # Restablecer los estados del genoma para la nueva generación
                    for state in genome_states:
                        state['last_signal'] = None
                        state['last_price'] = None

                    current_date = row_date

                # Preparar entradas sobre la marcha
                inputs = [
                    float(row['NTP']),
                    float(row['NCP']),
                    float(row['NT']),
                    float(row['NIP']),
                    float(row['N14IP'])
                ]
                bid_price = float(row['BidOpen'])
                ask_price = float(row['AskOpen'])

                # Procesa la decisión de cada genoma para esta fila
                for i, genome in enumerate(population):
                    raw_output = forward_pass(genome, inputs)
                    signal_val = interpret_output(raw_output)  # -1, 0 o 1
                    prev_signal = genome_states[i]['last_signal']
                    prev_price = genome_states[i]['last_price']

                    # Omitir procesamiento si la señal no ha cambiado
                    if signal_val == prev_signal:
                        continue

                    genome_states[i]['last_signal'] = signal_val

                    # Aplicar la lógica de fitness en las transiciones de señal
                    if signal_val == 1:  # Señal de compra
                        genome_states[i]['last_price'] = ask_price
                        if prev_signal == -1 and prev_price is not None:
                            if ask_price < prev_price:
                                genome['fitness'] += 1
                            else:
                                genome['fitness'] -= 1
                    elif signal_val == -1:  # Señal de venta
                        genome_states[i]['last_price'] = bid_price
                        if prev_signal == 1 and prev_price is not None:
                            if bid_price > prev_price:
                                genome['fitness'] += 1
                            else:
                                genome['fitness'] -= 1
                    else:  # Señal de retención
                        genome_states[i]['last_price'] = None

    # Después de procesar todos los archivos CSV, finalizar la formación
    population.sort(key=lambda g: g['fitness'], reverse=True)
    best_genome = population[0]
    print("Final Best genome fitness after all files:", best_genome['fitness'])
    save_best_model(best_genome, filename="best_model.json")

if __name__ == "__main__":
    folder_path = r""
    train_neural_network_data(folder_path)
y para MT5
import MetaTrader5 as mt5
import pandas as pd
import numpy as np
import json
import time

def tournament(values):
    """Tournament method to find maximum and minimum in a list."""
    max_val = values[0]
    min_val = values[0]
    for v in values[1:]:
        if v > max_val:
            max_val = v
        if v < min_val:
            min_val = v
    return max_val, min_val

# -------------------------------
# Cargar el mejor modelo desde JSON
with open("best_model.json", "r") as f:
    best_model = json.load(f)

def forward_pass(inputs, model):
    """
    Perform a forward pass through a feedforward network with one hidden layer.
    Uses tanh for both hidden and output layers.
    The output is discretized:
       >0.5 --> 1, < -0.5 --> -1, else 0.
    """
    x = np.array(inputs)  # forma (5,)
    hidden_weights = np.array(model["hidden_weights"])  # forma (8, 5)
    hidden_bias = np.array(model["hidden_bias"])          # forma (8,)
    output_weights = np.array(model["output_weights"])    # forma (8,)
    output_bias = model["output_bias"]                    # escalar

    hidden_input = np.dot(hidden_weights, x) + hidden_bias
    hidden_output = np.tanh(hidden_input)
    output_val = np.dot(output_weights, hidden_output) + output_bias
    output_activation = np.tanh(output_val)
    
    if output_activation > 0.5:
        return 1
    elif output_activation < -0.5:
        return -1
    else:
        return 0

# -------------------------------
# Funciones de gestión de pedidos mediante posiciones
def get_open_position(symbol):
    """
    Returns information about an open position for the symbol as a dictionary:
    {"ticket": ticket, "type": "buy"/"sell", "volume": volume}.
    If no position is open, returns None.
    """
    positions = mt5.positions_get(symbol=symbol)
    if positions is None or len(positions) == 0:
        return None
    pos = positions[0]  # Asumir sólo una posición abierta por símbolo
    if pos.type == mt5.POSITION_TYPE_BUY:
        return {"ticket": pos.ticket, "type": "buy", "volume": pos.volume, "symbol": pos.symbol}
    elif pos.type == mt5.POSITION_TYPE_SELL:
        return {"ticket": pos.ticket, "type": "sell", "volume": pos.volume, "symbol": pos.symbol}
    return None

def close_position(position_info):
    """
    Closes the given position using its ticket.
    """
    symbol = position_info["symbol"]
    tick = mt5.symbol_info_tick(symbol)
    # Para el cierre: si BUY está abierto, vendemos a bid; si SELL está abierto, compramos a ask.
    if position_info["type"] == "buy":
        price = tick.bid
        order_type = mt5.ORDER_TYPE_SELL
    else:
        price = tick.ask
        order_type = mt5.ORDER_TYPE_BUY

    request = {
        "action": mt5.TRADE_ACTION_DEAL,
        "symbol": symbol,
        "volume": position_info["volume"],
        "type": order_type,
        "position": position_info["ticket"],
        "price": price,
        "deviation": 10,
        "magic": 123456,
        "comment": "Close position",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_IOC,
    }
    result = mt5.order_send(request)
    print("Close position result:", result)

def place_order(symbol, order_type, volume=0.01):
    """
    Place an order for the symbol.
    - order_type "buy": place BUY order.
    - order_type "sell": place SELL order.
    Before placing, if an open position exists with the opposite signal,
    it is closed using its order ticket.
    """
    # Comprueba la posición abierta actual:
    current_position = get_open_position(symbol)
    if current_position:
        # Si la posición existente es opuesta a la nueva señal, ciérrela.
        if (order_type == "buy" and current_position["type"] == "sell") or \
           (order_type == "sell" and current_position["type"] == "buy"):
            print(f"Opposite position ({current_position['type']}) detected. Closing it first.")
            close_position(current_position)
        # Si es del mismo tipo, no hacer nada.
        elif (order_type == current_position["type"]):
            print(f"{order_type.upper()} order already open. No new order will be placed.")
            return

    tick = mt5.symbol_info_tick(symbol)
    if order_type == "buy":
        price = tick.ask
        order_type_mt5 = mt5.ORDER_TYPE_BUY
    elif order_type == "sell":
        price = tick.bid
        order_type_mt5 = mt5.ORDER_TYPE_SELL
    else:
        print("Invalid order type:", order_type)
        return

    request = {
        "action": mt5.TRADE_ACTION_DEAL,
        "symbol": symbol,
        "volume": volume,
        "type": order_type_mt5,
        "price": price,
        "deviation": 10,
        "magic": 123456,
        "comment": "NEAT AI trade",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_IOC,
    }
    result = mt5.order_send(request)
    print(f"Placed {order_type.upper()} order result:", result)

# -------------------------------
# Inicializar la conexión con MetaTrader 5
if not mt5.initialize():
    print("initialize() failed, error code =", mt5.last_error())
    quit()

symbol = "XAUUSDm"
timeframe = mt5.TIMEFRAME_M1  # 1 minuto

if not mt5.symbol_select(symbol, True):
    print("Failed to select symbol:", symbol)
    mt5.shutdown()
    quit()

# Bucle de supervisión continua
try:
    while True:
        print("\n" + "="*80)
        print("Retrieving candle data...")
        num_candles = 15
        rates = mt5.copy_rates_from_pos(symbol, timeframe, 1, num_candles)
        if rates is None:
            print("Failed to get rates for", symbol)
        else:
            df = pd.DataFrame(rates)
            df['tick_count'] = df['tick_volume']
            df['9ma'] = df['close'].rolling(window=9).mean()
            df['14ma'] = df['close'].rolling(window=14).mean()
            df['TP'] = (df['high'] + df['low'] + df['close']) / 3
            df['NTP']   = (df['TP']    - df['low']) / (df['high'] - df['low'])
            df['NIP9']  = (df['9ma']   - df['low']) / (df['high'] - df['low'])
            df['NIP14'] = (df['14ma']  - df['low']) / (df['high'] - df['low'])
            close_prices = list(df['close'].tail(14))
            HC, LC = tournament(close_prices)
            tick_counts = list(df['tick_count'].tail(14))
            HT, LT = tournament(tick_counts)
            df['NCP'] = (df['close'] - LC) / (HC - LC)
            df['NT']  = (df['tick_count'] - LT) / (HT - LT)
            df = df.round(3)
            
            # Imprime los datos normalizados de la última vela
            display_cols = ['high', 'low', 'close', 'tick_count', '9ma', '14ma', 'TP', 'NTP', 'NCP', 'NT', 'NIP9', 'NIP14']
            print("\nNormalized Candle Data (Last Candle):")
            print(df[display_cols].tail(1).to_string(index=False))
            
            # Extraer entradas de la última vela
            last_row = df.iloc[-1]
            input_vector = [
                last_row['NTP'],
                last_row['NCP'],
                last_row['NT'],
                last_row['NIP9'],
                last_row['NIP14']
            ]
            
            print("\nExtracted Inputs:")
            print(f"NTP = {last_row['NTP']}, NCP = {last_row['NCP']}, NT = {last_row['NT']}, NIP9 = {last_row['NIP9']}, NIP14 = {last_row['NIP14']}")
            print("Price Data - High:", last_row['high'], "Low:", last_row['low'], "Close:", last_row['close'])
            
            # Compute network signal
            signal = forward_pass(input_vector, best_model)
            print("\nNetwork Signal (discretized):", signal)
            
            # Ejecutar la operación basada en la señal utilizando nuestra lógica de gestión de órdenes.
            if signal == 1:
                print("Received BUY signal.")
                place_order(symbol, "buy", volume=0.01)
            elif signal == -1:
                print("Received SELL signal.")
                place_order(symbol, "sell", volume=0.01)
            else:
                print("Signal is neutral. No action taken.")
        
        # Wait until next minute boundary
        tick = mt5.symbol_info_tick(symbol)
        if tick is None:
            print("Failed to get tick info for", symbol)
            break
        server_time = tick.time
        next_minute = ((server_time // 60) + 1) * 60
        sleep_seconds = next_minute - server_time
        print(f"Sleeping for {sleep_seconds} seconds until next candle...")
        time.sleep(sleep_seconds)
except KeyboardInterrupt:
    print("Stopping continuous monitoring.")
finally:
    mt5.shutdown()
Irán Triay Hernández
Irán Triay Hernández | 6 abr 2025 en 02:10
Muy bien.
CODE X
CODE X | 6 abr 2025 en 13:57
Multi Dead redes neuronales y creé un modelo con la ayuda del chat GPT, porque no sé programar. Tardé dos semanas en normalizar los datos y dos días en crear el modelo, pero sólo tardé una hora en entrenarlo, 1.300 generaciones, 20 genomas por generación, mi portátil de segunda mano de 5 años estaba ardiendo y, cuando conecté el modelo a MT5, el modelo era muy agresivo y muy preciso en la predicción de las próximas velas, pero no era rentable. Pero fue divertido aprender y ver la predicción del modelo, por eso vine aquí a aprender más sobre IA, y este es el código de NEAT AI


y para MT5

Las redes neuronales son un tema realmente interesante y divertido. Sin embargo, me he tomado un descanso de explicarlo, ya que he decidido finalizar primero la repetición / simulador. Sin embargo, en cuanto termine de publicar los artículos sobre el simulador, volveremos con nuevos artículos sobre redes neuronales. El objetivo es siempre mostrar cómo funcionan. La mayoría de la gente piensa que son códigos mágicos, lo cual no es cierto. Pero siguen siendo un tema interesante y entretenido. Incluso estoy pensando en modelar algo para que todo el mundo pueda ver cómo una red neuronal aprende sin supervisión y sin datos previos. Lo cual es muy interesante, y puede ayudar a entender ciertas cosas. Detalle: Todo mi código estará escrito en MQL5. Y ya que has dicho que no eres programador. ¿Qué tal si aprendes MQL5 y empiezas a implementar tus propias soluciones? Estoy escribiendo una serie de artículos dirigidos a gente como tú. El último lo puedes ver aquí: https: //www.mql5.com/pt/articles/15833. En esta serie explico las cosas desde lo más básico. Así que si no sabes absolutamente nada de programación, vuelve al primer artículo de la serie. Los enlaces a los artículos anteriores estarán siempre al principio del artículo.

Andreas Alois Aigner
Andreas Alois Aigner | 9 abr 2025 en 09:16
Muchas gracias por esta introducción.
Características del Wizard MQL5 que debe conocer (Parte 19): Inferencia bayesiana Características del Wizard MQL5 que debe conocer (Parte 19): Inferencia bayesiana
La inferencia bayesiana es la adopción del teorema de Bayes para actualizar la hipótesis de probabilidad a medida que se dispone de nueva información. Esto intuitivamente se inclina hacia la adaptación en el análisis de series de tiempo, por lo que observamos cómo podríamos usarlo para crear clases personalizadas no solo para la señal sino también para la gestión de dinero y los trailing stops.
Red neuronal en la práctica: Esbozando una neurona Red neuronal en la práctica: Esbozando una neurona
En este artículo, vamos construir una neurona básica. Aunque parezca algo simple, y muchos piensen que el código es totalmente trivial y sin propósito, quiero que tú, querido lector y entusiasta del tema de redes neuronales, te diviertas explorando este sencillo esbozo de una neurona. No tengas miedo de modificar el código para entenderlo mejor.
Arbitraje triangular con predicciones Arbitraje triangular con predicciones
Este artículo simplifica el arbitraje triangular y le muestra cómo utilizar predicciones y software especializado para operar con divisas de forma más inteligente, incluso si es nuevo en el mercado. ¿Listo para operar con experiencia?
Desarrollamos un asesor experto multidivisa (Parte 7): Selección de grupos considerando el periodo forward Desarrollamos un asesor experto multidivisa (Parte 7): Selección de grupos considerando el periodo forward
Anteriormente hemos evaluado la selección de un grupo de instancias de estrategias comerciales para mejorar el rendimiento cuando trabajan juntas solo durante el mismo periodo de tiempo en el que se han optimizado las instancias individuales. Veamos qué ocurre en el periodo forward.