Merge pull request #83 from tiagosiebler/cleaning

Add support for inverse futures endpoints
This commit is contained in:
Tiago
2021-03-06 19:02:56 +00:00
committed by GitHub
9 changed files with 457 additions and 50 deletions

134
README.md
View File

@@ -5,7 +5,7 @@
[1]: https://www.npmjs.com/package/bybit-api [1]: https://www.npmjs.com/package/bybit-api
A production-ready Node.js connector for the Bybit APIs and WebSockets, with TypeScript & browser support. Node.js connector for the Bybit APIs and WebSockets, with TypeScript & browser support.
## Installation ## Installation
`npm install --save bybit-api` `npm install --save bybit-api`
@@ -13,11 +13,11 @@ A production-ready Node.js connector for the Bybit APIs and WebSockets, with Typ
## Issues & Discussion ## Issues & Discussion
- Issues? Check the [issues tab](https://github.com/tiagosiebler/bybit-api/issues). - Issues? Check the [issues tab](https://github.com/tiagosiebler/bybit-api/issues).
- Discuss & collaborate with other node devs? Join our [Node.js Algo Traders](https://t.me/nodetraders) engineering community on telegram. - Discuss & collaborate with other node devs? Join our [Node.js Algo Traders](https://t.me/nodetraders) engineering community on telegram.
- `'bybit-api' has no exported member 'RestClient'`: use `InverseClient` instead of `RestClient`
## Documentation ## Documentation
Most methods accept JS objects. These can be populated using parameters specified by Bybit's API documentation. Most methods accept JS objects. These can be populated using parameters specified by Bybit's API documentation.
- [Bybit API Inverse Documentation](https://bybit-exchange.github.io/docs/inverse/#t-introduction). - [Bybit API Inverse Documentation](https://bybit-exchange.github.io/docs/inverse/#t-introduction).
- [Bybit API Inverse Futures Documentation](https://bybit-exchange.github.io/docs/inverse_futures/#t-introduction).
- [Bybit API Linear Documentation](https://bybit-exchange.github.io/docs/linear/#t-introduction) - [Bybit API Linear Documentation](https://bybit-exchange.github.io/docs/linear/#t-introduction)
## Structure ## Structure
@@ -26,21 +26,22 @@ This project uses typescript. Resources are stored in 3 key structures:
- [lib](./lib) - the javascript version of the project (compiled from typescript). This should not be edited directly, as it will be overwritten with each release. - [lib](./lib) - the javascript version of the project (compiled from typescript). This should not be edited directly, as it will be overwritten with each release.
- [dist](./dist) - the packed bundle of the project for use in browser environments. - [dist](./dist) - the packed bundle of the project for use in browser environments.
## Usage ---
# Usage
Create API credentials at Bybit Create API credentials at Bybit
- [Livenet](https://bybit.com/app/user/api-management?affiliate_id=9410&language=en-US&group_id=0&group_type=1) - [Livenet](https://bybit.com/app/user/api-management?affiliate_id=9410&language=en-US&group_id=0&group_type=1)
- [Testnet](https://testnet.bybit.com/app/user/api-management) - [Testnet](https://testnet.bybit.com/app/user/api-management)
### Browser Usage ## REST API Clients
Build a bundle using webpack:
- `npm install`
- `npm build`
- `npm pack`
The bundle can be found in `dist/`. Altough usage should be largely consistent, smaller differences will exist. Documentation is still TODO. There are three REST API modules as there are some differences in each contract type.
1. `InverseClient` for inverse perpetual
2. `InverseFuturesClient` for inverse futures
3. `LinearClient` for linear perpetual
### Inverse Contracts ### REST Inverse
Since inverse and linear (USDT) contracts don't use the exact same APIs, the REST abstractions are split into two modules. To use the inverse REST APIs, import the `InverseClient`: <details><summary>To use the inverse REST APIs, import the `InverseClient`. Click here to expand and see full sample:</summary>
```javascript ```javascript
const { InverseClient } = require('bybit-api'); const { InverseClient } = require('bybit-api');
@@ -98,35 +99,60 @@ client.getOrderBook({ symbol: 'BTCUSD' })
}); });
``` ```
See inverse [inverse-client.ts](./src/inverse-client.ts) for further information. </details>
### Linear Contracts See [inverse-client.ts](./src/inverse-client.ts) for further information.
To use the Linear (USDT) REST APIs, import the `LinearClient`:
### REST Inverse Futures
<details><summary>To use the inverse futures REST APIs, import the `InverseFuturesClient`. Click here to expand and see full sample:</summary>
```javascript
const { InverseFuturesClient } = require('bybit-api');
const API_KEY = 'xxx';
const PRIVATE_KEY = 'yyy';
const useLivenet = false;
const client = new InverseFuturesClient(
API_KEY,
PRIVATE_KEY,
// optional, uses testnet by default. Set to 'true' to use livenet.
useLivenet,
// restClientOptions,
// requestLibraryOptions
);
client.getApiKeyInfo()
.then(result => {
console.log("apiKey result: ", result);
})
.catch(err => {
console.error("apiKey error: ", err);
});
client.getOrderBook({ symbol: 'BTCUSDH21' })
.then(result => {
console.log("getOrderBook inverse futures result: ", result);
})
.catch(err => {
console.error("getOrderBook inverse futures error: ", err);
});
```
</details>
See [inverse-futures-client.ts](./src/inverse-futures-client.ts) for further information.
**Note**: as of 6th March 2021 this is currently only for testnet. See the [Bybit API documentation](https://bybit-exchange.github.io/docs/inverse_futures/#t-introduction) for official updates.
### REST Linear
<details><summary>To use the Linear (USDT) REST APIs, import the `LinearClient`. Click here to expand and see full sample:</summary>
```javascript ```javascript
const { LinearClient } = require('bybit-api'); const { LinearClient } = require('bybit-api');
const restClientOptions = {
// override the max size of the request window (in ms)
recv_window?: number;
// how often to sync time drift with bybit servers
sync_interval_ms?: number | string;
// Default: false. Disable above sync mechanism if true.
disable_time_sync?: boolean;
// Default: false. If true, we'll throw errors if any params are undefined
strict_param_validation?: boolean;
// Optionally override API protocol + domain
// e.g 'https://api.bytick.com'
baseUrl?: string;
// Default: true. whether to try and post-process request exceptions.
parse_exceptions?: boolean;
};
const API_KEY = 'xxx'; const API_KEY = 'xxx';
const PRIVATE_KEY = 'yyy'; const PRIVATE_KEY = 'yyy';
const useLivenet = false; const useLivenet = false;
@@ -159,13 +185,10 @@ client.getOrderBook({ symbol: 'BTCUSDT' })
}); });
``` ```
### WebSockets </details>
Inverse & linear WebSockets can be used via a shared `WebsocketClient`. ## WebSockets
<details><summary>Inverse & linear WebSockets can be used via a shared `WebsocketClient`. Click here to expand and see full sample:</summary>
Note: to use the linear websockets, pass "linear: true" in the constructor options when instancing the `WebsocketClient`.
To connect to both linear and inverse websockets, make two instances of the WebsocketClient:
```javascript ```javascript
const { WebsocketClient } = require('bybit-api'); const { WebsocketClient } = require('bybit-api');
@@ -240,12 +263,21 @@ ws.on('error', err => {
console.error('ERR', err); console.error('ERR', err);
}); });
``` ```
</details>
See [websocket-client.ts](./src/websocket-client.ts) for further information. See [websocket-client.ts](./src/websocket-client.ts) for further information.
### Customise Logging Note: for linear websockets, pass `linear: true` in the constructor options when instancing the `WebsocketClient`. To connect to both linear and inverse websockets, make two instances of the WebsocketClient.
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:
```js ---
## 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.
<details><summary>Click here to expand and see full sample:</summary>
```javascript
const { WebsocketClient, DefaultLogger } = require('bybit-api'); const { WebsocketClient, DefaultLogger } = require('bybit-api');
// Disable all logging on the silly level // Disable all logging on the silly level
@@ -257,6 +289,20 @@ const ws = new WebsocketClient(
); );
``` ```
</details>
## Browser Usage
Build a bundle using webpack:
- `npm install`
- `npm build`
- `npm pack`
The bundle can be found in `dist/`. Altough usage should be largely consistent, smaller differences will exist. Documentation is still TODO.
However, note that browser usage will lead to CORS errors due Bybit. See [issue #79](#79) for more information & alternative suggestions.
---
## Contributions & Thanks ## Contributions & Thanks
### Donations ### Donations
#### tiagosiebler #### tiagosiebler

View File

@@ -1,6 +1,6 @@
{ {
"name": "bybit-api", "name": "bybit-api",
"version": "2.0.2", "version": "2.0.3",
"description": "Node.js connector for Bybit's Inverse & Linear REST APIs and WebSockets", "description": "Node.js connector for Bybit's Inverse & Linear REST APIs and WebSockets",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",

View File

@@ -1,4 +1,5 @@
export * from './inverse-client'; export * from './inverse-client';
export * from './inverse-futures-client';
export * from './linear-client'; export * from './linear-client';
export * from './websocket-client'; export * from './websocket-client';
export * from './logger'; export * from './logger';

View File

@@ -18,7 +18,7 @@ export class InverseClient extends SharedEndpoints {
constructor( constructor(
key?: string | undefined, key?: string | undefined,
secret?: string | undefined, secret?: string | undefined,
useLivenet?: boolean, useLivenet: boolean = false,
restClientOptions: RestClientOptions = {}, restClientOptions: RestClientOptions = {},
requestOptions: AxiosRequestConfig = {} requestOptions: AxiosRequestConfig = {}
) { ) {
@@ -282,6 +282,7 @@ export class InverseClient extends SharedEndpoints {
symbol: string; symbol: string;
take_profit?: number; take_profit?: number;
stop_loss?: number; stop_loss?: number;
trailing_stop?: number;
tp_trigger_by?: string; tp_trigger_by?: string;
sl_trigger_by?: string; sl_trigger_by?: string;
new_trailing_active?: number; new_trailing_active?: number;

View File

@@ -0,0 +1,354 @@
import { AxiosRequestConfig } from 'axios';
import { GenericAPIResponse, getRestBaseUrl, RestClientOptions } from './util/requestUtils';
import RequestWrapper from './util/requestWrapper';
import SharedEndpoints from './shared-endpoints';
export class InverseFuturesClient extends SharedEndpoints {
protected requestWrapper: RequestWrapper;
/**
* @public Creates an instance of the inverse futures REST API client.
*
* @param {string} key - your API key
* @param {string} secret - your API secret
* @param {boolean} [useLivenet=false]
* @param {RestClientOptions} [restClientOptions={}] options to configure REST API connectivity
* @param {AxiosRequestConfig} [requestOptions={}] HTTP networking options for axios
*/
constructor(
key?: string | undefined,
secret?: string | undefined,
useLivenet: boolean = false,
restClientOptions: RestClientOptions = {},
requestOptions: AxiosRequestConfig = {}
) {
super()
this.requestWrapper = new RequestWrapper(
key,
secret,
getRestBaseUrl(useLivenet, restClientOptions),
restClientOptions,
requestOptions
);
return this;
}
/**
*
* Market Data Endpoints
* Note: These are currently the same as the inverse client
*/
getKline(params: {
symbol: string;
interval: string;
from: number;
limit?: number;
}): GenericAPIResponse {
return this.requestWrapper.get('v2/public/kline/list', params);
}
/**
* Public trading records
*/
getTrades(params: {
symbol: string;
from?: number;
limit?: number;
}): GenericAPIResponse {
return this.requestWrapper.get('v2/public/trading-records', params);
}
getMarkPriceKline(params: {
symbol: string;
interval: string;
from: number;
limit?: number;
}): GenericAPIResponse {
return this.requestWrapper.get('v2/public/mark-price-kline', params);
}
getIndexPriceKline(params: {
symbol: string;
interval: string;
from: number;
limit?: number;
}): GenericAPIResponse {
return this.requestWrapper.get('v2/public/index-price-kline', params);
}
getPremiumIndexKline(params: {
symbol: string;
interval: string;
from: number;
limit?: number;
}): GenericAPIResponse {
return this.requestWrapper.get('v2/public/premium-index-kline', params);
}
/**
*
* Account Data Endpoints
*
*/
/**
* Active orders
*/
placeActiveOrder(orderRequest: {
side: string;
symbol: string;
order_type: string;
qty: number;
price?: number;
time_in_force: string;
take_profit?: number;
stop_loss?: number;
reduce_only?: boolean;
close_on_trigger?: boolean;
order_link_id?: string;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/order/create', orderRequest);
}
getActiveOrderList(params: {
symbol: string;
order_status?: string;
direction?: string;
limit?: number;
cursor?: string;
}): GenericAPIResponse {
return this.requestWrapper.get('futures/private/order/list', params);
}
cancelActiveOrder(params: {
symbol: string;
order_id?: string;
order_link_id?: string;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/order/cancel', params);
}
cancelAllActiveOrders(params: {
symbol: string;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/order/cancelAll', params);
}
replaceActiveOrder(params: {
order_id?: string;
order_link_id?: string;
symbol: string;
p_r_qty?: string;
p_r_price?: string;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/order/replace', params);
}
queryActiveOrder(params: {
order_id?: string;
order_link_id?: string;
symbol: string;
}): GenericAPIResponse {
return this.requestWrapper.get('futures/private/order', params);
}
/**
* Conditional orders
*/
placeConditionalOrder(params: {
side: string;
symbol: string;
order_type: string;
qty: string;
price?: string;
base_price: string;
stop_px: string;
time_in_force: string;
trigger_by?: string;
close_on_trigger?: boolean;
order_link_id?: string;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/stop-order/create', params);
}
getConditionalOrder(params: {
symbol: string;
stop_order_status?: string;
direction?: string;
limit?: number;
cursor?: string;
}): GenericAPIResponse {
return this.requestWrapper.get('futures/private/stop-order/list', params);
}
cancelConditionalOrder(params: {
symbol: string;
stop_order_id?: string;
order_link_id?: string;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/stop-order/cancel', params);
}
cancelAllConditionalOrders(params: {
symbol: string;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/stop-order/cancelAll', params);
}
replaceConditionalOrder(params: {
stop_order_id?: string;
order_link_id?: string;
symbol: string;
p_r_qty?: number;
p_r_price?: string;
p_r_trigger_price?: string;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/stop-order/replace', params);
}
queryConditionalOrder(params: {
symbol: string;
stop_order_id?: string;
order_link_id?: string;
}): GenericAPIResponse {
return this.requestWrapper.get('futures/private/stop-order', params);
}
/**
* Position
*/
/**
* Get position list
*/
getPosition(params?: {
symbol?: string;
}): GenericAPIResponse {
return this.requestWrapper.get('futures/private/position/list', params);
}
changePositionMargin(params: {
symbol: string;
margin: string;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/position/change-position-margin', params);
}
setTradingStop(params: {
symbol: string;
take_profit?: number;
stop_loss?: number;
trailing_stop?: number;
tp_trigger_by?: string;
sl_trigger_by?: string;
new_trailing_active?: number;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/position/trading-stop', params);
}
setUserLeverage(params: {
symbol: string;
buy_leverage: number;
sell_leverage: number;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/position/leverage/save', params);
}
/**
* Position mode switch
*/
setPositionMode(params: {
symbol: string;
mode: number;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/position/switch-mode', params);
}
/**
* Cross/Isolated margin switch. Must set leverage value when switching.
*/
setMarginType(params: {
symbol: string;
is_isolated: boolean;
buy_leverage: number;
sell_leverage: number;
}): GenericAPIResponse {
return this.requestWrapper.post('futures/private/position/switch-isolated', params);
}
getTradeRecords(params: {
order_id?: string;
symbol: string;
start_time?: number;
page?: number;
limit?: number;
order?: string;
}): GenericAPIResponse {
return this.requestWrapper.get('futures/private/execution/list', params);
}
getClosedPnl(params: {
symbol: string;
start_time?: number;
end_time?: number;
exec_type?: string;
page?: number;
limit?: number;
}): GenericAPIResponse {
return this.requestWrapper.get('futures/private/trade/closed-pnl/list', params);
}
/**
**** The following are all the same as the inverse client ****
*/
/**
* Risk Limit
*/
getRiskLimitList(): GenericAPIResponse {
return this.requestWrapper.get('open-api/wallet/risk-limit/list');
}
setRiskLimit(params: {
symbol: string;
risk_id: string;
}): GenericAPIResponse {
return this.requestWrapper.post('open-api/wallet/risk-limit', params);
}
/**
* Funding
*/
getLastFundingRate(params: {
symbol: string;
}): GenericAPIResponse {
return this.requestWrapper.get('v2/public/funding/prev-funding-rate', params);
}
getMyLastFundingFee(params: {
symbol: string;
}): GenericAPIResponse {
return this.requestWrapper.get('v2/private/funding/prev-funding', params);
}
getPredictedFunding(params: {
symbol: string;
}): GenericAPIResponse {
return this.requestWrapper.get('v2/private/funding/predicted-funding', params);
}
/**
* LCP Info
*/
getLcpInfo(params: {
symbol: string;
}): GenericAPIResponse {
return this.requestWrapper.get('v2/private/account/lcp', params);
}
};

View File

@@ -18,7 +18,7 @@ export class LinearClient extends SharedEndpoints {
constructor( constructor(
key?: string | undefined, key?: string | undefined,
secret?: string | undefined, secret?: string | undefined,
useLivenet?: boolean, useLivenet: boolean = false,
restClientOptions: RestClientOptions = {}, restClientOptions: RestClientOptions = {},
requestOptions: AxiosRequestConfig = {} requestOptions: AxiosRequestConfig = {}
) { ) {

View File

@@ -17,6 +17,9 @@ export default class SharedEndpoints {
return this.requestWrapper.get('v2/public/orderBook/L2', params); return this.requestWrapper.get('v2/public/orderBook/L2', params);
} }
/**
* Get latest information for symbol
*/
getTickers(params?: { getTickers(params?: {
symbol?: string; symbol?: string;
}): GenericAPIResponse { }): GenericAPIResponse {
@@ -27,6 +30,9 @@ export default class SharedEndpoints {
return this.requestWrapper.get('v2/public/symbols'); return this.requestWrapper.get('v2/public/symbols');
} }
/**
* Get liquidated orders
*/
getLiquidations(params: { getLiquidations(params: {
symbol: string; symbol: string;
from?: number; from?: number;

View File

@@ -42,13 +42,13 @@ export function serializeParams(params: object = {}, strict_validation = false):
.join('&'); .join('&');
}; };
export function getRestBaseUrl(useLivenet?: boolean, restInverseOptions?: RestClientOptions) { export function getRestBaseUrl(useLivenet: boolean, restInverseOptions: RestClientOptions) {
const baseUrlsInverse = { const baseUrlsInverse = {
livenet: 'https://api.bybit.com', livenet: 'https://api.bybit.com',
testnet: 'https://api-testnet.bybit.com' testnet: 'https://api-testnet.bybit.com'
}; };
if (restInverseOptions?.baseUrl) { if (restInverseOptions.baseUrl) {
return restInverseOptions.baseUrl; return restInverseOptions.baseUrl;
} }

View File

@@ -55,7 +55,6 @@ export default class RequestUtil {
this.secret = secret; this.secret = secret;
} }
// TODO: type check that endpoint never starts with forward slash??
get(endpoint: string, params?: any): GenericAPIResponse { get(endpoint: string, params?: any): GenericAPIResponse {
return this._call('GET', endpoint, params); return this._call('GET', endpoint, params);
} }