UNPKG

ccxws

Version:

Websocket client for 37 cryptocurrency exchanges

268 lines (267 loc) 10.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PoloniexClient = void 0; /* eslint-disable @typescript-eslint/member-ordering */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable prefer-const */ /* eslint-disable @typescript-eslint/restrict-plus-operands */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ const BasicClient_1 = require("../BasicClient"); const https = __importStar(require("../Https")); const Level2Point_1 = require("../Level2Point"); const Level2Snapshots_1 = require("../Level2Snapshots"); const Level2Update_1 = require("../Level2Update"); const NotImplementedFn_1 = require("../NotImplementedFn"); const Ticker_1 = require("../Ticker"); const Trade_1 = require("../Trade"); class PoloniexClient extends BasicClient_1.BasicClient { constructor({ wssPath = "wss://api2.poloniex.com/", autoloadSymbolMaps = true, watcherMs, } = {}) { super(wssPath, "Poloniex", undefined, watcherMs); this._sendSubCandles = NotImplementedFn_1.NotImplementedFn; this._sendUnsubCandles = NotImplementedFn_1.NotImplementedFn; this._sendSubLevel2Snapshots = NotImplementedFn_1.NotImplementedFn; this._sendUnsubLevel2Snapshots = NotImplementedFn_1.NotImplementedFn; this._sendSubLevel3Snapshots = NotImplementedFn_1.NotImplementedFn; this._sendUnsubLevel3Snapshots = NotImplementedFn_1.NotImplementedFn; this._sendSubLevel3Updates = NotImplementedFn_1.NotImplementedFn; this._sendUnsubLevel3Updates = NotImplementedFn_1.NotImplementedFn; this._idMap = new Map(); this.hasTickers = true; this.hasTrades = true; this.hasLevel2Updates = true; this._subbedToTickers = false; this.on("connected", this._resetSubCount.bind(this)); this.TICKERS_ID = 1002; this.MARKET_IDS = new Map(); if (autoloadSymbolMaps) { this.loadSymbolMaps().catch(err => this.emit("error", err)); } } /** Poloniex uses numeric identifiers for its markets. A static map of these markets can be obtained from: https://docs.poloniex.com/#currency-pair-ids We can use the ticker REST API as a mechanism to obtain the identifiers and create an index of id to symbol. */ async loadSymbolMaps() { const uri = "https://poloniex.com/public?command=returnTicker"; const result = await https.get(uri); for (const symbol in result) { const id = result[symbol].id; this.MARKET_IDS.set(id, symbol); } } _resetSubCount() { this._subCount = {}; this._subbedToTickers = false; } _sendSubTicker() { if (this._subbedToTickers) return; // send for first request this._subbedToTickers = true; this._wss.send(JSON.stringify({ command: "subscribe", channel: this.TICKERS_ID, })); } _sendUnsubTicker() { if (this._tickerSubs.size) return; // send when no more this._subbedToTickers = false; this._wss.send(JSON.stringify({ command: "unsubscribe", channel: this.TICKERS_ID, })); } _sendSubTrades(remote_id) { this._sendSubscribe(remote_id); } _sendUnsubTrades(remote_id) { this._sendUnsubscribe(remote_id); } _sendSubLevel2Updates(remote_id) { this._sendSubscribe(remote_id); } _sendUnsubLevel2Updates(remote_id) { this._sendUnsubscribe(remote_id); } _sendSubscribe(remote_id) { this._subCount[remote_id] = (this._subCount[remote_id] || 0) + 1; // increment market counter // if we have more than one sub, ignore the request as we're already subbed if (this._subCount[remote_id] > 1) return; this._wss.send(JSON.stringify({ command: "subscribe", channel: remote_id, })); } _sendUnsubscribe(remote_id) { this._subCount[remote_id] -= 1; // decrement market count // if we still have subs, then leave channel open if (this._subCount[remote_id]) return; this._wss.send(JSON.stringify({ command: "unsubscribe", channel: remote_id, })); } _onMessage(raw) { // different because messages are broadcast as joined updates // [148,540672082,[["o",1,"0.07313000","7.21110596"],["t","43781170",0,"0.07313000","0.00199702",1528900825]]] // we need to pick apart these messages and broadcast them accordingly const msg = JSON.parse(raw); const id = msg[0]; const seq = msg[1]; const updates = msg[2]; // tickers if (id === this.TICKERS_ID && updates) { const remoteId = this.MARKET_IDS.get(updates[0]); const market = this._tickerSubs.get(remoteId); if (!market) return; const ticker = this._createTicker(updates, market); this.emit("ticker", ticker, market); return; } if (!updates) return; const bids = []; const asks = []; for (const update of updates) { switch (update[0]) { // when connection is first established it will send an 'info' packet // that can be used to map the "id" to the market_symbol case "i": { const remote_id = update[1].currencyPair; this._idMap.set(id, remote_id); // capture snapshot if we're subscribed to l2updates const market = this._level2UpdateSubs.get(remote_id); if (!market) continue; const snapshot = this._constructoLevel2Snapshot(seq, update[1], market); this.emit("l2snapshot", snapshot, market); break; } // trade events will stream-in after we are subscribed to the channel // and hopefully after the info packet has been sent case "t": { const market = this._tradeSubs.get(this._idMap.get(id)); if (!market) continue; const trade = this._constructTradeFromMessage(update, market); this.emit("trade", trade, market); break; } case "o": { // only include updates if we are subscribed to the market const market = this._level2UpdateSubs.get(this._idMap.get(id)); if (!market) continue; //[171, 280657226, [["o", 0, "0.00225182", "0.00000000"], ["o", 0, "0.00225179", "860.66363984"]]] //[171, 280657227, [["o", 1, "0.00220001", "0.00000000"], ["o", 1, "0.00222288", "208.47334089"]]] const point = new Level2Point_1.Level2Point(update[2], update[3]); if (update[1] === 0) asks.push(point); if (update[1] === 1) bids.push(point); break; } } } // check if we have bids/asks and construct order update message if (bids.length || asks.length) { const market = this._level2UpdateSubs.get(this._idMap.get(id)); if (!market) return; const l2update = new Level2Update_1.Level2Update({ exchange: this.name, base: market.base, quote: market.quote, sequenceId: seq, asks, bids, }); this.emit("l2update", l2update, market); } } _createTicker(update, market) { const [, last, ask, bid, percent, quoteVol, baseVol, , high, low] = update; const open = parseFloat(last) / (1 + parseFloat(percent)); const dayChange = parseFloat(last) - open; return new Ticker_1.Ticker({ exchange: this.name, base: market.base, quote: market.quote, timestamp: Date.now(), last, open: open.toFixed(8), high, low, volume: baseVol, quoteVolume: quoteVol, change: dayChange.toFixed(8), changePercent: percent, ask, bid, }); } _constructTradeFromMessage(update, market) { let [, trade_id, side, price, size, unix] = update; side = side === 1 ? "buy" : "sell"; unix = unix * 1000; trade_id = parseInt(trade_id); return new Trade_1.Trade({ exchange: this.name, base: market.base, quote: market.quote, tradeId: trade_id.toFixed(), side, unix, price, amount: size, }); } _constructoLevel2Snapshot(seq, update, market) { const [asksObj, bidsObj] = update.orderBook; const asks = []; const bids = []; for (const price in asksObj) { asks.push(new Level2Point_1.Level2Point(price, asksObj[price])); } for (const price in bidsObj) { bids.push(new Level2Point_1.Level2Point(price, bidsObj[price])); } return new Level2Snapshots_1.Level2Snapshot({ exchange: this.name, base: market.base, quote: market.quote, sequenceId: seq, asks, bids, }); } } exports.PoloniexClient = PoloniexClient; //# sourceMappingURL=PoloniexClient.js.map