Usando archivos de texto para guardar los parámetros de entrada de asesores, indicadores y scripts

Andrei Novichkov | 2 agosto, 2016

Introducción

Al desarrollar y usar diferentes instrumentos de trading, en ocasiones surge la idea de que los recursos estándar para crear los parámetros de entrada con la ayuda de los modificadores extern y input no siempre son suficientes. Sí, tenemos soluciones queen la práctica abarcan todas las necesidades. Pero en una serie de casos,este enfoque resulta un poco aparatoso y carente de flexibilidad. Como ejemplo, se pueden dar las siguientes situaciones.

  • Con los recursosestándar nos vemos obligados a crear parámetros que se modificarán com muy poca frecuencia, o bien no se modificarán en absoluto. Recordemos, por ejemplo, el número mágico de la orden o la magnitud del deslizamiento.

  • Si en las propiedadesdel indicador o asesor es necesario elegir una única fuente entre decenas de indicadores del tipo RSI, WPR etcétera, entonces los parámetros de cada uno de ellos se deberán crear y hacer accesibles en la ventana de propiedades, aunque solo sean necesarioslos parámetros de uno. Otro ejemplo de este tipo es el horario de funcionamiento delasesor durante la semana. ¿Para qué guardar en el miércoles lo que era actual el lunes y el martes, o será actual el viernes? Pues nos veremos obligados a hacerlo, porque todo el horiario se ubicará en los parámetros de entrada. En otras palabras, describir en los parámetros de entrada objetos que se crean dinámicamente es bastante difícil y poco efectivo.

  • Si es necesario colocar en la lista que representa las propiedades alguna aclaración, comentarios o encabezamientos, entonces deberemos crear una nueva propiedad de línea con el contenido correspondiente. Esto no siempre resulta cómodo. 


Cómo guardar los parámetros en archivos de texto

Para complementar las posibilidades del método disponibley crear y guardar los parámetros de entrada, se pueden usar los archivos de texto. En ellos es posible ubicar lo que sea, y también es posible modificarlos y recolocarlos cómodamente. Su estructura se puede organizar según el modelo de los archivos INI-. Por ejemplo, este es el aspecto que puede tener una matriz del tipo integer guardada en un archivo de texto:

/*tamaño de la matriz*/
{array_size},5
/*matriz propiamente dicha*/
{array_init},0,1,2,3,4

En el ejemplo mostrado, al comienzo de la línea se anota el «ancla» o el «nombre de la sección», así como el contenido de esta sección después de la coma. El «ancla» puede ser cualquier línea de símbolos única. Ese archivo se crea y guarda en el «sandbox» del terminal. A continuación, en el bloque del indicador, asesor o en el código del script, abrimos este archivo para la lectura, como un archivo de formato CSV.

Cuando llega el momento de leer la matriz guardada, buscamos en el archivo el «ancla» por el nombre conocido{array_size}. Reubicamos el puntero al archivo al comienzo del archivo, llmando FileSeek(handle,0,SEEK_SET). Buscamos el ancla siguiente también por el nombre conocido {array_init}, y cuando lo encontramos, simplemente leemos las líneas las veces necesarias, transformándolas según el tipo de matriz, en este caso, en integer. En el archivo de inclusión ConfigFiles.mqh se encuentran varias funciones sencillas que implementan el proceso de búsqueda del «ancla» y la lectura de datos.

En otras palabras, en general, el objeto que guardamos en el archivo debe ser descrito como «ancla» con la consiguiente línea de datos separados por comas, como exige el formato CSV. Puede haber las «anclas» y líneas de datos que sea tras ellas, pero con una condición: para cada «ancla» solo puede haber una línea.

En este artículo se pueden anotar comentarios, aclaraciones, observaciones e incluso instrucciones prácticamente en cualquier forma y lugar.  Solo hay una exigencia bastante obvia: el texto no debe romper la secuencia «ancla» — datos. Otra condición preferible es que cualquier texto voluminoso debe ubicarse al final del archivo. Esto acelerará la búsqueda de «anclas».

De esta forma se podría organizar el guardado del horario de funcionamiento del asesor, sobre el que hemos hablado más arriba:

….......
{d0},0,0,22
{d1},0,0,22
{d2},1,0,22
…........
{d6},0,0,22

Aquí, el nombre del «ancla» representa con números determinados días de la semana (es decir, {d1} — es el día número uno, y así sucesivamente). Luego va un valor del tipo bool, que determina si el asesor comercia este día o no (en este caso, d1 no comercia). Si no se comercia, entonces los siguientes valores podremos no leerlos, pero los hemos dejado aquí. Al final, los dos últimos valores del tipo integer designan el inicio y el final del funcionamiento del asesor en horas.

En la apertura de la vela de día, el asesor lee el horario de funcionamiento para el nuevo día. Por supuesto que la línea de datos para cada día puede modificarse y completarse con otros valores necesarios, dependiendo de la lógica de funcionamiento. De esta forma, en la memoria se guardan solo los datos del día actual.

El nombre del «ancla» puede ser cualquiera, pero es preferible usar el sentido común. No es útil dar nombres abstractos a dichas "anclas", para ser más prácticos, es mejor llamarlas con criterio. Los nombres deben, por una parte, tener un cierto significado, y por otra, ser únicos. Hay que recordar que la búsqueda de datos se realiza según el nombre. Resulta obvio que es posible una situación en la que en un archivo haya varias «anclas» con un mismo nombre, pero esto ya es una cuestión de la implementación concreta.

Analicemos los fragmentos del código de un indicador plenamente operativo. Bien, nuestro indicador necesita para funcionar datos de varias parejas de divisas. El indicador pide los datos según un temporizador, luego los procesa de acuerdo con su lógica (en este caso, las peculiaridades de esta lógica nos interesan). Es necesario recordar que los brókeres añaden diferentes sufijos y prefijos a los nombres de la pareja de divisas, así que por ejemplo, de EURUSD puede resultar #.EURUSD.ch. Esta circunstancia se debe tener en cuenta a la hora de recurrir a otras parejas de divisas correctamente en el código. El orden de nuestras acciones será el siguiente.

1. Creamos el archivo de texto broker.cfg con el siguiente contenido: 

[PREFIX_SUFFIX],#.,.ch
2. Creamos el archivo de texto <nombre_del_indicador>.cfg con el siguiente contenido:
/*Número de parejas de divisas en la línea con el «ancla» [CHARTNAMES] */
[CHARTCOUNT],7
/*Nombres de las parejas de divisas cuyos gráficos son usados por el indicador para leer los datos*/
[CHARTNAMES],USDCAD,AUDCAD,NZDCAD,GBPCAD,EURCAD,CADCHF,CADJPY
/*En primer o segundo lugar en la pareja de divisas va la divisa principal (en este caso, CAD)*/
[DIRECT],0,0,0,0,0,1,1
3. Colocamos ambos archivos en el sandbox.

4. En el código del indicador definimos varias funciones auxiliares (o bien conectamos el archivo ConfigFiles.mqh):

// Abre el archivo con el nombre dado como archivo del formato CSV y retorna su manejador
   int __OpenConfigFile(string name)
     {
      int h= FileOpen(name,FILE_READ|FILE_SHARE_READ|FILE_CSV,',');
      if(h == INVALID_HANDLE) PrintFormat("Invalid filename %s",name);
      return (h);
     }

//+------------------------------------------------------------------+
//| La función lee un valor iRes del tipo long en la sección con el nombre    |
//| del "ancla" strSec en el archivo con el manejador handle. En caso de tener éxito         |
//| se retorna true, en caso de error, se retorna false.           |
//+------------------------------------------------------------------+
   bool _ReadIntInConfigSection(string strSec,int handle,long &iRes)
     {
      if(!FileSeek(handle,0,SEEK_SET) ) return (false);
      string s;
      while(!FileIsEnding(handle))
        {
         if(StringCompare(FileReadString(handle),strSec)==0)
           {
            iRes=StringToInteger(FileReadString(handle));
            return (true);
           }
        }
      return (false);
     }

//+------------------------------------------------------------------+
//| La función lee la matriz de las líneas sArray con el tamaño count en la sección con     |
//| el nombre del "ancla" strSec en el archivo con el manejador handle. En caso de tener éxito  |
//| se retorna true, en caso de error, se retorna false.           |
//+------------------------------------------------------------------+
   bool _ReadStringArrayInConfigSection(string strSec,int handle,string &sArray[],int count)
     {
      if(!FileSeek(handle,0,SEEK_SET) ) return (false);
      while(!FileIsEnding(handle))
        {
         if(StringCompare(FileReadString(handle),strSec)==0)
           {
            ArrayResize(sArray,count);
            for(int i=0; i<count; i++) sArray[i]=FileReadString(handle);
            return (true);
           }
        }
      return (false);
     }

//+------------------------------------------------------------------+
//| la función lee la matriz bArray del tipo bool  con el tamaño count en la sección con  |
//| el nombre del "ancla" strSec en el archivo con el manejador handle. En caso       |
//| de tener éxito, se retorna true, en caso de error, se retorna false.    |
//+------------------------------------------------------------------+
   bool _ReadBoolArrayInConfigSection(string strSec,int handle,bool &bArray[],int count)
     {
      string sArray[];
      if(!_ReadStringArrayInConfigSection(strSec, handle, sArray, count) ) return (false);
      ArrayResize(bArray,count);
      for(int i=0; i<count; i++)
        {
         bArray[i]=(bool)StringToInteger(sArray[i]);
        }
      return (true);
     }
Además, incluimos también este código del indicador:
   …..
   input string strBrokerFname  = "broker.cfg";       // Nombre del archivo con los datos del bróker
   input string strIndiPreFname = "some_name.cfg";    // Nombre del archivo con los ajustes del indicador
   …..

   string strName[];         // Matriz con los nombres de las parejas de divisas ([CHARTNAMES])
   int    iCount;            // Número de parejas de divisas
   bool   bDir[];            // Matriz con el «orden secuencial» ([DIRECT])

   …..
   int OnInit(void) 
     {

      string prefix,suffix;     // Префикс и суффикс. Variables locales obligatorias para 
                                // la inicialización, pero usadas una sola vez.
      prefix= ""; suffix = "";
      int h = _OpenConfigFile(strBrokerFname);
      // Leemos los datos sobre el prefijo y el sufijo del archivo de configuración. Si se registran 
      // errores, entonces continuamos, dejando los valores por defecto.
      if(h!=INVALID_HANDLE) 
        {
         if(!_GotoConfigSection("[PREFIX_SUFFIX]",h)) 
           {
            PrintFormat("Error in config file %s",strBrokerFname);
              } else {
            prefix = FileReadString(h);
            suffix = FileReadString(h);
           }
         FileClose(h);
        }
      ….
      // Leemos los ajustes del indicador. 
      if((h=__OpenConfigFile(strIndiPreFname))==INVALID_HANDLE)
         return (INIT_FAILED);

      // leemos el número de parejas divisas
      if(!_ReadIntInConfigSection("CHARTCOUNT]",h,iCount)) 
        {
         FileClose(h);
         return (INIT_FAILED);
        }
      // leemos la matriz con los nombres de las parejas de divisas, leyendo ya su número
      if(!_ReadStringArrayInConfigSection("[CHARTNAMES]",h,strName,iCount)) 
        {
         FileClose(h);
         return (INIT_FAILED);
        }

      // Damos a los nombres de las parejas de divisas el aspecto necesario
      for(int i=0; i<iCount; i++) 
        {
         strName[i]=prefix+strName[i]+suffix;
        }

      // leemos la matriz de los parámetros del tipo bool
      if(!_ReadBoolArrayInConfigSection("[DIRECT]",h,bDir,iCount)) 
        {
         FileClose(h);
         return (INIT_FAILED);
        }
      ….
      return(INIT_SUCCEEDED);
     }

Como resultado de la ejecución del código, se han analizado dos matrices dinámicas y dos variables locales de vital importancia. El archivo creado con el nombre broker.cfg nos hará falta más de una vez para no tener que introducir manualmente el prefijo y el sufijo.

Áreas de posible aplicación. Defectos y variantes alternativas

Aparte de las situaciones ya descritas, el métodopropuesto es cómodo para controlar los ajustes de varias copias de un indicador o asesor que funcionen con diferentes parejas de divisas y con tareas análogas que demanden un «centro de control». Es útil también cuando, para el ajuste a distancia, podemos limitarnos a editar o sustituir el archivo de texto sin implicar el acceso al propio terminal. Y es que con bastante frecuencia, el tráder tiene acceso a la computadora con el terminal instalado solo por FTP.

Otro ejemplo eses cuando el tráder gestiona una decena de terminalesque se encuentran en diferentes lugares, puede ser que incluso en diferentes países. Basta con preparar una vez un archivo con los ajustes y enviarlo a los «sandbox» de todos los terminales. Si recordamos el ejemplo de más arriba, entonces resulta totalmente plausible organizar el envío de ese horario una vez a la semana, para que los asesores en todos los terminales funcionen de forma sincronizada. El indicador cuyos fragmentos de código se han mostrado un poco más arriba, funciona con 28 parejas y es controlado simultáneamente solo por los dos archivos mencionados en el texto.

Otro método interesante de aplicación es el guardado simultáneo con una variable en el archivo y en el área de propiedades. Además, aparece laposibilidad de implementar la lógica de una cierta prioridad de lectura. Para ilustrarla,analizaremos un fragmento de pseudocódigo usando como ejemplo la inicialización del valor del número mágico:

…..
extern int Magic=0;
…..
int OnInit(void) 
  {
   …..
   if(Magic==0) 
     {
      // Esto significa que el usuario no ha inicializado Magic, y queda
      // el valor por defecto. Entonces buscamos el valor de la variable Magic en el archivo
      …...
     }
   if(Magic==0) 
     {
      // Sigue siendo cero, por consiguiente, en el archivo no existe esa variable
      // Aquí procesamos la situación creada,
      // en este caso, salimos con error
      return (INIT_FAILED);
     }


En este ejemplo, se descubre otra ventaja del método analizado. En caso de que el archivo se use para controlar muchas copias del indicador/asesor, este enfoque permite realizar ajustes aún más certeros de cada copia de forma individual,  evitando el ajuste centralizado desde el archivo. 

Para ser justos, vamos a enumerar también los defectos del método.

El primero es la baja velocidad.  Bueno, esto podemos aceptarlo, si este método lo usamos solo en la etapa de inicialización del indicado/asesor, o bien de forma periódica, por ejemplo, al abrir una nueva vela de día.

El segundo es la necesidad de tratar con mucho detalle y cuidado la documentación adjunta. Resulta bastante complicado contar y mostrar al tráder cómo controlar los instrumentos ajustados de tal forma. Y este es otro motivo para guardar en tales archivos los ajustes que se cambian con poca frecuencia. Y por supuesto, debemos prestar especial atención a la preparación y edición de los propios archivos de texto. Los errores en esta etapa pueden traer consecuencias financieras muy serias.

Prestemos atención a los métodos alternativos de resolución de tareas, cuyos ejemplos hemos mostrado.

El primero de estos métodos implica a los archivos INI. Se trata de verdad de un buen método de guardado de datos, un método comprobado y fiable. Los archivos INI se usan cuando no queremos escribir en el registro, y esto es algo que solo podemos agradecer. Su estructura es natural y conocida, comprensible y orgánica. El primer ejemplo del horario de funcionamiento anotado en un archivo INI podría tener este aspecto:

….......
[d0]
Allowed=0
BeginWork=0
EndWork=22
.........
[d2]
Allowed=1
BeginWork=0
EndWork=22

En todo lo demás, la lógica de trabajo con archivos INI es exactamente igual: hay que ubicarlos en el "sandbox", también padecerán de "ralentización", aunque será menor que al trabajar con los archivos CSV descritos más arriba. Tiene sentido recurrir a ellos, como sucede con los archivos CSV,  durante la inicialización o al abrir una nueva vela. Pero los comentarios en el texto del archivo INI ya no podremos ubicarlos como nosotros queramos, solo como está permitido para los archivos INI. Sin embargo, tomando esto en consideración, no deberíamos tener ningún problema con la documentación de apoyo en lo que respecta a las particularidades del formato del archivo.

El defecto de este formato reside en que deberemos conectar bibliotecas externas. En los lenguajes MQL no hay recursos para trabajar directamente con archivos de este formato, por eso deberemos importarlos desde kernell32.dll. En esta página se muestran varias bibliotecas que ejecutan esta operación, por eso no tiene sentido escribir otra más y adjuntarla aquí. Estas bibliotecas son sencillas, puesto que estamos hablando de importar solo dos funciones. En cualquier caso, merece la pena recordar que todavía podrían contener errores y que no se sabe si toda la construcción será compatible con Linux. Los propios usuarios a los que no asustan las consecuencias de conectar bibliotecas externas, pueden usar archivos INI al mismo nivel que los archivos CSV, de los que se habla en este artículo.

Aparte de los archivos INI, hay otros métodos posibles. Vamos a enumerarlos brevemente.

  1. Usar un registro de sistema. A mi parecer, se trata de una variante muy discutible y dudosa. El registro es una parte crítica para el funcionamiento adecuado y la velocidad del sistema operativo. ¿Acaso será correcto permitir a los scripts e indicadores escribir en él? Me parece  que se trata de una solución inadecuada desde el punto de vista estratégico.
  2. Usar una base de datos. Sí, este es un método comprobado y fiable para guardar cualquier información y en cualquier volumen. Pero, ¿necesita un indicador o asesor guardar un volumen de datos significativo? En una mayoría aplastante de casos, no. Las bases de datos poseen una funcionalidad claramente redundante a la hora de alcanzar las metas analizadas aquí. Además, merece la pena no olvidar la base de datos si realmente surge la necesidad de guardar varios gigabytes de información.
  3. Usar XML. En esencia, se trata de los mismos archivos de texto, pero con otra sintaxis que se deberá analizar por separado. Y por otra parte,  para poder trabajar con archivos XML será necesario escribir una biblioteca (o descargar una ya preparada). Pero es que eso ni siquiera es lo más complicado: al final del trabajo habrá que preparar la documentación correspondiente. ¿Merece la pena tanto esfuerzo para el desarrollador? En este caso, resulta bastante dudoso.

Conclusión

Como conclusión, querría notar que los archivos de texto se usan ya de forma habitual: por ejemplo, en los mecanismos de copiado de operaciones. Los propios terminales MetaTrader crean y usan multitud de archivos de texto. Entre ellos, los archivos de configuración y diferentes registros (log), correos y plantillas.

En el artículo se ha intentado ampliar la aplicación de un recurso tan sencillo como son los archivos de texto de formato CSV en la configuración de instrumentos de trading: asesores, indicadores y scripts. En varios ejemplos sencillos se ha mostrado qué nuevas posibilidades se consiguen con la ayuda de estos archivos. Se han tratado de analizar los posibles métodos alternativos y se han enumerado los defectos detectados.

De cualquier forma, será la tarea establecida en cada caso la que dicte la elección del método de guardado de los parámetros de entrada. La elección de este u otro método para resolver un problema concreto siempre queda a la discreción del desarrollador. La eficiencia de esta elección es uno de los factores que demuestran la experiencia del desarrollador.