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.bybit.api.v5.lib.BybitRestClient Maven / Gradle / Ivy
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 extends List extends Trade>> 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 extends List extends Olhcv>> 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 extends BybitOrder> 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;
};
}
}