package com.sigmaflare.binancej; import com.codepoetics.ambivalence.Either; 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.BinanceServiceUnreachableException; import lombok.Builder; import lombok.extern.slf4j.Slf4j; 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.List; import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; 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; @Slf4j 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"; @Builder MarketData(String apiKey, String secretKey) { super(apiKey, secretKey); } MarketData(String apiKey, String secretKey, CloseableHttpClient closeableHttpClient) { super(apiKey, secretKey, closeableHttpClient); } /** * 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 BinanceServiceUnreachableException In the case the service is unreachable */ public Either getOrderBookDepth(String symbol) throws BinanceServiceUnreachableException { 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 if successful, otherwise an ServiceError * @throws BinanceServiceUnreachableException In the case the service is unreachable */ public Either getOrderBookDepth(String symbol, int limit) throws BinanceServiceUnreachableException { String url = String.format("%s%s?symbol=%s&limit=%d", BASE_ENDPOINT, ORDER_BOOK_URL, symbol, limit); final HttpGet request = Helpers.getBuilder(url, apiKey); try { try (CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(request)) { StatusLine sl = closeableHttpResponse.getStatusLine(); HttpEntity httpEntity = closeableHttpResponse.getEntity(); if (httpEntity == null) { log.error(NO_RESPONSE_TEXT, url); throw new BinanceServiceUnreachableException(String.format(NO_RESPONSE_TEXT_FORMATTED, url), null); } String response = EntityUtils.toString(httpEntity); if (!Helpers.statusCodeIsOk(sl.getStatusCode())) { return Either.ofLeft(mapper.readValue(response, ServiceError.class)); } return Either.ofRight(mapper.readValue(response, OrderBookDepth.class)); } } catch (IOException e) { throw new BinanceServiceUnreachableException(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 if successful, otherwise an ServiceError * @throws BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ public Either> getCandleStickData(String symbol, Interval interval) throws BinanceServiceUnreachableException { if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); } String url = String.format( "%s%s?symbol=%s&interval=%s", BASE_ENDPOINT, 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 BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ public Either> getCandleStickData(String symbol, Interval interval, int limit) throws BinanceServiceUnreachableException { if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); } String url = String.format( "%s%s?symbol=%s&interval=%s&limit=%d", BASE_ENDPOINT, 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 if successful, otherwise an ServiceError * @throws BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ public Either> getCandleStickData(String symbol, Interval interval, long time, boolean isStartTime) throws BinanceServiceUnreachableException { if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); } String url; if (isStartTime) { url = String.format( "%s%s?symbol=%s&interval=%s&startTime=%d", BASE_ENDPOINT, CANDLESTICK_URL, symbol, interval.toString(), time); } else { url = String.format( "%s%s?symbol=%s&interval=%s&endTime=%d", BASE_ENDPOINT, 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 if successful, otherwise an ServiceError * @throws BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ public Either> getCandleStickData(String symbol, Interval interval, int limit, long time, boolean isStartTime) throws BinanceServiceUnreachableException { if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); } String url; if (isStartTime) { url = String.format( "%s%s?symbol=%s&interval=%s&limit=%d&startTime=%d", BASE_ENDPOINT, CANDLESTICK_URL, symbol, interval.toString(), limit, time); } else { url = String.format( "%s%s?symbol=%s&interval=%s&limit=%d&endTime=%d", BASE_ENDPOINT, 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 if successful, otherwise an ServiceError * @throws BinanceServiceUnreachableException If the service is unreachable * @throws IllegalArgumentException If the required arguments symbol and interval are not supplied */ public Either> getCandleStickData(String symbol, Interval interval, int limit, long startTime, long endTime) throws BinanceServiceUnreachableException { if (symbol == null || interval == null) { throw new IllegalArgumentException(SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED); } String url; url = String.format( "%s%s?symbol=%s&interval=%s&limit=%d&startTime=%d&endTime=%d", BASE_ENDPOINT, 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 TickerPrice if successful, otherwise an ServiceError * @throws BinanceServiceUnreachableException If the service is unreachable */ public Either getTickerPriceForSymbol(String symbol) throws BinanceServiceUnreachableException { if (symbol == null) { throw new IllegalArgumentException("Symbol must not be null"); } String url = String.format("%s%s?symbol=%s", BASE_ENDPOINT, TICKER_PRICE_URL, symbol); final HttpGet request = Helpers.getBuilder(url, apiKey); try { try (CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(request)) { StatusLine sl = closeableHttpResponse.getStatusLine(); HttpEntity httpEntity = closeableHttpResponse.getEntity(); if (httpEntity == null) { log.error(NO_RESPONSE_TEXT, url); throw new BinanceServiceUnreachableException(String.format(NO_RESPONSE_TEXT_FORMATTED, url), null); } String response = EntityUtils.toString(httpEntity); if (!Helpers.statusCodeIsOk(sl.getStatusCode())) { return Either.ofLeft(mapper.readValue(response, ServiceError.class)); } return Either.ofRight(mapper.readValue(response, TickerPrice.class)); } } catch (IOException e) { throw new BinanceServiceUnreachableException(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 Either a list of TickerPrice objects if successful, or an ServiceError * @throws BinanceServiceUnreachableException If the service is unreachable */ public Either> getTickerPrices() throws BinanceServiceUnreachableException { String url = String.format("%s%s", BASE_ENDPOINT, TICKER_PRICE_URL); final HttpGet request = Helpers.getBuilder(url, apiKey); try { try (CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(request)) { StatusLine sl = closeableHttpResponse.getStatusLine(); HttpEntity httpEntity = closeableHttpResponse.getEntity(); if (httpEntity == null) { log.error(NO_RESPONSE_TEXT, url); throw new BinanceServiceUnreachableException(String.format(NO_RESPONSE_TEXT_FORMATTED, url), null); } String response = EntityUtils.toString(httpEntity); if (!Helpers.statusCodeIsOk(sl.getStatusCode())) { return Either.ofLeft(mapper.readValue(response, ServiceError.class)); } JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, TickerPrice.class); return Either.ofRight(mapper.readValue(response, type)); } } catch (IOException e) { throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); } } /** * Gets candlestick data from the provided URL, allowing all of the getCandleStickData functions to act * as glorified URL builders. * * @param url The URL to use * @return A list of candlesticks if successful, otherwise an ServiceError * @throws BinanceServiceUnreachableException If the service is unreachable */ private Either> getCandleStickDataFromUrl(String url) throws BinanceServiceUnreachableException { final HttpGet request = Helpers.getBuilder(url, apiKey); try { try (CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(request)) { StatusLine sl = closeableHttpResponse.getStatusLine(); HttpEntity httpEntity = closeableHttpResponse.getEntity(); if (httpEntity == null) { log.error(NO_RESPONSE_TEXT, url); throw new BinanceServiceUnreachableException(String.format(NO_RESPONSE_TEXT_FORMATTED, url), null); } String response = EntityUtils.toString(httpEntity); if (!Helpers.statusCodeIsOk(sl.getStatusCode())) { return Either.ofLeft(mapper.readValue(response, ServiceError.class)); } JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, Candlestick.class); return Either.ofRight(mapper.readValue(response, type)); } } catch (IOException e) { throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); } } }