forked from nique_372/FastNLExamples
//+------------------------------------------------------------------+
| |
//| parser.js |
| |
//| Parser del .bin de un evento del calendario (CEcoEvents::Save). |
| |
//| Formato (little-endian), ver CalendarByLeo\Events\EcoEvent.mqh: |
| |
//| 3 strings : int32 largo + bytes ANSI (event_code, name, url) |
| |
//| int64 id, int64 country_id |
| |
//| 8 x int32 : importance, type, sector, frequency, time_mode, |
| |
//| multiplier, unit, digits |
| |
//| int32 n : cantidad de registros |
| |
//| n x 56 bytes (struct pack(8) EconomicEventData): |
| |
//| f64 actual, f64 previous, f64 revised_previous, f64 forecast |
| |
//| u64 id, i64 time, u32 extra_meta, i32 impact |
| |
//| Se lee solo la cabecera; cada registro se lee on-demand por |
| |
//| indice (tamano fijo => acceso directo sin recorrer el archivo). |
| |
//+------------------------------------------------------------------+
| |
(function (root, factory) {
| |
if (typeof module === 'object' && module.exports) module.exports = factory();
| |
else root.CalFmt = factory();
| |
})(this, function () {
| |
'use strict';
| |
| |
const RECORD_SIZE = 56;
| |
const ERROR_VARIABLE = -777999999912891; // "sin valor" (ver FastNL\Src\Prev\Def.mqh)
| |
const NUM_INT_FIELDS = 8;
| |
| |
// Enums de MT5 (ENUM_CALENDAR_EVENT_*)
| |
const IMPORTANCE = ['Ninguna', 'Baja', 'Media', 'Alta'];
| |
const TYPE = ['Evento', 'Indicador', 'Feriado'];
| |
const SECTOR = ['Ninguno', 'Mercado', 'PIB', 'Empleo', 'Precios', 'Dinero', 'Comercio', 'Gobierno',
| |
'Negocios', 'Consumo', 'Vivienda', 'Impuestos', 'Feriados'];
| |
const FREQUENCY = ['Ninguna', 'Semanal', 'Mensual', 'Trimestral', 'Anual', 'Diaria'];
| |
const TIMEMODE = ['Fecha y hora', 'Solo fecha', 'Sin hora', 'Tentativo'];
| |
const MULTIPLIER = ['', 'K', 'M', 'B', 'T'];
| |
const MULT_NAME = ['Ninguno', 'Miles', 'Millones', 'Billones', 'Trillones'];
| |
const UNIT = ['Ninguna', 'Porcentaje', 'Moneda', 'Horas', 'Empleos', 'Plataformas', 'USD', 'Personas',
| |
'Hipotecas', 'Votos', 'Barriles', 'Pies cubicos', 'Posiciones', 'Tiendas'];
| |
const IMPACT = ['N/A', 'Positivo', 'Negativo'];
| |
| |
// country_id de MQL5 = codigo numerico ISO 3166 (999 = Union Europea)
| |
const COUNTRY = {
| |
36: 'Australia', 76: 'Brasil', 124: 'Canada', 156: 'China', 250: 'Francia', 276: 'Alemania', 344: 'Hong Kong',
| |
356: 'India', 380: 'Italia', 392: 'Japon', 410: 'Corea del Sur', 484: 'Mexico', 554: 'Nueva Zelanda',
| |
578: 'Noruega', 710: 'Sudafrica', 724: 'Espana', 752: 'Suecia', 756: 'Suiza', 792: 'Turquia',
| |
826: 'Reino Unido', 840: 'Estados Unidos', 999: 'Union Europea'
| |
};
| |
| |
function label(arr, i) { return i >= 0 && i < arr.length ? arr[i] : 'Desconocido (' + i + ')'; }
| |
| |
// Cabecera. Lanza Error con mensaje legible si el archivo no cuadra con el formato.
| |
function parseHeader(buf) {
| |
const view = new DataView(buf);
| |
const dec = new TextDecoder('windows-1252'); // FILE_ANSI
| |
let o = 0;
| |
| |
function need(n, what) {
| |
if (o + n > buf.byteLength) throw new Error('Archivo truncado leyendo ' + what + ' (offset ' + o + ')');
| |
}
| |
function readString(what) {
| |
need(4, 'largo de ' + what);
| |
const len = view.getInt32(o, true); o += 4;
| |
if (len < 0 || len > 65536) throw new Error('Largo invalido en ' + what + ' (' + len + '): no parece un archivo de evento');
| |
need(len, what);
| |
const s = dec.decode(new Uint8Array(buf, o, len)); o += len;
| |
return s;
| |
}
| |
| |
const h = {};
| |
h.code = readString('event_code');
| |
h.name = readString('name');
| |
h.url = readString('source_url');
| |
| |
need(16 + NUM_INT_FIELDS * 4 + 4, 'campos del evento');
| |
h.id = view.getBigInt64(o, true).toString(); o += 8;
| |
h.countryId = Number(view.getBigInt64(o, true)); o += 8;
| |
const f = [];
| |
for (let i = 0; i < NUM_INT_FIELDS; i++) { f.push(view.getInt32(o, true)); o += 4; }
| |
[h.importance, h.type, h.sector, h.frequency, h.timeMode, h.multiplier, h.unit, h.digits] = f;
| |
| |
const n = view.getInt32(o, true); o += 4;
| |
if (n < 0) throw new Error('Cantidad de registros invalida (' + n + ')');
| |
h.declaredCount = n;
| |
h.dataOffset = o;
| |
const avail = Math.floor((buf.byteLength - o) / RECORD_SIZE);
| |
h.count = Math.min(n, avail);
| |
h.warning = null;
| |
if (avail < n) h.warning = 'El archivo declara ' + n + ' registros pero solo caben ' + avail + ' (truncado).';
| |
else if (buf.byteLength - o !== n * RECORD_SIZE) h.warning = 'Sobran ' + (buf.byteLength - o - n * RECORD_SIZE) + ' bytes al final del archivo.';
| |
| |
h.view = view;
| |
return h;
| |
}
| |
| |
// Registro i (0-based, orden del archivo)
| |
function readRow(h, i) {
| |
const v = h.view, o = h.dataOffset + i * RECORD_SIZE;
| |
const meta = v.getUint32(o + 48, true);
| |
return {
| |
actual: v.getFloat64(o, true),
| |
previous: v.getFloat64(o + 8, true),
| |
revised: v.getFloat64(o + 16, true),
| |
forecast: v.getFloat64(o + 24, true),
| |
id: v.getBigUint64(o + 32, true).toString(),
| |
time: Number(v.getBigInt64(o + 40, true)), // segundos (hora del servidor)
| |
meta: meta,
| |
countryIdx: meta & 0xff,
| |
eventIdx: (meta >>> 8) & 0xfffff,
| |
processed: ((meta >>> 28) & 1) === 1,
| |
impact: v.getInt32(o + 52, true)
| |
};
| |
}
| |
| |
function isEmpty(x) { return x === ERROR_VARIABLE || !isFinite(x); }
| |
| |
function fmtValue(x, h) {
| |
if (isEmpty(x)) return '—';
| |
const s = x.toFixed(Math.max(0, Math.min(h.digits, 12)));
| |
const suffix = (MULTIPLIER[h.multiplier] || '') + (h.unit === 1 ? '%' : '');
| |
return s + suffix;
| |
}
| |
| |
// El tiempo se guarda como segundos de la hora del servidor; se muestran los campos UTC tal cual (sin convertir zona).
| |
function fmtTime(sec) {
| |
if (!sec) return '—';
| |
const d = new Date(sec * 1000);
| |
const p = (n) => String(n).padStart(2, '0');
| |
return d.getUTCFullYear() + '-' + p(d.getUTCMonth() + 1) + '-' + p(d.getUTCDate()) + ' ' + p(d.getUTCHours()) + ':' + p(d.getUTCMinutes());
| |
}
| |
| |
return {
| |
RECORD_SIZE, ERROR_VARIABLE, parseHeader, readRow, fmtValue, fmtTime, isEmpty, label,
| |
IMPORTANCE, TYPE, SECTOR, FREQUENCY, TIMEMODE, MULT_NAME, UNIT, IMPACT, COUNTRY
| |
};
| |
});
|