From 6a925f18e1f4aed25f735c13c25453bf6fe76bf5 Mon Sep 17 00:00:00 2001 From: tiagosiebler Date: Sun, 9 Oct 2022 19:20:05 +0100 Subject: [PATCH 1/2] fix overly strict inverse & usdc options tests --- src/constants/enum.ts | 2 ++ test/inverse-futures/private.read.test.ts | 22 +++++++++++++++++++- test/inverse-futures/private.write.test.ts | 24 ++++++++++++++++++++-- test/usdc/options/private.write.test.ts | 18 ++++++++-------- 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/constants/enum.ts b/src/constants/enum.ts index 769ad4c..4843930 100644 --- a/src/constants/enum.ts +++ b/src/constants/enum.ts @@ -51,6 +51,8 @@ export const API_ERROR_CODE = { INSUFFICIENT_BALANCE_FOR_ORDER_COST_LINEAR: 130080, SAME_SLTP_MODE_LINEAR: 130150, RISK_ID_NOT_MODIFIED: 134026, + /** E.g. USDC Options trading, trying to access a symbol that is no longer active */ + CONTRACT_NAME_NOT_EXIST: 3100111, ORDER_NOT_EXIST: 3100136, NO_ACTIVE_ORDER: 3100205, /** E.g. USDC Options trading when the account hasn't been opened for USDC Options yet */ diff --git a/test/inverse-futures/private.read.test.ts b/test/inverse-futures/private.read.test.ts index 7df1ec0..0d52485 100644 --- a/test/inverse-futures/private.read.test.ts +++ b/test/inverse-futures/private.read.test.ts @@ -12,7 +12,27 @@ describe('Private Inverse-Futures REST API GET Endpoints', () => { }); // Warning: if some of these start to fail with 10001 params error, it's probably that this future expired and a newer one exists with a different symbol! - const symbol = 'BTCUSDU22'; + let symbol = ''; + + beforeAll(async () => { + const symbolsResponse = await api.getSymbols(); + + const prefix = 'BTCUSD'; + + const futuresAsset = symbolsResponse.result + .filter((row) => row.name.startsWith(prefix)) + .find((row) => { + const splitSymbol = row.name.split(prefix); + return splitSymbol[1] && splitSymbol[1] !== 'T'; + }); + + if (!futuresAsset?.name) { + throw new Error('No symbol'); + } + + symbol = futuresAsset?.name; + console.log('Symbol: ', symbol); + }); it('getApiKeyInfo()', async () => { expect(await api.getApiKeyInfo()).toMatchObject(successResponseObject()); diff --git a/test/inverse-futures/private.write.test.ts b/test/inverse-futures/private.write.test.ts index 074d8f9..b1de1c6 100644 --- a/test/inverse-futures/private.write.test.ts +++ b/test/inverse-futures/private.write.test.ts @@ -17,7 +17,27 @@ describe('Private Inverse-Futures REST API POST Endpoints', () => { }); // Warning: if some of these start to fail with 10001 params error, it's probably that this future expired and a newer one exists with a different symbol! - const symbol = 'BTCUSDU22'; + let symbol = ''; + + beforeAll(async () => { + const symbolsResponse = await api.getSymbols(); + + const prefix = 'BTCUSD'; + + const futuresAsset = symbolsResponse.result + .filter((row) => row.name.startsWith(prefix)) + .find((row) => { + const splitSymbol = row.name.split(prefix); + return splitSymbol[1] && splitSymbol[1] !== 'T'; + }); + + if (!futuresAsset?.name) { + throw new Error('No symbol'); + } + + symbol = futuresAsset?.name; + console.log('Symbol: ', symbol); + }); // These tests are primarily check auth is working by expecting balance or order not found style errors @@ -134,7 +154,7 @@ describe('Private Inverse-Futures REST API POST Endpoints', () => { take_profit: 50000, }) ).toMatchObject({ - ret_code: API_ERROR_CODE.POSITION_IDX_NOT_MATCH_POSITION_MODE, + ret_code: API_ERROR_CODE.POSITION_STATUS_NOT_NORMAL, }); }); diff --git a/test/usdc/options/private.write.test.ts b/test/usdc/options/private.write.test.ts index 442aa20..194287e 100644 --- a/test/usdc/options/private.write.test.ts +++ b/test/usdc/options/private.write.test.ts @@ -31,7 +31,7 @@ describe('Private USDC Options REST API POST Endpoints', () => { timeInForce: 'GoodTillCancel', }) ).toMatchObject({ - retCode: API_ERROR_CODE.ACCOUNT_NOT_EXIST, + retCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST, }); }); @@ -59,8 +59,8 @@ describe('Private USDC Options REST API POST Endpoints', () => { ]) ).toMatchObject({ result: [ - { errorCode: API_ERROR_CODE.ACCOUNT_NOT_EXIST }, - { errorCode: API_ERROR_CODE.ACCOUNT_NOT_EXIST }, + { errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST }, + { errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST }, ], }); }); @@ -72,7 +72,7 @@ describe('Private USDC Options REST API POST Endpoints', () => { orderId: 'somethingFake', }) ).toMatchObject({ - retCode: API_ERROR_CODE.ORDER_NOT_EXIST, + retCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST, }); }); @@ -90,8 +90,8 @@ describe('Private USDC Options REST API POST Endpoints', () => { ]) ).toMatchObject({ result: [ - { errorCode: API_ERROR_CODE.ORDER_NOT_EXIST }, - { errorCode: API_ERROR_CODE.ORDER_NOT_EXIST }, + { errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST }, + { errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST }, ], }); }); @@ -103,7 +103,7 @@ describe('Private USDC Options REST API POST Endpoints', () => { orderId: 'somethingFake1', }) ).toMatchObject({ - retCode: API_ERROR_CODE.ORDER_NOT_EXIST, + retCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST, }); }); @@ -121,8 +121,8 @@ describe('Private USDC Options REST API POST Endpoints', () => { ]) ).toMatchObject({ result: [ - { errorCode: API_ERROR_CODE.ORDER_NOT_EXIST }, - { errorCode: API_ERROR_CODE.ORDER_NOT_EXIST }, + { errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST }, + { errorCode: API_ERROR_CODE.CONTRACT_NAME_NOT_EXIST }, ], }); }); From 9c3a37e7b085e6e7aaf5f4eaa6b43dfbb51b5836 Mon Sep 17 00:00:00 2001 From: tiagosiebler Date: Sun, 9 Oct 2022 19:21:46 +0100 Subject: [PATCH 2/2] fix ws subscribe/unsubscribe workflows, which were repeating requests to each active ws connection unintentionally --- package.json | 2 +- src/websocket-client.ts | 41 ++++++++++++++++++++--------------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index cc17ac8..b4dc95a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bybit-api", - "version": "3.0.2", + "version": "3.1.0", "description": "Complete & robust node.js SDK for Bybit's REST APIs and WebSockets, with TypeScript & integration tests.", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/websocket-client.ts b/src/websocket-client.ts index 4e145a2..f7063ce 100644 --- a/src/websocket-client.ts +++ b/src/websocket-client.ts @@ -116,22 +116,21 @@ export class WebsocketClient extends EventEmitter { public subscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean) { const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; - topics.forEach((topic) => - this.wsStore.addTopic( - getWsKeyForTopic(this.options.market, topic, isPrivateTopic), - topic - ) - ); + topics.forEach((topic) => { + const wsKey = getWsKeyForTopic( + this.options.market, + topic, + isPrivateTopic + ); + + // Persist topic for reconnects + this.wsStore.addTopic(wsKey, topic); - // attempt to send subscription topic per websocket - this.wsStore.getKeys().forEach((wsKey: WsKey) => { // if connected, send subscription request if ( this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED) ) { - return this.requestSubscribeTopics(wsKey, [ - ...this.wsStore.getTopics(wsKey), - ]); + return this.requestSubscribeTopics(wsKey, [topic]); } // start connection process if it hasn't yet begun. Topics are automatically subscribed to on-connect @@ -157,21 +156,21 @@ export class WebsocketClient extends EventEmitter { */ public unsubscribe(wsTopics: WsTopic[] | WsTopic, isPrivateTopic?: boolean) { const topics = Array.isArray(wsTopics) ? wsTopics : [wsTopics]; - topics.forEach((topic) => - this.wsStore.deleteTopic( - getWsKeyForTopic(this.options.market, topic, isPrivateTopic), - topic - ) - ); + topics.forEach((topic) => { + const wsKey = getWsKeyForTopic( + this.options.market, + topic, + isPrivateTopic + ); + + // Remove topic from persistence for reconnects + this.wsStore.deleteTopic(wsKey, topic); - this.wsStore.getKeys().forEach((wsKey: WsKey) => { // unsubscribe request only necessary if active connection exists if ( this.wsStore.isConnectionState(wsKey, WsConnectionStateEnum.CONNECTED) ) { - this.requestUnsubscribeTopics(wsKey, [ - ...this.wsStore.getTopics(wsKey), - ]); + this.requestUnsubscribeTopics(wsKey, [topic]); } }); }