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
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
`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? 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.
- `'bybit-api' has no exported member 'RestClient'`: use `InverseClient` instead of `RestClient`
## 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 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)
## 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.
- [dist](./dist) - the packed bundle of the project for use in browser environments.
## Usage
---
# Usage
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)
- [Testnet](https://testnet.bybit.com/app/user/api-management)
### Browser Usage
Build a bundle using webpack:
- `npm install`
- `npm build`
- `npm pack`
## REST API Clients
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
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`:
### REST Inverse
<details><summary>To use the inverse REST APIs, import the `InverseClient`. Click here to expand and see full sample:</summary>
```javascript
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
To use the Linear (USDT) REST APIs, import the `LinearClient`:
See [inverse-client.ts](./src/inverse-client.ts) for further information.
### 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
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 PRIVATE_KEY = 'yyy';
const useLivenet = false;
@@ -159,13 +185,10 @@ client.getOrderBook({ symbol: 'BTCUSDT' })
});
```
### WebSockets
</details>
Inverse & linear WebSockets can be used via a shared `WebsocketClient`.
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:
## WebSockets
<details><summary>Inverse & linear WebSockets can be used via a shared `WebsocketClient`. Click here to expand and see full sample:</summary>
```javascript
const { WebsocketClient } = require('bybit-api');
@@ -240,12 +263,21 @@ ws.on('error', err => {
console.error('ERR', err);
});
```
</details>
See [websocket-client.ts](./src/websocket-client.ts) for further information.
### 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:
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.
```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');
// 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
### Donations
#### tiagosiebler

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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