Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Socket API example with orderbook
if (typeof WebSocket !== 'function') {
// for node.js install ws package
WebSocket = require('ws');
}
const logger = {
debug: (...arg) => {
// console.log((new Date).toISOString(), 'DEBUG', ...arg)
},
info: (...arg) => {
console.log((new Date).toISOString(), 'INFO', ...arg)
},
warn: (...arg) => {
console.log((new Date).toISOString(), 'WARN', ...arg)
}
};
class SocketClient {
constructor(onConnected) {
this._id = 1;
this._createSocket();
this._onConnected = onConnected;
this._promises = new Map();
this._handles = new Map();
}
_createSocket() {
this._ws = new WebSocket('wss://api.hitbtc.com/api/2/ws');
this._ws.onopen = () => {
logger.info('ws connected');
this._onConnected();
};
this._ws.onclose = () => {
logger.warn('ws closed');
this._promises.forEach((cb, id) => {
this._promises.delete(id);
cb.reject(new Error('Disconnected'));
});
setTimeout(() => this._createSocket(), 500);
};
this._ws.onerror = err => {
logger.warn('ws error', err);
};
this._ws.onmessage = msg => {
logger.debug('<', msg.data);
try {
const message = JSON.parse(msg.data);
if (message.id) {
if (this._promises.has(message.id)) {
const cb = this._promises.get(message.id);
this._promises.delete(message.id);
if (message.result) {
cb.resolve(message.result);
} else if (message.error) {
cb.reject(message.error);
} else {
logger.warn('Unprocessed response', message)
}
}
} else if (message.method && message.params) {
if (this._handles.has(message.method)) {
this._handles.get(message.method).forEach(cb => {
cb(message.params);
});
} else {
logger.warn('Unprocessed method', message);
}
} else {
logger.warn('Unprocessed message', message);
}
} catch (e) {
logger.warn('Fail parse message', e);
}
}
}
request(method, params = {}) {
if (this._ws.readyState === WebSocket.OPEN) {
return new Promise((resolve, reject) => {
const requestId = ++this._id;
this._promises.set(requestId, {resolve, reject});
const msg = JSON.stringify({method, params, id: requestId});
logger.debug('>', msg);
this._ws.send(msg);
setTimeout(() => {
if (this._promises.has(requestId)) {
this._promises.delete(requestId);
reject(new Error('Timeout'));
}
}, 10000);
});
} else {
return Promise.reject(new Error('WebSocket connection not established'))
}
}
setHandler(method, callback) {
if (!this._handles.has(method)) {
this._handles.set(method, []);
}
this._handles.get(method).push(callback);
}
}
function updateIndex(sortedArray, item, index) {
if (index < sortedArray.length && sortedArray[index].price === item.price) {
if (item.size === 0) {
sortedArray.splice(index, 1);
} else {
sortedArray[index].size = item.size;
}
} else if (item.size !== 0) {
sortedArray.splice(index, 0, item);
}
return index === 0;
}
function getSortedIndex(array, value, inverse) {
inverse = Boolean(inverse);
let low = 0, high = array ? array.length : low;
while (low < high) {
let mid = (low + high) >>> 1;
if ((!inverse && (+array[mid].price < +value)) || (inverse && (+array[mid].price > +value))) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
class OrderBookStore {
constructor(onChangeBest) {
this._data = {};
this._onChangeBest = onChangeBest;
}
getOrderBook(symbol) {
return this._data[symbol.toString()];
}
snapshotOrderBook(symbol, ask, bid) {
this._data[symbol.toString()] = {
ask: ask,
bid: bid
};
}
updateOrderBook(symbol, ask, bid) {
const data = this._data[symbol.toString()];
if (data) {
let bestChanged = false;
ask.forEach(function (v) {
bestChanged |= updateIndex(data.ask, v, getSortedIndex(data.ask, v.price));
});
bid.forEach(function (v) {
bestChanged |= updateIndex(data.bid, v, getSortedIndex(data.bid, v.price, true));
});
if (bestChanged && this._onChangeBest) {
this._onChangeBest(symbol, data.ask.length > 0 ? data.ask[0].price : null, data.bid.length > 0 ? data.bid[0].price : null);
}
}
}
}
function generateRandom() {
let d = Date.now();
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
logger.info('Start application');
const orderBooks = new OrderBookStore((symbol, bestASk, bestBid) => {
logger.info('New best orderbook', symbol, bestASk, bestBid);
});
const socketApi = new SocketClient(async () => {
try {
const symbols = await socketApi.request('getSymbols');
const subscribeSymbols = symbols.filter(s => s.baseCurrency === 'BTC');
for (let s of subscribeSymbols) {
logger.info('Subscribe to orderbook', s.id);
await socketApi.request('subscribeOrderbook', {symbol: s.id});
}
// try to auth place your keys
await socketApi.request('login', {"algo": "BASIC",
"pKey": "3ef4a9f8c8bf04bd8f09884b98403eae",
"sKey": "2deb570ab58fd553a4ed3ee249fd2d51"});
const balance = await socketApi.request('getTradingBalance');
logger.info('balance',balance.filter(b => b.available !== '0' || b.reserved !== '0'));
// place order
await socketApi.request('subscribeReports');
await socketApi.request('newOrder', {clientOrderId: generateRandom(), symbol: 'BTCUSD', side: 'sell', price: '17777.00', quantity: '0.01'});
} catch (e) {
logger.warn(e);
}
});
socketApi.setHandler('snapshotOrderbook', params => {
orderBooks.snapshotOrderBook(params.symbol, params.ask, params.bid);
});
socketApi.setHandler('updateOrderbook', params => {
orderBooks.updateOrderBook(params.symbol, params.ask, params.bid);
});
@i--storm

This comment has been minimized.

Copy link

i--storm commented Dec 11, 2018

updateIndex compares string variable item.size to zero in strict mode
it should look like so:

function updateIndex(sortedArray, item, index) {
if (index < sortedArray.length && sortedArray[index].price === item.price) {
if (parseFloat(item.size) === 0) {
sortedArray.splice(index, 1);
} else {
sortedArray[index].size = item.size;
}
} else if (parseFloat(item.size) !== 0) {
sortedArray.splice(index, 0, item);
}
return index === 0;
}

@StasToken

This comment has been minimized.

Copy link

StasToken commented May 10, 2019

Why not do whatever always given snapshot, they know what they have always orders. And orders that have already been closed will not disappear

@fwiessner

This comment has been minimized.

Copy link

fwiessner commented May 29, 2019

This code just does not seem to work right, i always end up with some parts of the orderbook not beeing updated anymore.

@StasToken

This comment has been minimized.

Copy link

StasToken commented May 30, 2019

This code just does not seem to work right, i always end up with some parts of the orderbook not beeing updated anymore.

I've been banging the brain and revealed the secret, they have closed orders given volume 0
It's completely idiotic, but I'll tell you how it works:

  • receive modified orders
  • sort them in ascending or descending order
  • after sorting, those orders that are value === 0 remove
  • order book update is finished!

don't forget to use the patch from i--storm which he pointed out above

PS: I don't know why these idiots did, this game full! but on the other to get the order book update will not work

@fwiessner

This comment has been minimized.

Copy link

fwiessner commented May 30, 2019

Yes, i know that order volume 0 means to close the order, that is not the problem. The problem is after running a while, asks gets lower than bids or vice versa and one side never seems to get updated anymore. I use ccxws with this orderbook implementation here but it does not work quite well, mostly because the index is maintained as sometimes as float and sometime as string which is weird, it then cannot update the index if i got a price like 7700.230000 and an update like 7700.23 the update fails because it seems index is handled as a string?

@StasToken

This comment has been minimized.

Copy link

StasToken commented May 30, 2019

Why don't you use type conversion?

let string = '7700.230000'; // "7700.230000" let number = Number(string); // 7700.23

In General, all input data is converted to number and then there should be no problem...

@valamidev

This comment has been minimized.

Copy link

valamidev commented Aug 9, 2019

I made an NPM package based on the logic of this implementation and solve some issues what I discovered:

https://www.npmjs.com/package/orderbook-synchronizer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.