English Русский 中文 Deutsch 日本語 Português
preview
Teoría de Categorías en MQL5 (Parte 17): Funtores y monoides

Teoría de Categorías en MQL5 (Parte 17): Funtores y monoides

MetaTrader 5Sistemas comerciales | 31 enero 2024, 10:40
236 1
Stephen Njuki
Stephen Njuki

Introducción

Continuamos analizando la teoría de categorías aplicada a los funtores. Hasta ahora, hemos visto la aplicación de la teoría de categorías en la implementación de ejemplares personalizados de la clase de trailing Expert y de la clase Expert Signal. En este artículo, veremos las aplicaciones que usan la clase Expert Money. Todas estas clases se suministran con Meta Editor IDE y se utilizan con el wizard MQL5 para construir asesores minimizando la escritura de código.

El tamaño de la posición es una de las cuestiones más importantes a la hora de diseñar un sistema comercial. Los resultados obtenidos durante las pruebas preliminares de cualquier asesor son muy sensibles a este parámetro. Con frecuencia, resulta recomendable eliminarlo por completo (utilizar margen fijo o tamaño de lote fijo) o, si lo necesitamos, añadirlo al final cuando ya tengamos una señal de entrada adecuada que esté bien equilibrada con nuestros métodos de salida. A pesar de ello, intentaremos establecer el tamaño ideal de la posición basándonos en el stop loss previsto en un ejemplar personalizado de la clase Expert Money.

Los funtores son un puente entre las categorías, que captan no solo las diferencias entre los objetos de cada categoría, sino también las diferencias en sus morfismos. Ya hemos visto cómo esta información recopilada puede utilizarse para predecir cambios en la volatilidad y las tendencias del mercado observando las categorías representadas en forma de gráficos y órdenes lineales. Los grafos de completitud (recall graphs) y los órdenes lineales no son categorías en sí mismas, pero los hemos visto como tales porque estaban presentes axiomas clave de las categorías.

Los funtores se implementaron usando ecuaciones lineales simples en los artículos 14 y 15, donde solo se requería el coeficiente de inclinación para el mapeo, además del desplazamiento en el eje Y para definir los objetos y morfismos de la categoría del codominio. A partir del artículo 16, empezamos a analizar los funtores como una red neuronal simple llamada perceptrón multicapa. Esta red, basada en los trabajos de Warren Sturgis McCulloch y Walter Pitts, aproxima cualquier función continua de -1 a +1, y también durante cierto tiempo reproduce cualquier disyunción exclusiva (XOR) si es multicapa.

En este artículo, resumiremos nuestro trabajo con los funtores y veremos cómo, en combinación con los monoides y el preorden de precios de los activos comerciales, podemos formular un sistema para establecer el tamaño de las posiciones al negociar con un valor, que sería BTCUSD (bitcoin). La última vez que vimos los monoides, su principal aplicación en el comercio consistía en categorizar los pasos comerciales para tomar decisiones basadas en las operaciones de cada monoide. Recordemos que un monoide es un conjunto, una operación binaria y un elemento unitario. Por lo tanto, hemos creado monoides para cada uno de los pasos condicionales a los que se enfrenta un tráder cuando decide abrir una posición.


Funtores y monoides en el trading

Los objetos (llamados dominios en artículos anteriores) son las células o bloques de construcción de las categorías. Dentro de una categoría, los objetos tienen correspondencias entre sí, que se denominan morfismos, y al igual que los morfismos enlazan objetos; los funtores enlazan categorías.

Así pues, el vínculo entre categorías que ofrecen los funtores ha demostrado su utilidad a la hora de realizar previsiones, ya que la categoría de codominio representa la serie temporal cuya previsión consideramos aquí. En nuestro último artículo, era información acerca de si debíamos abrir una posición larga o corta en el S&P 500.

Sin embargo, a diferencia del artículo anterior, donde los funtores conectaban grafos con órdenes lineales, tendremos monoides como categoría de dominio. Como ya hemos dicho, utilizaremos monoides como puntos de decisión en cada etapa de la transacción. Estos pasos, que por supuesto difieren de los usados por otros tráders debido a las diferencias en la estrategia, consisten en seleccionar un marco temporal, seleccionar un periodo de análisis retrospectivo, seleccionar el precio a aplicar y seleccionar un indicador que incorpore el marco temporal, el periodo de análisis retrospectivo y el precio a aplicar previamente seleccionados. La elección final se ha realizado para la acción comercial, es decir, si, dado el valor del indicador, seguimos la tendencia indicada o abrimos una posición contraria. Así, se han codificado monoides para cada una de estas soluciones, y la operación binaria de cada monoide se ha encargado de seleccionar el valor apropiado de cada conjunto tras enumerar todos los valores del conjunto.


Funtores como perceptrones multicapa (MLP)

Nunca insistiremos lo suficiente en el papel del perceptrón multicapa en el trading. El simple volumen de artículos sobre redes neuronales demuestra claramente que cada vez son más indispensables. La mayoría de los tráders lo utilizan para predecir las próximas tendencias de los precios, lo cual está plenamente justificado. No obstante, en mi opinión, a menudo pasan por alto la elección (y posiblemente la regularización) de los datos de entrada para la red. Supongamos que queremos pronosticar una serie temporal, pero ¿en qué flujo de datos basaremos nuestra previsión y por qué? Esto quizá no parezca tan importante, pero puede ser una de las razones por las que muchas redes entrenadas con grandes cantidades de datos no funcionan tan bien como en las pruebas.

Por lo tanto, dado que en el artículo 9 y en varios otros hemos utilizado monoides para tomar decisiones en cada paso de nuestro sistema comercial básico, haremos lo mismo en este artículo, con la única diferencia de que omitiremos el último paso. Nos quedan cuatro pasos. El quinto paso restante, en el que el monoide establece si seguirá la tendencia o comerciará en contra de ella, no resultará relevante aquí. A continuación, nuestro perceptrón multicapa (MLP) utilizará cada uno de los cuatro valores de salida monoidales restantes como entradas. El resultado objetivo para el que entrenaremos nuestro MLP será el punto de stop loss ideal para una posición abierta. El tamaño de esta brecha del stop loss será inversamente proporcional a los lotes comerciados en la posición, por lo que nos servirá como indicador clave del tamaño de nuestra posición.

Por lo tanto, así es como dimensionaríamos una posición basándonos en la brecha de stop loss pronosticada:

         //output from MLP forecast
         double _stoploss_gap=_y_output[0];
         
         //printf(__FUNCSIG__+" ct call: "+DoubleToString(_stoploss_gap));
      
         sl=m_symbol.Ask()+fabs(_stoploss_gap);
         
         //--- select lot size
         double _ct_1_lot_loss=(_stoploss_gap/m_symbol.TickSize())*m_symbol.TickValue();
         double lot=((m_percent/100.0)*m_account.FreeMargin())/_ct_1_lot_loss;
         
         //--- calculate margin requirements for 1 lot
         if(m_account.FreeMarginCheck(m_symbol.Name(),ORDER_TYPE_SELL,lot,m_symbol.Bid())<0.0)
         {
            printf(__FUNCSIG__" insufficient margin for sl lot! ");
            lot=m_account.MaxLotCheck(m_symbol.Name(),ORDER_TYPE_SELL,m_symbol.Bid(),m_percent);
         }
         
         //--- return trading volume
         return(Optimize(lot));

En primer lugar, calcularemos la pérdida del lote o el valor en dólares de la reducción en la que se incurre cuando una posición de un lote se abre en negativo sobre la brecha de stop loss prevista. Esta será la brecha dividida por el tamaño del tick multiplicada por el valor del tick. Si tomamos la distribución porcentual de margen m_percent normalmente asignada a una nueva posición como el porcentaje máximo de reducción permitido para una posición abierta, entonces nuestros lotes serán ese porcentaje dividido por 100, multiplicado por nuestro margen libre y dividido por la pérdida del lote. En otras palabras, ¿cuántas pérdidas por lote podremos sufrir con nuestro importe máximo de reducción?


Operaciones monoidales para el dimensionamiento de posiciones

Así, en los artículos anteriores, la toma de decisiones de cada monoide era realizada por una única función Operate, y dependiendo del método de operación para el monoide de entrada, implicaba el muestreo a partir de los valores del conjunto de monoides. Los métodos de operación usados en nuestro caso eran muy elementales. De los 6, solo utilizábamos 4, porque la suma y la multiplicación exigían que el cero y el uno estuvieran siempre presentes en los conjuntos de monoides, lo cual no era posible en nuestro caso. El código que destaca esta enumeración y función tiene el aspecto que sigue:

//+------------------------------------------------------------------+
//| Enumeration for Monoid Operations                                |
//+------------------------------------------------------------------+
enum EOperations
  {
      OP_FURTHEST=5,
      OP_CLOSEST=4,
      OP_MOST=3,
      OP_LEAST=2,
      OP_MULTIPLY=1,
      OP_ADD=0
  };


//+------------------------------------------------------------------+
//|   Operate function for executing monoid binary operations        |
//+------------------------------------------------------------------+
void CMoneyCT::Operate(CMonoid<double> &M,EOperations &O,int IdenityIndex,int &OutputIndex)
   {
      OutputIndex=-1;
      //
      double _values[];
      ArrayResize(_values,M.Cardinality());ArrayInitialize(_values,0.0);
      //
      for(int i=0;i<M.Cardinality();i++)
      {
         m_element.Let();
         if(M.Get(i,m_element))
         {
            if(!m_element.Get(0,_values[i]))
            {
               printf(__FUNCSIG__+" Failed to get double for 1 at: "+IntegerToString(i+1));
            }
         }
         else{ printf(__FUNCSIG__+" Failed to get element for 1 at: "+IntegerToString(i+1)); }
      }
      
      //
      
      if(O==OP_LEAST)
      {
         ...
      }
      else if(O==OP_MOST)
      {
         ...
      }
      else if(O==OP_CLOSEST)
      {
         ...
      }
      else if(O==OP_FURTHEST)
      {
         ...
      }
   }

Bien, puesto que ahora estamos interesados en el tamaño de la posición y no en la volatilidad de la predicción de las tendencias del mercado, el monoide final "acción" y su solución podrá ser sustituido por un functor. Así, los cuatro primeros monoides se ejecutarán para ayudar a determinar el valor del indicador y determinar el tamaño de la posición. Nos ceñiremos a los indicadores RSI y a las Bandas de Bollinger regularizadas para obtener un valor similar a RSI entre 0 y 100. Así, aunque este valor del indicador sea el resultado de los tres resultados anteriores del monoide, se combinará con ellos para crear un conjunto de cuatro valores que formarán los datos de entrada de nuestro perceptrón multicapa. Por lo tanto, los datos de entrada sobre el marco temporal y el precio aplicado tendrán que ser convertidos a un formato numérico que pueda ser procesado por un MLP, como hicimos en el artículo anterior.

Así que (repetimos nuevamente), un monoide, que es simplemente un conjunto, una operación binaria y un elemento unitario, simplemente permitirá seleccionar un elemento de ese conjunto definido por una operación binaria. Seleccionando el marco temporal, el periodo de análisis retrospectivo y el precio aplicado, obtendremos los datos de entrada para nuestro indicador, cuyo valor normalizado (0-100) servirá como cuarto valor de entrada en el MLP.

En un artículo anterior describimos los pasos necesarios para seleccionar el marco temporal, el periodo de análisis retrospectivo, el precio aplicado y el indicador utilizado. Adjuntamos el código actualizado. No obstante, a continuación le mostraremos cómo obtener los datos de salida de cada una de las funciones correspondientes:

         ENUM_TIMEFRAMES _timeframe_0=SetTimeframe(m_timeframe,0);
         int _lookback_0=SetLookback(m_lookback,_timeframe_0,0);
         ENUM_APPLIED_PRICE _appliedprice_0=SetAppliedprice(m_appliedprice,_timeframe_0,_lookback_0,0);
         double _indicator_0=SetIndicator(_timeframe_0,_lookback_0,_appliedprice_0,0);


Integración de funtores y monoides para el dimensionamiento de posiciones complejas

Para usar correctamente nuestro MLP, necesitaremos entrenarlo de la forma adecuada. En el último artículo, el entrenamiento se realizó durante la inicialización; asimismo, se cargaron los pesos de red más ventajosos (si estaban disponibles para la configuración de la red seleccionada) y se utilizaron como punto de partida del entrenamiento para configurar los pesos. En este artículo, no se realizará ningún preentrenamiento antes o durante la inicialización. En cambio, la red se entrenará con cada nueva barra. Esto no quiere decir que este sea el enfoque correcto: simplemente es una demostración de las muchas opciones que el usuario tiene a la hora de entrenar un MLP o una red. No obstante, como los pesos iniciales son siempre aleatorios, los mismos ajustes del asesor producirán inevitablemente resultados muy diferentes en distintas pruebas. Para evitar este problema, los pesos de una ejecución de prueba rentable se escribirán en un archivo y, al inicio de la siguiente ejecución con una configuración de red (un número de elementos en la capa oculta) similar, estos pesos se cargarán y servirán como pesos iniciales en la ejecución de prueba. Las funciones de lectura y escritura en la red son propietarias, por lo que aquí solo ofreceremos referencias a su biblioteca. Es ya tarea del lector implementar la suya propia.

Esta es la sinergia de los monoides y los MLP. Cualquiera de ellos puede crear predicciones sobre la distancia del stop por sí mismo. Hablando de una forma ideal, esto requeriría un control para la verificación, lo cual significa que necesitaríamos asesores aparte que implementen solo monoides y solo MLP y comparen los tres conjuntos de resultados. Por desgracia, esto no resultará posible en este artículo, pero adjuntamos el código fuente que demuestra ambas ideas, para que el lector pueda investigar y verificar (¿o refutar?) esta idea de sinergia.

Así, el código que combina estos dos componentes tendrá el aspecto que sigue:

      m_open.Refresh(-1);
      m_high.Refresh(-1);
      m_close.Refresh(-1);
      
      CMLPTrain _train;
      
      int _info=0;
      CMLPReport _report;
      CMatrixDouble _xy;_xy.Resize(1,__INPUTS+__OUTPUTS);
      
      _xy[0].Set(0,RegularizeTimeframe(_timeframe_1));
      _xy[0].Set(1,_lookback_1);
      _xy[0].Set(2,RegularizeAppliedprice(_appliedprice_1));
      _xy[0].Set(3,_indicator_1);
      //
      int _x=StartIndex()+1;
      
      double _sl_1=m_high.GetData(_x)-m_low.GetData(_x);
      
      if(m_open.GetData(_x)>m_close.GetData(_x))
      {
         _sl_1=m_high.GetData(_x)-m_open.GetData(_x);
      }
      
      double _stops=(2.0*(m_symbol.Ask()-m_symbol.Bid()))+((m_symbol.StopsLevel()+m_symbol.FreezeLevel())*m_symbol.Point());
      
      _xy[0].Set(__INPUTS,fmax(_stops,_sl_1));
      
      _train.MLPTrainLM(m_mlp,_xy,1,m_decay,m_restarts,_info,_report);


Ejemplo: Aplicación práctica y pruebas con la historia

Para analizar nuestro método de determinación del tamaño de una posición compuesta, utilizaremos bitcoins (BTCUSD) como valor de prueba. Las pruebas con optimización se realizarán durante el periodo comprendido entre el 1 de enero de 2020 y el 1 de agosto de 2023 en un marco temporal diario. Además de optimizar para conseguir el número ideal de pesos en la capa oculta (ya que utilizamos MLP con una sola capa oculta), también intentaremos ajustar los cuatro monoides que utilizamos para obtener el tamaño de posición. Esto significa que intentaremos establecer el elemento de identidad y el tipo de operación ideales para cada uno de los cuatro monoides usados para determinar el tamaño de nuestra posición. Nuestro análisis se centrará únicamente en el tamaño de la posición, lo cual significa que la señal del asesor que utilizaremos deberá ser una de las proporcionadas en la biblioteca MQL5. Para ello, usaremos la clase RSI de señales expertas. No implementaremos trailing stops, y como siempre, al igual que en todas las pruebas anteriores, no utilizaremos valores de take profit o stop loss, por lo que los parámetros take level y stop level serán iguales a cero. Sin embargo, nuestro asesor estará abierto a usar órdenes pendientes, por lo que el parámetro de nivel de precio también se optimizará como antes.

Realizaremos las pruebas con el functor objeto y el functor de morfismos. Los resultados se muestran a continuación:

r_1


r_2

Para cualquier valor de entrada dado, como la ejecución de antes con morfismos de objetos, los resultados no serán necesariamente reproducibles, ya que estamos asignando pesos aleatorios a cada MLP en la inicialización. Sirve de ayuda que podamos cargar los pesos de la ejecución rentable anterior en la inicialización para no tener que empezar cada vez de cero, pero aún así, como el entrenamiento se realiza en cada nueva barra, acabaremos ajustando los pesos del MLP rentable, y esto significa que no obtendremos los mismos resultados. Por ello, animamos al lector a escribir un método adaptado a su estrategia que registre y lea cuidadosamente los pesos de sus mejores pruebas.

Si como control ejecutamos el asesor con la misma señal RSI y con la misma señal sin trailing stop, pero con un método diferente de dimensionamiento de la posición, utilizando un margen fijo, obtendremos los siguientes resultados:

r_ctrl

A juzgar por los resultados, nuestros sistemas tendían a dar mejores resultados incluso sin pruebas exhaustivas. (La optimización se ha interrumpido, ya que el objetivo era únicamente mostrar el potencial). Sin embargo, como hemos comentado al principio de este artículo, los aspectos de dimensionamiento de la posición del sistema suelen encontrarse al final de la lista de prioridades para la mayoría de los tráders, ya que tienen un gran impacto en los resultados de las pruebas, pero en sentido estricto no significan nada si el sistema utiliza una señal de entrada estable.


Limitaciones y matices

Vamos a intentar destacar algunos de los problemas y limitaciones potenciales del dimensionamiento de posiciones basado en funtores. En primer lugar, la obtención de conjuntos de monoides que funcionen con la estrategia del usuario y la creación de modelos de aprendizaje de MLP adecuados requieren un entrenamiento prolongado. Ambos resultan fundamentales para poner en práctica la estrategia que aquí presentamos y llevará bastante tiempo dominarlos.

En segundo lugar, las brechas de precio o los valores cuyos datos OHLC de un minuto no coincidan con los datos reales de ticks no ofrecerán configuraciones de ponderaciones de monoides o MLP fiables al trabajar en una cuenta real (considerando el movimiento hacia adelante). Las razones son obvias, pero este es el principal escollo a la hora de poner en práctica las ideas aquí expuestas.

En tercer lugar, el sobreajuste y la generalización suponen otro problema propio de los MLP. Ya he insinuado esto antes, señalando la importancia del conjunto de datos de la capa de entrada, y mi opinión es que una solución sería utilizar datos regularizados significativos. "Significativo" en el sentido de que existen aspectos fundamentales creíbles del conjunto de datos de la capa de entrada que cabe esperar que influyan en la predicción que nos interesa.

También está la cuestión del ajuste de los parámetros, que algunos podrían decir que se relaciona con el punto anterior, pero yo diría que va más allá si consideramos la cantidad de recursos informáticos implicados y el tiempo necesario para alcanzar nuestros parámetros objetivo.

La escasa interpretabilidad y transparencia de los sistemas desarrollados con MLP es otro problema que los programadores deben considerar. Si tenemos un sistema que funciona y queremos empezar a atraer inversores, estos suelen exigir que revelemos más información sobre cómo funciona nuestro sistema gracias a los MLP. Dependiendo de los niveles de nuestra red y de su complejidad, esto puede ser una tarea desalentadora. Existen otros aspectos, como el preprocesamiento de datos, que es algo obligatorio para los MLP, ya que siempre necesitan cargar los pesos, cambiar el modo de mercado, actualizaciones y mantenimiento del modelo, etc. Es necesario tener en cuenta todos estos factores y desarrollar medidas adecuadas para abordarlos conforme se produzcan.

De hecho, podría argumentarse que lo dicho sobre el cambio de modo del mercado confirma la ventaja de los enfoques comerciales tradicionales, como el trading manual. Creo que todavía no existe una respuesta inequívoca, pues los sistemas actuales se están desarrollando y probando con grandes conjuntos de datos históricos que abarcan una amplia gama de modos de mercado.


Conclusión y perspectivas

Hoy hemos mostrado cómo se puede implementar en MQL5 una nueva visión del monoide como categoría. Asimismo, hemos demostrado que esta implementación puede resultar útil para determinar el tamaño de la posición de un sistema comercial, que en nuestro caso se basa en el indicador RSI para las señales de entrada y salida.

La importancia del uso de funtores y monoides como herramientas de dimensionamiento de posiciones implica que podemos hacer lo mismo con otros aspectos del sistema comercial, como las señales de entrada o, como suele ocurrir en estas series, con la colocación y el ajuste de un trailing stop.

Como señalamos al final, aún queda trabajo que hacer y obstáculos por superar antes de que los tráders puedan utilizar plenamente las ideas aquí presentadas, por lo que animamos a los lectores a explorar y experimentar con enfoques basados en funtores a la hora de desarrollar sus sistemas comerciales.


Enlaces

El artículo ofrece los hipervínculos necesarios a los artículos de la Wikipedia.


Aplicación: Fragmentos de código MQL5

Ponga el archivo MoneyCT_17_.mqh en MQL5\include\Expert\Money\, y ct_9.mqh en la carpeta Include.

Quizá le resulten útiles las recomendaciones mostradas aquí sobre cómo crear un asesor utilizando el Wizard. Como se indica en el artículo, hemos utilizado el oscilador RSI como señal de entrada. No hemos usado trailing stops. Como siempre, en este artículo no pretendemos presentarle el Grial, sino ofrecerle una idea que pueda adaptar a su propia estrategia. Los archivos *.*mq5 se han recopilado en el Wizard. Podrá compilarlos o crearlos por sí mismo. El archivo ct_17_control se ha construido con un margen fijo.


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

Archivos adjuntos |
ct_17.mq5 (9.25 KB)
ct_9.mqh (65.06 KB)
MoneyCT_17_.mqh (36.29 KB)
Hilario Miguel Ofarril Gonzalez
Hilario Miguel Ofarril Gonzalez | 6 feb. 2024 en 15:44
Exelente teoría. Me gusta tus avances .
Pruebas de permutación de Monte Carlo en MetaTrader 5 Pruebas de permutación de Monte Carlo en MetaTrader 5
En este artículo echaremos un vistazo a cómo podemos realizar pruebas de permutación sobre la base de datos de ticks barajados en cualquier asesor experto utilizando solo MetaTrader 5.
Redes neuronales: así de sencillo (Parte 54): Usamos un codificador aleatorio para una exploración eficiente (RE3) Redes neuronales: así de sencillo (Parte 54): Usamos un codificador aleatorio para una exploración eficiente (RE3)
Siempre que analizamos métodos de aprendizaje por refuerzo, nos enfrentamos al problema de explorar eficientemente el entorno. Con frecuencia, la resolución de este problema hace que el algoritmo se complique, llevándonos al entrenamiento de modelos adicionales. En este artículo veremos un enfoque alternativo para resolver el presente problema.
Aproximación por fuerza bruta a la búsqueda de patrones (Parte VI): Optimización cíclica Aproximación por fuerza bruta a la búsqueda de patrones (Parte VI): Optimización cíclica
En este artículo mostraremos la primera parte de las mejoras que nos permitieron no solo cerrar toda la cadena de automatización para comerciar en MetaTrader 4 y 5, sino también hacer algo mucho más interesante. A partir de ahora, esta solución nos permitirá automatizar completamente tanto el proceso de creación de asesores como el proceso de optimización, así como minimizar el gasto de recursos a la hora de encontrar configuraciones comerciales efectivas.
Comprobando la informatividad de distintos tipos de medias móviles Comprobando la informatividad de distintos tipos de medias móviles
Todos conocemos la importancia de la media móvil para muchos tráders. Existen diferentes tipos de medias móviles que pueden resultar útiles en el trading. Vamos a echarles un vistazo y a hacer una sencilla comparación para ver cuál puede dar mejores resultados.