UNPKG

ccxws

Version:

Websocket client for 37 cryptocurrency exchanges

322 lines (278 loc) 10.1 kB
/* eslint-disable @typescript-eslint/member-ordering */ /* eslint-disable prefer-const */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-implied-eval */ import moment = require("moment"); import { BasicClient } from "../BasicClient"; import { BasicMultiClient } from "../BasicMultiClient"; import { Candle } from "../Candle"; import { CandlePeriod } from "../CandlePeriod"; import { IClient } from "../IClient"; import { Level2Point } from "../Level2Point"; import { Level2Snapshot } from "../Level2Snapshots"; import { Level2Update } from "../Level2Update"; import { NotImplementedFn } from "../NotImplementedFn"; import { SubscriptionType } from "../SubscriptionType"; import { Ticker } from "../Ticker"; import { Trade } from "../Trade"; export type CoinexClientOptions = { // }; export class CoinexClient extends BasicMultiClient { public options: CoinexClientOptions; public candlePeriod: CandlePeriod; constructor(options: CoinexClientOptions = {}) { super(); this.options = options; this.hasTickers = true; this.hasTrades = true; this.hasCandles = false; this.hasLevel2Updates = true; this.candlePeriod = CandlePeriod._1m; } protected _createBasicClient(): IClient { return new CoinexSingleClient({ ...this.options, parent: this }); } } export class CoinexSingleClient extends BasicClient { public retryErrorTimeout: number; public parent: CoinexClient; protected _id: number; protected _idSubMap: Map<any, any>; protected _pingInterval: NodeJS.Timeout; constructor({ wssPath = "wss://socket.coinex.com/", watcherMs = 900 * 1000, parent }) { super(wssPath, "Coinex", undefined, watcherMs); this.hasTickers = true; this.hasTrades = true; this.hasCandles = false; this.hasLevel2Updates = true; this.retryErrorTimeout = 15000; this._id = 0; this._idSubMap = new Map(); this.parent = parent; } public get candlePeriod() { return this.parent.candlePeriod; } protected _beforeConnect() { this._wss.on("connected", this._startPing.bind(this)); this._wss.on("disconnected", this._stopPing.bind(this)); this._wss.on("closed", this._stopPing.bind(this)); } protected _startPing() { clearInterval(this._pingInterval); this._pingInterval = setInterval(this._sendPing.bind(this), 30000); } protected _stopPing() { clearInterval(this._pingInterval); } protected _sendPing() { if (this._wss) { this._wss.send( JSON.stringify({ method: "server.ping", params: [], id: ++this._id, }), ); } } protected _failSubscription(id) { // find the subscription const sub = this._idSubMap.get(id); if (!sub) return; // // unsubscribe from the appropriate event // const { type, remote_id } = sub; // // unsubscribe from the appropriate thiing // switch (type) { // case SubscriptionType.ticker: // this.unsubscribeTicker(remote_id); // break; // case SubscriptionType.trade: // this.unsubscribeTrades(remote_id); // break; // case SubscriptionType.level2update: // this.unsubscribeLevel2Updates(remote_id); // break; // } // remove the value this._idSubMap.delete(id); } // unsubscribeTicker(remote_id: any) { // throw new Error("Method not implemented."); // } // unsubscribeTrades(remote_id: any) { // throw new Error("Method not implemented."); // } // unsubscribeLevel2Updates(remote_id: any) { // throw new Error("Method not implemented."); // } protected _sendSubTicker(remote_id) { const id = this._id++; this._idSubMap.set(id, { remote_id, type: SubscriptionType.ticker }); this._wss.send( JSON.stringify({ method: "state.subscribe", params: [remote_id], id, }), ); } protected _sendUnsubTicker() { this._wss.send( JSON.stringify({ method: "state.unsubscribe", }), ); } protected _sendSubTrades(remote_id) { const id = this._id++; this._idSubMap.set(id, { remote_id, type: SubscriptionType.trade }); this._wss.send( JSON.stringify({ method: "deals.subscribe", params: [remote_id], id, }), ); } protected _sendUnsubTrades() { this._wss.send( JSON.stringify({ method: "deals.unsubscribe", }), ); } protected _sendSubLevel2Updates(remote_id) { const id = this._id++; this._idSubMap.set(id, { remote_id, type: SubscriptionType.level2update }); this._wss.send( JSON.stringify({ method: "depth.subscribe", params: [remote_id, 50, "0"], id, }), ); } protected _sendUnsubLevel2Updates() { this._wss.send( JSON.stringify({ method: "depth.unsubscribe", }), ); } protected _sendSubCandles = NotImplementedFn; protected _sendUnsubCandles = NotImplementedFn; protected _sendSubLevel2Snapshots = NotImplementedFn; protected _sendUnsubLevel2Snapshots = NotImplementedFn; protected _sendSubLevel3Snapshots = NotImplementedFn; protected _sendUnsubLevel3Snapshots = NotImplementedFn; protected _sendSubLevel3Updates = NotImplementedFn; protected _sendUnsubLevel3Updates = NotImplementedFn; protected _onMessage(raw) { const msg = JSON.parse(raw); const { error, method, params, id } = msg; // unsubscribe on failures if (error) { this.emit("error", msg); this._failSubscription(id); return; } // if params is not defined, then this is a response to an event // that we don't care about (like the initial connection event) if (!params) return; if (method === "state.update") { const marketId = Object.keys(params[0])[0]; const market = this._tickerSubs.get(marketId); if (!market) return; const ticker = this._constructTicker(params[0][marketId], market); this.emit("ticker", ticker, market); return; } if (method === "deals.update") { const marketId = params[0]; const market = this._tradeSubs.get(marketId); if (!market) return; for (const t of params[1].reverse()) { const trade = this._constructTrade(t, market); this.emit("trade", trade, market); } return; } if (method === "depth.update") { const marketId = params[2]; const market = this._level2UpdateSubs.get(marketId); if (!market) return; const isLevel2Snapshot = params[0]; if (isLevel2Snapshot) { const l2snapshot = this._constructLevel2Snapshot(params[1], market); this.emit("l2snapshot", l2snapshot, market); } else { const l2update = this._constructLevel2Update(params[1], market); this.emit("l2update", l2update, market); } return; } } protected _constructTicker(rawTick, market) { let { last, open, high, low, volume, deal } = rawTick, change = parseFloat(last) - parseFloat(open), changePercent = ((parseFloat(last) - parseFloat(open)) / parseFloat(open)) * 100; return new Ticker({ exchange: "Coinex", base: market.base, quote: market.quote, timestamp: Date.now(), last: last, open: open, high: high, low: low, volume: volume, quoteVolume: deal, change: change.toFixed(8), changePercent: changePercent.toFixed(8), }); } protected _constructTrade(rawTrade, market) { const { id, time, type, price, amount } = rawTrade; const unix = moment.utc(time * 1000).valueOf(); return new Trade({ exchange: this.name, base: market.base, quote: market.quote, tradeId: id.toFixed(), unix: unix, side: type, price, amount, buyOrderId: undefined, sellOrderId: undefined, }); } protected _constructLevel2Snapshot(rawUpdate, market) { let { bids, asks } = rawUpdate, structuredBids = bids ? bids.map(([price, size]) => new Level2Point(price, size)) : [], structuredAsks = asks ? asks.map(([price, size]) => new Level2Point(price, size)) : []; return new Level2Snapshot({ exchange: this.name, base: market.base, quote: market.quote, bids: structuredBids, asks: structuredAsks, }); } protected _constructLevel2Update(rawUpdate, market) { let { bids, asks } = rawUpdate, structuredBids = bids ? bids.map(([price, size]) => new Level2Point(price, size)) : [], structuredAsks = asks ? asks.map(([price, size]) => new Level2Point(price, size)) : []; return new Level2Update({ exchange: this.name, base: market.base, quote: market.quote, bids: structuredBids, asks: structuredAsks, }); } }