English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Usar Pseudo-Plantillas como Alternativa a Plantillas C++

Usar Pseudo-Plantillas como Alternativa a Plantillas C++

MetaTrader 5Ejemplos | 25 marzo 2014, 14:48
1 383 0
Mykola Demko
Mykola Demko


Introducción

La cuestión de las plantillas de implementación somo un estándar del lenguage se ha tratado varias veces en el foro mql5.com. Al enfrentarme a la barrera de rechazo de los desarrolladores de MQL5, mi interés en la implementación de plantillas usando métodos personalizados comenzó a crecer. El resultado de mis estudios se presenta en este artículo.


Un poco de Historia de C yC++

Desde el principio, el lenguaje C se desarrolló para ofrecer la posibilidad de llevar a cabo tareas de sistema. Los creadores del lenguaje C no implementaron un modelo abstracto de entorno de ejecucion del lenguaje; simplemente implementaron elementos para las necesidades de programadores de sistema. En primer lugar, estos son los métodos de trabajo directo con memoria, construcciones de estructura de aplicaciones de contro y de gestión de módulo.

En realidad, no se incluyó nada más en el lenguaje; el resto de elementos se llevaron a la biblioteca de ejecución. Por eso, algunas personas a veces llaman al lenguaje C "ensamblador estructurado" burlonamente. Pero independientemente de lo que digan, el enfoque pareció ser muy exitoso. Gracias a él se llegó a un nuevo nivel de equilibrio entre simplicidad y capacidad del lenguaje. 

De modo que C apareció como un lenguaje universal para programación de sistema. Pero no se quedó dentro de estos límites. A finales de los 80, cuando Fortran dejó el liderazgo, C ganó gran popularidad entre programadores en todo el mundo, y pasó a ser muy usado en diversas aplicaciones. Una significativa contribución a su popularidad vino con la distribución de Unix (y por tanto, el lenguaje C) en las universidades, donde se formó a una nueva generación de programadores.

Pero si todo está tan claro, ¿por qué existen todavía todos los demás lenguajes, y qué justifica su existencia? El talón de Aquiles del lenguaje C es que tiene un nivel demasiado bajo para los problemas que surgieron en los años 90. El problema tiene dos aspectos.

En primer lugar, el lenguaje contiene medios de nivel demasiado bajo: por una parte, el trabajo con memoria y aritmética de dirección. Esto hace que un cambio en el bitness del procesador cause muchos problemas para muchas aplicaciones C. Por otra parte, hay una falta de medios de alto nivel en C: tipo de datos y objetos abstractos, poliformismo y gestión de excepciones. Por tanto, en las aplicaciones C, una técnica de implementación de una tarea a menudo resulta dominante sobre su lado material.

Los primeros intentos para arreglar estas desventajas se hicieron a principio de los 80. En aquel momento, Bjarne Stroustrup, en los laboratorios AT&T Bell, comenzó a desarrollar la extensión del lenguaje C llamado "C con clases". El estilo del desarrollo se correspondía con el espíritu de la creación del lenguaje C mismo: añadir divesos elementos para hacer el trabajo de ciertos grupos de gente más conveniente.

La principal motivación en C++ es el mecanismo de clases, que da la posibilidad de usar nuevos tipos de datos. Un programador describe la representación interna de objeto de clase y el conjunto de métodos-funciones para acceder esa representación. Uno de los principales propósitos de la creación de C++ fue incrementar la proporción de reutilización de código ya escrito.

La innovación del lenguaje C++ no solo consiste en la introcucción de clases. Ha implementado un mecanismo de gestión estructural de excepciones (la falta de este mecanismo antes complicaba el desarrollo de aplicaciónes a prueba de fallos), un mecanismo de plantillas y muchas otras cosas. Por tanto, la línea de desarrollo principal del lenguaje se centró en extender sus posibilidades introduciendo nuevas construcciones de alto nivel manteniendo la compatibilidad total con ANSI С


Plantilla como Mecanismo de Macro-Sustitución

Para entender cómo se implementa una plantilla en MQL5, debe entender cómo funcionan en C++.

Veamos la definición.

Las plantillas con un elemento del lenguaje de programación C++ que permite el trabajo de funciones y operaciones con tipos genéricos. Esto permite que una función o clase funciones con muchos tipos de datos diferentes sin que tenga que ser reescrito para cada uno de ellos.

MQL5 no tiene plantillas, pero esto no significa que sea imposible usar un estilo de programación con plantillas. El mecanismo de plantillas en el lenguaje C++ es, enrealidad, un sofisticado mecanismo de macro-generación profundamente incrustado en el lenguaje. En otras palabras, cuando un programador usa una plantilla, el compilador determina el tipo de datos donde se llama a la función correspondiente, no donde se declara.

Las plantillas se introdujeron en C++ para disminuir la cantidad de código escrito por programadores. Pero no olvide que un código escrito en un teclado por un programador no es igual al código creado por un compilador. El mecanismo de las plantillas en sí mismo no resultó en la disminución del tamaño de los programas; simplemente disminuyó el tamaño de sus códigos fuente. Por ello, el mayor problema que se solucionó con el uso de plantillas fue la disminución del código escrito por los programadores.

Puesto que el código automático se genera durante la compilación, los programadores ordinarios no ven si el código de una función se genera una o varias veces. Durante la compilación de un código de plantilla, el código de una función se genera tantas veces como tipos haya en los que se use la plantilla. Básicamente, una plantilla invalida todo lo demás en la fase de la compilación.

El segundo aspecto de la introducción de plantillas en C++ es la distribución de memoria. En el lenguaje C, la memoria se distribuye de forma estadística. Para hacer esta distribución más flexible, se usa una plantilla que configura el tamaño de la memoria para arrays. Pero este aspecto lo implementaron desarrolladores de MQL4 en forma de arrays dinámicos, y también se hizo en MQL5 en forma de objetos dinámicos.

Por tanto, tan solo queda por resolver el problema de la sustitución de tipos. Los desarrolladores de MQL5 se negaron a resolverlo, argumentando que el uso del mecanismo sustitución de plantillas llevaría a la ruptura del compilador, lo que provocaría la aparición de un descompilador.

Bueno, ellos son los expertos. Y nosotros solo tenemos una opción: implementar este paradigma de forma personalizada.

En primer lugar, permítanme señalar que no vamos a cambiar el compilador ni los estándares del lenguaje. Yo sugiero cambiar el enfoque de las plantillas mismas. El hecho de que no podamos crear plantillas en la fase de compilación no significa que no se nos permita escribir el código automático Yo sugiero mover el uso de plantillas de la parte de la generación del código binario a la parte donde se escribe el código de texto. Llamemos a esto "pseudo-plantillas".


Pseudo-Plantillas

Una pseudo-plantilla tiene sus ventajas y sus desventajas en comparación con una plantilla C++. Las desventajas incluyen manipulaciones adicionales con el movimiento de archivos. Las ventajas incluyen posibilidades más flexibles que las determinadas por los estándares del lenguaje. Pasemos de las palabras a las acciones.

Para usar pseudo-plantillas, necesitaremos un análogo de preprocesador. Para ello, nosotros usaremos el script "Plantillas". Aquí están los requisitos generales para el script: debe leer un archivo específico (manteniendo la estructura de datos), encontrar una plantilla y sustituirla con tipos específicos.

Aquí debo hacer un comentario. Puesto que vamos a usar el mecanismo de invalidación en lugar de plantillas, el código se reescribirá tantas veces como tipos haya que deban ser anulados. En otras palabras, la sustitución se llevará a cabo en el código entero seleccionado para el análisis. Depués, el código se reescribirá varias veces durante el script, creado cada vez una nueva sustitución. Por tanto, aquí se entiende el dicho "trabajo manual llevado a cabo por máquinas".


Desarrollar el Código de Script

Determinemos las variables de entrada requeridas:

  1. Nombre del archivo a procesar.
  2. La variable para almacenar el tipo de datos que debe ser anulada.
  3. Nombre de la plantilla que se usará en lugar de los tipos de datos reales.
input string folder="Example templat";//name of file for processing

input string type="long;double;datetime;string"
                 ;//names of custom types, separator ";"
string TEMPLAT="_XXX_";// template name

Para hacer que el script multiplique solo una parte del código, configure los nombres de los marcadores. El marcador de apertura se hace para indicar el comienzo de la parte a procesar, y el marcador de cierre para indicar su final.

Al usar el script, me encontré con el problema de lectura de los marcadores.

Durante el análisis descubrí que, al dar formato a un documento en MetaEditor, a menudo se añade un espacio o tabulación (dependiendo de la situación) a las líneas de comentarios. El problema se resolvió eliminando los espacios antes y después de un símbolo significativo al determinar marcadores. Este elemento se lleva a cabo de forma automática en el script, pero hay algo que debe saber.

El nombre de un marcador no debe empezar o acabar con un espacio.

No es obligatorio poner un marcador de cierre; si no lo hay, el código se procesará hasta el final del archivo. Pero sí debe haber un marcador de apertura. Puesto que los nombres de los marcadores son constantes, yo uso la directiva de preprocesador #define en lugar de variables.

#define startread "//start point"
#define endread "//end point"

Para formar un array de tipos, creé el vacío de función ParserInputType(int i,string &type_d[],string text), que llena el array type_dates[] con valores que usan la variable 'type'.    

Una vez que el scrip recibe el nombre de un archivo y marcadores, comienza a leer el archivo. Para ahorrar el formateo del documento, el script lee la información línea a línea, pasando líneas encontradas en el array.

Por supuesto, puede poner todo en una variable. Pero en este caso, perderá la separación en bloques, y el texto se convertirá en una línea eterna. Por eso, la función de lectura del archivo usa el array de cadenas de caracteres que cambia su tamaño en cada iteración para obtener una nueva cadena de caracteres.

//+------------------------------------------------------------------+
//| downloading file                                                 |
//+------------------------------------------------------------------+
void ReadFile()
  {
   string subfolder="Templates";
   int han=FileOpen(subfolder+"\\"+folder+".mqh",FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI,"\r"); 
   if(han!=INVALID_HANDLE)
     {
      string temp="";
      //--- scrolling file to the starting point
      do {temp=FileReadString(han);StringTrimLeft(temp);StringTrimRight(temp);}
      while(startread!=temp);

      string text=""; int size;
      //--- reading the file to the array until a break point or the end of the file
      while(!FileIsEnding(han))
        {
         temp=text=FileReadString(han);
         // deleting symbols of tabulation to check the end
         StringTrimLeft(temp);StringTrimRight(temp);
         if(endread==temp)break;
         // flushing data to the array
         if(text!="")
           {
            size=ArraySize(fdates);
            ArrayResize(fdates,size+1);
            fdates[size]=text;
           }
        }
      FileClose(han);
     }
   else
     {
      Print("File open failed"+subfolder+"\\"+folder+".mqh, error",GetLastError());
      flagnew=true;
     }
  }

Por motivos de conveniencia, el archivo se abre en modo FILE_SHARE_READ. Nos da la posibilidad de comenzar el script sin cerrar el archivo editado. La extensión de archivo se especifica como 'mqh'. Por tanto, el script lee directamente el texto del código que se guarda en el archivo incluido. En realidad, un archivo con la extensión 'mqh' es un archivo de texto; puede asegurarse de ello simplemente renombrándolo para convertirlo en un archivo 'txt' y abriéndolo usando cualquier editor de texto. 

Al final de la lectura, la longitud del array es igual al número de líneas entre el comienzo y final de los marcadores.

El nombre del archivo abierto debe tener la extensión "templat", de lo contrario el archivo inicial se sobreescribirá, y se perderá toda la información.

Ahora fijémonos en el análisis de información. La función de analiza y sustituye información se llama desde la función de escritura a un vacío de archivo WriteFile(int count). Los comentarios se dan dentro de la función.

void WriteFile(int count)
  {
   ...
   if(han!=INVALID_HANDLE)
     {
      if(flagnew)// if the file cannot be read
        {
         ...
        }
      else
        {// if the file exists
         ArrayResize(tempfdates,count);
         int count_type=ArraySize(type_dates);
         //--- the cycle rewrites the contents of the file for each type of the type_dates template
         for(int j=0;j<count_type;j++)
           {
            for(int i=0;i<count;i++) // copy data into the temporary array
               tempfdates[i]=fdates[i];
            for(int i=0;i<count;i++) // replace templates with types
               Replace(tempfdates,i,j);

            for(int i=0;i<count;i++)
               FileWrite(han,tempfdates[i]); // flushing array in the file
           }
        }
     ...
  }

Puesto que los datos se sustituyen en su lugar y el array cambia tras la transformación, haremos una copia. Aquí configuramos el tamaño del array tempfdates[] usado para el almacenamiento temporal de datos y lo llenamos de acuerdo con el ejemplo fdates[].

Después se lleva a cabo la sustitución de plantillas usando la función Replace(). Los parámetros de la función son: el array que debe ser procesado (donde la sustitución de la plantilla se lleva a cabo), el contador de líneas i (para movernos dentro del array), y el contador de tiposj (para navegar a través del array de tipos).

Puesto que tenemos dos ciclos anidados, el código fuente se imprime tantas veces como tipos se hayan especificado.

//+------------------------------------------------------------------+
//| replacing templates with types                                   |
//+------------------------------------------------------------------+
void Replace(string &temp_m[],int i,int j)
  {
   if(i>=ArraySize(temp_m))return;
   if(j<ArraySize(type_dates))
      StringReplac(temp_m[i],TEMPLAT,type_dates[j]);// replacing  templat with types   
  }

La función Replace() contiene comprobaciones (para evitar la llamada a un índice no existente de un array) y llama a la función anidada StringReplac(). Hay una razón por la que el nombre de la función es similar a la función estándar StringReplace: tienen el mismo número de parámetros.

Por tanto, al añadir una sola letra "e", podemos cambiar la lógica entera de sustitución. La función estándar toma el valor del ejempolo 'find' y lo sustituye con la cadena de caracteres especificada 'sustitución'. Y mi función no solo sustituye, sino que lleva a cabo análisis si hay símbolos antes de 'find' (es decir, comprueba si 'find' es parte de una palabra); y si los hay, sustituye 'find' con 'replacement', pero en mayúsculas; de lo contrario, la sustitución se lleva a cabo tal y como está. Por tanto, además de configurar tipos, puede usarlos en nombre de los datos anulados.


Innovaciones

Ahora, permítame explicarles las innovaciones que se añadieron durante su uso. Ya mencioné que había problemas de lectura de marcadores durante el uso del script.

El problema se solucionó usando el siguiente código dentro de la función ReadFile() vacía:

      string temp="";
      //--- scrolling the file to the start point
      do {temp=FileReadString(han);StringTrimLeft(temp);StringTrimRight(temp);}
      while(startread!=temp);

El ciclo en sí mismo se implementó en la versión anterior, pero eliminando los símbolos de tabulación usando las funciones StringTrimLeft() y StringTrimRight() que aparecieron solo en la versión mejorada.

Además, las innovaciones incluyen la eliminación de la extensión "templat" del nombre del archivo de salida, de modo que el archivo de salida está listo para ser usado. Se implementa usando la función de eliminación de un ejemplo especificado de una cadena de caracteres especificada.

Código de la función de eliminación:

//+------------------------------------------------------------------+
//| Deleting the 'find' template from the 'text' string              |
//+------------------------------------------------------------------+
string StringDel(string text,const string find)
  {
   string str=text;
   StringReplace(str,find,"");
   return(str);
  }

El código que lleva a cabo la eliminación de un nombre de archivo se encuentra en el vacío de función WriteFile(int count):

   string newfolder;
   if(flagnew)newfolder=folder;// if it is the first start, create an empty file of pre-template
   else newfolder=StringDel(folder," templat");// or create the output file according to the template

Además de ello, se introduce el modo de preparación de una pre-plantilla. Si el archivo requerido no existe en el directorio Files/Templates, se formará como un archivo de pre-plantilla.

Ejemplo:

//#define _XXX_ long
 
//this is the start point
 _XXX_
//this is the end point

El código que crea estas líneas se encuentra en la función vacía WriteFile(int count):

      if(flagnew)// if the file couldn't be read
        {// fill the template file with the pre-template
         FileWrite(han,"#define "+TEMPLAT+" "+type_dates[0]);
         FileWrite(han," ");
         FileWrite(han,startread);
         FileWrite(han," "+TEMPLAT);
         FileWrite(han,endread);
         Print("Creating pre-template "+subfolder+"\\"+folder+".mqh");
        }

La ejecución del código está protegida por la variable global flagnew, que toma el valor 'true' si se dio un error al leer el archivo.

Al usar el script, añadí una plantilla adicional. El proceso de conexión de la segunda plantilla es el mismo. Las funciones que requieren cambios en ellas se colocan más cerca de la función OnStart() para la conexión de una plantilla adicional. Hemos conseguido una ruta para conectar nuevas plantillas. Por tanto, tenemos la posibilidad de conectar tantas plantillas como necesitemos. Ahora, comprobemos la operación.


Comprobar la Operación

En primer lugar, empecemos el script especificando todos los parámetros requeridos. En la ventana que aparece, especifique el nombre de archivo "Example templat".

Llene todos los campos de tipos de datos personalizados usando el separador ';'.

Iniciar ventana del script

En cuanto pulse el botón "OK, se creará el directorio "Templates". Contendrá el archivo de pre-plantilla "Example templat.mqh".

Este evento se muestra en el diario con el siguiente mensaje:

Mensajes de diario

Cambiemos la pre-plantilla y empecemos el script de nuevo. Esta vez, el archivo ya existe en el directorio de Templates (así como el directorio mismo), por eso no se mostrará el mensaje sobre el error de apertura del archivo. La sustitución se llevará a cabo de acuerdo con la plantilla especificada:

//this_is_the_start_point
 _XXX_ Value_XXX_;
//this_is_the_end_point

Abra el archivo creado "Example.mqh" de nuevo.

 long ValueLONG;
 double ValueDOUBLE;
 datetime ValueDATETIME;
 string ValueSTRING;

Como puede ver, se han creado 4 líneas a partir de una de acuerdo con el número de tipos que hemos pasado como parámetro. Ahora, escriba las dos siguientes líneas en el archivo de plantilla:

//this_is_the_start_point
 _XXX_ Value_XXX_;
 _XXX_ Type_XXX_;
//this_is_the_end_point

El resultado demuestra la lógica de la operación del script de forma clara.

En primer lugar, el código entero se reescribe con un tipo de dato, y después se lleva a cabo el procesamiento de otro tipo de datos. Esto se hace hasta que todos los tipos se hayan procesado.

 long ValueLONG;
 long TypeLONG;
 double ValueDOUBLE;
 double TypeDOUBLE;
 datetime ValueDATETIME;
 datetime TypeDATETIME;
 string ValueSTRING;
 string TypeSTRING;

Ahora, incluyamos la segunda plantilla en el texto de ejemplo.

//this_is_the_start_point
 _XXX_ Value_XXX_(_xxx_ ind){return((_XXX_)ind);};
 _XXX_ Type_XXX_(_xxx_ ind){return((_XXX_)ind);};

 //this_is_the_end_button

Resultado:

 long ValueLONG(int ind){return((long)ind);};
 long TypeLONG(int ind){return((long)ind);};
 
 double ValueDOUBLE(float ind){return((double)ind);};
 double TypeDOUBLE(float ind){return((double)ind);};
 
 datetime ValueDATETIME(int ind){return((datetime)ind);};
 datetime TypeDATETIME(int ind){return((datetime)ind);};
 
 string ValueSTRING(string ind){return((string)ind);};
 string TypeSTRING(string ind){return((string)ind);};

En el último ejemplo, puse un espacio tras la última línea intencionadamente. Ese especio demuesra dónde el script termina de procesar un tipo y pasa a procesar otro. Respecto a la segunda plantilla, podemos notar que el procesamiento de tipos se lleva a cabo de forma similar a la primera plantilla. Si no se encuentra un tipo correspondiente para un tipo de la primera plantilla, no se imprime nada.

Ahora quiero aclarar la cuestión de la depuración del código. Los ejemplos dados son bastante sencillos para su depuración. Durante la probramación, puede que necesite depurar una buena parte del código y multiplicarlo en cuanto se haya hecho. Para ello, hay una línea comentada reservada en la pre-plantilla: "//#define _XXX_ long".

Si elimina los comentarios, nuestra plantilla se convertirá en un tipo real. En otras palabras, le diremos al compilador cómo se debe interpretar la plantilla.

Desafortunadamente, no podemos depurar todos los tipos de esta forma. Pero podemos depurar un tipo, y después cambiar el tipo de la plantilla en 'define' para poder depurar todos los tipos uno tras otro. Por supuesto, para la depuración, debemos mover el archivo al directorio del archivo llamado o al directorio Include. Este es el inconveniente de la depuración que mencioné antes al hablar de las desventajas de las pseudo-plantillas.


Conclusión

En conclusión, quisiera decir que, aunque la idea de usar pseudo-plantillas es intersante y muy productiva, solo se trata de una idea con un pequeño inicio de su implementación. Aunque el código descrito arriba funciona y me ahorró muchas horas de escritura de código, quedan muchas preguntas por responder. En primer lugar, la cuestión del desarrollo de estándares.

MI script implementa la sustitución de bloques de plantillas. Pero este enfoque no es obligatorio. Puede crear un analizador más complejo que interprete ciertas reglas. Pero aquí está el comienzo. Espero que se abra un buen debate. Las buenas ideas mejoran con el conflicto. ¡Buena suerte!


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

Archivos adjuntos |
templates.mq5 (9.91 KB)
El Papel de las Distribuciones Estadísticas en el Trabajo del Trader El Papel de las Distribuciones Estadísticas en el Trabajo del Trader
Este artículo es una continuación lógica de mi artículo "Statistical Probability Distributions in MQL5" ("Distribuciones de Probabilidad Estadísticas en MQL5"), que presentó las clases para trabajar con algunas distribuciones estadísticas teóricas. Ahora que ya tenemos una base teórica, sugiero proceder directamente a conjuntos de datos reales para darle un uso a esta base.
La implementación del análisis automático de las Ondas de Elliott en MQL5 La implementación del análisis automático de las Ondas de Elliott en MQL5
Uno de los métodos más populares del análisis del mercado es el análisis de las ondas. Sin embargo, este proceso es bastante complejo lo que comporta el uso de herramientas adicionales. Una de estas herramientas es el marcador automático. En este artículo se describe el proceso de creación del analizador automático de las Ondas de Elliott en el lenguaje MQL5.
Filtrar Señales Basadas en Datos Estadísticos de Correlación de Precios Filtrar Señales Basadas en Datos Estadísticos de Correlación de Precios
¿Hay alguna correlación entre el comportamiento de precios del pasado y sus tendencias futuras? ¿Por qué el precio repite hoy el carácter de su movimiento del día anterior? ¿Se pueden usar estadísticas para predecir la dinámica de los precios? Hay una respuesta, y es afirmativa. Si tiene alguna duda de ello, este artículo es para usted. Le explicaré cómo crear un filtro funcional para un sistema de trading en MQL5, revelando un patrón interesante en cambios de precio.
Usar Indicadores de MetaTrader 5 con la Estructura de Aprendizaje Automático ENCOG para Predicción de Series Cronológicas Usar Indicadores de MetaTrader 5 con la Estructura de Aprendizaje Automático ENCOG para Predicción de Series Cronológicas
Este artículo presenta modos de conectar MetaTrader 5 a ENCOG - Red Neuronal Avanzada y Estructura de Aprendizaje Automático. Contiene la descripción e implementación de un indicador de red neuronal sencillo basado en indicadores técnicos estándar y un Asesor Experto basado en un indicador neuronal. Todos los códigos fuente, binarios combinados, DLLs y un ejemplo de red formada se pueden encontrar como archivos adjuntos a este artículo.