Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
estonlabs.cxtl.exchanges.b2c2.v1.lib.B2C2Cex Maven / Gradle / Ivy
package estonlabs.cxtl.exchanges.b2c2.v1.lib;
import estonlabs.cxtl.common.auth.Credentials;
import estonlabs.cxtl.common.exception.CxtlApiException;
import estonlabs.cxtl.common.exception.CxtlEventException;
import estonlabs.cxtl.common.exception.ErrorCode;
import estonlabs.cxtl.common.http.Event;
import estonlabs.cxtl.common.http.HeaderBuilder;
import estonlabs.cxtl.common.http.JsonRestClient;
import estonlabs.cxtl.common.http.MetricsLogger;
import estonlabs.cxtl.exchanges.a.specification.domain.*;
import estonlabs.cxtl.exchanges.a.specification.lib.Cex;
import estonlabs.cxtl.exchanges.a.specification.lib.ExchangeDataInterface;
import estonlabs.cxtl.exchanges.b2c2.v1.domain.*;
import lombok.NonNull;
import org.jetbrains.annotations.Nullable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.Proxy;
import java.util.*;
import java.util.stream.Collectors;
public class B2C2Cex implements Cex, ExchangeDataInterface {
private final JsonRestClient client;
private final MetricsLogger metricsLogger;
public B2C2Cex(JsonRestClient restClient, MetricsLogger metricsLogger) {
this.client = restClient;
this.metricsLogger = metricsLogger;
}
private HeaderBuilder authorized(Credentials credentials){
return (builder, message) -> builder.addHeader("Authorization", "Token " + credentials.getApiKey());
}
@Override
public Proxy getProxy() {
return client.getProxy();
}
@Override
public Mono extends List extends Olhcv>> getOlhcv(Object request) {
return Mono.empty();
}
@Override
public Mono extends List extends Trade>> getLatestPublicTrades(AssetClass assetClass, String symbol) {
return Mono.empty();
}
private Mono>> getInstrumentsByType(Credentials credentials){
return handleResponse(client.getMany(authorized(credentials),"/instruments", Instrument.class)).map(
instruments -> instruments.stream().collect(Collectors.groupingBy(Instrument::getType))
);
}
@Override
public Mono extends Map>> getTickers(Credentials credentials) {
return getInstrumentsByType(credentials);
}
public Mono>> getInstruments(List credentials) {
// Convert the list of credentials to a Flux of Monos
Flux>> > monos = Flux.fromIterable(credentials)
.map(this::getInstrumentsByType);
// Zip the Monos together and merge the results
return Flux.concat(monos)
.reduce((map1, map2) -> {
map2.forEach((key, value) ->
map1.merge(key, value, (list1, list2) -> {
list1.addAll(list2);
return list1;
})
);
return map1;
});
}
@Override
public Mono>> getTickers() {
return Mono.error(new IllegalStateException("B2C2 requires credentials to get tickers"));
}
@Override
public Exchange getExchange() {
return Exchange.B2C2;
}
@Override
public AssetClass[] getSupportedAssetClasses() {
return new AssetClass[]{AssetClass.SPOT, AssetClass.CFD};
}
@Override
public Mono placeOrder(Credentials credentials, OrderRequest order) {
return handleErrorResponse(client.postAsJson(authorized(credentials), "/order/", order, OrderResponse.class));
}
//only FOK and MKT orders are supported, hence no partial fills, no cancels
@Override
public Mono extends Ack> cancelOrder(Credentials credentials, CancelRequest request) {
return Mono.error(new CxtlApiException("B2C2 doesn't support order cancellation", new UnsupportedOperationException()));
}
@Override
public Mono> getOrders(Credentials credentials, OrderQueryRequest orderQueryRequest) {
return handleResponse(client.getMany(authorized(credentials), "/order", orderQueryRequest, OrderResponse.class));
}
public Mono getBalances(Credentials credentials) {
return handleResponse(client.get(authorized(credentials), "/balance", UserAssets.class));
}
public Mono> getPositions(Credentials credentials) {
return handleResponse(client.getMany(authorized(credentials), "/cfd/open_positions", OpenPositions.class));
}
public Mono getMarginRequirement(String currency, Credentials credentials) {
return handleResponse(client.get(authorized(credentials), "/margin_requirements?currency=" + currency, MarginRequirement.class));
}
@Override
public Mono getOrder(Credentials credentials, OrderQueryRequest orderQueryRequest) {
String path = "/order";
Mono> result;
if (orderQueryRequest.getOrderId() != null){
result = handleResponse(client.getMany(authorized(credentials), path + "/" + orderQueryRequest.getOrderId(),OrderResponse.class));
} else if (orderQueryRequest.getClientOrderId() != null){
result = handleResponse(client.getMany(authorized(credentials), path + "/" + orderQueryRequest.getClientOrderId(), OrderResponse.class));
} else {
result = handleResponse(client.getMany(authorized(credentials), path, orderQueryRequest, OrderResponse.class));
}
return result.map(l -> l == null || l.isEmpty() ? null : l.get(0));
}
public Mono rfq(Credentials credentials, RFQRequest rfqRequest) {
return handleErrorResponse(client.postAsJson(authorized(credentials), "/request_for_quote/", rfqRequest, RFQResponse.class));
}
@NonNull
private Mono onError(Throwable t) {
if(t instanceof CxtlApiException e){
return Mono.error(e);
}
if(t instanceof CxtlEventException e){
metricsLogger.finishedError(e.getEvent());
ErrorResponse response = client.getCodec().quietFromJson(e.getEvent().getResponseJson(), ErrorResponse.class);
if (response != null){
CxtlApiException concatMsg = checkForErrors(response);
if (concatMsg != null) return Mono.error(concatMsg);
}
}
return Mono.error(new CxtlApiException(t.getMessage(), "UNKNOWN", ErrorCode.UNKNOWN_ERROR));
}
@Nullable
private static CxtlApiException checkForErrors(ErrorResponse response) {
if (response.getErrors() != null && !response.getErrors().isEmpty()){
var concatMsg = response.getErrors().stream().map(e -> e.getMessage()+"("+e.getField()+")").collect(Collectors.joining());
var firstMessage = response.getErrors().get(0);
return new CxtlApiException(concatMsg, Integer.toString(firstMessage.getCode()), errorCode(firstMessage.getCode(), firstMessage.getField()));
}
return null;
}
private static ErrorCode errorCode(int code, String field) {
if (code == 1100 && Objects.equals(field, "quantity")){
return ErrorCode.INVALID_QTY;
}
return switch (code) {
case 1000, 1100 -> ErrorCode.UNKNOWN_ERROR;
case 1011, 1502, 1012 -> ErrorCode.INSUFFICIENT_BALANCE;
case 1006, 1010, 1019, 1026, 1501, 1015 -> ErrorCode.INVALID_QTY;
case 1005, 1009, 1600 -> ErrorCode.BAD_PX;
case 1001, 1003, 1004, 1007, 1002, 1022, 1025, 1601 -> ErrorCode.INVALID_SYMBOL;
case 1021, 1030, 1031 -> ErrorCode.UNKNOWN_ORDER;
case 1101 -> ErrorCode.INVALID_ARGUMENT;
default -> ErrorCode.UNKNOWN_ERROR;
};
}
private Mono handleErrorResponse(Mono> response){
return response.handle((e, sink) -> {
CxtlApiException concatMsg = checkForErrors(e.getResponse());
if(concatMsg != null) {
metricsLogger.finishedError(e);
sink.error(concatMsg);
return;
}
metricsLogger.finishedSuccess(e);
sink.next(e.getResponse());
}).onErrorResume(this::onError);
}
private Mono handleResponse(Mono> response){
return response.map( e ->{
metricsLogger.finishedSuccess(e);
return e.getResponse();
}).onErrorResume(this::onError);
}
}