Merge pull request #196 from tiagosiebler/next
v3.2.0: Contract V3 REST & WebSocket Clients. Improve websocket reconnection resilience
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
Node.js connector for the Bybit APIs and WebSockets:
|
Node.js connector for the Bybit APIs and WebSockets:
|
||||||
- Complete integration with all bybit APIs.
|
- Complete integration with all bybit APIs.
|
||||||
- TypeScript support (with type declarations for most API requests & responses).
|
- TypeScript support (with type declarations for most API requests & responses).
|
||||||
- Over 300 integration tests making real API calls & WebSocket connections, validating any changes before they reach npm.
|
- Over 300 end-to-end tests making real API calls & WebSocket connections, validating any changes before they reach npm.
|
||||||
- Robust WebSocket integration with configurable connection heartbeats & automatic reconnect then resubscribe workflows.
|
- Robust WebSocket integration with configurable connection heartbeats & automatic reconnect then resubscribe workflows.
|
||||||
- Browser support (via webpack bundle - see "Browser Usage" below).
|
- Browser support (via webpack bundle - see "Browser Usage" below).
|
||||||
|
|
||||||
@@ -25,10 +25,10 @@ Node.js connector for the Bybit APIs and WebSockets:
|
|||||||
## Related projects
|
## Related projects
|
||||||
Check out my related projects:
|
Check out my related projects:
|
||||||
- Try my connectors:
|
- Try my connectors:
|
||||||
- [ftx-api](https://www.npmjs.com/package/ftx-api)
|
|
||||||
- [bybit-api](https://www.npmjs.com/package/bybit-api)
|
|
||||||
- [binance](https://www.npmjs.com/package/binance)
|
- [binance](https://www.npmjs.com/package/binance)
|
||||||
|
- [bybit-api](https://www.npmjs.com/package/bybit-api)
|
||||||
- [okx-api](https://www.npmjs.com/package/okx-api)
|
- [okx-api](https://www.npmjs.com/package/okx-api)
|
||||||
|
- [ftx-api](https://www.npmjs.com/package/ftx-api)
|
||||||
- Try my misc utilities:
|
- Try my misc utilities:
|
||||||
- [orderbooks](https://www.npmjs.com/package/orderbooks)
|
- [orderbooks](https://www.npmjs.com/package/orderbooks)
|
||||||
- Check out my examples:
|
- Check out my examples:
|
||||||
@@ -180,6 +180,8 @@ The WebsocketClient can be configured to a specific API group using the market p
|
|||||||
| Copy Trading | `market: 'linear'` | The [copy trading](https://bybit-exchange.github.io/docs/copy_trading/#t-websocket) category. Use the linear market to listen to all copy trading topics. |
|
| Copy Trading | `market: 'linear'` | The [copy trading](https://bybit-exchange.github.io/docs/copy_trading/#t-websocket) category. Use the linear market to listen to all copy trading topics. |
|
||||||
| USDC Perps | `market: 'usdcPerp` | The [USDC perps](https://bybit-exchange.github.io/docs/usdc/perpetual/#t-websocket) category. |
|
| USDC Perps | `market: 'usdcPerp` | The [USDC perps](https://bybit-exchange.github.io/docs/usdc/perpetual/#t-websocket) category. |
|
||||||
| USDC Options | `market: 'usdcOption'`| The [USDC options](https://bybit-exchange.github.io/docs/usdc/option/#t-websocket) category. |
|
| USDC Options | `market: 'usdcOption'`| The [USDC options](https://bybit-exchange.github.io/docs/usdc/option/#t-websocket) category. |
|
||||||
|
| Contract v3 USDT | `market: 'contractUSDT'`| The [Contract V3](https://bybit-exchange.github.io/docs/derivativesV3/contract/#t-websocket) category (USDT perps) |
|
||||||
|
| Contract v3 Inverse | `market: 'contractInverse'`| The [Contract V3](https://bybit-exchange.github.io/docs/derivativesV3/contract/#t-websocket) category (inverse perps) |
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { WebsocketClient } = require('bybit-api');
|
const { WebsocketClient } = require('bybit-api');
|
||||||
|
|||||||
24
examples/rest-contract-private.ts
Normal file
24
examples/rest-contract-private.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ContractClient } from '../src/index';
|
||||||
|
|
||||||
|
// or
|
||||||
|
// import { ContractClient } from 'bybit-api';
|
||||||
|
|
||||||
|
const key = process.env.API_KEY_COM;
|
||||||
|
const secret = process.env.API_SECRET_COM;
|
||||||
|
|
||||||
|
const client = new ContractClient({
|
||||||
|
key,
|
||||||
|
secret,
|
||||||
|
strict_param_validation: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const getPositions = await client.getPositions({
|
||||||
|
settleCoin: 'USDT',
|
||||||
|
});
|
||||||
|
console.log('getPositions:', getPositions);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('request failed: ', e);
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -13,10 +13,13 @@ import { WebsocketClient, WS_KEY_MAP, DefaultLogger } from '../src';
|
|||||||
const secret = process.env.API_SECRET;
|
const secret = process.env.API_SECRET;
|
||||||
|
|
||||||
// USDT Perps:
|
// USDT Perps:
|
||||||
const market = 'linear';
|
// const market = 'linear';
|
||||||
// Inverse Perp
|
// Inverse Perp
|
||||||
// const market = 'inverse';
|
// const market = 'inverse';
|
||||||
// const market = 'spotv3';
|
// const market = 'spotv3';
|
||||||
|
// Contract v3
|
||||||
|
const market = 'contractUSDT';
|
||||||
|
// const market = 'contractInverse';
|
||||||
|
|
||||||
// Note: the WebsocketClient defaults to testnet. Set `livenet: true` to use live markets.
|
// Note: the WebsocketClient defaults to testnet. Set `livenet: true` to use live markets.
|
||||||
const wsClient = new WebsocketClient(
|
const wsClient = new WebsocketClient(
|
||||||
@@ -48,8 +51,19 @@ import { WebsocketClient, WS_KEY_MAP, DefaultLogger } from '../src';
|
|||||||
wsClient.on('reconnected', (data) => {
|
wsClient.on('reconnected', (data) => {
|
||||||
console.log('ws has reconnected ', data?.wsKey);
|
console.log('ws has reconnected ', data?.wsKey);
|
||||||
});
|
});
|
||||||
|
wsClient.on('error', (data) => {
|
||||||
|
console.error('ws exception: ', data);
|
||||||
|
});
|
||||||
|
|
||||||
// subscribe to private endpoints
|
// subscribe to private endpoints
|
||||||
// check the api docs in your api category to see the available topics
|
// check the api docs in your api category to see the available topics
|
||||||
wsClient.subscribe(['position', 'execution', 'order', 'wallet']);
|
// wsClient.subscribe(['position', 'execution', 'order', 'wallet']);
|
||||||
|
|
||||||
|
// Contract v3
|
||||||
|
wsClient.subscribe([
|
||||||
|
'user.position.contractAccount',
|
||||||
|
'user.execution.contractAccount',
|
||||||
|
'user.order.contractAccount',
|
||||||
|
'user.wallet.contractAccount',
|
||||||
|
]);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ import { DefaultLogger, WS_KEY_MAP, WebsocketClient } from '../src';
|
|||||||
wsClient.on('reconnected', (data) => {
|
wsClient.on('reconnected', (data) => {
|
||||||
console.log('ws has reconnected ', data?.wsKey);
|
console.log('ws has reconnected ', data?.wsKey);
|
||||||
});
|
});
|
||||||
|
// wsClient.on('error', (data) => {
|
||||||
|
// console.error('ws exception: ', data);
|
||||||
|
// });
|
||||||
|
|
||||||
// Inverse
|
// Inverse
|
||||||
// wsClient.subscribe('trade');
|
// wsClient.subscribe('trade');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bybit-api",
|
"name": "bybit-api",
|
||||||
"version": "3.1.3",
|
"version": "3.2.0",
|
||||||
"description": "Complete & robust node.js SDK for Bybit's REST APIs and WebSockets, with TypeScript & integration tests.",
|
"description": "Complete & robust node.js SDK for Bybit's REST APIs and WebSockets, with TypeScript & integration tests.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ export const API_ERROR_CODE = {
|
|||||||
INSUFFICIENT_BALANCE_FOR_ORDER_COST_LINEAR: 130080,
|
INSUFFICIENT_BALANCE_FOR_ORDER_COST_LINEAR: 130080,
|
||||||
SAME_SLTP_MODE_LINEAR: 130150,
|
SAME_SLTP_MODE_LINEAR: 130150,
|
||||||
RISK_ID_NOT_MODIFIED: 134026,
|
RISK_ID_NOT_MODIFIED: 134026,
|
||||||
|
CONTRACT_ORDER_NOT_EXISTS: 140001,
|
||||||
|
CONTRACT_INSUFFICIENT_BALANCE: 140007,
|
||||||
|
CONTRACT_POSITION_MODE_NOT_MODIFIED: 140025,
|
||||||
|
CONTRACT_MARGIN_MODE_NOT_MODIFIED: 140026,
|
||||||
|
CONTRACT_RISK_LIMIT_INFO_NOT_EXISTS: 140031,
|
||||||
|
CONTRACT_SET_LEVERAGE_NOT_MODIFIED: 140043,
|
||||||
/** E.g. USDC Options trading, trying to access a symbol that is no longer active */
|
/** E.g. USDC Options trading, trying to access a symbol that is no longer active */
|
||||||
CONTRACT_NAME_NOT_EXIST: 3100111,
|
CONTRACT_NAME_NOT_EXIST: 3100111,
|
||||||
ORDER_NOT_EXIST: 3100136,
|
ORDER_NOT_EXIST: 3100136,
|
||||||
|
|||||||
341
src/contract-client.ts
Normal file
341
src/contract-client.ts
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
import {
|
||||||
|
APIResponseWithTime,
|
||||||
|
APIResponseV3,
|
||||||
|
UMCandlesRequest,
|
||||||
|
UMCategory,
|
||||||
|
UMFundingRateHistoryRequest,
|
||||||
|
UMInstrumentInfoRequest,
|
||||||
|
UMOpenInterestRequest,
|
||||||
|
UMOptionDeliveryPriceRequest,
|
||||||
|
UMPublicTradesRequest,
|
||||||
|
ContractOrderRequest,
|
||||||
|
ContractHistoricOrdersRequest,
|
||||||
|
ContractCancelOrderRequest,
|
||||||
|
ContractModifyOrderRequest,
|
||||||
|
ContractActiveOrdersRequest,
|
||||||
|
ContractPositionsRequest,
|
||||||
|
ContractSetAutoAddMarginRequest,
|
||||||
|
ContractSetMarginSwitchRequest,
|
||||||
|
ContractSetPositionModeRequest,
|
||||||
|
ContractSetTPSLRequest,
|
||||||
|
ContractUserExecutionHistoryRequest,
|
||||||
|
ContractClosedPNLRequest,
|
||||||
|
ContractWalletFundRecordRequest,
|
||||||
|
} from './types';
|
||||||
|
import { REST_CLIENT_TYPE_ENUM } from './util';
|
||||||
|
import BaseRestClient from './util/BaseRestClient';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API client for Derivatives V3 Contract APIs
|
||||||
|
*/
|
||||||
|
export class ContractClient extends BaseRestClient {
|
||||||
|
getClientType() {
|
||||||
|
// Follows the same authentication mechanism as other v3 APIs (e.g. USDC)
|
||||||
|
return REST_CLIENT_TYPE_ENUM.v3;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchServerTime(): Promise<number> {
|
||||||
|
const res = await this.getServerTime();
|
||||||
|
return Number(res.time_now);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Market Data Endpoints : these seem exactly the same as the unified margin market data endpoints
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Query order book info. Each side has a depth of 25 orders. */
|
||||||
|
getOrderBook(
|
||||||
|
symbol: string,
|
||||||
|
category: string,
|
||||||
|
limit?: number
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.get('/derivatives/v3/public/order-book/L2', {
|
||||||
|
category,
|
||||||
|
symbol,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get candles/klines */
|
||||||
|
getCandles(params: UMCandlesRequest): Promise<APIResponseV3<any>> {
|
||||||
|
return this.get('/derivatives/v3/public/kline', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get a symbol price/statistics ticker */
|
||||||
|
getSymbolTicker(
|
||||||
|
category: UMCategory,
|
||||||
|
symbol?: string
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.get('/derivatives/v3/public/tickers', { category, symbol });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get trading rules per symbol/contract, incl price/amount/value/leverage filters */
|
||||||
|
getInstrumentInfo(
|
||||||
|
params: UMInstrumentInfoRequest
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.get('/derivatives/v3/public/instruments-info', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Query mark price kline (like getCandles() but for mark price). */
|
||||||
|
getMarkPriceCandles(params: UMCandlesRequest): Promise<APIResponseV3<any>> {
|
||||||
|
return this.get('/derivatives/v3/public/mark-price-kline', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Query Index Price Kline */
|
||||||
|
getIndexPriceCandles(params: UMCandlesRequest): Promise<APIResponseV3<any>> {
|
||||||
|
return this.get('/derivatives/v3/public/index-price-kline', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The funding rate is generated every 8 hours at 00:00 UTC, 08:00 UTC and 16:00 UTC.
|
||||||
|
* For example, if a request is sent at 12:00 UTC, the funding rate generated earlier that day at 08:00 UTC will be sent.
|
||||||
|
*/
|
||||||
|
getFundingRateHistory(
|
||||||
|
params: UMFundingRateHistoryRequest
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.get(
|
||||||
|
'/derivatives/v3/public/funding/history-funding-rate',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Risk Limit */
|
||||||
|
getRiskLimit(
|
||||||
|
category: UMCategory,
|
||||||
|
symbol: string
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.get('/derivatives/v3/public/risk-limit/list', {
|
||||||
|
category,
|
||||||
|
symbol,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get option delivery price */
|
||||||
|
getOptionDeliveryPrice(
|
||||||
|
params: UMOptionDeliveryPriceRequest
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.get('/derivatives/v3/public/delivery-price', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get public trading history */
|
||||||
|
getTrades(params: UMPublicTradesRequest): Promise<APIResponseV3<any>> {
|
||||||
|
return this.get('/derivatives/v3/public/recent-trade', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the total amount of unsettled contracts.
|
||||||
|
* In other words, the total number of contracts held in open positions.
|
||||||
|
*/
|
||||||
|
getOpenInterest(params: UMOpenInterestRequest): Promise<APIResponseV3<any>> {
|
||||||
|
return this.get('/derivatives/v3/public/open-interest', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Contract Account Endpoints
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** -> Order API */
|
||||||
|
|
||||||
|
/** Place an order */
|
||||||
|
submitOrder(params: ContractOrderRequest): Promise<APIResponseV3<any>> {
|
||||||
|
return this.postPrivate('/contract/v3/private/order/create', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Query order history. As order creation/cancellation is asynchronous, the data returned from the interface may be delayed. To access order information in real-time, call getActiveOrders() */
|
||||||
|
getHistoricOrders(
|
||||||
|
params: ContractHistoricOrdersRequest
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.getPrivate('/contract/v3/private/order/list', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cancel order */
|
||||||
|
cancelOrder(params: ContractCancelOrderRequest): Promise<APIResponseV3<any>> {
|
||||||
|
return this.postPrivate('/contract/v3/private/order/cancel', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cancel all orders */
|
||||||
|
cancelAllOrders(symbol: string): Promise<APIResponseV3<any>> {
|
||||||
|
return this.postPrivate('/contract/v3/private/order/cancel-all', {
|
||||||
|
symbol,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Replace order : Active order parameters (such as quantity, price) and stop order parameters cannot be modified in one request at the same time. Please request modification separately. */
|
||||||
|
modifyOrder(params: ContractModifyOrderRequest): Promise<APIResponseV3<any>> {
|
||||||
|
return this.postPrivate('/contract/v3/private/order/replace', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Query Open Order(s) (real-time) */
|
||||||
|
getActiveOrders(
|
||||||
|
params: ContractActiveOrdersRequest
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.getPrivate(
|
||||||
|
'/contract/v3/private/order/unfilled-orders',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** -> Positions API */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query my positions real-time. Accessing personal list of positions.
|
||||||
|
* Either symbol or settleCoin is required.
|
||||||
|
* Users can access their position holding information through this interface, such as the number of position holdings and wallet balance.
|
||||||
|
*/
|
||||||
|
getPositions(params?: ContractPositionsRequest): Promise<APIResponseV3<any>> {
|
||||||
|
return this.getPrivate('/contract/v3/private/position/list', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set auto add margin, or Auto-Margin Replenishment. */
|
||||||
|
setAutoAddMargin(
|
||||||
|
params: ContractSetAutoAddMarginRequest
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.postPrivate(
|
||||||
|
'/contract/v3/private/position/set-auto-add-margin',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Switch cross margin mode/isolated margin mode */
|
||||||
|
setMarginSwitch(
|
||||||
|
params: ContractSetMarginSwitchRequest
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.postPrivate(
|
||||||
|
'/contract/v3/private/position/switch-isolated',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Supports switching between One-Way Mode and Hedge Mode at the coin level. */
|
||||||
|
setPositionMode(
|
||||||
|
params: ContractSetPositionModeRequest
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.postPrivate(
|
||||||
|
'/contract/v3/private/position/switch-mode',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch mode between Full or Partial
|
||||||
|
*/
|
||||||
|
setTPSLMode(
|
||||||
|
symbol: string,
|
||||||
|
tpSlMode: 'Full' | 'Partial'
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.postPrivate('/contract/v3/private/position/switch-tpsl-mode', {
|
||||||
|
symbol,
|
||||||
|
tpSlMode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Leverage setting. */
|
||||||
|
setLeverage(
|
||||||
|
symbol: string,
|
||||||
|
buyLeverage: string,
|
||||||
|
sellLeverage: string
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.postPrivate('/contract/v3/private/position/set-leverage', {
|
||||||
|
symbol,
|
||||||
|
buyLeverage,
|
||||||
|
sellLeverage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set take profit, stop loss, and trailing stop for your open position.
|
||||||
|
* If using partial mode, TP/SL/TS orders will not close your entire position.
|
||||||
|
*/
|
||||||
|
setTPSL(params: ContractSetTPSLRequest): Promise<APIResponseV3<any>> {
|
||||||
|
return this.postPrivate(
|
||||||
|
'/contract/v3/private/position/trading-stop',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set risk limit */
|
||||||
|
setRiskLimit(
|
||||||
|
symbol: string,
|
||||||
|
riskId: number,
|
||||||
|
/** 0-one-way, 1-buy side, 2-sell side */
|
||||||
|
positionIdx: 0 | 1 | 2
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.postPrivate('/contract/v3/private/position/set-risk-limit', {
|
||||||
|
symbol,
|
||||||
|
riskId,
|
||||||
|
positionIdx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's trading records.
|
||||||
|
* The results are ordered in descending order (the first item is the latest). Returns records up to 2 years old.
|
||||||
|
*/
|
||||||
|
getUserExecutionHistory(
|
||||||
|
params: ContractUserExecutionHistoryRequest
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.getPrivate('/contract/v3/private/execution/list', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's closed profit and loss records.
|
||||||
|
* The results are ordered in descending order (the first item is the latest).
|
||||||
|
*/
|
||||||
|
getClosedProfitAndLoss(
|
||||||
|
params: ContractClosedPNLRequest
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.getPrivate('/contract/v3/private/position/closed-pnl', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the information of open interest limit. */
|
||||||
|
getOpenInterestLimitInfo(symbol: string): Promise<APIResponseV3<any>> {
|
||||||
|
return this.getPrivate('/contract/v3/private/position/closed-pnl', {
|
||||||
|
symbol,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** -> Account API */
|
||||||
|
|
||||||
|
/** Query wallet balance */
|
||||||
|
getBalances(coin?: string): Promise<APIResponseV3<any>> {
|
||||||
|
return this.getPrivate('/contract/v3/private/account/wallet/balance', {
|
||||||
|
coin,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get user trading fee rate */
|
||||||
|
getTradingFeeRate(symbol?: string): Promise<APIResponseV3<any>> {
|
||||||
|
return this.getPrivate('/contract/v3/private/account/fee-rate', {
|
||||||
|
symbol,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get wallet fund records.
|
||||||
|
* This endpoint also shows exchanges from the Asset Exchange, where the types for the exchange are ExchangeOrderWithdraw and ExchangeOrderDeposit.
|
||||||
|
* This endpoint returns incomplete information for transfers involving the derivatives wallet.
|
||||||
|
* Use the account asset API for creating and querying internal transfers.
|
||||||
|
*/
|
||||||
|
getWalletFundRecords(
|
||||||
|
params?: ContractWalletFundRecordRequest
|
||||||
|
): Promise<APIResponseV3<any>> {
|
||||||
|
return this.getPrivate(
|
||||||
|
'/contract/v3/private/account/wallet/fund-records',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* API Data Endpoints
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
getServerTime(): Promise<APIResponseWithTime> {
|
||||||
|
return this.get('/v2/public/time');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ export * from './spot-client-v3';
|
|||||||
export * from './usdc-option-client';
|
export * from './usdc-option-client';
|
||||||
export * from './usdc-perpetual-client';
|
export * from './usdc-perpetual-client';
|
||||||
export * from './unified-margin-client';
|
export * from './unified-margin-client';
|
||||||
|
export * from './contract-client';
|
||||||
export * from './websocket-client';
|
export * from './websocket-client';
|
||||||
export * from './util/logger';
|
export * from './util/logger';
|
||||||
export * from './util';
|
export * from './util';
|
||||||
|
|||||||
128
src/types/request/contract.ts
Normal file
128
src/types/request/contract.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import { OrderSide } from '../shared';
|
||||||
|
import { UMOrderType } from './unified-margin';
|
||||||
|
import { USDCOrderFilter, USDCTimeInForce } from './usdc-shared';
|
||||||
|
|
||||||
|
export interface ContractOrderRequest {
|
||||||
|
symbol: string;
|
||||||
|
side: OrderSide;
|
||||||
|
positionIdx?: '0' | '1' | '2';
|
||||||
|
orderType: UMOrderType;
|
||||||
|
qty: string;
|
||||||
|
price?: string;
|
||||||
|
triggerDirection?: '1' | '2';
|
||||||
|
triggerPrice?: string;
|
||||||
|
triggerBy?: string;
|
||||||
|
tpTriggerBy?: string;
|
||||||
|
slTriggerBy?: string;
|
||||||
|
timeInForce: USDCTimeInForce;
|
||||||
|
orderLinkId?: string;
|
||||||
|
takeProfit?: number;
|
||||||
|
stopLoss?: number;
|
||||||
|
reduceOnly?: boolean;
|
||||||
|
closeOnTrigger?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractHistoricOrdersRequest {
|
||||||
|
orderId?: string;
|
||||||
|
orderLinkId?: string;
|
||||||
|
symbol: string;
|
||||||
|
orderStatus?: string;
|
||||||
|
orderFilter?: USDCOrderFilter;
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractCancelOrderRequest {
|
||||||
|
symbol: string;
|
||||||
|
orderId?: string;
|
||||||
|
orderLinkId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractModifyOrderRequest {
|
||||||
|
orderId?: string;
|
||||||
|
orderLinkId?: string;
|
||||||
|
symbol: string;
|
||||||
|
qty?: string;
|
||||||
|
price?: string;
|
||||||
|
takeProfit?: number;
|
||||||
|
stopLoss?: number;
|
||||||
|
tpTriggerBy?: string;
|
||||||
|
slTriggerBy?: string;
|
||||||
|
triggerBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractActiveOrdersRequest {
|
||||||
|
symbol?: string;
|
||||||
|
orderId?: string;
|
||||||
|
orderLinkId?: string;
|
||||||
|
settleCoin?: string;
|
||||||
|
orderFilter?: USDCOrderFilter;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractPositionsRequest {
|
||||||
|
symbol?: string;
|
||||||
|
settleCoin?: string;
|
||||||
|
dataFilter?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractSetAutoAddMarginRequest {
|
||||||
|
symbol: string;
|
||||||
|
side: 'Buy' | 'Sell';
|
||||||
|
autoAddMargin: 1 | 0;
|
||||||
|
positionIdx?: 0 | 1 | 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractSetMarginSwitchRequest {
|
||||||
|
symbol: string;
|
||||||
|
tradeMode: 0 | 1;
|
||||||
|
buyLeverage: string;
|
||||||
|
sellLeverage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractSetPositionModeRequest {
|
||||||
|
symbol?: string;
|
||||||
|
coin?: string;
|
||||||
|
mode: 0 | 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractSetTPSLRequest {
|
||||||
|
symbol: string;
|
||||||
|
takeProfit?: string;
|
||||||
|
stopLoss?: string;
|
||||||
|
activePrice?: string;
|
||||||
|
trailingStop?: string;
|
||||||
|
tpTriggerBy?: string;
|
||||||
|
slTriggerBy?: string;
|
||||||
|
slSize?: string;
|
||||||
|
tpSize?: string;
|
||||||
|
/** 0-one-way, 1-buy side, 2-sell side */
|
||||||
|
positionIdx?: 0 | 1 | 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractUserExecutionHistoryRequest {
|
||||||
|
symbol: string;
|
||||||
|
orderId?: string;
|
||||||
|
startTime?: number;
|
||||||
|
endTime?: number;
|
||||||
|
execType?: 'Trade' | 'AdlTrade' | 'Funding' | 'BustTrade';
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractClosedPNLRequest {
|
||||||
|
symbol: string;
|
||||||
|
startTime?: number;
|
||||||
|
endTime?: number;
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContractWalletFundRecordRequest {
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
coin?: string;
|
||||||
|
walletFundType?: string;
|
||||||
|
limit?: string;
|
||||||
|
cursor?: string;
|
||||||
|
}
|
||||||
@@ -7,3 +7,4 @@ export * from './usdc-perp';
|
|||||||
export * from './usdc-options';
|
export * from './usdc-options';
|
||||||
export * from './usdc-shared';
|
export * from './usdc-shared';
|
||||||
export * from './unified-margin';
|
export * from './unified-margin';
|
||||||
|
export * from './contract';
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ContractClient } from '../contract-client';
|
||||||
import { InverseClient } from '../inverse-client';
|
import { InverseClient } from '../inverse-client';
|
||||||
import { LinearClient } from '../linear-client';
|
import { LinearClient } from '../linear-client';
|
||||||
import { SpotClient } from '../spot-client';
|
import { SpotClient } from '../spot-client';
|
||||||
@@ -13,7 +14,8 @@ export type RESTClient =
|
|||||||
| SpotClientV3
|
| SpotClientV3
|
||||||
| USDCOptionClient
|
| USDCOptionClient
|
||||||
| USDCPerpetualClient
|
| USDCPerpetualClient
|
||||||
| UnifiedMarginClient;
|
| UnifiedMarginClient
|
||||||
|
| ContractClient;
|
||||||
|
|
||||||
export type numberInString = string;
|
export type numberInString = string;
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ export type APIMarket =
|
|||||||
| 'usdcOption'
|
| 'usdcOption'
|
||||||
| 'usdcPerp'
|
| 'usdcPerp'
|
||||||
| 'unifiedPerp'
|
| 'unifiedPerp'
|
||||||
| 'unifiedOption';
|
| 'unifiedOption'
|
||||||
|
| 'contractUSDT'
|
||||||
|
| 'contractInverse';
|
||||||
|
|
||||||
// Same as inverse futures
|
// Same as inverse futures
|
||||||
export type WsPublicInverseTopic =
|
export type WsPublicInverseTopic =
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import BaseRestClient from './util/BaseRestClient';
|
|||||||
*/
|
*/
|
||||||
export class USDCPerpetualClient extends BaseRestClient {
|
export class USDCPerpetualClient extends BaseRestClient {
|
||||||
getClientType() {
|
getClientType() {
|
||||||
|
// Follows the same authentication mechanism as other v3 APIs (e.g. USDC)
|
||||||
return REST_CLIENT_TYPE_ENUM.v3;
|
return REST_CLIENT_TYPE_ENUM.v3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,26 @@ export const WS_BASE_URL_MAP: Record<
|
|||||||
testnet: 'useUnifiedEndpoint',
|
testnet: 'useUnifiedEndpoint',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
contractUSDT: {
|
||||||
|
public: {
|
||||||
|
livenet: 'wss://stream.bybit.com/contract/usdt/public/v3',
|
||||||
|
testnet: 'wss://stream-testnet.bybit.com/contract/usdt/public/v3',
|
||||||
|
},
|
||||||
|
private: {
|
||||||
|
livenet: 'wss://stream.bybit.com/contract/private/v3',
|
||||||
|
testnet: 'wss://stream-testnet.bybit.com/contract/private/v3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
contractInverse: {
|
||||||
|
public: {
|
||||||
|
livenet: 'wss://stream.bybit.com/contract/inverse/public/v3',
|
||||||
|
testnet: 'wss://stream-testnet.bybit.com/contract/inverse/public/v3',
|
||||||
|
},
|
||||||
|
private: {
|
||||||
|
livenet: 'wss://stream.bybit.com/contract/private/v3',
|
||||||
|
testnet: 'wss://stream-testnet.bybit.com/contract/private/v3',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WS_KEY_MAP = {
|
export const WS_KEY_MAP = {
|
||||||
@@ -139,6 +159,10 @@ export const WS_KEY_MAP = {
|
|||||||
unifiedOptionPublic: 'unifiedOptionPublic',
|
unifiedOptionPublic: 'unifiedOptionPublic',
|
||||||
unifiedPerpUSDTPublic: 'unifiedPerpUSDTPublic',
|
unifiedPerpUSDTPublic: 'unifiedPerpUSDTPublic',
|
||||||
unifiedPerpUSDCPublic: 'unifiedPerpUSDCPublic',
|
unifiedPerpUSDCPublic: 'unifiedPerpUSDCPublic',
|
||||||
|
contractUSDTPublic: 'contractUSDTPublic',
|
||||||
|
contractUSDTPrivate: 'contractUSDTPrivate',
|
||||||
|
contractInversePublic: 'contractInversePublic',
|
||||||
|
contractInversePrivate: 'contractInversePrivate',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [
|
export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [
|
||||||
@@ -146,6 +170,8 @@ export const WS_AUTH_ON_CONNECT_KEYS: WsKey[] = [
|
|||||||
WS_KEY_MAP.usdcOptionPrivate,
|
WS_KEY_MAP.usdcOptionPrivate,
|
||||||
WS_KEY_MAP.usdcPerpPrivate,
|
WS_KEY_MAP.usdcPerpPrivate,
|
||||||
WS_KEY_MAP.unifiedPrivate,
|
WS_KEY_MAP.unifiedPrivate,
|
||||||
|
WS_KEY_MAP.contractUSDTPrivate,
|
||||||
|
WS_KEY_MAP.contractInversePrivate,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PUBLIC_WS_KEYS = [
|
export const PUBLIC_WS_KEYS = [
|
||||||
@@ -157,6 +183,8 @@ export const PUBLIC_WS_KEYS = [
|
|||||||
WS_KEY_MAP.unifiedOptionPublic,
|
WS_KEY_MAP.unifiedOptionPublic,
|
||||||
WS_KEY_MAP.unifiedPerpUSDTPublic,
|
WS_KEY_MAP.unifiedPerpUSDTPublic,
|
||||||
WS_KEY_MAP.unifiedPerpUSDCPublic,
|
WS_KEY_MAP.unifiedPerpUSDCPublic,
|
||||||
|
WS_KEY_MAP.contractUSDTPublic,
|
||||||
|
WS_KEY_MAP.contractInversePublic,
|
||||||
] as string[];
|
] as string[];
|
||||||
|
|
||||||
/** Used to automatically determine if a sub request should be to the public or private ws (when there's two) */
|
/** Used to automatically determine if a sub request should be to the public or private ws (when there's two) */
|
||||||
@@ -193,6 +221,11 @@ const PRIVATE_TOPICS = [
|
|||||||
'user.order.unifiedAccount',
|
'user.order.unifiedAccount',
|
||||||
'user.wallet.unifiedAccount',
|
'user.wallet.unifiedAccount',
|
||||||
'user.greeks.unifiedAccount',
|
'user.greeks.unifiedAccount',
|
||||||
|
// contract v3
|
||||||
|
'user.position.contractAccount',
|
||||||
|
'user.execution.contractAccount',
|
||||||
|
'user.order.contractAccount',
|
||||||
|
'user.wallet.contractAccount',
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getWsKeyForTopic(
|
export function getWsKeyForTopic(
|
||||||
@@ -251,6 +284,16 @@ export function getWsKeyForTopic(
|
|||||||
`Failed to determine wskey for unified perps topic: "${topic}`
|
`Failed to determine wskey for unified perps topic: "${topic}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case 'contractInverse': {
|
||||||
|
return isPrivateTopic
|
||||||
|
? WS_KEY_MAP.contractInversePrivate
|
||||||
|
: WS_KEY_MAP.contractInversePublic;
|
||||||
|
}
|
||||||
|
case 'contractUSDT': {
|
||||||
|
return isPrivateTopic
|
||||||
|
? WS_KEY_MAP.contractUSDTPrivate
|
||||||
|
: WS_KEY_MAP.contractUSDTPublic;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw neverGuard(market, `getWsKeyForTopic(): Unhandled market`);
|
throw neverGuard(market, `getWsKeyForTopic(): Unhandled market`);
|
||||||
}
|
}
|
||||||
@@ -267,7 +310,9 @@ export function getMaxTopicsPerSubscribeEvent(
|
|||||||
case 'usdcPerp':
|
case 'usdcPerp':
|
||||||
case 'unifiedOption':
|
case 'unifiedOption':
|
||||||
case 'unifiedPerp':
|
case 'unifiedPerp':
|
||||||
case 'spot': {
|
case 'spot':
|
||||||
|
case 'contractInverse':
|
||||||
|
case 'contractUSDT': {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
case 'spotv3': {
|
case 'spotv3': {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import { InverseClient } from './inverse-client';
|
|||||||
import { LinearClient } from './linear-client';
|
import { LinearClient } from './linear-client';
|
||||||
import { SpotClientV3 } from './spot-client-v3';
|
import { SpotClientV3 } from './spot-client-v3';
|
||||||
import { SpotClient } from './spot-client';
|
import { SpotClient } from './spot-client';
|
||||||
|
import { USDCOptionClient } from './usdc-option-client';
|
||||||
|
import { USDCPerpetualClient } from './usdc-perpetual-client';
|
||||||
|
import { UnifiedMarginClient } from './unified-margin-client';
|
||||||
|
import { ContractClient } from './contract-client';
|
||||||
|
|
||||||
import { signMessage } from './util/node-support';
|
import { signMessage } from './util/node-support';
|
||||||
import WsStore from './util/WsStore';
|
import WsStore from './util/WsStore';
|
||||||
@@ -32,9 +36,6 @@ import {
|
|||||||
neverGuard,
|
neverGuard,
|
||||||
getMaxTopicsPerSubscribeEvent,
|
getMaxTopicsPerSubscribeEvent,
|
||||||
} from './util';
|
} from './util';
|
||||||
import { USDCOptionClient } from './usdc-option-client';
|
|
||||||
import { USDCPerpetualClient } from './usdc-perpetual-client';
|
|
||||||
import { UnifiedMarginClient } from './unified-margin-client';
|
|
||||||
|
|
||||||
const loggerCategory = { category: 'bybit-ws' };
|
const loggerCategory = { category: 'bybit-ws' };
|
||||||
|
|
||||||
@@ -106,6 +107,9 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.prepareRESTClient();
|
this.prepareRESTClient();
|
||||||
|
|
||||||
|
// add default error handling so this doesn't crash node (if the user didn't set a handler)
|
||||||
|
this.on('error', () => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,6 +236,14 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'contractInverse':
|
||||||
|
case 'contractUSDT': {
|
||||||
|
this.restClient = new ContractClient(
|
||||||
|
this.options.restOptions,
|
||||||
|
this.options.requestOptions
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw neverGuard(
|
throw neverGuard(
|
||||||
this.options.market,
|
this.options.market,
|
||||||
@@ -264,6 +276,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
|
|
||||||
public closeAll(force?: boolean) {
|
public closeAll(force?: boolean) {
|
||||||
const keys = this.wsStore.getKeys();
|
const keys = this.wsStore.getKeys();
|
||||||
|
this.logger.info(`Closing all ws connections: ${keys}`);
|
||||||
keys.forEach((key) => {
|
keys.forEach((key) => {
|
||||||
this.close(key, force);
|
this.close(key, force);
|
||||||
});
|
});
|
||||||
@@ -285,7 +298,9 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
case 'usdcOption':
|
case 'usdcOption':
|
||||||
case 'usdcPerp':
|
case 'usdcPerp':
|
||||||
case 'unifiedPerp':
|
case 'unifiedPerp':
|
||||||
case 'unifiedOption': {
|
case 'unifiedOption':
|
||||||
|
case 'contractUSDT':
|
||||||
|
case 'contractInverse': {
|
||||||
return [...this.connectPublic(), this.connectPrivate()];
|
return [...this.connectPublic(), this.connectPrivate()];
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -323,6 +338,10 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
this.connect(WS_KEY_MAP.unifiedPerpUSDCPublic),
|
this.connect(WS_KEY_MAP.unifiedPerpUSDCPublic),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
case 'contractUSDT':
|
||||||
|
return [this.connect(WS_KEY_MAP.contractUSDTPublic)];
|
||||||
|
case 'contractInverse':
|
||||||
|
return [this.connect(WS_KEY_MAP.contractInversePublic)];
|
||||||
default: {
|
default: {
|
||||||
throw neverGuard(
|
throw neverGuard(
|
||||||
this.options.market,
|
this.options.market,
|
||||||
@@ -356,6 +375,10 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
case 'unifiedOption': {
|
case 'unifiedOption': {
|
||||||
return this.connect(WS_KEY_MAP.unifiedPrivate);
|
return this.connect(WS_KEY_MAP.unifiedPrivate);
|
||||||
}
|
}
|
||||||
|
case 'contractUSDT':
|
||||||
|
return this.connect(WS_KEY_MAP.contractUSDTPrivate);
|
||||||
|
case 'contractInverse':
|
||||||
|
return this.connect(WS_KEY_MAP.contractInversePrivate);
|
||||||
default: {
|
default: {
|
||||||
throw neverGuard(
|
throw neverGuard(
|
||||||
this.options.market,
|
this.options.market,
|
||||||
@@ -399,7 +422,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
return this.wsStore.setWs(wsKey, ws);
|
return this.wsStore.setWs(wsKey, ws);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.parseWsError('Connection failed', err, wsKey);
|
this.parseWsError('Connection failed', err, wsKey);
|
||||||
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout!);
|
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,12 +442,22 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
this.logger.error(
|
if (
|
||||||
`${context} due to unexpected response error: "${
|
this.wsStore.getConnectionState(wsKey) !==
|
||||||
error?.msg || error?.message || error
|
WsConnectionStateEnum.CLOSING
|
||||||
}"`,
|
) {
|
||||||
{ ...loggerCategory, wsKey, error }
|
this.logger.error(
|
||||||
);
|
`${context} due to unexpected response error: "${
|
||||||
|
error?.msg || error?.message || error
|
||||||
|
}"`,
|
||||||
|
{ ...loggerCategory, wsKey, error }
|
||||||
|
);
|
||||||
|
this.executeReconnectableClose(wsKey, 'unhandled onWsError');
|
||||||
|
} else {
|
||||||
|
this.logger.info(
|
||||||
|
`${wsKey} socket forcefully closed. Will not reconnect.`
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.emit('error', error);
|
this.emit('error', error);
|
||||||
@@ -518,11 +551,16 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
this.setWsState(wsKey, WsConnectionStateEnum.RECONNECTING);
|
this.setWsState(wsKey, WsConnectionStateEnum.RECONNECTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.wsStore.get(wsKey)?.activeReconnectTimer) {
|
||||||
|
this.clearReconnectTimer(wsKey);
|
||||||
|
}
|
||||||
|
|
||||||
this.wsStore.get(wsKey, true).activeReconnectTimer = setTimeout(() => {
|
this.wsStore.get(wsKey, true).activeReconnectTimer = setTimeout(() => {
|
||||||
this.logger.info('Reconnecting to websocket', {
|
this.logger.info('Reconnecting to websocket', {
|
||||||
...loggerCategory,
|
...loggerCategory,
|
||||||
wsKey,
|
wsKey,
|
||||||
});
|
});
|
||||||
|
this.clearReconnectTimer(wsKey);
|
||||||
this.connect(wsKey);
|
this.connect(wsKey);
|
||||||
}, connectionDelayMs);
|
}, connectionDelayMs);
|
||||||
}
|
}
|
||||||
@@ -537,23 +575,47 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
this.logger.silly('Sending ping', { ...loggerCategory, wsKey });
|
this.logger.silly('Sending ping', { ...loggerCategory, wsKey });
|
||||||
this.tryWsSend(wsKey, JSON.stringify({ op: 'ping' }));
|
this.tryWsSend(wsKey, JSON.stringify({ op: 'ping' }));
|
||||||
|
|
||||||
this.wsStore.get(wsKey, true).activePongTimer = setTimeout(() => {
|
this.wsStore.get(wsKey, true).activePongTimer = setTimeout(
|
||||||
this.logger.info('Pong timeout - closing socket to reconnect', {
|
() => this.executeReconnectableClose(wsKey, 'Pong timeout'),
|
||||||
...loggerCategory,
|
this.options.pongTimeout
|
||||||
wsKey,
|
);
|
||||||
});
|
}
|
||||||
this.getWs(wsKey)?.terminate();
|
|
||||||
delete this.wsStore.get(wsKey, true).activePongTimer;
|
/**
|
||||||
}, this.options.pongTimeout);
|
* Closes a connection, if it's even open. If open, this will trigger a reconnect asynchronously.
|
||||||
|
* If closed, trigger a reconnect immediately
|
||||||
|
*/
|
||||||
|
private executeReconnectableClose(wsKey: WsKey, reason: string) {
|
||||||
|
this.logger.info(`${reason} - closing socket to reconnect`, {
|
||||||
|
...loggerCategory,
|
||||||
|
wsKey,
|
||||||
|
reason,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wasOpen = this.wsStore.isWsOpen(wsKey);
|
||||||
|
|
||||||
|
this.getWs(wsKey)?.terminate();
|
||||||
|
delete this.wsStore.get(wsKey, true).activePongTimer;
|
||||||
|
this.clearPingTimer(wsKey);
|
||||||
|
this.clearPongTimer(wsKey);
|
||||||
|
|
||||||
|
if (!wasOpen) {
|
||||||
|
this.logger.info(
|
||||||
|
`${reason} - socket already closed - trigger immediate reconnect`,
|
||||||
|
{
|
||||||
|
...loggerCategory,
|
||||||
|
wsKey,
|
||||||
|
reason,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearTimers(wsKey: WsKey) {
|
private clearTimers(wsKey: WsKey) {
|
||||||
this.clearPingTimer(wsKey);
|
this.clearPingTimer(wsKey);
|
||||||
this.clearPongTimer(wsKey);
|
this.clearPongTimer(wsKey);
|
||||||
const wsState = this.wsStore.get(wsKey);
|
this.clearReconnectTimer(wsKey);
|
||||||
if (wsState?.activeReconnectTimer) {
|
|
||||||
clearTimeout(wsState.activeReconnectTimer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a ping at intervals
|
// Send a ping at intervals
|
||||||
@@ -574,6 +636,14 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private clearReconnectTimer(wsKey: WsKey) {
|
||||||
|
const wsState = this.wsStore.get(wsKey);
|
||||||
|
if (wsState?.activeReconnectTimer) {
|
||||||
|
clearTimeout(wsState.activeReconnectTimer);
|
||||||
|
wsState.activeReconnectTimer = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private Use the `subscribe(topics)` method to subscribe to topics. Send WS message to subscribe to topics.
|
* @private Use the `subscribe(topics)` method to subscribe to topics. Send WS message to subscribe to topics.
|
||||||
*/
|
*/
|
||||||
@@ -682,7 +752,8 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
const ws = new WebSocket(url, undefined, agent ? { agent } : undefined);
|
const ws = new WebSocket(url, undefined, agent ? { agent } : undefined);
|
||||||
ws.onopen = (event) => this.onWsOpen(event, wsKey);
|
ws.onopen = (event) => this.onWsOpen(event, wsKey);
|
||||||
ws.onmessage = (event) => this.onWsMessage(event, wsKey);
|
ws.onmessage = (event) => this.onWsMessage(event, wsKey);
|
||||||
ws.onerror = (event) => this.onWsError(event, wsKey);
|
ws.onerror = (event) =>
|
||||||
|
this.parseWsError('Websocket onWsError', event, wsKey);
|
||||||
ws.onclose = (event) => this.onWsClose(event, wsKey);
|
ws.onclose = (event) => this.onWsClose(event, wsKey);
|
||||||
|
|
||||||
return ws;
|
return ws;
|
||||||
@@ -781,10 +852,6 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onWsError(error: any, wsKey: WsKey) {
|
|
||||||
this.parseWsError('Websocket error', error, wsKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onWsClose(event, wsKey: WsKey) {
|
private onWsClose(event, wsKey: WsKey) {
|
||||||
this.logger.info('Websocket connection closed', {
|
this.logger.info('Websocket connection closed', {
|
||||||
...loggerCategory,
|
...loggerCategory,
|
||||||
@@ -794,7 +861,7 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
if (
|
if (
|
||||||
this.wsStore.getConnectionState(wsKey) !== WsConnectionStateEnum.CLOSING
|
this.wsStore.getConnectionState(wsKey) !== WsConnectionStateEnum.CLOSING
|
||||||
) {
|
) {
|
||||||
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout!);
|
this.reconnectWithDelay(wsKey, this.options.reconnectTimeout);
|
||||||
this.emit('reconnect', { wsKey, event });
|
this.emit('reconnect', { wsKey, event });
|
||||||
} else {
|
} else {
|
||||||
this.setWsState(wsKey, WsConnectionStateEnum.INITIAL);
|
this.setWsState(wsKey, WsConnectionStateEnum.INITIAL);
|
||||||
@@ -864,6 +931,18 @@ export class WebsocketClient extends EventEmitter {
|
|||||||
case WS_KEY_MAP.unifiedPrivate: {
|
case WS_KEY_MAP.unifiedPrivate: {
|
||||||
return WS_BASE_URL_MAP.unifiedPerp.private[networkKey];
|
return WS_BASE_URL_MAP.unifiedPerp.private[networkKey];
|
||||||
}
|
}
|
||||||
|
case WS_KEY_MAP.contractInversePrivate: {
|
||||||
|
return WS_BASE_URL_MAP.contractInverse.private[networkKey];
|
||||||
|
}
|
||||||
|
case WS_KEY_MAP.contractInversePublic: {
|
||||||
|
return WS_BASE_URL_MAP.contractInverse.public[networkKey];
|
||||||
|
}
|
||||||
|
case WS_KEY_MAP.contractUSDTPrivate: {
|
||||||
|
return WS_BASE_URL_MAP.contractUSDT.private[networkKey];
|
||||||
|
}
|
||||||
|
case WS_KEY_MAP.contractUSDTPublic: {
|
||||||
|
return WS_BASE_URL_MAP.contractUSDT.public[networkKey];
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
this.logger.error('getWsUrl(): Unhandled wsKey: ', {
|
this.logger.error('getWsUrl(): Unhandled wsKey: ', {
|
||||||
...loggerCategory,
|
...loggerCategory,
|
||||||
|
|||||||
71
test/contract/private.read.test.ts
Normal file
71
test/contract/private.read.test.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { API_ERROR_CODE, ContractClient } from '../../src';
|
||||||
|
import { successResponseObjectV3 } from '../response.util';
|
||||||
|
|
||||||
|
describe('Private Contract REST API GET Endpoints', () => {
|
||||||
|
const API_KEY = process.env.API_KEY_COM;
|
||||||
|
const API_SECRET = process.env.API_SECRET_COM;
|
||||||
|
|
||||||
|
it('should have api credentials to test with', () => {
|
||||||
|
expect(API_KEY).toStrictEqual(expect.any(String));
|
||||||
|
expect(API_SECRET).toStrictEqual(expect.any(String));
|
||||||
|
});
|
||||||
|
|
||||||
|
const api = new ContractClient({
|
||||||
|
key: API_KEY,
|
||||||
|
secret: API_SECRET,
|
||||||
|
testnet: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const symbol = 'BTCUSDT';
|
||||||
|
it('getHistoricOrders()', async () => {
|
||||||
|
expect(await api.getHistoricOrders({ symbol })).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getActiveOrders()', async () => {
|
||||||
|
expect(await api.getActiveOrders({ symbol })).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getPositions()', async () => {
|
||||||
|
expect(await api.getPositions({ symbol })).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getUserExecutionHistory()', async () => {
|
||||||
|
expect(await api.getUserExecutionHistory({ symbol })).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getClosedProfitAndLoss()', async () => {
|
||||||
|
expect(await api.getClosedProfitAndLoss({ symbol })).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getOpenInterestLimitInfo()', async () => {
|
||||||
|
expect(await api.getOpenInterestLimitInfo(symbol)).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getBalances()', async () => {
|
||||||
|
expect(await api.getBalances()).toMatchObject(successResponseObjectV3());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getTradingFeeRate()', async () => {
|
||||||
|
expect(await api.getTradingFeeRate()).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getWalletFundRecords()', async () => {
|
||||||
|
expect(await api.getWalletFundRecords()).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
139
test/contract/private.write.test.ts
Normal file
139
test/contract/private.write.test.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { API_ERROR_CODE, ContractClient } from '../../src';
|
||||||
|
import { successResponseObjectV3 } from '../response.util';
|
||||||
|
|
||||||
|
describe('Private Contract REST API POST Endpoints', () => {
|
||||||
|
const API_KEY = process.env.API_KEY_COM;
|
||||||
|
const API_SECRET = process.env.API_SECRET_COM;
|
||||||
|
|
||||||
|
it('should have api credentials to test with', () => {
|
||||||
|
expect(API_KEY).toStrictEqual(expect.any(String));
|
||||||
|
expect(API_SECRET).toStrictEqual(expect.any(String));
|
||||||
|
});
|
||||||
|
|
||||||
|
const api = new ContractClient({
|
||||||
|
key: API_KEY,
|
||||||
|
secret: API_SECRET,
|
||||||
|
testnet: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const symbol = 'BTCUSDT';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* While it may seem silly, these tests simply validate the request is processed at all.
|
||||||
|
* Something very wrong would be a sign error or complaints about the endpoint/request method/server error.
|
||||||
|
*/
|
||||||
|
|
||||||
|
it('submitOrder()', async () => {
|
||||||
|
expect(
|
||||||
|
await api.submitOrder({
|
||||||
|
symbol,
|
||||||
|
side: 'Sell',
|
||||||
|
orderType: 'Limit',
|
||||||
|
qty: '1',
|
||||||
|
price: '20000',
|
||||||
|
orderLinkId: Date.now().toString(),
|
||||||
|
timeInForce: 'GoodTillCancel',
|
||||||
|
positionIdx: '2',
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
// retMsg: '',
|
||||||
|
retCode: API_ERROR_CODE.CONTRACT_INSUFFICIENT_BALANCE,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cancelOrder()', async () => {
|
||||||
|
expect(
|
||||||
|
await api.cancelOrder({
|
||||||
|
symbol,
|
||||||
|
orderId: 'somethingFake1',
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
retCode: API_ERROR_CODE.CONTRACT_ORDER_NOT_EXISTS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cancelAllOrders()', async () => {
|
||||||
|
expect(await api.cancelAllOrders(symbol)).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('modifyOrder()', async () => {
|
||||||
|
expect(
|
||||||
|
await api.modifyOrder({
|
||||||
|
symbol,
|
||||||
|
orderId: 'somethingFake',
|
||||||
|
price: '20000',
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
retCode: API_ERROR_CODE.CONTRACT_ORDER_NOT_EXISTS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setAutoAddMargin()', async () => {
|
||||||
|
expect(
|
||||||
|
await api.setAutoAddMargin({
|
||||||
|
autoAddMargin: 1,
|
||||||
|
side: 'Buy',
|
||||||
|
symbol,
|
||||||
|
positionIdx: 1,
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
retMsg: expect.stringMatching(/not modified/gim),
|
||||||
|
retCode: API_ERROR_CODE.PARAMS_MISSING_OR_WRONG,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setMarginSwitch()', async () => {
|
||||||
|
expect(
|
||||||
|
await api.setMarginSwitch({
|
||||||
|
symbol,
|
||||||
|
tradeMode: 1,
|
||||||
|
buyLeverage: '5',
|
||||||
|
sellLeverage: '5',
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
retCode: API_ERROR_CODE.CONTRACT_MARGIN_MODE_NOT_MODIFIED,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setPositionMode()', async () => {
|
||||||
|
expect(
|
||||||
|
await api.setPositionMode({
|
||||||
|
symbol,
|
||||||
|
mode: 3,
|
||||||
|
})
|
||||||
|
).toMatchObject({
|
||||||
|
retCode: API_ERROR_CODE.CONTRACT_POSITION_MODE_NOT_MODIFIED,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setTPSLMode()', async () => {
|
||||||
|
expect(await api.setTPSLMode(symbol, 'Full')).toMatchObject({
|
||||||
|
retCode: API_ERROR_CODE.PARAMS_MISSING_OR_WRONG,
|
||||||
|
retMsg: expect.stringMatching(/same/gim),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setLeverage()', async () => {
|
||||||
|
expect(await api.setLeverage(symbol, '5', '5')).toMatchObject({
|
||||||
|
retCode: API_ERROR_CODE.CONTRACT_SET_LEVERAGE_NOT_MODIFIED,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setTPSL()', async () => {
|
||||||
|
expect(
|
||||||
|
await api.setTPSL({ symbol, positionIdx: 1, stopLoss: '100' })
|
||||||
|
).toMatchObject({
|
||||||
|
retMsg: expect.stringMatching(/zero position/gim),
|
||||||
|
retCode: API_ERROR_CODE.PARAMS_MISSING_OR_WRONG,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setRiskLimit()', async () => {
|
||||||
|
expect(await api.setRiskLimit(symbol, 43, 2)).toMatchObject({
|
||||||
|
// retMsg: '',
|
||||||
|
retCode: API_ERROR_CODE.CONTRACT_RISK_LIMIT_INFO_NOT_EXISTS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
103
test/contract/public.read.test.ts
Normal file
103
test/contract/public.read.test.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { UMCandlesRequest, ContractClient } from '../../src';
|
||||||
|
import {
|
||||||
|
successResponseObject,
|
||||||
|
successResponseObjectV3,
|
||||||
|
} from '../response.util';
|
||||||
|
|
||||||
|
describe('Public Contract REST API Endpoints', () => {
|
||||||
|
const API_KEY = undefined;
|
||||||
|
const API_SECRET = undefined;
|
||||||
|
|
||||||
|
const api = new ContractClient({
|
||||||
|
key: API_KEY,
|
||||||
|
secret: API_SECRET,
|
||||||
|
testnet: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const symbol = 'BTCUSDT';
|
||||||
|
const category = 'linear';
|
||||||
|
const start = Number((Date.now() / 1000).toFixed(0));
|
||||||
|
const end = start + 1000 * 60 * 60 * 24;
|
||||||
|
const interval = '1';
|
||||||
|
|
||||||
|
const candleRequest: UMCandlesRequest = {
|
||||||
|
category,
|
||||||
|
symbol,
|
||||||
|
interval,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('getOrderBook()', async () => {
|
||||||
|
expect(await api.getOrderBook(symbol, category)).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getCandles()', async () => {
|
||||||
|
expect(await api.getCandles(candleRequest)).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getSymbolTicker()', async () => {
|
||||||
|
expect(await api.getSymbolTicker(category)).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getInstrumentInfo()', async () => {
|
||||||
|
expect(await api.getInstrumentInfo({ category })).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getMarkPriceCandles()', async () => {
|
||||||
|
expect(await api.getMarkPriceCandles(candleRequest)).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getIndexPriceCandles()', async () => {
|
||||||
|
expect(await api.getIndexPriceCandles(candleRequest)).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getFundingRateHistory()', async () => {
|
||||||
|
expect(
|
||||||
|
await api.getFundingRateHistory({
|
||||||
|
category,
|
||||||
|
symbol,
|
||||||
|
})
|
||||||
|
).toMatchObject(successResponseObjectV3());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getRiskLimit()', async () => {
|
||||||
|
expect(await api.getRiskLimit(category, symbol)).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getOptionDeliveryPrice()', async () => {
|
||||||
|
expect(await api.getOptionDeliveryPrice({ category })).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getTrades()', async () => {
|
||||||
|
expect(await api.getTrades({ category, symbol })).toMatchObject(
|
||||||
|
successResponseObjectV3()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getOpenInterest()', async () => {
|
||||||
|
expect(
|
||||||
|
await api.getOpenInterest({ symbol, category, interval: '5min' })
|
||||||
|
).toMatchObject(successResponseObjectV3());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getServerTime()', async () => {
|
||||||
|
expect(await api.getServerTime()).toMatchObject(successResponseObject());
|
||||||
|
});
|
||||||
|
});
|
||||||
74
test/contract/ws.private.test.ts
Normal file
74
test/contract/ws.private.test.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import {
|
||||||
|
WebsocketClient,
|
||||||
|
WSClientConfigurableOptions,
|
||||||
|
WS_KEY_MAP,
|
||||||
|
} from '../../src';
|
||||||
|
import {
|
||||||
|
logAllEvents,
|
||||||
|
getSilentLogger,
|
||||||
|
waitForSocketEvent,
|
||||||
|
WS_OPEN_EVENT_PARTIAL,
|
||||||
|
fullLogger,
|
||||||
|
} from '../ws.util';
|
||||||
|
|
||||||
|
describe('Private Contract Websocket Client', () => {
|
||||||
|
const API_KEY = process.env.API_KEY_COM;
|
||||||
|
const API_SECRET = process.env.API_SECRET_COM;
|
||||||
|
|
||||||
|
let wsClient: WebsocketClient;
|
||||||
|
|
||||||
|
const wsClientOptions: WSClientConfigurableOptions = {
|
||||||
|
market: 'contractUSDT',
|
||||||
|
key: API_KEY,
|
||||||
|
secret: API_SECRET,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
wsClient = new WebsocketClient(
|
||||||
|
wsClientOptions,
|
||||||
|
getSilentLogger('expectSuccessNoAuth')
|
||||||
|
// fullLogger
|
||||||
|
);
|
||||||
|
// logAllEvents(wsClient);
|
||||||
|
wsClient.connectPrivate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
wsClient.closeAll(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open a private ws connection', async () => {
|
||||||
|
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
|
||||||
|
try {
|
||||||
|
expect(await wsOpenPromise).toMatchObject({
|
||||||
|
event: WS_OPEN_EVENT_PARTIAL,
|
||||||
|
wsKey: WS_KEY_MAP.contractUSDTPrivate,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).toBeFalsy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should authenticate successfully', async () => {
|
||||||
|
const wsResponsePromise = waitForSocketEvent(wsClient, 'response');
|
||||||
|
// const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
|
||||||
|
|
||||||
|
try {
|
||||||
|
expect(await wsResponsePromise).toMatchObject({
|
||||||
|
op: 'auth',
|
||||||
|
req_id: 'contractUSDTPrivate-auth',
|
||||||
|
success: true,
|
||||||
|
wsKey: WS_KEY_MAP.contractUSDTPrivate,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// sub failed
|
||||||
|
expect(e).toBeFalsy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// expect(await wsUpdatePromise).toStrictEqual('');
|
||||||
|
// } catch (e) {
|
||||||
|
// // no data
|
||||||
|
// expect(e).toBeFalsy();
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
});
|
||||||
74
test/contract/ws.public.inverse.test.ts
Normal file
74
test/contract/ws.public.inverse.test.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import {
|
||||||
|
WebsocketClient,
|
||||||
|
WSClientConfigurableOptions,
|
||||||
|
WS_KEY_MAP,
|
||||||
|
} from '../../src';
|
||||||
|
import {
|
||||||
|
logAllEvents,
|
||||||
|
getSilentLogger,
|
||||||
|
waitForSocketEvent,
|
||||||
|
WS_OPEN_EVENT_PARTIAL,
|
||||||
|
} from '../ws.util';
|
||||||
|
|
||||||
|
describe('Public Contract Inverse Websocket Client', () => {
|
||||||
|
let wsClient: WebsocketClient;
|
||||||
|
|
||||||
|
const wsClientOptions: WSClientConfigurableOptions = {
|
||||||
|
market: 'contractInverse',
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
wsClient = new WebsocketClient(
|
||||||
|
wsClientOptions,
|
||||||
|
getSilentLogger('expectSuccessNoAuth')
|
||||||
|
);
|
||||||
|
wsClient.connectPublic();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
wsClient.closeAll(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open a public ws connection', async () => {
|
||||||
|
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
|
||||||
|
try {
|
||||||
|
expect(await wsOpenPromise).toMatchObject({
|
||||||
|
event: WS_OPEN_EVENT_PARTIAL,
|
||||||
|
wsKey: WS_KEY_MAP.contractInversePublic,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('open: ', e);
|
||||||
|
expect(e).toBeFalsy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should subscribe to public orderbook events', async () => {
|
||||||
|
const wsResponsePromise = waitForSocketEvent(wsClient, 'response');
|
||||||
|
const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
|
||||||
|
|
||||||
|
const wsTopic = 'orderbook.25.BTCUSD';
|
||||||
|
wsClient.subscribe(wsTopic);
|
||||||
|
|
||||||
|
try {
|
||||||
|
expect(await wsResponsePromise).toMatchObject({
|
||||||
|
success: true,
|
||||||
|
op: 'subscribe',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('response: ', e);
|
||||||
|
// sub failed (or jest expect threw)
|
||||||
|
expect(e).toBeFalsy();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
expect(await wsUpdatePromise).toMatchObject({
|
||||||
|
topic: wsTopic,
|
||||||
|
data: {
|
||||||
|
a: expect.any(Array),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Wait for "${wsTopic}" event exception: `, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
74
test/contract/ws.public.usdt.test.ts
Normal file
74
test/contract/ws.public.usdt.test.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import {
|
||||||
|
WebsocketClient,
|
||||||
|
WSClientConfigurableOptions,
|
||||||
|
WS_KEY_MAP,
|
||||||
|
} from '../../src';
|
||||||
|
import {
|
||||||
|
logAllEvents,
|
||||||
|
getSilentLogger,
|
||||||
|
waitForSocketEvent,
|
||||||
|
WS_OPEN_EVENT_PARTIAL,
|
||||||
|
} from '../ws.util';
|
||||||
|
|
||||||
|
describe('Public Contract USDT Websocket Client', () => {
|
||||||
|
let wsClient: WebsocketClient;
|
||||||
|
|
||||||
|
const wsClientOptions: WSClientConfigurableOptions = {
|
||||||
|
market: 'contractUSDT',
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
wsClient = new WebsocketClient(
|
||||||
|
wsClientOptions,
|
||||||
|
getSilentLogger('expectSuccessNoAuth')
|
||||||
|
);
|
||||||
|
wsClient.connectPublic();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
wsClient.closeAll(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open a public ws connection', async () => {
|
||||||
|
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
|
||||||
|
try {
|
||||||
|
expect(await wsOpenPromise).toMatchObject({
|
||||||
|
event: WS_OPEN_EVENT_PARTIAL,
|
||||||
|
wsKey: WS_KEY_MAP.contractUSDTPublic,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('open: ', e);
|
||||||
|
expect(e).toBeFalsy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should subscribe to public orderbook events', async () => {
|
||||||
|
const wsResponsePromise = waitForSocketEvent(wsClient, 'response');
|
||||||
|
const wsUpdatePromise = waitForSocketEvent(wsClient, 'update');
|
||||||
|
|
||||||
|
const wsTopic = 'orderbook.25.BTCUSDT';
|
||||||
|
wsClient.subscribe(wsTopic);
|
||||||
|
|
||||||
|
try {
|
||||||
|
expect(await wsResponsePromise).toMatchObject({
|
||||||
|
success: true,
|
||||||
|
op: 'subscribe',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('response: ', e);
|
||||||
|
// sub failed (or jest expect threw)
|
||||||
|
expect(e).toBeFalsy();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
expect(await wsUpdatePromise).toMatchObject({
|
||||||
|
topic: wsTopic,
|
||||||
|
data: {
|
||||||
|
a: expect.any(Array),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Wait for "${wsTopic}" event exception: `, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -110,6 +110,7 @@ describe('Private Inverse REST API POST Endpoints', () => {
|
|||||||
symbol,
|
symbol,
|
||||||
p_r_price: '50000',
|
p_r_price: '50000',
|
||||||
p_r_qty: 1,
|
p_r_qty: 1,
|
||||||
|
order_link_id: 'fakeorderid',
|
||||||
})
|
})
|
||||||
).toMatchObject({
|
).toMatchObject({
|
||||||
ret_code: API_ERROR_CODE.ORDER_NOT_FOUND_OR_TOO_LATE,
|
ret_code: API_ERROR_CODE.ORDER_NOT_FOUND_OR_TOO_LATE,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ describe('Private Inverse Perps Websocket Client', () => {
|
|||||||
// console.error()
|
// console.error()
|
||||||
expect(e?.message).toStrictEqual('Unexpected server response: 401');
|
expect(e?.message).toStrictEqual('Unexpected server response: 401');
|
||||||
}
|
}
|
||||||
badClient.closeAll();
|
badClient.closeAll(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ describe('Private Inverse Perps Websocket Client', () => {
|
|||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
// await promiseSleep(2000);
|
// await promiseSleep(2000);
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a ws connection', async () => {
|
it('should open a ws connection', async () => {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ describe('Public Inverse Perps Websocket Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a public ws connection', async () => {
|
it('should open a public ws connection', async () => {
|
||||||
|
|||||||
@@ -176,7 +176,9 @@ describe('Private Linear REST API POST Endpoints', () => {
|
|||||||
margin: 5,
|
margin: 5,
|
||||||
})
|
})
|
||||||
).toMatchObject({
|
).toMatchObject({
|
||||||
|
// ret_msg: '',
|
||||||
ret_code: API_ERROR_CODE.POSITION_SIZE_IS_ZERO,
|
ret_code: API_ERROR_CODE.POSITION_SIZE_IS_ZERO,
|
||||||
|
// ret_code: API_ERROR_CODE.ISOLATED_NOT_MODIFIED_LINEAR,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ describe('Private Linear Perps Websocket Client', () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
expect(e?.message).toStrictEqual('Unexpected server response: 401');
|
expect(e?.message).toStrictEqual('Unexpected server response: 401');
|
||||||
}
|
}
|
||||||
badClient.closeAll();
|
badClient.closeAll(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ describe('Private Linear Perps Websocket Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a ws connection', async () => {
|
it('should open a ws connection', async () => {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('Public Linear Perps Websocket Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a public ws connection', async () => {
|
it('should open a public ws connection', async () => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
getSilentLogger,
|
getSilentLogger,
|
||||||
waitForSocketEvent,
|
waitForSocketEvent,
|
||||||
WS_OPEN_EVENT_PARTIAL,
|
WS_OPEN_EVENT_PARTIAL,
|
||||||
|
fullLogger,
|
||||||
} from '../ws.util';
|
} from '../ws.util';
|
||||||
|
|
||||||
describe('Private Spot V1 Websocket Client', () => {
|
describe('Private Spot V1 Websocket Client', () => {
|
||||||
@@ -30,13 +31,14 @@ describe('Private Spot V1 Websocket Client', () => {
|
|||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
wsClient = new WebsocketClient(
|
wsClient = new WebsocketClient(
|
||||||
wsClientOptions,
|
wsClientOptions,
|
||||||
|
// fullLogger
|
||||||
getSilentLogger('expectSuccess')
|
getSilentLogger('expectSuccess')
|
||||||
);
|
);
|
||||||
logAllEvents(wsClient);
|
logAllEvents(wsClient);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: how to detect if auth failed for the v1 spot ws
|
// TODO: how to detect if auth failed for the v1 spot ws
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ describe('Private Spot V3 Websocket Client', () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.error()
|
// console.error()
|
||||||
}
|
}
|
||||||
badClient.closeAll();
|
badClient.closeAll(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ describe('Private Spot V3 Websocket Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a private ws connection', async () => {
|
it('should open a private ws connection', async () => {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe('Public Spot V1 Websocket Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a public ws connection', async () => {
|
it('should open a public ws connection', async () => {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe('Public Spot V3 Websocket Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a public ws connection', async () => {
|
it('should open a public ws connection', async () => {
|
||||||
|
|||||||
@@ -12,10 +12,15 @@ import {
|
|||||||
} from '../ws.util';
|
} from '../ws.util';
|
||||||
|
|
||||||
describe('Private Unified Margin Websocket Client', () => {
|
describe('Private Unified Margin Websocket Client', () => {
|
||||||
|
const API_KEY = process.env.API_KEY_COM;
|
||||||
|
const API_SECRET = process.env.API_SECRET_COM;
|
||||||
|
|
||||||
let wsClient: WebsocketClient;
|
let wsClient: WebsocketClient;
|
||||||
|
|
||||||
const wsClientOptions: WSClientConfigurableOptions = {
|
const wsClientOptions: WSClientConfigurableOptions = {
|
||||||
market: 'unifiedPerp',
|
market: 'unifiedPerp',
|
||||||
|
key: API_KEY,
|
||||||
|
secret: API_SECRET,
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@@ -29,7 +34,7 @@ describe('Private Unified Margin Websocket Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a public ws connection', async () => {
|
it('should open a public ws connection', async () => {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ describe('Public Unified Margin Websocket Client (Options)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a public ws connection', async () => {
|
it('should open a public ws connection', async () => {
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ describe('Public Unified Margin Websocket Client (Perps - USDC)', () => {
|
|||||||
wsClient.connectPublic();
|
wsClient.connectPublic();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
wsClient.closeAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should open a public ws connection', async () => {
|
it('should open a public ws connection', async () => {
|
||||||
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
|
const wsOpenPromise = waitForSocketEvent(wsClient, 'open');
|
||||||
try {
|
try {
|
||||||
@@ -42,6 +38,8 @@ describe('Public Unified Margin Websocket Client (Perps - USDC)', () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
expect(e).toBeFalsy();
|
expect(e).toBeFalsy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: are there USDC topics? This doesn't seem to work
|
// TODO: are there USDC topics? This doesn't seem to work
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ describe('Public Unified Margin Websocket Client (Perps - USDT)', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a public ws connection', async () => {
|
it('should open a public ws connection', async () => {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ describe('Private USDC Option Websocket Client', () => {
|
|||||||
|
|
||||||
// badClient.subscribe(wsTopic);
|
// badClient.subscribe(wsTopic);
|
||||||
badClient.removeAllListeners();
|
badClient.removeAllListeners();
|
||||||
badClient.closeAll();
|
badClient.closeAll(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ describe('Private USDC Option Websocket Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a private ws connection', async () => {
|
it('should open a private ws connection', async () => {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ describe('Public USDC Option Websocket Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a public ws connection', async () => {
|
it('should open a public ws connection', async () => {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ describe('Private USDC Perp Websocket Client', () => {
|
|||||||
|
|
||||||
// badClient.subscribe(wsTopic);
|
// badClient.subscribe(wsTopic);
|
||||||
badClient.removeAllListeners();
|
badClient.removeAllListeners();
|
||||||
badClient.closeAll();
|
badClient.closeAll(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ describe('Private USDC Perp Websocket Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a private ws connection', async () => {
|
it('should open a private ws connection', async () => {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ describe('Public USDC Perp Websocket Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
wsClient.closeAll();
|
wsClient.closeAll(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open a public ws connection', async () => {
|
it('should open a public ws connection', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user