English Русский Deutsch 日本語 Português
preview
Usamos algoritmos de optimización para ajustar los parámetros del asesor sobre la marcha

Usamos algoritmos de optimización para ajustar los parámetros del asesor sobre la marcha

MetaTrader 5Probador | 25 julio 2024, 14:40
139 0
Andrey Dik
Andrey Dik

Contenido:

1. Introducción
2. Arquitectura del asesor comercial con auto-optimización
3. Virtualización de indicadores
4. Virtualización de la estrategia
5. Pruebas de funcionalidad



1. Introducción

A menudo me preguntan cómo aplicar algoritmos de optimización a los asesores y a las estrategias en general. En este artículo me gustaría repasar los aspectos prácticos del uso de algoritmos de optimización.

En el mundo financiero actual, donde cada milisegundo puede marcar una diferencia esencial, el trading algorítmico se hace cada vez más necesario. Los algoritmos de optimización desempeñan un papel fundamental en la creación de estrategias comerciales eficaces. Quizá algunos escépticos crean que los algoritmos de optimización y el comercio no tienen puntos en común. Sin embargo, en este artículo mostraremos cómo pueden interactuar estas dos áreas y qué beneficios podemos obtener haciendo esto.

Para los tráders principiantes, comprender los principios básicos de los algoritmos de optimización puede suponer una poderosa herramienta para encontrar transacciones rentables y minimizar riesgos. Y para los profesionales experimentados, un conocimiento profundo en esta área puede abrir nuevos horizontes y ayudarles a crear sofisticadas estrategias comerciales que superen sus expectativas.

La auto-optimización en un asesor comercial es el proceso mediante el cual el asesor adapta los parámetros de su estrategia comercial para lograr un mejor rendimiento basándose en los datos históricos y las condiciones actuales del mercado. Los principios de este proceso pueden contener los siguientes aspectos:

  • Recogida y análisis de datos. El asesor debe recopilar y analizar los datos históricos del mercado. Esto puede implicar el uso de diversas técnicas de análisis de datos, como el análisis estadístico, el aprendizaje automático y la inteligencia artificial.
  • Identificación de objetivos. El asesor deberá tener claramente definidos los objetivos que pretende alcanzar. Por ejemplo, maximizar el beneficio, minimizar el riesgo o alcanzar un determinado nivel de rentabilidad.
  • Aplicación de algoritmos de optimización. El asesor puede utilizar varios algoritmos de optimización para lograr los mejores resultados. Estos algoritmos ayudan al asesor a encontrar los valores óptimos de los parámetros de la estrategia.
  • Pruebas y validación. Una vez optimizado, el asesor deberá probarse con datos históricos y validarse en las condiciones actuales del mercado para garantizar su eficacia. Las pruebas ayudarán a evaluar el rendimiento del asesor y su capacidad para adaptarse a las condiciones cambiantes del mercado.
  • Supervisión y actualización. El asesor deberá supervisar constantemente sus resultados y actualizar los parámetros de su estrategia según sea necesario. Los mercados cambian constantemente y el asesor deberá estar preparado para adaptarse a las nuevas condiciones y a los cambios de tendencia y volatilidad.

Podemos distinguir varios escenarios principales para la aplicación de algoritmos de optimización en el trading:

  • Optimización de los parámetros de las estrategias comerciales. Los algoritmos de optimización pueden usarse para ajustar los parámetros de las estrategias comerciales. Estos métodos pueden utilizarse para determinar los mejores valores de los parámetros como los periodos de las medias móviles, los niveles de stop loss y take profit, u otros parámetros relacionados con las señales y las reglas comerciales.
  • Optimización de la hora de entrada y salida de las posiciones. Los algoritmos de optimización pueden ayudar a determinar los momentos óptimos para entrar y salir de las posiciones basándose en datos históricos y en las actuales condiciones del mercado. Por ejemplo, podemos utilizar algoritmos de optimización para determinar los intervalos de tiempo óptimos para las señales comerciales.
  • Gestión de portafolios. Los algoritmos de optimización pueden ayudarnos a determinar la asignación óptima de activos en un portafolio para alcanzar determinados objetivos. Por ejemplo, pueden utilizarse técnicas de optimización como la optimización de la varianza media (mean-variance optimization) para encontrar el conjunto de activos más eficiente según la rentabilidad y el riesgo previstos. Esto puede incluir la determinación de la combinación óptima de acciones, bonos y otros activos, así como la optimización del tamaño de las posiciones y la diversificación del portafolio.
  • Desarrollo de estrategias comerciales. Los algoritmos de optimización pueden usarse para desarrollar nuevas estrategias comerciales. Por ejemplo, la programación genética puede usarse para encontrar evolutivamente reglas óptimas de entrada y salida de posiciones basadas en datos históricos.
  • Gestión de riesgos. Los algoritmos de optimización pueden ayudarnos a gestionar el riesgo en la negociación. Por ejemplo, podemos utilizar algoritmos de optimización para calcular el tamaño óptimo de la posición o determinar un nivel de stop-loss dinámico que minimice las pérdidas potenciales.
  • Selección de las mejores herramientas comerciales. Los algoritmos de optimización pueden ayudarnos a seleccionar los mejores instrumentos o activos comerciales. Por ejemplo, podemos utilizar algoritmos de optimización para clasificar los activos según diversos criterios, como la rentabilidad, la volatilidad o la liquidez.
  • Previsión de los mercados financieros. Los algoritmos de optimización pueden usarse para predecir los mercados financieros. Podemos aplicar algoritmos de optimización para ajustar los parámetros de los modelos predictivos o para seleccionar combinaciones óptimas de modelos predictivos.

Estos son solo algunos ejemplos de situaciones en las que se aplican algoritmos de optimización en el sector del trading. En general, los algoritmos de optimización pueden ayudar a automatizar y mejorar diversos aspectos de la negociación, desde la búsqueda de estrategias óptimas hasta la gestión de riesgos y portafolios.


2. Arquitectura del asesor comercial con auto-optimización

A la hora de implementar la auto-optimización de un asesor, existen varios esquemas posibles, pero uno de los más sencillos y con el mínimo necesario para implementar cualquier característica y funcionalidad requerida es el esquema mostrado en la figura 1.

En la línea temporal "History", el asesor se encuentra en el punto "time now" en el que se toma la decisión de optimización. El asesor "EA" llama a la función de gestión "Manager function", que gestiona el proceso de optimización, y transmite los ajustes de optimización "optimization parameters" a esta función.

A su vez, el manager solicita un conjunto de parámetros al algoritmo de optimización "optimization ALGO" o "AO", que en adelante denominaremos "conjunto". A continuación, el manager transfiere el conjunto a la estrategia comercial virtual "EA Virt", que es un análogo completo de una estrategia real que funciona y ejecuta operaciones, "EA".

"EA Virt" realiza una operación virtual desde el punto "past" de la historia hasta el punto "time now". El manager ejecuta "EA Virt" tantas veces como se especifique el tamaño de la población en los "parámetros de optimización". "EA Virt" retorna a su vez el resultado de la ejecución en la historia como "ff result".

"ff result" es el resultado de aptitud o adaptabilidad o criterio de optimización, que puede ser cualquier cosa a discreción del usuario. Puede ser, por ejemplo, el balance, el factor de beneficio, la esperanza matemática o un criterio complejo, una integral o el spread agregado, medido en muchos momentos de "History". Así, el resultado de la función de aptitud, o "ff result", es lo que el usuario considera un indicador importante de la calidad de la estrategia comercial.

A continuación, "ff result", que es la puntuación de un conjunto concreto, será transmitido por el manager al algoritmo de optimización.

Al alcanzarse la condición de parada, el manager transfiere el mejor conjunto al asesor comercial "EA", después de lo cual el asesor continúa trabajando (negociando) con nuevos parámetros actualizados desde el punto "tiempo ahora" hasta el punto de reoptimización "reoptimiz", donde se realizará la reoptimización para una profundidad dada de la historia.

El punto de reoptimización puede elegirse partiendo de varias consideraciones, puede ser un número rígidamente definido de barras históricas, como en el ejemplo siguiente, o alguna condición específica, como una disminución del rendimiento comercial hasta algún nivel crítico.

Scheme

Figura 1. Esquema para organizar el trabajo de auto-optimización del asesor.

Según el esquema del algoritmo de optimización "optimization ALGO", este puede considerarse como una "caja negra" que realiza su trabajo de forma autónoma (sin embargo, para él todo lo que está fuera es también una "caja negra"), independientemente de una estrategia comercial, un manager y una estrategia virtual concretos. El manager solicita un conjunto al algoritmo de optimización y le retorna una estimación de ese conjunto, que el algoritmo de optimización utilizará para determinar el siguiente conjunto. Este ciclo continuará hasta que se encuentre el mejor conjunto de parámetros para satisfacer los requisitos del usuario. Así, el algoritmo de optimización busca en los parámetros óptimos exactamente aquellos que cumplen las necesidades del usuario especificadas a través de la función de aptitud en "EA Virt".


3. Virtualización de indicadores

Para ejecutar el asesor con datos históricos, deberemos crear una copia virtual de la estrategia comercial que realizará las mismas operaciones que en el trabajo en una cuenta comercial. Cuando no se utilizan indicadores, la virtualización de las condiciones lógicas dentro del asesor se vuelve relativamente simple: todo lo que necesitamos es describir las acciones lógicas según un punto temporal en la serie de precios, mientras que el uso de indicadores resulta más difícil y la mayoría de las estrategias comerciales se basan en el uso de varios indicadores.

El problema es que para encontrar los parámetros óptimos de los indicadores debemos crear manejadores de indicadores con el conjunto actual en una iteración determinada. Una vez finalizada la ejecución con datos históricos, estos manejadores deben borrarse, de lo contrario la memoria RAM de la computadora puede llenarse rápidamente, sobre todo si existe un gran número de posibles variantes de conjuntos de parámetros. Esto no supone ningún problema si este procedimiento se realiza en el gráfico de símbolos, pero en el simulador la eliminación de manejadores no está permitida.

Para solucionar el problema, necesitaremos "virtualizar" el cálculo del indicador dentro del asesor ejecutado para evitar el uso de manejadores. Tomaremos como ejemplo el indicador Stochastic.

La parte de cálculo de cada indicador contiene la función estándar "OnCalculate". Esta función debería renombrarse, por ejemplo, a "Calculate" y dejarse prácticamente sin cambios. 

Necesitaremos formar el indicador como una clase (una estructura servirá), que llamaremos "C_Stochastic". En la declaración de la clase deberemos especificar los búferes principales del indicador como campos públicos (los búferes de cálculo adicionales pueden ser privados) y declarar la función de inicialización "Init" a la que deberemos transmitir los parámetros del indicador.

//——————————————————————————————————————————————————————————————————————————————
class C_iStochastic
{
  public: void Init (const int InpKPeriod,       // K period
                     const int InpDPeriod,       // D period
                     const int InpSlowing)       // Slowing
  {
    inpKPeriod = InpKPeriod;
    inpDPeriod = InpDPeriod;
    inpSlowing = InpSlowing;
  }

  public: int Calculate (const int rates_total,
                         const int prev_calculated,
                         const double &high  [],
                         const double &low   [],
                         const double &close []);

  //--- indicator buffers
  public:  double ExtMainBuffer   [];
  public:  double ExtSignalBuffer [];
  private: double ExtHighesBuffer [];
  private: double ExtLowesBuffer  [];

  private: int inpKPeriod; // K period
  private: int inpDPeriod; // D period
  private: int inpSlowing; // Slowing
};
//——————————————————————————————————————————————————————————————————————————————

Y, propiamente dicho, el cálculo del indicador en el método "Calculate". El cálculo del indicador no difiere en absoluto del indicador del paquete estándar del terminal. La única diferencia es la asignación de tamaño para los búferes de indicador y su inicialización

Como ve, se trata de un ejemplo muy sencillo para comprender el principio de la virtualización de indicadores. El cálculo se realizará para toda la profundidad de los periodos especificada en los parámetros del indicador. Es posible organizar la posibilidad de cálculo adicional de solo la última barra e implementar búferes de anillo, pero el propósito del artículo es mostrar un ejemplo sencillo que requiera una intervención mínima en la conversión del indicador en una forma virtual y sea accesible a los usuarios con conocimientos mínimos de programación.

//——————————————————————————————————————————————————————————————————————————————
int C_iStochastic::Calculate (const int rates_total,
                              const int prev_calculated,
                              const double &high  [],
                              const double &low   [],
                              const double &close [])
{
  if (rates_total <= inpKPeriod + inpDPeriod + inpSlowing) return (0);

  ArrayResize (ExtHighesBuffer, rates_total);
  ArrayResize (ExtLowesBuffer,  rates_total);
  ArrayResize (ExtMainBuffer,   rates_total);
  ArrayResize (ExtSignalBuffer, rates_total);

  ArrayInitialize (ExtHighesBuffer, 0.0);
  ArrayInitialize (ExtLowesBuffer,  0.0);
  ArrayInitialize (ExtMainBuffer,   0.0);
  ArrayInitialize (ExtSignalBuffer, 0.0);

  int i, k, start;

  start = inpKPeriod - 1;

  if (start + 1 < prev_calculated)
  {
    start = prev_calculated - 2;
    Print ("start ", start);
  }
  else
  {
    for (i = 0; i < start; i++)
    {
      ExtLowesBuffer  [i] = 0.0;
      ExtHighesBuffer [i] = 0.0;
    }
  }

  //--- calculate HighesBuffer[] and ExtHighesBuffer[]
  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double dmin =  1000000.0;
    double dmax = -1000000.0;

    for (k = i - inpKPeriod + 1; k <= i; k++)
    {
      if (dmin > low  [k]) dmin = low  [k];
      if (dmax < high [k]) dmax = high [k];
    }

    ExtLowesBuffer  [i] = dmin;
    ExtHighesBuffer [i] = dmax;
  }

  //--- %K
  start = inpKPeriod - 1 + inpSlowing - 1;

  if (start + 1 < prev_calculated) start = prev_calculated - 2;
  else
  {
    for (i = 0; i < start; i++) ExtMainBuffer [i] = 0.0;
  }

  //--- main cycle
  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double sum_low  = 0.0;
    double sum_high = 0.0;

    for (k = (i - inpSlowing + 1); k <= i; k++)
    {
      sum_low  += (close [k] - ExtLowesBuffer [k]);
      sum_high += (ExtHighesBuffer [k] - ExtLowesBuffer [k]);
    }

    if (sum_high == 0.0) ExtMainBuffer [i] = 100.0;
    else                 ExtMainBuffer [i] = sum_low / sum_high * 100;
  }

  //--- signal
  start = inpDPeriod - 1;

  if (start + 1 < prev_calculated) start = prev_calculated - 2;
  else
  {
    for (i = 0; i < start; i++) ExtSignalBuffer [i] = 0.0;
  }

  for (i = start; i < rates_total && !IsStopped (); i++)
  {
    double sum = 0.0;
    for (k = 0; k < inpDPeriod; k++) sum += ExtMainBuffer [i - k];
    ExtSignalBuffer [i] = sum / inpDPeriod;
  }

  //--- OnCalculate done. Return new prev_calculated.
  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————

Además, ofreceremos un ejemplo de virtualización del indicador "MACD":

//——————————————————————————————————————————————————————————————————————————————
class C_iMACD
{
  public: void Init (const int InpFastEMA,       // Fast   EMA period
                     const int InpSlowEMA,       // Slow   EMA period
                     const int InpSignalSMA)     // Signal SMA period
  {
    inpFastEMA   = InpFastEMA;
    inpSlowEMA   = InpSlowEMA;
    inpSignalSMA = InpSignalSMA;

    maxPeriod = InpFastEMA;
    if (maxPeriod < InpSlowEMA)   maxPeriod = InpSlowEMA;
    if (maxPeriod < InpSignalSMA) maxPeriod = InpSignalSMA;
  }

  public: int Calculate (const int rates_total,
                         const int prev_calculated,
                         const double &close []);

  //--- indicator buffers
  public:  double ExtMacdBuffer   [];
  public:  double ExtSignalBuffer [];
  private: double ExtFastMaBuffer [];
  private: double ExtSlowMaBuffer [];

  private: int ExponentialMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer []);
  private: int SimpleMAOnBuffer      (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer []);

  private: int inpFastEMA;   // Fast EMA period
  private: int inpSlowEMA;   // Slow EMA period
  private: int inpSignalSMA; // Signal SMA period
  private: int maxPeriod;
};
//——————————————————————————————————————————————————————————————————————————————

Parte de cálculo del indicador:

//——————————————————————————————————————————————————————————————————————————————
int C_iMACD::Calculate (const int rates_total,
                        const int prev_calculated,
                        const double &close [])
{
  if (rates_total < maxPeriod) return (0);

  ArrayResize (ExtMacdBuffer,   rates_total);
  ArrayResize (ExtSignalBuffer, rates_total);
  ArrayResize (ExtFastMaBuffer, rates_total);
  ArrayResize (ExtSlowMaBuffer, rates_total);

  ArrayInitialize (ExtMacdBuffer,   0.0);
  ArrayInitialize (ExtSignalBuffer, 0.0);
  ArrayInitialize (ExtFastMaBuffer, 0.0);
  ArrayInitialize (ExtSlowMaBuffer, 0.0);

  ExponentialMAOnBuffer (rates_total, prev_calculated, 0, inpFastEMA, close, ExtFastMaBuffer);
  ExponentialMAOnBuffer (rates_total, prev_calculated, 0, inpSlowEMA, close, ExtSlowMaBuffer);

  int start;
  if (prev_calculated == 0) start = 0;

  else start = prev_calculated - 1;

  //--- calculate MACD
  for (int i = start; i < rates_total && !IsStopped (); i++) ExtMacdBuffer [i] = ExtFastMaBuffer [i] - ExtSlowMaBuffer [i];

  //--- calculate Signal
  SimpleMAOnBuffer (rates_total, prev_calculated, 0, inpSignalSMA, ExtMacdBuffer, ExtSignalBuffer);

  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————

No tendremos que modificar en absoluto el cálculo del suavizado exponencial:

//——————————————————————————————————————————————————————————————————————————————
int C_iMACD::ExponentialMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer [])
{
  //--- check period
  if (period <= 1 || period > (rates_total - begin)) return (0);

  //--- save and clear 'as_series' flags
  bool as_series_price  = ArrayGetAsSeries (price);
  bool as_series_buffer = ArrayGetAsSeries (buffer);

  ArraySetAsSeries (price, false);
  ArraySetAsSeries (buffer, false);

  //--- calculate start position
  int    start_position;
  double smooth_factor = 2.0 / (1.0 + period);

  if (prev_calculated == 0) // first calculation or number of bars was changed
  {
    //--- set empty value for first bars
    for (int i = 0; i < begin; i++) buffer [i] = 0.0;

    //--- calculate first visible value
    start_position = period + begin;
    buffer [begin] = price [begin];

    for (int i = begin + 1; i < start_position; i++) buffer [i] = price [i] * smooth_factor + buffer [i - 1] * (1.0 - smooth_factor);
  }
  else start_position = prev_calculated - 1;

  //--- main loop
  for (int i = start_position; i < rates_total; i++) buffer [i] = price [i] * smooth_factor + buffer [i - 1] * (1.0 - smooth_factor);

  //--- restore as_series flags
  ArraySetAsSeries (price,  as_series_price);
  ArraySetAsSeries (buffer, as_series_buffer);
  //---
  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————

Tampoco deberemos modificar el cálculo del suavizado simple:

//——————————————————————————————————————————————————————————————————————————————
int C_iMACD::SimpleMAOnBuffer (const int rates_total, const int prev_calculated, const int begin, const int period, const double& price [],double& buffer [])
{
  //--- check period
  if (period <= 1 || period > (rates_total - begin)) return (0);

  //--- save as_series flags
  bool as_series_price = ArrayGetAsSeries (price);
  bool as_series_buffer = ArrayGetAsSeries (buffer);

  ArraySetAsSeries (price, false);
  ArraySetAsSeries (buffer, false);

  //--- calculate start position
  int start_position;

  if (prev_calculated == 0) // first calculation or number of bars was changed
  {
    //--- set empty value for first bars
    start_position = period + begin;

    for (int i = 0; i < start_position - 1; i++) buffer [i] = 0.0;

    //--- calculate first visible value
    double first_value = 0;

    for (int i = begin; i < start_position; i++) first_value += price [i];

    buffer [start_position - 1] = first_value / period;
  }
  else start_position = prev_calculated - 1;

  //--- main loop
  for (int i = start_position; i < rates_total; i++) buffer [i] = buffer [i - 1] + (price [i] - price [i - period]) / period;

  //--- restore as_series flags
  ArraySetAsSeries (price, as_series_price);
  ArraySetAsSeries (buffer, as_series_buffer);

  //---
  return (rates_total);
}
//——————————————————————————————————————————————————————————————————————————————


4. Virtualización de la estrategia

Uno de los estimados lectores de mis artículos sobre algoritmos de optimización, LUIS ALBERTO BIANUCCI, me ha facilitado amablemente el código de un asesor basado en el indicador Stochastic. Me pidió que creara un ejemplo basado en este código para mostrar una forma de organizar el autoaprendizaje en un asesor conectando la biblioteca "AO Core" y que considerara este ejemplo en el artículo. Así, otros usuarios podrán usar este método al introducir algoritmos de optimización en sus propios desarrollos. Querríamos destacar que este método resulta adecuado para conectar cualquiera de los algoritmos de optimización tratados en mi serie de artículos "Algoritmos de optimización de la población", debido a que los algoritmos están diseñados de forma universal y pueden aplicarse con éxito a cualquier proyecto de usuario.

Más arriba en el artículo hemos analizado la virtualización del indicador como parte del asesor, y ahora pasaremos a la virtualización de la estrategia. Al principio del código del asesor, declararemos la importación de la biblioteca, los archivos de inclusión de la biblioteca comercial estándar y el archivo de inclusión del estocástico virtual.

A continuación vendrán "input", los parámetros del asesor, entre los cuales notaremos "InpKPeriod_P" y "InpUpperLevel_P", estos parámetros deberán ser optimizados, y representan el periodo del indicador "Stochastic" y los niveles.

input string   InpKPeriod_P        = "18|9|3|24";  //STO K period:      it is necessary to optimize
input string   InpUpperLevel_P  = "96|88|2|98"; //STO upper level: it is necessary to optimize

Tenga en cuenta que los parámetros se declararán con el tipo string, los parámetros son compuestos e incluyen el valor por defecto, el valor inicial de optimización, el paso y el valor final de optimización.

//——————————————————————————————————————————————————————————————————————————————
#import "\\Market\\AO Core.ex5"
bool   Init (int colonySize, double &range_min [], double &range_max [], double &range_step []);
//------------------------------------------------------------------------------
void   Preparation    ();
void   GetVariantCalc (double &variant [], int pos);
void   SetFitness     (double value,       int pos);
void   Revision       ();
//------------------------------------------------------------------------------
void   GetVariant     (double &variant [], int pos);
double GetFitness     (int pos);
#import
//——————————————————————————————————————————————————————————————————————————————

#include <Trade\Trade.mqh>;
#include "cStochastic.mqh"


input group         "==== GENERAL ====";
sinput long         InpMagicNumber      = 132516;       //Magic Number
sinput double       InpLotSize          = 0.01;         //Lots

input group         "==== Trading ====";
input int           InpStopLoss         = 1450;         //Stoploss
input int           InpTakeProfit       = 1200;         //Takeprofit

input group         "==== Stochastic ==|value|start|step|end|==";
input string        InpKPeriod_P        = "18|9|3|24";  //STO K period   : it is necessary to optimize
input string        InpUpperLevel_P     = "96|88|2|98"; //STO upper level: it is necessary to optimize

input group         "====Self-optimization====";
sinput bool         SelfOptimization    = true;
sinput int          InpBarsOptimize     = 18000;        //Number of bars in the history for optimization
sinput int          InpBarsReOptimize   = 1440;         //After how many bars, EA will reoptimize
sinput int          InpPopSize          = 50;           //Population size
sinput int          NumberFFlaunches    = 10000;        //Number of runs in the history during optimization
sinput int          Spread              = 10;           //Spread

MqlTick Tick;
CTrade  Trade;

C_iStochastic IStoch;

double Set        [];
double Range_Min  [];
double Range_Step [];
double Range_Max  [];

double TickSize = 0.0;

En la inicialización del asesor en la función "OnInit", estableceremos el tamaño de los arrays de parámetros según el número de parámetros a optimizar: "Set" - conjunto de parámetros, "Range_Min" - valores mínimos de los parámetros (valores iniciales), "Range_Step" - paso de los parámetros y "Range_Max" - valores máximos de los parámetros. A partir de los parámetros string, extraeremos los valores correspondientes y los asignaremos a los arrays.

//——————————————————————————————————————————————————————————————————————————————
int OnInit ()
{
  TickSize = SymbolInfoDouble (_Symbol, SYMBOL_TRADE_TICK_SIZE);

  ArrayResize (Set,        2);
  ArrayResize (Range_Min,  2);
  ArrayResize (Range_Step, 2);
  ArrayResize (Range_Max,  2);

  string result [];
  if (StringSplit (InpKPeriod_P, StringGetCharacter ("|", 0), result) != 4) return INIT_FAILED;

  Set        [0] = (double)StringToInteger (result [0]);
  Range_Min  [0] = (double)StringToInteger (result [1]);
  Range_Step [0] = (double)StringToInteger (result [2]);
  Range_Max  [0] = (double)StringToInteger (result [3]);

  if (StringSplit (InpUpperLevel_P, StringGetCharacter ("|", 0), result) != 4) return INIT_FAILED;

  Set        [1] = (double)StringToInteger (result [0]);
  Range_Min  [1] = (double)StringToInteger (result [1]);
  Range_Step [1] = (double)StringToInteger (result [2]);
  Range_Max  [1] = (double)StringToInteger (result [3]);

  IStoch.Init ((int)Set [0], 1, 3);

  //  set magicnumber to trade object
  Trade.SetExpertMagicNumber (InpMagicNumber);

  //---
  return (INIT_SUCCEEDED);
}
//——————————————————————————————————————————————————————————————————————————————

En el código del asesor, en la función"OnTick", insertaremos un bloque de llamada a la auto-optimización, la función "Optimize", que es el "manager" en el esquema de la figura 1, e inicia la optimización. En los casos en los que se deberían haber utilizado variables externas que requieran optimización, utilizaremos valores del array "Set".

//——————————————————————————————————————————————————————————————————————————————
void OnTick ()
{
  //----------------------------------------------------------------------------
  if (!IsNewBar ())
  {
    return;
  }

  //----------------------------------------------------------------------------
  if (SelfOptimization)
  {
    //--------------------------------------------------------------------------
    static datetime LastOptimizeTime = 0;

    datetime timeNow  = iTime (_Symbol, PERIOD_CURRENT, 0);
    datetime timeReop = iTime (_Symbol, PERIOD_CURRENT, InpBarsReOptimize);

    if (LastOptimizeTime <= timeReop)
    {
      LastOptimizeTime = timeNow;
      Print ("-------------------Start of optimization----------------------");

      Print ("Old set:");
      ArrayPrint (Set);

      Optimize (Set,
                Range_Min,
                Range_Step,
                Range_Max,
                InpBarsOptimize,
                InpPopSize,
                NumberFFlaunches,
                Spread * SymbolInfoDouble (_Symbol, SYMBOL_TRADE_TICK_SIZE));

      Print ("New set:");
      ArrayPrint (Set);

      IStoch.Init ((int)Set [0], 1, 3);
    }
  }

  //----------------------------------------------------------------------------
  if (!SymbolInfoTick (_Symbol, Tick))
  {
    Print ("Failed to get current symbol tick"); return;
  }

  //data preparation------------------------------------------------------------
  MqlRates rates [];
  int dataCount = CopyRates (_Symbol, PERIOD_CURRENT, 0, (int)Set [0] + 1 + 3 + 1, rates);

  if (dataCount == -1)
  {
    Print ("Data get error");
    return;
  }

  double hi [];
  double lo [];
  double cl [];

  ArrayResize (hi, dataCount);
  ArrayResize (lo, dataCount);
  ArrayResize (cl, dataCount);

  for (int i = 0; i < dataCount; i++)
  {
    hi [i] = rates [i].high;
    lo [i] = rates [i].low;
    cl [i] = rates [i].close;
  }

  int calc = IStoch.Calculate (dataCount, 0, hi, lo, cl);
  if (calc <= 0) return;

  double buff0 = IStoch.ExtMainBuffer [ArraySize (IStoch.ExtMainBuffer) - 2];
  double buff1 = IStoch.ExtMainBuffer [ArraySize (IStoch.ExtMainBuffer) - 3];

  //----------------------------------------------------------------------------
  // count open positions
  int cntBuy, cntSell;
  if (!CountOpenPositions (cntBuy, cntSell))
  {
    Print ("Failed to count open positions");
    return;
  }

  //----------------------------------------------------------------------------
  // check for buy
  if (cntBuy == 0 && buff1 <= (100 - (int)Set [1]) && buff0 > (100 - (int)Set [1]))
  {
    ClosePositions (2);

    double sl = NP (Tick.bid - InpStopLoss   * TickSize);
    double tp = NP (Tick.bid + InpTakeProfit * TickSize);

    Trade.PositionOpen (_Symbol, ORDER_TYPE_BUY, InpLotSize, Tick.ask, sl, tp, "Stochastic EA");
  }

  //----------------------------------------------------------------------------
  // check for sell
  if (cntSell == 0 && buff1 >= (int)Set [1] && buff0 < (int)Set [1])
  {
    ClosePositions (1);

    double sl = NP (Tick.ask + InpStopLoss   * TickSize);
    double tp = NP (Tick.ask - InpTakeProfit * TickSize);

    Trade.PositionOpen (_Symbol, ORDER_TYPE_SELL, InpLotSize, Tick.bid, sl, tp, "Stochastic EA");
  }
}
//——————————————————————————————————————————————————————————————————————————————

La función "Optimize" realiza los mismos pasos habituales en los scripts de prueba de los algoritmos de optimización de la serie de artículos "Algoritmos de optimización de la población":

1. Inicialización del algoritmo de optimización.
2.1. Preparación de la población.
2.2. Obtención de un conjunto de parámetros partiendo del algoritmo de optimización.
2.3. Cálculo de una función de aptitud con los parámetros transmitidos.
2.4. Actualización de la mejor solución.
2.5. Obtención de la mejor solución del algoritmo.

//——————————————————————————————————————————————————————————————————————————————
void Optimize (double      &set        [],
               double      &range_min  [],
               double      &range_step [],
               double      &range_max  [],
               const int    inpBarsOptimize,
               const int    inpPopSize,
               const int    numberFFlaunches,
               const double spread)
{
  //----------------------------------------------------------------------------
  double parametersSet [];
  ArrayResize(parametersSet, ArraySize(set));

  //----------------------------------------------------------------------------
  int epochCount = numberFFlaunches / inpPopSize;

  Init(inpPopSize, range_min, range_max, range_step);

  // Optimization-------------------------------------------------------------
  for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++)
  {
    Preparation ();

    for (int set = 0; set < inpPopSize; set++)
    {
      GetVariantCalc (parametersSet, set);
      SetFitness     (VirtualStrategy (parametersSet, inpBarsOptimize, spread), set);
    }

    Revision ();
  }

  Print ("Fitness: ", GetFitness (0));
  GetVariant (parametersSet, 0);
  ArrayCopy (set, parametersSet, 0, 0, WHOLE_ARRAY);
}
//——————————————————————————————————————————————————————————————————————————————

La función "VirtualStrategy" pone a prueba la estrategia usando datos históricos (en el esquema de la figura 1 es "EA Virt"). Acepta el array de parámetros "set", el número de barras a optimizar "barsOptimize" y el valor "spread".

En primer lugar, se prepararán los datos. Los datos históricos se cargarán en el array "rates". A continuación, se crearán los arrays "hi", "lo" y "cl", necesarios para calcular Stochastic.

Luego se inicializará el indicador Stochastic y se realizará su cálculo a partir de los datos históricos. Si el cálculo falla, la función retornará el valor "-DBL_MAX" (el peor valor posible de la función de aptitud).

Después seguirá la prueba de la estrategia sobre datos históricos, cuya lógica se corresponderá plenamente con el código principal del asesor. Y se creará un objeto "deals" para almacenar las transacciones. A continuación se realizará una pasada por los datos históricos, donde para cada barra se comprobarán las condiciones de apertura y cierre de las posiciones en función del valor del indicador y de los niveles "upLevel" y "dnLevel". Si se cumplen las condiciones, la posición se abrirá o se cerrará.

Una vez finalizado la pasada por los datos históricos, la función comprobará el número de transacciones realizadas. Si no se han dado transacciones, la función retornará el valor "-DBL_MAX". En caso contrario, la función retornará el balance total.

El valor de retorno de "VirtualStrategy" será el valor de la función de aptitud. En este caso, será el valor del balance final en puntos (como hemos dicho antes, la función de aptitud puede ser el balance, el factor de beneficio o cualquier otro indicador de la calidad de los resultados de las operaciones con datos históricos).

Debemos señalar que la estrategia virtual debe ajustarse lo más posible a la estrategia del asesor. En este ejemplo, la negociación se realizará según los precios de apertura, lo cual se corresponde con el control de apertura de barras en el asesor comercial principal. Si la lógica de la estrategia comercial se ejecuta en cada tick, el usuario deberá encargarse de descargar la historia de ticks durante la prueba virtual y ajustar la función "VirtualStrategy" en consecuencia.

//——————————————————————————————————————————————————————————————————————————————
double VirtualStrategy (double &set [], int barsOptimize, double spread)
{
  //data preparation------------------------------------------------------------
  MqlRates rates [];
  int dataCount = CopyRates(_Symbol, PERIOD_CURRENT, 0, barsOptimize + 1, rates);

  if (dataCount == -1)
  {
    Print ("Data get error");
    return -DBL_MAX;
  }

  double hi [];
  double lo [];
  double cl [];

  ArrayResize (hi, dataCount);
  ArrayResize (lo, dataCount);
  ArrayResize (cl, dataCount);

  for (int i = 0; i < dataCount; i++)
  {
    hi [i] = rates [i].high;
    lo [i] = rates [i].low;
    cl [i] = rates [i].close;
  }

  C_iStochastic iStoch;
  iStoch.Init ((int)set [0], 1, 3);

  int calc = iStoch.Calculate (dataCount, 0, hi, lo, cl);
  if (calc <= 0) return -DBL_MAX;

  //============================================================================
  //test of strategy on history-------------------------------------------------
  S_Deals deals;

  double iStMain0 = 0.0;
  double iStMain1 = 0.0;
  double upLevel  = set [1];
  double dnLevel  = 100.0 - set [1];
  double balance  = 0.0;

  //running through history-----------------------------------------------------
  for (int i = 2; i < dataCount; i++)
  {
    if (i >= dataCount)
    {
      deals.ClosPos (-1, rates [i].open, spread);
      deals.ClosPos (1, rates [i].open, spread);
      break;
    }

    iStMain0 = iStoch.ExtMainBuffer [i - 1];
    iStMain1 = iStoch.ExtMainBuffer [i - 2];

    if (iStMain0 == 0.0 || iStMain1 == 0.0) continue;

    //buy-------------------------------
    if (iStMain1 <= dnLevel && dnLevel < iStMain0)
    {
      deals.ClosPos (-1, rates [i].open, spread);

      if (deals.GetBuys () == 0) deals.OpenPos (1, rates [i].open, spread);
    }

    //sell------------------------------
    if (iStMain1 >= upLevel && upLevel > iStMain0)
    {
      deals.ClosPos (1, rates [i].open, spread);

      if (deals.GetSels () == 0) deals.OpenPos (-1, rates [i].open, spread);
    }
  }
  //----------------------------------------------------------------------------

  if (deals.histSelsCNT + deals.histBuysCNT <= 0) return -DBL_MAX;
  return deals.balance;
}
//——————————————————————————————————————————————————————————————————————————————

Si desea utilizar un algoritmo de optimización de los artículos "Algoritmos de optimización de la población" (el archivo del artículo contiene el algoritmo "Evolución de grupos sociales", ESG, como ejemplo), deberá especificar la ruta al algoritmo en el asesor:

#include "AO_ESG.mqh"

En la función "Optimize", declararemos el objeto de algoritmo ESG y estableceremos los valores límite de los parámetros que se van a optimizar. Entonces, para usar el ESG, la función "Optimize" tendría el siguiente aspecto:

//——————————————————————————————————————————————————————————————————————————————
void Optimize (double      &set        [],
               double      &range_min  [],
               double      &range_step [],
               double      &range_max  [],
               const int    inpBarsOptimize,
               const int    inpPopSize,
               const int    numberFFlaunches,
               const double spread)
{
  //----------------------------------------------------------------------------
  int epochCount = numberFFlaunches / inpPopSize;

  C_AO_ESG AO;
  
  int    Population_P     = 200;   //Population size
  int    Groups_P         = 100;   //Number of groups
  double GroupRadius_P    = 0.1;   //Group radius
  double ExpansionRatio_P = 2.0;   //Expansion ratio
  double Power_P          = 10.0;  //Power
  
  AO.Init (ArraySize (set), Population_P, Groups_P, GroupRadius_P, ExpansionRatio_P, Power_P);
  
  for (int i = 0; i < ArraySize (set); i++)
  {
    AO.rangeMin  [i] = range_min  [i];
    AO.rangeStep [i] = range_step [i];
    AO.rangeMax  [i] = range_max  [i];
  }
  
  // Optimization-------------------------------------------------------------
  for (int epochCNT = 1; epochCNT <= epochCount && !IsStopped (); epochCNT++)
  {
    AO.Moving ();
    
    for (int set = 0; set < ArraySize (AO.a); set++)
    {
      AO.a [set].f = VirtualStrategy (AO.a [set].c, inpBarsOptimize, spread);
    }

    AO.Revision ();
  }

  Print ("Fitness: ", AO.fB);
  ArrayCopy (set, AO.cB, 0, 0, WHOLE_ARRAY);
}
//——————————————————————————————————————————————————————————————————————————————

Las estrategias de algoritmos de búsqueda presentadas en mi serie de artículos sobre optimización ofrecen un enfoque simple y directo para su análisis y comparación mutuo, tal cual. No incluyen técnicas de aceleración de la búsqueda, como la selección de duplicados y otras técnicas, y requieren más iteraciones y tiempo.


5. Pruebas de funcionalidad

Vamos a probar nuestro asesor auto-optimizador basado en el indicador "Stochastic" durante un periodo de un año con los parámetros que se muestran a continuación en la captura de pantalla, primero en el modo "false" del parámetro "SelfOptimization", es decir, sin auto-optimización.

photo_

Figura 2. Ajustes del asesor.

OriginalTest

Figura 3. Resultados con la auto-optimización desactivada.

SelfOpt

Figura 4. Resultados con la auto-optimización activada.


Conclusiones

En este artículo hemos analizado el principio de organización de la auto-optimización en un asesor. Este método es muy sencillo y requiere una intervención mínima en el código fuente del asesor. Para cada estrategia específica, le recomendamos realizar una serie de experimentos para determinar las longitudes óptimas de los segmentos históricos sobre los que optimizar y negociar. Estos valores son individuales y dependerán de la estrategia.

Debemos comprender que la optimización no puede conducir a resultados positivos si la propia estrategia no posee conjuntos rentables. No podemos extraer oro de la arena si no se encuentra ahí. La optimización es una herramienta útil a la hora de mejorar el rendimiento de las estrategias, pero no puede crear conjuntos rentables donde no los hay. Por ello, deberemos desarrollar inicialmente una estrategia que tenga potencial de beneficio y luego ya utilizar la optimización para mejorarla.

Las ventajas de este enfoque son la posibilidad de validar la estrategia usando datos históricos mediante pruebas walk-forward y de encontrar criterios de optimización adecuados y apropiados para una estrategia concreta. Las pruebas walk-forward permiten evaluar la eficacia de una estrategia partiendo de los datos históricos y considerando los cambios en las condiciones del mercado a lo largo del tiempo. Esto ayuda a evitar la sobreoptimización, en la que una estrategia solo funciona bien durante un cierto periodo histórico, pero no puede aplicarse con éxito en tiempo real. Así pues, las pruebas walk-forward ofrecen una evaluación más sólida del rendimiento de la estrategia.

El walk-forward testing (WFT) es una técnica de evaluación y ensayo de estrategias comerciales en los mercados financieros. Se usa para determinar la eficacia y sostenibilidad de las estrategias comerciales sobre datos históricos y su capacidad para ofrecer rentabilidad en el futuro.

La idea básica del WFT consiste en dividir los datos disponibles en varios periodos: un periodo histórico utilizado para desarrollar y afinar la estrategia (periodo de entrenamiento) y varios periodos posteriores utilizados para evaluar y validar la estrategia (periodos de prueba). El proceso se repite varias veces, adelantando cada vez un paso el periodo de entrenamiento y adelantando también el periodo de prueba. Así, la estrategia se pone a prueba en distintos horizontes temporales para ver si puede adaptarse a las condiciones del mercado, siempre cambiantes.

Walk-forward es una forma más realista de evaluar las estrategias porque tiene en cuenta la amplia variedad de condiciones del mercado a lo largo del tiempo. También ayuda a evitar el sobreentrenamiento de una estrategia con datos históricos y ofrece una imagen más precisa de su rendimiento en el mundo real.

En el archivo adjunto a este artículo encontrará ejemplos que muestran cómo conectar los algoritmos de optimización a un asesor. Podrá estudiar el código y aplicarlo a su estrategia específica para lograr resultados óptimos.

Este ejemplo no ha sido pensado para negociar en cuentas reales, ya que faltan las comprobaciones necesarias, y solo pretende demostrar las posibilidades de la auto-optimización.

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

Archivos adjuntos |
Equilibrio de riesgos en la negociación simultánea de varios instrumentos comerciales Equilibrio de riesgos en la negociación simultánea de varios instrumentos comerciales
Este artículo permitirá a los principiantes escribir desde cero la implementación de un script para el equilibrio de riesgos en la negociación simultánea de varios instrumentos comerciales, mientras que los usuarios experimentados podrán obtener nuevas ideas para la implementación de sus soluciones en cuanto a las opciones propuestas en este artículo.
Previsión y apertura de órdenes basadas en aprendizaje profundo (Deep Learning) con el paquete Python MetaTrader 5 y el archivo modelo ONNX Previsión y apertura de órdenes basadas en aprendizaje profundo (Deep Learning) con el paquete Python MetaTrader 5 y el archivo modelo ONNX
El proyecto consiste en utilizar Python para realizar previsiones basadas en el aprendizaje profundo en los mercados financieros. Exploraremos los entresijos de la comprobación del rendimiento del modelo utilizando métricas clave como el error medio absoluto (MAE, Mean Absolute Error), el error medio cuadrático (MSE, Mean Squared Error) y R-cuadrado (R2), y aprenderemos a envolverlo todo en un ejecutable. También haremos un fichero modelo ONNX con su EA.
Redes neuronales: así de sencillo (Parte 73): AutoBots para predecir la evolución de los precios Redes neuronales: así de sencillo (Parte 73): AutoBots para predecir la evolución de los precios
Seguimos hablando de algoritmos para entrenar modelos de predicción de trayectorias. En este artículo nos familiarizaremos con un método llamado "AutoBots".
Desarrollamos un Asesor Experto multidivisas (Parte 2): Transición a posiciones virtuales de estrategias comerciales Desarrollamos un Asesor Experto multidivisas (Parte 2): Transición a posiciones virtuales de estrategias comerciales
Hoy continuaremos con el desarrollo de un asesor multidivisa con varias estrategias funcionando en paralelo. Intentaremos transferir todo el trabajo relacionado con la apertura de posiciones de mercado desde el nivel de las estrategias al nivel de un experto que gestiona estas. Las propias estrategias solo negociarán virtualmente, sin abrir posiciones de mercado.