diff --git a/README.md b/README.md index ff9f3c00..943ba198 100644 --- a/README.md +++ b/README.md @@ -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 +
To use the inverse REST APIs, import the `InverseClient`. Click here to expand and see full sample: ```javascript const { InverseClient } = require('bybit-api'); @@ -98,34 +99,59 @@ client.getOrderBook({ symbol: 'BTCUSD' }) }); ``` -See inverse [inverse-client.ts](./src/inverse-client.ts) for further information. +
+ +See [inverse-client.ts](./src/inverse-client.ts) for further information. -### Linear Contracts -To use the Linear (USDT) REST APIs, import the `LinearClient`: +### REST Inverse Futures +
To use the inverse futures REST APIs, import the `InverseFuturesClient`. Click here to expand and see full sample: ```javascript -const { LinearClient } = require('bybit-api'); +const { InverseFuturesClient } = require('bybit-api'); -const restClientOptions = { - // override the max size of the request window (in ms) - recv_window?: number; +const API_KEY = 'xxx'; +const PRIVATE_KEY = 'yyy'; +const useLivenet = false; - // how often to sync time drift with bybit servers - sync_interval_ms?: number | string; +const client = new InverseFuturesClient( + API_KEY, + PRIVATE_KEY, - // Default: false. Disable above sync mechanism if true. - disable_time_sync?: boolean; + // optional, uses testnet by default. Set to 'true' to use livenet. + useLivenet, - // Default: false. If true, we'll throw errors if any params are undefined - strict_param_validation?: boolean; + // restClientOptions, + // requestLibraryOptions +); - // Optionally override API protocol + domain - // e.g 'https://api.bytick.com' - baseUrl?: string; +client.getApiKeyInfo() + .then(result => { + console.log("apiKey result: ", result); + }) + .catch(err => { + console.error("apiKey error: ", err); + }); - // Default: true. whether to try and post-process request exceptions. - parse_exceptions?: boolean; -}; +client.getOrderBook({ symbol: 'BTCUSDH21' }) + .then(result => { + console.log("getOrderBook inverse futures result: ", result); + }) + .catch(err => { + console.error("getOrderBook inverse futures error: ", err); + }); +``` + +
+ +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 +
To use the Linear (USDT) REST APIs, import the `LinearClient`. Click here to expand and see full sample: + +```javascript +const { LinearClient } = require('bybit-api'); const API_KEY = 'xxx'; const PRIVATE_KEY = 'yyy'; @@ -159,13 +185,10 @@ client.getOrderBook({ symbol: 'BTCUSDT' }) }); ``` -### WebSockets - -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 +
Inverse & linear WebSockets can be used via a shared `WebsocketClient`. Click here to expand and see full sample: ```javascript const { WebsocketClient } = require('bybit-api'); @@ -240,12 +263,21 @@ ws.on('error', err => { console.error('ERR', err); }); ``` + +
+ 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. + +--- + +## 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. -```js +
Click here to expand and see full sample: + +```javascript const { WebsocketClient, DefaultLogger } = require('bybit-api'); // Disable all logging on the silly level @@ -257,6 +289,20 @@ const ws = new WebsocketClient( ); ``` +
+ +## 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 diff --git a/package.json b/package.json index 6568d7c1..ed63cc9d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/index.ts b/src/index.ts index bc82e2e4..bdd98508 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './inverse-client'; +export * from './inverse-futures-client'; export * from './linear-client'; export * from './websocket-client'; export * from './logger'; diff --git a/src/inverse-client.ts b/src/inverse-client.ts index 2a1779e8..80520bdc 100644 --- a/src/inverse-client.ts +++ b/src/inverse-client.ts @@ -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; diff --git a/src/inverse-futures-client.ts b/src/inverse-futures-client.ts new file mode 100644 index 00000000..fcd4cd09 --- /dev/null +++ b/src/inverse-futures-client.ts @@ -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); + } +}; diff --git a/src/linear-client.ts b/src/linear-client.ts index 4c114116..9efc57ce 100644 --- a/src/linear-client.ts +++ b/src/linear-client.ts @@ -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 = {} ) { diff --git a/src/shared-endpoints.ts b/src/shared-endpoints.ts index 1f3a9b2e..730a3207 100644 --- a/src/shared-endpoints.ts +++ b/src/shared-endpoints.ts @@ -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; diff --git a/src/util/requestUtils.ts b/src/util/requestUtils.ts index 903a7a23..a54086cd 100644 --- a/src/util/requestUtils.ts +++ b/src/util/requestUtils.ts @@ -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; } diff --git a/src/util/requestWrapper.ts b/src/util/requestWrapper.ts index e19f8f30..e618bc67 100644 --- a/src/util/requestWrapper.ts +++ b/src/util/requestWrapper.ts @@ -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); }