add spot websocket client (#99)
This commit is contained in:
23
README.md
23
README.md
@@ -42,7 +42,7 @@ There are three REST API modules as there are some differences in each contract
|
|||||||
3. `LinearClient` for linear perpetual
|
3. `LinearClient` for linear perpetual
|
||||||
|
|
||||||
### REST Inverse
|
### REST Inverse
|
||||||
<details><summary>To use the inverse REST APIs, import the `InverseClient`. Click here to expand and see full sample:</summary>
|
To use the inverse REST APIs, import the `InverseClient`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { InverseClient } = require('bybit-api');
|
const { InverseClient } = require('bybit-api');
|
||||||
@@ -100,12 +100,11 @@ client.getOrderBook({ symbol: 'BTCUSD' })
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
See [inverse-client.ts](./src/inverse-client.ts) for further information.
|
See [inverse-client.ts](./src/inverse-client.ts) for further information.
|
||||||
|
|
||||||
### REST Inverse Futures
|
### REST Inverse Futures
|
||||||
<details><summary>To use the inverse futures REST APIs, import the `InverseFuturesClient`. Click here to expand and see full sample:</summary>
|
To use the inverse futures REST APIs, import the `InverseFuturesClient`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { InverseFuturesClient } = require('bybit-api');
|
const { InverseFuturesClient } = require('bybit-api');
|
||||||
@@ -142,12 +141,10 @@ client.getOrderBook({ symbol: 'BTCUSDH21' })
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
See [inverse-futures-client.ts](./src/inverse-futures-client.ts) for further information.
|
See [inverse-futures-client.ts](./src/inverse-futures-client.ts) for further information.
|
||||||
|
|
||||||
### REST Linear
|
### REST Linear
|
||||||
<details><summary>To use the Linear (USDT) REST APIs, import the `LinearClient`. Click here to expand and see full sample:</summary>
|
To use the Linear (USDT) REST APIs, import the `LinearClient`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { LinearClient } = require('bybit-api');
|
const { LinearClient } = require('bybit-api');
|
||||||
@@ -184,10 +181,8 @@ client.getOrderBook({ symbol: 'BTCUSDT' })
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## WebSockets
|
## WebSockets
|
||||||
<details><summary>Inverse & linear WebSockets can be used via a shared `WebsocketClient`. Click here to expand and see full sample:</summary>
|
Inverse, linear & spot WebSockets can be used via a shared `WebsocketClient`. However, make sure to make one instance of WebsocketClient per market type (spot vs inverse vs linear vs linearfutures):
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { WebsocketClient } = require('bybit-api');
|
const { WebsocketClient } = require('bybit-api');
|
||||||
@@ -206,9 +201,14 @@ const wsConfig = {
|
|||||||
// defaults to false == testnet. Set to true for livenet.
|
// defaults to false == testnet. Set to true for livenet.
|
||||||
// livenet: true
|
// livenet: true
|
||||||
|
|
||||||
|
// NOTE: to listen to multiple markets (spot vs inverse vs linear vs linearfutures) at once, make one WebsocketClient instance per market
|
||||||
|
|
||||||
// defaults to false == inverse. Set to true for linear (USDT) trading.
|
// defaults to false == inverse. Set to true for linear (USDT) trading.
|
||||||
// linear: true
|
// linear: true
|
||||||
|
|
||||||
|
// defaults to false == inverse. Set to true for spot trading. These booleans will be changed into a single setting in future.
|
||||||
|
// spot: true
|
||||||
|
|
||||||
// how long to wait (in ms) before deciding the connection should be terminated & reconnected
|
// how long to wait (in ms) before deciding the connection should be terminated & reconnected
|
||||||
// pongTimeout: 1000,
|
// pongTimeout: 1000,
|
||||||
|
|
||||||
@@ -263,7 +263,6 @@ ws.on('error', err => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
See [websocket-client.ts](./src/websocket-client.ts) for further information.
|
See [websocket-client.ts](./src/websocket-client.ts) for further information.
|
||||||
|
|
||||||
@@ -274,8 +273,6 @@ Note: for linear websockets, pass `linear: true` in the constructor options when
|
|||||||
## Customise Logging
|
## Customise Logging
|
||||||
Pass a custom logger which supports the log methods `silly`, `debug`, `notice`, `info`, `warning` and `error`, or override methods from the default logger as desired.
|
Pass a custom logger which supports the log methods `silly`, `debug`, `notice`, `info`, `warning` and `error`, or override methods from the default logger as desired.
|
||||||
|
|
||||||
<details><summary>Click here to expand and see full sample:</summary>
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { WebsocketClient, DefaultLogger } = require('bybit-api');
|
const { WebsocketClient, DefaultLogger } = require('bybit-api');
|
||||||
|
|
||||||
@@ -288,8 +285,6 @@ const ws = new WebsocketClient(
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Browser Usage
|
## Browser Usage
|
||||||
Build a bundle using webpack:
|
Build a bundle using webpack:
|
||||||
- `npm install`
|
- `npm install`
|
||||||
|
|||||||
@@ -27,6 +27,19 @@ const linearEndpoints = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const spotEndpoints = {
|
||||||
|
private: {
|
||||||
|
livenet: 'wss://stream.bybit.com/spot/ws',
|
||||||
|
testnet: 'wss://stream-testnet.bybit.com/spot/ws',
|
||||||
|
},
|
||||||
|
public: {
|
||||||
|
livenet: 'wss://stream.bybit.com/spot/quote/ws/v1',
|
||||||
|
livenet2: 'wss://stream.bybit.com/spot/quote/ws/v2',
|
||||||
|
testnet: 'wss://stream-testnet.bybit.com/spot/quote/ws/v1',
|
||||||
|
testnet2: 'wss://stream-testnet.bybit.com/spot/quote/ws/v2',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loggerCategory = { category: 'bybit-ws' };
|
const loggerCategory = { category: 'bybit-ws' };
|
||||||
|
|
||||||
const READY_STATE_INITIAL = 0;
|
const READY_STATE_INITIAL = 0;
|
||||||
@@ -47,7 +60,11 @@ export interface WSClientConfigurableOptions {
|
|||||||
key?: string;
|
key?: string;
|
||||||
secret?: string;
|
secret?: string;
|
||||||
livenet?: boolean;
|
livenet?: boolean;
|
||||||
|
|
||||||
|
// defaults to inverse. Only set one at a time (this interface will change in future)
|
||||||
linear?: boolean;
|
linear?: boolean;
|
||||||
|
spot?: boolean;
|
||||||
|
|
||||||
pongTimeout?: number;
|
pongTimeout?: number;
|
||||||
pingInterval?: number;
|
pingInterval?: number;
|
||||||
reconnectTimeout?: number;
|
reconnectTimeout?: number;
|
||||||
@@ -59,6 +76,7 @@ export interface WSClientConfigurableOptions {
|
|||||||
export interface WebsocketClientOptions extends WSClientConfigurableOptions {
|
export interface WebsocketClientOptions extends WSClientConfigurableOptions {
|
||||||
livenet: boolean;
|
livenet: boolean;
|
||||||
linear: boolean;
|
linear: boolean;
|
||||||
|
spot: boolean;
|
||||||
pongTimeout: number;
|
pongTimeout: number;
|
||||||
pingInterval: number;
|
pingInterval: number;
|
||||||
reconnectTimeout: number;
|
reconnectTimeout: number;
|
||||||
@@ -68,9 +86,11 @@ export interface WebsocketClientOptions extends WSClientConfigurableOptions {
|
|||||||
export const wsKeyInverse = 'inverse';
|
export const wsKeyInverse = 'inverse';
|
||||||
export const wsKeyLinearPrivate = 'linearPrivate';
|
export const wsKeyLinearPrivate = 'linearPrivate';
|
||||||
export const wsKeyLinearPublic = 'linearPublic';
|
export const wsKeyLinearPublic = 'linearPublic';
|
||||||
|
export const wsKeySpotPrivate = 'spotPrivate';
|
||||||
|
export const wsKeySpotPublic = 'spotPublic';
|
||||||
|
|
||||||
// This is used to differentiate between each of the available websocket streams (as bybit has multiple websockets)
|
// This is used to differentiate between each of the available websocket streams (as bybit has multiple websockets)
|
||||||
export type WsKey = 'inverse' | 'linearPrivate' | 'linearPublic';
|
export type WsKey = 'inverse' | 'linearPrivate' | 'linearPublic' | 'spotPrivate' | 'spotPublic';
|
||||||
|
|
||||||
const getLinearWsKeyForTopic = (topic: string): WsKey => {
|
const getLinearWsKeyForTopic = (topic: string): WsKey => {
|
||||||
const privateLinearTopics = ['position', 'execution', 'order', 'stop_order', 'wallet'];
|
const privateLinearTopics = ['position', 'execution', 'order', 'stop_order', 'wallet'];
|
||||||
@@ -80,6 +100,14 @@ const getLinearWsKeyForTopic = (topic: string): WsKey => {
|
|||||||
|
|
||||||
return wsKeyLinearPublic;
|
return wsKeyLinearPublic;
|
||||||
}
|
}
|
||||||
|
const getSpotWsKeyForTopic = (topic: string): WsKey => {
|
||||||
|
const privateLinearTopics = ['position', 'execution', 'order', 'stop_order'];
|
||||||
|
if (privateLinearTopics.includes(topic)) {
|
||||||
|
return wsKeySpotPrivate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wsKeySpotPublic;
|
||||||
|
}
|
||||||
|
|
||||||
export declare interface WebsocketClient {
|
export declare interface WebsocketClient {
|
||||||
on(event: 'open' | 'reconnected', listener: ({ wsKey: WsKey, event: any }) => void): this;
|
on(event: 'open' | 'reconnected', listener: ({ wsKey: WsKey, event: any }) => void): this;
|
||||||
@@ -102,6 +130,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
this.options = {
|
this.options = {
|
||||||
livenet: false,
|
livenet: false,
|
||||||
linear: false,
|
linear: false,
|
||||||
|
spot: false,
|
||||||
pongTimeout: 1000,
|
pongTimeout: 1000,
|
||||||
pingInterval: 10000,
|
pingInterval: 10000,
|
||||||
reconnectTimeout: 500,
|
reconnectTimeout: 500,
|
||||||
@@ -110,6 +139,9 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
|
|
||||||
if (this.isLinear()) {
|
if (this.isLinear()) {
|
||||||
this.restClient = new LinearClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
this.restClient = new LinearClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
||||||
|
} else if (this.isSpot()) {
|
||||||
|
// TODO: spot client
|
||||||
|
this.restClient = new LinearClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
||||||
} else {
|
} else {
|
||||||
this.restClient = new InverseClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
this.restClient = new InverseClient(undefined, undefined, this.isLivenet(), this.options.restOptions, this.options.requestOptions);
|
||||||
}
|
}
|
||||||
@@ -123,8 +155,12 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
return this.options.linear === true;
|
return this.options.linear === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isSpot(): boolean {
|
||||||
|
return this.options.spot === true;
|
||||||
|
}
|
||||||
|
|
||||||
public isInverse(): boolean {
|
public isInverse(): boolean {
|
||||||
return !this.isLinear();
|
return !this.isLinear() && !this.isSpot();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,6 +227,10 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
if (this.isLinear()) {
|
if (this.isLinear()) {
|
||||||
return [this.connect(wsKeyLinearPublic), this.connect(wsKeyLinearPrivate)];
|
return [this.connect(wsKeyLinearPublic), this.connect(wsKeyLinearPrivate)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isSpot()) {
|
||||||
|
return [this.connect(wsKeySpotPublic), this.connect(wsKeySpotPrivate)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async connect(wsKey: WsKey): Promise<WebSocket | undefined> {
|
private async connect(wsKey: WsKey): Promise<WebSocket | undefined> {
|
||||||
@@ -246,7 +286,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
private async getAuthParams(wsKey: WsKey): Promise<string> {
|
private async getAuthParams(wsKey: WsKey): Promise<string> {
|
||||||
const { key, secret } = this.options;
|
const { key, secret } = this.options;
|
||||||
|
|
||||||
if (key && secret && wsKey !== wsKeyLinearPublic) {
|
if (key && secret && wsKey !== wsKeyLinearPublic && wsKey !== wsKeySpotPublic) {
|
||||||
this.logger.debug('Getting auth\'d request params', { ...loggerCategory, wsKey });
|
this.logger.debug('Getting auth\'d request params', { ...loggerCategory, wsKey });
|
||||||
|
|
||||||
const timeOffset = await this.restClient.getTimeOffset();
|
const timeOffset = await this.restClient.getTimeOffset();
|
||||||
@@ -365,7 +405,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
|
|
||||||
private onWsOpen(event, wsKey: WsKey) {
|
private onWsOpen(event, wsKey: WsKey) {
|
||||||
if (this.wsStore.isConnectionState(wsKey, READY_STATE_CONNECTING)) {
|
if (this.wsStore.isConnectionState(wsKey, READY_STATE_CONNECTING)) {
|
||||||
this.logger.info('Websocket connected', { ...loggerCategory, wsKey, livenet: this.isLivenet(), linear: this.isLinear() });
|
this.logger.info('Websocket connected', { ...loggerCategory, wsKey, livenet: this.isLivenet(), linear: this.isLinear(), spot: this.isSpot() });
|
||||||
this.emit('open', { wsKey, event });
|
this.emit('open', { wsKey, event });
|
||||||
} else if (this.wsStore.isConnectionState(wsKey, READY_STATE_RECONNECTING)) {
|
} else if (this.wsStore.isConnectionState(wsKey, READY_STATE_RECONNECTING)) {
|
||||||
this.logger.info('Websocket reconnected', { ...loggerCategory, wsKey });
|
this.logger.info('Websocket reconnected', { ...loggerCategory, wsKey });
|
||||||
@@ -439,7 +479,8 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
return this.options.wsUrl;
|
return this.options.wsUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
const networkKey = this.options.livenet ? 'livenet' : 'testnet';
|
const networkKey = this.isLivenet() ? 'livenet' : 'testnet';
|
||||||
|
// TODO: reptitive
|
||||||
if (this.isLinear() || wsKey.startsWith('linear')){
|
if (this.isLinear() || wsKey.startsWith('linear')){
|
||||||
if (wsKey === wsKeyLinearPublic) {
|
if (wsKey === wsKeyLinearPublic) {
|
||||||
return linearEndpoints.public[networkKey];
|
return linearEndpoints.public[networkKey];
|
||||||
@@ -452,10 +493,31 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
this.logger.error('Unhandled linear wsKey: ', { ...loggerCategory, wsKey });
|
this.logger.error('Unhandled linear wsKey: ', { ...loggerCategory, wsKey });
|
||||||
return linearEndpoints[networkKey];
|
return linearEndpoints[networkKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isSpot() || wsKey.startsWith('spot')){
|
||||||
|
if (wsKey === wsKeySpotPublic) {
|
||||||
|
return spotEndpoints.public[networkKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wsKey === wsKeySpotPrivate) {
|
||||||
|
return spotEndpoints.private[networkKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.error('Unhandled spot wsKey: ', { ...loggerCategory, wsKey });
|
||||||
|
return spotEndpoints[networkKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to inverse
|
||||||
return inverseEndpoints[networkKey];
|
return inverseEndpoints[networkKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
private getWsKeyForTopic(topic: string) {
|
private getWsKeyForTopic(topic: string) {
|
||||||
return this.isInverse() ? wsKeyInverse : getLinearWsKeyForTopic(topic);
|
if (this.isInverse()) {
|
||||||
|
return wsKeyInverse;
|
||||||
|
}
|
||||||
|
if (this.isLinear()) {
|
||||||
|
return getLinearWsKeyForTopic(topic)
|
||||||
|
}
|
||||||
|
return getSpotWsKeyForTopic(topic);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user