UNPKG

@proton/ccxt

Version:

A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 130+ exchanges

266 lines (262 loc) 9.94 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); /* eslint-disable max-classes-per-file */ // @ts-nocheck // ---------------------------------------------------------------------------- // // Upto 10x faster after initializing memory for the floating point array // Author: github.com/frosty00 // Email: carlo.revelli@berkeley.edu // function bisectLeft(array, x) { let low = 0; let high = array.length - 1; while (low <= high) { const mid = (low + high) >>> 1; if (array[mid] - x < 0) low = mid + 1; else high = mid - 1; } return low; } const SIZE = 1024; const SEED = new Float64Array(new Array(SIZE).fill(Number.MAX_VALUE)); class OrderBookSide extends Array { constructor(deltas = [], depth = undefined) { super(); // a string-keyed dictionary of price levels / ids / indices Object.defineProperty(this, 'index', { __proto__: null, value: new Float64Array(SEED), writable: true, }); Object.defineProperty(this, 'depth', { __proto__: null, value: depth || Number.MAX_SAFE_INTEGER, writable: true, }); // sort upon initiation this.length = 0; for (let i = 0; i < deltas.length; i++) { this.storeArray(deltas[i].slice()); // slice is muy importante } } storeArray(delta) { const price = delta[0]; const size = delta[1]; const index_price = this.side ? -price : price; const index = bisectLeft(this.index, index_price); if (size) { if (this.index[index] === index_price) { this[index][1] = size; } else { this.length++; this.index.copyWithin(index + 1, index, this.index.length); this.index[index] = index_price; this.copyWithin(index + 1, index, this.length); this[index] = delta; // in the rare case of very large orderbooks being sent if (this.length > this.index.length - 1) { const existing = Array.from(this.index); existing.length = this.length * 2; existing.fill(Number.MAX_VALUE, this.index.length); this.index = new Float64Array(existing); } } } else if (this.index[index] === index_price) { this.index.copyWithin(index, index + 1, this.index.length); this.index[this.length - 1] = Number.MAX_VALUE; this.copyWithin(index, index + 1, this.length); this.length--; } } // index an incoming delta in the string-price-keyed dictionary store(price, size) { this.storeArray([price, size]); } // replace stored orders with new values limit() { if (this.length > this.depth) { for (let i = this.depth; i < this.length; i++) { this.index[i] = Number.MAX_VALUE; } this.length = this.depth; } } } // ---------------------------------------------------------------------------- // overwrites absolute volumes at price levels // or deletes price levels based on order counts (3rd value in a bidask delta) // this class stores vector arrays of values indexed by price class CountedOrderBookSide extends OrderBookSide { store(price, size, count) { this.storeArray([price, size, count]); } storeArray(delta) { const price = delta[0]; const size = delta[1]; const count = delta[2]; const index_price = this.side ? -price : price; const index = bisectLeft(this.index, index_price); if (size && count) { if (this.index[index] === index_price) { const entry = this[index]; entry[1] = size; entry[2] = count; } else { this.length++; this.index.copyWithin(index + 1, index, this.index.length); this.index[index] = index_price; this.copyWithin(index + 1, index, this.length); this[index] = delta; // in the rare case of very large orderbooks being sent if (this.length > this.index.length - 1) { const existing = Array.from(this.index); existing.length = this.length * 2; existing.fill(Number.MAX_VALUE, this.index.length); this.index = new Float64Array(existing); } } } else if (this.index[index] === index_price) { this.index.copyWithin(index, index + 1, this.index.length); this.index[this.length - 1] = Number.MAX_VALUE; this.copyWithin(index, index + 1, this.length); this.length--; } } } // ---------------------------------------------------------------------------- // stores vector arrays indexed by id (3rd value in a bidask delta array) class IndexedOrderBookSide extends Array { constructor(deltas = [], depth = Number.MAX_SAFE_INTEGER) { super(deltas.length); // a string-keyed dictionary of price levels / ids / indices Object.defineProperty(this, 'hashmap', { __proto__: null, value: new Map(), writable: true, }); Object.defineProperty(this, 'index', { __proto__: null, value: new Float64Array(SEED), writable: true, }); Object.defineProperty(this, 'depth', { __proto__: null, value: depth || Number.MAX_SAFE_INTEGER, writable: true, }); // sort upon initiation for (let i = 0; i < deltas.length; i++) { this.length = i; this.storeArray(deltas[i].slice()); // slice is muy importante } } store(price, size, id) { this.storeArray([price, size, id]); } storeArray(delta) { const price = delta[0]; const size = delta[1]; const id = delta[2]; let index_price; if (price !== undefined) { index_price = this.side ? -price : price; } else { index_price = undefined; } if (size) { if (this.hashmap.has(id)) { const old_price = this.hashmap.get(id); index_price = index_price || old_price; // in case price is not sent delta[0] = Math.abs(index_price); if (index_price === old_price) { const index = bisectLeft(this.index, index_price); this.index[index] = index_price; this[index] = delta; return; } else { // remove old price from index const old_index = bisectLeft(this.index, old_price); this.index.copyWithin(old_index, old_index + 1, this.index.length); this.index[this.length - 1] = Number.MAX_VALUE; this.copyWithin(old_index, old_index + 1, this.length); this.length--; } } // insert new price level this.hashmap.set(id, index_price); const index = bisectLeft(this.index, index_price); // insert new price level into index this.length++; this.index.copyWithin(index + 1, index, this.index.length); this.index[index] = index_price; this.copyWithin(index + 1, index, this.length); this[index] = delta; // in the rare case of very large orderbooks being sent if (this.length > this.index.length - 1) { const existing = Array.from(this.index); existing.length = this.length * 2; existing.fill(Number.MAX_VALUE, this.index.length); this.index = new Float64Array(existing); } } else if (this.hashmap.has(id)) { const old_price = this.hashmap.get(id); const index = bisectLeft(this.index, old_price); this.index.copyWithin(index, index + 1, this.index.length); this.index[this.length - 1] = Number.MAX_VALUE; this.copyWithin(index, index + 1, this.length); this.length--; this.hashmap.delete(id); } } // replace stored orders with new values limit() { if (this.length > this.depth) { for (let i = this.depth; i < this.length; i++) { // diff this.hashmap.delete(this.index[i]); this.index[i] = Number.MAX_VALUE; } this.length = this.depth; } } } // ---------------------------------------------------------------------------- // a more elegant syntax is possible here, but native inheritance is portable class Asks extends OrderBookSide { get side() { return false; } } class Bids extends OrderBookSide { get side() { return true; } } class CountedAsks extends CountedOrderBookSide { get side() { return false; } } class CountedBids extends CountedOrderBookSide { get side() { return true; } } class IndexedAsks extends IndexedOrderBookSide { get side() { return false; } } class IndexedBids extends IndexedOrderBookSide { get side() { return true; } } exports.Asks = Asks; exports.Bids = Bids; exports.CountedAsks = CountedAsks; exports.CountedBids = CountedBids; exports.CountedOrderBookSide = CountedOrderBookSide; exports.IndexedAsks = IndexedAsks; exports.IndexedBids = IndexedBids; exports.IndexedOrderBookSide = IndexedOrderBookSide; exports.OrderBookSide = OrderBookSide;