diff --git a/README.md b/README.md index 44591dee..d6d2bf62 100644 --- a/README.md +++ b/README.md @@ -353,6 +353,21 @@ console.log(await client.getOrder({ Output ```js +{ + symbol: 'ENGETH', + orderId: 191938, + clientOrderId: '1XZTVBTGS4K1e', + price: '0.00138000', + origQty: '1.00000000', + executedQty: '1.00000000', + status: 'FILLED', + timeInForce: 'GTC', + type: 'LIMIT', + side: 'SELL', + stopPrice: '0.00000000', + icebergQty: '0.00000000', + time: 1508611114735 +} ``` @@ -428,6 +443,21 @@ console.log(await client.allOrders({ Output ```js +[{ + symbol: 'ENGETH', + orderId: 191938, + clientOrderId: '1XZTVBTGS4K1e', + price: '0.00138000', + origQty: '1.00000000', + executedQty: '1.00000000', + status: 'FILLED', + timeInForce: 'GTC', + type: 'LIMIT', + side: 'SELL', + stopPrice: '0.00000000', + icebergQty: '0.00000000', + time: 1508611114735 +}] ``` @@ -437,7 +467,7 @@ console.log(await client.allOrders({ Get current account information. ```js -console.log(await client.openOrders()) +console.log(await client.accountInfo()) ``` |Param|Type|Required| @@ -448,6 +478,19 @@ console.log(await client.openOrders()) Output ```js +{ + makerCommission: 10, + takerCommission: 10, + buyerCommission: 0, + sellerCommission: 0, + canTrade: true, + canWithdraw: true, + canDeposit: true, + balances: [ + { asset: 'BTC', free: '0.00000000', locked: '0.00000000' }, + { asset: 'LTC', free: '0.00000000', locked: '0.00000000' }, + ] +} ``` @@ -473,6 +516,18 @@ console.log(await client.myTrades({ Output ```js +[{ + id: 9960, + orderId: 191939, + price: '0.00138000', + qty: '10.00000000', + commission: '0.00001380', + commissionAsset: 'ETH', + time: 1508611114735, + isBuyer: false, + isMaker: false, + isBestMatch: true +}] ``` @@ -481,7 +536,8 @@ console.log(await client.myTrades({ #### depth -Live depth market data feed for a given symbol. +Live depth market data feed. The first parameter can either +be a single symbol string or an array of symbols. ```js client.ws.depth('ETHBTC', depth => { @@ -489,9 +545,32 @@ client.ws.depth('ETHBTC', depth => { }) ``` +
+Output + +```js +{ + eventType: 'depthUpdate', + eventTime: 1508612956950, + symbol: 'ETHBTC', + updateId: 18331140, + bidDepth: [ + { price: '0.04896500', quantity: '0.00000000' }, + { price: '0.04891100', quantity: '15.00000000' }, + { price: '0.04891000', quantity: '0.00000000' } ], + askDepth: [ + { price: '0.04910600', quantity: '0.00000000' }, + { price: '0.04910700', quantity: '11.24900000' } + ] +} +``` + +
+ #### candles -Live candle data feed for a given symbol and interval. +Live candle data feed for a given interval. You can pass either a symbol string +or a symbol array. ```js client.ws.candles('ETHBTC', '1m', candle => { @@ -499,16 +578,57 @@ client.ws.candles('ETHBTC', '1m', candle => { }) ``` +
+Output + +```js +{ + eventType: 'kline', + eventTime: 1508613366276, + symbol: 'ETHBTC', + open: '0.04898000', + high: '0.04902700', + low: '0.04898000', + close: '0.04901900', + volume: '37.89600000', + trades: 30, + interval: '5m', + isFinal: false, + quoteVolume: '1.85728874', + buyVolume: '21.79900000', + quoteBuyVolume: '1.06838790' +} +``` + +
+ #### trades -Live trade data feed for a given symbol. +Live trade data feed. Pass either a single symbol string or an array of symbols. ```js -client.ws.trades('ETHBTC', trade => { +client.ws.trades(['ETHBTC', 'BNBBTC'], trade => { console.log(trade) }) ``` +
+Output + +```js +{ + eventType: 'aggTrade', + eventTime: 1508614495052, + symbol: 'ETHBTC', + price: '0.04923600', + quantity: '3.43500000', + maker: false, + tradeId: 2148226 +} +``` + +
+ #### user Live user messages data feed. @@ -523,3 +643,20 @@ const clean = await client.ws.user(msg => { Note that this method returns a promise returning a `clean` callback, that will clear the keep-alive interval and close the data stream. + +
+Output + +```js +{ + eventType: 'account', + eventTime: 1508614885818, + balances: { + '123': { available: '0.00000000', locked: '0.00000000' }, + '456': { available: '0.00000000', locked: '0.00000000' }, + BTC: { available: '0.00000000', locked: '0.00000000' }, + ] +} +``` + +
diff --git a/src/websocket.js b/src/websocket.js index 0bd1104a..e5a6cb49 100644 --- a/src/websocket.js +++ b/src/websocket.js @@ -1,86 +1,102 @@ import WebSocket from 'ws' +import zip from 'lodash.zipobject' import httpMethods from 'http' const BASE = 'wss://stream.binance.com:9443/ws' -const depth = (symbol, cb) => { - const w = new WebSocket(`${BASE}/${symbol.toLowerCase()}@depth`) - w.on('message', msg => { - const { - e: eventType, - E: eventTime, - s: symbol, - u: updateId, - b: bidDepth, - a: askDepth, - } = JSON.parse(msg) +const depth = (payload, cb) => + (Array.isArray(payload) ? payload : [payload]).forEach(symbol => { + const w = new WebSocket(`${BASE}/${symbol.toLowerCase()}@depth`) + w.on('message', msg => { + const { + e: eventType, + E: eventTime, + s: symbol, + u: updateId, + b: bidDepth, + a: askDepth, + } = JSON.parse(msg) - cb({ eventType, eventTime, symbol, updateId, bidDepth, askDepth }) + cb({ + eventType, + eventTime, + symbol, + updateId, + bidDepth: bidDepth.map(b => zip(['price', 'quantity'], b)), + askDepth: askDepth.map(a => zip(['price', 'quantity'], a)), + }) + }) }) -} -const candles = (symbol, interval, cb) => { - const w = new WebSocket(`${BASE}/${symbol.toLowerCase()}@kline_${interval}`) - w.on('message', msg => { - const { e: eventType, E: eventTime, s: symbol, k: tick } = JSON.parse(msg) - const { - o: open, - h: high, - l: low, - c: close, - v: volume, - n: trades, - i: interval, - x: isFinal, - q: quoteVolume, - V: buyVolume, - Q: quoteBuyVolume, - } = tick +const candles = (payload, interval, cb) => { + if (!interval || !cb) { + throw new Error('Please pass a symbol, interval and callback.') + } - cb({ - eventType, - eventTime, - symbol, - open, - high, - low, - close, - volume, - trades, - interval, - isFinal, - quoteVolume, - buyVolume, - quoteBuyVolume, + ;(Array.isArray(payload) ? payload : [payload]).forEach(symbol => { + const w = new WebSocket(`${BASE}/${symbol.toLowerCase()}@kline_${interval}`) + w.on('message', msg => { + const { e: eventType, E: eventTime, s: symbol, k: tick } = JSON.parse(msg) + const { + o: open, + h: high, + l: low, + c: close, + v: volume, + n: trades, + i: interval, + x: isFinal, + q: quoteVolume, + V: buyVolume, + Q: quoteBuyVolume, + } = tick + + cb({ + eventType, + eventTime, + symbol, + open, + high, + low, + close, + volume, + trades, + interval, + isFinal, + quoteVolume, + buyVolume, + quoteBuyVolume, + }) }) }) } -const trades = (symbol, cb) => { - const w = new WebSocket(`${BASE}/${symbol.toLowerCase()}@aggTrades`) - w.on('message', msg => { - const { - e: eventType, - E: eventTime, - s: symbol, - p: price, - q: quantity, - m: maker, - a: tradeId, - } = JSON.parse(msg) +const trades = (payload, cb) => + (Array.isArray(payload) ? payload : [payload]).forEach(symbol => { + const w = new WebSocket(`${BASE}/${symbol.toLowerCase()}@aggTrade`) + w.on('message', msg => { + const { + e: eventType, + E: eventTime, + s: symbol, + p: price, + q: quantity, + m: maker, + a: tradeId, + } = JSON.parse(msg) - cb({ - eventType, - eventTime, - symbol, - price, - quantity, - maker, - tradeId, + cb({ + eventType, + eventTime, + symbol, + price, + quantity, + maker, + tradeId, + }) }) }) -} const userTransforms = { outboundAccountInfo: m => ({ diff --git a/test/authenticated.js b/test/authenticated.js index 8fe8ad3c..2f13a481 100644 --- a/test/authenticated.js +++ b/test/authenticated.js @@ -2,6 +2,8 @@ import test from 'ava' import Binance from 'index' +import { checkFields } from './utils' + const client = Binance({ apiKey: process.env.API_KEY, apiSecret: process.env.API_SECRET, @@ -24,3 +26,77 @@ test.serial('[REST] order', async t => { t.pass() }) + +test.serial('[REST] allOrders / getOrder', async t => { + try { + await client.getOrder({ symbol: 'ETHBTC' }) + } catch (e) { + t.is( + e.message, + "Param 'origClientOrderId' or 'orderId' must be sent, but both were empty/null!", + ) + } + + try { + await client.getOrder({ symbol: 'ETHBTC', orderId: 1 }) + } catch (e) { + t.is(e.message, 'Order does not exist.') + } + + // Note that this test will fail if you don't have any ENG order in your account ;) + const orders = await client.allOrders({ + symbol: 'ENGETH', + }) + + t.true(Array.isArray(orders)) + t.truthy(orders.length) + + const [order] = orders + + checkFields(t, order, ['orderId', 'symbol', 'price', 'type', 'side']) + + const res = await client.getOrder({ + symbol: 'ENGETH', + orderId: order.orderId, + }) + + t.truthy(res) + checkFields(t, res, ['orderId', 'symbol', 'price', 'type', 'side']) +}) + +test.serial('[REST] openOrders', async t => { + const orders = await client.openOrders({ + symbol: 'ETHBTC', + }) + + t.true(Array.isArray(orders)) +}) + +test.serial('[REST] cancelOrder', async t => { + try { + await client.cancelOrder({ symbol: 'ETHBTC', orderId: 1 }) + } catch (e) { + t.is(e.message, 'UNKNOWN_ORDER') + } +}) + +test.serial('[REST] accountInfo', async t => { + const account = await client.accountInfo() + t.truthy(account) + checkFields(t, account, ['makerCommission', 'takerCommission', 'balances']) + t.truthy(account.balances.length) +}) + +test.serial('[REST] myTrades', async t => { + const trades = await client.myTrades({ symbol: 'ENGETH' }) + t.true(Array.isArray(trades)) + const [trade] = trades + checkFields(t, trade, ['id', 'orderId', 'qty', 'commission', 'time']) +}) + +test.serial('[WS] user', async t => { + const clean = await client.ws.user() + t.truthy(clean) + t.true(typeof clean === 'function') + clean() +}) diff --git a/test/index.js b/test/index.js index ba3e9ee4..0321e23a 100644 --- a/test/index.js +++ b/test/index.js @@ -4,16 +4,12 @@ import dotenv from 'dotenv' import Binance from 'index' import { candleFields } from 'http' +import { checkFields } from './utils' + dotenv.load() const client = Binance() -const checkFields = (t, object, fields) => { - fields.forEach(field => { - t.truthy(object[field]) - }) -} - test.serial('[REST] ping', async t => { t.truthy(await client.ping(), 'A simple ping should work') }) @@ -63,7 +59,7 @@ test.serial('[REST] candles', async t => { test.serial('[REST] aggTrades', async t => { try { - client.aggTrades({}) + await client.aggTrades({}) } catch (e) { t.is(e.message, 'Method aggTrades requires symbol parameter.') } @@ -77,7 +73,7 @@ test.serial('[REST] aggTrades', async t => { test.serial('[REST] dailyStats', async t => { try { - client.dailyStats({}) + await client.dailyStats({}) } catch (e) { t.is(e.message, 'Method dailyStats requires symbol parameter.') } @@ -99,16 +95,55 @@ test.serial('[REST] allBookTickers', async t => { t.truthy(tickers.ETHBTC) }) -test.serial('[REST] Signed call without creds', t => { - const client = Binance() - +test.serial('[REST] Signed call without creds', async t => { try { - client.order({ symbol: 'ETHBTC', side: 'BUY', quantity: 1 }) + await client.order({ symbol: 'ETHBTC', side: 'BUY', quantity: 1 }) } catch (e) { t.is(e.message, 'You need to pass an API key and secret to make authenticated calls.') } }) +test.serial('[WS] depth', t => { + return new Promise(resolve => { + client.ws.depth('ETHBTC', depth => { + t.is(depth, depth, 'ETHBTC') + checkFields(t, depth, [ + 'eventType', + 'eventTime', + 'updateId', + 'symbol', + 'bidDepth', + 'askDepth', + ]) + resolve() + }) + }) +}) + +test.serial('[WS] candles', t => { + try { + client.ws.candles('ETHBTC', d => d) + } catch (e) { + t.is(e.message, 'Please pass a symbol, interval and callback.') + } + + return new Promise(resolve => { + client.ws.candles('ETHBTC', '5m', candle => { + checkFields(t, candle, ['open', 'high', 'low', 'close', 'volume', 'trades', 'quoteVolume']) + resolve() + }) + }) +}) + +test.serial('[WS] trades', t => { + return new Promise(resolve => { + client.ws.trades(['BNBBTC', 'ETHBTC', 'BNTBTC'], trade => { + checkFields(t, trade, ['eventType', 'tradeId', 'maker', 'quantity', 'price', 'symbol']) + resolve() + }) + }) +}) + if (process.env.API_KEY) { require('./authenticated') } diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 00000000..9bbe3291 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,5 @@ +export const checkFields = (t, object, fields) => { + fields.forEach(field => { + t.truthy(object[field]) + }) +}