UNPKG

sfccxt

Version:

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

270 lines (245 loc) 9.82 kB
/* eslint-disable max-classes-per-file */ 'use strict'; // ---------------------------------------------------------------------------- // // 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, // make it invisible value: new Float64Array (SEED), writable: true, }) Object.defineProperty (this, 'depth', { __proto__: null, // make it invisible 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, // make it invisible value: new Map (), writable: true, }) Object.defineProperty (this, 'index', { __proto__: null, // make it invisible value: new Float64Array (SEED), writable: true, }) Object.defineProperty (this, 'depth', { __proto__: null, // make it invisible 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 }} // ---------------------------------------------------------------------------- module.exports = { // basic Asks, Bids, OrderBookSide, // count-based CountedAsks, CountedBids, CountedOrderBookSide, // order-id based IndexedAsks, IndexedBids, IndexedOrderBookSide, }