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