Browse Source

Initial GET Work (#1)

master
Taylor Bockman 6 years ago committed by GitHub
parent
commit
52abe3700b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      .circleci/config.yml
  2. 32
      .github/ISSUE_TEMPLATE.md
  3. 27
      .github/PULL_REQUEST_TEMPLATE.md
  4. 86
      CONTRIBUTING.md
  5. 21
      LICENSE
  6. 130
      README.md
  7. 11
      build.gradle
  8. 17
      src/main/java/com/sigmaflare/Binance.java
  9. 38
      src/main/java/com/sigmaflare/binancej/BaseBinanceApi.java
  10. 11
      src/main/java/com/sigmaflare/binancej/Constant.java
  11. 148
      src/main/java/com/sigmaflare/binancej/GeneralUtilities.java
  12. 87
      src/main/java/com/sigmaflare/binancej/Helpers.java
  13. 382
      src/main/java/com/sigmaflare/binancej/MarketData.java
  14. 43
      src/main/java/com/sigmaflare/binancej/entities/Candlestick.java
  15. 7
      src/main/java/com/sigmaflare/binancej/entities/ExchangeFilter.java
  16. 37
      src/main/java/com/sigmaflare/binancej/entities/ExchangeInfo.java
  17. 34
      src/main/java/com/sigmaflare/binancej/entities/Interval.java
  18. 30
      src/main/java/com/sigmaflare/binancej/entities/LotSizeFilter.java
  19. 22
      src/main/java/com/sigmaflare/binancej/entities/MinNotionalFilter.java
  20. 29
      src/main/java/com/sigmaflare/binancej/entities/OrderBookDepth.java
  21. 18
      src/main/java/com/sigmaflare/binancej/entities/OrderBookPricing.java
  22. 11
      src/main/java/com/sigmaflare/binancej/entities/OrderType.java
  23. 7
      src/main/java/com/sigmaflare/binancej/entities/Ping.java
  24. 30
      src/main/java/com/sigmaflare/binancej/entities/PriceFilter.java
  25. 23
      src/main/java/com/sigmaflare/binancej/entities/RateLimit.java
  26. 7
      src/main/java/com/sigmaflare/binancej/entities/RateLimitInterval.java
  27. 6
      src/main/java/com/sigmaflare/binancej/entities/RateLimitType.java
  28. 21
      src/main/java/com/sigmaflare/binancej/entities/ServiceError.java
  29. 51
      src/main/java/com/sigmaflare/binancej/entities/Symbol.java
  30. 22
      src/main/java/com/sigmaflare/binancej/entities/SymbolFilter.java
  31. 11
      src/main/java/com/sigmaflare/binancej/entities/SymbolStatus.java
  32. 24
      src/main/java/com/sigmaflare/binancej/entities/TickerPrice.java
  33. 16
      src/main/java/com/sigmaflare/binancej/entities/Time.java
  34. 46
      src/main/java/com/sigmaflare/binancej/entities/transform/CandlestickDeserializer.java
  35. 66
      src/main/java/com/sigmaflare/binancej/entities/transform/OrderBookDepthResponseDeserializer.java
  36. 12
      src/main/java/com/sigmaflare/binancej/exceptions/BinanceServiceException.java
  37. 11
      src/main/java/com/sigmaflare/binancej/exceptions/BinanceServiceUnreachableException.java
  38. 12
      src/test/java/LibraryTest.java
  39. 289
      src/test/java/com/sigmaflare/binancej/CandlestickMethodTests.java
  40. 120
      src/test/java/com/sigmaflare/binancej/ExchangeInfoMethodTests.java
  41. 124
      src/test/java/com/sigmaflare/binancej/OrderBookDepthTests.java
  42. 105
      src/test/java/com/sigmaflare/binancej/PingMethodTests.java
  43. 207
      src/test/java/com/sigmaflare/binancej/TickerPriceTests.java
  44. 108
      src/test/java/com/sigmaflare/binancej/TimeMethodTests.java
  45. 29
      src/test/java/com/sigmaflare/binancej/matchers/GetMatcher.java
  46. 37
      src/test/java/com/sigmaflare/binancej/matchers/PostMatcher.java
  47. 16
      src/test/resources/candlestick_response_sample.json
  48. 45
      src/test/resources/exchange_info_response_sample.json
  49. 10
      src/test/resources/multiple_ticker_prices_response.json
  50. 17
      src/test/resources/orderbook_depth_sample.json
  51. 4
      src/test/resources/single_ticker_price_response.json

42
.circleci/config.yml

@ -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

32
.github/ISSUE_TEMPLATE.md

@ -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:

27
.github/PULL_REQUEST_TEMPLATE.md

@ -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/)

86
CONTRIBUTING.md

@ -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.

21
LICENSE

@ -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.

130
README.md

@ -1,24 +1,22 @@
#BinanceJ
A Java 8 implementation of the [Binance API Specification](https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md).
[![CircleCI](https://circleci.com/gh/angrygoats/binancej/tree/master.svg?style=svg&circle-token=ec4614038357b8ff4bcc4773bedd4a264a1947b7)](https://circleci.com/gh/angrygoats/binancej/tree/master)
## Rate Limiting
A Java 8 implementation of the [Binance API Specification](https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md).
BinanceJ does not perform rate limiting, so you are responsible for limiting your requests.
## Licensing
## TODO
BinanceJ is released under the MIT license.
* Only implement the GET methods for now
* Test everything with mocked http clients and such
* Use closable clients, etc
## Rate Limiting
BinanceJ does not perform rate limiting, so you are responsible for limiting your requests. Pay attention to the errors
you receive from Binance and check to see if they are a 429. If they are, you need to back off or face a temporary
ban.
## TO IMPLEMENT ON THIS RUN:
## API Coverage
1. Error code detection to make things easy (think like you did before)
2. All enums for each endpoint
3. Errors and Responses inheirit from the same base object and errors are returned conditionally with an EITHER type structure
The following endpoints are currently covered:
1. `GET /api/v1/ping`
2. `GET /api/v1/time`
@ -27,4 +25,110 @@ BinanceJ does not perform rate limiting, so you are responsible for limiting you
5. `GET /api/v1/klines`
6. `GET /api/v3/ticker/price`
for (5) you will need to implement all enums (such as candlestick hours, etc) as well as compose the various fields of the returned array into an object.
More will be added in future PRs as they become necessary to me or the people using the library.
## Return Types
All functions return an `Either` type. For those of you not in the know - an Either type represents a disjoint union.
Typically this means a "success" and "failure" case that must be handled uniquely. They work well with stream
processing and are a very natural way to delineate success and failure cleanly. Additionally it eliminates the pattern
of throwing exceptions on errors, and allows exceptions to be reserved for truly exceptional behavior as intended.
By convention I adapted the Haskell Either type style to this code. What this means is that the Either type's Right
value is the correct one (mnemonic: "right" as in correct) and the Left value is the serviceError.
### How do I extract the raw value from the Either type?
There are a few ways to do this using Ambivalent, but the most prevalent way in BinanceJ's tests is:
```java
Either<TypeA, TypeB> val = clazz.getThing();
// Good path case
if(val.isRight()) {
TypeB thing = val.right().join(Function.identity(), Function.identity());
}
```
Alternatively you are welcome to use `Helpers.extractEitherValueSafely` method to make your code cleaner.
## Examples
### Server alive check with ping
```java
GeneralUtilities generalUtilities = GeneralUtilities.builder().apiKey("KEY").secretKey("KEY").build();
Either<ServiceError, Ping> res = generalUtilities.ping();
if(res.isRight()) {
// Successful ping
}
```
### Getting current server time
```java
GeneralUtilities generalUtilities = GeneralUtilities.builder().apiKey("KEY").secretKey("KEY").build();
Either<ServiceError, ServerTime> res = generalUtilities.getServerTime();
if(res.isRight() {
ServerTime response = Helpers.extractEitherValueSafely(res.right());
//...
}
```
### Getting Exchange Information
```java
GeneralUtilities generalUtilities = GeneralUtilities.builder().apiKey("KEY").secretKey("KEY").build();
Either<ServiceError, ExchangeInfo> res = generalUtilities.getExchangeInfo();
if(res.isRight() {
ExchangeInfo response = Helpers.extractEitherValueSafely(res.right());
//...
}
```
### Getting Candlestick data
```java
MarketData marketData = MarketData.builder().apiKey("KEY").secretKey("KEY").build();
Either<ServiceError, List<Candlestick>> res = marketData.getCandlestickData("ETHBTC", Interval.ONE_MINUTE);
if(res.isRight() {
List<Candlestick> data = Helpers.extractEitherValueSafely(res.right());
//...
}
```
### Getting market depth
```java
MarketData marketData = MarketData.builder().apiKey("KEY").secretKey("KEY").build();
Either<ServiceError, OrderBookDepth> res = marketData.getOrderBookDepth("ETHBTC", 1000);
if(res.isRight() {
OrderBookDepth orderBookDepth = Helpers.extractEitherValueSafely(res.right());
//...
}
```
### Getting ticker price for an instrument
```java
MarketData marketData = MarketData.builder().apiKey("KEY").secretKey("KEY").build();
Either<ServiceError, TickerPrice> res = marketData.getTickerPriceForSymbol("ETHBTC");
if(res.isRight() {
TickerPrice tickerPrice = Helpers.extractEitherValueSafely(res.right());
//...
}
```
## Contributing
Head over to our [CONTRIBUTING.md](CONTRIBUTING.md) to get started. All features are welcome as long as they are
in scope of the API and following the contributing guide.

11
build.gradle

@ -1,18 +1,29 @@
plugins {
// Apply the java-library plugin to add support for Java Library
id 'java-library'
id 'com.github.ethankhall.semantic-versioning' version "1.1.0" apply true
}
project.version.with { major = 1; minor= 0; patch = 0 }
sourceCompatibility = 1.8
dependencies {
compile group: 'org.projectlombok', name: 'lombok', version: '1.16.20'
compile group: 'com.codepoetics', name: 'ambivalence', version: '0.2'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5'
compile group: 'commons-io', name: 'commons-io', version: '2.6'
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.5'
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.11.0'
testCompile 'junit:junit:4.12'
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.18.3'
}
repositories {

17
src/main/java/com/sigmaflare/Binance.java

@ -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;
}
}

38
src/main/java/com/sigmaflare/binancej/BaseBinanceApi.java

@ -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();
}
}

11
src/main/java/com/sigmaflare/binancej/Constant.java

@ -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";
}

148
src/main/java/com/sigmaflare/binancej/GeneralUtilities.java

@ -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());
}
}
}

87
src/main/java/com/sigmaflare/binancej/Helpers.java

@ -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());
}
}

382
src/main/java/com/sigmaflare/binancej/MarketData.java

@ -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());
}
}
}

43
src/main/java/com/sigmaflare/binancej/entities/Candlestick.java

@ -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;
}

7
src/main/java/com/sigmaflare/binancej/entities/ExchangeFilter.java

@ -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 {
}

37
src/main/java/com/sigmaflare/binancej/entities/ExchangeInfo.java

@ -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;
}

34
src/main/java/com/sigmaflare/binancej/entities/Interval.java

@ -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;
}
}

30
src/main/java/com/sigmaflare/binancej/entities/LotSizeFilter.java

@ -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;
}

22
src/main/java/com/sigmaflare/binancej/entities/MinNotionalFilter.java

@ -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;
}

29
src/main/java/com/sigmaflare/binancej/entities/OrderBookDepth.java

@ -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;
}

18
src/main/java/com/sigmaflare/binancej/entities/OrderBookPricing.java

@ -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;
}

11
src/main/java/com/sigmaflare/binancej/entities/OrderType.java

@ -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
}

7
src/main/java/com/sigmaflare/binancej/entities/Ping.java

@ -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 {
}

30
src/main/java/com/sigmaflare/binancej/entities/PriceFilter.java

@ -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;
}

23
src/main/java/com/sigmaflare/binancej/entities/RateLimit.java

@ -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;
}

7
src/main/java/com/sigmaflare/binancej/entities/RateLimitInterval.java

@ -0,0 +1,7 @@
package com.sigmaflare.binancej.entities;
public enum RateLimitInterval {
SECOND,
MINUTE,
DAY
}

6
src/main/java/com/sigmaflare/binancej/entities/RateLimitType.java

@ -0,0 +1,6 @@
package com.sigmaflare.binancej.entities;
public enum RateLimitType {
REQUESTS,
ORDERS
}

21
src/main/java/com/sigmaflare/binancej/entities/ServiceError.java

@ -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;
}

51
src/main/java/com/sigmaflare/binancej/entities/Symbol.java

@ -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;
}

22
src/main/java/com/sigmaflare/binancej/entities/SymbolFilter.java

@ -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;
}

11
src/main/java/com/sigmaflare/binancej/entities/SymbolStatus.java

@ -0,0 +1,11 @@
package com.sigmaflare.binancej.entities;
public enum SymbolStatus {
PRE_TRADING,
TRADING,
POST_TRADING,
END_OF_DAY,
HALT,
AUCTION_MATCH,
BREAK
}

24
src/main/java/com/sigmaflare/binancej/entities/TickerPrice.java

@ -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;
}

16
src/main/java/com/sigmaflare/binancej/entities/Time.java

@ -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;
}

46
src/main/java/com/sigmaflare/binancej/entities/transform/CandlestickDeserializer.java

@ -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();
}
}

66
src/main/java/com/sigmaflare/binancej/entities/transform/OrderBookDepthResponseDeserializer.java

@ -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();
}
}

12
src/main/java/com/sigmaflare/binancej/exceptions/BinanceServiceException.java

@ -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); }
}

11
src/main/java/com/sigmaflare/binancej/exceptions/BinanceServiceUnreachableException.java

@ -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;
}

12
src/test/java/LibraryTest.java

@ -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());
}
}

289
src/test/java/com/sigmaflare/binancej/CandlestickMethodTests.java

@ -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);
}
}

120
src/test/java/com/sigmaflare/binancej/ExchangeInfoMethodTests.java

@ -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);
}
}

124
src/test/java/com/sigmaflare/binancej/OrderBookDepthTests.java

@ -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);
}
}

105
src/test/java/com/sigmaflare/binancej/PingMethodTests.java

@ -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);
}
}

207
src/test/java/com/sigmaflare/binancej/TickerPriceTests.java

@ -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);
}
}

108
src/test/java/com/sigmaflare/binancej/TimeMethodTests.java

@ -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);
}
}

29
src/test/java/com/sigmaflare/binancej/matchers/GetMatcher.java

@ -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);
}
}

37
src/test/java/com/sigmaflare/binancej/matchers/PostMatcher.java

@ -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;
}
}
}

16
src/test/resources/candlestick_response_sample.json

@ -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"
]
]

45
src/test/resources/exchange_info_response_sample.json

@ -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"
}]
}]
}

10
src/test/resources/multiple_ticker_prices_response.json

@ -0,0 +1,10 @@
[
{
"symbol": "LTCBTC",
"price": "4.00000200"
},
{
"symbol": "ETHBTC",
"price": "0.07946600"
}
]

17
src/test/resources/orderbook_depth_sample.json

@ -0,0 +1,17 @@
{
"lastUpdateId": 1027024,
"bids": [
[
"4.00000000",
"431.00000000",
[]
]
],
"asks": [
[
"4.00000200",
"12.00000000",
[]
]
]
}

4
src/test/resources/single_ticker_price_response.json

@ -0,0 +1,4 @@
{
"symbol": "LTCBTC",
"price": "4.00000200"
}
Loading…
Cancel
Save