FX31337 / FX-BT-Scripts

:page_facing_up: Useful scripts for backtesting.
MIT License
34 stars 39 forks source link

type-2 block #115

Open gfic-limited opened 3 months ago

gfic-limited commented 3 months ago

Hi,

Your metaquotes history download decoding of type-2 block needs to be fixed, otherwise the data is junk.

The Type-3 block has 5 bytes, which is decoded correctly. The Type-2 block has 6 bytes, of which the first byte is a minutes offset, and the remaining 5 bytes are the same as the Type-3 block. To decode, you do the same as you would do for a Type-3 block, but instead of incrementing the minutes by one (or, as you do, msec by 60000), you increment the minutes by the value of the first byte.

I have manually compared this to the data in the history centre to confirm it is correct.

This is the Javascript implementation:

Thank you for your implementation, otherwise I would have been stuck. Standing on the shoulders of others, and all that.

EDIT: after debugging, refactoring and testing, this is the final result if useful for others. Tested on data 2010-2024


// -----------------------------------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------------------------

function mq_test () { __TEST_SETUP ();

  // const file = mq_history_data ('http://history.metaquotes.net/symbols/AUDCAD/AUDCAD_2015_04_9a095e32298dccd59efc60af8c088932.dat');
  // const data = mq_history_bars_native (file);
  // const bars = mq_history_bars_decode (data)?.bars;
  // debugLog (bars.length);

  debugLog (mq_history_bars_decode (mq_history_bars_native (mq_history_data ('http://history.metaquotes.net/symbols/AUDCAD/AUDCAD_2015_04_9a095e32298dccd59efc60af8c088932.dat')))?.bars.length);
  debugLog (mq_history_bars_decode (mq_history_bars_native (mq_history_data ('http://history.metaquotes.net/symbols/EURUSD/EURUSD_2022_08_937abd52066eb6a5b3eb011f2ee32eba.dat')))?.bars.length);
  debugLog (mq_history_bars_decode (mq_history_bars_native (mq_history_data ('http://history.metaquotes.net/symbols/CHFJPY/CHFJPY_2008_05_8b50ae09d294b00cac783282648ff7c9.dat')))?.bars.length);

  // debugLog (mq_history ('AUDSGD').length);
}

// -----------------------------------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------------------------

const MQ_HISTORY_DEFAULT_YEAR = 2020;

function mq_history (symbol = 'EURUSD', year_from = MQ_HISTORY_DEFAULT_YEAR, info = mq_history_info ()) {
  function __history (symbol, year_from, year_to, info) {
    return util_uint8array_join (util_num_range (year_from, year_to).map (year_of => {
      const specs = info.select (symbol, year_of);
      return SymbolDataStore.get (__mq_storage_name_data (symbol, year_of, specs.length),
        (name) => { app_message (`--> ${name}`);
          return util_uint8array_to_arraybuffer (util_uint8array_join (specs.map (spec => mq_history_bars_native (mq_history_data (spec)))));
        }, { type: 'arraybuffer', cacheable: true });
    }).map (bar => util_arraybuffer_to_uint8array (bar)));
  }
  const year_to = util_date_year (), month_to = info.select (symbol, year_to).map (({ month }) => Number (month)).max ();
  const bars = SymbolDataStore.get (__mq_storage_name_data (symbol, year_to, month_to, year_from),
    (name) => { app_message (`--> ${name}`);
      return util_uint8array_to_arraybuffer (__history (symbol, year_from, year_to, info));
    }, { type: 'arraybuffer', cacheable: true });
  return bars && mq_history_bars_decode (util_arraybuffer_to_uint8array (bars))?.bars;
}

function mq_refresh () {
  const info = mq_history_info ();
  info.map (({ symbol }) => symbol).uniq ().forEach (symbol => {
    app_message (`*** ${symbol} ***`);
    const bars = mq_history (symbol, MQ_HISTORY_DEFAULT_YEAR, info);
    app_message (bars.length);
    [ ...bars.slice (0, 3), `... ${bars.length - 6} ...`, ...bars.slice (-3)].forEach (bar =>
      app_message (typeof bar === 'string' ? bar : util_obj_strNiceOrdered ({ ...bar, timestamp: util_date_epochToStr_ISO (bar.time) },
        ['timestamp', 'open', 'high', 'low', 'close', 'volume'], ',', true)));
  });
}

// -----------------------------------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------------------------

// http://history.metaquotes.net/symbols/AUDCHF/AUDCHF_2024_04_c3d6d3b4e6c43d50dd374d8a106a8e7d.dat

const __mq_history_link = (symbol, filename) => `http://history.metaquotes.net/symbols/${symbol}/${filename}`;
const __mq_history_link_list = (symbol) => __mq_history_link (symbol, 'list.txt');
const __mq_history_link_data = ({ symbol, year, month, checksum }) => __mq_history_link (symbol,`${symbol}_${year}_${month.padStart (2, '0')}_${checksum}.dat`);
const __mq_history_csum = (data, csum) => util_uint8array_to_base16 (util_hash_md5 (data)) === csum; // yuck
const __mq_history_spec = (name) => name.match (/^(?<symbol>\w+)_(?<year>\d{4})_(?<month>\d{2})_(?<checksum>[\da-f]{32})\.dat$/)?.groups || {};
const __mq_history_bars = (bars) => bars.map (({ time, open, high, low, close, volume }) => ({ time, open, high, low, close, volume }));

const __mq_history_symbols = () => [ ...(new SymbolDataInfo ()).all (), ...MARKET_SYMBOLS () ];

const __mq_storage_name_list = (year = util_date_year (), week = util_date_week ()) => `000000-metaquotes-${year}W${String (week).padStart (2, '0')}.json`;
const __mq_storage_name_data = (symbol, year, month, year_ = undefined) => `${symbol}-metaquotes-${year}${String (month).padStart (2, '0')}${year_ ? '-' + year_ : ''}.dat`;

const __mq_history_info_select = (info, symbol_, year_) => info.filter (({ symbol, year }) => symbol === symbol_ && (year_ && (Number (year) === year_)))
  .sort ((a, b) => util_sort_cmp_num (Number (a.month), Number (b.month)));

function mq_history_info (partial_okay = false) {
  const __history_info = (symbols) =>
    symbols.uniq ().flatMap (symbol => util_exception_catcher (() => {
      const files = mq_history_list (symbol);
      debugLog ([ symbol, files.length, __mq_history_link_data (files.first ()), __mq_history_link_data (files.final ()) ]);
      return files;
    })).filter (Boolean);
  const date = util_date_obj ();
  const info = __json_parse (SymbolDataStore.get (__mq_storage_name_list (), () => __json_stringify (__history_info (__mq_history_symbols ())), { type: 'text', cacheable: true }))
    .filter (({ month, year })=> partial_okay || !(Number (year) === date.year && Number (month) === (date.month + 1)))
    .sort ((a, b) => util_sort_cmp_str (a.symbol, b.symbol) || util_sort_cmp_num (Number (a.year), Number (b.year)) || util_sort_cmp_num (Number (a.month), Number (b.month)));
  info.select = (symbol ,year) => __mq_history_info_select (info, symbol, year);
  return info;
}

function mq_history_list (symbol) {
  return util_exception_catcher (() => system_connect.request (util_function_name (mq_history_list), __mq_history_link_list (symbol)))
    ?.split ('\n').filter (Boolean).map (name => __mq_history_spec (name));
}
function mq_history_data (spec) {
  const data = system_connect.request (util_function_name (mq_history_data), typeof spec === 'string' ? spec : __mq_history_link_data (spec), {}, 'data');
  if (data && __mq_history_csum (data, typeof spec === 'string' ? spec.match (/_(?<checksum>[\da-f]{32})\.dat$/)?.groups?.checksum : spec.checksum))
    return data;
  return undefined;
}

function mq_history_bars_native (buff) {
  const { head, data, stat } = __mq_datfile_decode_file (buff);
  debugLog (util_uint8array_to_hexdump (head));
  debugLog (((view) => ({ '0x74': view.getUint32 (0x74, true), '0x7c': view.getUint32 (0x7c, true) })) (new DataView (head.buffer)));
  debugLog (stat);
  return data;
}
function mq_history_bars_decode (buff, debug = false) {
  const bars = __mq_datfile_decode_data (buff);
  debugLog ({ bars: bars.length, first: util_date_epochToStr_ISO (bars.first ().time), final: util_date_epochToStr_ISO (bars.final ().time) });
  const issues = __mq_datfile_issues (bars);
  issues.forEach (issue => console.log (issue));
  return { bars: debug ? bars : __mq_history_bars (bars), issues };
}
function mq_history_bars (buff, debug = false) {
  const { head, data, stat } = __mq_datfile_decode_file (buff);
  debugLog (util_uint8array_to_hexdump (head));
  debugLog (stat);
  const bars = __mq_datfile_decode_data (data);
  debugLog ({ bars: bars.length, first: util_date_epochToStr_ISO (bars.first ().time), final: util_date_epochToStr_ISO (bars.final ().time) });
  const issues = __mq_datfile_issues (bars);
  issues.forEach (issue => console.log (issue));
  return { bars: debug ? bars : __mq_history_bars (bars), issues };
}

// -----------------------------------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------------------------

const __EXPECT_EQUAL = (a, b, m) => (a === b) || __THROW (`${m}, got ${a}, expected ${b}`);
const __EXPECT_TRUE = (a, m) => (a) || __THROW (m);
const __EXPECT_FAIL = (m) => __THROW (m);

const __mq_datafile_spec = {
  BLOCK_TYPE: { ONE: 0x3F, TWO: 0x7F, THREE: 0xBF },
  BLOCK_SIZE: { ONE: 16, TWO: 6, THREE: 5 },
  head_length: 0x88, key_length: 0x80, last_bytes: [ 0x11, 0x00, 0x00 ],
  exp: BigInt (17),
  mod: BigInt ('0x' + [ // LSB
    '2300905D', '1B6C06DF', 'E4D0D140', 'ED8B47C4', '93970C42', '920C45E6', '22C90AFB', '37B67A10',
    '0F67F0F6', '4237AB4F', '9FA30B14', '916B3CA6', 'D48FA715', '689FCCA6', 'D3DBE628', '5200D9B3',
    '732F7BBC', 'DC592279', '39861B5F', '0A007CBA', 'BF311219', 'D3461CB2', '519A4042', 'DE59FBB0',
    'DD6662ED', 'E9D7BAFC', '878F5459', '63294CBF', '103206C9', 'D2FA9C90', '49832FEF', 'ADEAAD39',
    '00000000', '00000000',
  ].reverse ().join (''))
}

function __mq_datfile_decode_key (data) {
  const { exp, mod } = __mq_datafile_spec;
  const key = BigInt ('0x' + util_uint8array_to_base16 (data));
  const res = (key ** exp) % mod;
  return util_base16_to_uint8array (res.toString (16).slice (-128*2).padStart (128*2, '0'));
}

function __mq_datfile_decode_file (data) {
  const { head_length, key_length, last_bytes, BLOCK_TYPE } = __mq_datafile_spec;

  const head = data.slice (0, head_length);
  const key = __mq_datfile_decode_key (data.slice (head_length, head_length + key_length).reverse ()).reverse (); // LE
  __EXPECT_EQUAL (key.length, key_length, `bad data: key length`);
  const body = util_uint8array_xor (key, data.slice (head_length + key_length)), body_view = new DataView (body.buffer);
  const packed_size = body_view.getUint32 (0, true), unpacked_size = body_view.getUint32 (4, true);

  const packed_data = body.slice (8), packed_length = packed_data.length;
  __EXPECT_EQUAL (packed_length, packed_size, `bad data: packed size`);
  __EXPECT_EQUAL (packed_data.slice (-1 * last_bytes.length).every ((byte, index) => byte == last_bytes [index]), true, `bad data: end bytes`);

  const unpacked_data = LZ01X_Implementation.decompress (packed_data, { blockSize: 16384 }), unpacked_length = unpacked_data.length;
  __EXPECT_EQUAL (unpacked_length, unpacked_size, `bad data: unpacked size`);
  const unpacked_view = new DataView (unpacked_data.buffer), unpacked_byte = unpacked_view.getUint8 (0);
  __EXPECT_TRUE (unpacked_byte > BLOCK_TYPE.ONE && unpacked_byte <= BLOCK_TYPE.TWO, `bad data: compressed series does not start with type-1`);

  const stat = { packed_size, packed_length, unpacked_size, unpacked_length, key_length: key.length };

  return { head, data: unpacked_data, stat };
}

function __mq_datfile_decode_data (data) {
  const { BLOCK_TYPE, BLOCK_SIZE } = __mq_datafile_spec;

  const __bar_base = (time, open, high_, low_, close_, volume) => ({
    time, open, high: open + high_, low: open - low_, close: open + close_, volume
  });
  const __bar_full = (view, addr) => __bar_base (view.getUint32 (addr, true) * 1000,
    view.getUint32 (addr + 4, true), view.getUint16 (addr + 8, true), view.getUint16 (addr + 10, true), view.getInt16 (addr + 12, true),
    view.getUint16 (addr + 14, true));
  const __bar_incr = (view, addr, last, time) => __bar_base (last.time + (time * 60000),
    last.close + view.getInt8 (addr), view.getUint8 (addr + 1), view.getUint8 (addr + 2), view.getInt8 (addr + 3),
    view.getUint8 (addr + 4));

  const view = new DataView (data.buffer), head = view.getUint8 (0);
  __EXPECT_TRUE (head > BLOCK_TYPE.ONE && head <= BLOCK_TYPE.TWO, `bad data: compressed series does not start with type-1`);

  var addr = 0, last = undefined, offs = 0;
  const bars = [], stop = data.length;

  while (addr < stop) {
    const byte = view.getUint8 (addr ++), left = stop - addr;
    if (byte > BLOCK_TYPE.THREE) { // Type-3 Block
      const size = (byte - BLOCK_TYPE.THREE);
      __EXPECT_TRUE (left >= BLOCK_SIZE.THREE * size, `bad data: short block, type-3, addr ${addr - 1}, stop ${stop}`);
      for (offs = 0; offs < size; offs ++, addr += BLOCK_SIZE.THREE)
        bars.push (last = { ...__bar_incr (view, addr, last, 1), type: 3, addr });
    } else if (byte > BLOCK_TYPE.TWO) { // Type-2 Block
      const size = (byte - BLOCK_TYPE.TWO);
      __EXPECT_TRUE (left >= BLOCK_SIZE.TWO * size, `bad data: short block, type-2, addr ${addr - 1}, stop ${stop}`);
      for (offs = 0; offs < size; offs ++, addr += BLOCK_SIZE.TWO)
        bars.push (last = { ...__bar_incr (view, addr + 1, last, view.getInt8 (addr)), type: 2, addr });
    } else if (byte > BLOCK_TYPE.ONE) { // Type-1 Block
      const size = (byte - BLOCK_TYPE.ONE);
      __EXPECT_TRUE (left >= BLOCK_SIZE.ONE * size, `bad data: short block, type-1, addr ${addr - 1}, stop ${stop}`);
      for (offs = 0; offs < size; offs ++, addr += BLOCK_SIZE.ONE)
        bars.push (last = { ...__bar_full  (view, addr), type: 1, addr });
    } else {
      const char = (byte >= 32 && byte <= 126) ? String.fromCharCode (byte) : '.', time = util_date_epochToStr_ISO (last.time);
      __EXPECT_FAIL (`bad data: compressed series has unexpected type byte ${byte} '${char}' at addr ${addr} [last=${time}]`);
    }
  }
  __EXPECT_EQUAL (addr, stop, `bad data: short block at end`);

  return bars;
}

function __mq_datfile_issues (bars) {
  return bars.map (bar => {
    const { open, high, low, close, time, type, addr } = bar, issues = [];
    if (open > high || open < low)
      issues.push ('open');
    if ([ open, high, low, close ].max () !== high)
      issues.push ('high');
    if ([ open, high, low, close ].min () !== low)
      issues.push ('low');
    if (close > high || close < low)
      issues.push ('close');
    return (issues.length > 0) &&
      `[ISSUE] ${util_date_epochToStr_ISO (time)} type-${type} ${addr.toString (16)} ${issues.join (' ')} [${util_obj_strNice (bar)}]`;
  }).filter (Boolean);
}

// -----------------------------------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------------------------

function util_uint8array_to_hexdump (data, maximum = Number.POSITIVE_INFINITY) {
  const lines = [], width = 16, address = 8;
  const char_blank = ' ', code_blank = '  ', code_space = ' ', char_space = '';
  const char_printable = (char) => (char >= 32 && char <= 126) ? String.fromCharCode (char) : '.';
  const code_hexstring = (byte) => byte.toString (16).padStart (2, '0');
  for (var block = 0, length = Math.min (data.length, maximum); block < length; block += width) {
    const codes = [], chars = [];
    for (var index = 0, offs = block; index < width; index ++, offs ++) {
      codes.push (offs < length ? code_hexstring (data [offs]) : code_blank);
      chars.push (offs < length ? char_printable (data [offs]) : char_blank);
    }
    lines.push ([
      `${block.toString (16).padStart (address, '0')}:`,
      `${codes.slice (0, codes.length / 2).join (code_space)}${code_blank}${codes.slice (codes.length / 2).join (code_space)}`,
      `|${chars.slice (0, chars.length / 2).join (char_space)}${char_blank}${chars.slice (chars.length / 2).join (char_space)}|`
    ].join (' '));
  }
  return lines.join ('\n');
}
function util_uint8array_join (arrays) {
  return arrays.reduce (({ offset, result }, array) => { result.set (array, offset); offset += array.length; return { offset, result }; },
    { offset: 0, result: new Uint8Array (arrays.map (array => array.length).sum ())}).result;
}
function util_uint8array_xor (key, array) {
  for (var i = 0, j = 0, k = key.length; i < array.length; i ++, j = (j + 1) % k)
    array [i] ^= key [j];
  return array;
}
function util_uint8array_to_arraybuffer (array) {
  return new Uint8Array (array).buffer;
  return (array.constructor.name === 'Uint8Array') ? array.buffer: new Uint8Array (array).buffer;
}
function util_arraybuffer_to_uint8array (buffer) {
  return new Uint8Array (buffer);
}
function util_uint8array_to_base16 (array) {
  return Array.from (array, byte => byte.toString (16).padStart (2, '0')).join ('');
}
function util_base16_to_uint8array (string) {
  const array = new Uint8Array (string.length / 2);
  for (let i = 0, j = 0; i < string.length; i += 2, j ++)
    array [j] = Number.parseInt (string.slice (i, i + 2), 16);
  return array;
}

function util_hash_md5 (x) {
  return require ('node:crypto').createHash ('MD5').update (x).digest ();
}
function util_hash_md5 (x) {
  return new Uint8Array (Utilities.computeDigest (Utilities.DigestAlgorithm.MD5, x));
}

// -----------------------------------------------------------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------------------------------------------------------
gfic-limited commented 3 months ago

Just as a question, how did you determine the file format, and what is the meaning of any control bytes that less than 0x3f, i.e. type-0 or something?

kenorb commented 3 months ago

Just as a question, how did you determine the file format, and what is the meaning of any control bytes that less than 0x3f, i.e. type-0 or something?

There are no available docs for this format, so at first it was with help of some friend a while back then by trial and error. He had some software already to read this format and gave us some hints.

gfic-limited commented 3 months ago

Got it. In any case, it seems there is no data that has a type-0 style entry. I screened all of the files on the history server from 2020 onwards (not interested in anything before that). Initially I had a bug which suggested type-0 data (stupid javascript in place array annoyance).

From 2015 onwards. All good;

-rw-r--r-- 1  17M Apr 18 19:17 cache/AUDCAD-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:17 cache/AUDCHF-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:17 cache/AUDJPY-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:17 cache/AUDNZD-metaquotes-202403-2015.dat
-rw-r--r-- 1 4.7M Apr 18 19:23 cache/AUDSGD-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:23 cache/AUDUSD-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:23 cache/CADCHF-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:24 cache/CADJPY-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:24 cache/CHFJPY-metaquotes-202403-2015.dat
-rw-r--r-- 1 4.6M Apr 18 19:24 cache/CHFSGD-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:25 cache/EURAUD-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:25 cache/EURCAD-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:26 cache/EURCHF-metaquotes-202403-2015.dat
-rw-r--r-- 1  25M Apr 18 19:26 cache/EURCZK-metaquotes-202403-2015.dat
-rw-r--r-- 1  12M Apr 18 19:27 cache/EURDKK-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:27 cache/EURGBP-metaquotes-202403-2015.dat
-rw-r--r-- 1  21M Apr 18 19:28 cache/EURHKD-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:28 cache/EURHUF-metaquotes-202403-2015.dat
-rw-r--r-- 1  19M Apr 18 19:29 cache/EURJPY-metaquotes-202403-2015.dat
-rw-r--r-- 1  28M Apr 18 19:29 cache/EURNOK-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:30 cache/EURNZD-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:30 cache/EURPLN-metaquotes-202403-2015.dat
-rw-r--r-- 1  25M Apr 18 19:31 cache/EURSEK-metaquotes-202403-2015.dat
-rw-r--r-- 1 4.9M Apr 18 19:31 cache/EURSGD-metaquotes-202403-2015.dat
-rw-r--r-- 1  29M Apr 18 19:31 cache/EURTRY-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:32 cache/EURUSD-metaquotes-202403-2015.dat
-rw-r--r-- 1  37M Apr 18 19:32 cache/EURZAR-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:33 cache/GBPAUD-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:33 cache/GBPCAD-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:34 cache/GBPCHF-metaquotes-202403-2015.dat
-rw-r--r-- 1 6.9M Apr 18 19:34 cache/GBPDKK-metaquotes-202403-2015.dat
-rw-r--r-- 1  19M Apr 18 19:34 cache/GBPJPY-metaquotes-202403-2015.dat
-rw-r--r-- 1  34M Apr 18 19:35 cache/GBPNOK-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:35 cache/GBPNZD-metaquotes-202403-2015.dat
-rw-r--r-- 1  32M Apr 18 19:36 cache/GBPSEK-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:36 cache/GBPSGD-metaquotes-202403-2015.dat
-rw-r--r-- 1 4.9M Apr 18 19:37 cache/GBPTRY-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:37 cache/GBPUSD-metaquotes-202403-2015.dat
-rw-r--r-- 1 4.6M Apr 18 19:37 cache/NOKJPY-metaquotes-202403-2015.dat
-rw-r--r-- 1 4.9M Apr 18 19:37 cache/NOKSEK-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:38 cache/NZDCAD-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:38 cache/NZDCHF-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:38 cache/NZDJPY-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:39 cache/NZDUSD-metaquotes-202403-2015.dat
-rw-r--r-- 1 4.6M Apr 18 19:39 cache/SEKJPY-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:40 cache/SGDJPY-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:40 cache/USDCAD-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:41 cache/USDCHF-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:41 cache/USDCNH-metaquotes-202403-2015.dat
-rw-r--r-- 1  36M Apr 18 19:42 cache/USDCZK-metaquotes-202403-2015.dat
-rw-r--r-- 1  22M Apr 18 19:42 cache/USDDKK-metaquotes-202403-2015.dat
-rw-r--r-- 1  16M Apr 18 19:42 cache/USDHKD-metaquotes-202403-2015.dat
-rw-r--r-- 1  19M Apr 18 19:43 cache/USDHUF-metaquotes-202403-2015.dat
-rw-r--r-- 1  18M Apr 18 19:43 cache/USDJPY-metaquotes-202403-2015.dat
-rw-r--r-- 1  38M Apr 18 19:44 cache/USDMXN-metaquotes-202403-2015.dat
-rw-r--r-- 1  31M Apr 18 19:44 cache/USDNOK-metaquotes-202403-2015.dat
-rw-r--r-- 1  21M Apr 18 19:45 cache/USDPLN-metaquotes-202403-2015.dat
-rw-r--r-- 1 5.3M Apr 18 19:45 cache/USDRUB-metaquotes-202402-2015.dat
-rw-r--r-- 1  29M Apr 18 19:46 cache/USDSEK-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:46 cache/USDSGD-metaquotes-202403-2015.dat
-rw-r--r-- 1 6.7M Apr 18 19:46 cache/USDTHB-metaquotes-202403-2015.dat
-rw-r--r-- 1  27M Apr 18 19:47 cache/USDTRY-metaquotes-202403-2015.dat
-rw-r--r-- 1  39M Apr 18 19:48 cache/USDZAR-metaquotes-202403-2015.dat
-rw-r--r-- 1 4.2M Apr 18 19:48 cache/XAGEUR-metaquotes-202403-2015.dat
-rw-r--r-- 1  16M Apr 18 19:48 cache/XAGUSD-metaquotes-202403-2015.dat
-rw-r--r-- 1 4.5M Apr 18 19:48 cache/XAUAUD-metaquotes-202403-2015.dat
-rw-r--r-- 1 4.4M Apr 18 19:48 cache/XAUEUR-metaquotes-202403-2015.dat
-rw-r--r-- 1  17M Apr 18 19:49 cache/XAUUSD-metaquotes-202403-2015.dat
-rw-r--r-- 1  27M Apr 18 19:49 cache/XPDUSD-metaquotes-202403-2015.dat
-rw-r--r-- 1  30M Apr 18 19:49 cache/XPTUSD-metaquotes-202403-2015.dat
-rw-r--r-- 1 5.0M Apr 18 19:49 cache/ZARJPY-metaquotes-202403-2015.dat