UNPKG

binance-api-client

Version:

A wrapper which can be used to interact with Binance's API. Entirely developed in TypeScript.

858 lines (809 loc) 31.5 kB
import { AuthenticationMethod } from "./enums/AuthenticationMethod"; import { ApiVersion } from "./enums/ApiVersion"; import * as CryptoJs from "crypto-js"; import { HttpMethod } from "./enums/HttpMethod"; import { AuthenticationError } from "./errors/AuthenticationError"; import * as request from "request-promise"; import * as Path from "path"; import { isNullOrUndefined } from "util"; import { URL } from "url"; import { OrderBook } from "./models/depth/OrderBook"; import { ApiError } from "./errors/ApiError"; import { Order } from "./models/order/Order"; import { CandlestickInterval } from "./enums/CandlestickInterval"; import { Candlestick } from "./models/candlestick/Candlestick"; import { TickerStatistics } from "./models/ticker/TickerStatistics"; import { LatestPrice } from "./models/depth/LatestPrice"; import { Ticker } from "./models/ticker/Ticker"; import { OrderSide } from "./enums/OrderSide"; import { OrderType } from "./enums/OrderType"; import { TimeInForce } from "./enums/TimeInForce"; import { OrderAcknowledgement } from "./models/order/OrderAcknowledgement"; import { CanceledOrderData } from "./models/order/CanceledOrderData"; import { AccountData } from "./models/account/AccountData"; import { Trade } from "./models/trade/Trade"; import * as WebSocket from "ws"; import { OrderBookUpdate } from "./models/websocket/depth/OrderBookUpdate"; import { CandlestickUpdate } from "./models/websocket/candlestick/CandlestickUpdate"; import { TradeUpdate } from "./models/websocket/trade/TradeUpdate"; import { AccountUpdate } from "./models/websocket/account/AccountUpdate"; import { OrderUpdate } from "./models/websocket/order/OrderUpdate"; import { ExchangeInfo } from "./models/misc/ExchangeInfo"; import { ResponseType } from "./enums/ResponseType"; import { OrderResult } from "./models/order/OrderResult"; import { OrderFull } from "./models/order/OrderFull"; import { HeartbeatHandler } from "websocket-heartbeats"; /** * Represents a single Binance API client. */ export class BinanceApiClient { private static readonly WS_BASE_URL: string = "wss://stream.binance.com:9443/ws/"; private static readonly DEFAULT_WS_TIMEOUT: number = 60000; private static API_KEY: string; private static API_SECRET: string; /** * Initializes a new Binance API client. * * @param apiKey The personal account API key. * @param apiSecret The personal account API secret. */ constructor(apiKey?: string, apiSecret?: string) { BinanceApiClient.API_KEY = apiKey; BinanceApiClient.API_SECRET = apiSecret; } /** * Interface to the "GET v1/ping" Binance's API operation. */ public async ping(): Promise<void> { await this.makeRequest( HttpMethod.GET, ApiVersion.V1, "ping", AuthenticationMethod.NONE ); } public async getExchangeInfo(): Promise<ExchangeInfo> { return new ExchangeInfo( await this.makeRequest( HttpMethod.GET, ApiVersion.V1, "exchangeInfo", AuthenticationMethod.NONE ) ); } /** * Interface to the "GET v1/time" Binance's API operation. * * @returns The Binance's server time. */ public async getServerTime(): Promise<Date> { return new Date( (await this.makeRequest( HttpMethod.GET, ApiVersion.V1, "time", AuthenticationMethod.NONE )).serverTime ); } /** * Interface to the "GET v1/depth" Binance's API operation. * * @param symbol The symbol for which we want to retrieve the order book. * @param limit The maximum number of orders in the returned order book. * * @returns The order book respecting the given constraints. */ public async getOrderBook( symbol: string, limit?: number ): Promise<OrderBook> { return new OrderBook( await this.makeRequest( HttpMethod.GET, ApiVersion.V1, "depth", AuthenticationMethod.NONE, ["symbol", symbol], ["limit", limit] ) ); } /** * Interface to the "GET v1/klines" Binance's API operation. Get * candlestick bars for the specified symbol, respecting * all the other given constraints. Candlesticks are uniquely * identified by their opening time. * * @param symbol The symbol for which we want to retrieve the * candlesticks. * @param interval The interval which the requested candlesticks refer to. * @param limit The maximum number of candlesticks returned. * @param startTime The time from which the candlesticks are returned. * @param endTime The time until which the candlesticks are returned. * * @returns A candlesticks array respecting the given constraints. */ public async getCandlesticks( symbol: string, interval: CandlestickInterval, limit?: number, startTime?: number, endTime?: number ): Promise<Candlestick[]> { const candlesticksJson: any = await this.makeRequest( HttpMethod.GET, ApiVersion.V1, "klines", AuthenticationMethod.NONE, ["symbol", symbol], ["interval", interval], ["limit", limit], ["startTime", startTime], ["endTime", endTime] ); const candlesticks: Candlestick[] = []; for (const candlestickJson of candlesticksJson) { candlesticks.push(new Candlestick(candlestickJson)); } return candlesticks; } /** * Interface to the "GET v1/ticker/24hr" Binance's API operation. * Get last 24 hours price change statistics. * * @param symbol The symbol for which we want to retrieve the * last day ticker statistics. * * @returns The last 24-hour ticker statistics. */ public async getLastDayTickerStatistics( symbol: string ): Promise<TickerStatistics> { return new TickerStatistics( await this.makeRequest( HttpMethod.GET, ApiVersion.V1, "ticker/24hr", AuthenticationMethod.NONE, ["symbol", symbol] ) ); } /** * Interface to the "GET v1/ticker/allPrices" Binance's API operation. * Get the latest price for all symbols. * * @returns A latest prices array for all the symbols. */ public async getLatestPrices(): Promise<LatestPrice[]> { const latestPricesJson: any = await this.makeRequest( HttpMethod.GET, ApiVersion.V1, "ticker/allPrices", AuthenticationMethod.NONE ); const latestPrices: LatestPrice[] = []; for (const latestPriceJson of latestPricesJson) { latestPrices.push(new LatestPrice(latestPriceJson)); } return latestPrices; } /** * Interface to the "GET v1/ticker/allBookTickers" Binance's API operation. * Get the best price/quantity in the order book for all symbols. * * @returns The best price/quantity in the order book for all symbols. */ public async getTickers(): Promise<Ticker[]> { const tickersJson: any = await this.makeRequest( HttpMethod.GET, ApiVersion.V1, "ticker/allBookTickers", AuthenticationMethod.NONE ); const tickers: Ticker[] = []; for (const tickerJson of tickersJson) { tickers.push(new Ticker(tickerJson)); } return tickers; } /** * Interface to the "POST v3/order" Binance's API operation. Places a new order * respecting the given constraints. * * @param symbol The market on which the order is to be placed. * @param side Whether the order is a buy or sell. * @param type Whether the order is at limit or market. * @param timeInForce Whether the time in force should be GTC or IOC. * @param quantity The quantity of assets that is to be moved. * @param price The price at which the order should be filled. * @param clientOrderId A unique ID associated with the order * (automatically generated if not sent). * @param stopPrice The price at which a stop order should be filled. * @param icebergQuantity Only used with iceberg orders. * @param responseType Set the response JSON. ACK, RESULT, or FULL; default: RESULT. * * @returns The just-placed order data. */ public async placeOrder( symbol: string, side: OrderSide, type: OrderType, timeInForce: TimeInForce, quantity: number, price: number, clientOrderId?: string, stopPrice?: number, icebergQuantity?: number, responseType?: ResponseType ): Promise<OrderAcknowledgement | OrderResult | OrderFull> { const jsonResponse: any = await this.makeRequest( HttpMethod.POST, ApiVersion.V3, "order", AuthenticationMethod.SIGNED, ["symbol", symbol], ["side", OrderSide[side]], ["type", OrderType[type]], [ "timeInForce", type === OrderType.MARKET || type === OrderType.STOP_LOSS ? null : TimeInForce[timeInForce] ], ["quantity", quantity], [ "price", type === OrderType.MARKET || type === OrderType.STOP_LOSS ? null : price ], ["newClientOrderId", clientOrderId], ["stopPrice", stopPrice], ["icebergQty", icebergQuantity], [ "newOrderRespType", isNullOrUndefined(responseType) ? null : ResponseType[responseType] ] ); switch (responseType) { case ResponseType.RESULT: { return new OrderResult(jsonResponse); } case ResponseType.FULL: { return new OrderFull(jsonResponse); } default: { return new OrderAcknowledgement(jsonResponse); } } } /** * Interface to the "POST v3/order/test" Binance's API operation. Places a new * test order respecting the given constraints. * * @param symbol The market on which the order is to be placed. * @param side Whether the order is a buy or sell. * @param type Whether the order is at limit or market. * @param timeInForce Whether the time in force should be GTC or IOC. * @param quantity The quantity of assets that is to be moved. * @param price The price at which the order should be filled. * @param clientId A unique ID associated with the order. * (automatically generated if not sent). * @param stopPrice The price at which a stop order should be filled. * @param icebergQuantity Only used with iceberg orders. * @param timeout The request validity maximum time frame * (defaults to 5000 ms). */ public async testOrder( symbol: string, side: OrderSide, type: OrderType, timeInForce: TimeInForce, quantity: number, price: number, clientId?: string, stopPrice?: number, icebergQuantity?: number, timeout?: number ): Promise<void> { await this.makeRequest( HttpMethod.POST, ApiVersion.V3, "order/test", AuthenticationMethod.SIGNED, ["symbol", symbol], ["side", OrderSide[side]], ["type", OrderType[type]], ["timeInForce", TimeInForce[timeInForce]], ["quantity", quantity], ["price", price], ["newClientOrderId", clientId], ["stopPrice", stopPrice], ["icebergQty", icebergQuantity], ["recvWindow", timeout] ); } /** * Interface to the "GET v3/order" Binance's API operation. Gets a * placed order detail given some constraints. * * @param symbol The market on which the order was originally placed. * @param id The wanted order ID. * @param clientId The wanted client given order ID (its description). * @param timeout The request validity maximum time frame * (defaults to 5000 ms). * * @return The placed order detail respecting the given constraints. */ public async getOrder( symbol: string, id?: number, clientId?: string, timeout?: number ): Promise<Order> { return new Order( await this.makeRequest( HttpMethod.GET, ApiVersion.V3, "order", AuthenticationMethod.SIGNED, ["symbol", symbol], ["orderId", id], ["origClientOrderId", clientId], ["recvWindow", timeout] ) ); } /** * Interface to the "DEconstE v3/order" Binance's API operation. * Cancels a previously placed order. * * @param symbol The market on which the order was originally placed. * @param id The wanted order ID. * @param oldClientId The pre-cancel client given order ID (its description). * @param newClientId The post-cancel order ID (automatically generated if not passed). * @param timeout The request validity maximum time frame * (defaults to 5000 ms). * * @return The just-canceled order data. */ public async cancelOrder( symbol: string, id?: number, oldClientId?: string, newClientId?: string, timeout?: number ): Promise<CanceledOrderData> { return new CanceledOrderData( await this.makeRequest( HttpMethod.DELETE, ApiVersion.V3, "order", AuthenticationMethod.SIGNED, ["symbol", symbol], ["orderId", id], ["origClientOrderId", oldClientId], ["newClientOrderId", newClientId], ["recvWindow", timeout] ) ); } /** * Interface to the "GET v3/openOrders" Binance's API operation. * * @param market The symbol for which we want to retrieve the open orders (if any). * @param timeout The request validity maximum time frame (defaults to 5000 ms). * * @returns An array representing all of the account's open orders. */ public async getOpenOrders( market?: string, timeout?: number ): Promise<Order[]> { const openOrdersJson: any = await this.makeRequest( HttpMethod.GET, ApiVersion.V3, "openOrders", AuthenticationMethod.SIGNED, ["symbol", market], ["recvWindow", timeout] ); const openOrders: Order[] = []; for (const openOrderJson of openOrdersJson) { openOrders.push(new Order(openOrderJson)); } return openOrders; } /** * Interface to the "GET v3/allOrders" Binance's API operation. Get all account * orders (active, canceled, or filled). * * @param symbol The symbol for which we want to retrieve the orders. * @param id The order ID from which we want to retrieve the orders * (if set, the API will retrieve the orders with an ID greater * or equal to the one specified, otherwise the most recent orders). * @param limit The maximum number of returned orders. * @param timeout The request validity maximum time frame (defaults to 5000 ms). * * @returns An array representing all of the account's orders in every state. */ public async getOrders( symbol: string, id?: number, limit?: number, timeout?: number ): Promise<Order[]> { const ordersJson: any = await this.makeRequest( HttpMethod.GET, ApiVersion.V3, "allOrders", AuthenticationMethod.SIGNED, ["symbol", symbol], ["orderId", id], ["limit", limit], ["recvWindow", timeout] ); const orders: Order[] = []; for (const orderJson of ordersJson) { orders.push(new Order(orderJson)); } return orders; } /** * Interface to the "GET v3/account" Binance's API operation. Get current * account information. * * @param timeout The request validity maximum time frame (defaults to 5000 ms). * * @returns The current account information. */ public async getAccountData(timeout?: number): Promise<AccountData> { return new AccountData( await this.makeRequest( HttpMethod.GET, ApiVersion.V3, "account", AuthenticationMethod.SIGNED, ["recvWindow", timeout] ) ); } /** * Interface to the "GET v3/myTrades" Binance's API operation. Get trades for * a specific account and symbol. * * @param symbol The market on which the trades were originally executed. * @param limit The maximum number of returned trades. * @param fromId The trade's ID to start fetching from. If not given, the * API will retrieve the most recent trades first. * @param timeout The request validity maximum time frame (defaults to 5000 ms). * * @returns The account's trade list respecting the given constraints. */ public async getTrades( symbol: string, limit?: number, fromId?: number, timeout?: number ): Promise<Trade[]> { const tradesJson: any = await this.makeRequest( HttpMethod.GET, ApiVersion.V3, "myTrades", AuthenticationMethod.SIGNED, ["symbol", symbol], ["limit", limit], ["fromId", fromId], ["recvWindow", timeout] ); const trades: Trade[] = []; for (const tradeJson of tradesJson) { trades.push(new Trade(tradeJson)); } return trades; } /** * Interface to the "POST v1/userDataStream" Binance's API operation. * Initializes a new data stream. * * @returns A listen key to be passed as a parameter when starting a * new data stream. */ public async openUserStream(): Promise<string> { return (await this.makeRequest( HttpMethod.POST, ApiVersion.V1, "userDataStream", AuthenticationMethod.API_KEY )).listenKey; } /** * Interface to the "PUT v1/userDataStream" Binance's API operation. * Pings a user data stream in order to prevent a time out. * * @param streamId A string representing the stream's ID * (returned by [[openUserStream]]). */ public async keepAliveUserStream(streamId: string): Promise<void> { await this.makeRequest( HttpMethod.PUT, ApiVersion.V1, "userDataStream", AuthenticationMethod.API_KEY, ["listenKey", streamId] ); } /** * Interface to the "DEconstE v1/userDataStream" Binance's API operation. * Closes out a user data stream. * * @param streamId A string representing the stream's ID * (returned by [[openUserStream]]). */ public async closeUserStream(streamId: string): Promise<void> { await this.makeRequest( HttpMethod.DELETE, ApiVersion.V1, "userDataStream", AuthenticationMethod.API_KEY, ["listenKey", streamId] ); } /** * Initializes a web socket data stream that gives us information about a * single symbol's order book updates. Stream keepalive is performed through * [[keepAliveUserStream]] following the rules described * [here](https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md) * * @param symbol The symbol of which we want to get the order book updates. * @param onUpdate A function to be called when a new update is received. * @param connectionTimeout Timeout based on which the web socket connection is * considered to be broken based on a heartbeat monitor. * @param onLostConnection A callback to be invoked when the web socket connection * is detected as broken. */ public monitorOrderBook( symbol: string, onUpdate: (update: OrderBookUpdate) => any, connectionTimeout: number, onLostConnection: () => any ): void { const websocket: WebSocket = new WebSocket( BinanceApiClient.WS_BASE_URL + symbol.toLowerCase() + "@depth", { perMessageDeflate: false } ); new HeartbeatHandler( websocket, isNullOrUndefined(connectionTimeout) ? BinanceApiClient.DEFAULT_WS_TIMEOUT : connectionTimeout, onLostConnection ).handle(); websocket.on("message", (data: any) => { onUpdate(new OrderBookUpdate(JSON.parse(data))); }); } /** * Initializes a web socket data stream that gives us information about * Kline/candlestick updates. Stream keepalive is performed through * [[keepAliveUserStream]] following the rules described * [here](https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md) * * @param symbol The symbol of which we want to get the candlestick updates. * @param interval The interval to which the requested candlestick updates * refer to. * @param onUpdate A function to be called when a new update is received. * @param connectionTimeout Timeout based on which the web socket connection is * considered to be broken based on a heartbeat monitor. * @param onLostConnection A callback to be invoked when the web socket connection * is detected as broken. */ public async monitorCandlesticks( symbol: string, interval: CandlestickInterval, onUpdate: (update: CandlestickUpdate) => any, connectionTimeout?: number, onLostConnection?: () => any ): Promise<void> { const websocket: WebSocket = new WebSocket( BinanceApiClient.WS_BASE_URL + symbol.toLowerCase() + "@kline_" + interval, { perMessageDeflate: false } ); new HeartbeatHandler( websocket, isNullOrUndefined(connectionTimeout) ? BinanceApiClient.DEFAULT_WS_TIMEOUT : connectionTimeout, onLostConnection ).handle(); websocket.on("message", (data: any) => { onUpdate(new CandlestickUpdate(JSON.parse(data))); }); } /** * Initializes a web socket data stream that gives us information about * trade updates. Stream keepalive is performed through * [[keepAliveUserStream]] following the rules described * [here](https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md) * * @param symbol The symbol of which we want to get the trade updates. * @param onUpdate A function to be called when a new update is received. * @param connectionTimeout Timeout based on which the web socket connection is * considered to be broken based on a heartbeat monitor. * @param onLostConnection A callback to be invoked when the web socket connection * is detected as broken. */ public monitorTrades( symbol: string, onUpdate: (update: TradeUpdate) => any, connectionTimeout: number, onLostConnection: () => any ): void { const websocket: WebSocket = new WebSocket( BinanceApiClient.WS_BASE_URL + symbol.toLowerCase() + "@aggTrade", { perMessageDeflate: false } ); new HeartbeatHandler( websocket, isNullOrUndefined(connectionTimeout) ? BinanceApiClient.DEFAULT_WS_TIMEOUT : connectionTimeout, onLostConnection ).handle(); websocket.on("message", (data: any) => { onUpdate(new TradeUpdate(JSON.parse(data))); }); } /** * Initializes a web socket data stream that gives us information about * the personal account updates. Stream keepalive is performed through * [[keepAliveUserStream]] following the rules described * [here](https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md) * * @param listenKey The listen key returned when a user data stream gets * initialized by [[openUserStream]]. * @param onUpdate A function to be called when a new update is received. * @param connectionTimeout Timeout based on which the web socket connection is * considered to be broken based on a heartbeat monitor. * @param onLostConnection A callback to be invoked when the web socket connection * is detected as broken. */ public monitorUser( listenKey: string, onUpdate: (update: AccountUpdate | OrderUpdate) => any, connectionTimeout: number, onLostConnection: () => any ): void { const websocket: WebSocket = new WebSocket( BinanceApiClient.WS_BASE_URL + listenKey, { perMessageDeflate: false } ); new HeartbeatHandler( websocket, isNullOrUndefined(connectionTimeout) ? BinanceApiClient.DEFAULT_WS_TIMEOUT : connectionTimeout, onLostConnection ).handle(); websocket.on("message", (data: any) => { const jsonData = JSON.parse(data); switch (jsonData.e) { case "outboundAccountInfo": { onUpdate(new AccountUpdate(jsonData)); break; } case "executionReport": { onUpdate(new OrderUpdate(jsonData)); break; } } }); } /** * Utility method that sets up and sends a request to the Binance's API, handling * the authentication through the API key and API secret parameters possibly given * when instantiating the client itself. * * @param httpMethod The HTTP method through which the specified API is accessed. * @param accessedResource The Binance's API resource that we would like to access. * @param apiVersion The API version at which the wanted resource can be accessed. * @param requiredAuthentication The authentication type required in order to access the * specified resource. * @param parameters The parameters which the accessed resource may use in order to * give us the expected result. * * @returns Either the promise of the Binance's API JSON response, or the * JSON response if using the await construct. */ private async makeRequest( httpMethod: HttpMethod, apiVersion: ApiVersion, accessedResource: string, requiredAuthentication: AuthenticationMethod, ...parameters: [string, any][] ): Promise<any> { const apiUrl: URL = new URL( Path.join("api", apiVersion, accessedResource), "https://api.binance.com" ); for (const parameter of parameters) { if (isNullOrUndefined(parameter[1])) { continue; } apiUrl.searchParams.append(parameter[0], parameter[1].toString()); } const headers: any = this.setupAuthentication( httpMethod, apiUrl, requiredAuthentication ); try { return await request({ method: HttpMethod[httpMethod], url: apiUrl.href, headers: headers, json: true }); } catch (error) { throw new ApiError(error.error.code, error.error.msg); } } /** * Utility method setting up the request in order to handle Binance's various * authentication methods. * * @param httpMethod The HTTP method used to access the wanted resource * (mainly used for error logging purposes). * @param apiUrl The URL at which the wanted resource can be accessed. * @param authenticationMethod The authentication method through which the wanted * resource can be accessed through the specified URL. */ private setupAuthentication( httpMethod: HttpMethod, apiUrl: URL, authenticationMethod: AuthenticationMethod ): any { const headers: any = {}; if (authenticationMethod === AuthenticationMethod.NONE) { return; } if (isNullOrUndefined(BinanceApiClient.API_KEY)) { throw new AuthenticationError( httpMethod, apiUrl, authenticationMethod ); } headers["X-MBX-APIKEY"] = BinanceApiClient.API_KEY; if (authenticationMethod === AuthenticationMethod.SIGNED) { if (isNullOrUndefined(BinanceApiClient.API_SECRET)) { throw new AuthenticationError( httpMethod, apiUrl, authenticationMethod ); } apiUrl.searchParams.append( "timestamp", new Date().getTime().toString() ); apiUrl.searchParams.append( "signature", CryptoJs.HmacSHA256( apiUrl.searchParams.toString(), BinanceApiClient.API_SECRET ).toString() ); } return headers; } }