diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index a0e1bd1e..1770a26a 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -46,6 +46,7 @@ jobs: - binance-futures-usdtm - binanceje - binanceus + - bitbank - bitfinex - bitflyer - bithumb diff --git a/README.md b/README.md index 64658268..bd842ff0 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ binance.subscribeLevel2Snapshots(market); | Binance Futures USDT-M | 1 | BinanceFuturesUsdtM | ✓ | ✓ | ✓ | ✓ | ✓\*\* | - | - | | Binance Jersey | 1 | BinanceJe | ✓ | ✓ | ✓ | ✓ | ✓\*\* | - | - | | Binance US | 1 | BinanceUs | ✓ | ✓ | ✓ | ✓ | ✓\*\* | - | - | +| bitbank | 1 | Bitbank | ✓ | ✓ | - | ✓ | ✓ | - | - | | Bitfinex | 2 | Bitfinex | ✓ | ✓ | - | - | ✓\* | - | ✓\* | | bitFlyer | 1 | Bitflyer | ✓ | ✓ | - | - | ✓\*\* | - | - | | Bithumb | 1 | Bithumb | ✓ | ✓ | - | - | ✓\*\* | - | - | diff --git a/__tests__/exchanges/bitbank-client.spec.js b/__tests__/exchanges/bitbank-client.spec.js new file mode 100644 index 00000000..ccd26c11 --- /dev/null +++ b/__tests__/exchanges/bitbank-client.spec.js @@ -0,0 +1,121 @@ +const { testClient } = require("../test-runner"); +const BitbankClient = require("../../src/exchanges/bitbank-client"); + +testClient({ + clientFactory: () => new BitbankClient(), + clientName: "bitbankClient", + exchangeName: "bitbank", + markets: [ + { + id: "btc_jpy", + base: "BTC", + quote: "JPY", + }, + { + id: "xrp_jpy", + base: "XRP", + quote: "JPY", + }, + { + id: "xrp_btc", + base: "XRP", + quote: "BTC", + }, + { + id: "ltc_jpy", + base: "LTC", + quote: "JPY", + }, + { + id: "ltc_btc", + base: "LTC", + quote: "BTC", + }, + { + id: "eth_jpy", + base: "XRP", + quote: "JPY", + }, + { + id: "eth_btc", + base: "XRP", + quote: "BTC", + }, + { + id: "mona_jpy", + base: "MONA", + quote: "JPY", + }, + { + id: "mona_btc", + base: "MONA", + quote: "BTC", + }, + { + id: "bcc_jpy", + base: "BCC", + quote: "JPY", + }, + { + id: "bcc_btc", + base: "BCC", + quote: "BTC", + }, + { + id: "xlm_jpy", + base: "XLM", + quote: "JPY", + }, + { + id: "xlm_btc", + base: "XLM", + quote: "BTC", + }, + ], + + testConnectEvents: false, + testDisconnectEvents: false, + testReconnectionEvents: false, + testCloseEvents: false, + + hasTickers: true, + hasTrades: true, + hasCandles: false, + hasLevel2Snapshots: true, + hasLevel2Updates: true, + hasLevel3Snapshots: false, + hasLevel3Updates: false, + + ticker: { + hasTimestamp: true, + hasLast: true, + hasOpen: false, + hasHigh: true, + hasLow: true, + hasVolume: true, + hasQuoteVolume: false, + hasChange: false, + hasChangePercent: false, + hasBid: true, + hasBidVolume: false, + hasAsk: true, + hasAskVolume: false, + }, + + trade: { + hasTradeId: true, + }, + + l2snapshot: { + hasTimestampMs: true, + hasSequenceId: false, + hasCount: false, + }, + + l2update: { + hasSnapshot: false, + hasTimestampMs: true, + hasSequenceId: false, + hasCount: false, + }, +}); diff --git a/src/exchanges/bitbank-client.js b/src/exchanges/bitbank-client.js new file mode 100644 index 00000000..0cd2d96a --- /dev/null +++ b/src/exchanges/bitbank-client.js @@ -0,0 +1,178 @@ +const BasicClient = require("../basic-client"); +const Ticker = require("../ticker"); +const Trade = require("../trade"); +const Level2Point = require("../level2-point"); +const Level2Snapshot = require("../level2-snapshot"); +const Level2Update = require("../level2-update"); + +class BitbankClient extends BasicClient { + constructor({ + wssPath = "wss://stream.bitbank.cc/socket.io/?EIO=3&transport=websocket", + watcherMs, + } = {}) { + super(wssPath, "bitbank", undefined, watcherMs); + + this.hasTickers = true; + this.hasTrades = true; + this.hasLevel2Snapshots = true; + this.hasLevel2Updates = true; + } + + _sendSubTicker(remote_id) { + this._wss.send(`42["join-room", "ticker_${remote_id}"]`); + } + + _sendUnsubTicker(remote_id) { + this._wss.send(`42["left-room", "ticker_${remote_id}"]`); + } + + _sendSubTrades(remote_id) { + this._wss.send(`42["join-room", "transactions_${remote_id}"]`); + } + + _sendUnsubTrades(remote_id) { + this._wss.send(`42["left-room", "transactions_${remote_id}"]`); + } + + _sendSubLevel2Snapshots(remote_id) { + this._wss.send(`42["join-room", "depth_whole_${remote_id}"]`); + } + + _sendUnsubLevel2Snapshots(remote_id) { + this._wss.send(`42["left-room", "depth_whole_${remote_id}"]`); + } + + _sendSubLevel2Updates(remote_id) { + this._wss.send(`42["join-room", "depth_diff_${remote_id}"]`); + } + + _sendUnsubLevel2Updates(remote_id) { + this._wss.send(`42["left-room", "depth_diff_${remote_id}"]`); + } + + _onMessage(raw) { + let ep = parseInt(raw.slice(0, 1)); // engine.io-protocol + let sp; // socket.io-protocol + let content; + + if (ep === 4) { + sp = parseInt(raw.slice(1, 2)); + if (sp === 2) { + content = raw.slice(2); + } + } + if (!(ep === 4 && sp === 2)) { + return; + } + let msg = JSON.parse(content)[1]["message"]["data"]; + let room = JSON.parse(content)[1]["room_name"]; + + // tickers + if (room.startsWith("ticker_")) { + const id = room.replace("ticker_", ""); + let market = this._tickerSubs.get(id); + if (!market) return; + + let ticker = this._constructTicker(msg, market); + this.emit("ticker", ticker, market); + + return; + } + + // trade + if (room.startsWith("transactions_")) { + const id = room.replace("transactions_", ""); + for (let tx of msg.transactions) { + let market = this._tradeSubs.get(id); + if (!market) return; + + let trade = this._constructTradesFromMessage(tx, market); + this.emit("trade", trade, market); + } + return; + } + + // l2 snapshot + if (room.startsWith("depth_whole_")) { + const id = room.replace("depth_whole_", ""); + let market = this._level2SnapshotSubs.get(id); + if (!market) return; + + let snapshot = this._constructLevel2Snapshot(msg, market); + this.emit("l2snapshot", snapshot, market); + return; + } + + // l2 update + if (room.startsWith("depth_diff_")) { + const id = room.replace("depth_diff_", ""); + let market = this._level2UpdateSubs.get(id); + if (!market) return; + + let update = this._constructLevel2Updates(msg, market); + this.emit("l2update", update, market); + return; + } + } + + _constructTicker(msg, market) { + let { last, timestamp, sell, vol, buy, high, low } = msg; + + return new Ticker({ + exchange: this._name, + base: market.base, + quote: market.quote, + timestamp: timestamp, + last: last, + high: high, + low: low, + volume: vol, + bid: sell, + ask: buy, + }); + } + + _constructTradesFromMessage(msg, market) { + let { side, executed_at, amount, price, transaction_id } = msg; + + return new Trade({ + exchange: this._name, + base: market.base, + quote: market.quote, + side: side.toLowerCase(), + unix: executed_at, + price: price, + amount: amount, + tradeId: transaction_id.toFixed(), + }); + } + + _constructLevel2Snapshot(msg, market) { + let asks = msg.asks.map(p => new Level2Point(p[0], p[1])); + let bids = msg.bids.map(p => new Level2Point(p[0], p[1])); + + return new Level2Snapshot({ + exchange: this._name, + base: market.base, + quote: market.quote, + timestampMs: msg.timestamp, + asks, + bids, + }); + } + + _constructLevel2Updates(msg, market) { + let asks = msg.a.map(p => new Level2Point(p[0], p[1])); + let bids = msg.b.map(p => new Level2Point(p[0], p[1])); + + return new Level2Update({ + exchange: this._name, + base: market.base, + quote: market.quote, + timestampMs: msg.t, + asks, + bids, + }); + } +} +module.exports = BitbankClient; diff --git a/src/index.js b/src/index.js index 0f31505a..fe87d7f5 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ const bibox = require("./exchanges/bibox-client"); const binance = require("./exchanges/binance-client"); const binanceje = require("./exchanges/binanceje-client"); const binanceus = require("./exchanges/binanceus-client"); +const bitbank = require("./exchanges/bitbank-client"); const bitfinex = require("./exchanges/bitfinex-client"); const bitflyer = require("./exchanges/bitflyer-client"); const bitmex = require("./exchanges/bitmex-client"); @@ -30,6 +31,7 @@ module.exports = { binance, binanceje, binanceus, + bitbank, bitfinex, bitflyer, bitmex, @@ -62,6 +64,7 @@ module.exports = { BinanceFuturesUsdtM: require("./exchanges/binance-futures-usdtm-client"), BinanceJe: binanceje, BinanceUs: binanceus, + Bitbank: bitbank, Bitfinex: bitfinex, Bitflyer: bitflyer, Bithumb: require("./exchanges/bithumb-client"),