Skip to content

Instantly share code, notes, and snippets.

@matthewgream
Created April 17, 2024 12:05
Show Gist options
  • Save matthewgream/1a3bf613424f3c7c499727afb3988a3f to your computer and use it in GitHub Desktop.
Save matthewgream/1a3bf613424f3c7c499727afb3988a3f to your computer and use it in GitHub Desktop.
function __mq_datfile_decode_data (buffer) {
const __bar_full = (view, addr, open = view.getUint32 (addr + 4, true)) => ({
time: (new Date (view.getUint32 (addr, true) * 1000)).getTime (),
open,
high: open + view.getUint16 (addr + 8, true),
low: open - view.getUint16 (addr + 10, true),
close: open + view.getInt16 (addr + 12, true),
volume: view.getUint16 (addr + 14, true)
});
const __bar_incr = (view, addr, last, time = 1, open = last.close + view.getInt8 (addr)) => ({
time: (new Date (last.time + (time * 60000))).getTime (),
open,
high: open + view.getUint8 (addr + 1),
low: open - view.getUint8 (addr + 2),
close: open + view.getInt8 (addr + 3),
volume: view.getUint8 (addr + 4)
});
const view = new DataView (buffer);
if (!(view.getUint8 (0) > 0x3F && view.getUint8 (0) <= 0x7F))
__THROW (`bad data: compressed series does not start with type-1`);
let addr = 0, last = undefined;
const bars = [], stop = view.byteLength;
while (addr < stop) {
const byte = view.getUint8 (addr ++);
if (byte > 0xBF) // Type-3 Block
for (let offs = 0; offs < (byte - 0xBF); offs ++, addr += 5)
bars.push (last = { ...__bar_incr (view, addr, last), type: 3, addr });
else if (byte > 0x7F) // Type-2 Block
for (let offs = 0; offs < (byte - 0x7F); offs ++, addr += 6)
bars.push (last = { ...__bar_incr (view, addr + 1, last, view.getInt8 (addr)), type: 2, addr });
else if (byte > 0x3F) // Type-1 Block
for (let offs = 0; offs < (byte - 0x3F); offs ++, addr += 16)
bars.push (last = { ...__bar_full (view, addr), type: 1, addr });
}
return bars;
}
function __mq_datfile_checks (bars) {
return bars.map (bar => {
const anomalies = [], prices = [ bar.open, bar.high, bar.low, bar.close ];
if (bar.open > bar.high || bar.open < bar.low)
anomalies.push ("open");
if (prices.max () !== bar.high)
anomalies.push ("high");
if (prices.min () !== bar.low)
anomalies.push ("low");
if (bar.close > bar.high || bar.close < bar.low)
anomalies.push ("close");
return (anomalies.length > 0) &&
`[ANOMALY] ${util_date_epochToStr_ISO (bar.time)} type-${bar.type} ${bar.addr.toString (16)} ${anomalies.join (' ')} [${util_obj_strNice (bar)}]`;
}).filter (Boolean);
}
function __mq_datfile_decode_key (bytes) {
const k = '0x' + util_uint8array_to_base16 (bytes), e = 17, m = '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 ('');
const r = (BigInt (k) ** BigInt (e)) % BigInt (m);
return util_base16_to_uint8array (r.toString (16).slice (-128*2).padStart (128*2, '0'));
}
function __mq_datfile_decode_body (buffer) {
const head_length = 0x88, key_length = 0x80, last_bytes = [ 0x11, 0x00, 0x00 ];
const content = new Uint8Array (buffer);
const head = content.slice (0, head_length);
const key = __mq_datfile_decode_key (content.slice (head_length, head_length + key_length).reverse ()).reverse (); // LE
if (key.length != key_length)
__THROW (`bad data: key length, got ${key.length}, expected ${key_length}`);
const body = util_uint8array_xor (key, content.slice (head_length + key_length));
const view = new DataView (body.buffer);
const packed_size = view.getUint32 (0, true), unpacked_size = view.getUint32 (4, true);
if (packed_size != (body.length - 8))
__THROW (`bad data: packed_size, got ${body.length - 8}, expected ${packed_size}`);
if (!body.slice (-1 * last_bytes.length).every ((byte, offset) => (byte == last_bytes [offset])))
__THROW (`bad data: end bytes, got [${body.slice (-1 * last_bytes.length).join (',')}], expected ${last_bytes.join (',')}`);
const stat = { packed_size, unpacked_size, body_length: body.length - 8, key_length: key.length };
const data = LZ01X_Implementation.decompress (body.slice (8), { blockSize: 16384 });
if (unpacked_size != data.length)
__THROW (`bad data: unpacked_size, got ${data.length}, expected ${unpacked_size}`);
return { head, data, stat };
}
function util_uint8array_to_base16 (arr) {
return Array.from (arr, byte => byte.toString (16).padStart (2, '0')).join ('');
}
function util_base16_to_uint8array (str) {
const bytes = [];
for (var i = 0, l = str.length; i < l; i += 2)
bytes.push (Number.parseInt (str.slice (i, i + 2), 16));
return new Uint8Array (bytes);
}
function util_uint8array_xor (key, arr) {
for (var i = 0, j = 0, l = arr.length, k = key.length; i < l; i ++, j = (j + 1) % k)
arr [i] ^= key [j];
return arr;
}
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_buffer_tohex (util_hash_md5 (data)) === csum;
const __mq_history_spec = (name) => name.match (/^(?<symbol>\w+)_(?<year>\d{4})_(?<month>\d{2})_(?<checksum>[\da-f]{32})\.dat$/)?.groups || {};
function mq_history_list (symbol) {
return util_exception_catcher (() => system_connect.request (util_function_name (icmarkets_history_list), __mq_history_link_list (symbol)))
?.split ('\n').filter (Boolean).map (name => __mq_history_spec (name));
}
function mq_history_data (spec) {
const buffer = system_connect.request (util_function_name (icmarkets_history_data), typeof spec === 'string' ? spec : __mq_history_link_data (spec), {}, 'arraybuffer');
if (buffer && __mq_history_csum (buffer, typeof spec === 'string' ? spec.match (/_(?<checksum>[\da-f]{32})\.dat$/)?.groups?.checksum : spec.checksum)) return buffer;
return undefined;
}
...
const buffer = mq_history_data ('http://history.metaquotes.net/symbols/EURJPY/EURJPY_2023_09_3d2055bf5d8d3e8700c35587d612f64b.dat');
const { head, data, stat } = __mq_datfile_decode_body (buffer);
console.log (stat);
console.log (util_buffer_toString (head));
const bars = __mq_datfile_decode_data (data.buffer);
console.log ({ bars: bars.length, first: util_date_epochToStr_ISO (bars.first ().time), final: util_date_epochToStr_ISO (bars.final ().time) });
__mq_datfile_checks (bars).forEach (result => console.log (result));
console.log ('***');
bars.forEach (bar => console.log (util_obj_strNiceOrdered ({ ...bar, timestamp: util_date_epochToStr_ISO (bar.time) }, ['timestamp', 'open', 'high', 'low', 'close', 'volume', 'type', 'addr'])));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment