package com.sigmaflare.binancej; import com.fasterxml.jackson.databind.JavaType; import com.sigmaflare.binancej.entities.Candlestick; import com.sigmaflare.binancej.entities.Interval; import com.sigmaflare.binancej.entities.OrderBookDepth; import com.sigmaflare.binancej.entities.ServiceError; import com.sigmaflare.binancej.entities.TickerPrice; import com.sigmaflare.binancej.exceptions.BinanceServiceException; import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; import com.sigmaflare.binancej.exceptions.UnexpectedErrorException; import lombok.Builder; import org.apache.http.HttpEntity; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static com.sigmaflare.binancej.Constant.NO_RESPONSE_TEXT; import static com.sigmaflare.binancej.Constant.NO_RESPONSE_TEXT_FORMATTED; import static com.sigmaflare.binancej.Constant.SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED; import static com.sigmaflare.binancej.HttpRequests.buildGetRequestFromEndpoint; public class MarketData extends BaseBinanceApi { private static final String ORDER_BOOK_URL = "/api/v1/depth"; private static final String CANDLESTICK_URL = "/api/v1/klines"; private static final String TICKER_PRICE_URL = "/api/v3/ticker/price"; private static final ArrayList LIMITS = new ArrayList<>(Arrays.asList(5, 10, 20, 50, 100, 500, 1000)); private static final String LIMIT_EXCEPTION_MESSAGE = "Limit must be in [5, 10, 20, 50, 100, 500, 1000]"; @Builder MarketData(String apiKey, String secretKey) { super(apiKey, secretKey); } MarketData(String apiKey, String secretKey, CloseableHttpClient closeableHttpClient) { super(apiKey, secretKey, closeableHttpClient); } private void checkCandlestickLimit(int limit) { if(limit < 0 || limit > 500) { throw new IllegalArgumentException("Limit must be greater than 0 and less than or equal to 500"); } } /** * Overloaded version of getOrderBookDepth that uses the default limit of 100 * * @param symbol The symbol * @return A populated OrderBookDepth if successful, otherwise an ServiceError * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable */ public OrderBookDepth getOrderBookDepth(String symbol) throws BinanceServiceException { return getOrderBookDepth(symbol, 100); } /** * Retrieves orderbook depth information * * @param symbol The symbol * @param limit The record limit (default: 100, maximum 1000) * @return A populated OrderBookDepth * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws UnexpectedErrorException If an unexpected exception occurs */ public OrderBookDepth getOrderBookDepth(String symbol, int limit) throws BinanceServiceException { if(!LIMITS.contains(limit)) { throw new IllegalArgumentException(LIMIT_EXCEPTION_MESSAGE); } String urlWithParams = String.format("%s?symbol=%s&limit=%d", ORDER_BOOK_URL, symbol, limit); final HttpGet request = buildGetRequestFromEndpoint(urlWithParams, apiKey); try { try (CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(request)) { StatusLine sl = closeableHttpResponse.getStatusLine(); HttpEntity httpEntity = closeableHttpResponse.getEntity(); if (httpEntity == null) { throw new BinanceServiceUnreachableException( String.format(NO_RESPONSE_TEXT_FORMATTED, request.getURI().toASCIIString()), null); } String response = EntityUtils.toString(httpEntity); if (!HttpRequests.statusCodeIsOk(sl.getStatusCode())) { ServiceError error = mapper.readValue(response, ServiceError.class); throw HttpRequests.buildHttpException(sl.getStatusCode(), error); } return mapper.readValue(response, OrderBookDepth.class); } } catch (IOException e) { throw new UnexpectedErrorException(e.getMessage(), e.getCause()); } } /** * Retrieves candlestick data for the supplied symbol and interval * * @param symbol The symbol * @param interval The interval * @return A list of candlesticks * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ public List getCandleStickData(String symbol, Interval interval) throws BinanceServiceException { if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); } String url = String.format( "%s?symbol=%s&interval=%s", CANDLESTICK_URL, symbol, interval.toString()); return getCandleStickDataFromUrl(url); } /** * Retrieves candlestick data for the supplied symbol and interval * * @param symbol The symbol * @param interval The interval * @param limit The output limit * @return A list of candlesticks if successful, otherwise an ServiceError * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ public List getCandleStickData(String symbol, Interval interval, int limit) throws BinanceServiceException { checkCandlestickLimit(limit); if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); } String url = String.format( "%s?symbol=%s&interval=%s&limit=%d", CANDLESTICK_URL, symbol, interval.toString(), limit); return getCandleStickDataFromUrl(url); } /** * Retrieves candlestick data for the supplied symbol and interval * * @param symbol The symbol * @param interval The interval * @param time The start/end time * @param isStartTime Indicates whether the time is a start or end time * @return A list of candlesticks * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ public List getCandleStickData(String symbol, Interval interval, long time, boolean isStartTime) throws BinanceServiceException { if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); } String url; if (isStartTime) { url = String.format( "%s?symbol=%s&interval=%s&startTime=%d", CANDLESTICK_URL, symbol, interval.toString(), time); } else { url = String.format( "%s?symbol=%s&interval=%s&endTime=%d", CANDLESTICK_URL, symbol, interval.toString(), time); } return getCandleStickDataFromUrl(url); } /** * Retrieves candlestick data for the supplied symbol and interval * * @param symbol The symbol * @param interval The interval * @param limit The output limit * @param time The timeframe * @param isStartTime indicates whether time is a startTime (true) or endTime (false) * @return A list of candlesticks * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ public List getCandleStickData(String symbol, Interval interval, int limit, long time, boolean isStartTime) throws BinanceServiceException { checkCandlestickLimit(limit); if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); } String url; if (isStartTime) { url = String.format( "%s?symbol=%s&interval=%s&limit=%d&startTime=%d", CANDLESTICK_URL, symbol, interval.toString(), limit, time); } else { url = String.format( "%s?symbol=%s&interval=%s&limit=%d&endTime=%d", CANDLESTICK_URL, symbol, interval.toString(), limit, time); } return getCandleStickDataFromUrl(url); } /** * Retrieves candlestick data for the supplied symbol and interval * * @param symbol The symbol * @param interval The interval * @param limit The output limit * @param startTime The start timeframe * @param endTime The end timeframe * @return A list of candlesticks * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ public List getCandleStickData(String symbol, Interval interval, int limit, long startTime, long endTime) throws BinanceServiceException { checkCandlestickLimit(limit); if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); } String url; url = String.format( "%s?symbol=%s&interval=%s&limit=%d&startTime=%d&endTime=%d", CANDLESTICK_URL, symbol, interval.toString(), limit, startTime, endTime); return getCandleStickDataFromUrl(url); } /** * Retrieves the current ticker price for the specified symbol * * @param symbol The symbol * @return A populated list of TickerPrice objects * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws UnexpectedErrorException If an unexpected exception occurs */ public TickerPrice getTickerPriceForSymbol(String symbol) throws BinanceServiceException { if (symbol == null) { throw new IllegalArgumentException("Symbol must not be null"); } String urlWithParams = String.format("%s?symbol=%s", TICKER_PRICE_URL, symbol); final HttpGet request = buildGetRequestFromEndpoint(urlWithParams, apiKey); try { try (CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(request)) { StatusLine sl = closeableHttpResponse.getStatusLine(); HttpEntity httpEntity = closeableHttpResponse.getEntity(); if (httpEntity == null) { throw new BinanceServiceUnreachableException( String.format(NO_RESPONSE_TEXT_FORMATTED, request.getURI().toASCIIString()), null); } String response = EntityUtils.toString(httpEntity); if (!HttpRequests.statusCodeIsOk(sl.getStatusCode())) { ServiceError error = mapper.readValue(response, ServiceError.class); throw HttpRequests.buildHttpException(sl.getStatusCode(), error); } return mapper.readValue(response, TickerPrice.class); } } catch (IOException e) { throw new UnexpectedErrorException(e.getMessage(), e.getCause()); } } /** * Retrieves ticker prices for all supported Binance symbols. This is good to use in terms of cost if you need * more than one symbol's current ticker price. * * @return A populated list of TickerPrice objects * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws UnexpectedErrorException If an unexpected exception occurs */ public List getTickerPrices() throws BinanceServiceException { final HttpGet request = buildGetRequestFromEndpoint(TICKER_PRICE_URL, apiKey); try { try (CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(request)) { StatusLine sl = closeableHttpResponse.getStatusLine(); HttpEntity httpEntity = closeableHttpResponse.getEntity(); if (httpEntity == null) { throw new BinanceServiceUnreachableException( String.format(NO_RESPONSE_TEXT_FORMATTED, request.getURI().toASCIIString()), null); } String response = EntityUtils.toString(httpEntity); if (!HttpRequests.statusCodeIsOk(sl.getStatusCode())) { ServiceError error = mapper.readValue(response, ServiceError.class); throw HttpRequests.buildHttpException(sl.getStatusCode(), error); } JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, TickerPrice.class); return mapper.readValue(response, type); } } catch (IOException e) { throw new UnexpectedErrorException(e.getMessage(), e.getCause()); } } /** * Gets candlestick data from the provided URL, allowing all of the getCandleStickData functions to act * as glorified URL builders. * * @param endpoint The endpoint to use that has the associated parameters populated * @return A list of candlesticks * @throws com.sigmaflare.binancej.exceptions.IpBannedException If the IP is banned * @throws com.sigmaflare.binancej.exceptions.RateLimitExceededException If the rate limit is exceeded * @throws com.sigmaflare.binancej.exceptions.MalformedRequestException If the user's request is malformed * @throws com.sigmaflare.binancej.exceptions.InternalServiceErrorException If the error occurs on Binance's side * @throws com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException If the service is unreachable * @throws UnexpectedErrorException If an unexpected exception occurs */ private List getCandleStickDataFromUrl(String endpoint) throws BinanceServiceException { final HttpGet request = buildGetRequestFromEndpoint(endpoint, apiKey); try { try (CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(request)) { StatusLine sl = closeableHttpResponse.getStatusLine(); HttpEntity httpEntity = closeableHttpResponse.getEntity(); if (httpEntity == null) { throw new BinanceServiceUnreachableException( String.format(NO_RESPONSE_TEXT_FORMATTED, request.getURI().toASCIIString()), null); } String response = EntityUtils.toString(httpEntity); if (!HttpRequests.statusCodeIsOk(sl.getStatusCode())) { ServiceError error = mapper.readValue(response, ServiceError.class); throw HttpRequests.buildHttpException(sl.getStatusCode(), error); } JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, Candlestick.class); return mapper.readValue(response, type); } } catch (IOException e) { throw new UnexpectedErrorException(e.getMessage(), e.getCause()); } } }