
Características del Wizard MQL5 que debe conocer (Parte 21): Pruebas con datos del calendario económico
Introducción
Continuamos la serie sobre Asesores Expertos ensamblados con el Asistente (Wizard MQL5), analizando cómo las noticias del calendario económico podrían integrarse en un Asesor Experto durante las pruebas, ya sea para confirmar una idea o para construir un sistema de trading más sólido, gracias en gran parte a este artículo. Ese artículo es parte de una serie ya que es el primero, y por lo tanto animo a los lectores a leerlo y seguirlo sin embargo, nuestra opinión aquí es estrictamente sobre cómo los asesores expertos montados por asistentes podrían beneficiarse de estas herramientas MQL5 IDE. Para los nuevos lectores, hay artículos introductorios aquí y aquí sobre cómo desarrollar y montar Asesores Expertos mediante el Asistente MQL5.
Los datos económicos pueden ser la fuente de una ventaja en un sistema de negociación, ya que se inclinan más por los «fundamentos» de los valores que por los «aspectos técnicos», más frecuentes en forma de indicadores tradicionales, indicadores personalizados y otras herramientas de acción de precios. Estos «fundamentos» pueden adoptar la forma de tasas de inflación, tipos de interés de los bancos centrales, tasas de desempleo, datos de productividad y un montón de otros datos noticiosos que suelen tener un gran impacto en los precios de los valores, como demuestra su volatilidad cada vez que se produce una publicación. El más famoso de ellos probablemente sea el de las nóminas no agrícolas, que se publica casi todos los primeros viernes de cada mes. Además, seguramente hay otros puntos de datos de noticias clave que no reciben la atención necesaria y, por lo tanto, la mayoría de los comerciantes los pasan por alto, por lo que probar estrategias basadas en estos puntos de datos de noticias económicas podría ayudar a descubrir algunos de ellos y, por lo tanto, brindar una ventaja potencial al comerciante.
SQLite: Las bases de datos se pueden crear dentro del MetaEditor IDE y puesto que son repositorios de datos, sobre el papel, deberíamos poder utilizarlas como fuente de datos para un Asesor Experto de forma que actúen como búferes de indicadores. Más que esto, sin embargo, pueden almacenar los datos económicos localmente, lo que puede permitir fácilmente realizar pruebas fuera de línea y también utilizarlos en caso de que la fuente de datos de noticias se corrompa por razones desconocidas, lo que constituye un riesgo continuo a medida que algunos (o inevitablemente la mayoría) de los puntos de datos envejecen. Entonces, en este artículo exploramos cómo se pueden usar las bases de datos SQLite para archivar noticias del Calendario Económico, de modo que los Asesores Expertos ensamblados mediante un asistente puedan usarlas para generar señales comerciales.
Limitaciones actuales y soluciones
Sin embargo, hay una trampa. Además de la imposibilidad de leer los datos del calendario económico en el probador de estrategias, de mis pruebas sobre la lectura de bases de datos dentro del probador de estrategias parece que existe una limitación similar. En el momento de escribir esto podría tratarse de un error de codificación por mi parte, pero al intentar leer datos de la base de datos con este listado:
//+------------------------------------------------------------------+ //| Read Data //+------------------------------------------------------------------+ double CSignalEconData::Read(string DB, datetime Time, string Event) { double _data = 0.0; //--- create or open the database in the common terminal folder ResetLastError(); int _db_handle = DatabaseOpen(DB, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE); if(_db_handle == INVALID_HANDLE) { Print("DB: ", DB, " open failed with err: ", GetLastError()); return(_data); } string _sql = " SELECT ACTUAL " + " FROM " + " ( " + " SELECT ACTUAL " + " FROM PRICES " + " WHERE DATE <= '" + TimeToString(Time) + "' " + " AND EVENT = '" + Event + "' " + " ORDER BY DATE DESC " + " LIMIT 1 " + " ) "; int _request = DatabasePrepare(_db_handle, _sql); if(_request == INVALID_HANDLE) { Print("request failed with err: ", GetLastError()); DatabaseClose(_db_handle); return(_data); } while(DatabaseRead(_request)) { //--- read the values of each field from the obtained entry ResetLastError(); if(!DatabaseColumnDouble(_request, 0, _data)) { Print(" DatabaseRead() failed with err: ", GetLastError()); DatabaseFinalize(_request); DatabaseClose(_db_handle); } } return(_data); }
Produce el error 5601, con el mensaje de que ¡la tabla a la que intento acceder no existe! Sin embargo, al ejecutar el script SQL exacto desde el IDE de la base de datos MetaEditor o en un script adjunto a un gráfico no tengo esos problemas, ya que se devuelve el resultado esperado. Por lo tanto, podría ser un descuido por mi parte donde hay algún código extra que necesito incluir para hacer que esto se ejecute, en el probador de estrategias O el acceso a las bases de datos en el probador de estrategias no está permitido. ¡El Chatbot de la Mesa de Servicio no puede ayudar!
Entonces, ¿qué podríamos hacer en esta situación? Está claro que archivar los datos económicos en una base de datos, de forma local, como se ha mencionado anteriormente, tiene sus ventajas, por lo que sería una lástima no llevar esto más lejos probando y desarrollando Asesores Expertos basados en ello. La solución que propongo consiste en exportar los datos económicos a un archivo CSV y leerlo durante el comprobador de estrategias.
A pesar de confiar en los archivos CSV y utilizarlos como solución en este caso, se enfrentan a una serie de retos y limitaciones si se piensa que podrían suplantar a las bases de datos. Se podría argumentar que, en lugar de exportar los datos a una base de datos y luego a un archivo CSV, ¿por qué no exportarlos directamente al archivo CSV? He aquí la razón.
Los archivos CSV son mucho más ineficaces para almacenar datos que las bases de datos. Esto se demuestra a través de una serie de factores, entre los que destacan la integridad y la validación de los datos. Las bases de datos aplican comprobaciones de integridad y restricciones mediante claves primarias y claves externas, mientras que los archivos CSV carecen claramente de esta capacidad. En segundo lugar, partiendo de esa base, el rendimiento y la escalabilidad son un punto fuerte de las bases de datos gracias a la indexación, que permite realizar búsquedas muy eficaces en grandes conjuntos de datos, mientras que los archivos CSV siempre dependerán de la búsqueda lineal, que puede resultar muy lenta cuando se trata de grandes volúmenes de datos.
En tercer lugar, el acceso concurrente está incorporado en la mayoría de las bases de datos y esto puede permitir el acceso en tiempo real para múltiples usuarios, por otro lado los archivos CSV no pueden manejar esto. Además, las bases de datos proporcionan un acceso seguro con funciones como la autenticación de usuarios, el control de acceso basado en funciones y el cifrado. Los archivos CSV no ofrecen seguridad por defecto, lo que dificulta la protección de datos sensibles.
Además, las bases de datos proporcionan herramientas automatizadas para la copia de seguridad y la recuperación, cosa que no hace el CSV; las bases de datos admiten consultas complejas que utilizan uniones y manipulación con SQL para un análisis exhaustivo, mientras que los archivos CSV requerirían scripts de terceras partes para llegar a la misma capacidad. Las bases de datos ACID proporcionan conformidad en sus transacciones, cosa que no hacen los archivos CSV.
Para continuar, las bases de datos también soportan la normalización que reduce la redundancia de datos y por lo tanto permite un almacenamiento más compacto y eficiente con menos duplicidad en el almacenamiento mientras que la estructura plana inherente de CSV está destinada a engendrar mucha redundancia. Las bases de datos también admiten el control de versiones (algo importante, ya que muchos datos pueden actualizarse con el tiempo), una característica clave para las auditorías y que los archivos CSV no ofrecen. Los CSV son propensos a corromper datos durante las actualizaciones y enfrentan desafíos en la gestión de estructuras de datos complejas. Hay muchas otras ventajas cruciales de las bases de datos sobre los archivos CSV, sin embargo, limitaremos nuestra lista a estas para resaltar los beneficios. Cada uno de estos beneficios mencionados puede desempeñar un papel crucial en la conservación de datos económicos para su análisis y estudio, especialmente durante períodos prolongados de tiempo, algo que será difícil de manejar con archivos CSV.
Sin embargo, antes de exportar datos a un archivo CSV para que el probador de estrategias pueda acceder a ellos, debemos crear la base de datos y cargar en ella los datos económicos, por lo que esto es lo que cubriremos a continuación.
Construyendo la base de datos SQLite
Para construir nuestra base de datos SQLite, usaremos un script. El listado correspondiente se comparte a continuación:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ //| Sourced from: https://www.mql5.com/en/articles/7463#database_functions //| and here: https://www.mql5.com/en/docs/database/databasebind //+------------------------------------------------------------------+ void OnStart() { //--- create or open a database string _db_file = __currency + "_econ_cal.sqlite"; int _db_handle = DatabaseOpen(_db_file, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE); if(_db_handle == INVALID_HANDLE) { Print("DB: ", _db_file, " open failed with code ", GetLastError()); return; } else Print("Database: ", _db_file, " opened successfully"); ... ... }
Este código proviene principalmente de aquí, con algunas modificaciones. La creación de una base de datos se realiza a través de un identificador, como declarar un identificador de lectura o escritura de un archivo. Estamos creando una base de datos para cada par de divisas, lo cual admito que es un desperdicio y muy difícil de manejar, y un mejor enfoque hubiera sido tener todos estos puntos de datos económicos de todas las divisas en una sola base de datos, sin embargo, nunca llegué a ser tan diligente, me disculpo. El lector podría enmendarlo. Una vez creado nuestro identificador, necesitaremos verificar que éste sea válido antes de continuar. Si es válido, esto indica que tenemos una base de datos en blanco y por lo tanto podemos proceder a crear la tabla para albergar nuestros datos. Nombramos nuestros precios de tabla porque en este artículo nos centraremos solo en los Calendarios de eventos del sector del tipo 'precios sectoriales'. Este es un sector general que seguramente incluirá no solo datos de tasa de inflación sino también índices de precios al consumidor y al productor y es el foco de atención ya que buscamos desarrollar una clase de señal personalizada que base sus condiciones largas o cortas en las tasas de inflación relativas del par de divisas que se negocia. Se podrían adoptar muchos enfoques alternativos para desarrollar estas señales de condiciones largas y cortas, pero la ruta elegida aquí es probablemente una de las más simples. Al crear la tabla, al igual que con la mayoría de los objetos de base de datos, comenzaríamos por verificar si existe y, si es así, se eliminaría (se descartaría) para que podamos crear la tabla que vamos a completar y usar. El listado que hace esto se comparte a continuación:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { ... //--- if the PRICES table exists, delete it if(DatabaseTableExists(_db_handle, "PRICES")) { //--- delete the table if(!DatabaseExecute(_db_handle, "DROP TABLE PRICES")) { Print("Failed to drop table PRICES with code ", GetLastError()); DatabaseClose(_db_handle); return; } } //--- create the PRICES table if(!DatabaseExecute(_db_handle, "CREATE TABLE PRICES(" "DATE TEXT ," "FORECAST REAL ," "ACTUAL REAL ," "EVENT TEXT);")) { Print("DB: ", _db_file, " create table failed with code ", GetLastError()); DatabaseClose(_db_handle); return; } //--- display the list of all fields in the PRICES table if(DatabasePrint(_db_handle, "PRAGMA TABLE_INFO(PRICES)", 0) < 0) { PrintFormat("DatabasePrint(\"PRAGMA TABLE_INFO(PRICES)\") failed, error code=%d at line %d", GetLastError(), __LINE__); DatabaseClose(_db_handle); return; } ... }
Nuestra tabla creada contará simplemente con 4 columnas, a saber, la columna 'DATE' que será de tipo texto y que registrará cuándo se publicó la noticia económica, la columna 'FORECAST' para el punto de datos económicos previsto que será de tipo real, la columna 'ACTUAL' que también será de tipo real e incluirá el punto de datos económicos reales para esa fecha, y por último la columna 'EVENT' que será de tipo texto y ayudará a etiquetar correctamente este punto de datos ya que en cualquier fecha para una divisa determinada podemos tener múltiples puntos de datos dentro de la categoría de precios del sector de eventos. Por lo tanto, el tipo de etiqueta utilizada para cada punto de datos corresponderá al código de evento de los datos. Esto se debe a que al recuperar los datos del calendario económico, utilizamos la función 'CalendarValueHistoryByEvent' para devolver valores de noticias del calendario que están emparejados con eventos específicos. Cada uno de estos eventos tiene un código descriptivo de cadena, y son estos códigos los que asignamos a nuestros datos cuando los almacenamos en la base de datos. A continuación se muestra el listado de la función 'Get' que recupera estos datos del calendario económico:
//+------------------------------------------------------------------+ //| Get Currency Events //+------------------------------------------------------------------+ bool Get(string Currency, datetime Start, datetime Stop, ENUM_CALENDAR_EVENT_SECTOR Sector, string &Data[][4]) { ResetLastError(); MqlCalendarEvent _event[]; int _events = CalendarEventByCurrency(Currency, _event); printf(__FUNCSIG__ + " for Currency: " + Currency + " events are: " + IntegerToString(_events)); // MqlCalendarValue _value[]; int _rows = 1; ArrayResize(Data, __COLS * _rows); for(int e = 0; e < _events; e++) { int _values = CalendarValueHistoryByEvent(_event[e].id, _value, Start, Stop); // if(_event[e].sector != Sector) { continue; } printf(__FUNCSIG__ + " Calendar Event code: " + _event[e].event_code + ", belongs to sector: " + EnumToString(_event[e].sector)); // _rows += _values; ArrayResize(Data, __COLS * _rows); for(int v = 0; v < _values; v++) { // printf(__FUNCSIG__ + " Calendar Event code: " + _event[e].event_code + ", for value: " + TimeToString(_value[v].period) + " on: " + TimeToString(_value[v].time) + ", has... "); // Data[_rows - _values + v - 1][0] = TimeToString(_value[v].time); // if(_value[v].HasForecastValue()) { Data[_rows - _values + v - 1][1] = DoubleToString(_value[v].GetForecastValue()); } if(_value[v].HasActualValue()) { Data[_rows - _values + v - 1][2] = DoubleToString(_value[v].GetActualValue()); } // Data[_rows - _values + v - 1][3] = _event[e].event_code; } } return(true); }
Utilizamos una matriz de cadenas multidimensional denominada '_data' para recuperar los datos del calendario económico y su segunda dimensión coincide con el número de columnas de la tabla 'PRICES' que utilizaremos para almacenar los datos, lo que significa que sus filas son iguales en número a las filas de datos que insertaremos en la tabla 'PRICES'. Para acelerar la carga de datos de nuestra matriz a la tabla, primero utilizamos las funciones 'DatabaseTransactionBegin()' y 'DatabaseTransactionCommit()' para iniciar y finalizar respectivamente las operaciones de escritura de datos. Esto se explica aquí, en el artículo ya referenciado anteriormente, como una vía más eficaz frente al trabajo sin ellas. En segundo lugar, utilizamos la función DatabaseBind para escribir realmente los datos de nuestra matriz en la base de datos. Dado que nuestras columnas de datos coinciden con la tabla de datos de destino, este proceso también es relativamente sencillo y muy eficaz a pesar de ser un poco largo, como se muestra en el siguiente listado:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ //| Sourced from: https://www.mql5.com/en/articles/7463#database_functions //| and here: https://www.mql5.com/en/docs/database/databasebind //+------------------------------------------------------------------+ void OnStart() { ... //--- create a parametrized _sql_request to add _points to the PRICES table string _sql = "INSERT INTO PRICES (DATE,FORECAST,ACTUAL,EVENT)" " VALUES (?1,?2,?3,?4);"; // _sql_request parameters int _sql_request = DatabasePrepare(_db_handle, _sql); if(_sql_request == INVALID_HANDLE) { PrintFormat("DatabasePrepare() failed with code=%d", GetLastError()); Print("SQL _sql_request: ", _sql); DatabaseClose(_db_handle); return; } //--- go through all the _points and add them to the PRICES table string _data[][__COLS]; Get(__currency, __start_date, __stop_date, __event_sector, _data); int _points = int(_data.Size() / __COLS); bool _request_err = false; DatabaseTransactionBegin(_db_handle); for(int i = 0; i < _points; i++) { //--- set the values of the parameters before adding a data point ResetLastError(); string _date = _data[i][0]; if(!DatabaseBind(_sql_request, 0, _date)) { PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError()); _request_err = true; break; } //--- if the previous DatabaseBind() call was successful, set the next parameter if(!DatabaseBind(_sql_request, 1, _data[i][1])) { PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError()); _request_err = true; break; } if(!DatabaseBind(_sql_request, 2, _data[i][2])) { PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError()); _request_err = true; break; } if(!DatabaseBind(_sql_request, 3, _data[i][3])) { PrintFormat("DatabaseBind() failed at line %d with code=%d", __LINE__, GetLastError()); _request_err = true; break; } //--- execute a _sql_request for inserting the entry and check for an error if(!DatabaseRead(_sql_request) && (GetLastError() != ERR_DATABASE_NO_MORE_DATA)) { PrintFormat("DatabaseRead() failed with code=%d", GetLastError()); DatabaseFinalize(_sql_request); _request_err = true; break; } else PrintFormat("%d: added data for %s", i + 1, _date); //--- reset the _sql_request before the next parameter update if(!DatabaseReset(_sql_request)) { PrintFormat("DatabaseReset() failed with code=%d", GetLastError()); DatabaseFinalize(_sql_request); _request_err = true; break; } } //--- done going through all the data points //--- transactions status if(_request_err) { PrintFormat("Table PRICES: failed to add %s data", _points); DatabaseTransactionRollback(_db_handle); DatabaseClose(_db_handle); return; } else { DatabaseTransactionCommit(_db_handle); PrintFormat("Table PRICES: added %d data", _points); } ... }
Con los datos insertados en la tabla 'PRICE', ahora tenemos que crear un archivo CSV a partir de nuestra base de datos, ya que el acceso al utilizar el comprobador de estrategias parece estar inhibido. Para recapitular, nuestra función 'Read()' que contiene el SQL utilizado para leer la base de datos puede ejecutarse sin problemas dentro del MetaEditor como se muestra en la siguiente imagen:
Además, si adjuntamos el script 'sql_read' (la fuente completa está más abajo) a cualquier gráfico con entradas de tiempo similares y consultamos la base de datos USD obtenemos el mismo resultado, lo que implica que no hay ningún problema con la base de datos en MetaEditor IDE o en el entorno de terminal MT5. Por favor, vea la imagen de impresión del registro más abajo:
El hecho de adjuntar y ejecutar el script significa potencialmente que un Asesor Experto adjunto a un gráfico podría ser capaz de leer los valores de la base de datos sin ningún problema. Sin embargo, para nuestros propósitos ahora mismo no podemos leer los valores de la base de datos cuando ejecutamos el comprobador de estrategias y esto significa que tenemos que confiar en los archivos CSV.
Exportación de datos del calendario económico
Para exportar los datos a un CSV sólo tenemos que utilizar una de las funciones incorporadas 'DatabaseExport()' como se muestra en el código siguiente:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { ... //--- save the PRICES table to a CSV file string _csv_file = "PRICES.csv"; if(DatabaseExport(_db_handle, "SELECT * FROM PRICES", _csv_file, DATABASE_EXPORT_HEADER | DATABASE_EXPORT_INDEX | DATABASE_EXPORT_QUOTED_STRINGS, ";")) Print("Database: table PRICES saved in ", _csv_file); else Print("Database: DatabaseExport(\"SELECT * FROM PRICES\") failed with code", GetLastError()); //--- close the database file and inform of that DatabaseClose(_db_handle); PrintFormat("Database: %s created and closed", _db_file); }
Este enfoque es la ruta menos intensiva en código, ya que si, por ejemplo, primero seleccionáramos los datos en un objeto (por ejemplo, una matriz) y luego hiciéramos un bucle a través de todos los valores de la matriz y los guardáramos en una cadena separada por comas que luego exportaríamos, conseguiríamos el mismo resultado, pero estoy casi seguro de que, dejando a un lado el ajetreo de codificación, lo que hemos adoptado aquí tiene un tiempo de ejecución mucho menor que el enfoque del bucle for. Esto podría deberse a que SQLite es una biblioteca en lenguaje C, y MQL5 también se basa mucho en C.
El diseño de nuestra tabla de base de datos, para «PRICES», carece de una clave primaria explícita y estas claves con grandes conjuntos de datos son importantes para crear índices que hagan de las bases de datos una herramienta rápida y potente. Lo que se puede hacer como modificación de esta tabla podría ser añadir una columna de incremento automático que sirva como clave primaria o emparejar las columnas «EVENT» y «DATE» para que ambas sean claves primarias, ya que desde el punto de vista del diseño los valores combinados en ambas columnas serán únicos en todas las filas de datos. La ambigüedad de los códigos adoptados para etiquetar los sucesos como almacenados en la columna «EVENT» exige cierta diligencia adicional para asegurarse de que el punto de datos que le interesa es el que realmente recupera.
Por ejemplo, para este artículo nos centraremos en el par GBPUSD, lo que significa que las dos divisas que nos interesan son la GBP y el USD. (¡Nótese que hemos eludido el euro dados los múltiples puntos de datos no sólo de la zona euro sino también de sus países miembros!) Si nos fijamos en los códigos de eventos para los datos de inflación de estas divisas menos ambiguos, para la GBP tenemos «cpi-yy» y para el USD «core-pce-price-index-yy». Tenga en cuenta que existen otros códigos de inflación interanual del consumo en USD que no tendremos en cuenta, por lo que deberá considerar detenidamente su selección. Y además, este etiquetado no es estándar per se, lo que significa que dentro de unos años o incluso menos podría ser revisado de tal forma que cualquier sistema automatizado también necesitará actualizar su código. Esto podría apuntar a la idea de que uno tenga su propio etiquetado personalizado con un filtro de validación de datos del calendario que ayude a garantizar que los datos correctos se codifican correctamente pero, como se ha mencionado, sería necesaria cierta inspección humana, de vez en cuando, ya que la codificación podría cambiarse en un momento.
Clase de señal MQL5
Estamos utilizando archivos CSV para ello, como ya se ha mencionado, y aunque debería ser un proceso sencillo, los formatos ANSI y UTF8 podrían presentar algunos retos a la hora de leer estos datos si no se conocen sus diferencias. Hemos adoptado una función estándar de lector CSV de aquí para leer los datos CSV exportados, y se carga en la función que inicializa los indicadores para cada divisa. La divisa del margen (GBP) y la divisa del beneficio (USD). Al hacer esto, es inevitable que haya limitaciones en la lectura de archivos CSV de gran tamaño, ya que sobrecargan la memoria RAM. Una posible solución podría ser dividir el archivo CSV por tiempo, de forma que en el momento de la inicialización sólo se cargue uno de los archivos y cuando su último punto de datos sea demasiado antiguo para la hora actual en el comprobador de estrategias, ese archivo se «libere» y se cargue un archivo CSV más nuevo.
Todas estas soluciones solucionan problemas que no existirían si el acceso a la base de datos en el probador de estrategias fuera posible. Así, nuestra clase de señal, puesto que no está leyendo de una base de datos, simplemente tomará como entradas los nombres de los archivos CSV tanto para la divisa del margen como para la divisa del beneficio. Con nuestra clase de señal el único búfer de serie que utilizaremos será la clase 'm_time' y estrictamente hablando ni siquiera necesitamos el búfer ya que la hora actual es suficiente sin embargo, se utiliza aquí para obtener la hora en el índice cero. La recuperación de los valores de los datos del calendario a partir del archivo CSV cargado se realiza mediante la función «Leer», cuyo código se muestra a continuación:
//+------------------------------------------------------------------+ //| Read Data //+------------------------------------------------------------------+ double CSignalEconData::Read(datetime Time, SLine &Data[]) { double _data = 0.0; int _rows = ArraySize(Data); _data = StringToDouble(Data[0].field[1]); for(int i = 0; i < _rows; i++) { if(Time >= StringToTime(Data[i].field[0])) { _data = StringToDouble(Data[i].field[1]); } else if(Time < StringToTime(Data[i].field[0])) { break; } } return(_data); }
Es iterativa ya que utiliza un bucle for si, no obstante, hubiéramos podido acceder a estos mismos datos desde una base de datos indexada, la misma operación se ejecutaría mucho más rápidamente. En conjuntos de datos pequeños como el utilizado para este artículo, esta diferencia de rendimiento podría pasarse por alto, pero a medida que aumenta el tamaño del conjunto de datos y se examinan más datos históricos, los argumentos a favor de leer el SQLite dentro del comprobador de estrategias se hacen más sólidos.
La función de lectura se llama tanto para la moneda margen como para la moneda beneficio, y devuelve las últimas tasas de inflación. Nuestra señal se genera simplemente en función del tamaño relativo de estos índices. Si la divisa del margen tiene una tasa de inflación más alta que la divisa de beneficio, entonces nos pondríamos cortos en el par. Si, por el contrario, la tasa de inflación de la divisa del margen es menor, entonces iríamos en largo. Esta lógica se muestra a continuación como parte de la función 'LongCondition()':
//+------------------------------------------------------------------+ //| "Voting" that price will grow. | //+------------------------------------------------------------------+ int CSignalEconData::LongCondition(void) { int result = 0; m_time.Refresh(-1); double _m = Read(m_time.GetData(0), m_margin_data); double _p = Read(m_time.GetData(0), m_profit_data); if(_m < _p) { result = int(100.0 * ((_p - _m) / _p)); } return(result); }
Si ejecutamos el Asesor Experto sin ningún tipo de optimización, utilizando la primera configuración por defecto tras montarlo con el asistente y compilarlo, obtendremos los siguientes resultados:
La inflación es claramente determinante en la tendencia de los pares de divisas. Los datos de inflación que hemos utilizado se publican mensualmente, por lo que nuestro marco temporal de prueba también es mensual. Pero este no debería ser necesariamente el caso, ya que en marcos temporales incluso más pequeños se puede mantener la misma posición mientras se buscan puntos de entrada más agudos o mejores. El par analizado es el GBPUSD.
Conclusión
En resumen, las bases de datos SQLite introducen muchos beneficios y ventajas en la forma en que permiten almacenar y conservar conjuntos de datos personalizados. Los datos del calendario económico que se publican en los principales acontecimientos noticiosos son uno de esos conjuntos de datos que podrían archivarse para su posterior análisis a fin de ayudar a comprender cuáles son los principales impulsores de la acción del mercado. Una estrategia muy sencilla como la que se examina en este artículo, que se centra en la inflación, podría marcar la diferencia en un sistema que también utiliza indicadores técnicos. Como siempre, esto no es un consejo de inversión y se anima al lector a realizar su propia diligencia debida antes de emprender cualquiera de las ideas compartidas en este artículo o serie.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/14993
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
El almacenamiento en caché del calendario económico incorporado para probar y optimizar EAs se describió en el libro de algotrading.
Su implementación de la lectura de eventos (CSignalEconData::Read) es ineficiente y poco práctica.
PS. Para trabajar con SQLite desde el probador, uno debe crear/colocar la base de datos en la carpeta común y abrirla con la bandera DATABASE_OPEN_COMMON.
Tu implementación de la lectura de eventos (CSignalEconData::Read) es ineficiente y poco útil.
¿Qué se esconde bajo esta frase? ¿Cómo se mide la eficiencia?
¿Qué se esconde bajo esta frase? ¿Cómo se mide la eficacia?
La búsqueda de una fecha concreta se realiza mediante un bucle directo a través de todo el array de eventos en cada llamada, lo que cargará la CPU en progresión geométrica. Además, en cada iteración se llama a StringToTime y StringToDouble.
Durante una prueba con un año o más de miles de eventos económicos se ralentizará significativamente, por no hablar de la optimización.
La búsqueda de una fecha-hora específica se implementa mediante un bucle directo a través de todo el array de eventos en cada llamada, lo que carga exponencialmente el procesador. Además, StringToTime y StringToDouble son llamados en cada iteración.
Cuando se prueba un año o más de miles de eventos económicos esto ralentizará significativamente, por no hablar de la optimización.
Gracias por la aclaración.