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

estonlabs.cxtl.exchanges.bybit.api.v5.lib.BybitRestClient Maven / Gradle / Ivy

There is a newer version: 1.4.14
Show newest version
package estonlabs.cxtl.exchanges.bybit.api.v5.lib;


import estonlabs.cxtl.common.auth.Credentials;
import estonlabs.cxtl.common.exception.CxtlApiException;
import estonlabs.cxtl.common.exception.ErrorCode;
import estonlabs.cxtl.common.http.Event;
import estonlabs.cxtl.common.http.JsonRestClient;
import estonlabs.cxtl.common.http.MetricsLogger;
import estonlabs.cxtl.common.http.RestClient;
import estonlabs.cxtl.common.security.HmacUtils;
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.bybit.api.v5.domain.request.*;
import estonlabs.cxtl.exchanges.bybit.api.v5.domain.response.*;
import estonlabs.cxtl.exchanges.bybit.api.v5.domain.types.Category;
import okhttp3.Request;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;

import java.time.Instant;
import java.util.List;
import java.util.Map;

import static estonlabs.cxtl.exchanges.bybit.api.v5.domain.request.OrderQueryRequest.OPEN_ONLY;

public class BybitRestClient implements Cex, ExchangeDataInterface {
    private static final Logger LOGGER = LoggerFactory.getLogger(BybitRestClient.class);
    private final RestClientAdapter client;

    public BybitRestClient(JsonRestClient client, MetricsLogger metricsLogger,
                           String receiveWindow, String brokerId) {

        this.client = new RestClientAdapter(client,metricsLogger,receiveWindow , brokerId);
    }

    @Override
    public Exchange getExchange() {
        return Exchange.BYBIT;
    }


    @Override
    public Mono> getLatestPublicTrades(AssetClass assetClass, String symbol) {
        return getLatestPublicTrades(assetClass, symbol, null);
    }

    public Mono> getLatestPublicTrades(AssetClass assetClass, String symbol, Integer limit) {
        TradeQueryRequest req = TradeQueryRequest.builder()
                .symbol(symbol)
                .category(Category.from(assetClass))
                .limit(limit).build();
        return client.get("/market/recent-trade",req, LatestTradesResponse.class)
                .map(LatestTrades::getList)
                .switchIfEmpty(Mono.just(List.of()));
    }
    @Override
    public AssetClass[] getSupportedAssetClasses() {
        return new AssetClass[]{AssetClass.SPOT, AssetClass.PERP, AssetClass.PERP_INVERSE};
    }

    @Override
    public Mono> getOlhcv(Object request) {
        return Mono.empty();
    }

    @Override
    public Mono>> getTickers() {
       return Mono.zip(getTickers(AssetClass.SPOT),
                       getTickers(AssetClass.PERP),
                       getTickers(AssetClass.PERP_INVERSE))
                .map(tuple -> Map.of(
                    AssetClass.SPOT, tuple.getT1(),
                    AssetClass.PERP, tuple.getT2(),
                    AssetClass.PERP_INVERSE, tuple.getT3()
                ));
    }
    public Mono> getTickers(AssetClass assetClass) {
        TickerRequest req = TickerRequest.builder()
                .category(Category.from(assetClass)).build();
        return client.get("/market/instruments-info",req, TickerListResponse.class)
                .map(ListResponse::getList)
                .switchIfEmpty(Mono.just(List.of()))
                .doOnError(e -> LOGGER.error("Failed to get tickers", e));
    }

    @NotNull
    @Override
    public Mono placeOrder(@NotNull Credentials credentials, @NotNull OrderRequest order) {
        return client.post(credentials,"/order/create", order, AckResponse.class);
    }

    @NotNull
    @Override
    public Mono cancelOrder(@NotNull Credentials credentials, @NotNull CancelRequest request) {
        return client.post(credentials,"/order/cancel", request, AckResponse.class);
    }

    @NotNull
    @Override
    public Mono> getOrders(@NotNull Credentials credentials, OrderQueryRequest orderQueryRequest) {
        return fetchOrders(credentials, orderQueryRequest)
                .map(OrderList::getList)
                .switchIfEmpty(Mono.just(List.of()));
    }

    @Override
    public Mono getOrder(Credentials credentials, OrderQueryRequest orderQuery) {
        return getOrders(credentials,orderQuery).map(orders -> orders.isEmpty()?null:orders.get(0));
    }

    /**
     * Orders from the past 10 mins
     * @param credentials - who is making the request
     * @param query - the orders to query
     * @return - all orders in scope
     */
    public Mono getRecentOrders(Credentials credentials, OrderQueryRequest query) {
        return fetchOrders(credentials, query,"/order/realtime")
                .onErrorResume(e -> Mono.empty())
                .filter(l -> l != null && !l.getList().isEmpty())
                .switchIfEmpty(Mono.empty());
    }

    public Mono getHistoricalOrders(Credentials credentials, OrderQueryRequest query) {
        return fetchOrders(credentials, query,"/order/history");
    }

    /**
     * It will first try recent history (past 10 mins), if anything at all is returned then it will not try historical.
     * If recent returns nothing then historical is called
     * @param credentials - who is making the request
     * @param query - the orders to query
     * @return - all orders in scope
     */
    public Mono fetchOrders(Credentials credentials, OrderQueryRequest query) {
        return getRecentOrders(credentials, query)
                .switchIfEmpty(Mono.defer(()->{
                    if(query.getOpenOnly() != null && query.getOpenOnly().equals(OPEN_ONLY)){
                        return Mono.empty();
                    }
                    return getHistoricalOrders(credentials, query);
                }));
    }

    private Mono fetchOrders(Credentials credentials, OrderQueryRequest query, String path) {
        return client.get(credentials, path , query, OrderListResponse.class);
    }

    public Mono getAssets(Credentials credentials, AssetInfoRequest request) {

        return client.get(credentials,"/account/wallet-balance",request, AssetInfoResponse.class);
    }

    public static class RestClientAdapter {
        private final RestClient client;
        private final MetricsLogger metricsLogger;
        private final String receiveWindow;
        private final String brokerId;
        public RestClientAdapter(RestClient client, MetricsLogger metricsLogger, String receiveWindow, String brokerId) {
            this.client = client;
            this.metricsLogger = metricsLogger;
            this.receiveWindow = receiveWindow;
            this.brokerId = brokerId;
        }

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

        public > Mono get(Credentials credentials, String path, IN request,  Class type) {
            return client.get((builder, json) -> addHeaders(credentials, builder, json),
                    path, request, type).flatMap(this::handleResponse);
        }

        public > Mono post(Credentials credentials, String path, IN request,  Class type) {
            return client.postAsJson((builder, json) -> addHeaders(credentials, builder, json),
                    path, request, type).flatMap(this::handleResponse);
        }
        private > Mono handleResponse(Event event) {
            ApiResponse response = event.getResponse();
            int retCode = response.getRetCode();
            if(retCode >0){
                metricsLogger.finishedError(event);
                return Mono.error(new CxtlApiException(response.getRetMsg(),Integer.toString(retCode),errorCode(retCode)));
            }
            return Mono.just(event.getResponse().getResult()).doFinally( v->  metricsLogger.finishedSuccess(event));
        }

        public Request.Builder addHeaders(Credentials credentials, Request.Builder builder, String message) {
            long now = Instant.now().toEpochMilli();
            String signature = sign(credentials,message, now);
            if(brokerId != null){
                builder.addHeader("Referer", brokerId);
            }

            builder
                .addHeader("X-BAPI-API-KEY", credentials.getApiKey())
                .addHeader("X-BAPI-SIGN", signature)
                .addHeader("X-BAPI-SIGN-TYPE", "2")
                .addHeader("X-BAPI-TIMESTAMP", Long.toString(now))
                .addHeader("Content-Type", "application/json");

                if(receiveWindow != null){
                    builder.addHeader("X-BAPI-RECV-WINDOW", receiveWindow);
                }

            return builder;

        }

        /**
         * The way to generate the sign for POST requests
         * @param message:  input message
         * @param timestamp: timestamp of the request
         * @return signature used to be a parameter in the header
         */
        private String sign(Credentials credentials, String message, long timestamp){
            String sb = timestamp + credentials.getApiKey() + receiveWindow + message;
            return HmacUtils.sign(credentials, sb);
        }
    }
    private static ErrorCode errorCode(int code) {
        return switch (code) {
            case 170131 -> ErrorCode.INSUFFICIENT_BALANCE;
            case 170132, 170133 -> ErrorCode.BAD_PX;
            case 170124, 170135, 170136, 170137, 170140 -> ErrorCode.INVALID_QTY;
            case 170121 -> ErrorCode.INVALID_SYMBOL;
            case 170213,110001 -> ErrorCode.UNKNOWN_ORDER;
            default -> ErrorCode.UNKNOWN_ERROR;
        };
    }
}







© 2015 - 2025 Weber Informatics LLC | Privacy Policy