diff --git a/README.md b/README.md index b78e642..ebd9177 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Check out my related projects: - [binance](https://www.npmjs.com/package/binance) - [okx-api](https://www.npmjs.com/package/okx-api) - [bitget-api](https://www.npmjs.com/package/bitget-api) + - [bitmart-api](https://www.npmjs.com/package/bitmart-api) - Try my misc utilities: - [orderbooks](https://www.npmjs.com/package/orderbooks) - [accountstate](https://www.npmjs.com/package/accountstate) diff --git a/examples/demo-trading.ts b/examples/demo-trading.ts new file mode 100644 index 0000000..6a7add0 --- /dev/null +++ b/examples/demo-trading.ts @@ -0,0 +1,125 @@ +import { RestClientV5, WebsocketClient } from '../src/index'; + +// or +// import { RestClientV5 } from 'bybit-api'; + +const key = process.env.API_KEY_COM; +const secret = process.env.API_SECRET_COM; + +/** + * + * + * This example demonstrates how to use Bybit's demo trading functionality, both for REST and WS. + * + * Refer to the API docs for more information: https://bybit-exchange.github.io/docs/v5/demo + * + * + */ + +const restClient = new RestClientV5({ + key: key, + secret: secret, + parseAPIRateLimits: true, + /** + * Set this to true to enable demo trading: + */ + demoTrading: true, +}); + +const wsClient = new WebsocketClient({ + market: 'v5', + /** + * Set this to true to enable demo trading for the private account data WS + * Topics: order,execution,position,wallet,greeks + */ + demoTrading: true, +}); + +function setWsClientEventListeners( + websocketClient: WebsocketClient, + accountRef: string, +): void { + websocketClient.on('update', (data) => { + console.log(new Date(), accountRef, 'data ', JSON.stringify(data)); + // console.log('raw message received ', JSON.stringify(data, null, 2)); + }); + + websocketClient.on('open', (data) => { + console.log(new Date(), accountRef, 'connection opened open:', data.wsKey); + }); + websocketClient.on('response', (data) => { + console.log( + new Date(), + accountRef, + 'log response: ', + JSON.stringify(data, null, 2), + ); + }); + websocketClient.on('reconnect', ({ wsKey }) => { + console.log( + new Date(), + accountRef, + 'ws automatically reconnecting.... ', + wsKey, + ); + }); + websocketClient.on('reconnected', (data) => { + console.log(new Date(), accountRef, 'ws has reconnected ', data?.wsKey); + }); + websocketClient.on('error', (data) => { + console.error(new Date(), accountRef, 'ws exception: ', data); + }); +} + +(async () => { + try { + setWsClientEventListeners(wsClient, 'demoAcc'); + + const balResponse1 = await restClient.getWalletBalance({ + accountType: 'CONTRACT', + }); + console.log('balResponse1: ', JSON.stringify(balResponse1, null, 2)); + + const demoFunds = await restClient.requestDemoTradingFunds(); + console.log(`requested demo funds: `, demoFunds); + + const balResponse2 = await restClient.getWalletBalance({ + accountType: 'CONTRACT', + }); + console.log('balResponse2: ', JSON.stringify(balResponse2, null, 2)); + + /** Simple examples for private REST API calls with bybit's V5 REST APIs */ + const response = await restClient.getPositionInfo({ + category: 'linear', + symbol: 'BTCUSDT', + }); + + console.log('response:', response); + + // Trade USDT linear perps + const buyOrderResult = await restClient.submitOrder({ + category: 'linear', + symbol: 'BTCUSDT', + orderType: 'Market', + qty: '1', + side: 'Buy', + }); + console.log('buyOrderResult:', buyOrderResult); + + const sellOrderResult = await restClient.submitOrder({ + category: 'linear', + symbol: 'BTCUSDT', + orderType: 'Market', + qty: '1', + side: 'Sell', + }); + console.log('sellOrderResult:', sellOrderResult); + + const balResponse3 = await restClient.getWalletBalance({ + accountType: 'CONTRACT', + }); + console.log('balResponse2: ', JSON.stringify(balResponse3, null, 2)); + } catch (e) { + console.error('request failed: ', e); + } +})(); diff --git a/package-lock.json b/package-lock.json index 2a9f6e0..b03e00c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bybit-api", - "version": "3.9.7", + "version": "3.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "bybit-api", - "version": "3.9.7", + "version": "3.10.0", "license": "MIT", "dependencies": { "axios": "^1.6.6", @@ -3230,9 +3230,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -9303,9 +9303,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "fs.realpath": { "version": "1.0.0", diff --git a/package.json b/package.json index fc56c34..c631442 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bybit-api", - "version": "3.9.8", + "version": "3.10.0", "description": "Complete & robust Node.js SDK for Bybit's REST APIs and WebSockets, with TypeScript & strong end to end tests.", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/src/rest-client-v5.ts b/src/rest-client-v5.ts index 8146164..0a94c83 100644 --- a/src/rest-client-v5.ts +++ b/src/rest-client-v5.ts @@ -166,6 +166,10 @@ export class RestClientV5 extends BaseRestClient { return this.get('/v3/public/time'); } + requestDemoTradingFunds(): Promise<{}> { + return this.postPrivate('/v5/account/demo-apply-money'); + } + /** * ****** Market APIs diff --git a/src/types/websockets.ts b/src/types/websockets.ts index ee9730f..76042b2 100644 --- a/src/types/websockets.ts +++ b/src/types/websockets.ts @@ -88,6 +88,13 @@ export interface WSClientConfigurableOptions { secret?: string; testnet?: boolean; + /** + * Set to `true` to connect to Bybit's V5 demo trading: https://bybit-exchange.github.io/docs/v5/demo + * + * Only the "V5" "market" is supported here. + */ + demoTrading?: boolean; + /** * The API group this client should connect to. * @@ -109,7 +116,6 @@ export interface WSClientConfigurableOptions { } export interface WebsocketClientOptions extends WSClientConfigurableOptions { - testnet?: boolean; market: APIMarket; pongTimeout: number; pingInterval: number; diff --git a/src/util/requestUtils.ts b/src/util/requestUtils.ts index bace4a3..1a5be38 100644 --- a/src/util/requestUtils.ts +++ b/src/util/requestUtils.ts @@ -13,6 +13,11 @@ export interface RestClientOptions { /** Set to `true` to connect to testnet. Uses the live environment by default. */ testnet?: boolean; + /** + * Set to `true` to use Bybit's V5 demo trading: https://bybit-exchange.github.io/docs/v5/demo + */ + demoTrading?: boolean; + /** Override the max size of the request window (in ms) */ recv_window?: number; @@ -92,15 +97,20 @@ export function serializeParams( export function getRestBaseUrl( useTestnet: boolean, - restInverseOptions: RestClientOptions, + restClientOptions: RestClientOptions, ): string { const exchangeBaseUrls = { livenet: 'https://api.bybit.com', testnet: 'https://api-testnet.bybit.com', + demoLivenet: 'https://api-demo.bybit.com', }; - if (restInverseOptions.baseUrl) { - return restInverseOptions.baseUrl; + if (restClientOptions.baseUrl) { + return restClientOptions.baseUrl; + } + + if (restClientOptions.demoTrading) { + return exchangeBaseUrls.demoLivenet; } if (useTestnet) { diff --git a/src/util/websocket-util.ts b/src/util/websocket-util.ts index 400b81f..b6084d3 100644 --- a/src/util/websocket-util.ts +++ b/src/util/websocket-util.ts @@ -1,6 +1,6 @@ import WebSocket from 'isomorphic-ws'; -import { APIMarket, CategoryV5, WsKey } from '../types'; +import { APIMarket, CategoryV5, WebsocketClientOptions, WsKey } from '../types'; import { DefaultLogger } from './logger'; interface NetworkMapV3 { @@ -398,14 +398,21 @@ export function getWsKeyForTopic( export function getWsUrl( wsKey: WsKey, - wsUrl: string | undefined, - isTestnet: boolean, + wsClientOptions: WebsocketClientOptions, logger: typeof DefaultLogger, ): string { + const wsUrl = wsClientOptions.wsUrl; if (wsUrl) { return wsUrl; } + // https://bybit-exchange.github.io/docs/v5/demo + const isDemoTrading = wsClientOptions.demoTrading; + if (isDemoTrading) { + return 'wss://stream-demo.bybit.com/v5/private'; + } + + const isTestnet = wsClientOptions.testnet; const networkKey = isTestnet ? 'testnet' : 'livenet'; switch (wsKey) { diff --git a/src/websocket-client.ts b/src/websocket-client.ts index 595243e..7d6a82d 100644 --- a/src/websocket-client.ts +++ b/src/websocket-client.ts @@ -623,12 +623,7 @@ export class WebsocketClient extends EventEmitter { } const authParams = await this.getAuthParams(wsKey); - const url = getWsUrl( - wsKey, - this.options.wsUrl, - this.isTestnet(), - this.logger, - ); + const url = getWsUrl(wsKey, this.options, this.logger); const ws = this.connectToWsUrl(url + authParams, wsKey); return this.wsStore.setWs(wsKey, ws);