All Downloads are FREE. Search and download functionalities are using the official Maven repository.

estonlabs.cxtl.exchanges.binance.fapi.lib.BinanceFuturesRestClient Maven / Gradle / Ivy

package estonlabs.cxtl.exchanges.binance.fapi.lib;

import estonlabs.cxtl.common.auth.Credentials;
import estonlabs.cxtl.common.codec.JacksonCodec;
import estonlabs.cxtl.common.exception.CxtlApiException;
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.common.security.HmacUtils;
import estonlabs.cxtl.exchanges.binance.fapi.domain.*;
import lombok.NonNull;
import okhttp3.Request;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;

import static java.lang.Math.abs;

public class BinanceFuturesRestClient{
    private static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
    private final JsonRestClient client;
    private final MetricsLogger metricsLogger;

    private final Long recvWindow;

    public BinanceFuturesRestClient(JsonRestClient client, MetricsLogger metricsLogger, Long recvWindow) {
        this.client = client;
        this.metricsLogger = metricsLogger;
        this.recvWindow = recvWindow;
    }

    public Mono createListenKey(Credentials credentials){
        return client.postEmpty(headers(credentials),"/fapi/v1/listenKey", ListenKey.class).map(this::onSuccess);
    }

    public Mono refreshListenKey(Credentials credentials){
        return client.putEmpty(headers(credentials),"/fapi/v1/listenKey", ListenKey.class).map(this::onSuccess);
    }

    public Mono> getLatestPublicTrades(String symbol) {
        return client.getMany("/fapi/v1/trades",new Symbol(symbol), TradeData.class).map(this::onSuccess);
    }

    public Mono> getTickers() {
        return client.getMany("/fapi/v1/ticker/price", Symbol.class).map(this::onSuccess);
    }

    public Mono placeOrder(Credentials credentials, OrderRequest order) {
        return post(credentials,"/fapi/v1/order", order, Order.class);
    }

    public Mono cancelOrder(Credentials credentials, OrderQuery request) {
        return delete(credentials,"/fapi/v1/order", request, Order.class);
    }


    public Mono> getOrders(Credentials credentials, OrderQuery orderQuery) {
        String path = "/fapi/v1/order";
        if(orderQuery.getOrigClientOrderId() == null && orderQuery.getOrderId() == null){
            path = "/fapi/v1/openOrders";
        }
        return getMany(credentials, path, orderQuery, Order.class);
    }

    public Mono getOrder(Credentials credentials, OrderQuery orderQuery) {
        String path = "/fapi/v1/order";
        if(orderQuery.getOrigClientOrderId() == null && orderQuery.getOrderId() == null){
            path = "/fapi/v1/openOrders";
        }
        return get(credentials, path, orderQuery, Order.class);
    }


    public Request.Builder addHeaders(Credentials credentials, Request.Builder builder) {
        return builder.addHeader("Content-Type", CONTENT_TYPE).addHeader("X-MBX-APIKEY", credentials.getApiKey());
    }


    public  Mono get(Credentials credentials, String path, IN request,  Class type) {
        return client.get(headers(credentials),
                path, sign(credentials, request), type).flatMap(this::handleResponse);
    }

    public  Mono> getMany(Credentials credentials, String path, IN request,  Class type) {
        return client.getMany(headers(credentials), path, sign(credentials, request), type)
                .map(this::onSuccess);
    }

    public  Mono delete(Credentials credentials, String path, IN request, Class type) {
        return client.deleteAsParams(headers(credentials),
                path, sign(credentials, request), type).flatMap(this::handleResponse);
    }

    public  Mono post(Credentials credentials, String path, IN request, Class type) {
         return client.postAsParams(headers(credentials),
                path, sign(credentials, request), type).flatMap(this::handleResponse);
    }

    @NonNull
    private HeaderBuilder headers(Credentials credentials) {
        return (builder, json) -> addHeaders(credentials, builder);
    }

    private  IN sign(Credentials credentials, IN request) {
        if(request== null) return null;

        request.setRecvWindow(recvWindow);
        final Map map = client.getCodec().toMap(request);
        final String strMessage = JacksonCodec.mapToStringBuilder(map,new StringBuilder()).toString();
        String signature = HmacUtils.sign(credentials, strMessage);
        request.setSignature(signature);
        return request;
    }

    private  Mono handleResponse(Event event) {
        OUT response = event.getResponse();
        if(response.getCode() != null && response.getCode() != 0){
            metricsLogger.finishedError(event);
            return Mono.error(new CxtlApiException(response.getMsg(),Integer.toString(response.getCode()),errorCode(response.getCode())));
        }
        return Mono.just(response).doFinally( v->  metricsLogger.finishedSuccess(event));
    }

    private static ErrorCode errorCode(int code) {
        code = abs(code);
        return switch (code) {
            case 2018, 2019 -> ErrorCode.INSUFFICIENT_BALANCE;
            case 4001, 4002,4016 -> ErrorCode.BAD_PX;
            case 1111,2024, 4003, 4004, 4005,4164 -> ErrorCode.INVALID_QTY;
            case 1121, 1122, 1126 -> ErrorCode.INVALID_SYMBOL;
            case 2013,4015,2011 -> ErrorCode.UNKNOWN_ORDER;
            default -> ErrorCode.UNKNOWN_ERROR;
        };
    }

    private  I onSuccess(Event e) {
        metricsLogger.finishedSuccess(e);
        return e.getResponse();
    }
}