UNPKG

@strong-roots-capital/bybit-api

Version:

TypeScript API for Bybit cryptocurrency exchange

253 lines 15.5 kB
"use strict"; /** * Note: currently only supports inverse perpetual swaps */ 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; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.bybitPublicWebsocket = void 0; var util = __importStar(require("util")); var ratlog_debug_1 = require("@strong-roots-capital/ratlog-debug"); var Console = __importStar(require("fp-ts/Console")); var E = __importStar(require("fp-ts/Either")); var O = __importStar(require("fp-ts/Option")); var T = __importStar(require("fp-ts/Task")); var TE = __importStar(require("fp-ts/TaskEither")); var function_1 = require("fp-ts/function"); var t = __importStar(require("io-ts")); var io_ts_types_1 = require("io-ts-types"); var PathReporter = __importStar(require("io-ts/lib/PathReporter")); var od_1 = __importDefault(require("od")); var rxjs_1 = require("rxjs"); var webSocket_1 = require("rxjs/webSocket"); var ts_pattern_1 = require("ts-pattern"); var ws_1 = __importDefault(require("ws")); var unsafe_parse_1 = require("./codecs/unsafe-parse"); var BybitChannelMessage_1 = require("./codecs/ws/BybitChannelMessage"); var BybitKlineSubscriptionRequest_1 = require("./codecs/ws/BybitKlineSubscriptionRequest"); var BybitSubscriptionRequest_1 = require("./codecs/ws/BybitSubscriptionRequest"); var BybitSubscriptionResponse_1 = require("./codecs/ws/BybitSubscriptionResponse"); var BybitWebsocketMessage_1 = require("./codecs/ws/BybitWebsocketMessage"); var rest_1 = require("./rest"); var debug = { warn: ratlog_debug_1.log.tag('warning'), ws: ratlog_debug_1.log.tag('bybit:websocket'), kline: ratlog_debug_1.log.tag('bybit:kline'), }; var one = (0, unsafe_parse_1.unsafeParse)(t.Int, 1); var wsKline = function (_a) { var interval = _a.interval, kline = _a.kline, timestamp = _a.timestamp; var end = od_1.default.add(interval.unit, interval.quantifier, kline.open_time); return { start: kline.open_time, end: end, open: kline.open, close: kline.close, high: kline.high, low: kline.low, volume: kline.volume, turnover: kline.turnover, timestamp: timestamp, confirm: end < timestamp, }; }; var decodeBybitChannelMessage = function (message) { return (0, function_1.pipe)(TE.fromEither((0, io_ts_types_1.nonEmptyArray)(BybitChannelMessage_1.BybitChannelMessage).decode(message.data)), TE.chainFirstIOK(function (_) { return function () { return debug.kline(JSON.stringify(_)); }; }), TE.mapLeft((0, function_1.flow)(function (error) { return PathReporter.failure(error); }, function (errors) { return ({ type: 'unable to decode websocket message', error: errors.join('\n'), }); }))); }; var bybitPublicWebsocket = function (settings) { var subscribers = new Map(); var pending = new Map(); var subject = (0, webSocket_1.webSocket)({ url: settings.testnet ? 'wss://stream-testnet.bybit.com/realtime' : 'wss://stream.bybit.com/realtime', // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment WebSocketCtor: ws_1.default, }); var restClient = (0, rest_1.bybitRestClient)({ testnet: settings.testnet, }); function subscribe(request) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, new Promise(function (resolve, reject) { debug.ws(util.format('Received request to subscribe to %s %s %s', request.channel, request.symbol, request.interval)); var encodedRequest = BybitSubscriptionRequest_1.BybitSubscriptionRequest.encode(request); if (pending.has(encodedRequest) || subscribers.has(encodedRequest)) { reject({ type: 'already subscribed to this websocket topic', stream: encodedRequest, }); } pending.set(encodedRequest, resolve); // send the request to subscribe to bybit subject.next({ op: 'subscribe', args: [encodedRequest], // Need to bypass the type system here because the subject // has no way to type input and output separately // eslint-disable-next-line @typescript-eslint/no-explicit-any }); })]; }); }); } // handle messages at the library level to implement inverse-multiplexing subject.subscribe({ next: function (message) { // BybitWebsocketMessage.is is a "fromString" decoder, so we have to // decode before invoking the type-guard in the match statement below var decoded = t .union([BybitWebsocketMessage_1.BybitWebsocketMessage, BybitSubscriptionResponse_1.BybitSubscriptionResponse, t.unknown]) .decode(message); if (E.isLeft(decoded)) { throw new Error('absurd -- union of unknown will always decode'); } (0, ts_pattern_1.match)(decoded.right) .when(BybitWebsocketMessage_1.BybitWebsocketMessage.is, function (message) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, (0, function_1.pipe)(TE.Do, TE.bind('topic', function () { return TE.of(message.topic); }), TE.bind('data', function () { return decodeBybitChannelMessage(message); }), TE.bindW('subscribee', function () { return TE.fromEither(E.fromNullable({ type: 'cannot find a subscriber for this websocket topic', topic: message.topic, })(subscribers.get(BybitKlineSubscriptionRequest_1.BybitKlineSubscriptionRequestFromString.encode(message.topic)))); }), TE.fold(function (error) { return T.fromIO(Console.error('Error handling websocket data: ' + JSON.stringify(error))); }, function (_a) { var data = _a.data, topic = _a.topic, subscribee = _a.subscribee; return function () { return __awaiter(void 0, void 0, void 0, function () { var openTime_1; return __generator(this, function (_a) { if (!subscribee.initialized) { openTime_1 = (0, function_1.pipe)(data[0].start, od_1.default.subtract(topic.interval.unit, topic.interval.quantifier)); // sequence publication of the rest data subscribee.promise = subscribee.promise.then(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { // Note: Nobody is listening to these errors so they have to be fatal return [2 /*return*/, (0, function_1.pipe)(restClient.kline({ symbol: topic.symbol, interval: topic.interval, from: openTime_1, limit: one, }), TE.fold(function (error) { return T.fromIO(function () { throw new Error('Unable to query most-recent complete candle: ' + JSON.stringify(error)); }); }, function (response) { return T.fromIO(function () { var previousCandle = response.result.filter(function (kline) { return kline.open_time.getTime() === openTime_1.getTime(); }); if (!previousCandle[0]) { throw new Error('Expected kline query to return previous candle'); } subscribee.initialized = true; subscribee.subject.next([ wsKline({ interval: topic.interval, kline: previousCandle[0], timestamp: response.time_now, }), ]); }); }), function (invokeTask) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, invokeTask()]; }); }); })]; }); }); }); } // then sequence publication of the websocket data subscribee.promise = subscribee.promise.then(function () { return subscribee.subject.next(data); }); return [2 /*return*/, subscribee.promise]; }); }); }; }), function (invokeTask) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, invokeTask()]; }); }); })]; }); }); }) .when(BybitSubscriptionResponse_1.BybitSubscriptionResponse.is, function (response) { debug.ws(util.format('Subscription handeshake complete for %s', JSON.stringify(response.request.args))); var resolver = O.fromNullable(pending.get(response.request.args[0])); pending.delete(response.request.args[0]); var subject = new rxjs_1.BehaviorSubject([]); (0, function_1.pipe)(resolver, O.map(function (resolve) { subscribers.set(response.request.args[0], { initialized: false, promise: new Promise(function (resolve) { return resolve(); }), subject: subject, }); resolve(subject.asObservable()); })); }) .otherwise(function () { return debug.warn('unmatched message:', JSON.stringify(message)); }); }, error: console.error, complete: function_1.constVoid, }); return { subscribe: subscribe }; }; exports.bybitPublicWebsocket = bybitPublicWebsocket; //# sourceMappingURL=websocket-public.js.map