Taylor Bockman
7 years ago
committed by
GitHub
51 changed files with 2697 additions and 42 deletions
@ -0,0 +1,42 @@
|
||||
# Java Gradle CircleCI 2.0 configuration file |
||||
# |
||||
# Check https://circleci.com/docs/2.0/language-java/ for more details |
||||
# |
||||
version: 2 |
||||
jobs: |
||||
build: |
||||
docker: |
||||
# specify the version you desire here |
||||
- image: circleci/openjdk:8-jdk |
||||
|
||||
# Specify service dependencies here if necessary |
||||
# CircleCI maintains a library of pre-built images |
||||
# documented at https://circleci.com/docs/2.0/circleci-images/ |
||||
# - image: circleci/postgres:9.4 |
||||
|
||||
working_directory: ~/repo |
||||
|
||||
environment: |
||||
# Customize the JVM maximum heap limit |
||||
JVM_OPTS: -Xmx3200m |
||||
TERM: dumb |
||||
|
||||
steps: |
||||
- checkout |
||||
|
||||
# Download and cache dependencies |
||||
- restore_cache: |
||||
keys: |
||||
- v1-dependencies-{{ checksum "build.gradle" }} |
||||
# fallback to using the latest cache if no exact match is found |
||||
- v1-dependencies- |
||||
|
||||
- run: gradle dependencies |
||||
|
||||
- save_cache: |
||||
paths: |
||||
- ~/.gradle |
||||
key: v1-dependencies-{{ checksum "build.gradle" }} |
||||
|
||||
# run tests! |
||||
- run: gradle test |
@ -0,0 +1,32 @@
|
||||
Your issue may already be reported! |
||||
Please search on the [issue track](../) before creating one. |
||||
|
||||
## Expected Behavior |
||||
<!--- If you're describing a bug, tell us what should happen --> |
||||
<!--- If you're suggesting a change/improvement, tell us how it should work --> |
||||
|
||||
## Current Behavior |
||||
<!--- If describing a bug, tell us what happens instead of the expected behavior --> |
||||
<!--- If suggesting a change/improvement, explain the difference from current behavior --> |
||||
|
||||
## Possible Solution |
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, --> |
||||
<!--- or ideas how to implement the addition or change --> |
||||
|
||||
## Steps to Reproduce (for bugs) |
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to --> |
||||
<!--- reproduce this bug. Include code to reproduce, if relevant --> |
||||
1. |
||||
2. |
||||
3. |
||||
4. |
||||
|
||||
## Context |
||||
<!--- How has this issue affected you? What are you trying to accomplish? --> |
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world --> |
||||
|
||||
## Your Environment |
||||
<!--- Include as many relevant details about the environment you experienced the bug in --> |
||||
* Version used: |
||||
* Operating System and version (desktop or mobile): |
||||
* Link to your project: |
@ -0,0 +1,27 @@
|
||||
<!--- Provide a general summary of your changes in the Title above --> |
||||
|
||||
## Description |
||||
<!--- Describe your changes in detail --> |
||||
|
||||
## Motivation and Context |
||||
<!--- Why is this change required? What problem does it solve? --> |
||||
<!--- If it fixes an open issue, please link to the issue here. --> |
||||
|
||||
## How Has This Been Tested? |
||||
<!--- Please describe in detail how you tested your changes. --> |
||||
<!--- Include details of your testing environment, tests ran to see how --> |
||||
<!--- your change affects other areas of the code, etc. --> |
||||
|
||||
## Types of changes |
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> |
||||
- [ ] Bug fix (non-breaking change which fixes an issue) |
||||
- [ ] New feature (non-breaking change which adds functionality) |
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) |
||||
|
||||
## Checklist: |
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. --> |
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> |
||||
- [ ] My code follows the code style of this project. |
||||
- [ ] My change requires a change to the documentation. |
||||
- [ ] I have updated the documentation accordingly. |
||||
- [ ] I have updated the version of the library as required per the specifications of [Semantic Versioning](https://semver.org/) |
@ -0,0 +1,86 @@
|
||||
# Contributing to the BinanceJ Project |
||||
|
||||
### Welcome to BinanceJ! |
||||
|
||||
The goal of this project is to provide the de-facto standard Binance API in Java. |
||||
|
||||
--- |
||||
#### Sections |
||||
|
||||
> * [Getting Started](#getting-started) |
||||
> * [Testing](#testing) |
||||
> * [Code Standards](#code-standards) |
||||
> * [Versioning](#versioning) |
||||
> * [Submitting Pull Requests](#submitting-pull-requests) |
||||
> * [Submitting Issues](#submitting-issues) |
||||
|
||||
--- |
||||
|
||||
## Getting Started |
||||
|
||||
### Overall Requirements |
||||
|
||||
* Java 8 |
||||
* Gradle |
||||
* [Sonarlint](https://www.sonarlint.org/) |
||||
|
||||
It is highly suggested you use something like Intellij IDEA when developing this project though |
||||
there is no strict enforcement on that. |
||||
|
||||
To get started with the project, do as follows: |
||||
|
||||
```sh |
||||
git clone git@github.com:angrygoats/binancej.git |
||||
cd binancej |
||||
gradle clean && gradle build |
||||
``` |
||||
|
||||
## Testing |
||||
|
||||
We operate a strict TDD shop here. As a result, any PR submitted without tests for anything but _the most trivial_ of |
||||
features will be rejected immediately. 100% code coverage is not necessary (but desirable where possible). You should |
||||
aim to cover all major conditions your code can go through thoroughly. |
||||
|
||||
The tools we primarily use are Junit4 and Mockito. Since the API is so simple this all we really need. |
||||
|
||||
To run the tests, simple run: |
||||
|
||||
```sh |
||||
gradle test |
||||
``` |
||||
|
||||
from the root of the project. |
||||
|
||||
|
||||
## Code Standards |
||||
|
||||
Clean Java is good Java. As all of us know Java can get unnecessarily verbose at times, and in order to keep readability |
||||
in view at all times good code standards should be followed: |
||||
|
||||
1. Run Sonarlint and follow it's suggestions |
||||
2. 4 spaces (not tabs) |
||||
3. Keep a space between each member of a class for readability |
||||
4. Javadoc every function you write with the exception of already documented functions you are `@Override`'ing |
||||
5. Javadoc classes that are not obvious from their name |
||||
6. __Absolutely no wild card importing__ |
||||
7. Clean up your imports - do not leave unused imports in any code you write |
||||
8. You should make an effort to clean up all code you touch if the clean up will not take too long |
||||
* Any thing that will take long should be made into an issue |
||||
9. Make use of Lombok wherever you can to reduce boilerplate as much as possible |
||||
10. Take advantage of Java 8 features such as streams to simplify your code where possible |
||||
11. If you are using IntelliJ, take advantage of code formatting under `Code -> Reformat Code` |
||||
|
||||
## Versioning |
||||
|
||||
The BinanceJ project uses [Semantic Versioning](https://semver.org/). In the `build.gradle` you will find a line |
||||
to adjust the semantic versioning of the library. |
||||
|
||||
## Submitting Pull Requests |
||||
|
||||
Please be sure to follow the supplied pull request template. PRs will be rejected if they lack tests or the test |
||||
coverage isn't sufficient. |
||||
|
||||
## Submitting Issues |
||||
|
||||
Please be sure to follow the supplied issue template. If the template does not contain fields you need, feel free |
||||
to add them if they provide more context to the issue you are experiencing. |
@ -0,0 +1,21 @@
|
||||
MIT License |
||||
|
||||
Copyright (c) 2018 Taylor Bockman |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -1,17 +0,0 @@
|
||||
package com.sigmaflare; |
||||
|
||||
|
||||
public class Binance { |
||||
private final String apiKey; |
||||
private final String secretKey; |
||||
private static final String BASE_ENDPOINT = "https://api.binance.com"; |
||||
|
||||
public Binance(String apiKey, String secretKey) { |
||||
this.apiKey = apiKey; |
||||
this.secretKey = secretKey; |
||||
} |
||||
|
||||
public boolean someLibraryMethod() { |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,38 @@
|
||||
package com.sigmaflare.binancej; |
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.apache.http.impl.client.HttpClientBuilder; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
public abstract class BaseBinanceApi { |
||||
protected final String apiKey; |
||||
protected final String secretKey; |
||||
protected final CloseableHttpClient closeableHttpClient; |
||||
|
||||
protected static final ObjectMapper mapper = Helpers.objectMapperBuilder(); |
||||
|
||||
public BaseBinanceApi(String apiKey, String secretKey) { |
||||
this.apiKey = apiKey; |
||||
this.secretKey = secretKey; |
||||
this.closeableHttpClient = HttpClientBuilder.create().build(); |
||||
} |
||||
|
||||
public BaseBinanceApi(String apiKey, String secretKey, CloseableHttpClient closeableHttpClient) { |
||||
this.apiKey = apiKey; |
||||
this.secretKey = secretKey; |
||||
this.closeableHttpClient = closeableHttpClient; |
||||
} |
||||
|
||||
|
||||
public void close() throws IOException { |
||||
closeableHttpClient.close(); |
||||
} |
||||
|
||||
@Override |
||||
protected void finalize() throws IOException { |
||||
close(); |
||||
} |
||||
} |
@ -0,0 +1,11 @@
|
||||
package com.sigmaflare.binancej; |
||||
|
||||
final class Constant { |
||||
private Constant() {} |
||||
|
||||
static final String BASE_ENDPOINT = "https://api.binance.com"; |
||||
|
||||
static final String NO_RESPONSE_TEXT = "No response returned from {}"; |
||||
static final String NO_RESPONSE_TEXT_FORMATTED = "No response returned from %s"; |
||||
static final String SYMBOL_AND_INTERVAL_MUST_BE_SUPPLIED = "Symbol and interval must be supplied"; |
||||
} |
@ -0,0 +1,148 @@
|
||||
package com.sigmaflare.binancej; |
||||
|
||||
import com.codepoetics.ambivalence.Either; |
||||
import com.sigmaflare.binancej.entities.ExchangeInfo; |
||||
import com.sigmaflare.binancej.entities.Ping; |
||||
import com.sigmaflare.binancej.entities.ServiceError; |
||||
import com.sigmaflare.binancej.entities.Time; |
||||
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 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; |
||||
|
||||
/** |
||||
* GeneralUtilities is a container class for methods that interact with the infrastructure |
||||
* on the Binance side. Methods like server health and exchange information are found here. |
||||
*/ |
||||
@Slf4j |
||||
public class GeneralUtilities extends BaseBinanceApi { |
||||
private static final String PING_URL = "/api/v1/ping"; |
||||
private static final String TIME_URL = "/api/v1/time"; |
||||
private static final String EXCHANGE_INFO_URL = "/api/v1/exchangeInfo"; |
||||
|
||||
@Builder |
||||
public GeneralUtilities(String apiKey, String secretKey) { |
||||
super(apiKey, secretKey); |
||||
} |
||||
|
||||
GeneralUtilities(String apiKey, String secretKey, CloseableHttpClient closeableHttpClient) { |
||||
super(apiKey, secretKey, closeableHttpClient); |
||||
} |
||||
|
||||
/** |
||||
* Hits the ping endpoint to check if the service is alive. |
||||
* |
||||
* @return empty Ping object if it returned 200, otherwise ServiceError |
||||
*/ |
||||
public Either<ServiceError, Ping> ping() throws BinanceServiceUnreachableException { |
||||
final String url = String.format("%s%s", BASE_ENDPOINT, PING_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)); |
||||
} |
||||
|
||||
return Either.ofRight(new Ping()); |
||||
} |
||||
} catch (IOException e) { |
||||
throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Gets the current server time on Binance's servers |
||||
* |
||||
* @return A Time object if successful, otherwise an ServiceError object |
||||
* @throws BinanceServiceUnreachableException Throws when the request fails |
||||
*/ |
||||
public Either<ServiceError, Time> getServerTime() throws BinanceServiceUnreachableException { |
||||
final String url = String.format("%s%s", BASE_ENDPOINT, TIME_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)); |
||||
} |
||||
|
||||
return Either.ofRight(mapper.readValue(response, Time.class)); |
||||
} |
||||
} catch (IOException e) { |
||||
throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Retrieves exchange information |
||||
* |
||||
* @return An ExchangeInfo when successful, otherwise an ServiceError |
||||
* @throws BinanceServiceUnreachableException If the service cannot be reached |
||||
*/ |
||||
public Either<ServiceError, ExchangeInfo> getExchangeInfo() throws BinanceServiceUnreachableException { |
||||
final String url = String.format("%s%s", BASE_ENDPOINT, EXCHANGE_INFO_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)); |
||||
} |
||||
|
||||
return Either.ofRight(mapper.readValue(response, ExchangeInfo.class)); |
||||
} |
||||
} catch (IOException e) { |
||||
throw new BinanceServiceUnreachableException(e.getMessage(), e.getCause()); |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,87 @@
|
||||
package com.sigmaflare.binancej; |
||||
|
||||
|
||||
import com.codepoetics.ambivalence.LeftProjection; |
||||
import com.codepoetics.ambivalence.RightProjection; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import org.apache.http.client.methods.HttpGet; |
||||
import org.apache.http.client.methods.HttpPost; |
||||
|
||||
import java.util.function.Function; |
||||
|
||||
public final class Helpers { |
||||
private Helpers() { |
||||
} |
||||
|
||||
/** |
||||
* Checks to insure the status code is ok. |
||||
* |
||||
* @param statusCode The status code |
||||
* @return True of the status code is acceptable (200-399), and false otherwise |
||||
*/ |
||||
public static boolean statusCodeIsOk(int statusCode) { |
||||
return statusCode >= 200 && statusCode < 400; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Builds a configured HttpGet |
||||
* |
||||
* @param url The full URL to GET to |
||||
* @param apiKey The API key |
||||
* @return A configured HttpGet object |
||||
*/ |
||||
public static HttpGet getBuilder(String url, String apiKey) { |
||||
HttpGet httpGet = new HttpGet(url); |
||||
httpGet.setHeader("X-MBX-APIKEY", apiKey); |
||||
return httpGet; |
||||
} |
||||
|
||||
/** |
||||
* Builds a configured HttpPost |
||||
* |
||||
* @param url The full URL to POST to |
||||
* @param apiKey The API key |
||||
* @return A configured HttpPost object |
||||
*/ |
||||
public static HttpPost postBuilder(String url, String apiKey) { |
||||
HttpPost httpPost = new HttpPost(url); |
||||
httpPost.setHeader("X-MBX-APIKEY", apiKey); |
||||
return httpPost; |
||||
} |
||||
|
||||
/** |
||||
* Builds a fully configured ObjectMapper |
||||
* |
||||
* @return A fully configured ObjectMapper |
||||
*/ |
||||
public static ObjectMapper objectMapperBuilder() { |
||||
return new ObjectMapper().findAndRegisterModules(); |
||||
} |
||||
|
||||
/** |
||||
* Safely extracts the value from a LeftProjection of an Either |
||||
* |
||||
* @param val The LeftProjection to perform the extraction on |
||||
* @param <L> The Left type |
||||
* @param <R> The Right type |
||||
* @return The unwrapped L type object |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public static <L, R> L extractEitherValueSafely(LeftProjection<L, R> val) { |
||||
return (L) val.join(Function.identity(), Function.identity()); |
||||
} |
||||
|
||||
/** |
||||
* Safely extracts the value from a RightProjection of an Either |
||||
* |
||||
* @param val The RightProjection to perform the extraction on |
||||
* @param <L> The Left type |
||||
* @param <R> The Right type |
||||
* @return The unwrapped R type object |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public static <L, R> R extractEitherValueSafely(RightProjection<L, R> val) { |
||||
return (R) val.join(Function.identity(), Function.identity()); |
||||
} |
||||
} |
@ -0,0 +1,382 @@
|
||||
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<ServiceError, OrderBookDepth> 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<ServiceError, OrderBookDepth> 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<ServiceError, List<Candlestick>> 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<ServiceError, List<Candlestick>> 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<ServiceError, List<Candlestick>> |
||||
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<ServiceError, List<Candlestick>> |
||||
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<ServiceError, List<Candlestick>> |
||||
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<ServiceError, TickerPrice> 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<ServiceError, List<TickerPrice>> 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<ServiceError, List<Candlestick>> |
||||
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()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,43 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; |
||||
import com.sigmaflare.binancej.entities.transform.CandlestickDeserializer; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
|
||||
import java.math.BigDecimal; |
||||
|
||||
/** |
||||
* Represents a single candlestick/kline. No JsonProperty information is stored because we use a |
||||
* custom serializer to clean up the data that's given to us from the endpoint. |
||||
*/ |
||||
@Data |
||||
@Builder |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
@JsonDeserialize(using = CandlestickDeserializer.class) |
||||
public class Candlestick { |
||||
private long openTime; |
||||
|
||||
private BigDecimal open; |
||||
|
||||
private BigDecimal high; |
||||
|
||||
private BigDecimal low; |
||||
|
||||
private BigDecimal close; |
||||
|
||||
private BigDecimal volume; |
||||
|
||||
private long closeTime; |
||||
|
||||
private BigDecimal quoteAssetVolume; |
||||
|
||||
private long numberOfTrades; |
||||
|
||||
private BigDecimal takerBuyBaseAssetVolume; |
||||
|
||||
private BigDecimal takerBuyQuoteAssetVolume; |
||||
} |
@ -0,0 +1,7 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
/** |
||||
* The documents do not make it clear what is in this object so it is left blank. |
||||
*/ |
||||
public class ExchangeFilter { |
||||
} |
@ -0,0 +1,37 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
import lombok.NonNull; |
||||
|
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
@Data |
||||
@Builder |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
public class ExchangeInfo { |
||||
@NonNull |
||||
@JsonProperty("timezone") |
||||
private String timezone; |
||||
|
||||
@JsonProperty("serverTime") |
||||
private Long serverTime; |
||||
|
||||
@NonNull |
||||
@JsonProperty("rateLimits") |
||||
private Set<RateLimit> rateLimits; |
||||
|
||||
@NonNull |
||||
@JsonProperty("exchangeFilters") |
||||
private Set<ExchangeFilter> exchangeFilters; |
||||
|
||||
@NonNull |
||||
@JsonProperty("symbols") |
||||
private List<Symbol> symbols; |
||||
} |
@ -0,0 +1,34 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
public enum Interval { |
||||
ONE_MINUTE("1m"), |
||||
THREE_MINUTES("3m"), |
||||
FIVE_MINUTES("5m"), |
||||
FIFTEEN_MINUTES("15m"), |
||||
THIRTY_MINUTES("30m"), |
||||
ONE_HOUR("1h"), |
||||
TWO_HOURS("2h"), |
||||
FOUR_HOURS("4h"), |
||||
SIX_HOURS("6h"), |
||||
EIGHT_HOURS("8h"), |
||||
TWELVE_HOURS("12h"), |
||||
ONE_DAY("1d"), |
||||
THREE_DAYS("3d"), |
||||
ONE_WEEK("1w"), |
||||
ONE_MONTH("1M"); |
||||
|
||||
private final String representation; |
||||
|
||||
/** |
||||
* Constructor |
||||
* @param representation The string representation of the time interval to construct |
||||
*/ |
||||
Interval(String representation) { |
||||
this.representation = representation; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return representation; |
||||
} |
||||
} |
@ -0,0 +1,30 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
import com.fasterxml.jackson.annotation.JsonTypeName; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
import lombok.NoArgsConstructor; |
||||
import lombok.NonNull; |
||||
|
||||
@Data |
||||
@Builder |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
@EqualsAndHashCode(callSuper = false) |
||||
@JsonTypeName("LOT_SIZE") |
||||
public class LotSizeFilter extends SymbolFilter { |
||||
@NonNull |
||||
@JsonProperty("minQty") |
||||
private String minQty; |
||||
|
||||
@NonNull |
||||
@JsonProperty("maxQty") |
||||
private String maxQty; |
||||
|
||||
@NonNull |
||||
@JsonProperty("stepSize") |
||||
private String stepSize; |
||||
} |
@ -0,0 +1,22 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
import com.fasterxml.jackson.annotation.JsonTypeName; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
import lombok.NoArgsConstructor; |
||||
import lombok.NonNull; |
||||
|
||||
@Data |
||||
@Builder |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
@JsonTypeName("MIN_NOTIONAL") |
||||
@EqualsAndHashCode(callSuper = false) |
||||
public class MinNotionalFilter extends SymbolFilter { |
||||
@NonNull |
||||
@JsonProperty("minNotional") |
||||
private String minNotional; |
||||
} |
@ -0,0 +1,29 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; |
||||
import com.sigmaflare.binancej.entities.transform.OrderBookDepthResponseDeserializer; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Represents the Order book depth for Binance's market. |
||||
* |
||||
* Order book depth is a mess coming from Binance's API, so this is handled purely with |
||||
* a custom deserializer to make using the resulting POJOs easier |
||||
*/ |
||||
@Data |
||||
@Builder |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
@JsonDeserialize(using = OrderBookDepthResponseDeserializer.class) |
||||
public class OrderBookDepth { |
||||
private Long lastUpdateId; |
||||
|
||||
private List<OrderBookPricing> bids; |
||||
|
||||
private List<OrderBookPricing> asks; |
||||
} |
@ -0,0 +1,18 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
|
||||
import java.math.BigDecimal; |
||||
|
||||
@Data |
||||
@Builder |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
public class OrderBookPricing { |
||||
private BigDecimal price; |
||||
|
||||
private BigDecimal quantity; |
||||
} |
@ -0,0 +1,11 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
public enum OrderType { |
||||
LIMIT, |
||||
MARKET, |
||||
STOP_LOSS, |
||||
STOP_LOSS_LIMIT, |
||||
TAKE_PROFIT, |
||||
TAKE_PROFIT_LIMIT, |
||||
LIMIT_MAKER |
||||
} |
@ -0,0 +1,7 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
/** |
||||
* Ping doesn't return anything, but we need to have this object to keep the API consistent. |
||||
*/ |
||||
public class Ping { |
||||
} |
@ -0,0 +1,30 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
import com.fasterxml.jackson.annotation.JsonTypeName; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
import lombok.NoArgsConstructor; |
||||
import lombok.NonNull; |
||||
|
||||
@Data |
||||
@Builder |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
@JsonTypeName("PRICE_FILTER") |
||||
@EqualsAndHashCode(callSuper = false) |
||||
public class PriceFilter extends SymbolFilter { |
||||
@NonNull |
||||
@JsonProperty("minPrice") |
||||
private String minPrice; |
||||
|
||||
@NonNull |
||||
@JsonProperty("maxPrice") |
||||
private String maxPrice; |
||||
|
||||
@NonNull |
||||
@JsonProperty("tickSize") |
||||
private String tickSize; |
||||
} |
@ -0,0 +1,23 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
import lombok.NonNull; |
||||
|
||||
@Data |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
public class RateLimit { |
||||
@NonNull |
||||
@JsonProperty("rateLimitType") |
||||
private RateLimitType rateLimitType; |
||||
|
||||
@NonNull |
||||
@JsonProperty("interval") |
||||
private RateLimitInterval rateLimitInterval; |
||||
|
||||
@JsonProperty("limit") |
||||
private Long limit; |
||||
} |
@ -0,0 +1,7 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
public enum RateLimitInterval { |
||||
SECOND, |
||||
MINUTE, |
||||
DAY |
||||
} |
@ -0,0 +1,6 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
public enum RateLimitType { |
||||
REQUESTS, |
||||
ORDERS |
||||
} |
@ -0,0 +1,21 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
import lombok.NonNull; |
||||
|
||||
@Data |
||||
@Builder |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
public class ServiceError { |
||||
@JsonProperty("code") |
||||
public int code; |
||||
|
||||
@NonNull |
||||
@JsonProperty("msg") |
||||
public String message; |
||||
} |
@ -0,0 +1,51 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
import lombok.NonNull; |
||||
|
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
@Data |
||||
@Builder |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
public class Symbol { |
||||
@NonNull |
||||
@JsonProperty("symbol") |
||||
private String ticker; |
||||
|
||||
@NonNull |
||||
@JsonProperty("status") |
||||
private SymbolStatus symbolStatus; |
||||
|
||||
@NonNull |
||||
@JsonProperty("baseAsset") |
||||
private String baseAsset; |
||||
|
||||
@JsonProperty("baseAssetPrecision") |
||||
private int baseAssetPrecision; |
||||
|
||||
@NonNull |
||||
@JsonProperty("quoteAsset") |
||||
private String quoteAsset; |
||||
|
||||
@JsonProperty("quotePrecision") |
||||
private int quotePrecision; |
||||
|
||||
@NonNull |
||||
@JsonProperty("orderTypes") |
||||
private Set<OrderType> orderTypes; |
||||
|
||||
@JsonProperty("icebergAllowed") |
||||
private boolean icebergAllowed; |
||||
|
||||
@NonNull |
||||
@JsonProperty("filters") |
||||
private List<SymbolFilter> symbolFilters; |
||||
} |
@ -0,0 +1,22 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
import com.fasterxml.jackson.annotation.JsonSubTypes; |
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
||||
import lombok.NonNull; |
||||
|
||||
// The "property" here refers to an identifying feature of the JSON that will be used to
|
||||
// match to a @JsonTypeName
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "filterType") |
||||
@JsonSubTypes({ |
||||
@JsonSubTypes.Type(value = PriceFilter.class, name = "PRICE_FILTER"), |
||||
@JsonSubTypes.Type(value = LotSizeFilter.class, name = "LOT_SIZE"), |
||||
@JsonSubTypes.Type(value = MinNotionalFilter.class, name = "MIN_NOTIONAL") |
||||
}) |
||||
public abstract class SymbolFilter { |
||||
// This seems like an enum, but the entire space of values is not enumerated
|
||||
// so it is left as a string for now
|
||||
@NonNull |
||||
@JsonProperty("filterType") |
||||
private String filterType; |
||||
} |
@ -0,0 +1,11 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
public enum SymbolStatus { |
||||
PRE_TRADING, |
||||
TRADING, |
||||
POST_TRADING, |
||||
END_OF_DAY, |
||||
HALT, |
||||
AUCTION_MATCH, |
||||
BREAK |
||||
} |
@ -0,0 +1,24 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
import lombok.NonNull; |
||||
|
||||
import java.math.BigDecimal; |
||||
|
||||
@Data |
||||
@Builder |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
public class TickerPrice { |
||||
@NonNull |
||||
@JsonProperty("symbol") |
||||
private String symbol; |
||||
|
||||
@NonNull |
||||
@JsonProperty("price") |
||||
private BigDecimal price; |
||||
} |
@ -0,0 +1,16 @@
|
||||
package com.sigmaflare.binancej.entities; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Builder; |
||||
import lombok.Data; |
||||
import lombok.NoArgsConstructor; |
||||
|
||||
@Data |
||||
@Builder |
||||
@AllArgsConstructor |
||||
@NoArgsConstructor |
||||
public class Time { |
||||
@JsonProperty("serverTime") |
||||
private long serverTime; |
||||
} |
@ -0,0 +1,46 @@
|
||||
package com.sigmaflare.binancej.entities.transform; |
||||
|
||||
import com.fasterxml.jackson.core.JsonParser; |
||||
import com.fasterxml.jackson.databind.DeserializationContext; |
||||
import com.fasterxml.jackson.databind.JsonNode; |
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; |
||||
import com.sigmaflare.binancej.entities.Candlestick; |
||||
|
||||
import java.io.IOException; |
||||
import java.math.BigDecimal; |
||||
|
||||
/** |
||||
* A custom deserializer for OrderBookDepth. Binance returns some non-standard looking data |
||||
* in it's array, so we convert the entire thing into our own representation here to make |
||||
* it easier to use. |
||||
*/ |
||||
public class CandlestickDeserializer extends StdDeserializer<Candlestick> { |
||||
|
||||
public CandlestickDeserializer() { |
||||
this(null); |
||||
} |
||||
|
||||
public CandlestickDeserializer(Class<?> vc) { |
||||
super(vc); |
||||
} |
||||
|
||||
@Override |
||||
public Candlestick deserialize(JsonParser jp, DeserializationContext context) throws IOException { |
||||
JsonNode n = jp.getCodec().readTree(jp); |
||||
|
||||
return Candlestick |
||||
.builder() |
||||
.openTime(n.get(0).asLong()) |
||||
.open(new BigDecimal(n.get(1).asText())) |
||||
.high(new BigDecimal(n.get(2).asText())) |
||||
.low(new BigDecimal(n.get(3).asText())) |
||||
.close(new BigDecimal(n.get(4).asText())) |
||||
.volume(new BigDecimal(n.get(5).asText())) |
||||
.closeTime(n.get(6).asLong()) |
||||
.quoteAssetVolume(new BigDecimal(n.get(7).asText())) |
||||
.numberOfTrades(n.get(8).asLong()) |
||||
.takerBuyBaseAssetVolume(new BigDecimal(n.get(9).asText())) |
||||
.takerBuyQuoteAssetVolume(new BigDecimal(n.get(10).asText())) |
||||
.build(); |
||||
} |
||||
} |
@ -0,0 +1,66 @@
|
||||
package com.sigmaflare.binancej.entities.transform; |
||||
|
||||
import com.fasterxml.jackson.core.JsonParser; |
||||
import com.fasterxml.jackson.databind.DeserializationContext; |
||||
import com.fasterxml.jackson.databind.JsonNode; |
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; |
||||
import com.sigmaflare.binancej.entities.OrderBookDepth; |
||||
import com.sigmaflare.binancej.entities.OrderBookPricing; |
||||
|
||||
import java.io.IOException; |
||||
import java.math.BigDecimal; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* A custom deserializer for OrderBookDepth. Binance returns some non-standard looking data |
||||
* in it's array, so we convert the entire thing into our own representation here to make |
||||
* it easier to use. |
||||
*/ |
||||
public class OrderBookDepthResponseDeserializer extends StdDeserializer<OrderBookDepth> { |
||||
|
||||
public OrderBookDepthResponseDeserializer() { |
||||
this(null); |
||||
} |
||||
|
||||
public OrderBookDepthResponseDeserializer(Class<?> vc) { |
||||
super(vc); |
||||
} |
||||
|
||||
@Override |
||||
public OrderBookDepth deserialize(JsonParser jp, DeserializationContext context) throws IOException { |
||||
JsonNode node = jp.getCodec().readTree(jp); |
||||
|
||||
Long lastUpdateId = node.get("lastUpdateId").asLong(); |
||||
List<OrderBookPricing> bids = new ArrayList<>(); |
||||
List<OrderBookPricing> asks = new ArrayList<>(); |
||||
|
||||
|
||||
// NOTE: Jackson nodes don't seem to support conversion to streams
|
||||
|
||||
for (JsonNode n : node.get("bids")) { |
||||
bids.add(OrderBookPricing |
||||
.builder() |
||||
.price(new BigDecimal(n.get(0).asText())) |
||||
.quantity(new BigDecimal(n.get(1).asText())) |
||||
.build() |
||||
); |
||||
} |
||||
|
||||
for (JsonNode n : node.get("asks")) { |
||||
asks.add(OrderBookPricing |
||||
.builder() |
||||
.price(new BigDecimal(n.get(0).asText())) |
||||
.quantity(new BigDecimal(n.get(1).asText())) |
||||
.build() |
||||
); |
||||
} |
||||
|
||||
return OrderBookDepth |
||||
.builder() |
||||
.lastUpdateId(lastUpdateId) |
||||
.bids(bids) |
||||
.asks(asks) |
||||
.build(); |
||||
} |
||||
} |
@ -0,0 +1,12 @@
|
||||
package com.sigmaflare.binancej.exceptions; |
||||
|
||||
|
||||
public class BinanceServiceException extends Exception { |
||||
public BinanceServiceException() { super(); } |
||||
|
||||
public BinanceServiceException(String message) { super(message); } |
||||
|
||||
public BinanceServiceException(String message, Throwable cause) { super(message, cause); } |
||||
|
||||
public BinanceServiceException(Throwable cause) { super(cause); } |
||||
} |
@ -0,0 +1,11 @@
|
||||
package com.sigmaflare.binancej.exceptions; |
||||
|
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Getter; |
||||
|
||||
@Getter |
||||
@AllArgsConstructor |
||||
public class BinanceServiceUnreachableException extends BinanceServiceException { |
||||
public String message; |
||||
public Throwable cause; |
||||
} |
@ -1,12 +0,0 @@
|
||||
/* |
||||
* This Java source file was generated by the Gradle 'init' task. |
||||
*/ |
||||
import org.junit.Test; |
||||
import static org.junit.Assert.*; |
||||
|
||||
public class LibraryTest { |
||||
@Test public void testSomeLibraryMethod() { |
||||
Library classUnderTest = new Library(); |
||||
assertTrue("someLibraryMethod should return 'true'", classUnderTest.someLibraryMethod()); |
||||
} |
||||
} |
@ -0,0 +1,289 @@
|
||||
package com.sigmaflare.binancej; |
||||
|
||||
import com.codepoetics.ambivalence.Either; |
||||
import com.fasterxml.jackson.databind.JavaType; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import com.sigmaflare.binancej.entities.Candlestick; |
||||
import com.sigmaflare.binancej.entities.ServiceError; |
||||
import com.sigmaflare.binancej.entities.Interval; |
||||
import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; |
||||
import com.sigmaflare.binancej.matchers.GetMatcher; |
||||
import org.apache.commons.io.FileUtils; |
||||
import org.apache.http.ProtocolVersion; |
||||
import org.apache.http.client.methods.CloseableHttpResponse; |
||||
import org.apache.http.entity.BasicHttpEntity; |
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.apache.http.message.BasicStatusLine; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.math.BigDecimal; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.List; |
||||
|
||||
import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; |
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertTrue; |
||||
import static org.mockito.ArgumentMatchers.argThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
public class CandlestickMethodTests { |
||||
private static ObjectMapper mapper = Helpers.objectMapperBuilder(); |
||||
private final String url = String.format("%s%s", BASE_ENDPOINT, "/api/v1/klines?symbol=TEST&interval=8h"); |
||||
private final String urlWithLimit = String.format("%s%s", url, "&limit=1000"); |
||||
private final String urlWithStartTime = String.format("%s%s", url, "&startTime=1234"); |
||||
private final String urlWithEndTime = String.format("%s%s", url, "&endTime=1234"); |
||||
private final String urlWithLimitAndStartTime = String.format("%s%s", urlWithLimit, "&startTime=1234"); |
||||
private final String urlWithLimitAndEndTime = String.format("%s%s", urlWithLimit, "&endTime=1234"); |
||||
|
||||
private List<Candlestick> candlesticks; |
||||
|
||||
// This will just make mocking easier on us...
|
||||
private String data; |
||||
|
||||
@Before |
||||
public void setUp() throws IOException { |
||||
ClassLoader classLoader = ExchangeInfoMethodTests.class.getClassLoader(); |
||||
File file = new File(classLoader.getResource("candlestick_response_sample.json").getFile()); |
||||
|
||||
JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, Candlestick.class); |
||||
|
||||
data = FileUtils.readFileToString(file, "UTF-8"); |
||||
|
||||
candlesticks = mapper.readValue(data, type); |
||||
} |
||||
|
||||
/** |
||||
* testFieldsAreCorrect is a test method to insure field alignment. Since a custom deserializer |
||||
* is used to convert an array into a Candlestick object, it's important that field alignment |
||||
* is checked so numbers are guaranteed. |
||||
*/ |
||||
@Test |
||||
public void testFieldsAreCorrect() { |
||||
Candlestick c = candlesticks.get(0); |
||||
|
||||
assertEquals(1499040000000L, c.getOpenTime()); |
||||
assertEquals(new BigDecimal("0.01634790"), c.getOpen()); |
||||
assertEquals(new BigDecimal("0.80000000"), c.getHigh()); |
||||
assertEquals(new BigDecimal("0.01575800"), c.getLow()); |
||||
assertEquals(new BigDecimal("0.01577100"), c.getClose()); |
||||
assertEquals(new BigDecimal("148976.11427815"), c.getVolume()); |
||||
assertEquals(1499644799999L, c.getCloseTime()); |
||||
assertEquals(new BigDecimal("2434.19055334"), c.getQuoteAssetVolume()); |
||||
assertEquals(308L, c.getNumberOfTrades()); |
||||
assertEquals(new BigDecimal("1756.87402397"), c.getTakerBuyBaseAssetVolume()); |
||||
assertEquals(new BigDecimal("28.46694368"), c.getTakerBuyQuoteAssetVolume()); |
||||
// There's a field marked ignore after TakerBuyQuoteAssetVolume...
|
||||
} |
||||
|
||||
@Test |
||||
public void testCandlestickServiceReturnsSuccessfully() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, List<Candlestick>> res = |
||||
marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS); |
||||
|
||||
assertTrue(res.isRight()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.right()), candlesticks); |
||||
} |
||||
|
||||
@Test |
||||
public void testCandlestickServiceWithLimitReturnsSuccessfully() |
||||
throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(urlWithLimit, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, List<Candlestick>> res = |
||||
marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1000); |
||||
|
||||
assertTrue(res.isRight()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.right()), candlesticks); |
||||
} |
||||
|
||||
@Test |
||||
public void testCandlestickServiceWithLimitAndStartTimeReturnsSuccessfully() |
||||
throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(urlWithLimitAndStartTime, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, List<Candlestick>> res = |
||||
marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1000, 1234L,true); |
||||
|
||||
assertTrue(res.isRight()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.right()), candlesticks); |
||||
} |
||||
|
||||
@Test |
||||
public void testCandlestickServiceWithLimitAndEndTimeReturnsSuccessfully() |
||||
throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(urlWithLimitAndEndTime, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, List<Candlestick>> res = |
||||
marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1000, 1234L,false); |
||||
|
||||
assertTrue(res.isRight()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.right()), candlesticks); |
||||
} |
||||
|
||||
@Test |
||||
public void testCandlestickServiceWithStartTimeReturnsSuccessfully() |
||||
throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(urlWithStartTime, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, List<Candlestick>> res = |
||||
marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1234L,true); |
||||
|
||||
assertTrue(res.isRight()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.right()), candlesticks); |
||||
} |
||||
|
||||
@Test |
||||
public void testCandlestickServiceWithEndTimeReturnsSuccessfully() |
||||
throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(urlWithEndTime, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, List<Candlestick>> res = |
||||
marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS, 1234L,false); |
||||
|
||||
assertTrue(res.isRight()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.right()), candlesticks); |
||||
} |
||||
|
||||
@Test(expected = BinanceServiceUnreachableException.class) |
||||
public void testCandlestickServiceWithoutHttpEntityThrows() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS); |
||||
} |
||||
|
||||
@Test |
||||
public void TestCandlestickServiceBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
400, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
ServiceError serviceError = ServiceError.builder().code(400).message("Bad API Key").build(); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent( |
||||
new ByteArrayInputStream(mapper.writeValueAsString(serviceError).getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, List<Candlestick>> res = |
||||
marketData.getCandleStickData("TEST", Interval.EIGHT_HOURS); |
||||
|
||||
assertTrue(res.isLeft()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.left()), serviceError); |
||||
} |
||||
} |
@ -0,0 +1,120 @@
|
||||
package com.sigmaflare.binancej; |
||||
|
||||
|
||||
import com.codepoetics.ambivalence.Either; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import com.sigmaflare.binancej.entities.ExchangeInfo; |
||||
import com.sigmaflare.binancej.entities.ServiceError; |
||||
import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; |
||||
import com.sigmaflare.binancej.matchers.GetMatcher; |
||||
import org.apache.commons.io.FileUtils; |
||||
import org.apache.http.ProtocolVersion; |
||||
import org.apache.http.client.methods.CloseableHttpResponse; |
||||
import org.apache.http.entity.BasicHttpEntity; |
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.apache.http.message.BasicStatusLine; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; |
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertTrue; |
||||
import static org.mockito.ArgumentMatchers.argThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
public class ExchangeInfoMethodTests { |
||||
private static ObjectMapper mapper = Helpers.objectMapperBuilder(); |
||||
private String url = String.format("%s%s", BASE_ENDPOINT, "/api/v1/exchangeInfo"); |
||||
|
||||
private ExchangeInfo exchangeInfo; |
||||
|
||||
@Before |
||||
public void setUp() throws IOException { |
||||
ClassLoader classLoader = ExchangeInfoMethodTests.class.getClassLoader(); |
||||
File file = new File(classLoader.getResource("exchange_info_response_sample.json").getFile()); |
||||
|
||||
exchangeInfo = |
||||
mapper.readValue(FileUtils.readFileToString(file, "UTF-8"), ExchangeInfo.class); |
||||
} |
||||
|
||||
@Test |
||||
public void TestExchangeInfoServiceReturnsSuccessfully() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream( |
||||
mapper.writeValueAsString(exchangeInfo).getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
GeneralUtilities generalUtilities = |
||||
new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, ExchangeInfo> res = generalUtilities.getExchangeInfo(); |
||||
|
||||
assertTrue(res.isRight()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.right()), exchangeInfo); |
||||
} |
||||
|
||||
@Test(expected = BinanceServiceUnreachableException.class) |
||||
public void TestExchangeInfoServiceWithNoHttpEntityFails() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
GeneralUtilities generalUtilities = |
||||
new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
generalUtilities.getExchangeInfo(); |
||||
} |
||||
|
||||
@Test |
||||
public void TestExchangeInfoServiceBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
400, "Bad API Key")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
ServiceError serviceError = ServiceError.builder().code(400).message("Bad API Key").build(); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent( |
||||
new ByteArrayInputStream(mapper.writeValueAsString(serviceError).getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
GeneralUtilities generalUtilities = |
||||
new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, ExchangeInfo> res = generalUtilities.getExchangeInfo(); |
||||
|
||||
assertTrue(res.isLeft()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.left()), serviceError); |
||||
} |
||||
} |
@ -0,0 +1,124 @@
|
||||
package com.sigmaflare.binancej; |
||||
|
||||
import com.codepoetics.ambivalence.Either; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import com.sigmaflare.binancej.entities.OrderBookDepth; |
||||
import com.sigmaflare.binancej.entities.ServiceError; |
||||
import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; |
||||
import com.sigmaflare.binancej.matchers.GetMatcher; |
||||
import org.apache.commons.io.FileUtils; |
||||
import org.apache.http.ProtocolVersion; |
||||
import org.apache.http.client.methods.CloseableHttpResponse; |
||||
import org.apache.http.entity.BasicHttpEntity; |
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.apache.http.message.BasicStatusLine; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; |
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertTrue; |
||||
import static org.mockito.ArgumentMatchers.argThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
public class OrderBookDepthTests { |
||||
private static ObjectMapper mapper = Helpers.objectMapperBuilder(); |
||||
private final String url = String.format("%s%s", BASE_ENDPOINT, "/api/v1/depth?symbol=ETHBTC&limit=100"); |
||||
private final String urlWithLimit = String.format("%s%s", BASE_ENDPOINT, "/api/v1/depth?symbol=ETHBTC&limit=1000"); |
||||
|
||||
private OrderBookDepth orderBookDepth; |
||||
private String orderBookDepthJson; |
||||
|
||||
@Before |
||||
public void setUp() throws IOException { |
||||
ClassLoader classLoader = ExchangeInfoMethodTests.class.getClassLoader(); |
||||
File file = new File(classLoader.getResource("orderbook_depth_sample.json").getFile()); |
||||
|
||||
orderBookDepthJson = FileUtils.readFileToString(file, "UTF-8"); |
||||
|
||||
orderBookDepth = |
||||
mapper.readValue(orderBookDepthJson, OrderBookDepth.class); |
||||
} |
||||
|
||||
@Test |
||||
public void TestOrderBookDepthReturnsSuccessfully() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
|
||||
// This changes from the usual way we do this because we do post-processing in the deserializer and the JSON
|
||||
// will no longer match the stringified OrderBookDepth.
|
||||
httpEntity.setContent(new ByteArrayInputStream(orderBookDepthJson.getBytes())); |
||||
|
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, OrderBookDepth> res = marketData.getOrderBookDepth("ETHBTC"); |
||||
|
||||
assertTrue(res.isRight()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.right()), orderBookDepth); |
||||
|
||||
} |
||||
|
||||
@Test(expected = BinanceServiceUnreachableException.class) |
||||
public void TestOrderBookDepthWithNoHttpEntityFails() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(urlWithLimit, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
marketData.getOrderBookDepth("ETHBTC", 1000); |
||||
} |
||||
|
||||
@Test |
||||
public void TestOrderBookDepthWithBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
400, "Bad API Key")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(urlWithLimit, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
ServiceError serviceError = ServiceError.builder().code(400).message("Bad API Key").build(); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream( |
||||
mapper.writeValueAsString(serviceError).getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
Either<ServiceError, OrderBookDepth> res = marketData.getOrderBookDepth("ETHBTC", 1000); |
||||
|
||||
assertTrue(res.isLeft()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.left()), serviceError); |
||||
} |
||||
} |
@ -0,0 +1,105 @@
|
||||
package com.sigmaflare.binancej; |
||||
|
||||
|
||||
import com.codepoetics.ambivalence.Either; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import com.sigmaflare.binancej.entities.ServiceError; |
||||
import com.sigmaflare.binancej.entities.Ping; |
||||
import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; |
||||
import com.sigmaflare.binancej.matchers.GetMatcher; |
||||
import org.apache.http.ProtocolVersion; |
||||
import org.apache.http.client.methods.CloseableHttpResponse; |
||||
import org.apache.http.entity.BasicHttpEntity; |
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.apache.http.message.BasicStatusLine; |
||||
import org.junit.Test; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; |
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertTrue; |
||||
import static org.mockito.ArgumentMatchers.argThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
public class PingMethodTests { |
||||
private static ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); |
||||
private final String url = String.format("%s%s", BASE_ENDPOINT, "/api/v1/ping"); |
||||
|
||||
@Test |
||||
public void TestServiceAliveReturnsSuccess() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream("{}".getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
GeneralUtilities generalUtilities = |
||||
new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, Ping> res = generalUtilities.ping(); |
||||
|
||||
assertTrue(res.isRight()); |
||||
|
||||
} |
||||
|
||||
@Test(expected = BinanceServiceUnreachableException.class) |
||||
public void TestServiceFailsToReturnHttpEntity() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
GeneralUtilities generalUtilities = |
||||
new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
generalUtilities.ping(); |
||||
} |
||||
|
||||
@Test |
||||
public void TestServiceBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
400, "Bad API Key")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
ServiceError serviceError = ServiceError.builder().code(400).message("Bad API Key").build(); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent( |
||||
new ByteArrayInputStream(mapper.writeValueAsString(serviceError).getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
GeneralUtilities generalUtilities |
||||
= new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, Ping> res = generalUtilities.ping(); |
||||
|
||||
assertTrue(res.isLeft()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.left()), serviceError); |
||||
} |
||||
} |
@ -0,0 +1,207 @@
|
||||
package com.sigmaflare.binancej; |
||||
|
||||
import com.codepoetics.ambivalence.Either; |
||||
import com.fasterxml.jackson.databind.JavaType; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import com.sigmaflare.binancej.entities.ServiceError; |
||||
import com.sigmaflare.binancej.entities.TickerPrice; |
||||
import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; |
||||
import com.sigmaflare.binancej.matchers.GetMatcher; |
||||
import org.apache.commons.io.FileUtils; |
||||
import org.apache.http.ProtocolVersion; |
||||
import org.apache.http.client.methods.CloseableHttpResponse; |
||||
import org.apache.http.entity.BasicHttpEntity; |
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.apache.http.message.BasicStatusLine; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.List; |
||||
|
||||
import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; |
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertTrue; |
||||
import static org.mockito.ArgumentMatchers.argThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
public class TickerPriceTests { |
||||
private static final ObjectMapper mapper = Helpers.objectMapperBuilder(); |
||||
private final String url = String.format("%s%s", BASE_ENDPOINT, "/api/v3/ticker/price"); |
||||
private final String specificUrl = String.format("%s%s", url, "?symbol=TEST"); |
||||
|
||||
private String singleTickerPriceJson; |
||||
private String multipleTickerPricesJson; |
||||
|
||||
private TickerPrice tickerPrice; |
||||
private List<TickerPrice> tickerPrices; |
||||
|
||||
|
||||
@Before |
||||
public void setUp() throws IOException { |
||||
ClassLoader classLoader = ExchangeInfoMethodTests.class.getClassLoader(); |
||||
File singleTickerPriceFile = |
||||
new File(classLoader.getResource("single_ticker_price_response.json").getFile()); |
||||
|
||||
File multipleTickerPriceFile = |
||||
new File(classLoader.getResource("multiple_ticker_prices_response.json").getFile()); |
||||
|
||||
JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, TickerPrice.class); |
||||
|
||||
singleTickerPriceJson = FileUtils.readFileToString(singleTickerPriceFile, "UTF-8"); |
||||
multipleTickerPricesJson = FileUtils.readFileToString(multipleTickerPriceFile, "UTF-8"); |
||||
|
||||
tickerPrice = mapper.readValue(singleTickerPriceJson, TickerPrice.class); |
||||
|
||||
tickerPrices = mapper.readValue(multipleTickerPricesJson, type); |
||||
} |
||||
|
||||
@Test |
||||
public void testTickerPriceForSymbolReturnsSuccessfully() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(specificUrl, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream(singleTickerPriceJson.getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, TickerPrice> res = marketData.getTickerPriceForSymbol("TEST"); |
||||
|
||||
assertTrue(res.isRight()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.right()), tickerPrice); |
||||
} |
||||
|
||||
@Test |
||||
public void testTickerPricesSuccessfully() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream(multipleTickerPricesJson.getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, List<TickerPrice>> res = marketData.getTickerPrices(); |
||||
|
||||
assertTrue(res.isRight()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.right()), tickerPrices); |
||||
} |
||||
|
||||
@Test(expected = BinanceServiceUnreachableException.class) |
||||
public void testTickerPriceForSymbolWithoutHttpEntityThrows() |
||||
throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(specificUrl, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
marketData.getTickerPriceForSymbol("TEST"); |
||||
} |
||||
|
||||
@Test(expected = BinanceServiceUnreachableException.class) |
||||
public void testTickerPricesWithoutHttpEntityThrows() |
||||
throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
marketData.getTickerPrices(); |
||||
} |
||||
|
||||
@Test |
||||
public void testTickerPriceForSymbolWithBadApiKeyReturns400() |
||||
throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
400, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(specificUrl, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
ServiceError serviceError = ServiceError.builder().code(400).message("Bad API Key").build(); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent( |
||||
new ByteArrayInputStream(mapper.writeValueAsString(serviceError).getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, TickerPrice> res = marketData.getTickerPriceForSymbol("TEST"); |
||||
|
||||
assertTrue(res.isLeft()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.left()), serviceError); |
||||
} |
||||
|
||||
@Test |
||||
public void testTickerPricesBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
400, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
ServiceError serviceError = ServiceError.builder().code(400).message("Bad API Key").build(); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent( |
||||
new ByteArrayInputStream(mapper.writeValueAsString(serviceError).getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
MarketData marketData = new MarketData("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, List<TickerPrice>> res = marketData.getTickerPrices(); |
||||
|
||||
assertTrue(res.isLeft()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.left()), serviceError); |
||||
} |
||||
} |
@ -0,0 +1,108 @@
|
||||
package com.sigmaflare.binancej; |
||||
|
||||
import com.codepoetics.ambivalence.Either; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import com.sigmaflare.binancej.entities.ServiceError; |
||||
import com.sigmaflare.binancej.entities.Time; |
||||
import com.sigmaflare.binancej.exceptions.BinanceServiceUnreachableException; |
||||
import com.sigmaflare.binancej.matchers.GetMatcher; |
||||
import org.apache.http.ProtocolVersion; |
||||
import org.apache.http.client.methods.CloseableHttpResponse; |
||||
import org.apache.http.entity.BasicHttpEntity; |
||||
import org.apache.http.impl.client.CloseableHttpClient; |
||||
import org.apache.http.message.BasicStatusLine; |
||||
import org.junit.Test; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.IOException; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import static com.sigmaflare.binancej.Constant.BASE_ENDPOINT; |
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertTrue; |
||||
import static org.mockito.ArgumentMatchers.argThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
public class TimeMethodTests { |
||||
private static ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); |
||||
private final String url = String.format("%s%s", BASE_ENDPOINT, "/api/v1/time"); |
||||
|
||||
@Test |
||||
public void TestTimeReturnsSuccessfully() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
Time time = Time.builder().serverTime(System.currentTimeMillis() / 1000L).build(); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream( |
||||
mapper.writeValueAsString(time).getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
GeneralUtilities generalUtilities = |
||||
new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, Time> res = generalUtilities.getServerTime(); |
||||
|
||||
assertTrue(res.isRight()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.right()), time); |
||||
} |
||||
|
||||
@Test(expected = BinanceServiceUnreachableException.class) |
||||
public void TestTimeNoEntityReturnedFails() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
200, "test")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
GeneralUtilities generalUtilities = |
||||
new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
generalUtilities.getServerTime(); |
||||
} |
||||
|
||||
@Test |
||||
public void TestTimeBadApiKeyReturns400() throws IOException, BinanceServiceUnreachableException { |
||||
CloseableHttpClient mockedCloseableHttpClient = mock(CloseableHttpClient.class); |
||||
CloseableHttpResponse mockedCloseableHttpResponse = mock(CloseableHttpResponse.class); |
||||
|
||||
when(mockedCloseableHttpResponse.getStatusLine()) |
||||
.thenReturn(new BasicStatusLine(new ProtocolVersion("TEST", 1, 0), |
||||
400, "Bad API Key")); |
||||
|
||||
when(mockedCloseableHttpClient.execute(argThat(new GetMatcher(url, "1234")))) |
||||
.thenReturn(mockedCloseableHttpResponse); |
||||
|
||||
ServiceError serviceError = ServiceError.builder().code(400).message("Bad API Key").build(); |
||||
|
||||
BasicHttpEntity httpEntity = new BasicHttpEntity(); |
||||
httpEntity.setContent(new ByteArrayInputStream( |
||||
mapper.writeValueAsString(serviceError).getBytes(StandardCharsets.UTF_8))); |
||||
|
||||
|
||||
when(mockedCloseableHttpResponse.getEntity()).thenReturn(httpEntity); |
||||
|
||||
GeneralUtilities generalUtilities = |
||||
new GeneralUtilities("1234", "abcd", mockedCloseableHttpClient); |
||||
|
||||
Either<ServiceError, Time> res = generalUtilities.getServerTime(); |
||||
|
||||
assertTrue(res.isLeft()); |
||||
assertEquals(Helpers.extractEitherValueSafely(res.left()), serviceError); |
||||
} |
||||
} |
@ -0,0 +1,29 @@
|
||||
package com.sigmaflare.binancej.matchers; |
||||
|
||||
import org.apache.http.client.methods.HttpGet; |
||||
import org.mockito.ArgumentMatcher; |
||||
|
||||
public class GetMatcher implements ArgumentMatcher<HttpGet> { |
||||
private String url; |
||||
private String apiKey; |
||||
|
||||
public GetMatcher(String url, String apiKey) { |
||||
this.url = url; |
||||
this.apiKey = apiKey; |
||||
} |
||||
|
||||
/** |
||||
* Matches HttpGet objects based on "loose matching". We really only care that the URL and associated |
||||
* headers we care about are correct. |
||||
* @param httpGet The HttpGet object under test |
||||
* @return True if they are equal by our definition, false otherwise |
||||
*/ |
||||
public boolean matches(HttpGet httpGet) { |
||||
final String url = httpGet.getURI().toASCIIString(); |
||||
final String apiKeyHeaderValue = httpGet.getFirstHeader("X-MBX-APIKEY").getValue(); |
||||
|
||||
|
||||
return url.equals(this.url) && apiKeyHeaderValue != null && apiKeyHeaderValue.equals(apiKey); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,37 @@
|
||||
package com.sigmaflare.binancej.matchers; |
||||
|
||||
import org.apache.commons.io.IOUtils; |
||||
import org.apache.http.client.methods.HttpPost; |
||||
import org.mockito.ArgumentMatcher; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
public class PostMatcher implements ArgumentMatcher<HttpPost> { |
||||
private String url; |
||||
private String apiKey; |
||||
private String requestBody; |
||||
|
||||
public PostMatcher(String url, String apiKey, String requestBody) { |
||||
this.url = url; |
||||
this.apiKey = apiKey; |
||||
this.requestBody = requestBody; |
||||
} |
||||
|
||||
/** |
||||
* Matches HttpPost objects based on "loose matching". We really only care that the URL and associated |
||||
* headers we care about are correct. |
||||
* @param httpPost The HttpPost object under test |
||||
* @return True if they are equal by our definition, false otherwise |
||||
*/ |
||||
public boolean matches(HttpPost httpPost) { |
||||
final String url = httpPost.getURI().toASCIIString(); |
||||
final String apiKeyHeaderValue = httpPost.getFirstHeader("X-MBX-APIKEY").getValue(); |
||||
|
||||
try { |
||||
String testBody = IOUtils.toString(httpPost.getEntity().getContent(), "UTF-8"); |
||||
return this.url.equals(url) && apiKey.equals(apiKeyHeaderValue) && requestBody.equals(testBody); |
||||
} catch(IOException e) { |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,16 @@
|
||||
[ |
||||
[ |
||||
1499040000000, |
||||
"0.01634790", |
||||
"0.80000000", |
||||
"0.01575800", |
||||
"0.01577100", |
||||
"148976.11427815", |
||||
1499644799999, |
||||
"2434.19055334", |
||||
308, |
||||
"1756.87402397", |
||||
"28.46694368", |
||||
"17928899.62484339" |
||||
] |
||||
] |
@ -0,0 +1,45 @@
|
||||
{ |
||||
"timezone": "UTC", |
||||
"serverTime": 1508631584636, |
||||
"rateLimits": [{ |
||||
"rateLimitType": "REQUESTS", |
||||
"interval": "MINUTE", |
||||
"limit": 1200 |
||||
}, |
||||
{ |
||||
"rateLimitType": "ORDERS", |
||||
"interval": "SECOND", |
||||
"limit": 10 |
||||
}, |
||||
{ |
||||
"rateLimitType": "ORDERS", |
||||
"interval": "DAY", |
||||
"limit": 100000 |
||||
} |
||||
], |
||||
"exchangeFilters": [], |
||||
"symbols": [{ |
||||
"symbol": "ETHBTC", |
||||
"status": "TRADING", |
||||
"baseAsset": "ETH", |
||||
"baseAssetPrecision": 8, |
||||
"quoteAsset": "BTC", |
||||
"quotePrecision": 8, |
||||
"orderTypes": ["LIMIT", "MARKET"], |
||||
"icebergAllowed": false, |
||||
"filters": [{ |
||||
"filterType": "PRICE_FILTER", |
||||
"minPrice": "0.00000100", |
||||
"maxPrice": "100000.00000000", |
||||
"tickSize": "0.00000100" |
||||
}, { |
||||
"filterType": "LOT_SIZE", |
||||
"minQty": "0.00100000", |
||||
"maxQty": "100000.00000000", |
||||
"stepSize": "0.00100000" |
||||
}, { |
||||
"filterType": "MIN_NOTIONAL", |
||||
"minNotional": "0.00100000" |
||||
}] |
||||
}] |
||||
} |
@ -0,0 +1,10 @@
|
||||
[ |
||||
{ |
||||
"symbol": "LTCBTC", |
||||
"price": "4.00000200" |
||||
}, |
||||
{ |
||||
"symbol": "ETHBTC", |
||||
"price": "0.07946600" |
||||
} |
||||
] |
@ -0,0 +1,17 @@
|
||||
{ |
||||
"lastUpdateId": 1027024, |
||||
"bids": [ |
||||
[ |
||||
"4.00000000", |
||||
"431.00000000", |
||||
[] |
||||
] |
||||
], |
||||
"asks": [ |
||||
[ |
||||
"4.00000200", |
||||
"12.00000000", |
||||
[] |
||||
] |
||||
] |
||||
} |
Loading…
Reference in new issue