diff --git a/README.md b/README.md index 85bdc22..0d403b7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # bitget-api [![Build & Test](https://github.com/tiagosiebler/bitget-api/actions/workflows/integrationtest.yml/badge.svg?branch=master)](https://github.com/tiagosiebler/bitget-api/actions/workflows/integrationtest.yml) [![npm version](https://img.shields.io/npm/v/bitget-api)][1] [![npm size](https://img.shields.io/bundlephobia/min/bitget-api/latest)][1] [![npm downloads](https://img.shields.io/npm/dt/bitget-api)][1] [![last commit](https://img.shields.io/github/last-commit/tiagosiebler/bitget-api)][1] -[![CodeFactor](https://www.codefactor.io/repository/github/tiagosiebler/bitget-api/badge)](https://www.codefactor.io/repository/github/tiagosiebler/bitget-api) +[![CodeFactor](https://www.codefactor.io/repository/github/tiagosiebler/bitget-api/badge)](https://www.codefactor.io/repository/github/tiagosiebler/bitget-api) [![Telegram](https://img.shields.io/badge/chat-on%20telegram-blue.svg)](https://t.me/nodetraders) [![connector logo](https://github.com/tiagosiebler/bitget-api/blob/master/docs/images/logo1.png?raw=true)][1] diff --git a/examples/rest-trade-futures.ts b/examples/rest-trade-futures.ts new file mode 100644 index 0000000..ba156f4 --- /dev/null +++ b/examples/rest-trade-futures.ts @@ -0,0 +1,104 @@ +import { FuturesClient, WebsocketClient } from '../src/index'; + +// or +// import { SpotClient } from 'bitget-api'; + +// read from environmental variables +const API_KEY = process.env.API_KEY_COM; +const API_SECRET = process.env.API_SECRET_COM; +const API_PASS = process.env.API_PASS_COM; + +const client = new FuturesClient({ + apiKey: API_KEY, + // apiKey: 'apiKeyHere', + apiSecret: API_SECRET, + // apiSecret: 'apiSecretHere', + apiPass: API_PASS, + // apiPass: 'apiPassHere', +}); + +const wsClient = new WebsocketClient({ + apiKey: API_KEY, + apiSecret: API_SECRET, + apiPass: API_PASS, +}); + +function logWSEvent(type, data) { + console.log(new Date(), `WS ${type} event: `, data); +} + +// simple sleep function +function promiseSleep(milliseconds) { + return new Promise((resolve) => setTimeout(resolve, milliseconds)); +} + +// WARNING: for sensitive math you should be using a library such as decimal.js! +function roundDown(value, decimals) { + return Number( + Math.floor(parseFloat(value + 'e' + decimals)) + 'e-' + decimals + ); +} + +/** This is a simple script wrapped in a immediately invoked function expression, designed to check for any available BTC balance and immediately sell the full amount for USDT */ +(async () => { + try { + // Add event listeners to log websocket events on account + wsClient.on('update', (data) => logWSEvent('update', data)); + wsClient.on('open', (data) => logWSEvent('open', data)); + wsClient.on('response', (data) => logWSEvent('response', data)); + wsClient.on('reconnect', (data) => logWSEvent('reconnect', data)); + wsClient.on('reconnected', (data) => logWSEvent('reconnected', data)); + wsClient.on('authenticated', (data) => logWSEvent('authenticated', data)); + wsClient.on('exception', (data) => logWSEvent('exception', data)); + + // Subscribe to private account topics + wsClient.subscribeTopic('UMCBL', 'account'); + // : position updates + wsClient.subscribeTopic('UMCBL', 'positions'); + // : order updates + wsClient.subscribeTopic('UMCBL', 'orders'); + + // wait briefly for ws to be ready (could also use the response or authenticated events, to make sure topics are subscribed to before starting) + await promiseSleep(2.5 * 1000); + + const symbol = 'BTCUSDT_UMCBL'; + const marginCoin = 'USDT'; + + const balanceResult = await client.getAccount(symbol, marginCoin); + const accountBalance = balanceResult.data; + // const balances = allBalances.filter((bal) => Number(bal.available) != 0); + const usdtAmount = accountBalance.available; + console.log('USDT balance: ', usdtAmount); + + if (!usdtAmount) { + console.error('No USDT to trade'); + return; + } + + const symbolRulesResult = await client.getSymbols('umcbl'); + const bitcoinUSDFuturesRule = symbolRulesResult.data.find( + (row) => row.symbol === symbol + ); + console.log('symbol rules: ', bitcoinUSDFuturesRule); + if (!bitcoinUSDFuturesRule) { + console.error('Failed to get trading rules for ' + symbol); + return; + } + + const order = { + marginCoin, + orderType: 'market', + side: 'open_long', + size: bitcoinUSDFuturesRule.minTradeNum, + symbol, + } as const; + + console.log('placing order: ', order); + + const result = await client.submitOrder(order); + + console.log('order result: ', result); + } catch (e) { + console.error('request failed: ', e); + } +})(); diff --git a/examples/rest-trade-spot.ts b/examples/rest-trade-spot.ts new file mode 100644 index 0000000..342c317 --- /dev/null +++ b/examples/rest-trade-spot.ts @@ -0,0 +1,104 @@ +import { SpotClient, WebsocketClient } from '../src/index'; + +// or +// import { SpotClient } from 'bitget-api'; + +// read from environmental variables +const API_KEY = process.env.API_KEY_COM; +const API_SECRET = process.env.API_SECRET_COM; +const API_PASS = process.env.API_PASS_COM; + +const client = new SpotClient({ + apiKey: API_KEY, + // apiKey: 'apiKeyHere', + apiSecret: API_SECRET, + // apiSecret: 'apiSecretHere', + apiPass: API_PASS, + // apiPass: 'apiPassHere', +}); + +const wsClient = new WebsocketClient({ + apiKey: API_KEY, + apiSecret: API_SECRET, + apiPass: API_PASS, +}); + +function logWSEvent(type, data) { + console.log(new Date(), `WS ${type} event: `, data); +} + +// simple sleep function +function promiseSleep(milliseconds) { + return new Promise((resolve) => setTimeout(resolve, milliseconds)); +} + +// WARNING: for sensitive math you should be using a library such as decimal.js! +function roundDown(value, decimals) { + return Number( + Math.floor(parseFloat(value + 'e' + decimals)) + 'e-' + decimals + ); +} + +/** This is a simple script wrapped in a immediately invoked function expression, designed to check for any available BTC balance and immediately sell the full amount for USDT */ +(async () => { + try { + // Add event listeners to log websocket events on account + wsClient.on('update', (data) => logWSEvent('update', data)); + wsClient.on('open', (data) => logWSEvent('open', data)); + wsClient.on('response', (data) => logWSEvent('response', data)); + wsClient.on('reconnect', (data) => logWSEvent('reconnect', data)); + wsClient.on('reconnected', (data) => logWSEvent('reconnected', data)); + wsClient.on('authenticated', (data) => logWSEvent('authenticated', data)); + wsClient.on('exception', (data) => logWSEvent('exception', data)); + + // Subscribe to private account topics + wsClient.subscribeTopic('SPBL', 'account'); + wsClient.subscribeTopic('SPBL', 'orders'); + + // wait briefly for ws to be ready (could also use the response or authenticated events, to make sure topics are subscribed to before starting) + await promiseSleep(2.5 * 1000); + + const balanceResult = await client.getBalance(); + const allBalances = balanceResult.data; + // const balances = allBalances.filter((bal) => Number(bal.available) != 0); + const balanceBTC = allBalances.find((bal) => bal.coinName === 'BTC'); + const btcAmount = balanceBTC ? Number(balanceBTC.available) : 0; + // console.log('balance: ', JSON.stringify(balances, null, 2)); + console.log('BTC balance result: ', balanceBTC); + + if (!btcAmount) { + console.error('No BTC to trade'); + return; + } + + console.log(`BTC available: ${btcAmount}`); + const symbol = 'BTCUSDT_SPBL'; + + const symbolsResult = await client.getSymbols(); + const btcRules = symbolsResult.data.find((rule) => rule.symbol === symbol); + console.log('btc trading rules: ', btcRules); + if (!btcRules) { + return console.log('no rules found for trading ' + symbol); + } + + const quantityScale = Number(btcRules.quantityScale); + // const quantityRoundedDown = btcAmount - btcAmount % 0.01 + const quantity = roundDown(btcAmount, quantityScale); + + const order = { + symbol: symbol, + side: 'sell', + force: 'normal', + orderType: 'market', + quantity: String(quantity), + } as const; + + console.log('submitting order: ', order); + + const sellResult = await client.submitOrder(order); + + console.log('sell result: ', sellResult); + } catch (e) { + console.error('request failed: ', e); + } +})(); diff --git a/src/futures-client.ts b/src/futures-client.ts index 4b53927..333f816 100644 --- a/src/futures-client.ts +++ b/src/futures-client.ts @@ -15,6 +15,8 @@ import { CancelFuturesPlanTPSL, HistoricPlanOrderTPSLRequest, NewFuturesPlanStopOrder, + FuturesAccount, + FuturesSymbolRule, } from './types'; import { REST_CLIENT_TYPE_ENUM } from './util'; import BaseRestClient from './util/BaseRestClient'; @@ -34,7 +36,9 @@ export class FuturesClient extends BaseRestClient { */ /** Get Symbols : Get basic configuration information of all trading pairs (including rules) */ - getSymbols(productType: FuturesProductType): Promise> { + getSymbols( + productType: FuturesProductType + ): Promise> { return this.get('/api/mix/v1/market/contracts', { productType }); } @@ -125,7 +129,10 @@ export class FuturesClient extends BaseRestClient { */ /** Get Single Account */ - getAccount(symbol: string, marginCoin: string): Promise> { + getAccount( + symbol: string, + marginCoin: string + ): Promise> { return this.getPrivate('/api/mix/v1/account/account', { symbol, marginCoin, diff --git a/src/spot-client.ts b/src/spot-client.ts index e5ee80e..b239ec3 100644 --- a/src/spot-client.ts +++ b/src/spot-client.ts @@ -5,6 +5,8 @@ import { Pagination, APIResponse, KlineInterval, + CoinBalance, + SymbolRules, } from './types'; import { REST_CLIENT_TYPE_ENUM } from './util'; import BaseRestClient from './util/BaseRestClient'; @@ -39,7 +41,7 @@ export class SpotClient extends BaseRestClient { } /** Get Symbols : Get basic configuration information of all trading pairs (including rules) */ - getSymbols(): Promise> { + getSymbols(): Promise> { return this.get('/api/spot/v1/public/products'); } @@ -184,7 +186,7 @@ export class SpotClient extends BaseRestClient { } /** Get Account : get account assets */ - getBalance(coin?: string): Promise> { + getBalance(coin?: string): Promise> { return this.getPrivate('/api/spot/v1/account/assets', { coin }); } diff --git a/src/types/request/futures.ts b/src/types/request/futures.ts index 7a1674f..a0ff151 100644 --- a/src/types/request/futures.ts +++ b/src/types/request/futures.ts @@ -1,3 +1,5 @@ +import { OrderTimeInForce } from './shared'; + export type FuturesProductType = | 'umcbl' | 'dmcbl' @@ -39,7 +41,7 @@ export interface NewFuturesOrder { price?: string; side: FuturesOrderSide; orderType: FuturesOrderType; - timeInForceValue?: string; + timeInForceValue?: OrderTimeInForce; clientOid?: string; presetTakeProfitPrice?: string; presetStopLossPrice?: string; diff --git a/src/types/request/shared.ts b/src/types/request/shared.ts index 5f5b1b0..0e9bd1e 100644 --- a/src/types/request/shared.ts +++ b/src/types/request/shared.ts @@ -7,3 +7,5 @@ export interface Pagination { /** Elements per page */ limit?: string; } + +export type OrderTimeInForce = 'normal' | 'post_only' | 'fok' | 'ioc'; diff --git a/src/types/request/spot.ts b/src/types/request/spot.ts index 4c10179..edc5e46 100644 --- a/src/types/request/spot.ts +++ b/src/types/request/spot.ts @@ -1,7 +1,4 @@ -import { numberInString, OrderSide } from '../shared'; - -export type OrderTypeSpot = 'LIMIT' | 'MARKET' | 'LIMIT_MAKER'; -export type OrderTimeInForce = 'GTC' | 'FOK' | 'IOC'; +import { OrderTimeInForce } from './shared'; export type WalletType = 'spot' | 'mix_usdt' | 'mix_usd'; @@ -15,9 +12,9 @@ export interface NewWalletTransfer { export interface NewSpotOrder { symbol: string; - side: string; - orderType: string; - force: string; + side: 'buy' | 'sell'; + orderType: 'limit' | 'market'; + force: OrderTimeInForce; price?: string; quantity: string; clientOrderId?: string; diff --git a/src/types/response/futures.ts b/src/types/response/futures.ts new file mode 100644 index 0000000..3977553 --- /dev/null +++ b/src/types/response/futures.ts @@ -0,0 +1,35 @@ +export interface FuturesAccount { + marginCoin: string; + locked: number; + available: number; + crossMaxAvailable: number; + fixedMaxAvailable: number; + maxTransferOut: number; + equity: number; + usdtEquity: number; + btcEquity: number; + crossRiskRate: number; + crossMarginLeverage: number; + fixedLongLeverage: number; + fixedShortLeverage: number; + marginMode: string; + holdMode: string; +} + +export interface FuturesSymbolRule { + baseCoin: string; + buyLimitPriceRatio: string; + feeRateUpRatio: string; + makerFeeRate: string; + minTradeNum: string; + openCostUpRatio: string; + priceEndStep: string; + pricePlace: string; + quoteCoin: string; + sellLimitPriceRatio: string; + sizeMultiplier: string; + supportMarginCoins: string[]; + symbol: string; + takerFeeRate: string; + volumePlace: string; +} diff --git a/src/types/response/index.ts b/src/types/response/index.ts index c3da79f..0c86356 100644 --- a/src/types/response/index.ts +++ b/src/types/response/index.ts @@ -1 +1,3 @@ export * from './shared'; +export * from './spot'; +export * from './futures'; diff --git a/src/types/response/spot.ts b/src/types/response/spot.ts new file mode 100644 index 0000000..4c6ea61 --- /dev/null +++ b/src/types/response/spot.ts @@ -0,0 +1,22 @@ +export interface CoinBalance { + coinId: number; + coinName: string; + available: string; + frozen: string; + lock: string; + uTime: string; +} + +export interface SymbolRules { + symbol: string; + symbolName: string; + baseCoin: string; + quoteCoin: string; + minTradeAmount: string; + maxTradeAmount: string; + takerFeeRate: string; + makerFeeRate: string; + priceScale: string; + quantityScale: string; + status: string; +}