English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Algoritmos de optimización de la población

Algoritmos de optimización de la población

MetaTrader 5Ejemplos | 21 noviembre 2022, 13:28
381 0
Andrey Dik
Andrey Dik

“En todo lo que sucede en el universo, se manifiesta la acción 
de alguna regla de máximo o mínimo"
Leonard Euler, siglo XVIII

Contenido:

  1. Perspectiva histórica
  2. Clasificación de AO
  3. Convergencia y tasa de convergencia. Estabilidad de la convergencia. Escalabilidad del algoritmo de optimización
  4. Funciones de prueba, construcción de un criterio de evaluación complejo de AO
  5. Banco de pruebas
  6. Un ejemplo sencillo de AO usando RNG
  7. Tabla de resultados

 

1. Perspectiva histórica

Los algoritmos de optimización son algoritmos que permiten encontrar dentro del dominio de una función los puntos extremos en los que alcanza su valor mínimo o máximo.

Ya en la antigua Grecia, los expertos sabían que:

- De todas las figuras con un perímetro dado, el círculo tiene el área más grande.

- De todos los polígonos con un número dado de lados y un perímetro dado, un polígono regular tiene el área más grande.

- De todas las figuras tridimensionales con un área dada, la esfera tiene el mayor volumen.

Al mismo tiempo, se formuló un problema que obtuvo las primeras soluciones variacionales. Según la leyenda, fue alrededor del año 825 a.c. Dido, la hermana del rey de la ciudad fenicia de Tiro, tras mudarse a la costa sur del mar Mediterráneo, pidió a la tribu local un pedazo de tierra que pudiera cubrirse con la piel de un toro. Los residentes locales le proporcionaron una piel, que la ingeniosa niña cortó en cintas estrechas, que luego ató entre sí. Con la cuerda resultante, cubrió el territorio frente a la costa, sobre el cual fundó la ciudad de Cartago.

El problema es cómo, entre las curvas planas cerradas de una longitud dada, encontrar la curva más eficiente que cubra el área de superficie máxima. El área máxima en este problema estará representada por el área descrita por el semicírculo.

  didona1

 

 

Hasta el nuevo amanecer del pensamiento, desde la antigua cultura del Mediterráneo, evitando la opresión de la Inquisición con hogueras y charlatanerías de la Edad Media, somos transportados a la era del Renacimiento, cuando se crearon y plasmaron ideas nuevas, plasmadas por el poder del vuelo libre del pensamiento hacia nuevas teorías. Así, Johann Bernoulli publica en junio de 1696 ante los lectores del Acta Eruditorum el siguiente texto: "Yo, Johann Bernoulli, hablo con los matemáticos más brillantes del mundo. No existe nada más atractivo para las personas inteligentes que un problema honesto y difícil cuya posible solución le traerá fama y quedará para siempre como un monumento. Siguiendo el ejemplo de Pascal, Fermat, etc., espero ganarme la gratitud de toda la comunidad científica presentándoles a los mejores matemáticos de nuestro tiempo un desafío que pondrá a prueba sus métodos y la fuerza de su intelecto. Si alguien me comunica una solución al problema propuesto, lo declararé públicamente digno de elogio".

Formulación del problema de I. Bernoulli sobre la braquistócrona:

"Dados dos puntos A y B en un plano vertical, ¿cuál es la curva trazada por un punto sobre el que solo actúa la gravedad, que parte de A y llega a B en el menor tiempo?". Para ofrecer un poco más de contexto, allá por 1638, mucho antes de la publicación de Bernoulli, el propio Galileo trató de resolver un problema similar para el camino del descenso más rápido. La respuesta a este problema es: el camino más rápido de todos, de un punto a otro, no es el camino más corto, como parece a primera vista; no es una línea recta, sino una curva, una cicloide que determina la curvatura de la curva en cada punto.

Braquistócrona9k

 Curva braquistócrona

Todas las demás pruebas, incluidas las de Newton (que no se revelaron en ese momento), se basan en encontrar el gradiente en cada punto. También resulta importante señalar que el método de solución obtenido por Isaac Newton formó la base del cálculo de variaciones. Los métodos del cálculo de variaciones se suelen usar para resolver problemas en los que los criterios de optimalidad se presentan en forma de funcionales y cuyas soluciones suponen funciones desconocidas. Estos problemas suelen surgir en la optimización estática de procesos con parámetros distribuidos o en problemas de optimización dinámica.

Las condiciones extremas de primer orden en el cálculo de variaciones fueron obtenidas por Leonard Euler y Joseph Lagrange (las ecuaciones de Euler-Lagrange); estas ecuaciones son muy utilizadas en problemas de optimización y, junto con el principio de mínima acción, se usan para calcular trayectorias en mecánica. Pronto, sin embargo, quedó claro que las soluciones de estas ecuaciones no ofrecen en todos los casos un extremo real, y surgió el problema de encontrar condiciones suficientes que garantizaran su hallazgo. El trabajo sobre la cuestión siguió adelante, y los matemáticos Legendre y Jacobi, y luego su alumno Hesse, derivaron las condiciones extremas de segundo orden. La cuestión sobre la existencia de una solución en el cálculo de variaciones fue planteada por primera vez por Weierstrass más tarde, en la segunda mitad del siglo XIX.

En la segunda mitad del siglo XVIII, los intentos de encontrar soluciones óptimas a los problemas conformaron los fundamentos matemáticos y los principios de la optimización. Sin embargo, en la práctica, hasta la segunda mitad del siglo XX, los métodos de optimización en muchas áreas de la ciencia y la tecnología en realidad tenían poca aplicación, ya que el uso práctico de los métodos matemáticos requería enormes recursos informáticos y solo con el desarrollo de la tecnología en el mundo moderno la situación cambió sustancialmente, pues al fin surgieron oportunidades para implementar métodos de optimización complejos, creándose así una gran variedad de algoritmos.

En la década de 1980, se inició el desarrollo intensivo de una clase de algoritmos de optimización estocástica, que fueron el resultado de modelos tomados de la propia naturaleza.

 

2. Clasificación de AO

Class

 Classification AO

A la hora de optimizar sistemas comerciales, los más interesantes son los algoritmos de optimización metaheurística, pues no requieren conocimiento previo de la fórmula de la función optimizada. Asimismo, no se ha probado su convergencia al óptimo global, pero se ha establecido experimentalmente que en la mayoría de los casos ofrecen una solución bastante buena, y esto resulta suficiente para resolver una serie de problemas.

Muchos AO surgieron como modelos tomados de la naturaleza, y también se les llama de comportamiento, de enjambre, o de población. Algoritmos así suponen, por ejemplo, el comportamiento de los pájaros en una bandada (algoritmo del enjambre de partículas) o los principios del comportamiento de una colonia de hormigas (algoritmo de "colonia de hormigas" o "algoritmo de hormigas").

Los algoritmos de población involucran el procesamiento simultáneo de varias opciones para resolver el problema de optimización y representan una alternativa a los algoritmos clásicos de búsqueda a lo largo de trayectorias de movimiento en cuyo área de búsqueda solo evoluciona un candidato al resolver el problema.

 

3. Convergencia y tasa de convergencia. Estabilidad de la convergencia. Escalabilidad del algoritmo de optimización

Para cada implementación algorítmica y para cada clase de problema de optimización, será necesario realizar un cuidadoso análisis de la eficiencia, la velocidad y la convergencia, así como de la influencia de las condiciones del problema y los parámetros del algoritmo.

3.1) Convergencia y tasa de convergencia.

 


La propiedad de un algoritmo iterativo para alcanzar el óptimo de la función objetivo o acercarse lo suficiente a él en un número finito de pasos. Así, en las capturas de pantalla anteriores, podemos ver en el lado derecho un gráfico construido iterativamente sobre los resultados de la función de prueba calculada. Basándonos en las dos imágenes presentadas, podemos concluir que la convergencia se ve influida por la complejidad de la superficie de la función analizada: cuanto más compleja sea, más difícil resultará encontrar el extremo global.

La tasa de convergencia de los algoritmos supone uno de los indicadores más importantes de la calidad de un algoritmo de optimización, y es una de las principales características de los métodos de optimización. Con frecuencia, al decir que un algoritmo es más rápido que otro, nos referimos precisamente a la tasa de convergencia. Cuanto más cerca se encuentre el resultado del extremo global y más rápido se obtenga (nos referimos a las iteraciones anteriores del algoritmo), mayor será este indicador. Tenga en cuenta que la tasa de convergencia de los métodos por lo general no supera la cuadrática. En casos raros, el método puede tener una tasa cúbica de convergencia.

3.2) Estabilidad de la convergencia.

Debemos señalar que el número de iteraciones necesarias para lograr el resultado depende no solo de la capacidad de búsqueda del propio algoritmo, sino también de la función estudiada. Si la función se caracteriza por una alta complejidad de la superficie (presencia de torceduras, discreción, discontinuidades), el algoritmo podrá volverse inestable y no logrará ofrecer una precisión aceptable en absoluto. Además, la estabilidad de la convergencia puede entenderse como la repetibilidad de los resultados de optimización al realizarse varias pruebas consecutivas. Si los resultados muestras grandes discrepancias en los valores, la estabilidad del algoritmo será baja.

3.3) Escalabilidad del algoritmo de optimización.


convergence7 convergence6

La escalabilidad del algoritmo de optimización es la capacidad de mantener la convergencia al darse un aumento en la dimensión del problema: en otras palabras, si sucede un aumento en el número de variables de la función optimizada, la convergencia deberá permanecer en un nivel aceptable a efectos prácticos. Los algoritmos de optimización de búsqueda de población poseen ventajas innegables en comparación con los algoritmos clásicos, especialmente al intentar resolver problemas de gran dimensionalidad y problemas poco formalizados. Bajo estas condiciones, los algoritmos de población pueden ofrecer una alta probabilidad de hallar el extremo global de la función optimizada.

En el caso de una función optimizada suave y unimodal, los algoritmos de población resultan generalmente menos eficientes que cualquier método de gradiente clásico. Además, las desventajas de los algoritmos de población incluyen una fuerte dependencia de su eficiencia respecto a los grados de libertad (número de parámetros de ajuste), que son bastante numerosos en la mayoría de los algoritmos.

 

4. Funciones de prueba, construcción de un criterio de evaluación complejo de AO

No existe una metodología generalmente aceptada para probar algoritmos de optimización y compararlos entre sí, pero sí que hay muchas funciones de prueba que han sido inventadas en diferentes años por investigadores. No obstante, podemos usar las funciones que creé antes de publicar el primer artículo. Encontrará estas funciones en las carpetas del terminal \MQL5\Experts\Examples\Math 3D\Functions.mqh y \MQL5\Experts\Examples\Math 3D Morpher\Functions.mqh. Estas funciones cumplen con todos los criterios de complejidad para las pruebas de AO. Además, las funciones Forest y Megacity han sido desarrolladas para ofrecer una exploración más completa de las capacidades de búsqueda de AO.

Función de prueba de Skin:


La función es fluida en toda su definición y posee muchos valores max/min locales que difieren ligeramente en cuanto a su valor (trampas de convergencia), lo cual hace que los algoritmos que no llegan al extremo global se atasquen.

Skin

Función de prueba Skin.

Función de prueba Forest:


La función representa varios máximos que no tienen un diferencial en sus puntos, por lo que resulta difícil para los algoritmos de optimización cuya robustez depende críticamente de la suavidad de la función estudiada.

Forest

Función de prueba Forest.

Función de prueba Megacity:

Una función discreta forma "áreas" (en esos lugares, el cambio de las variables no provoca un cambio significativo en el valor de la función), por lo tanto, resulta difícil para los algoritmos que requieren un gradiente.

Chinatown

Función de prueba Megacity.



5. Banco de pruebas

Para realizar una comparación exhaustiva de los algoritmos de optimización, se intentó crear un criterio de evaluación general. La complejidad de esta idea radica en el hecho de que no está claro cómo comparar los algoritmos, porque cada uno de ellos es bueno a su manera para la clase de problemas correspondiente. Unos, por ejemplo, convergen rápidamente pero no se escalan bien, mientras que otros se escalan bien pero resultan inestables. 

  •   Convergencia: Para estudiar la convergencia, utilizaremos las tres funciones presentadas anteriormente, cuyo máximo y mínimo se convertirán en un rango de 0,0 (peor resultado) a 1,0 (mejor resultado), lo cual nos permitirá evaluar la capacidad de los algoritmos para garantizar la convergencia en diferentes tipos de problemas.
  •   Tasa de convergencia: Los mejores resultados del algoritmo se miden en las ejecuciones 1 000 y 10 000 de la función probada. Por lo tanto, podremos ver cómo de rápido converge el AO. Cuanto más rápida resulte la convergencia, más curvo será el gráfico de convergencia hacia el máximo.
  •   Estabilidad: Vamos a realizar cinco ejecuciones de optimización para cada una de las funciones y a calcular el valor promedio en el rango de 0.0 a 1.0. Esto es necesario porque los resultados de algunos algoritmos pueden variar mucho de una ejecución a otra. Cuanto mayor resulte la convergencia en cada una de las cinco pruebas, mayor será la puntuación de la estabilidad.
  •   Escalabilidad: Algunos AO solo pueden mostrar resultados prácticos en funciones con un número pequeño de variables, por ejemplo, no más de dos, y algunos ni siquiera pueden trabajar con más de 1 variable. Además, existen algoritmos capaces de trabajar con funciones con mil variables, dichos algoritmos de optimización pueden usarse como AO para redes neuronales.  

Para que las funciones de prueba sean más cómodas de usar, escribiremos una clase principal y un enumerador que nos permitirá seleccionar un objeto de la clase secundaria de la función de prueba correspondiente en el futuro:

//——————————————————————————————————————————————————————————————————————————————
class C_Function
{
  public: //====================================================================
  double CalcFunc (double &args [], //function arguments
                   int     amount)  //amount of runs functions
  {
    double x, y;
    double sum = 0.0;
    for (int i = 0; i < amount; i++)
    {
      x = args [i * 2];
      y = args [i * 2 + 1];

      sum += Core (x, y);
    }

    sum /= amount;

    return sum;
  }
  double GetMinArg () { return minArg;}
  double GetMaxArg () { return maxArg;}
  double GetMinFun () { return minFun;}
  double GetMaxFun () { return maxFun;}
  string GetNamFun () { return fuName;}

  protected: //==================================================================
  void SetMinArg (double min) { minArg = min;}
  void SetMaxArg (double max) { maxArg = max;}
  void SetMinFun (double min) { minFun = min;}
  void SetMaxFun (double max) { maxFun = max;}
  void SetNamFun (string nam) { fuName = nam;}

  private: //====================================================================
  virtual double Core (double x, double y) { return 0.0;}
  
  double minArg;
  double maxArg;
  double minFun;
  double maxFun;
  string fuName;
};
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
enum EFunc
{
  Skin,
  Forest,
  Megacity 
};

C_Function *SelectFunction (EFunc f)
{
  C_Function *func;
  switch (f)
  {
    case  Skin:
      func = new C_Skin (); return (GetPointer (func));
    case  Forest:
      func = new C_Forest (); return (GetPointer (func));
    case  Megasity:
      func = new C_Megacity(); return (GetPointer (func));
    
    default:
      func = new C_Skin (); return (GetPointer (func));
  }
}
//——————————————————————————————————————————————————————————————————————————————

         

Entonces las clases secundarias tendrán el aspecto que sigue:

//——————————————————————————————————————————————————————————————————————————————
class C_Skin : public C_Function
{
  public: //===================================================================
  C_Skin ()
  {
    SetNamFun ("Skin");
    SetMinArg (-5.0);
    SetMaxArg (5.0);
    SetMinFun (-4.3182);  //[x=3.07021;y=3.315935] 1 point
    SetMaxFun (14.0606);  //[x=-3.315699;y=-3.072485] 1 point
  }

  private: //===================================================================
  double Core (double x, double y)
  {
    double a1=2*x*x;
    double a2=2*y*y;
    double b1=MathCos(a1)-1.1;
    b1=b1*b1;
    double c1=MathSin(0.5*x)-1.2;
    c1=c1*c1;
    double d1=MathCos(a2)-1.1;
    d1=d1*d1;
    double e1=MathSin(0.5*y)-1.2;
    e1=e1*e1;

   double res=b1+c1-d1+e1;
   return(res);
  }
};
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
class C_Forest : public C_Function
{
  public: //===================================================================
  C_Forest ()
  {
    SetNamFun ("Forest");
    SetMinArg (-50.0);
    SetMaxArg (-18.0);
    SetMinFun (0.0);             //many points
    SetMaxFun (15.95123239744);  //[x=-25.132741228718345;y=-32.55751918948773] 1 point
  }

  private: //===================================================================
  double Core (double x, double y)
  {
    double a = MathSin (MathSqrt (MathAbs (x - 1.13) + MathAbs (y - 2.0)));
    double b = MathCos (MathSqrt (MathAbs (MathSin (x))) + MathSqrt (MathAbs (MathSin (y - 2.0))));
    double f = a + b;

    double res = MathPow (f, 4);
    if (res < 0.0) res = 0.0;
    return (res);
  }
};
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
class C_Megacity : public C_Function
{
  public: //===================================================================
  C_Megacity ()
  {
    SetNamFun ("Megacity");
    SetMinArg (-15.0);
    SetMaxArg (15.0);
    SetMinFun (0.0);   //many points
    SetMaxFun (15.0);  //[x=`3.16;y=1.990] 1 point
  }

  private: //===================================================================
  double Core (double x, double y)
  {
    double a = MathSin (MathSqrt (MathAbs (x - 1.13) + MathAbs (y - 2.0)));
    double b = MathCos (MathSqrt (MathAbs (MathSin (x))) + MathSqrt (MathAbs (MathSin (y - 2.0))));
    double f = a + b;

    double res = floor (MathPow (f, 4));
    return (res);
  }
};
//——————————————————————————————————————————————————————————————————————————————


Para comprobar la validez de los resultados de las pruebas de AO obtenidos en el banco de pruebas, podemos usar un script en el que se iteran completamente las funciones X e Y con el tamaño de paso Step. Debemos tener cuidado al elegir el paso, ya que un tamaño de paso demasiado pequeño hará que los cálculos tarden demasiado. Por ejemplo, la función Skin tiene un rango de argumentos [-5;5], con un paso de 0.00001 en el eje X, será (5-(-5))/0.00001=1'000'000 (millones) de pasos, con el mismo número de pasos en el eje Y. Por consiguiente, el número total de ejecuciones de la función de prueba para calcular el valor en cada punto será 1'000'000 х 1'000'000= 1'000'000'000'000 (10^12, billones).

Debemos entender cómo de difícil es la tarea para el AO, ya que habrá que encontrar el máximo en tan solo 10 000 pasos (aproximadamente este valor se usa en el optimizador MetaTrader 5). Tenga en cuenta que este cálculo se realiza para una función con dos variables, y el número máximo de variables que se usarán en las pruebas es de 1 000.

También deberemos resaltar un punto importante: ¡ las pruebas del algoritmo en este artículo y los siguientes usan el paso 0.0! o el mínimo posible para una implementación específica para el AO correspondiente.

//——————————————————————————————————————————————————————————————————————————————
input EFunc  Function          = Skin;
input double Step              = 0.01;

//——————————————————————————————————————————————————————————————————————————————
void OnStart ()
{
  C_Function *TestFunc = SelectFunction (Function);

  double argMin = TestFunc.GetMinArg ();
  double argMax = TestFunc.GetMaxArg ();

  double maxFuncValue = 0;
  double xMaxFunc     = 0.0;
  double yMaxFunc     = 0.0;
  
  double minFuncValue = 0;
  double xMinFunc     = 0.0;
  double yMinFunc     = 0.0;
  
  double fValue       = 0.0;

  double arg [2];

  arg [0] = argMin;
  arg [1] = argMin;

  long cnt = 0;

  while (arg [1] <= argMax && !IsStopped ())
  {
    arg [0] = argMin;

    while (arg [0] <= argMax && !IsStopped ())
    {
      cnt++;

      fValue = TestFunc.CalcFunc (arg, 1);

      if (fValue > maxFuncValue)
      {
        maxFuncValue = fValue;
        xMaxFunc = arg [0];
        yMaxFunc = arg [1];
      }
      if (fValue < minFuncValue)
      {
        minFuncValue = fValue;
        xMinFunc = arg [0];
        yMinFunc = arg [1];
      }

      arg [0] += Step;
      
      if (cnt == 1)
      {
       maxFuncValue = fValue;
       minFuncValue = fValue;
      }
    }

    arg [1] += Step;
  }
  
  Print ("======", TestFunc.GetNamFun (), ", launch counter: ", cnt);
  Print ("MaxFuncValue: ", DoubleToString (maxFuncValue, 16), " X: ", DoubleToString (xMaxFunc, 16), " Y: ", DoubleToString (yMaxFunc, 16));
  Print ("MinFuncValue: ", DoubleToString (minFuncValue, 16), " X: ", DoubleToString (xMinFunc, 16), " Y: ", DoubleToString (yMinFunc, 16));
         
  delete TestFunc;
}

//——————————————————————————————————————————————————————————————————————————————


Escribimos el banco de pruebas:

#property script_show_inputs
#property strict

#include <Canvas\Canvas.mqh>
#include <\Math\Functions.mqh>
#include "AO_RND.mqh"
#define  AOname "C_AO_RND"

//——————————————————————————————————————————————————————————————————————————————
input int    Population_P       = 50;
input double ArgumentStep_P     = 0.0;
input int    Test1FuncRuns_P    = 1;
input int    Test2FuncRuns_P    = 20;
input int    Test3FuncRuns_P    = 500;
input int    Measur1FuncValue_P = 1000;
input int    Measur2FuncValue_P = 10000;
input int    NumberRepetTest_P  = 5;
input int    RenderSleepMsc_P   = 0;

//——————————————————————————————————————————————————————————————————————————————
int WidthMonitor = 750;  //monitor screen width
int HeighMonitor = 375;  //monitor screen height

int WidthScrFunc = 375 - 2;  //test function screen width
int HeighScrFunc = 375 - 2;  //test function screen height

CCanvas Canvas;  //drawing table
C_AO_RND AO;     //AO object

C_Skin     SkiF;
C_Forest   ForF;
C_Megacity MegF;

struct S_CLR
{
    color clr [];
};

S_CLR FunctScrin []; //two-dimensional matrix of colors
double ScoreAll    = 0.0;
long   TestCounter = 0;

//——————————————————————————————————————————————————————————————————————————————
void OnStart ()
{
  //creating a table -----------------------------------------------------------
  string canvasName = "AO_Test_Func_Canvas";
  if (!Canvas.CreateBitmapLabel (canvasName, 5, 30, WidthMonitor, HeighMonitor, COLOR_FORMAT_ARGB_RAW))
  {
    Print ("Error creating Canvas: ", GetLastError ());
    return;
  }
  ObjectSetInteger (0, canvasName, OBJPROP_HIDDEN, false);
  ObjectSetInteger (0, canvasName, OBJPROP_SELECTABLE, true);

  ArrayResize (FunctScrin, HeighScrFunc);
  for (int i = 0; i < HeighScrFunc; i++)
  {
    ArrayResize (FunctScrin [i].clr, HeighScrFunc);
  }
  
  //============================================================================
  //Test Skin###################################################################
  Print ("=============================");
  CanvasErase ();
  FuncTests (SkiF, Test1FuncRuns_P, SkiF.GetMinFun (), SkiF.GetMaxFun (), -3.315699, -3.072485, clrLime);
  FuncTests (SkiF, Test2FuncRuns_P, SkiF.GetMinFun (), SkiF.GetMaxFun (), -3.315699, -3.072485, clrAqua);
  FuncTests (SkiF, Test3FuncRuns_P, SkiF.GetMinFun (), SkiF.GetMaxFun (), -3.315699, -3.072485, clrOrangeRed);
  
  //Test Forest#################################################################
  Print ("=============================");
  CanvasErase ();
  FuncTests (ForF, Test1FuncRuns_P, ForF.GetMinFun (), ForF.GetMaxFun (), -25.132741228718345, -32.55751918948773, clrLime);
  FuncTests (ForF, Test2FuncRuns_P, ForF.GetMinFun (), ForF.GetMaxFun (), -25.132741228718345, -32.55751918948773, clrAqua);
  FuncTests (ForF, Test3FuncRuns_P, ForF.GetMinFun (), ForF.GetMaxFun (), -25.132741228718345, -32.55751918948773, clrOrangeRed);
  
  //Test Megacity###############################################################
  Print ("=============================");
  CanvasErase ();
  FuncTests (MegF, Test1FuncRuns_P, MegF.GetMinFun (), MegF.GetMaxFun (), 3.16, 1.990, clrLime);
  FuncTests (MegF, Test2FuncRuns_P, MegF.GetMinFun (), MegF.GetMaxFun (), 3.16, 1.990, clrAqua);
  FuncTests (MegF, Test3FuncRuns_P, MegF.GetMinFun (), MegF.GetMaxFun (), 3.16, 1.990, clrOrangeRed);
  
  Print ("=============================");
  Print ("All score for ", AOname, ": ", ScoreAll / TestCounter);
}
//——————————————————————————————————————————————————————————————————————————————

void CanvasErase ()
{
  Canvas.Erase (XRGB (0, 0, 0));
  Canvas.FillRectangle (1,                1, HeighMonitor - 2, HeighMonitor - 2, COLOR2RGB (clrWhite));
  Canvas.FillRectangle (HeighMonitor + 1, 1, WidthMonitor - 2, HeighMonitor - 2, COLOR2RGB (clrWhite));
}

//——————————————————————————————————————————————————————————————————————————————
void FuncTests (C_Function &f,
                int        funcCount,
                double     minFuncVal,
                double     maxFuncVal,
                double     xBest,
                double     yBest,
                color      clrConv)
{
  DrawFunctionGraph (f.GetMinArg (), f.GetMaxArg (), minFuncVal, maxFuncVal, f);
  SendGraphToCanvas (1, 1);
  int x = (int)Scale (xBest, f.GetMinArg (), f.GetMaxArg (), 0, WidthScrFunc - 1, false);
  int y = (int)Scale (yBest, f.GetMinArg (), f.GetMaxArg (), 0, HeighScrFunc - 1, false);
  Canvas.Circle (x + 1, y + 1, 10, COLOR2RGB (clrBlack));
  Canvas.Circle (x + 1, y + 1, 11, COLOR2RGB (clrBlack));
  Canvas.Update ();
  Sleep (1000);

  int xConv = 0.0;
  int yConv = 0.0;

  int EpochCmidl = 0;
  int EpochCount = 0;

  double aveMid = 0.0;
  double aveEnd = 0.0;

  //----------------------------------------------------------------------------
  for (int test = 0; test < NumberRepetTest_P; test++)
  {
    InitAO (funcCount * 2, f.GetMaxArg (), f.GetMinArg (), ArgumentStep_P);

    EpochCmidl = Measur1FuncValue_P / (ArraySize (AO.S_Colony));
    EpochCount = Measur2FuncValue_P / (ArraySize (AO.S_Colony));

    // Optimization-------------------------------------------------------------
    AO.F_EpochReset ();

    for (int epochCNT = 1; epochCNT <= EpochCount && !IsStopped (); epochCNT++)
    {
      AO.F_Preparation ();

      for (int set = 0; set < ArraySize (AO.S_Colony); set++)
      {
        AO.A_FFcol [set] = f.CalcFunc (AO.S_Colony [set].args, funcCount);
      }

      AO.F_Sorting ();

      if (epochCNT == EpochCmidl) aveMid += AO.A_FFpop [0];

      SendGraphToCanvas  (1, 1);
      Canvas.Circle (x + 1, y + 1, 10, COLOR2RGB (clrBlack));
      Canvas.Circle (x + 1, y + 1, 11, COLOR2RGB (clrBlack));

      //draw a population on canvas
      for (int i = 0; i < ArraySize (AO.S_Population); i++)
      {
        if (i > 0) PointDr (AO.S_Population [i].args, f.GetMinArg (), f.GetMaxArg (), clrWhite, 1, 1, funcCount);
      }
      PointDr (AO.S_Population [0].args, f.GetMinArg (), f.GetMaxArg (), clrBlack, 1, 1, funcCount);
      
      Canvas.Circle (x + 1, y + 1, 10, COLOR2RGB (clrBlack));
      Canvas.Circle (x + 1, y + 1, 11, COLOR2RGB (clrBlack));

      xConv = (int)Scale (epochCNT,       1,          EpochCount, 2, WidthScrFunc - 2, false);
      yConv = (int)Scale (AO.A_FFpop [0], minFuncVal, maxFuncVal, 1, HeighScrFunc - 2, true);

      Canvas.FillCircle (xConv + HeighMonitor + 1, yConv + 1, 1, COLOR2RGB (clrConv));

      Canvas.Update ();
      Sleep (RenderSleepMsc_P);
    }

    aveEnd += AO.A_FFpop [0];

    Sleep (1000);
  }

  aveMid /= (double)NumberRepetTest_P;
  aveEnd /= (double)NumberRepetTest_P;

  double score1 = Scale (aveMid, minFuncVal, maxFuncVal, 0.0, 1.0, false);
  double score2 = Scale (aveEnd, minFuncVal, maxFuncVal, 0.0, 1.0, false);
  
  ScoreAll += score1 + score2;
  TestCounter += 2;

  Print (funcCount, " ", f.GetNamFun (), "'s; Func runs ", Measur1FuncValue_P, " result: ", aveMid, "; Func runs ", Measur2FuncValue_P, " result: ", aveEnd);
  Print ("Score1: ", DoubleToString (score1, 5), "; Score2: ", DoubleToString (score2, 5));
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
void InitAO (const int    params,  //amount of the optimized arguments
             const double max,     //maximum of the optimized argument
             const double min,     //minimum of the optimized argument
             const double step)    //step of the optimized argument
{
  AO.F_Init (params, Population_P);
  for (int idx = 0; idx < params; idx++)
  {
    AO.A_RangeMax  [idx] = max;
    AO.A_RangeMin  [idx] = min;
    AO.A_RangeStep [idx] = step;
  }
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
void PointDr (double &args [], double Min, double Max, color clr, int shiftX, int shiftY, int count)
{
  double x = 0.0;
  double y = 0.0;
  
  double xAve = 0.0;
  double yAve = 0.0;
  
  int width  = 0;
  int height = 0;
  
  color clrF = clrNONE;
  
  for (int i = 0; i < count; i++)
  {
    xAve += args [i * 2];
    yAve += args [i * 2 + 1];
       
    x = args [i * 2];
    y = args [i * 2 + 1];
    
    width  = (int)Scale (x, Min, Max, 0, WidthScrFunc - 1, false);
    height = (int)Scale (y, Min, Max, 0, HeighScrFunc - 1, false);
    
    clrF = DoubleToColor (i, 0, count - 1, 0, 360);
    Canvas.FillCircle (width + shiftX, height + shiftY, 1, COLOR2RGB (clrF));
  }
  
  xAve /= (double)count;
  yAve /= (double)count;

  width  = (int)Scale (xAve, Min, Max, 0, WidthScrFunc - 1, false);
  height = (int)Scale (yAve, Min, Max, 0, HeighScrFunc - 1, false);

  Canvas.FillCircle (width + shiftX, height + shiftY, 3, COLOR2RGB (clrBlack));
  Canvas.FillCircle (width + shiftX, height + shiftY, 2, COLOR2RGB (clr));
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
void SendGraphToCanvas (int shiftX, int shiftY)
{
  for (int w = 0; w < HeighScrFunc; w++)
  {
    for (int h = 0; h < HeighScrFunc; h++)
    {
      Canvas.PixelSet (w + shiftX, h + shiftY, COLOR2RGB (FunctScrin [w].clr [h]));
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
void DrawFunctionGraph (double     min,
                        double     max,
                        double     fMin,
                        double     fMax,
                        C_Function &f)
{
  double ar [2];
  double fV;

  for (int w = 0; w < HeighScrFunc; w++)
  {
    ar [0] = Scale (w, 0, HeighScrFunc, min, max, false);
    for (int h = 0; h < HeighScrFunc; h++)
    {
      ar [1] = Scale (h, 0, HeighScrFunc, min, max, false);
      fV = f.CalcFunc (ar, 1);
      FunctScrin [w].clr [h] = DoubleToColor (fV, fMin, fMax, 0, 250);
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
//Scaling a number from a range to a specified range
double Scale (double In, double InMIN, double InMAX, double OutMIN, double OutMAX, bool Revers = false)
{
  if (OutMIN == OutMAX) return (OutMIN);
  if (InMIN == InMAX) return ((OutMIN + OutMAX) / 2.0);
  else
  {
    if (Revers)
    {
      if (In < InMIN) return (OutMAX);
      if (In > InMAX) return (OutMIN);
      return (((InMAX - In) * (OutMAX - OutMIN) / (InMAX - InMIN)) + OutMIN);
    }
    else
    {
      if (In < InMIN) return (OutMIN);
      if (In > InMAX) return (OutMAX);
      return (((In - InMIN) * (OutMAX - OutMIN) / (InMAX - InMIN)) + OutMIN);
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
color DoubleToColor (const double in,    //input value
                     const double inMin, //minimum of input values
                     const double inMax, //maximum of input values
                     const int    loH,   //lower bound of HSL range values
                     const int    upH)   //upper bound of HSL range values
{
  int h = (int)Scale (in, inMin, inMax, loH, upH, true);
  return HSLtoRGB (h, 1.0, 0.5);
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
color HSLtoRGB (const int    h, //0   ... 360
                const double s, //0.0 ... 1.0
                const double l) //0.0 ... 1.0
{
  int r;
  int g;
  int b;
  if (s == 0.0)
  {
    r = g = b = (unsigned char)(l * 255);
    return StringToColor ((string)r + "," + (string)g + "," + (string)b);
  }
  else
  {
    double v1, v2;
    double hue = (double)h / 360.0;
    v2 = (l < 0.5) ? (l * (1.0 + s)) : ((l + s) - (l * s));
    v1 = 2.0 * l - v2;
    r = (unsigned char)(255 * HueToRGB (v1, v2, hue + (1.0 / 3.0)));
    g = (unsigned char)(255 * HueToRGB (v1, v2, hue));
    b = (unsigned char)(255 * HueToRGB (v1, v2, hue - (1.0 / 3.0)));
    return StringToColor ((string)r + "," + (string)g + "," + (string)b);
  }
}
//——————————————————————————————————————————————————————————————————————————————
//——————————————————————————————————————————————————————————————————————————————
double HueToRGB (double v1, double v2, double vH)
{
  if (vH < 0) vH += 1;
  if (vH > 1) vH -= 1;
  if ((6 * vH) < 1) return (v1 + (v2 - v1) * 6 * vH);
  if ((2 * vH) < 1) return v2;
  if ((3 * vH) < 2) return (v1 + (v2 - v1) * ((2.0f / 3) - vH) * 6);
  return v1;
}
//——————————————————————————————————————————————————————————————————————————————

Para convertir un número double de un rango a un color, hemos utilizado un algoritmo de conversión de HSL a RGB (el sistema de color de MetaTrader 5).

El banco de pruebas muestra una imagen en el gráfico; esta se ha dividido por la mitad.

  • La función de prueba se muestra en el lado izquierdo. Su gráfico tridimensional se proyecta en un plano, donde el rojo indica el máximo, mientras que el azul indica el mínimo. Inmediatamente mostramos la posición de los puntos en la población (el color se corresponde con el número ordinal de la función de prueba con el número de variables 40 y 1000, el coloreado no se realiza para una función con dos variables), marcamos en color blanco los puntos cuyas coordenadas se promedian, mientras que el mejor se marca en negro. 
  • El gráfico de convergencia se muestra en el lado derecho, las pruebas con 2 variables están marcadas en verde, las pruebas con 40 variables están en azul, mientras que las pruebas con 1 000 variables están en rojo. Cada una de las pruebas se realiza cinco veces (5 gráficos de convergencia de cada color). Aquí podemos observar cuánto se deteriora la convergencia de AO con un aumento en el número de variables.


6. Un ejemplo sencillo de AO usando RNG

Para el ejemplo de prueba, implementemos la estrategia de búsqueda más simple. No tiene valor práctico, pero, de alguna manera, resultará un estándar con el que podremos comparar en el futuro los algoritmos de optimización, así como estos entre sí. La estrategia consiste en generar un nuevo conjunto de variables de función en una elección aleatoria 50/50: o bien copiamos una variable de un conjunto principal seleccionado aleatoriamente de la población o bien generamos una variable del rango min/max. Después de obtener los valores de las funciones de prueba, el nuevo conjunto resultante de conjuntos de variables se copiará a la segunda mitad de la población, y luego se clasificará. Así, nuevos conjuntos reemplazarán constantemente a la mitad de la población, mientras que los mejores conjuntos se concentrarán en la otra mitad.

A continuación, mostramos el código de AO basado en RNG

//+————————————————————————————————————————————————————————————————————————————+
class C_AO_RND
{
  public: //||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

  struct ArrColony
  {
      double args [];
  };

  //----------------------------------------------------------------------------
  double    A_RangeStep []; //Step ranges of genes
  double    A_RangeMin  []; //Min ranges of genes
  double    A_RangeMax  []; //Max ranges of genes

  ArrColony S_Population []; //Population
  ArrColony S_Colony     []; //Colony

  double    A_FFpop [];      //Values of fitness of individuals in population
  double    A_FFcol [];      //Values of fitness of individuals in colony

  //----------------------------------------------------------------------------
  // Initialization of algorithm
  void F_Init (int argCount,       //Number of arguments

               int populationSize) //Population size
  {
    MathSrand ((int)GetMicrosecondCount ()); //reset of the generator

    p_argCount  = argCount;
    p_sizeOfPop = populationSize;
    p_sizeOfCol = populationSize / 2;

    p_dwelling  = false;

    f_arrayInitResize (A_RangeStep, argCount, 0.0);
    f_arrayInitResize (A_RangeMin,  argCount, 0.0);
    f_arrayInitResize (A_RangeMax,  argCount, 0.0);

    ArrayResize (S_Population, p_sizeOfPop);
    ArrayResize (s_populTemp, p_sizeOfPop);
    for (int i = 0; i < p_sizeOfPop; i++)
    {
      f_arrayInitResize (S_Population [i].args, argCount, 0.0);
      f_arrayInitResize (s_populTemp [i].args, argCount, 0.0);
    }

    ArrayResize (S_Colony, p_sizeOfCol);
    for (int i = 0; i < p_sizeOfCol; i++)
    {
      f_arrayInitResize (S_Colony [i].args, argCount, 0.0);
    }

    f_arrayInitResize (A_FFpop, p_sizeOfPop, -DBL_MAX);
    f_arrayInitResize (A_FFcol, p_sizeOfCol, -DBL_MAX);

    f_arrayInitResize (a_indexes, p_sizeOfPop, 0);
    f_arrayInitResize (a_valueOnIndexes, p_sizeOfPop, 0.0);
  }

  //----------------------------------------------------------------------------
  void F_EpochReset ()   //Reset of epoch, allows to begin evolution again without initial initialization of variables
  {
    p_dwelling = false;
    ArrayInitialize (A_FFpop, -DBL_MAX);
    ArrayInitialize (A_FFcol, -DBL_MAX);
  }
  //----------------------------------------------------------------------------
  void F_Preparation ();  //Preparation
  //----------------------------------------------------------------------------
  void F_Sorting ();      //The settling of a colony in population and the subsequent sorting of population

  private: //|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
  //----------------------------------------------------------------------------
  void F_PopulSorting ();

  //----------------------------------------------------------------------------
  ArrColony          s_populTemp      []; //Temporal population
  int                a_indexes        []; //Indexes of chromosomes
  double             a_valueOnIndexes []; //VFF of the appropriate indexes of chromosomes

  //----------------------------------------------------------------------------
  template <typename T1>
  void f_arrayInitResize (T1 &arr [], const int size, const T1 value)
  {
    ArrayResize     (arr, size);
    ArrayInitialize (arr, value);
  }

  //----------------------------------------------------------------------------
  double f_seInDiSp         (double In, double InMin, double InMax, double step);
  double f_RNDfromCI        (double min, double max);
  double f_scale            (double In, double InMIN, double InMAX, double OutMIN, double OutMAX);

  //---Constants----------------------------------------------------------------
  int  p_argCount;   //Quantity of arguments in a set of arguments
  int  p_sizeOfCol;  //Quantity of set in a colony
  int  p_sizeOfPop;  //Quantity of set in population
  bool p_dwelling;   //Flag of the first settling of a colony in population
};
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
void C_AO_RND::F_Preparation ()
{
  //if starts of algorithm weren't yet - generate a colony with random arguments
  if (!p_dwelling)
  {
    for (int person = 0; person < p_sizeOfCol; person++)
    {
      for (int arg = 0; arg < p_argCount; arg++)
      {
        S_Colony [person].args [arg] = f_seInDiSp (f_RNDfromCI (A_RangeMin [arg], A_RangeMax [arg]),
                                                    A_RangeMin  [arg],
                                                    A_RangeMax  [arg],
                                                    A_RangeStep [arg]);
      }
    }

    p_dwelling = true;
  }
  //generation of a colony using with copying arguments from parent sets--------
  else
  {
    int parentAdress = 0;
    double rnd       = 0.0;
    double argVal    = 0.0;

    for (int setArg = 0; setArg < p_sizeOfCol; setArg++)
    {
      //get a random address of the parent set
      parentAdress = (int)f_RNDfromCI (0, p_sizeOfPop - 1);

      for (int arg = 0; arg < p_argCount; arg++)
      {
        if (A_RangeMin [arg] == A_RangeMax [arg]) continue;

        rnd = f_RNDfromCI (0.0, 1.0);

        if (rnd < 0.5)
        {
          S_Colony [setArg].args [arg] = S_Population [parentAdress].args [arg];
        }
        else
        {
          argVal = f_RNDfromCI (A_RangeMin [arg], A_RangeMax [arg]);
          argVal = f_seInDiSp (argVal, A_RangeMin [arg], A_RangeMax [arg], A_RangeStep [arg]);

          S_Colony [setArg].args [arg] = argVal;
        }
      }
    }
  }
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
void C_AO_RND::F_Sorting ()
{
  for (int person = 0; person < p_sizeOfCol; person++)
  {
    ArrayCopy (S_Population [person + p_sizeOfCol].args, S_Colony [person].args, 0, 0, WHOLE_ARRAY);
  }
  ArrayCopy (A_FFpop, A_FFcol, p_sizeOfCol, 0, WHOLE_ARRAY);

  F_PopulSorting ();
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
// Ranging of population.
void C_AO_RND::F_PopulSorting ()
{
  //----------------------------------------------------------------------------
  int   cnt = 1, i = 0, u = 0;
  int   t0 = 0;
  double t1 = 0.0;
  //----------------------------------------------------------------------------

  // We will put indexes in the temporary array
  for (i = 0; i < p_sizeOfPop; i++)
  {
    a_indexes [i] = i;
    a_valueOnIndexes [i] = A_FFpop [i];
  }
  while (cnt > 0)
  {
    cnt = 0;
    for (i = 0; i < p_sizeOfPop - 1; i++)
    {
      if (a_valueOnIndexes [i] < a_valueOnIndexes [i + 1])
      {
        //-----------------------
        t0 = a_indexes [i + 1];
        t1 = a_valueOnIndexes [i + 1];
        a_indexes [i + 1] = a_indexes [i];
        a_valueOnIndexes [i + 1] = a_valueOnIndexes [i];
        a_indexes [i] = t0;
        a_valueOnIndexes [i] = t1;
        //-----------------------
        cnt++;
      }
    }
  }

  // On the received indexes create the sorted temporary population
  for (u = 0; u < p_sizeOfPop; u++) ArrayCopy (s_populTemp [u].args, S_Population [a_indexes [u]].args, 0, 0, WHOLE_ARRAY);

  // Copy the sorted array back
  for (u = 0; u < p_sizeOfPop; u++) ArrayCopy (S_Population [u].args, s_populTemp [u].args, 0, 0, WHOLE_ARRAY);

  ArrayCopy (A_FFpop, a_valueOnIndexes, 0, 0, WHOLE_ARRAY);
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
// Choice in discrete space
double C_AO_RND::f_seInDiSp (double in, double inMin, double inMax, double step)
{
  if (in <= inMin) return (inMin);
  if (in >= inMax) return (inMax);
  if (step == 0.0) return (in);
  else return (inMin + step * (double)MathRound ((in - inMin) / step));
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
// Random number generator in the custom interval.
double C_AO_RND::f_RNDfromCI (double min, double max)
{
  if (min == max) return (min);
  double Min, Max;
  if (min > max)
  {
    Min = max;
    Max = min;
  }
  else
  {
    Min = min;
    Max = max;
  }
  return (double(Min + ((Max - Min) * (double)MathRand () / 32767.0)));
}
//——————————————————————————————————————————————————————————————————————————————

//——————————————————————————————————————————————————————————————————————————————
double C_AO_RND::f_scale (double In, double InMIN, double InMAX, double OutMIN, double OutMAX)
{
  if (OutMIN == OutMAX) return (OutMIN);
  if (InMIN == InMAX) return (double((OutMIN + OutMAX) / 2.0));
  else
  {
    if (In < InMIN) return (OutMIN);
    if (In > InMAX) return (OutMAX);
    return (((In - InMIN) * (OutMAX - OutMIN) / (InMAX - InMIN)) + OutMIN);
  }
}
//——————————————————————————————————————————————————————————————————————————————


7. Tabla de resultados

AO

Runs

Skin

Forest

Megacity (discrete)

Final result

2 params (1 F)

40 params (20 F)

1000 params (500 F)

2 params (1 F)

40 params (20 F)

1000 params (500 F)

2 params (1 F)

40 params (20 F)

1000 params (500 F)

RND

1000

0,98744

0,61852

0,49408

0,89582

0,19645

0,14042

0,77333

0,19000

0,14283

0,51254

10000

0,99977

0,69448

0,50188

0,98181

0,24433

0,14042

0,88000

0,20133

0,14283


Después de realizar los tests necesarios en el banco de pruebas, los resultados de AO RND han resultado bastante inesperados: el algoritmo es capaz de encontrar el óptimo entre funciones de dos variables con una precisión muy alta, aunque los resultados para las funciones Forest y Megacity resultan notablemente peores. Sin embargo, por otro lado, hemos confirmado las conjeturas sobre las propiedades débiles de búsqueda para funciones con muchas variables, por lo que ya con 40 argumentos los resultados resultan muy mediocres. El indicador final acumulado ha sido de 0.51254.

En artículos posteriores, analizaremos y probaremos algoritmos de optimización ampliamente conocidos y usados, y también continuaremos la tabla, formando la clasificación de AO.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/8122

Archivos adjuntos |
MQL5.zip (9.71 KB)
Redes neuronales: así de sencillo (Parte 25): Practicando el Transfer Learning Redes neuronales: así de sencillo (Parte 25): Practicando el Transfer Learning
En los últimos dos artículos, hemos creado una herramienta que nos permite crear y editar modelos de redes neuronales. Ahora es el momento de evaluar el uso potencial de la tecnología de Transfer Learning en ejemplos prácticos.
Aprendiendo a diseñar un sistema de trading con Bulls Power Aprendiendo a diseñar un sistema de trading con Bulls Power
Bienvenidos a un nuevo artículo de la serie dedicada a la creación de sistemas comerciales basados en indicadores técnicos populares. En esta ocasión, hablaremos sobre el índice de fuerza alcista Bulls Power y crearemos un sistema comercial basado en sus indicadores.
Matemáticas del mercado: beneficios, pérdidas, costes Matemáticas del mercado: beneficios, pérdidas, costes
En este artículo, le mostraremos cómo calcular el beneficio o las pérdidas totales de cualquier operación, incluyendo la comisión y el swap. Hoy crearemos un modelo matemático más preciso, escribiremos el código basado en él y lo compararemos con un referente. También intentaremos meternos analizar los entresijos de la función principal de MQL5 para calcular el beneficio y llegaremos al fondo de todos los valores necesarios de la especificación.
DoEasy. Elementos de control (Parte 15): Objeto WinForms TabControl - múltiples filas de encabezados de pestañas, métodos de trabajo con pestañas DoEasy. Elementos de control (Parte 15): Objeto WinForms TabControl - múltiples filas de encabezados de pestañas, métodos de trabajo con pestañas
En este artículo, continuaremos desarrollando el objeto WinForm TabControl: hoy crearemos la clase de objeto de pestaña, haremos posible la disposición de los encabezados de las pestañas en varias filas y añadiremos los métodos para trabajar con las pestañas del objeto.